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 "x-api-key: cito_live_your_key" \ https://api.citoapi.com/api/v1/lol/live
mkdir lol-discord-botcd lol-discord-botnpm init -ynpm install discord.js axios dotenv
DISCORD_TOKEN=your_discord_bot_tokenCITO_API_KEY=your_cito_api_key
// index.jsconst { 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: { 'x-api-key': process.env.CITO_API_KEY },}); client.on('ready', () => { console.log(`Logged in as ${client.user.tag}`);});
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.'); } }});
// !lol player fakerif (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).`); }}
// !lol standings lcsif (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'); }}
// !lol roster cloud9if (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).`); }}
// !lol h2h t1 gengif (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.'); }}
// !lol todayif (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.'); }}
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>** — League standings (lcs, lec, lck, lpl)', '**!lol player <name>** — Player stats (faker, caps, chovy)', '**!lol roster <team>** — Team roster (cloud9, t1, fnatic)', '**!lol h2h <team1> <team2>** — Head-to-head record', ].join('\n')); message.reply({ embeds: [embed] });}
client.login(process.env.DISCORD_TOKEN);
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 minconst 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.
Get your free API key — 500 calls/month, no credit card
Create a Discord bot at discord.dev
Copy the code above and run node index.jsQuestions? Join our Discord and we'll help you get set up.
---
Related reading: