Back to Blog
TutorialMarch 2, 202510 min read

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.

Share:

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:

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.

  • 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.js
  • Questions? Join our Discord and we'll help you get set up.

    ---

    Related reading:

    Ready to Build?

    Get your API key and start building with esports data in minutes.