rbxsync
Declaratively manage Roblox game passes, badges, and developer products from a single TOML config file.
rbxsync syncs your local configuration to Roblox, tracks remote state in a lockfile, detects icon changes with BLAKE3 hashing, and generates a Luau module with all your asset IDs.
Features
- Declarative config - Define all your passes, badges, and products in a single
rbxsync.toml - Two-way sync - Push local changes to Roblox or pull remote state to your config and lockfile
- Icon management - Upload icons, detect changes via BLAKE3 hashing, download remote icons
- Conflict detection - Detects when remote icons differ from local and lets you choose which to keep
- Code generation - Generates a Luau module (+ optional TypeScript definitions) mapping resource names to asset IDs
- Flat & nested styles - Choose between flat path-like keys or nested tables
- Custom codegen paths - Remap sections and individual items to custom paths
- Extra entries - Inject asset IDs for manually managed assets or other universes into the generated file
- Alpha bleed - Applies alpha bleeding to icons before uploading (enabled by default)
- Duplicate detection - Warns when multiple remote resources share the same name
Installation
Rokit
Cargo
Quick Start
From an existing experience
This fetches all your existing passes, badges, and products, downloads their icons, and generates both the config and lockfile. You can find your universe ID in the Creator Hub under your experience's URL.
From scratch
This creates a rbxsync.toml template. Edit it with your universe ID, creator info, and resources, then run:
Commands
Initialize a new config file.
| Flag | Description |
|---|---|
--from-remote |
Populate config from existing remote resources |
--universe-id |
Universe ID (required with --from-remote) |
Sync local config to Roblox. Creates, updates, and tracks resources.
| Flag | Description |
|---|---|
--dry-run |
Show what would change without applying |
--only |
Only sync specific types: passes, badges, products (comma-separated) |
--badge-cost |
Expected cost in Robux when creating a badge (default: 0) |
Pull remote state into the config and lockfile.
Remote is the source of truth: remote-visible fields (name, price, description, etc.) are updated in the config while config-only fields (icon, path, regional_pricing) are preserved. New remote resources are added to the config.
| Flag | Description |
|---|---|
--dry-run |
Show what remote state differs without writing anything |
--accept-remote |
Download remote icons and update local files |
--accept-local |
Keep local icons and re-upload on next sync |
Validate config, check lockfile consistency, and report if anything is out of sync.
Rename a resource key in both config and lockfile. The display name is preserved automatically.
List remote resources. resource is one of: passes, badges, products.
Configuration
rbxsync requires a rbxsync.toml file in the working directory (or specify with --config).
[]
= 123456789
[]
= "group" # "user" or "group"
= 123456
[]
= "src/shared/GameIds.luau"
# typescript = false # Also generate a .d.ts file
# style = "flat" # "flat" (default) or "nested"
[]
= true # Apply alpha bleed before uploading (default: true)
= "icons" # Directory for downloaded icons (default: "icons")
[]
= "VIP Pass" # explicit display name on Roblox
= 499
= "VIP access to exclusive areas"
= "icons/vip.png"
[] # name defaults to "Premium"
= 999
= "Premium membership"
= "icons/premium.png"
[] # name defaults to "Welcome"
= "Welcome to the game!"
= "icons/welcome.png"
= true
[] # name defaults to "Coins100"
= 99
= "100 coins"
= "icons/coins.png"
| Field | Type | Description |
|---|---|---|
universe_id |
u64 |
Your Roblox universe ID |
creator.type |
string |
"user" or "group" |
creator.id |
u64 |
Your Roblox user or group ID |
| Field | Type | Default | Description |
|---|---|---|---|
output |
string |
-- | Path to generate the Luau module (omit to disable) |
typescript |
bool |
false |
Also generate a TypeScript definition file (.d.ts) |
style |
string |
"flat" |
"flat" or "nested" (see Code Generation) |
Override the default section name for each resource type. Dot-separated segments become either a prefix (flat) or nested tables (nested).
| Field | Type | Default | Description |
|---|---|---|---|
passes |
string |
"passes" |
Path for game passes |
badges |
string |
"badges" |
Path for badges |
products |
string |
"products" |
Path for developer products |
[]
= "gamepasses"
= "achievements"
= "devproducts"
Inject asset IDs into the generated file. Useful for manually managed assets or assets from other universes that you still want available in code.
[]
= 1234567
= 9876543
| Field | Type | Default | Description |
|---|---|---|---|
bleed |
bool |
true |
Apply alpha bleed to images before uploading. Changing this won't invalidate the lockfile or reupload existing images |
dir |
string |
"icons" |
Directory for icons downloaded by pull --accept-remote |
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
No | Display name (defaults to the TOML key) |
price |
u64 |
No | Price in Robux (omit for free) |
description |
string |
No | Pass description |
icon |
string |
No | Path to icon file |
for_sale |
bool |
No | Whether the pass is for sale (default: true) |
regional_pricing |
bool |
No | Enable regional pricing (default: false) |
path |
string |
No | Override the codegen path for this item |
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
No | Display name (defaults to the TOML key) |
description |
string |
No | Badge description |
icon |
string |
No | Path to icon file |
enabled |
bool |
No | Whether the badge is active (default: true) |
path |
string |
No | Override the codegen path for this item |
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
No | Display name (defaults to the TOML key) |
price |
u64 |
Yes | Price in Robux |
description |
string |
No | Product description |
icon |
string |
No | Path to icon file |
for_sale |
bool |
No | Whether the product is for sale (default: true) |
regional_pricing |
bool |
No | Enable regional pricing (default: false) |
store_page |
bool |
No | Show on the store page (default: false) |
path |
string |
No | Override the codegen path for this item |
Authentication
rbxsync uses the Roblox Open Cloud API. Create an API key at https://create.roblox.com/dashboard/credentials.
You can provide the key via the --api-key flag or the RBXSYNC_API_KEY environment variable:
The --api-key flag takes precedence over the environment variable.
Required API scopes
| Resource | Scopes | Documentation |
|---|---|---|
| Game Passes | game-pass:read, game-pass:write |
Game Passes API |
| Developer Products | developer-product:read, developer-product:write |
Developer Products API |
| Badges | legacy-universe.badge:read, legacy-universe.badge:write, legacy-universe.badge:manage-and-spend-robux |
Badges API, Universes - Badges |
| Assets (icons) | legacy-asset:manage |
Assets |
Code Generation
When codegen.output is set, rbxsync generates a Luau module after every sync. The variable name is derived from the filename.
Styles
Flat (default)
Items are stored with dot-separated path keys. This works well with TypeScript as the string-literal keys provide full autocomplete.
[]
= "src/shared/GameIds.luau"
= "flat"
-- This file is auto-generated by rbxsync. Do not edit manually.
local GameIds =
return GameIds
Access: GameIds["passes.VIP"]
Nested
Items are organized into nested tables. Better for Luau since it provides direct table access without string keys.
[]
= "src/shared/GameIds.luau"
= "nested"
-- This file is auto-generated by rbxsync. Do not edit manually.
local GameIds =
return GameIds
Access: GameIds.passes.VIP
Custom paths
Use [codegen.paths] to remap entire sections, or per-item path to override individual items:
[]
= "gamepasses"
= "achievements"
= "devproducts"
[]
= 99
= "shop.specials" # overrides the section default
With style = "nested", this produces:
local GameIds =
With style = "flat":
local GameIds =
Extra entries
Inject asset IDs for manually managed assets or other universes into the generated file:
[]
= 1234567
= 9876543
These entries are merged alongside synced resources in the output. With style = "flat":
local GameIds =
TypeScript
When typescript = true, a .d.ts file is generated alongside the Luau module:
// This file is auto-generated by rbxsync. Do not edit manually.
declare const GameIds: {
"badges.Welcome": number
"passes.VIP": number
"products.Coins100": number
}
export = GameIds
Key escaping
Resource names that aren't valid Luau identifiers are automatically escaped:
= 12345,
Lockfile
rbxsync generates a rbxsync.lock.toml that tracks remote state: asset IDs, icon hashes, and metadata. Commit this file to version control.
Icon Conflict Resolution
When you run pull and a remote icon differs from what's in the lockfile:
! pass 'VIP': icon differs from remote
Local: icons/vip.png (blake3: a1b2c3d4e5f6...)
Remote: asset 129268487446043
Resolve with:
--accept-remote-- Downloads the remote icon to your local path--accept-local-- Keeps your local icon and re-uploads it on nextsync
Attributions
Thank you to Tarmac for the alpha bleeding implementation, which was used in this project.