npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

bgg-ts-client

v0.3.0

Published

Fully typed TypeScript client for the BoardGameGeek XMLv2 API with error handling and pagination support

Downloads

767

Readme

GitHub release (latest by date)

bgg-ts-client

A TypeScript client for the official BoardGameGeek XML API v2.

Note: This project is a fork of boardgamegeekclient by learningprocesss. It is now developed and maintained independently.

Key features

  • :ballot_box_with_check: Support Authorization via BGG tokens
  • :ballot_box_with_check: Fully typed requests and responses
  • :ballot_box_with_check: Easy to use
  • :ballot_box_with_check: Typescript written
  • :ballot_box_with_check: Promisified
  • :ballot_box_with_check: thing, family, forumlist, forum, thread, user, guild, play, collection, search, hot endpoints
  • :ballot_box_with_check: Structured error handling with typed error classes

Prerequisites

Starting in Fall 2025, BoardGameGeek requires all API clients to use authorization. Before using this package, you must register your application on BoardGameGeek and obtain an access token.

Register and manage your application here: https://boardgamegeek.com/applications

Installation

npm i bgg-ts-client
yarn add bgg-ts-client

Breaking changes (migrating from 0.2 → 0.3)

0.3 is a major internal rewrite. The high-level BggClient API (.query() / .queryWithProgress() per endpoint) is unchanged, but imports, DTO type names, and several field shapes changed. Full detail with before/after tables is in CHANGELOG.md. Everything that can break existing code:

Imports & exports

  • Per-item DTOs are now exported from the package root. In 0.2 the root only exported BggClient and the error classes, so consumers deep-imported types from bgg-ts-client/dist/esm/dto/concrete/subdto/…. Those internal folders (dto/concrete/subdto/ and dto/concrete/paginated/) have been removed — deep imports into them break. Import from the root instead:

    // 0.2
    import { BggCollectionItemDto } from 'bgg-ts-client/dist/esm/dto/concrete/subdto/BggCollectionItemDto';
    // 0.3
    import { BggCollectionItemDto, BggThingDto } from 'bgg-ts-client';
  • Removed standalone types: BggArticleDto, BggForumlistForumDto, BggForumThreadDto (standalone file), BggGuildMemberDto / BggGuildMemeberDto, BggThingMarketlistingsDto, BggStatisticsPaginatedDto, BggStatisticsRatingDto, BggStatisticsRatingRanksDto, BggThingVideoPaginatedDto, BggThingCommentPaginatedDto, BggPollResultDto, BggPollResultItemDto, and the BggPlaysPlay* family. Their data now lives inline on the endpoint DTOs listed below.

  • IDtoParser no longer exposes jsonToDto or a parser field — use the synchronous parse(parsedXml).

  • Dependencies: jackson-js removed; fast-xml-parser bumped ^3.18^4.5.

Type renames

| 0.2 | 0.3 | |---|---| | BggCollectionItemStatusDto | BggCollectionStatusDto | | BggCollectionItemStatsDto | BggCollectionStatsDto | | BggPlaysPlayDto | BggPlayDto (⚠ meaning changed — see below) | | BggPlaysPlayPlayerDto | BggPlayPlayerDto | | BggForumlistForumDto | BggForumDescriptorDto | | BggThingMarketlistingsDto | BggMarketplaceItemDto | | BggGuildMemeberDto (typo) | nested inside BggGuildDto |

BggPlayDto meaning changed

In 0.2, BggPlayDto was the wrapper returned by client.play.query() (username, userid, total, page, plays). In 0.3 that wrapper is the new BggPlaysDto, and BggPlayDto is a single play. Re-type references that touch .plays / .username as BggPlaysDto.

BggThingDto field changes

  • name: string (single) is removed — use names: BggNameDto[] (each with type: 'primary' | 'alternate', sortindex, value). alternateNames: string[] is retained and derived from names.
  • statistics is flattened: was BggStatisticsPaginatedDto with a nested .ratings object (statistics.ratings.average, statistics.ratings.averageweight, statistics.ratings.ranks). Now statistics: BggThingStatsDto | undefined with those fields directly on itstatistics.average, statistics.averageweight, statistics.ranks. The .ratings layer is gone.
  • marketplacelistings: BggThingMarketlistingsDto[] renamed to marketplace: BggMarketplaceItemDto[] | undefined.
  • videos was a paginated wrapper (BggThingVideoPaginatedDto) → now videos: BggVideoDto[] | undefined.
  • comments: BggThingCommentPaginatedDtocomments: BggThingCommentsDto | undefined (same { totalitems, page, items } shape).
  • polls restructured: BggPollDto[] is now a discriminated union on name (suggested_numplayers | suggested_playerage | language_dependence), totalvotes is a number, and results is typed per poll. resultItemList is removed — read results directly.
  • pollSummary: anypollSummary: BggPollSummaryDto[] | undefined.
  • Previously-required scalars (type, description, yearpublished, …) are now | undefined; guard them under strictNullChecks.

Field type coercions (strings/flags → real types)

BGG's "0"/"1" flags, "yes"/"no", dates, and numeric strings are now parsed into boolean, Date, and number. Notable cases:

  • CollectionBggCollectionStatusDto flags (own, prevowned, fortrade, want, wanttoplay, wanttobuy, wishlist, preordered) "0"/"1"boolean | undefined; lastmodified stringDate | undefined. New nameSortindex field.
  • PlaysBggPlayDto.dateDate | undefined; incomplete, nowinstatsboolean | undefined. BggPlayPlayerDto: new/winboolean | undefined, ratingnumber | undefined, scorestring | undefined.
  • Dates elsewhereBggForumDescriptorDto.lastpostdate, BggForumDto.lastpostdate, BggForumThreadDto.postdate/lastpostdate, BggGuildDto.created, BggUserDto.lastlogin, BggVideoDto.postdate, BggMarketplaceItemDto.listdateDate | undefined. The BGG "never" sentinel (Thu, 01 Jan 1970 …) is preserved as new Date(0), not normalized away.
  • BggLinkDto.inbound: boolean | undefined is now exposed.

Errors

New BggTimeoutError and BggRateLimitError (both extend BggApiError) are thrown when the networkError / rateLimited retry buckets exhaust. Existing instanceof BggApiError checks keep matching — see Errors.

Usage

In Node.js (commonjs) environment

const { BggClient } = require("bgg-ts-client");

In ES environment

import { BggClient } from 'bgg-ts-client';

Initialize BggClient and get singleton instance

const client = BggClient.Create({ apiKey: 'YOUR_API_KEY' });

Cookie auth (private collections)

Some endpoints — most notably collection with showprivate=1 — only return data for the authenticated user. Pass a session cookie alongside your API key:

const client = BggClient.Create({
  apiKey: 'YOUR_API_KEY',
  cookie: 'bggusername=foo; bggpassword=...; SessionID=...',
});

To obtain the cookie, log in to boardgamegeek.com in a browser, open DevTools → Application/Storage → Cookies → boardgamegeek.com, and copy the cookie header value verbatim. Cookies expire — re-issue when needed.

Note: cookie auth is realistically Node-only. Browsers will block cross-origin cookie requests to boardgamegeek.com unless you proxy through your own server.

Retry policy

The client retries transient failures automatically. Four independent policies handle the kinds of failures BGG returns:

| Bucket | Triggers on | Default baseDelayMs | Default maxDelayMs | Default maxAttempts | |---|---|---|---|---| | queued | HTTP 202 (BGG queueing collection requests) | 2000 | 30000 | 8 | | rateLimited | HTTP 429 or 503 | 2000 | 30000 | 5 | | serverError | other HTTP 5xx | 1000 | 10000 | 3 | | networkError | fetch/connect/timeout | 1000 | 10000 | 3 |

Each retry waits min(baseDelayMs * 2^(attempt-1), maxDelayMs) plus jitter. Override any subset; missing keys keep the defaults:

const client = BggClient.Create({
  apiKey: 'YOUR_API_KEY',
  retry: {
    rateLimited: { maxAttempts: 8, maxDelayMs: 60000 },
  },
});

When retries exhaust, the client throws a typed error (see below).

Errors

All errors are subclasses of BggClientError (which carries the endpoint name and the underlying cause). Inspect the cause to disambiguate.

  • BggClientError — wraps everything thrown from a query(). endpoint + cause.
  • BggApiError — HTTP error from BGG (or its error envelope at HTTP 200). statusCode + url + message.
  • BggTimeoutError — extends BggApiError. Thrown after networkError retries exhaust on connect/timeout failures.
  • BggRateLimitError — extends BggApiError. Thrown after rateLimited retries exhaust on HTTP 429.
  • BggParseError — XML or DTO parse failure. rawData truncated to 500 chars.

instanceof BggApiError matches BggTimeoutError and BggRateLimitError too, so existing checks keep working.

Field types

DTO fields use proper TypeScript types where BGG's wire format is unambiguous: dates are Date objects (e.g. play.date, collection.status.lastmodified, forum.lastpostdate), boolean-ish XML attributes ("0"/"1", "yes"/"no") are boolean, and numeric attributes are number. Missing or malformed values are undefined rather than empty strings.

Every DTO also carries an extras: Record<string, unknown> field. When BGG adds new XML attributes or elements that the typed schema doesn't yet model, they appear here verbatim — your code can read them without waiting for a client release.

API

Interact with boardgamegeek entities using the corresponding client object and fire a request with query or queryWithProgress method.

Thing

Get boardgame, boardgame expansion, boardgame accessory, videogame, rpgitem, rpgissue information. Thing client exposes query and queryWithProgress.

Examples

const things: BggThingDto[] = await client.thing.query({ id: [174430, 35421],
                                                         videos: 1,
                                                         comments: 1,
                                                         marketplace: 1,
                                                         stats: 1,
                                                         type: "boardgame" });

// with progress handler as parameter

await client.thing.queryWithProgress({
                id: [250621, 257668, 226255, 340790, 279307, 279306, 345121, 271447, 187104, 253618, 271512, 432, 68448, 173346, 346703, 302260, 239472, 172818, 231398, 202408, 267814, 267813, 191189, 267127, 281946, 264647, 2272, 230085, 31260, 247367, 256442, 161970, 6249, 181293],
                videos: 1,
                comments: 1,
                marketplace: 1,
                stats: 1,
                type: "boardgame"
            }, { limit: 10 }, _data => {

            });

// with progress handler registered on the client itself

client.thing.progressHandler = (_data) => { };

await client.thing.queryWithProgress({
    id: [250621, 257668, 226255, 340790, 279307, 279306, 345121, 271447, 187104, 253618, 271512, 432, 68448, 173346, 346703, 302260, 239472, 172818, 231398, 202408, 267814, 267813, 191189, 267127, 281946, 264647, 2272, 230085, 31260, 247367, 256442, 161970, 6249, 181293],
    videos: 1,
    comments: 1,
    marketplace: 1,
    stats: 1,
    type: "boardgame"
}, { limit: 10 });

Family

Get rpg, rpgperiodical, boardgamefamily information. Family client exposes query and queryWithProgress.

Examples

const families = await client.family.query({ id: [174430, 35421] });

Forum List

Get a list of forums (in boardgame or family page (of the id), forums tab, left sidebars with all forums). ForumList client exposes query and queryWithProgress.

Examples

const forumlists: BggForumlistDto[] = await client.forumlist.query({ id: [8374,22184,59218,1029,2076], type: ['family']});

Forum

Get a single forum.

Examples

const forum = await client.forum.query({ id: 19, page: 3 });

Thread

Get a single thread.

Examples

const threads: BggThreadDto[] = await client.thread.query({ id: 2571698, minarticledate: '2021-01-03', count: 15 });

User

Get public profile information about a user by username.

Examples

const users: BggUserDto[] = await client.user.query({ name: 'mattiabanned', hot: 1, top: 1 });

Guild

Get a single guild.

Examples

const guilds: BggGuildDto[] = await client.guild.query({ id: 1000, members: 1, sort: 'date', page: 1 });

Play

Request plays logged by a particular user or for a particular item.

Examples

const response: BggPlaysDto = await client.play.query({ username: 'mattiabanned' });
// wrapper carries BGG metadata: username, userid, total, page, termsofuse
const plays: BggPlayDto[] = response.plays;

Collection

Request the collection of a particular user.

Examples

const response: BggCollectionDto = await client.collection.query({ username: 'mattiabanned', excludesubtype: ["boardgameaccessory"] });
// wrapper carries BGG metadata: totalitems, pubdate, termsofuse
const items: BggCollectionItemDto[] = response.items;
// each item's name is a plain string; sortindex is exposed separately as `nameSortindex`

Search

Search BGG for items by name.

Examples

const response: BggSearchDto = await client.search.query({ query: 'gloomhaven', type: 'boardgame', exact: 1 });
// wrapper carries BGG metadata: total, termsofuse
const results: BggSearchItemDto[] = response.items;

Hot

Get the current BGG hotness list.

Examples

const hot: BggHotDto[] = await client.hot.query({ type: 'boardgame' });

Error Handling

All errors thrown from a query() call are wrapped in a BggClientError. Inspect cause to distinguish HTTP failures from parse failures, and use instanceof against the specific subclasses for finer control. See the Errors section above for what each class represents.

import {
  BggClient,
  BggClientError,
  BggApiError,
  BggTimeoutError,
  BggRateLimitError,
  BggParseError,
} from 'bgg-ts-client';

const client = BggClient.Create({ apiKey: 'YOUR_API_KEY' });

try {
  const things = await client.thing.query({ id: [174430], type: 'boardgame' });
} catch (error) {
  if (error instanceof BggClientError) {
    console.log(error.endpoint); // e.g. "thing"

    if (error.cause instanceof BggRateLimitError) {
      console.log('Rate-limit retries exhausted; back off further.');
    } else if (error.cause instanceof BggTimeoutError) {
      console.log('Network/timeout retries exhausted.');
    } else if (error.cause instanceof BggApiError) {
      console.log(error.cause.statusCode, error.cause.url);
    } else if (error.cause instanceof BggParseError) {
      console.log(error.cause.rawData); // truncated to 500 chars
    }
  }
}