Powrót do bloga
Poradnik|2026-04-08|11 min read

How to Build a TCG Card Price Tracker in JavaScript (50 Lines)

A complete step-by-step tutorial for building a live trading card price tracker using @tcgpricelookup/sdk, TypeScript, and a few lines of JavaScript. From npm install to a working price-alert script in under 15 minutes.

How to Build a TCG Card Price Tracker in JavaScript

Every developer who gets interested in trading card prices eventually asks the same thing: how do I track the value of my collection (or portfolio, or watchlist) programmatically? The naive answer — scraping TCGPlayer or eBay yourself — becomes a multi-week project with proxy servers, HTML parsing, rate limits, and maintenance overhead the moment either site tweaks their layout.

This post walks through the fast answer: a working TCG price tracker in under 50 lines of TypeScript using the official @tcgpricelookup/sdk on npm. By the end you'll have:

  1. A script that fetches live prices for a list of cards across 8 trading card games
  2. A diff-based alert system that emails/logs when prices move beyond a threshold
  3. A clean reusable pattern you can drop into any Node.js app, Discord bot, or scheduled cron job

The SDK is MIT-licensed, open-source on GitHub, and uses native fetch with zero runtime dependencies beyond itself. Works in Node 18+, browsers, Cloudflare Workers, Bun, and Deno.


What We're Building

The final script does three things:

  1. Loads a watchlist — a small JSON file with card IDs, nicknames, and price thresholds
  2. Fetches current prices in a single API call using batch lookup
  3. Compares against the last known prices and prints alerts when a card moves by more than the configured threshold

It's the foundation for almost every TCG price tool — portfolio trackers, arbitrage scanners, Discord price bots, investor dashboards, etc. The boring parts (HTTP, auth, error handling, rate limits, batch chunking) are all handled by the SDK so you can focus on the interesting logic.


Prerequisites

  • Node.js 18 or newer (the SDK uses native fetch)
  • A free TCG Price Lookup API key — sign up at tcgpricelookup.com/tcg-api. No credit card. 200 requests/day on the Free tier is plenty for a personal watchlist.
  • Basic TypeScript knowledge (15 minutes of tutorial reading if you're a JavaScript dev)
  • A terminal

Step 1: Project Setup

Create a fresh project and install the SDK:

mkdir tcg-price-tracker
cd tcg-price-tracker
npm init -y
npm install @tcgpricelookup/sdk
npm install -D typescript tsx @types/node

Create a minimal tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "noEmit": true
  }
}

Add a script to your package.json:

{
  "scripts": {
    "tracker": "tsx tracker.ts"
  }
}

Step 2: Store Your API Key

Never hardcode your key. Export it in your shell (or use a .env file):

export TCG_API_KEY="tcg_your_key_here"

The SDK reads it from process.env.TCG_API_KEY by convention in most examples, though you pass it explicitly to the client constructor.


Step 3: The Watchlist File

Create watchlist.json with cards you want to track. You can get card IDs by searching the TCG Price Lookup catalog — every card has a UUID visible in the URL and the API response.

[
  {
    "id": "019535a1-d5d0-7c12-a3e8-b7f4c6d8e9a2",
    "nickname": "Charizard ex (Obsidian Flames)",
    "alertThreshold": 0.10
  },
  {
    "id": "019c9751-afc3-7af6-920f-392ad69d859c",
    "nickname": "Charizard (Base Set) 1st Ed",
    "alertThreshold": 0.05
  }
]

alertThreshold is a decimal — 0.10 means alert if the price moves by 10% or more in either direction.


Step 4: The Tracker Script

Here's the complete tracker.ts — 46 lines of real code (plus imports and comments):

import { TcgLookupClient } from "@tcgpricelookup/sdk";
import { readFile, writeFile } from "node:fs/promises";

interface WatchItem {
  id: string;
  nickname: string;
  alertThreshold: number;
}

interface Snapshot {
  [cardId: string]: number | null;
}

const SNAPSHOT_FILE = "last-prices.json";

async function main() {
  // 1. Client setup. Auto-retries on 429 / 5xx.
  const tcg = new TcgLookupClient({
    apiKey: process.env.TCG_API_KEY!,
    retry: { attempts: 3, baseDelayMs: 600 },
  });

  // 2. Load watchlist + last-known prices
  const watchlist: WatchItem[] = JSON.parse(
    await readFile("watchlist.json", "utf-8")
  );
  const lastPrices: Snapshot = await readFile(SNAPSHOT_FILE, "utf-8")
    .then(JSON.parse)
    .catch(() => ({})); // first run: empty snapshot

  // 3. Single batched request for all cards. SDK auto-chunks if > 20.
  const ids = watchlist.map((w) => w.id);
  const { data: cards } = await tcg.cards.search({ ids });

  // 4. Diff and alert
  const newSnapshot: Snapshot = {};
  for (const item of watchlist) {
    const card = cards.find((c) => c.id === item.id);
    const current =
      card?.prices.raw.near_mint?.tcgplayer?.market ?? null;
    newSnapshot[item.id] = current;

    const previous = lastPrices[item.id];
    if (current == null || previous == null) {
      console.log(`[SEED]  ${item.nickname}: $${current ?? "n/a"}`);
      continue;
    }
    const delta = (current - previous) / previous;
    const pct = (delta * 100).toFixed(1);
    const symbol = delta >= 0 ? "▲" : "▼";

    if (Math.abs(delta) >= item.alertThreshold) {
      console.log(
        `[ALERT] ${item.nickname}: $${previous} → $${current} (${symbol}${pct}%)`
      );
    } else {
      console.log(
        `[ok]    ${item.nickname}: $${current} (${symbol}${pct}%)`
      );
    }
  }

  // 5. Persist snapshot for next run
  await writeFile(SNAPSHOT_FILE, JSON.stringify(newSnapshot, null, 2));
  console.log(`\n${tcg.rateLimit.remaining}/${tcg.rateLimit.limit} calls left today`);
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

That's the whole script. Under 50 lines of real logic, native fetch, no dependencies beyond the SDK, handles retries and rate limits automatically.


Step 5: Run It

npm run tracker

First run output:

[SEED]  Charizard ex (Obsidian Flames): $48.97
[SEED]  Charizard (Base Set) 1st Ed: $488.20

99998/100000 calls left today

Wait a day, run it again:

[ok]    Charizard ex (Obsidian Flames): $49.12 (▲0.3%)
[ALERT] Charizard (Base Set) 1st Ed: $488.20 → $520.00 (▲6.5%)

99997/100000 calls left today

Congratulations — you have a working TCG price tracker. It handles retries automatically via the SDK's retry option, chunks larger watchlists transparently when you pass more than 20 IDs, and persists state between runs via a simple JSON file.


How the Batch Lookup Saves You Calls

The tcg.cards.search({ ids }) call is worth understanding in detail. When you pass an array of card IDs:

  1. The SDK takes your array (any length)
  2. Chunks it into groups of 20 (the backend's cap per request)
  3. Fires all chunk requests in parallel
  4. Merges the responses into a single { data, total, limit, offset } shape
  5. Returns the combined result as if it were one call

For your daily quota, each underlying HTTP request counts as one call, but the chunking is transparent so from your code's perspective you're making one function call. A 100-card watchlist = 5 HTTP requests = 5 quota hits per daily run. On the Free tier (200 calls/day) you could run this 40 times per day across a 100-card watchlist and still have headroom.

For more detail on the batch lookup pattern, see the API reference. The TCGPlayer vs eBay post also explains why the response includes both TCGPlayer and eBay data by default.


Extending: Email Alerts

The tracker prints to stdout. A tiny extension: pipe the alerts to your email via a transactional email service. Here's the relevant change using a generic email hook:

async function sendEmail(to: string, subject: string, body: string) {
  // Resend, SendGrid, Postmark, or your SMTP of choice
  // await fetch("https://api.resend.com/emails", { ... });
}

// Replace the console.log ALERT line with:
if (Math.abs(delta) >= item.alertThreshold) {
  const line = `${item.nickname}: $${previous} → $${current} (${symbol}${pct}%)`;
  console.log(`[ALERT] ${line}`);
  await sendEmail(
    "[email protected]",
    `TCG price alert: ${item.nickname}`,
    line
  );
}

With this change, you get email whenever a card crosses your threshold. Schedule the script via cron or a GitHub Action and you've built a free price-alert service for your personal collection.


Extending: Graded Card Prices

If your watchlist includes vintage or graded cards, raw TCGPlayer data isn't enough — you want the eBay graded sold-listing averages. This requires the Trader plan or above (the Free tier returns raw TCGPlayer prices only). See our PSA vs BGS vs CGC comparison for why graded data comes from eBay.

The SDK's TypeScript types already cover graded prices, so accessing them is just:

// PSA 10 price, safely optional-chained
const psa10 = card.prices.graded?.psa?.["10"]?.ebay?.avg_7d;

// BGS 9.5 price
const bgs95 = card.prices.graded?.bgs?.["9.5"]?.ebay?.avg_7d;

// CGC 10 price
const cgc10 = card.prices.graded?.cgc?.["10"]?.ebay?.avg_7d;

Add a graderTarget field to your watchlist items (e.g. "psa-10" or "bgs-9.5") and swap the current price-extraction in the tracker for graded lookup when present. Your watchlist becomes a mixed raw+graded tracker in 10 extra lines.


Extending: Discord Bot

A Discord slash command that runs the same logic is trivially small. The full architecture:

  1. Use discord.js for the bot framework
  2. Register a /price command that takes a card name argument
  3. Inside the command handler, call tcg.cards.search({ q: cardName, limit: 3 })
  4. Format the top hits into a Discord embed with the card name, TCGPlayer market, eBay 7-day average, and a link back to the /catalog page

Full working Discord bot is out of scope for this post but that's the entire architecture — roughly 80 lines of code. The SDK handles all the API plumbing so the bot can focus on Discord-specific formatting.


Handling Errors Properly

The bare tracker above has a catch at the bottom that logs any error and exits. For production you probably want finer-grained error handling using the SDK's typed errors:

import {
  TcgLookupClient,
  AuthenticationError,
  PlanAccessError,
  RateLimitError,
  NotFoundError,
} from "@tcgpricelookup/sdk";

try {
  const { data } = await tcg.cards.search({ ids });
  // ... normal flow
} catch (err) {
  if (err instanceof AuthenticationError) {
    console.error("Invalid API key. Check TCG_API_KEY env var.");
    process.exit(1);
  }
  if (err instanceof PlanAccessError) {
    console.error(
      "Your plan doesn't include this feature. Upgrade at /pricing."
    );
    process.exit(1);
  }
  if (err instanceof RateLimitError) {
    console.error("Rate limited. The SDK already auto-retried 3x.");
    process.exit(1);
  }
  if (err instanceof NotFoundError) {
    console.error("Card ID in your watchlist doesn't exist anymore.");
    // don't exit — just skip that item
  }
  throw err; // unknown, rethrow
}

Each error class carries .status, .url, .body, and a human-readable .message. For the full error hierarchy, check the SDK README.


Running It as a Cron Job

Once the tracker works locally, schedule it. Simplest option — a cron entry:

0 9 * * * cd /path/to/tcg-price-tracker && /usr/local/bin/npm run tracker >> tracker.log 2>&1

This runs every day at 9am. Pipe output to a log file and read it periodically, or add the email extension above so you get alerts in your inbox.

For a cloud option, GitHub Actions works great for this. Create .github/workflows/tracker.yml:

name: TCG price tracker
on:
  schedule:
    - cron: "0 9 * * *"
  workflow_dispatch:
jobs:
  track:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - run: npm ci
      - run: npm run tracker
        env:
          TCG_API_KEY: ${{ secrets.TCG_API_KEY }}
      - uses: actions/upload-artifact@v4
        with:
          name: last-prices
          path: last-prices.json

Add TCG_API_KEY as a GitHub secret and you have a free serverless cron job running your price tracker every day.


Why Not Just Scrape TCGPlayer Yourself?

This is the question every developer asks when they first consider this. The honest answer: you can, and it will take you 2-4 weeks of continuous effort before it becomes worse than the SDK, and then keep costing you maintenance forever.

Here's what scraping requires that the SDK handles for you:

  • Proxy rotation to avoid IP blocks ($200-500/month)
  • Captcha solving for TCGPlayer's bot detection ($50-200/month)
  • HTML parsing that breaks every few weeks when TCGPlayer updates their markup
  • Rate-limit management tuned per source
  • Condition normalization — "NM", "Near Mint", "Near-Mint" all need to map to one canonical string
  • Multi-game scraping — you need separate scrapers for TCGPlayer, eBay, and per-game edge cases
  • Maintenance when something breaks — which is all the time

We walked through this math in detail on the TCG API landing page under the "Build vs Buy" section. The rough real cost of rolling your own scraper for year 1 is $3,000-$8,000 in infrastructure + developer time, and then it keeps costing you ongoing maintenance. The SDK costs you $0 to start (Free tier) and scales linearly with usage.

If you're going to spend your dev time on a price-tracking project, spend it on the product features — not the data layer.


Taking It Further

The 50-line tracker is the minimum viable product. Where to go from here:

  • Portfolio valuation — extend the tracker to sum the total value of your watchlist in one number, track historical totals, and chart over time
  • Multi-game expansion — watchlists can mix cards from all 8 supported games without any code change
  • Price history charts — use tcg.cards.history(id, { period: "30d" }) (Trader plan) to pull daily price points and build a chart with Chart.js, Recharts, or similar
  • Webhook integrations — pipe alerts into Slack, Telegram, or a custom notification service instead of email
  • Automated arbitrage — compare TCGPlayer market against eBay sold averages to find cards where the two diverge significantly (classic flipper strategy)
  • Public price dashboard — wrap the tracker output in a tiny Next.js app and make it a public landing page for your portfolio

Every one of these is a small extension of the same cards.search({ ids }) call. The SDK doesn't fight you.


Frequently Asked Questions

How many cards can I track on the Free tier?

The Free tier gives you 200 requests per day. Each cards.search({ ids }) call with up to 20 card IDs counts as 1 request. A 100-card watchlist run once per day is 5 requests — roughly 2.5% of your daily quota. You could run this hundreds of times a day before hitting the cap.

Does the SDK work in browsers or only Node?

Both. The SDK uses native fetch, which is available in modern browsers, Node 18+, Cloudflare Workers, Bun, and Deno. However, never expose your API key in client-side JavaScript — proxy the SDK calls through a backend you control. The authentication docs cover the security implications.

What about the tcglookup CLI?

For terminal-only workflows where you don't need to write code, check out tcglookup — the official command-line tool. It wraps the same SDK with pretty colored tables and a --json flag for piping to jq. Install with npm install -g tcglookup and run tcglookup search "charizard" --game pokemon from any terminal.

Can I use this SDK with other frameworks (Next.js, Remix, SvelteKit)?

Yes. The SDK is framework-agnostic. In a Next.js server component, call it directly in an async function. In an API route, call it server-side and return the data as JSON to your client. In Remix loaders or SvelteKit load functions, same pattern. The only rule is: never call it from client-side components — that would expose your API key in the browser.

Where are the full SDK docs?

The GitHub README has quickstart, code examples for every endpoint, error handling, retry configuration, and TypeScript type reference. The API reference on tcgpricelookup.com has the raw HTTP endpoint documentation if you want to understand what the SDK is doing under the hood.

How do I get help if something doesn't work?

Open an issue at github.com/TCG-Price-Lookup/tcglookup-js for SDK-specific problems. For API issues (unexpected responses, missing cards, data quality), email [email protected]. We read every issue and respond within 24 hours on weekdays.


Complete Source on GitHub

The full tracker script is available as a reference gist linked from the TCG API landing page. The @tcgpricelookup/sdk source and documentation live at:

MIT licensed, open-source, zero runtime dependencies, continuously tested across Node 18/20/22, browsers, Cloudflare Workers, Bun, and Deno.


About This Tutorial

This post is part of a series of developer tutorials on building trading card price tools with the TCG Price Lookup API. For more content:

Sign up for a free API key at tcgpricelookup.com/tcg-api — no credit card, 200 requests per day, and the whole catalog is yours to build on.

Related posts: Complete MTG Card Price Guide 2026 · Complete Yu-Gi-Oh! Card Price Guide 2026 · One Piece TCG Price Guide 2026