Self-host and scale Fortnite lobby bots for Bot Lobbies with the official FNLB Node.js / Bun SDK.
npm install fnlb@latest
# or
bun install fnlb@latestYou need Node.js 22+ or Bun, and an API token from your FNLB account.
Create a client, then call start() with your token and the category that owns the bot.
import FNLB from 'fnlb';
const fnlb = new FNLB();
await fnlb.start({
token: 'your-api-token', // https://app.fnlb.net/account
categories: ['your-category-id'] // bot page → About this bot → Category ID
});When you're done:
await fnlb.stop();start() accepts token (API Token or OAuth2 access token):
| Credential | Prefix | Where to get it |
|---|---|---|
| API token | FNLB_… |
app.fnlb.net/account |
| OAuth2 access token | FNLBOA2AT_… |
Needs bots.run, categories.read, and bots.read (see OAuth2 docs) |
await fnlb.start({
token: 'FNLBOA2AT_...',
categories: ['your-category-id']
});botsPerShard is the max number of bots that can run inside a single shard (subprocess):
import FNLB from 'fnlb';
const fnlb = new FNLB();
await fnlb.start({
token: 'your-api-token',
categories: ['your-category-id'],
botsPerShard: 10
});Each shard is its own subprocess. Multiply shards × bots-per-shard for your capacity ceiling:
import FNLB from 'fnlb';
const fnlb = new FNLB();
await fnlb.start({
token: 'your-api-token',
categories: ['your-category-id'],
numberOfShards: 2,
botsPerShard: 10 // up to 20 bots total
});Pass one or more category IDs. Find them on app.fnlb.net/bots → select a bot → About this bot → Category ID.
await fnlb.start({
token: 'your-api-token',
categories: ['category-id-1', 'category-id-2'],
numberOfShards: 2,
botsPerShard: 10
});Pass exact bot IDs from the same About this bot panel (FNLB ID):
await fnlb.start({
token: 'your-api-token',
bots: ['bot-id-1', 'bot-id-2'],
botsPerShard: 2
});categories and bots are include lists:
- Categories only - bots in those categories, plus unassigned bots
- Bots only - only the listed IDs
- Both - union of category bots and listed bot IDs
- Neither - your full bot pool
Invalid IDs are ignored. Multiple shards can share the same bots list; the gateway assigns each ID to one shard.
await fnlb.start({
token: 'your-api-token',
categories: ['category-id-1', 'category-id-2'],
bots: ['bot-id-1', 'bot-id-2'],
numberOfShards: 2,
botsPerShard: 10
});Omit categories (or pass []) when you only want specific bot IDs from any category. If categories is set, bots without a category are still included.
After start(), you can change which categories/bots the running shards may use without calling stop().
Optional second argument: a partial category config applied to those ids (same merge rules as overrideCategoryConfig).
import FNLB from 'fnlb';
import { CategoryConfigPartyPrivacy } from '@fnlb-project/shared/types';
const fnlb = new FNLB();
await fnlb.start({
apiToken: 'your-api-token',
categories: ['category-id-1'],
botsPerShard: 10
});
// Expand the include list
fnlb.addCategories(['category-id-2'], {
privacy: CategoryConfigPartyPrivacy.Private
});
fnlb.addBots(['bot-id-1'], {
acceptFriendRequests: false
});
// Replace the include list
fnlb.setCategories(['category-id-2', 'category-id-3']);
fnlb.setBots(['bot-id-1', 'bot-id-2'], {
statusText: ['Custom status']
});
// Clear (back to unrestricted)
fnlb.setCategories();
fnlb.setBots();
// Shrink it (bots that no longer match are stopped; capacity refills from the new pool)
fnlb.removeCategories(['category-id-1']);
fnlb.removeBots(['bot-id-1']);
// Current include lists (`undefined` = unrestricted for that dimension)
fnlb.getCategories();
fnlb.getBots();Rules:
undefinedmeans unrestricted for that dimension.add*on an unrestricted dimension starts an include list (narrows from “all”).add*on an existing list unions and dedupes.set*replaces the list (undefined/ omitted = unrestricted).remove*on unrestricted is a no-op.- Removing until empty returns that dimension to unrestricted.
- Overrides passed to
add*/set*are stored per id and removed withremove*(or whenset*drops that id). - Throws if no shards are running.
Change a few category options for this run without editing the dashboard.
overrideCategoryConfig is scoped. Merge order (later wins): FNLB config → all → matching category → matching bot.
import FNLB from 'fnlb';
import { CategoryConfigPartyPrivacy } from '@fnlb-project/shared/types';
const fnlb = new FNLB();
await fnlb.start({
apiToken: 'your-api-token',
overrideCategoryConfig: {
// every bot
all: {
acceptFriendRequests: false
},
// bots in this category
categories: {
'category-id-1': {
privacy: CategoryConfigPartyPrivacy.Private
}
},
// one specific bot
bots: {
'bot-id-1': {
statusText: ['Special bot']
}
}
}
});Overrides stay applied even if the category config is updated live.
See CATEGORY_CONFIG.md for every supported key and type.
Useful when you run more than one host and want clear logs / gateway labels:
import FNLB from 'fnlb';
const fnlb = new FNLB({ clusterName: 'MyAwesomeCluster' });
await fnlb.start({
token: 'your-api-token'
});import FNLB from 'fnlb';
const fnlb = new FNLB();
async function startFNLB() {
await fnlb.start({
token: 'your-api-token',
categories: ['your-category-id'],
numberOfShards: 1,
botsPerShard: 5
});
}
async function restartFNLB() {
console.log('Restarting FNLB...');
await fnlb.stop();
await startFNLB();
}
await startFNLB();
setInterval(restartFNLB, 3_600_000); // every hourPrefer a ready-made env-based launcher? Use the Self-Hosted template.
| Option | Description |
|---|---|
clusterName |
Label for this cluster in logs / gateway |
fnlbPath |
Directory for the .fnlb download cache (default: current working directory) |
channel |
Default release channel: stable | beta | dev |
updateIntervalMs |
How often to check for package updates |
maxDownloadRetries / maxBackoffMs |
Download retry behavior |
onLogMessage / onSubProcessLogMessage |
Log callbacks |
disableLogs / disableErrorLogs |
Mute SDK logs |
disableSubProcessLogs / disableSubProcessErrorLogs |
Mute shard stdout/stderr mirroring |
| Option | Description |
|---|---|
token |
Auth credential (one required) |
categories |
Category ID include list |
bots |
Bot ID include list |
numberOfShards |
Number of subprocesses (default 1) |
botsPerShard |
Max bots per shard (default 1) |
overrideCategoryConfig |
Scoped partial category config for this run - see CATEGORY_CONFIG.md |
channel |
Release channel for this run |
logLevel |
'INFO' or 'DEBUG' (LogLevel enum exported) |
hideUsernames / hideEmails |
Redact PII in shard logs |
extraEnv |
Extra env vars passed into each shard process |
