Build a League of Legends Esports Discord Bot in 10 Minutes
Create a Discord bot that shows live LCS/LEC/LCK scores, player stats, and team rosters using the Cito API. Full code included.
Introduction
Discord is where LoL fans live. Whether it's your friend group watching LCS together, your fantasy league server, or a community of 10,000 fans — having a bot that pulls live match data, player stats, and standings makes your server way more engaging.
In this tutorial, you'll build a Discord bot that can:
- Show live LCS, LEC, and LCK match scores
- Look up any pro player's stats
- Display current league standings
- Show team rosters and head-to-head records
- Alert your server when matches go live
And it takes about 10 minutes.
Try the API First
Before building the bot, test the data you'll be working with:
curl -H "Authorization: Bearer pk_demo_cito_live_a06c129e0a9ce1c39c3035bd187541c4" \
https://api.citoapi.com/api/v1/lol/live
Demo key: 50 calls/day. Get your free key for 500 calls/month in production.
Prerequisites
You need:
- Node.js 18+
- A Discord bot token (create one at discord.dev)
- A Cito API key (sign up free)
Project Setup
mkdir lol-discord-bot
cd lol-discord-bot
npm init -y
npm install discord.js axios dotenv
Create .env:
DISCORD_TOKEN=your_discord_bot_token
CITO_API_KEY=your_cito_api_key
The Bot: Core Setup
// index.js
const { Client, GatewayIntentBits, EmbedBuilder } = require('discord.js');
const axios = require('axios');
require('dotenv').config();
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
],
});
const api = axios.create({
baseURL: 'https://api.citoapi.com/api/v1/lol',
headers: { 'Authorization': Bearer ${process.env.CITO_API_KEY} },
});
client.on('ready', () => {
console.log(Logged in as ${client.user.tag});
});
Command: Live Matches
Show what's currently being played across all leagues:
client.on('messageCreate', async (message) => {
if (message.author.bot) return;
// !lol live
if (message.content === '!lol live') {
try {
const { data } = await api.get('/live');
if (!data.data || data.data.length === 0) {
message.reply('No live matches right now. Check back during broadcast hours!');
return;
}
const embed = new EmbedBuilder()
.setTitle('Live LoL Esports Matches')
.setColor(0x00E5CC)
.setTimestamp();
data.data.forEach(match => {
embed.addFields({
name: [${match.league}] ${match.team1.name} vs ${match.team2.name},
value: Score: ${match.team1.score} - ${match.team2.score} | Game ${match.currentGame} | ${match.status},
});
});
message.reply({ embeds: [embed] });
} catch (err) {
message.reply('Error fetching live matches. Try again in a moment.');
}
}
});
Command: Player Stats
Look up any pro player by name:
// !lol player faker
if (message.content.startsWith('!lol player ')) {
const playerId = message.content.slice(12).trim().toLowerCase();
try {
const { data } = await api.get(/players/${playerId}/stats);
const p = data.data;
const embed = new EmbedBuilder()
.setTitle(${p.name} - ${p.team})
.setDescription(${p.role} | ${p.nationality})
.setColor(0x00E5CC)
.addFields(
{ name: 'KDA', value: p.kda?.toString() || 'N/A', inline: true },
{ name: 'CS/min', value: p.csPerMin?.toString() || 'N/A', inline: true },
{ name: 'DPM', value: p.damagePerMin?.toString() || 'N/A', inline: true },
{ name: 'Win Rate', value: ${p.winRate || 'N/A'}%, inline: true },
{ name: 'Games', value: p.gamesPlayed?.toString() || 'N/A', inline: true },
{ name: 'Vision', value: p.visionScore?.toString() || 'N/A', inline: true },
)
.setTimestamp();
message.reply({ embeds: [embed] });
} catch (err) {
message.reply(Player "${playerId}" not found. Try the exact pro name (e.g., faker, chovy, caps).);
}
}
Command: League Standings
Show current standings for any league:
// !lol standings lcs
if (message.content.startsWith('!lol standings ')) {
const leagueId = message.content.slice(15).trim().toLowerCase();
try {
const { data } = await api.get(/leagues/${leagueId}/standings);
const embed = new EmbedBuilder()
.setTitle(${leagueId.toUpperCase()} Standings)
.setColor(0x00E5CC)
.setTimestamp();
let description = '';
data.data.forEach((team, i) => {
const medal = i === 0 ? '🥇' : i === 1 ? '🥈' : i === 2 ? '🥉' : ${i + 1}.;
description += ${medal} ${team.name} — ${team.wins}W ${team.losses}L\n;
});
embed.setDescription(description);
message.reply({ embeds: [embed] });
} catch (err) {
message.reply('League not found. Try: lcs, lec, lck, lpl');
}
}
Command: Team Roster
Show a team's current lineup:
// !lol roster cloud9
if (message.content.startsWith('!lol roster ')) {
const slug = message.content.slice(12).trim().toLowerCase();
try {
const { data } = await api.get(/teams/${slug}/roster);
const embed = new EmbedBuilder()
.setTitle(${data.data.teamName} — Current Roster)
.setColor(0x00E5CC);
data.data.players.forEach(player => {
embed.addFields({
name: player.role.toUpperCase(),
value: ${player.name} (${player.realName}),
inline: true,
});
});
message.reply({ embeds: [embed] });
} catch (err) {
message.reply(Team "${slug}" not found. Use the team slug (e.g., cloud9, t1, fnatic).);
}
}
Command: Head-to-Head
Compare two teams' records:
// !lol h2h t1 geng
if (message.content.startsWith('!lol h2h ')) {
const parts = message.content.slice(9).trim().split(' ');
if (parts.length !== 2) {
message.reply('Usage: !lol h2h team1 team2');
return;
}
try {
const { data } = await api.get(/teams/${parts[0]}/h2h/${parts[1]});
const h2h = data.data;
const embed = new EmbedBuilder()
.setTitle(${h2h.team1.name} vs ${h2h.team2.name})
.setDescription(All-time record: ${h2h.team1.wins} - ${h2h.team2.wins})
.setColor(0x00E5CC)
.addFields(
{ name: 'Last 5 Matches', value: h2h.recentResults || 'N/A' },
);
message.reply({ embeds: [embed] });
} catch (err) {
message.reply('Could not find head-to-head data for those teams.');
}
}
Command: Today's Schedule
// !lol today
if (message.content === '!lol today') {
try {
const { data } = await api.get('/schedule/today');
if (!data.data || data.data.length === 0) {
message.reply('No matches scheduled for today.');
return;
}
const embed = new EmbedBuilder()
.setTitle("Today's LoL Esports Matches")
.setColor(0x00E5CC)
.setTimestamp();
data.data.forEach(match => {
embed.addFields({
name: [${match.league}] ${match.time},
value: ${match.team1} vs ${match.team2},
});
});
message.reply({ embeds: [embed] });
} catch (err) {
message.reply('Error fetching schedule.');
}
}
Adding a Help Command
if (message.content === '!lol help') {
const embed = new EmbedBuilder()
.setTitle('LoL Esports Bot Commands')
.setColor(0x00E5CC)
.setDescription([
'!lol live — Show live matches',
'!lol today — Today\'s schedule',
'!lol standings — League standings (lcs, lec, lck, lpl)',
'!lol player — Player stats (faker, caps, chovy)',
'!lol roster — Team roster (cloud9, t1, fnatic)',
'!lol h2h — Head-to-head record',
].join('\n'));
message.reply({ embeds: [embed] });
}
Start the Bot
Add the login call and run it:
client.login(process.env.DISCORD_TOKEN);
node index.js
Your bot should come online. Type !lol help in any channel it has access to.
Deploying for 24/7 Uptime
For production, host on:
- Railway — Free tier, easy deploy from GitHub
- Fly.io — Free tier, globally distributed
- Any VPS — $5/month on DigitalOcean or Hetzner
The bot uses minimal resources (< 50MB RAM).
Adding Caching
Reduce API calls by caching frequently requested data:
const cache = new Map();async function cachedGet(path, ttl = 60000) {
const cached = cache.get(path);
if (cached && Date.now() - cached.time < ttl) return cached.data;
const { data } = await api.get(path);
cache.set(path, { data, time: Date.now() });
return data;
}
// Use it: standings don't change often, cache 5 min
const standings = await cachedGet('/leagues/lcs/standings', 300000);
Next Steps
- Slash commands: Upgrade from
!prefix to Discord's native slash commands for better UX - Match alerts: Use webhooks to notify your server when matches start
- Leaderboard tracking: Weekly KDA/stats leaderboards for your fantasy league
- Champion drafts: Show live pick/ban data during matches
Get Started
The full source code above is everything you need. Copy it, plug in your keys, and you have a working LoL esports bot.
node index.jsQuestions? Join our Discord and we'll help you get set up.
---
Related reading:
- LoL Esports API: Complete Developer Guide — All 102 endpoints explained
- Build a LoL Fantasy Esports App — Fantasy platform with live scoring
- How to Build a Discord Bot with Esports Data — Multi-game Discord bot tutorial
Ready to Build?
Get your API key and start building with esports data in minutes.