A local-first, privacy-focused tracker for Roblox FastFlags. It browses the current live flag set across 13 platforms, keeps a full change history, and can notify a Discord webhook when a flag you're watching changes.
It pulls from Roblox's public client-settings endpoint — the same data every Roblox client fetches on launch, not anything privileged or scraped.
This is built to be cloned and run on your own machine, not deployed to a public server. The database, the poller, and the web UI all stay local by default. The only outbound network traffic is to Roblox's public settings endpoint and, if you configure one, your own Discord webhook — nothing else, no telemetry, no third-party accounts required.
- Searchable, filterable browser for the live flag set on any of 13 tracked platforms (desktop, mobile, console, Studio, and installer variants)
- Full changelog with before/after diffs, per platform
- Per-flag history view
- Favorites/watchlist with optional Discord notifications when a watched flag changes
- Raw JSON export per platform, in the same
{name: value}shape Roblox's own endpoint returns - A continuous background poller (
npm run bot) instead of relying on an external cron service
npm install
cp .env.local.example .env.local # defaults work as-is for local dev
npm run db:migrate # creates local.db
npm run poll # first poll — populates the flag table
npm run devOpen http://localhost:3000.
Two ways to poll:
npm run poll— one-shot, fetches all platforms once and exits.npm run bot— continuous: loops forever, polling everyPOLL_INTERVAL_MS(default 2 minutes). This is the intended way to run it day-to-day.
The bot loops internally, so it only needs to be started once and left running — register it as a single long-running Task Scheduler task rather than a repeating one:
$Action = New-ScheduledTaskAction -Execute "wscript.exe" -Argument '"<project path>\scripts\run-bot-hidden.vbs"' -WorkingDirectory "<project path>"
$Trigger = New-ScheduledTaskTrigger -AtStartup
$Principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
$Settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances IgnoreNew -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -Hidden
Register-ScheduledTask -TaskName "FFlagsTrackerBot" -Action $Action -Trigger $Trigger -Principal $Principal -Settings $Settings
Start-ScheduledTask -TaskName "FFlagsTrackerBot" # starts it now instead of waiting for next bootThis runs as SYSTEM with an AtStartup trigger, so the bot starts at boot before anyone logs in, without Task Scheduler needing to store a Windows account password. Prefer it tied to your own account instead, starting only once you sign in? Use New-ScheduledTaskTrigger -AtLogOn and drop -Principal (defaults to whichever account registers the task).
A console window opens and never closes. The task above launches scripts/run-bot-hidden.vbs, a WScript.Shell.Run(..., 0, False) wrapper, specifically to avoid this — plain cmd.exe under a Task Scheduler action opens a visible window that stays open for as long as the bot runs (forever, by design). If you're seeing a window, check the task's action is pointed at the .vbs wrapper and not calling cmd.exe/npm directly.
The bot exits or stops polling after ~5 minutes. Task Scheduler's default ExecutionTimeLimit is 5 minutes, which is fine for a one-shot npm run poll but kills a long-running bot mid-loop. Make sure it's set to [TimeSpan]::Zero (unlimited), as in the snippet above.
SQLITE_BUSY: database is locked errors. lib/db/client.ts sets PRAGMA journal_mode = WAL and busy_timeout = 5000 on connect specifically so the web app and the bot can write to local.db concurrently without this — if you still hit it, check whether something else (a DB browser tool, another instance of the bot) has the file open too.
The bot doesn't seem to survive a restart. Depends on which trigger you used. AtStartup + SYSTEM (above) starts at boot, before login. AtLogOn only starts once the registered user actually signs in — expected behavior, not a bug, but worth knowing which one you set up.
The local setup above is the intended way to run this. To host it elsewhere instead, point DATABASE_URL at a Turso database (same libSQL driver, no code changes needed), set CRON_SECRET, and hit /api/cron/poll with Authorization: Bearer <CRON_SECRET> on a schedule via Vercel Cron or a GitHub Actions workflow instead of running the bot.
Note that app/actions.ts's favorite/notify actions have no auth — fine for a single local user, not fine if the app becomes reachable by anyone else. Add auth before taking this route.
See .env.local.example. DATABASE_URL defaults to a local SQLite file if unset; DISCORD_WEBHOOK_URL and CRON_SECRET are optional.
lib/roblox.ts— fetches and parses the Roblox client-settings feed; defines the 13 tracked platforms.lib/poll.ts— diffs the fetched flags against the DB and records changes, per platform.lib/db/schema.ts— Drizzle schema (flags,flag_changes,favorites,poll_runs), all keyed by(platform, name).scripts/poll.ts(one-shot),scripts/bot.ts(continuous loop),app/api/cron/poll/route.ts(remote trigger) — three entry points into the same poll logic.app/— live browser (/), changelog (/changelog), flag detail (/flag/[platform]/[name]), favorites (/favorites).app/api/flags/[platform]— raw JSON export of the current flag set for a platform.
Tracks LIVE flags across all 13 Roblox platforms (client, Studio, and installer variants). Does not track deployment channels (zcanary/zintegration) — the public settings endpoint only ever serves LIVE, and channel-specific data would require an authenticated Roblox session plus automating an actual Studio install, which this project deliberately doesn't do.
How that was verified
clientsettings.roblox.com/v2/settings/application/{platform} — the endpoint this project polls — only ever returns the LIVE channel, confirmed by:
- Requesting with a
?channel=zcanaryquery param, aRoblox-Channelheader, and anRBXChannelIdcookie — all three return a response byte-identical to a plain request. The endpoint silently ignores every channel-selection attempt tried. - Requesting
/bulk/{channel}and/{channel}path variants — both return404. - Requesting the actual channel-version endpoint,
clientsettingscdn.roblox.com/v2/client-version/{binaryType}/channel/{channel}(the same one Roblox's own tooling uses internally, identified via MaximumADHD's client-mod-manager source) —LIVEreturns real version data with no authentication, butzcanaryandzintegrationboth return401 Not authorized. This is the actual gate: non-LIVE channel data requires an authenticated Roblox session, not just a different request shape.
Getting real channel-specific flag values means installing and launching Roblox Studio bound to that channel and reading the local settings file it writes after syncing — genuine client automation, not an HTTP call. That's a different and more invasive category of thing than polling a public JSON endpoint, so this project deliberately doesn't do it.
Inspired by MaximumADHD/Roblox-FFlag-Tracker, which tracks all of the above plus deployment channels via real Studio automation. If channel-level data matters to you, that's the place to look.
Issues and pull requests are welcome. The codebase is a standard Next.js App Router project — see Structure above for the map.
MIT — free to use, modify, and redistribute; just keep the copyright notice.