Adding a Tracker to the Registry¶
Adding a new tracker to an existing platform (UNIT3D, Gazelle, GGn, Nebulance, MAM, or AvistaZ) requires one file and two lines in the barrel export. No adapter code needed.
If your tracker runs on a new platform, read Tracker API Responses first — you'll need to write an adapter before adding the registry entry.
Standardization Philosophy¶
Every tracker file follows the same field order and completeness rules. This makes diffs clean and reviews fast.
Every field must be present, even if empty. Use "" for empty strings, [] for empty arrays, and false for booleans. Never omit fields or use undefined — if it's there, you've decided on it.
abbreviation: "" // not: abbreviation: undefined
logo: "" // not: logo: undefined
trackerHubSlug: "" // not: trackerHubSlug: undefined
bannedGroups: [] // not: bannedGroups: undefined
warning: false // not: warning: undefined
Three exceptions:
- Omit the
statsblock entirely when you don't have real data. Don't include it withundefinedvalues. rules.fulfillmentPeriodHours,rules.hnrBanLimit, andrules.fullRulesMarkdownare truly optional — omit them when unknown.- Platform-specific fields (
gazelleAuthStyle,gazelleEnrich,unit3dAuthStyle) only belong in tracker files for their own platform. Don't add them to other trackers.
1. Copy the Template¶
Copy src/data/trackers/_template.ts to a new file matching your tracker's slug (lowercase, hyphens only):
Full template:
// src/data/trackers/_template.ts
//
// Copy this file to add a new tracker to the registry.
//
// 1. Duplicate this file and rename it to your tracker's slug (e.g. mytracker.ts)
// 2. Fill in all fields below — every field must be present (use "" / [] / false
// rather than omitting). See inline comments for guidance.
// 3. Export from src/data/trackers/index.ts (add to the barrel + ALL_TRACKERS array)
// 4. Run `pnpm test` to validate your entry
//
// Set draft: true while the entry is incomplete. Draft trackers skip strict
// validation in CI, so you can submit a PR with partial data.
//
// Allowed content categories:
// Movies, TV, Music, Games, Apps, Sports, Books, Audiobooks, Comics,
// Manga, Anime, XXX, Documentaries, Education, Tutorials, Fanres,
// iOS Apps, Graphics, Audio
//
// Validator checks:
// - slug: lowercase letters and hyphens only
// - platform: "unit3d" | "gazelle" | "ggn" | "nebulance" | "mam" | "custom"
// - apiPath must match platform default:
// unit3d → "/api/user"
// gazelle → "/ajax.php"
// ggn → "/api.php"
// nebulance → "/api.php"
// mam → "/jsonLoad.php"
// - url: https only
// - contentCategories: values must come from the allowed list above
// - language: required
// - rules: required (minimumRatio, seedTimeHours, loginIntervalDays as numbers)
import type { TrackerRegistryEntry } from "@/data/tracker-registry"
export const mytracker: TrackerRegistryEntry = {
// ── Identity ────────────────────────────────────────────────────────
slug: "mytracker", // lowercase, hyphens only (e.g. "my-tracker")
name: "My Tracker", // display name
abbreviation: "", // short code (e.g. "ATH", "RED") — "" if none
url: "https://example.com", // base URL (https only)
description: "TODO", // 1-2 sentence overview
// ── Platform & API ──────────────────────────────────────────────────
platform: "unit3d", // "unit3d" | "gazelle" | "ggn" | "nebulance" | "mam" | "custom"
// Platform-specific fields (uncomment for your platform):
// gazelleAuthStyle: "token", // gazelle only — "token" | "raw"
// gazelleEnrich: true, // gazelle only — enables enrichment call
// unit3dAuthStyle: "bearer", // unit3d only — "bearer" | "query"
apiPath: "/api/user", // unit3d: "/api/user" | gazelle: "/ajax.php" | ggn: "/api.php" | mam: "/jsonLoad.php"
// ── Content ─────────────────────────────────────────────────────────
specialty: "", // what the tracker is known for (e.g. "HD Movies", "Anime")
contentCategories: [], // see allowed list in header
language: "English",
// ── Visual ──────────────────────────────────────────────────────────
color: "#000000", // hex accent color for the tracker's detail page
logo: "", // "/tracker-logos/mytracker_logo.svg" — file must exist in public/ — "" if none
// ── External Links ──────────────────────────────────────────────────
trackerHubSlug: "", // slug on trackerhub.xyz, if listed — "" if none
statusPageUrl: "", // external status page URL — "" if none
// ── Community ───────────────────────────────────────────────────────
userClasses: [], // [{ name: "Power User", requirements: "Upload ≥ 100 GiB" }]
releaseGroups: [], // [{ name: "GrpName", description: "Encodes" }] or ["GrpName"]
bannedGroups: [], // ["GroupName"] — groups explicitly banned by the tracker
notableMembers: [], // ["handle"] — notable community figures
// ── Rules ───────────────────────────────────────────────────────────
rules: {
minimumRatio: 0, // 0 = no minimum
seedTimeHours: 0, // 0 = no minimum
loginIntervalDays: 0, // 0 = no login interval policy
// fulfillmentPeriodHours: 72, // optional — hours to complete H&R seeding
// hnrBanLimit: 3, // optional — number of H&Rs before ban
// fullRulesMarkdown: `...`, // optional — detailed rules as markdown string
},
// ── Status ──────────────────────────────────────────────────────────
warning: false, // true if the tracker has a known issue or is at risk
warningNote: "", // short description of the warning — "" if none
// ── Flags ───────────────────────────────────────────────────────────
draft: true, // remove (or set false) once all required fields are filled in
supportsTransitPapers: false, // true if the tracker supports transit papers export
profileUrlPattern: "", // e.g. "/user.php?id={id}" — required when supportsTransitPapers: true
// ── Stats (omit this block entirely if no real data is available) ───
// stats: {
// userCount: undefined,
// activeUsers: undefined,
// torrentCount: undefined,
// seedSize: undefined, // e.g. "500 TiB"
// statsUpdatedAt: undefined, // ISO 8601 date string
// },
}
2. Field Reference¶
Fields are documented in template order, grouped by section.
Identity¶
slug¶
Type: string
The unique identifier for this tracker. Used in filenames, URLs, and database lookups. Lowercase with hyphens only.
name¶
Type: string
Human-readable display name. This is what users see in the UI.
abbreviation¶
Type: string
A short code for the tracker, used in compact UI contexts. Use "" if none.
url¶
Type: string
The base URL including protocol. HTTPS only, no trailing slash.
The adapter appends apiPath to this URL to make API requests.
description¶
Type: string
One or two sentences about what the tracker is — content focus, community reputation, anything someone might want to know before joining.
description: "The largest general music tracker (also has some software). Has an interview to join, although the wait can be notoriously long."
Platform & API¶
platform¶
Type: "unit3d" | "gazelle" | "ggn" | "nebulance" | "mam" | "custom"
Which adapter handles API requests. This tells the scheduler how to fetch stats. Pick the one that matches the tracker's software.
| Platform | What it means |
|---|---|
"unit3d" |
Runs UNIT3D |
"gazelle" |
Runs Gazelle or a fork (Orpheus, Gazelle-Music, etc.) |
"ggn" |
GazelleGames only — has its own custom API |
"nebulance" |
Uses Nebulance's API |
"mam" |
MyAnonaMouse — cookie-based auth via mam_id |
"avistaz" |
AvistaZ network — cookie auth + profile scraping |
"custom" |
Placeholder, not implemented yet |
gazelleAuthStyle¶
Type: "token" | "raw" — Gazelle trackers only
Controls how the API token is sent in the request.
"token"— sends the token in anAuthorization: token TOKENheader (used by REDacted, Orpheus)"raw"— sends the token directly in theAuthorizationheader without a prefix
Only include this field for Gazelle trackers. If you are unsure which style a Gazelle tracker uses, check docs/kb/docs/contributing/tracker-responses-gazelle.md.
gazelleEnrich¶
Type: boolean — Gazelle trackers only
When true, the adapter makes a second API call (action=user&id=X) after the initial action=index call to get seeding/leeching counts, warned status, joined date, avatar, ranks, and community stats. Set this to true for all Gazelle trackers — without it, seeding and leeching will show as 0.
Only include this field for Gazelle trackers.
unit3dAuthStyle¶
Type: "bearer" | "query" — UNIT3D trackers only
Controls how the API token is sent in the request.
"bearer"— sends the token in anAuthorization: Bearer TOKENheader (required by UNIT3D v8+)"query"— sends the token as a?api_token=TOKENquery parameter (legacy UNIT3D)
Omit this field to use the default query parameter method. Set to "bearer" if the tracker's UNIT3D instance has been updated to v8+ and returns 401 with query param auth.
Only include this field for UNIT3D trackers.
apiPath¶
Type: string
The path appended to url for API requests. Must match the platform's actual endpoint.
| Platform | Default |
|---|---|
unit3d |
/api/user |
gazelle |
/ajax.php |
ggn |
/api.php |
nebulance |
/api.php |
avistaz |
/profile |
Almost all trackers use the platform default. Only change it if you've verified the tracker deviates.
AvistaZ trackers scrape HTML using browser cookies. All five AvistaZ sites (AvistaZ, AnimeZ, PrivateHD, CinemaZ, ExoticaZ) use the same adapter.
Content¶
specialty¶
Type: string
A short phrase describing what the tracker specializes in. Shown in the UI and used for filtering. Use "" if none.
contentCategories¶
Type: string[]
The categories of content hosted on the tracker. Must use values from the allowed list exactly as written (case-sensitive):
Movies, TV, Music, Games, Apps, Sports, Books, Audiobooks, Comics,
Manga, Anime, XXX, Documentaries, Education, Tutorials, Fanres,
iOS Apps, Graphics, Audio
language¶
Type: string
Primary language of the tracker. Use "English" for English-language trackers.
Visual¶
color¶
Type: string
A hex color code used to theme the tracker's detail page — chart colors, scrollbar, stat card accents. Pick something that represents the tracker's visual identity.
color: "#00d4ff" // Aither cyan
color: "#f44336" // REDacted red
color: "#7b1fa2" // GazelleGames purple
color: "#1a4fc2" // Nebulance blue
logo¶
Type: string
Path to the tracker's logo file in public/. Only set this if the file exists. Use "" if none. SVG preferred, PNG acceptable.
External Links¶
trackerHubSlug¶
Type: string
The tracker's slug on trackerhub.xyz, if the tracker is listed there. Used to link to its Trackerhub profile. Use "" if not listed.
statusPageUrl¶
Type: string
URL to an external status page. Many trackers have one at trackerstatus.info. Use "" if none.
Status¶
warning¶
Type: boolean
Set to true if there is something users should know before adding this tracker (for example, known API instability). Use false when there is no known issue.
warningNote¶
Type: string
Short description of the warning. Use "" if warning is false.
Flags¶
draft¶
Type: boolean
When true, the tracker is excluded from TRACKER_REGISTRY and skips strict validation in CI. Use this while you are filling in fields. Set to false (or remove the field) once the entry is complete.
supportsTransitPapers¶
Type: boolean
Set to true if the tracker supports the transit papers export feature. Use false otherwise.
profileUrlPattern¶
Type: string
The URL pattern used to construct a user's profile link. Required when supportsTransitPapers is true. Use "" if not applicable.
Stats¶
Omit the stats block entirely when you don't have real data. Don't include it with undefined values — the absence of the block signals that stats haven't been sourced yet.
When you have data, include only the fields you know:
stats: {
userCount: 12000,
torrentCount: 450000,
seedSize: "8.2 PiB",
statsUpdatedAt: "2025-09-01",
}
Available fields:
stats: {
userCount?: number
activeUsers?: number
torrentCount?: number
seedSize?: string // e.g. "500 TiB"
statsUpdatedAt?: string // ISO 8601 date string
}
3. User Classes¶
The userClasses array documents the tracker's rank/class system. Each entry is a TrackerUserClass:
interface TrackerUserClass {
name: string // required — display name of the class
requirements?: string // what it takes to reach this class
perks?: RankPerk[] // structured perks (optional, rarely populated)
icon?: string // path to an icon (optional)
}
For most trackers, name and requirements are enough. Write requirements as a human-readable summary — hit the key numeric thresholds (upload, ratio, account age, seed count) without being exhaustive.
// From aither.ts — upload-based progression
userClasses: [
{ name: "Leech", requirements: "Ratio below 0.4 — download privileges revoked" },
{ name: "Phobos", requirements: "Ratio ≥ 0.4. 4 download slots" },
{
name: "Zeus",
requirements:
"Upload ≥ 2 TiB or seed size ≥ 2 TiB, ratio ≥ 0.6, age ≥ 3 months, avg seedtime ≥ 10 days. 25 download slots",
},
// ...
// Staff-assigned ranks follow community ranks
{ name: "Uploader", requirements: "Staff — role model uploader. Freeleech, H&R immune" },
]
// From redacted.ts — simpler progression
userClasses: [
{ name: "User", requirements: "Default class on registration" },
{ name: "Member", requirements: "1 week, 25 GB up, 0.65 ratio" },
{ name: "Power User", requirements: "2 weeks, 25 GB up, 0.65 ratio, 5 torrents seeding" },
]
// From gazellegames.ts — achievement-point based
userClasses: [
{ name: "Amateur", requirements: "Default starting class" },
{
name: "Gamer",
requirements:
"600 achievement points. Invites, requests, collections, Top 10, peerlists, mass downloader",
},
]
If the tracker has no formal class system, leave the array empty:
The perks array accepts structured perk objects for when you want machine-readable perk data:
perks: [
{ type: "freeleech", label: "Freeleech on all torrents" },
{ type: "hnr-immune", label: "H&R immunity" },
{ type: "download-slots", label: "50 download slots" },
]
Valid RankPerkType values: "download-slots", "upload", "invite", "freeleech", "double-upload", "hnr-immune", "mod-bypass", "custom".
Structured perks are not required — a plain requirements string is enough for the UI to display the information.
4. Release Groups¶
The releaseGroups field accepts a mixed array — entries can be either a plain string or a ReleaseGroup object:
interface ReleaseGroup {
name: string
description?: string
}
releaseGroups: (string | ReleaseGroup)[]
Use the object form when you have something useful to say about what the group releases. Use a plain string when the name alone is sufficient (typically for banned groups, which belong in bannedGroups instead).
// From aither.ts — objects with descriptions
releaseGroups: [
{ name: "ATELiER", description: "Main house group — high-quality 1080p encodes and remuxes" },
{ name: "Kitsune", description: "Primary — WEB-DL" },
{
name: "MainFrame",
description: "Primary — 2160p encodes, secondary 1080p encodes and remuxes",
},
{ name: "ARTiCUN0" }, // object without description — name only
]
If the tracker has no notable internal groups, or you do not know them, use an empty array:
The bannedGroups field is a separate string[] for groups that are explicitly banned from the tracker:
5. Rules¶
The rules field documents the tracker's seeding and account policies. The type:
interface TrackerRules {
minimumRatio: number // required — 0 = no minimum
seedTimeHours: number // required — 0 = no minimum
loginIntervalDays: number // required — days before account is disabled; 0 = no policy
fulfillmentPeriodHours?: number // total hours allowed to complete H&R seeding
hnrBanLimit?: number // number of active H&Rs before downloading is blocked
fullRulesMarkdown?: string // the full rules as a markdown string
}
minimumRatio, seedTimeHours, and loginIntervalDays are required even if the tracker has no policy — use 0 to mean "not enforced."
// From nebulance.ts — ratioless tracker
rules: {
minimumRatio: 0, // ratioless
seedTimeHours: 72,
loginIntervalDays: 90,
}
// From aither.ts — full policy
rules: {
minimumRatio: 0.4,
seedTimeHours: 120,
loginIntervalDays: 90,
fulfillmentPeriodHours: 480, // 20 days to complete H&R seeding
hnrBanLimit: 3, // 3 active H&Rs → downloads blocked
fullRulesMarkdown: "...",
}
For fullRulesMarkdown, use array-join format. It keeps diffs clean and avoids multiline template literal indentation issues:
fullRulesMarkdown: [
"## Golden Rules",
"**1.** Do not defy the expressed wishes of the staff.",
"**2.** Access is a privilege, not a right.",
"",
"## Ratio System",
"Required ratio starts at 0.00 and rises as you download more.",
"",
"## Seeding Rules",
"- Torrents must be seeded for **72 hours** after snatching.",
].join("\n"),
6. Register in the Barrel File¶
Open src/data/trackers/index.ts and add in three places (alphabetized):
Export block:
Import block:
ALL_TRACKERS array:
The const name must match what you exported from your tracker file.
7. Verify It Works¶
Type check¶
This catches missing required fields or type mismatches. Fix all errors before proceeding.
Run the test suite¶
The test suite validates tracker registry entries: required fields, valid platform types, correct apiPath values per platform, valid content category names, and plain-string bannedGroups entries.
Check it in the UI¶
- Start the dev server:
pnpm dev - Go to
/trackers/new - Search for your tracker by name in the Add Tracker dialog
- Add it with a valid API token
- Click Poll Now on the tracker card
- Confirm the tracker status changes and stats appear in the dashboard
If the tracker appears in search results and polls successfully, the registry entry is correct.
8. Common Mistakes¶
Forgetting to add to the barrel file¶
The most common mistake. If you create src/data/trackers/mytracker.ts but skip editing index.ts, the tracker won't appear in the UI. Export and import it in index.ts, and add the variable to ALL_TRACKERS.
Wrong platform type¶
Using "unit3d" for a Gazelle tracker (or vice versa) causes the adapter to send the wrong API request. The scheduler will log a fetch error or return garbled data. Check the tracker's tech stack — most UNIT3D sites document /api/user, and most Gazelle sites use /ajax.php.
Wrong apiPath¶
Each platform has a default API path (see the field reference table above). Setting apiPath: "/api/user" on a Gazelle tracker will cause every poll to fail with 404. Match what the platform actually serves.
draft: true left in a finished entry¶
If draft: true, the entry is filtered out at runtime and never shown to users. Remove the field or set draft: false when you're done.
Invalid content category names¶
The contentCategories array only accepts the fixed allowed list. A typo like "Movie" instead of "Movies" will fail validation. The list is case-sensitive.
color not a valid hex code¶
The color field must be a full six-digit hex string starting with #. Shorthand hex (#fff) and named colors don't work. Use real hex.
Logo path points to a missing file¶
If you set logo: "/tracker-logos/mytracker.svg" but the file doesn't exist in public/, the image will 404 and show broken in the UI. Add the file or set logo: "".