Getting Started¶
This guide shows the normal setup flow for a new SonoLink bot. It is not a migration guide: it assumes you are starting from a Discord bot and want to connect it to Lavalink for the first time.
The short version is:
Install SonoLink and one supported Discord library.
Run a Lavalink 4 server.
Create a
sonolink.Client.Register at least one Lavalink node.
Start the SonoLink client.
Connect voice channels with
sonolink.Player.
Before you begin¶
SonoLink does not play audio by itself. It connects your bot to a separate Lavalink server, and Lavalink performs the audio loading, decoding, and streaming work.
You need:
Python 3.12 or newer.
One supported Discord library: py-cord, discord.py, disnake, or nextcord.
SonoLink installed with
pip install sonolink.A running Lavalink 4 server.
If you have not installed SonoLink yet, read Installation. If you do not have Lavalink running yet, read Lavalink Setup.
The three main objects¶
Most SonoLink bots use these objects:
sonolink.Clientbelongs to your Discord client and owns the Lavalink connections.sonolink.Noderepresents one Lavalink server.sonolink.Playeris the voice connection used in a Discord guild.
You normally create one sonolink.Client when your bot starts, register one or more
nodes with sonolink.Client.create_node(), then connect voice channels with
sonolink.Player inside commands.
Connection lifecycle¶
SonoLink uses the same general shape as most Lavalink wrappers: a long-lived client owns your Lavalink nodes, and each Discord guild gets a player when the bot joins voice.
The important part is the order:
Create the Discord bot.
Create
sonolink.Clientand attach it to the bot.Register nodes with
sonolink.Client.create_node().Start the SonoLink client once the Discord client is ready.
Connect voice channels with
sonolink.Player.
For most bots, you do not need to manually choose a node when connecting a player. SonoLink will use the client attached to your bot and select a connected node for you.
Creating the SonoLink client¶
Create the SonoLink client after creating your Discord bot. The client should live for as long as your bot process lives.
This example uses Pycord, so the flow may differ for discord.py, Disnake, and Nextcord (see more about that below).
from typing import Any
import discord
import sonolink
class Bot(discord.Bot):
def __init__(self) -> None:
intents = discord.Intents(guilds=True, voice_states=True)
super().__init__(intents=intents)
# The SonoLink client owns your Lavalink nodes and is attached to this bot.
self.sl_client: sonolink.Client[Any] = sonolink.Client(self)
async def on_connect(self) -> None:
await super().on_connect()
# Start opens the registered Lavalink node connections.
await self.sl_client.start()
bot = Bot()
Registering a node¶
A node is SonoLink’s connection information for a Lavalink server. You should register your
node before calling sonolink.Client.start().
bot.sl_client.create_node(
uri="http://localhost:2333",
password="youshallnotpass",
id="main",
)
uri is the address of your Lavalink server. For local development this is usually
http://localhost:2333.
If you prefer keeping the host and port separate, you may pass host and port instead
of uri. This is available since SonoLink 1.1.0:
bot.sl_client.create_node(
host="localhost",
port=2333,
password="youshallnotpass",
id="main",
)
uri and host/port are mutually exclusive. Use whichever style fits your
configuration best.
password must match the lavalink.server.password value in your Lavalink
application.yml.
id is optional, but naming nodes is useful once you have more than one.
Calling sonolink.Client.create_node() registers the node. Calling
sonolink.Client.start() opens the connection to Lavalink. If you skip this step,
sonolink.Player will not have a node to use.
Note
sonolink.Client.start() should be called once your Discord client is ready,
typically in discord.on_connect() (py-cord),
discord.Client.setup_hook() (discord.py), disnake.on_connect()
(disnake), or nextcord.on_connect() (nextcord) rather than in
on_ready.
Connecting a player¶
When a command needs audio, connect to the user’s voice channel with sonolink.Player.
The player will use the SonoLink client attached to your bot and choose a connected node.
@bot.slash_command(name="join", description="Connects the bot to your voice channel.")
async def join(ctx: discord.ApplicationContext) -> None:
if not isinstance(ctx.author, discord.Member):
await ctx.respond("This command can only be used in a server.")
return
if not ctx.author.voice or not ctx.author.voice.channel:
await ctx.respond("You must be in a voice channel.")
return
player = await ctx.author.voice.channel.connect(cls=sonolink.Player)
assert isinstance(player, sonolink.Player)
await ctx.respond("Connected.")
Searching and playing¶
Use sonolink.Client.search_track() to ask Lavalink for a track, then play it with the
guild’s sonolink.Player.
@bot.slash_command(name="play", description="Plays a song.")
@discord.option("query", description="A search query or URL.")
async def play(ctx: discord.ApplicationContext, query: str) -> None:
await ctx.defer()
if not isinstance(ctx.author, discord.Member):
await ctx.respond("This command can only be used in a server.")
return
player = ctx.guild.voice_client if ctx.guild else None
if player is None:
if not ctx.author.voice or not ctx.author.voice.channel:
await ctx.respond("You must be in a voice channel.")
return
player = await ctx.author.voice.channel.connect(cls=sonolink.Player)
if not isinstance(player, sonolink.Player):
await ctx.respond("The bot is already connected with another voice client.")
return
# Ask Lavalink to resolve the user's query through the SonoLink client.
result = await bot.sl_client.search_track(query)
if result.is_error() or result.is_empty() or result.result is None:
await ctx.respond("No tracks found.")
return
# Search results can be a list of tracks, a playlist, or a single track.
data = result.result
if isinstance(data, list):
track = data[0]
elif isinstance(data, sonolink.models.Playlist):
track = data.tracks[0]
else:
track = data
# Queue the selected track on the guild's SonoLink player.
player.queue.put(track)
if not player.current:
# If nothing is playing yet, pull the first queued track and start it.
track = player.queue.get()
await player.play(track)
await ctx.respond(f"Now playing `{track.title}`.")
else:
await ctx.respond(f"Queued `{track.title}`.")
Repository examples¶
The examples/ directory in the GitHub repository contains complete bots for each
supported Discord library. These are useful when you want a working file to copy from instead
of building each command from the guide snippets.
The repository currently includes examples for:
Each framework folder includes a simple playback example, plus examples for settings, filters, events, and more advanced usage.
Moving from another library¶
If you are moving from another Lavalink wrapper, start with the migration guide for that library. Those pages map the old concepts to SonoLink’s client, node, player, queue, search, filter, and event APIs.
Full minimal bot¶
Putting the pieces together:
from typing import Any
import discord
import sonolink
import sonolink.models
class Bot(discord.Bot):
def __init__(self) -> None:
intents = discord.Intents(guilds=True, voice_states=True)
super().__init__(intents=intents)
self.sl_client: sonolink.Client[Any] = sonolink.Client(self)
async def on_connect(self) -> None:
await super().on_connect()
await self.sl_client.start()
bot = Bot()
bot.sl_client.create_node(
uri="http://localhost:2333",
password="youshallnotpass",
id="main",
)
@bot.slash_command(name="play", description="Plays a song.")
@discord.option("query", description="A search query or URL.")
async def play(ctx: discord.ApplicationContext, query: str) -> None:
await ctx.defer()
if not isinstance(ctx.author, discord.Member):
await ctx.respond("This command can only be used in a server.")
return
player = ctx.guild.voice_client if ctx.guild else None
if player is None:
if not ctx.author.voice or not ctx.author.voice.channel:
await ctx.respond("You must be in a voice channel.")
return
player = await ctx.author.voice.channel.connect(cls=sonolink.Player)
if not isinstance(player, sonolink.Player):
await ctx.respond("The bot is already connected with another voice client.")
return
result = await bot.sl_client.search_track(query)
if result.is_error() or result.is_empty() or result.result is None:
await ctx.respond("No tracks found.")
return
data = result.result
if isinstance(data, list):
track = data[0]
elif isinstance(data, sonolink.models.Playlist):
track = data.tracks[0]
else:
track = data
player.queue.put(track)
if not player.current:
track = player.queue.get()
await player.play(track)
await ctx.respond(f"Now playing `{track.title}`.")
else:
await ctx.respond(f"Queued `{track.title}`.")
bot.run("TOKEN")
Where to go next¶
After this guide:
Read Working With Players for queue usage, skipping, pausing, filters, and clean disconnects.
Read Lavalink Setup if your node fails to connect or you need production setup.
Read Filters And Playback State when you want to apply Lavalink audio filters.
Read Migrating to SonoLink if you are moving from another Lavalink wrapper.
Check the repository examples for framework-specific complete bots.
Q&A / Troubleshooting¶
Why does the bot join voice but not play anything?¶
Check that Lavalink is running and that the node uri or host/port values and
password match your Lavalink configuration.
Why does connecting a player fail because no node is available?¶
Make sure you called sonolink.Client.create_node() before
sonolink.Client.start(). Registering a node stores the Lavalink connection details;
starting the client opens the actual connection.
Why do track searches always return empty results?¶
Check your Lavalink source configuration. Source support depends on your Lavalink server and installed plugins. For YouTube support, see the plugin notes in Lavalink Setup.
Check your Lavalink source configuration. Source support depends on your Lavalink server and installed plugins. For YouTube support, see the plugin notes in Lavalink Setup.