Bundle
Learn about bundling and building your Discord bot with djs-core.
Production bots should not ship your entire src/ folder. The djs-core build command scans your project, generates a single entry file, and outputs a deployable artifact in dist/.
Quick start
Add build scripts
In your
package.json:{"scripts": {"dev": "djs-core dev","build": "djs-core build --bundled","start:prod": "bun dist/index.js"}}Build
Terminal window bun run buildIf you omit flags, the CLI asks which build type you want (bundled, external, compile, or Docker).
Run the output
Terminal window bun run start:prodOr directly:
Terminal window bun dist/index.js
Use djs-core dev while developing. Switch to djs-core build when you deploy — faster startup, single file, no TypeScript at runtime.
What happens during a build?
- Scan — all handlers under
src/interactions/commands,src/components/*,src/events, and optionallysrc/cron. - Generate — a production entry at
.djscore/index.tsthat imports every route and wires handlers intoDjsClient. - Bundle — Bun compiles that entry into
dist/(or a native binary with--compile). - Copy assets —
config.jsonis copied or embedded whenexperimental.userConfigis enabled (see below).
The .djscore/ folder is auto-generated. Do not edit it manually — it is recreated on every build.
Build types
| Type | CLI flag / prompt | Output | Best for |
|---|---|---|---|
| Bun (bundled) | --bundled |
dist/index.js (single file) |
Most deployments, VPS, simple hosting |
| Bun (external) | --external |
dist/index.js + package.json |
When you want smaller bundle + bun install in dist/ |
| Compile | --compile / -c |
dist/bot native binary (~90 MB) |
Single executable, no Bun runtime on server |
| Docker | interactive prompt | dist/index.js + Dockerfile |
Container deployments |
Bundled (recommended)
Everything is inlined into one JavaScript file. Fastest to deploy — copy dist/index.js, set env vars, run with Bun.
djs-core build --bundledbun dist/index.jsExternal dependencies
Dependencies from your package.json stay external. A minimal package.json is generated inside dist/.
djs-core build --externalcd dist && bun install && bun startNative binary
Compiles a standalone executable. Great for distributing a bot without installing Bun on the target machine.
djs-core build --compile./dist/bot # Linux/macOS./dist/bot.exe # WindowsCompiled binaries are large (~90 MB) because they embed the Bun runtime. Prefer --bundled if size matters.
Docker
Generates a Dockerfile next to the bundle:
djs-core build# Select "Docker" in the prompt, then:docker build -t my-bot distdocker run --env-file .env my-botCLI reference
djs-core build [options]| Option | Description |
|---|---|
-p, --path <path> |
Project root (default: .) |
-o, --outdir <dir> |
Output folder (default: dist) |
--bundled |
Single-file Bun bundle |
--external |
Bundle with external node_modules |
-c, --compile |
Native executable |
--no-minify |
Disable minification |
--bundled, --external, and --compile are mutually exclusive. Pick one per build.
Examples
# Default interactive pickerdjs-core build
# Production bundle to custom folderdjs-core build --bundled --outdir release
# Compile without minify (debugging)djs-core build --compile --no-minify
# Build a monorepo app subfolderdjs-core build --path ./apps/my-bot --bundledConfiguration for production builds
experimental.bundle + userConfig
If you use a config.json for bot-specific settings (prefixes, feature flags, etc.), enable both flags to embed the JSON inside the bundle at build time:
import { defineConfig } from "@djs-core/runtime";
export default defineConfig({ token: process.env.TOKEN!, servers: ["YOUR_GUILD_ID"], experimental: { userConfig: true, bundle: true, },});With userConfig: true but bundle: false, config.json is copied to dist/ during build and loaded at runtime instead.
Enables loading config.json into client.config. Types are auto-generated in .djscore/config.types.ts.
Embeds config.json into the production bundle. Required for bun build --compile workflows with user config (e.g. Prisma SQLite plugin).
Cron tasks in production
Cron handlers under src/cron/ are only included when experimental.cron is true in djs.config.ts. See Cron Tasks.
Project layout after build
my-bot/├── src/ # your source (not needed at runtime)├── djs.config.ts # needed at build time├── .djscore/│ └── index.ts # generated entry (recreated each build)└── dist/ ├── index.js # bundled output ├── bot # only with --compile ├── Dockerfile # only with Docker build type ├── package.json # only with --external └── config.json # copied when userConfig without bundleDeployment examples
VPS with Bun
bun run buildTOKEN=xxx bun dist/index.jsUse systemd, pm2, or a process manager to keep the bot alive.
Standalone binary
djs-core build --compilescp dist/bot user@server:/opt/my-bot/ssh user@server '/opt/my-bot/bot'Docker
# Generated by djs-core build (Docker option)FROM oven/bun:alpineWORKDIR /appCOPY index.js .CMD ["bun", "index.js"]docker build -t my-discord-bot distdocker run -d --env-file .env --name bot my-discord-botdev vs build vs start
| Command | Loads from | Hot reload | Use case |
|---|---|---|---|
djs-core dev |
src/ (dynamic import) |
Yes | Local development |
djs-core build |
src/ → dist/ |
— | Create production artifact |
bun dist/index.js |
dist/ bundle |
No | Run production build |
djs-core start |
src/ (dynamic import) |
No | Run from source without bundling |
For production, prefer build then bun dist/index.js. djs-core start is useful when your server has the full repo and TypeScript sources, but it does not run the dist/ output.
Troubleshooting
Build fails after adding a handler — make sure every file under src/interactions/commands, src/components, etc. has a valid default export (Command, Button, EventListener, …).
config.json not found at runtime — enable experimental.userConfig, rebuild, and check that config.json exists at the project root before building.
Binary too large — use --bundled instead of --compile, or --external if you need smaller artifacts with separate node_modules.
Plugin not available in bundle — plugins declared in djs.config.ts are resolved at build time. Run djs-core generate-config-types after changing plugins so types and imports stay in sync.