Skip to content
· 10 min read

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.

tutorialdiscordbotjavascript

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 keysign 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:

  1. Click “New Application” and give it a name like “TCG Price Bot”
  2. Go to the Bot section and click “Add Bot”
  3. Copy the bot token — you’ll need this shortly
  4. Under “Privileged Gateway Intents”, enable Message Content Intent
  5. Go to OAuth2 > URL Generator, select the bot scope, and give it Send Messages and Embed Links permissions
  6. 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:

Terminal window
mkdir tcg-price-bot
cd tcg-price-bot
npm init -y
npm install discord.js

Create 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:

Terminal window
node bot.js

Head to your Discord server and try:

!price charizard ex
!price lightning bolt --game magic
!price blue-eyes white dragon --game yugioh
!movers magic

For 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 set
const 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-sparks

Parsing 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.