Getting started
The fastest way to see Shardwire working: scaffold, set two secrets, run two commands.
You are two small programs away from a working bot: one starts The Connection on the bot side, the other connects your app and listens for events.
Choose your path
| Path | Best for | How |
|---|---|---|
npm create shardwire → Express Server (default) | New project anywhere; shardwire from npm; TypeScript + tsx + Express (GET /health) | Run npm create shardwire (interactive default is Express Server). Or npm create shardwire my-app, then cd into the folder and follow the printed steps. Template source: express-server on GitHub |
npm create shardwire → React App | Browser dashboard / UI with @shardwire/react | Choose React App in the prompt, or npm create shardwire -t react-vite. Template source: react-vite on GitHub |
Scaffold from npm (golden path)
The default Express Server template is TypeScript entrypoints (src/bot.ts, src/app.ts) run with tsx. The app process starts Express on PORT (default 3000) with GET /health, then connects the bridge client. After npm install, copy .env.example → .env, set DISCORD_TOKEN and SHARDWIRE_SECRET, run npm run register once for the included /hello command, then run npm run bot and npm run app in two terminals. Slash-command registration lives in src/register.ts — see First slash command.
Prerequisites
- Express Server template / any Node app using
connectBotBridge: Node.js 22+ (that app process relies on nativeglobalThis.WebSocket). - React App template: follow your bundler’s runtime floor (the official Vite scaffold uses
^20.19.0 || >=22.12.0). - A Discord bot token and a shared secret string you invent (any long random string).
Create a .env next to the scaffold with DISCORD_TOKEN and the secret variables your template expects. The Express Server scaffold uses one shared SHARDWIRE_SECRET for the Node app process. The React App scaffold instead creates a dedicated browser-scoped secret on the bot side (SHARDWIRE_BROWSER_SECRET) and expects VITE_SHARDWIRE_SECRET plus VITE_SHARDWIRE_SECRET_ID=browser in the browser bundle — see Connection and authentication if the app cannot connect.
Run npm run bot — this starts the bot bridge (Discord + bridge server).
Run npm run app (or npm run dev for Vite) — this connects your app over The Connection and subscribes to events.
Common mistakes (quick checks)
- Secret mismatch — the string in
server.secrets(bot) must equalsecret(app). For the React/Vite scaffold,VITE_SHARDWIRE_SECRETmust matchSHARDWIRE_BROWSER_SECRET, andVITE_SHARDWIRE_SECRET_IDmust match the configured secret id (browserin the official template). See Connection and authentication. - Wrong URL — path is usually
/shardwire; loopbackws://vs remotewss://. See App URL protocol and Non-loopback requires wss. - Bot not running first — start the bot process before the app. See Connection and authentication.
The two tiny programs
Bot side (holds Discord + exposes the bridge)—under 10 lines of core setup:
import { createBotBridge } from 'shardwire';
const bridge = createBotBridge({
token: process.env.DISCORD_TOKEN!,
intents: ['Guilds', 'GuildMessages', 'GuildMembers', 'MessageContent'],
server: { port: 3001, secrets: [process.env.SHARDWIRE_SECRET!] },
});
await bridge.ready();App side (your logic)—also under 10 lines to connect and subscribe:
import { connectBotBridge } from 'shardwire/client';
const app = connectBotBridge({
url: 'ws://127.0.0.1:3001/shardwire',
secret: process.env.SHARDWIRE_SECRET!,
appName: 'my-app',
});
app.on('messageCreate', ({ message }) => console.log(message.content));
await app.ready();If you used npm create shardwire (Express Server template), appName is already set in src/app.ts to match your project’s generated manifest name.
Browser apps should use scoped secrets
If your app runs in the browser, do not expose a broad internal secret. Use object-form server.secrets with an id plus allow derived from generateSecretScope(manifest), then pass that secret value and id from the browser.
Import path on the app
In Vite / browser bundles, always import from shardwire/client so the heavy bot runtime is not pulled into the browser. In Node-only scripts you can still use shardwire/client—it is the same app API.
Migration paths from raw discord.js
If you already have a discord.js codebase, you can migrate incrementally:
- Hybrid mode: keep your existing client logic while adding app-side Shardwire subscriptions/actions.
- Single-process mode: keep bot + app APIs in one process now, split later when you need scale.
- Raw passthrough: call supported
discord.jsmethods throughapp.raw(...)as a break-glass fallback.
import { createBotBridge } from 'shardwire';
const bot = createBotBridge({
mode: 'hybrid',
exposeClient: true,
token: process.env.DISCORD_TOKEN!,
intents: ['Guilds', 'GuildMessages', 'MessageContent'],
raw: { enabled: true, allow: ['guilds.fetch', 'channels.fetch'] },
server: { port: 3001, secrets: [process.env.SHARDWIRE_SECRET!] },
});
await bot.ready();
const client = bot.client(); // optional underlying discord.js Clientimport { createBotBridge } from 'shardwire';
const bridge = createBotBridge({
mode: 'single-process',
token: process.env.DISCORD_TOKEN!,
intents: ['Guilds', 'GuildMessages', 'MessageContent'],
raw: { enabled: true, allow: ['guilds.fetch'] },
});
const app = bridge.app();
await bridge.ready();
if (!app) throw new Error('single-process app bridge unavailable');
await app.raw('guilds.fetch', ['123456789012345678']);What “good” looks like
- The bot terminal prints that the bridge is ready (
bot bridge readyin official templates). - The app terminal prints capabilities (what events and actions you can use), or the Vite page shows ready with capabilities JSON.
- Sending a message your bot can see shows up in the app logs (or UI).
Deep dives (Reference)
When something errors with See: https://shardwire.js.org/docs/troubleshooting#…, open Troubleshooting and jump to that anchor—each one maps to a short explanation plus links back here or to Reference.