How to Build a Discord Card Price Bot in 2026
Step-by-step guide to building a Discord bot that looks up real-time trading card prices using TCG API. Works with Pokemon, Magic, Yu-Gi-Oh!, and 89+ more games.
Discord is the home base for trading card communities. Whether your server is about Pokemon, Magic: The Gathering, Yu-Gi-Oh!, or any other TCG, one question comes up constantly: “How much is this card worth?”
In this tutorial, you’ll build a Discord bot that answers that question instantly. Type !price charizard ex and get back real-time market prices — Normal, Foil, and everything in between. The bot works with all 89+ games supported by TCG API.
What You’ll Build
A Discord bot with these commands:
!price <card name>— Look up a card’s current market price!price <card name> --game magic— Search a specific game!movers— Show the day’s biggest price changes
The bot responds with rich embeds showing card names, set info, and prices for every available printing.
Prerequisites
Before you start, make sure you have:
- Node.js 18+ installed
- A Discord account with access to the Discord Developer Portal
- A TCG API key — sign up free here (100 requests/day on the free tier)
- A Discord server where you have permission to add bots
Step 1: Create a Discord Bot
Head to the Discord Developer Portal and create a new application:
- Click “New Application” and give it a name like “TCG Price Bot”
- Go to the Bot section and click “Add Bot”
- Copy the bot token — you’ll need this shortly
- Under “Privileged Gateway Intents”, enable Message Content Intent
- Go to OAuth2 > URL Generator, select the
botscope, and give itSend MessagesandEmbed Linkspermissions - Copy the generated URL and open it to invite the bot to your server
Step 2: Set Up the Project
Create a new directory and install the one dependency we need:
mkdir tcg-price-botcd tcg-price-botnpm init -ynpm install discord.jsCreate a config.json file with your credentials:
{ "discordToken": "your-discord-bot-token", "tcgApiKey": "your-tcg-api-key", "prefix": "!"}Step 3: Build the Bot
Create bot.js with the basic bot structure:
const { Client, GatewayIntentBits, EmbedBuilder } = require('discord.js');const config = require('./config.json');
const API_BASE = 'https://api.tcgapi.dev/v1';
const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent ]});
client.once('ready', () => { console.log(`Logged in as ${client.user.tag}`);});
client.on('messageCreate', async (message) => { if (message.author.bot) return; if (!message.content.startsWith(config.prefix)) return;
const args = message.content.slice(config.prefix.length).trim().split(/\s+/); const command = args.shift().toLowerCase();
if (command === 'price') { await handlePriceCommand(message, args); } else if (command === 'movers') { await handleMoversCommand(message, args); }});
client.login(config.discordToken);Step 4: Connect to TCG API
Add a helper function for making API requests:
async function tcgApiRequest(endpoint) { const response = await fetch(`${API_BASE}${endpoint}`, { headers: { 'Authorization': `Bearer ${config.tcgApiKey}`, 'Accept': 'application/json' } });
if (!response.ok) { throw new Error(`API error: ${response.status} ${response.statusText}`); }
return response.json();}Step 5: The Price Command
Now build the main price lookup. This function parses the user’s query, searches for cards, and formats the results as Discord embeds:
async function handlePriceCommand(message, args) { // Parse --game flag if present let game = 'pokemon'; // default game const gameIndex = args.indexOf('--game'); if (gameIndex !== -1 && args[gameIndex + 1]) { game = args[gameIndex + 1].toLowerCase(); args.splice(gameIndex, 2); }
const query = args.join(' '); if (!query) { return message.reply('Usage: `!price <card name>` or `!price <card name> --game magic`'); }
try { const searchUrl = `/search/cards?q=${encodeURIComponent(query)}&game=${game}&per_page=3`; const data = await tcgApiRequest(searchUrl); const cards = data.data || [];
if (cards.length === 0) { return message.reply(`No cards found for "${query}" in ${game}. Try a different search or use \`--game\` to pick a game.`); }
// Send an embed for each result for (const card of cards) { const embed = buildPriceEmbed(card); await message.reply({ embeds: [embed] }); } } catch (err) { console.error('Price lookup error:', err); message.reply('Something went wrong fetching prices. Please try again.'); }}Step 6: Format Price Embeds
Build rich embeds that display card prices clearly:
function buildPriceEmbed(card) { const embed = new EmbedBuilder() .setTitle(card.name) .setColor(0x5865F2) .addFields({ name: 'Set', value: card.set_name || 'Unknown', inline: true }) .addFields({ name: 'Game', value: card.game_name || 'Unknown', inline: true }) .setFooter({ text: 'Prices from TCG API | tcgapi.dev' }) .setTimestamp();
// Add pricing for each printing (Normal, Foil, 1st Edition, etc.) const prices = card.prices || {}; if (Object.keys(prices).length === 0) { embed.addFields({ name: 'Prices', value: 'No pricing data available' }); } else { for (const [printing, priceData] of Object.entries(prices)) { const market = priceData.market_price ? `$${priceData.market_price.toFixed(2)}` : 'N/A'; const low = priceData.low_price ? `$${priceData.low_price.toFixed(2)}` : 'N/A';
let priceText = `Market: **${market}**\nLow: ${low}`;
// Add 24h change if available if (priceData.change_24h !== undefined && priceData.change_24h !== null) { const sign = priceData.change_24h >= 0 ? '+' : ''; priceText += `\n24h: ${sign}$${priceData.change_24h.toFixed(2)}`; }
embed.addFields({ name: printing, value: priceText, inline: true }); } }
// Add card image if available if (card.image_url) { embed.setThumbnail(card.image_url); }
return embed;}Step 7: The Movers Command
Add a command to show the day’s biggest price swings:
async function handleMoversCommand(message, args) { const game = args[0] || 'pokemon';
try { const data = await tcgApiRequest(`/games/${game}/prices/movers?period=24h&per_page=5`);
const embed = new EmbedBuilder() .setTitle(`Top Price Movers — ${game}`) .setColor(0x57F287) .setFooter({ text: 'Prices from TCG API | tcgapi.dev' }) .setTimestamp();
const gainers = (data.data?.gainers || []).slice(0, 5); const losers = (data.data?.losers || []).slice(0, 5);
if (gainers.length > 0) { const gainerText = gainers.map(c => { const pct = c.price_change_pct >= 0 ? `+${c.price_change_pct.toFixed(1)}%` : `${c.price_change_pct.toFixed(1)}%`; return `**${c.name}** — $${c.market_price.toFixed(2)} (${pct})`; }).join('\n'); embed.addFields({ name: 'Gainers', value: gainerText }); }
if (losers.length > 0) { const loserText = losers.map(c => { const pct = `${c.price_change_pct.toFixed(1)}%`; return `**${c.name}** — $${c.market_price.toFixed(2)} (${pct})`; }).join('\n'); embed.addFields({ name: 'Losers', value: loserText }); }
message.reply({ embeds: [embed] }); } catch (err) { console.error('Movers error:', err); message.reply('Something went wrong fetching price movers.'); }}Step 8: Run and Deploy
Start the bot locally to test:
node bot.jsHead to your Discord server and try:
!price charizard ex!price lightning bolt --game magic!price blue-eyes white dragon --game yugioh!movers magicFor production, you’ll want to keep the bot running 24/7. A few options:
- PM2 — Simple process manager:
npm install -g pm2 && pm2 start bot.js - VPS — Run on a $5/mo server (DigitalOcean, Hetzner, etc.)
- Railway / Render — Free-tier hosting for small bots
Handling Multiple Results
When a search returns several cards (e.g., “pikachu” matches hundreds), the bot shows the top 3 results. You can refine this by adding set filters:
// Search within a specific setconst searchUrl = `/search/cards?q=${encodeURIComponent(query)}&game=${game}&set=surging-sparks&per_page=3`;Or let users narrow results by adding a set flag:
!price pikachu --set surging-sparksParsing this is straightforward — look for --set the same way you handle --game and append &set= to the API query string.
Extending the Bot
Once the basics work, consider adding:
- Slash commands — Discord’s modern interaction model (recommended for public bots)
- Autocomplete — Suggest card names as users type using the search endpoint
- Collection tracking — Let users save cards and check their total portfolio value
- Price alerts — Monitor cards and DM users when prices cross a threshold
- Multi-game support — The API covers 89+ games, so your bot already works with all of them
Get Started
Ready to build your own Discord price bot? Sign up for a free TCG API key and follow the quickstart guide to make your first API call. The free tier gives you 100 requests per day — more than enough for a small server bot. When your server grows, paid plans start at $9.99/month with 1,000 requests per day.
Need help? Join the TCG API Discord and ask in #developers.
Ready to get started?
Free tier includes 100 requests per day. No credit card required.