Skip to content

About

JavaScript / Node.js SDK for TigerTag — the open NFC identification standard for 3D-printing materials (Apache-2.0).

Topics

Resources

Security policy

Stars

7 stars

Watchers

0 watching

Forks

Repository files navigation

TigerTag JavaScript SDK — Open-source RFID protocol for manufacturing material identification

TigerTag JavaScript SDK

npm Tests Node License: Apache 2.0 Protocol Offline first

Offline JavaScript / Node.js SDK for TigerTag RFID material identification.

TigerTag is the world's most widely deployed open-source RFID protocol for manufacturing material identification — with over 2 million chips deployed worldwide. Adopted by major brands including eSun, Rosa3D, Sunlu, R3D, Landu and many more. Currently covers filament and resin. Designed to extend to any physical material (sheet goods, wood, PMMA, metals, composites…). All material data is stored directly on the NTAG chip — 100% offline.


Industry adoption

TigerTag is the #1 RFID material identification protocol in the 3D printing industry and the only open-source standard with broad manufacturer adoption at scale.

Metric Value
Chips deployed worldwide 2 000 000+
Filament / resin brands eSun · Rosa3D · Sunlu · R3D · Landu · and more
Connected printers & slicers Snapmaker · Bambu Lab · FlashForge · Elegoo · Creality · and more coming
Exclusive integrations HueForge (Transmission Distance) · TD1s by Ajax (filament manager)
Cost for end users 100% free — protocol, SDK, Studio Manager, mobile apps
Protocol status Open specification (CC-BY-4.0) — irrevocable, royalty-free right to implement
Hardware Tiger Scale (DIY ~30 € open-source) · TigerTag Pod (read/write desktop + mobile)
Ecosystem maturity Desktop app · Mobile app · Pod · DIY scale · Firebase · Python SDK · JS SDK
Chip compatibility NTAG213 · NTAG215 · NTAG216 · any ISO 14443-3 compatible

What makes TigerTag unique

1 — Proof of authenticity (ECDSA-P256)

TigerTag is the only material RFID protocol to offer cryptographic proof of authenticity. Each signed chip carries an ECDSA-P256 signature that binds the chip UID to the product data. Any reader — including this SDK — can verify the signature fully offline, with no server call:

const result = tag.verify();   // VALID — chip is genuine and untampered
                               // INVALID — data has been modified or chip is cloned
                               // NOT SIGNED — unsigned Maker tag (verification not required)

No other RFID material protocol provides on-chip cryptographic authentication at this level.

2 — Chips reusable forever

TigerTag chips are never write-locked. Once a spool is finished, the chip gets a second life:

  • Erase and reprogram as a fresh TigerTag for a new spool: TigerTag.erase()
  • Reprogram with any NFC / NDEF standard for a completely different use case
  • Use as a plain NTAG tag in any NFC-capable application

Zero electronic waste. The chip is a permanent, reusable asset — not single-use packaging. No competing protocol offers this combination of authentication and unlimited reusability.

3 — Remote update by the manufacturer (TigerTag+)

TigerTag+ is the only material RFID protocol with remote over-the-air update capability for manufacturers. When a brand publishes improved print settings or corrected temperatures to the TigerTag cloud API, every chip already deployed can receive those updates:

// Fetch latest manufacturer data and apply to chip:
const [patchedTag, changes] = await tag.patchFromApi();
console.log(`${changes.length} field(s) updated by manufacturer`);

// Or inspect what changed before applying:
const diffs = await tag.diffApi();
for (const d of diffs) {
  console.log(`${d.field}: chip=${d.chipValue} → manufacturer=${d.apiValue}`);
}

4 — Native HueForge integration

TigerTag is the only RFID protocol natively integrated with HueForge. The TD (Transmission Distance) value is stored directly on the chip (tdValue property) and read by HueForge without any manual entry. It is also the only protocol supported by TD1s by Ajax, the open-source filament manager.


Hardware ecosystem

Device What it does Price
Tiger Scale Open-source DIY ESP32 smart scale — reads the TigerTag, weighs the spool, updates measureAvailable in real time ~30 € in parts
TigerTag Pod Plug-and-play NFC reader/writer — read and write chips from your desktop (via TigerTag Studio Manager) or from your phone (via TigerTag RFID Connect on iOS and Android) —

Everything is free for end users: the protocol, this SDK, TigerTag Studio Manager, the mobile apps, and all community tools. No subscription, no lock-in.


Try the Playground

No NFC hardware required — explore the full SDK output directly in your browser.

Launch Playground

# Start the dev server
node tools/server.js 7432

# Open in browser
open http://localhost:7432/tools/playground.html

One page, two servers. tools/playground.html is the same file, byte for byte, in the JavaScript SDK and the Python SDK; each repository's tools/server.* implements the same server contract (docs/playground-api.md) with its own SDK, and the page adapts its names and code (create() shown in camelCase or snake_case, toRawDict() / to_raw_dict()…) to GET /api/version. node scripts/check_playground_sync.js checks that the copy here is identical to the other repository's (the local checkout next to this one, or GitHub main); the test suite runs it and skips it when neither is reachable. Change the page in both repositories together.

Or via npm:

npm run playground

The playground has five panels:

Panel Purpose
Sidebar (left) Build a TigerTag / TigerTag+ / Init tag: choose version, brand, material, colors, print settings. Generate button pinned at the bottom — always visible.
Center Protocol preview cards: Protocol, Material, Colors, Print Settings, Quantity, Traceability, Cloud API
SDK Input (collapsible) Shows the exact TigerTag.create({...}) call for the current tag — the write side. Opens automatically when you click Burn. Payload is generated server-side via POST /api/build (SDK is always the authoritative serializer — browser never computes chip bytes).
SDK Output (collapsible) Shows pretty(), describe(), verify(), toRawDict(), toDict(), rawApi(), diffApi() — the read side. Opens automatically on Generate / NFC scan / Import.
Raw Hex (modal) Raw Read — reads all 144 bytes (pages 4–39) from every connected reader and shows a structured hex table: page (decimal), offset (bytes), page (hex: 0x04–0x27), B0–B3, u32 BE, annotated field label (value) field_name · …. Signature pages dimmed. Multiple readers shown side-by-side in collapsible panels. Copy hex button outputs one 0x04 B0 B1 B2 B3 line per page with Copied feedback.

SDK Input / Output and Raw Hex reader panels are all collapsible via their adjacent rails.

Available Qty auto-link — the Available Qty field automatically mirrors Initial Qty until you edit it manually. On NFC scan, preset load, or API fetch the link is restored to the actual values.

ACR122U / PC-SC live reader + Burn

Place a chip on your reader and the playground auto-populates instantly — no manual action needed.

# Enable live reader support (one-time install)
npm install ws nfc-pcsc

# Then launch as usual — readers detected automatically
npm run playground

Multiple simultaneous USB readers supported. Each reader gets its own status badge in the header (green dot = connected, orange pulsing dot = reading card) and its own Raw Hex panel.

Burn — once a chip is on a reader, click Burn to write the current payload to all connected readers that hold a card. Writes pages 0x04–0x27 (36 pages) sequentially: the tag data, then 00 on every signature page 0x18–0x27. The playground never writes a signature — only a certified manufacturer can issue one; the playground only reads signatures to verify them — and a burn never leaves a stale one (pages 0–3 and 0x28+ are never touched). The SDK Input panel opens automatically so you can see exactly what was written.

Raw Read — reads all 144 bytes from every card-holding reader and displays the raw chip memory as a structured hex table with field annotations. Useful for debugging and verifying burns.

Server endpoints:

Method Path Response
GET /api/version { version: string }
POST /api/parse { pretty, describe, verify, raw_dict, dict } — full SDK parse of a hex payload
POST /api/build { payload: hex } — TigerTag.create(kwargs).toBytes() — SDK-authoritative payload
POST /api/diff { api_data, diffs, in_sync, error } — chip vs cloud diff

Install

npm install tigertag

or, with pnpm:

pnpm add tigertag

Both install the same package from the npm registry (yarn and bun work too: yarn add tigertag, bun add tigertag).

Zero configuration. Zero network required on first run. Bundled reference databases ship with the package. Requires Node.js 18+ (uses built-in crypto and fetch — no extra deps).


Quick start

const { TigerTag } = require('tigertag');

const tag = TigerTag.fromPages(uid, payload);   // from your NFC SDK
console.log(tag.pretty());                      // human-readable summary
console.log(String(tag.verify()));              // VALID / NOT SIGNED / INVALID
console.log(tag.toDict());                      // JSON-ready object

Works immediately after npm install tigertag. No setup required.


What is TigerTag?

TigerTag is an open-source RFID protocol that stores manufacturing material data directly on NFC chips (NTAG213 / NTAG215 / NTAG216, ISO 14443-3 compatible). No cloud dependency for reading — all data lives on the chip.

Tag types:

Tag type idProduct Offline Cloud
TigerTag (Maker) 0xFFFFFFFF Yes — full data on chip —
TigerTag Init 0x00000000 Yes — blank template —
TigerTag+ numeric ID Yes — full data on chip Yes — API for live updates

Protocol spec: github.com/TigerTag-Project/TigerTag-RFID-Guide


Constructors

Method Input When to use
TigerTag.fromPages(uid, payload) 7-byte UID + 80 or 144 bytes NFC SDK integration (recommended)
TigerTag.fromDump(data) 80 / 144 / 180 bytes Binary dumps, ACR122U raw read
TigerTag.fromFile(path) path to .bin file Testing, offline batch processing
TigerTag.fromRawDict(raw) toRawDict() output (snake_case) Reconstruct from stored raw dict
TigerTag.fromCloudDoc(doc) Firestore cloud document Write pipeline: cloud → chip

fromPages is the primary constructor for production use. NFC SDKs always provide the UID as a separate property — pass it directly for full signature verification.

fromDump with 180 bytes (full chip dump including system pages) auto-extracts the 7-byte UID.


Input formats

fromPages(uid, payload) — NFC SDK workflow

NFC SDKs always expose the UID as a dedicated property. Pages 0–3 (system pages: lock bytes, capability container) are never part of the user data payload.

Payload Pages UID Verifiable
144 bytes 0x04–0x27 (user data + signature) Required (7 bytes) Yes
80 bytes 0x04–0x17 (user data, no signature) Required (7 bytes) N/A

fromDump(data) — binary dump workflow

Dump Content UID Verifiable
180 bytes Full chip (pages 0–44, includes system pages) Auto-extracted Yes
144 bytes Partial dump (user data + signature, no system pages) Not available No
80 bytes User data only Not available N/A

Key methods

// Read
tag.pretty(db, sigResult)              // → string   human-readable summary
                                       //            Quantity section shows "(= 750 g)" hint when unit ≠ base
tag.describe(db)                       // → string   LLM-friendly paragraph
                                       //            Quantity sentence includes "— 750 g available, 1000 g total"
tag.toDict(db)                         // → object   JSON-serializable, all labels resolved
                                       //            .measure includes measure_gr/ml/mm/mm2 + measure_available_*
tag.toRawDict()                        // → object   raw protocol fields, no resolution
                                       //            includes measure_gr/ml/mm/mm2 + measure_available_* (base-unit)
                                       //            color_r2/g2/b2 and color_r3/g3/b3 are zeroed for inactive slots
                                       //            num_colors — active color slot count from aspect DB (1/2/3)
                                       //            color_list — string[] of #RRGGBB for active slots only
                                       //            tag_info — raw u8 at +39 (index << 4 | count)
tag.toBytes(includeSignature = false)  // → Buffer   re-serialize to chip bytes
tag.validate()                         // → string[] sanity check — list of warnings
                                       //            (includes tag index > tag count checks)
tag.verify(db)                         // → SignatureResult

// Write (immutable — all return a new TigerTag)
TigerTag.create(fields)               // → TigerTag  build from scratch
TigerTag.asInit(uid)                  // → TigerTag  blank Init tag
TigerTag.erase()                      // → Buffer(80) zero bytes — write to chip to wipe
TigerTag.fromRawDict(raw)             // → TigerTag  from toRawDict() snake_case object
TigerTag.fromCloudDoc(doc)            // → TigerTag  from Firestore cloud doc (data1-data7, TD)
tag.patch(fields)                     // → TigerTag  surgical camelCase field update
tag.patchFromRawDict(raw)             // → TigerTag  surgical snake_case field update

// Cloud (TigerTag+ only — uses built-in fetch, Node.js 18+)
tag.rawApi(timeout)                   // → Promise<object|null>  fetch live product data
tag.diffApi(apiData, db)              // → Promise<ApiDiff[]>    compare chip vs API
tag.patchFromApi(apiData, db)         // → Promise<[TigerTag, ApiDiff[]]>  apply API values
tag.syncDb(dbPath, force)             // → Promise<string[]>     update reference databases

Key properties

tag.isMaker           // true if idProduct === 0xFFFFFFFF
tag.isInit            // true if idProduct === 0x00000000
tag.isPlus            // true if idProduct is a valid cloud ID
tag.isSigned          // true if signature bytes are non-zero
tag.uidHex            // "04AABBCCDDEE11" or null
tag.color1Hex         // "#FF3232"
tag.tdValue           // 12.5  (HueForge Transmission Distance)
tag.tagInfo           // 0x12  raw u8 at +39, reads "index/count" (0x00 = unknown, tags written before v2.2)
tag.tagIndex          // 1     which tag this one is, from 1 (high nibble) — 0 unknown
tag.tagCount          // 2     TigerTags on the item (low nibble) — 0 unknown, 1 single tag, 2 twin tag
tag.manufacturingDate // Date (UTC)
tag.stockPercent      // 75.0  or null
tag.productPageUrl    // "https://tigertag.io/products/..." or null
tag.apiUrl            // "https://api.tigertag.io/..." or null
tag.imgUrls           // { icon16, icon32, thumbnail, small, medium, large, original }
                      // CDN image URLs — TigerTag+ only; null for Maker / Init tags

Write / CRUD operations

const { TigerTag } = require('tigertag');

// Build a new tag from scratch
const tag = TigerTag.create({
  uid: Buffer.from('04A1B2C3D4E5F6', 'hex'),
  idMaterial: 38219,        // PLA
  idBrand: 19961,           // Rosa3D
  nozzleTempMin: 195,
  nozzleTempMax: 230,
  color1R: 255, color1G: 0, color1B: 0, color1A: 255,
  measure: 1000, idUnit: 21,
  // measureAvailable: 750,  // optional — partial spool; defaults to measure (full)
  // tagCount: 2, tagIndex: 1,  // optional — twin tag, tag 1 of 2 → byte +39 = 0x12 (default 0 = unknown)
});

// Blank TigerTag Init chip (ready for programming)
const initTag = TigerTag.asInit(Buffer.from('04A1B2C3D4E5F6', 'hex'));

// Erase a chip — write the returned 80 bytes to the NFC chip
const blankBytes = TigerTag.erase();

// Immutable surgical update — returns a new TigerTag, original unchanged
const patched = tag.patch({ nozzleTempMin: 200, dryTemp: 55 });

// Tag index / tag count (protocol v2.2) — write tag 2 of a twin tag.
// Both tags of an item (a filament spool, a resin bottle…) share the same tagCount and timestamp.
// describe() then says "Tag 2 of 2 on this filament." (the idType label; "item" when unknown).
// tagInfo is not covered by the ECDSA signature: changing it never invalidates a signed tag.
const second = tag.patch({ tagCount: 2, tagIndex: 2 });   // byte +39 = 0x22

// TigerTag+ cloud sync
const apiData = await tag.rawApi();           // fetch live product data
const diffs = await tag.diffApi(apiData);     // what differs chip vs cloud?
const [patchedTag, applied] = await tag.patchFromApi();  // apply all cloud values
console.log(`${applied.length} field(s) updated from cloud`);

Protected fields — patch() throws if you try to modify: idTigertag, idProduct, uid, signatureR, signatureS.

Cloud document → chip write pipeline

fromCloudDoc() maps the Firestore document format to chip fields. Combine with patchFromRawDict() for surgical overrides before writing.

// Full write: Firestore doc → chip bytes (80 bytes, pages 0x04–0x17)
const tag = TigerTag.fromCloudDoc(firestoreDoc);
const bytes = tag.toBytes();          // → Buffer(80) ready for NFC write

// Surgical patch: only override td_raw and message, keep all other fields
const patched = TigerTag.fromCloudDoc(firestoreDoc)
  .patchFromRawDict({ td_raw: 150, message: 'Opened 2025-06' });
const bytes = patched.toBytes();

// From a stored toRawDict() snapshot — same snake_case shape
const tag2 = TigerTag.fromRawDict(storedRawDict);
const patched2 = tag2.patchFromRawDict({ measure_available: 650 });

Firestore field mapping used by fromCloudDoc():

Firestore field Chip field
data1 idDiameter
data2 nozzleTempMin
data3 nozzleTempMax
data4 dryTemp
data5 dryTime
data6 bedTempMin
data7 bedTempMax
TD tdRaw (float × 10 → integer, e.g. 1.5 → 15)
weight_available / measure_gr measureAvailable

TigerTag+ from the official catalogue

Give only a TigerTag+ product ID and get a complete tag, ready to burn. The official catalogue (id_catalog.json, 14 000+ products, ~12 MB) is not bundled: the SDK downloads it on first use (internet needed once) and caches it in a per-user cache folder (~/Library/Caches/tigertag, %LOCALAPPDATA%\tigertag, $XDG_CACHE_HOME/tigertag or ~/.cache/tigertag; override with TIGERTAG_CACHE_DIR).

const { TigerTag, catalogEntry, refreshCatalog, catalogInfo } = require('tigertag');

const tag   = await TigerTag.fromCatalog(3527039449);   // Elegoo Rapid TPU 95A - Black
const bytes = tag.toBytes();                            // 80 bytes, ready to write

// Twin tag: same timestamp on both tags
const ts   = Math.floor((Date.now() - Date.UTC(2000, 0, 1)) / 1000);
const tag1 = await TigerTag.fromCatalog(3527039449, { tagCount: 2, tagIndex: 1, timestamp: ts });
const tag2 = await TigerTag.fromCatalog(3527039449, { tagCount: 2, tagIndex: 2, timestamp: ts });

// Display metadata (title, brand, sku, barcode, img_src, material, measure…)
const entry = await catalogEntry(3527039449);

// The catalogue changes every day: check for a new version now (ETag — unchanged = 304, no download)
await refreshCatalog();
catalogInfo();   // { downloaded, count, fetchedAt, checkedAt, url, etag, lastModified, cacheFile }
Catalogue RFID_Data TigerTag field
id_material, id_aspect1, id_aspect2 (null → 0, none), id_type, id_brand, id_unit, measure same names (camelCase)
color_r/g/b/a colour 1 (RGBA)
color_r2…b2, color_r3…b3 (when present), otherwise color_info.colors[1] / [2] colour 2 / colour 3
data1 idDiameter
data2 / data3 nozzleTempMin / nozzleTempMax
data4 / data5 dryTemp / dryTime
data6 / data7 bedTempMin / bedTempMax

null values become 0. A product without RFID_Data (a few resins) throws a clear error, as does an unknown ID or an offline first use with no cached copy. loadCatalog({ url, cacheDir, maxAge, force }) returns the whole catalogue as a Map (id → entry) and checks for a new version once the cached copy is older than maxAge (default 1 day, since the catalogue changes every day); TigerTag.fromCatalogEntry(entry, options) builds the tag from an entry you already have.

ApiDiff

ApiDiff is a plain object { field, chipValue, apiValue }:

const { TigerTag } = require('tigertag');

const tag = TigerTag.fromPages(uid, payload);
const diffs = await tag.diffApi();

for (const d of diffs) {
  console.log(`${d.field}: chip=${d.chipValue}  →  api=${d.apiValue}`);
}

Fields compared: nozzle_min, nozzle_max, bed_min, bed_max, dry_temp, dry_time, type, material, brand, diameter, aspect_1, aspect_2, color_1, color_2, color_3, measure_g, measure_unit.


Signature verification

const result = tag.verify();   // fully autonomous — finds the public key from the bundled DB

result.ok        // true only for VALID
result.status    // "valid" | "invalid" | "unsigned" | "no_key" | "no_uid"
String(result)   // "VALID" | "INVALID" | "NOT SIGNED" | "NO PUBLIC KEY — …" | …
result.toDict()  // { status: "valid", ok: true, detail: "…" }
Status Meaning
valid Signature matches — chip is authentic
invalid Signature present but does not match UID + data
unsigned No signature bytes — Maker tag or unverified
no_key No matching public key in database for this protocol version
no_uid UID not provided — cannot verify (use fromPages(uid, payload))

ECDSA-P256 verification uses the public key bundled in database/id_version.json — works fully offline, no external dependencies (Node.js built-in crypto module).


Database (TigerTagDB)

const { TigerTagDB } = require('tigertag');

const db = new TigerTagDB();                         // freshest local data, daily check in the background
const db = await TigerTagDB.open();                  // waits for the daily check (5 s max, never throws)
const db = new TigerTagDB({ offline: true });        // zero network calls
const db = new TigerTagDB({ dbPath: '/my/tables' }); // your own files, used exclusively

db.material(38219)     // { id: 38219, label: "PLA", density: 1.24, ... }
db.brand(1)            // { id: 1, label: "Generic", ... }
db.version(0x01000001) // { id: ..., label: ..., public_key: "-----BEGIN..." }
TigerTagDB.label(entry) // safe label extraction helper

Reference data: offline, automatic and manual updates

The reference data is the 7 tables (id_version, id_material, id_aspect, id_type, id_diameter, id_brand, id_measure_unit + last_update.json) and the product catalogue (id_catalog.json). It is always available offline: a copy ships in the package (database/, the catalogue as id_catalog.json.gz, refreshed at every release), and kept fresh automatically.

Where each table comes from

Priority Source When
1 dbPath (your own folder) Used exclusively: a missing file is an error, there is no fallback and no automatic network call
2 Downloaded copy in the data dir When its last_update.json timestamp is newer than the bundled one
3 Bundled copy (database/ in the package) Always present — the fallback

The data dir holds the downloaded copies: dataDir option, else TIGERTAG_DATA_DIR, else TIGERTAG_CACHE_DIR, else the per-user cache folder (~/Library/Caches/tigertag, %LOCALAPPDATA%\tigertag, $XDG_CACHE_HOME/tigertag or ~/.cache/tigertag). Point it at a project folder to keep the data with your project.

Automatic update (autoUpdate, default on): new TigerTagDB() is synchronous and never waits for the network — it loads the freshest local copy and starts ONE background check per process (await db.ready resolves when it is done; the instance is then reloaded). await TigerTagDB.open() runs the same check before returning. The check runs at most once per maxAge (default 1 day, tracked in db_state.json in the data dir; retried after 1 h when it failed), makes ONE request to https://api.tigertag.io/api:tigertag/all/last_update (GitHub mirror as fallback), downloads only the changed tables, uses a ~5 s timeout and never throws (verbose: true logs it). autoUpdate: false disables only this check (autoSync is a deprecated alias). The catalogue shares the data dir: TigerTag.fromCatalog() checks for a new catalogue once its copy is older than 1 day and works offline from the bundled .gz.

Offline: offline: true on TigerTagDB, loadCatalog, fromCatalog, the CLI --offline flag, or TIGERTAG_OFFLINE=1 → zero network calls.

Manual update

const db = new TigerTagDB();
await db.update();                    // → ['id_brand.json', 'last_update.json'] (only what changed)
await db.update({ force: true });     // re-download every table
await db.update({ catalog: true });   // tables + product catalogue
db.info();   // { offline, autoUpdate, dataDir, customDir, lastCheck, lastError,
             //   tables: { brands: { file, source: 'custom'|'downloaded'|'bundled', path, timestamp }, … },
             //   catalog: { source, count, fetchedAt, checkedAt, … } }
tigertag update                       # tables, into the data dir
tigertag update --force --catalog     # everything, re-downloaded
tigertag update --data-dir ./refdata  # into a project folder
tigertag update --db ./my-tables      # into your own (exclusive) folder
tigertag dump.bin --offline           # parse with no network call

db.sync(force) and syncDatabases(folder) still work (sync() is now an alias of update()).

Behaviour change in 1.2.0: a custom dbPath is used exclusively — a missing file now throws instead of silently falling back to the bundled copy; new TigerTagDB() checks for updates once a day in the background (into the data dir, never into the package folder); syncDb() / tigertag --sync-only without a folder update the data dir instead of the bundled database/ folder.


NFC SDK integration

fromPages() accepts exactly what NFC SDKs provide:

// Node.js — nfc-pcsc (ACR122U / PN532)
reader.on('card', async (card) => {
  const uid = Buffer.from(card.uid, 'hex');        // 7 bytes
  const payload = await reader.read(4, 144, 4);    // pages 4–39, 144 bytes
  const tag = TigerTag.fromPages(uid, payload);
  console.log(tag.pretty());
  console.log(String(tag.verify()));               // VALID / NOT SIGNED
});

ACR122U — full example (nfc-pcsc)

npm install nfc-pcsc tigertag
const { NFC } = require('nfc-pcsc');
const { TigerTag } = require('tigertag');

const nfc = new NFC();

nfc.on('reader', (reader) => {
  reader.autoProcessing = false;

  reader.on('card', async (card) => {
    try {
      const uid = Buffer.from(card.uid, 'hex');         // 7 bytes
      const payload = await reader.read(4, 144, 4);     // pages 4–39, 144 bytes
      const tag = TigerTag.fromPages(uid, payload);
      console.log(tag.pretty());
      console.log(String(tag.verify()));                // VALID / NOT SIGNED / INVALID
    } catch (err) {
      console.error(err);
    }
  });
});

See examples/integrate_nfc_sdk.js for patterns covering Android, iOS, Flutter, Arduino, and Electron/nfc-pcsc.


Chip memory layout

NTAG chip memory layout — pages 0x04–0x27


Protocol memory layout

Pages 0x04–0x27  (144 bytes: user data + ECDSA signature)

Offset  Size  Field
──────────────────────────────────────────────────────
0x00    4     idTigertag        u32 BE — version identifier
0x04    4     idProduct         u32 BE — 0xFFFFFFFF=Maker, 0=Init, else cloud ID
0x08    2     idMaterial        u16 BE
0x0A    1     idAspect1         u8
0x0B    1     idAspect2         u8
0x0C    1     idType            u8
0x0D    1     idDiameter        u8
0x0E    2     idBrand           u16 BE
0x10    4     color1 RGBA       u8×4
0x14    3     measure           u24 BE
0x17    1     idUnit            u8
0x18    2     nozzleTempMin     u16 BE
0x1A    2     nozzleTempMax     u16 BE
0x1C    1     dryTemp           u8
0x1D    1     dryTime           u8 hours
0x1E    1     bedTempMin        u8
0x1F    1     bedTempMax        u8
0x20    4     timestamp         u32 BE — seconds since 2000-01-01 UTC
0x24    3     color2 RGB        u8×3
0x27    1     tagInfo           u8 — high nibble tag index, low nibble tag count (0x12 = tag 1 of 2, 0 = unknown)
0x28    3     color3 RGB        u8×3 + 0x00 padding
0x2C    2     tdRaw             u16 BE — HueForge TD × 10
0x2E    2     (padding)
0x30    28    customMessage     UTF-8, zero-padded
0x4C    3     measureAvailable  u24 BE
0x4F    1     (padding)
── Signature (only in 144-byte payload) ──────────────
0x50    32    signatureR        ECDSA-P256 R component
0x70    32    signatureS        ECDSA-P256 S component

Dump formats:

Size Format
180 bytes Full chip dump (pages 0–44) — UID auto-extracted
144 bytes User data + signature (pages 0x04–0x27)
80 bytes User data only (pages 0x04–0x17)

ECDSA signature scheme

Signed message = SHA-256( uid_bytes(7) + id_tigertag_BE(4) + id_product_BE(4) )
Signature      = 64 bytes raw: R(32) + S(32)
Algorithm      = ECDSA-P256 (prime256v1)
Public key     = PEM in database/id_version.json[].public_key

Uses Node.js built-in crypto — no node-forge, no OpenSSL wrappers, no external dependencies.


CLI

# Parse a dump file
tigertag dump.bin

# JSON output
tigertag dump.bin --json

# Raw protocol fields (no DB lookup)
tigertag dump.bin --raw

# Use a custom database folder (exclusively)
tigertag dump.bin --db /path/to/db

# Parse with no network call at all
tigertag dump.bin --offline

# Update the reference tables now (into the data dir); --catalog adds the product catalogue
tigertag update
tigertag update --force --catalog --data-dir ./refdata

# Show version
tigertag --version

# Also available as a module runner:
node -e "require('tigertag')"

Electron integration

// main.js — replace parseTigerTag() subprocess dependency
const { TigerTag, TigerTagDB } = require('tigertag');

// Called from your NFC reader callback
function parseTigerTag(payload, uid) {
  const tag = TigerTag.fromPages(Buffer.from(uid), Buffer.from(payload));
  const db  = new TigerTagDB();
  return {
    dict: tag.toDict(db),
    raw:  tag.toRawDict(),
    sig:  tag.verify(db).toDict(),
  };
}

Exports

const {
  TigerTag,
  TigerTagDB,
  SignatureResult,
  ApiDiff,
  syncDatabases,
  loadCatalog,
  refreshCatalog,
  catalogInfo,
  catalogEntry,
  catalogCacheDir,
  CATALOG_URL,
  ID_TIGERTAG,
  ID_TIGERTAG_PLUS,
  ID_TIGERTAG_INIT,
  MAKER_PRODUCT_ID,
  INIT_PRODUCT_ID,
} = require('tigertag');

Examples

File Description
examples/basic_parse.js Parse a TigerTag payload, inspect fields
examples/verify_signature.js Full ECDSA sign → verify round-trip
examples/integrate_nfc_sdk.js Integration patterns for all major NFC SDKs

Tests

npm test

75 tests across all SDK features. Uses fixtures from test/fixtures/ — no NFC hardware needed.

To regenerate fixtures:

node scripts/generate_fixtures.js

Requirements

  • Node.js 18+ (uses built-in fetch and crypto)
  • No external runtime dependencies

TigerTag ecosystem

Official hardware

Device Description Cost
TigerTag Pod Plug-and-play NFC reader/writer — connects to desktop (Studio Manager) or phone (RFID Connect app). Read and write chips with no soldering, no setup. —
Tiger Scale Open-source DIY ESP32 smart scale — reads the tag on scan, weighs the spool, and updates measureAvailable on the chip in real time. Full BOM and firmware available. ~30 € in parts

Official software

Tool Platform Description
TigerTag-RFID-Guide Spec Open protocol specification
TigerTag-SDK-JS Node.js This SDK — parse, verify, write, diff
TigerTag-SDK-Python Python Python port — parse, verify, write, diff
TigerTag Studio Manager Windows / macOS / Linux Open-source desktop inventory manager — works with TigerTag Pod and ACR122U
TigerTag RFID Connect iOS Official mobile app — read/write using the phone's built-in NFC
TigerTag RFID Connect Android Official mobile app — read/write using the phone's built-in NFC
TigerTag Firebase Integration Cloud Firebase backend integration example
Tiger Scale ESP32 firmware Open-source firmware for the DIY smart scale

Community integrations: OpenRFID · Home Assistant · Snapmaker U1 firmware · TD1s by Ajax


Support the project

TigerSystem is a personal, community open-source project, built and maintained in free time. Everything stays free; if it saves you a spool or two, you can support it:

Support goes to the maintainer — never required, always appreciated.

License

Open source: Apache License 2.0 — see LICENSE

This SDK is Apache-2.0, which carries an express patent grant.

The TigerTag protocol itself requires no licence and no payment to implement, in any product, open source or proprietary, at any volume. See LICENSING.md.

Trademark, TigerTag+ signature issuance, official product-ID allocation, and officially supplied media are separate from the protocol — see LICENSE_COMMERCIAL.md. Contact licensing@tigertag.io

Protocol spec: github.com/TigerTag-Project/TigerTag-RFID-Guide


About

JavaScript / Node.js SDK for TigerTag — the open NFC identification standard for 3D-printing materials (Apache-2.0).

Topics

Resources

Security policy

Stars

7 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages