rbxsync 0.1.0

Declaratively manage Roblox game passes, badges, and developer products
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# 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](https://github.com/BLAKE3-team/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 pre-existing asset IDs into the generated file without syncing them
- **Alpha bleed** - Fixes resize artifacts on icons before uploading (enabled by default)
- **Duplicate detection** - Warns when multiple remote resources share the same name

## Installation

### Rokit

```sh
rokit add dev-bap/rbxsync
```

### Cargo

```sh
cargo install rbxsync
```

## Quick Start

### From scratch

```sh
rbxsync init
```

This creates a `rbxsync.toml` template. Edit it with your universe ID, creator info, and resources, then run:

```sh
rbxsync sync --api-key YOUR_API_KEY
```

### From existing remote resources

```sh
rbxsync init --from-remote --universe-id 123456 --api-key YOUR_API_KEY
```

This fetches all your existing passes, badges, and products, downloads their icons, and generates both the config and lockfile.

## Commands

### `rbxsync init`

Initialize a new config file.

| Flag | Description |
| --- | --- |
| `--from-remote` | Populate config from existing remote resources |
| `--universe-id` | Universe ID (required with `--from-remote`) |

### `rbxsync sync`

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`) |

### `rbxsync pull`

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. Detects icon conflicts.

| 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 |

### `rbxsync check`

Validate config, check lockfile consistency, and report if anything is out of sync.

### `rbxsync rename <resource> <old_key> <new_key>`

Rename a resource key in both config and lockfile. The display name is preserved automatically.

```sh
rbxsync rename passes VIP vip_pass
```

### `rbxsync list <resource>`

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`).

```toml
[experience]
universe_id = 123456789

[experience.creator]
type = "group"         # "user" or "group"
id = 35757120

[codegen]
output = "src/shared/GameIds.luau"
# typescript = false   # Also generate a .d.ts file
# style = "flat"       # "flat" (default) or "nested"

[icons]
bleed = true           # Apply alpha bleed before uploading (default: true)
dir = "icons"          # Directory for downloaded icons (default: "icons")

[passes.VIP]
price = 499
description = "VIP access to exclusive areas"
icon = "icons/vip.png"

[passes.Premium]
price = 999
description = "Premium membership"
icon = "icons/premium.png"

[badges.Welcome]
description = "Welcome to the game!"
icon = "icons/welcome.png"
enabled = true

[products.Coins100]
price = 99
description = "100 coins"
icon = "icons/coins.png"
```

### Experience

| 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 |

### Codegen

| 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]#code-generation) |

#### `[codegen.paths]`

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 |

```toml
[codegen.paths]
passes = "player.vips"
badges = "rewards"
products = "shop.items"
```

#### `[codegen.extra]`

Inject pre-existing asset IDs into the generated file. Useful for assets that were created outside rbxsync (e.g. legacy passes, manually created products) but that you still want available in code.

```toml
[codegen.extra]
"passes.legacy_vip" = 1234567
"products.starter_pack" = 9876543
```

### Icons

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `bleed` | `bool` | `true` | Apply alpha bleed to fix resize artifacts |
| `dir` | `string` | `"icons"` | Directory for icons downloaded by `pull --accept-remote` |

### Game Passes

| 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 |

### Badges

| 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 |

### Developer Products

| 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](https://create.roblox.com/docs/cloud/open-cloud). Create an API key at https://create.roblox.com/dashboard/credentials and pass it via `--api-key`:

```sh
rbxsync sync --api-key YOUR_API_KEY
```

Or read it from a file:

```sh
rbxsync sync --api-key $(cat apikey.txt)
```

### Required API scopes

| Resource | Scopes | Documentation |
| --- | --- | --- |
| Game Passes | `game-pass:read`, `game-pass:write` | [Game Passes API]https://create.roblox.com/docs/cloud/api/game-passes |
| Developer Products | `developer-product:read`, `developer-product:write` | [Developer Products API]https://create.roblox.com/docs/cloud/api/developer-products |
| Badges | `legacy-universe.badge:read`, `legacy-universe.badge:write`, `legacy-universe.badge:manage-and-spend-robux` | [Badges API]https://create.roblox.com/docs/cloud/api/badges, [Universes - Badges]https://create.roblox.com/docs/cloud/features/universes#badges |
| Assets (icons) | `legacy-asset:manage` | [Assets]https://create.roblox.com/docs/cloud/features/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.

```toml
[codegen]
output = "src/shared/GameIds.luau"
style = "flat"
```

```lua
-- This file is auto-generated by rbxsync. Do not edit manually.

local GameIds = {
	["badges.Welcome"] = 98765,
	["passes.Premium"] = 67891,
	["passes.VIP"] = 67890,
	["products.Coins100"] = 11111,
}

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.

```toml
[codegen]
output = "src/shared/GameIds.luau"
style = "nested"
```

```lua
-- This file is auto-generated by rbxsync. Do not edit manually.

local GameIds = {
	badges = {
		Welcome = 98765,
	},
	passes = {
		Premium = 67891,
		VIP = 67890,
	},
	products = {
		Coins100 = 11111,
	},
}

return GameIds
```

Access: `GameIds.passes.VIP`

### Custom paths

Use `[codegen.paths]` to remap entire sections, or per-item `path` to override individual items:

```toml
[codegen.paths]
passes = "player.vips"
badges = "rewards"
products = "shop.items"

[products.special_offer]
price = 99
path = "shop.specials"       # overrides the section default
```

With `style = "nested"`, this produces:

```lua
local GameIds = {
	player = {
		vips = {
			VIP = 67890,
		},
	},
	shop = {
		items = {
			Coins100 = 11111,
		},
		specials = {
			special_offer = 12345,
		},
	},
}
```

With `style = "flat"`:

```lua
local GameIds = {
	["player.vips.VIP"] = 67890,
	["shop.items.Coins100"] = 11111,
	["shop.specials.special_offer"] = 12345,
}
```

### Extra entries

Inject pre-existing assets into the generated file without syncing them:

```toml
[codegen.extra]
"passes.legacy_vip" = 1234567
"products.starter_pack" = 9876543
```

These entries are merged alongside synced resources in the output, using the same flat/nested style.

### TypeScript

When `typescript = true`, a `.d.ts` file is generated alongside the Luau module:

```typescript
// 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:

```lua
["my-pass"] = 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 next `sync`

## Attributions

Thank you to [Tarmac](https://github.com/Roblox/tarmac) for the alpha bleeding implementation, which was used in this project.