Skip to content

Configuration

import { RyanlinkManager } from 'ryanlink';
const manager = new RyanlinkManager({
// ── Required ──────────────────────────────────────────────────────────────
nodes: [
{
id: 'main',
host: 'localhost',
port: 2333,
authorization: 'youshallnotpass',
secure: false,
retryAmount: 5,
retryDelay: 10000,
retryTimespan: -1,
heartBeatInterval: 30000,
closeOnError: true,
enablePingOnStatsCheck: true,
autoChecks: {
pluginValidations: true,
sourcesValidations: true,
},
}
],
sendToShard: (guildId, payload) => {
// Send gateway payload to the correct shard
const totalShards = client.ws.shards.size || 1;
const shardId = Number((BigInt(guildId) >> 22n) % BigInt(totalShards));
const shard = client.ws.shards.get(shardId);
if (shard) shard.send(payload);
else client.guilds.cache.get(guildId)?.shard?.send(payload);
},
client: {
id: 'your-bot-id',
username: 'YourBot',
},
// ── Session Resuming ──────────────────────────────────────────────────────
resuming: {
enabled: true,
timeout: 60000,
},
// ── Player Defaults ───────────────────────────────────────────────────────
playerClass: CustomPlayer, // optional custom player class
autoSkip: true, // auto-skip on track end
autoSkipOnResolveError: true, // skip unresolvable tracks
trackResolveRetryLimit: 3, // global resolve retry limit
emitNewSongsOnly: false, // only emit trackStart for new songs
playerOptions: {
defaultSearchPlatform: 'ytsearch',
useUnresolvedData: false,
clientBasedPositionUpdateInterval: 100,
volumeDecrementer: 1,
applyVolumeAsFilter: false,
allowCustomSources: false,
requesterTransformer: (requester) => requester,
onDisconnect: {
destroyPlayer: true,
autoReconnect: false,
autoReconnectOnlyWithTracks: false,
},
onEmptyQueue: {
destroyAfterMs: undefined,
autoPlayFunction: async (player, lastTrack) => {
// your autoplay logic, or use the built-in:
// await Autoplay.defaultAutoplay(player, lastTrack);
},
},
autoplayConfig: {
enabled: true,
defaultSource: 'ytsearch',
limit: 1,
minDuration: 20000,
maxDuration: 900000,
durationTolerance: 90000,
historyLimit: 20,
prefetchThreshold: 1,
excludeKeywords: ['nightcore', 'bass boosted', '8d audio', 'slowed', 'reverb', 'bass boost', 'pitch shift', 'speed up', 'sped up'],
},
minAutoPlayMs: 10000,
enforceSponsorBlockRequestForEventEnablement: true,
trackResolveRetryLimit: 3,
onTrackStart: null,
onQueueEnd: null,
onNodeFailover: null,
},
// ── Queue ─────────────────────────────────────────────────────────────────
queueOptions: {
maxPreviousTracks: 25,
queueChangesWatcher: null,
},
// ── Links ─────────────────────────────────────────────────────────────────
linksAllowed: true,
linksWhitelist: [],
linksBlacklist: [],
// ── Advanced ──────────────────────────────────────────────────────────────
advancedOptions: {
enableDebugEvents: false,
maxFilterFixDuration: 600000,
debugOptions: {
logCustomSearches: false,
noAudio: false,
playerDestroy: {
dontThrowError: false,
debugLog: false,
},
},
},
});

| Option | Type | Default | Description | | :--- | :--- | :--- | :--- | | id | string | host:port | Unique node identifier | | host | string | — | Lavalink host | | port | number | — | Lavalink port (1–65535) | | authorization | string | — | Lavalink password | | secure | boolean | false | Use WSS/HTTPS (port must be 443) | | sessionId | string | — | Manual session ID for resuming | | regions | string[] | [] | Voice regions this node serves (lowercased automatically) | | nodeType | 'Core' \| 'NodeLink' | 'Core' | Node type — use 'NodeLink' for NodeLink servers | | retryAmount | number | 5 | Max reconnect attempts | | retryDelay | number | 10000 | Base delay (ms) between reconnect attempts (exponential backoff) | | retryTimespan | number | -1 | Window (ms) to count retries; -1 = unlimited | | requestSignalTimeoutMS | number | 10000 | Abort timeout for REST requests | | heartBeatInterval | number | 30000 | WebSocket heartbeat interval (ms) | | closeOnError | boolean | true | Close socket on error and force reconnect | | enablePingOnStatsCheck | boolean | true | Trigger heartbeat on every stats event | | autoChecks.pluginValidations | boolean | true | Validate plugin names before requests | | autoChecks.sourcesValidations | boolean | true | Validate source managers before requests |


| Option | Type | Default | Description | | :--- | :--- | :--- | :--- | | defaultSearchPlatform | string | 'ytsearch' | Default search prefix for plain queries | | useUnresolvedData | boolean | false | Prefer unresolved track metadata over resolved | | clientBasedPositionUpdateInterval | number | 100 | Client-side position update interval (ms) | | volumeDecrementer | number | 1 | Multiplier applied to internal volume (e.g. 0.75 = 75% max) | | applyVolumeAsFilter | boolean | false | Apply volume as a filter instead of player volume | | allowCustomSources | boolean | false | Skip source validation for unknown search prefixes | | minAutoPlayMs | number | 10000 | Minimum ms between autoplay triggers | | trackResolveRetryLimit | number | 3 | Per-player resolve retry limit | | enforceSponsorBlockRequestForEventEnablement | boolean | true | Auto-call setSponsorBlock on track start | | onDisconnect.destroyPlayer | boolean | true | Destroy player on voice disconnect | | onDisconnect.autoReconnect | boolean | false | Auto-reconnect on voice disconnect | | onDisconnect.autoReconnectOnlyWithTracks | boolean | false | Only reconnect if queue has tracks | | onEmptyQueue.destroyAfterMs | number | undefined | Destroy player N ms after queue empties | | onTrackStart | function \| null | null | Per-player hook called on every track start | | onQueueEnd | function \| null | null | Per-player hook called when queue empties | | onNodeFailover | function \| null | null | Per-player hook called when node changes |


const player = manager.createPlayer({
guildId: '123456789',
voiceChannelId: '987654321',
textChannelId: '111222333', // optional
selfDeaf: true,
selfMute: false,
volume: 100, // 0–1000
node: 'main', // optional: pin to specific node id
vcRegion: 'us-east', // optional: prefer nodes in this region
autoplay: false,
recentHistory: [], // optional: pre-populate history buffer
customData: {}, // optional: arbitrary key-value data
trackResolveRetryLimit: 3,
smartLeave: false, // destroy player when voice channel empties
autoPause: false, // pause when all users leave, resume when they return
autoPauseOnMute: false, // pause when bot is muted
onTrackStart: (p, track) => {},
onQueueEnd: (p) => {},
onNodeFailover: (p, from, to) => {},
});
await player.connect();

// Basic search via manager
const result = await manager.search({ query: 'never gonna give you up', source: 'ytsearch' }, requester);
// Via player (also supports bandcamp local engine)
const result2 = await player.search('spsearch:daft punk', requester);
// LavaSearch (requires lavasearch-plugin)
const rich = await player.audioSearch({
query: 'daft punk',
source: 'spsearch',
types: ['track', 'album', 'artist'],
}, requester);

| Option | Type | Default | Description | | :--- | :--- | :--- | :--- | | maxPreviousTracks | number | 25 | Size of the previous tracks buffer | | queueChangesWatcher | QueueChangesWatcher \| null | null | Callback interface for queue mutations |

Implement this interface to react to queue changes:

const manager = new RyanlinkManager({
queueOptions: {
queueChangesWatcher: {
tracksAdd(guildId, tracks, position, oldQueue, newQueue) {
console.log(`[${guildId}] Added ${tracks.length} track(s) at position ${position}`);
},
tracksRemoved(guildId, tracks, position, oldQueue, newQueue) {
console.log(`[${guildId}] Removed ${tracks.length} track(s) from position ${position}`);
},
shuffled(guildId, oldQueue, newQueue) {
console.log(`[${guildId}] Queue shuffled`);
},
},
},
});

Always implement sendToShard correctly for your Discord library. Without it, Ryanlink cannot communicate with Discord’s voice servers.