Deploying places with nevermore deploy
nevermore deploy builds a Rojo project, uploads it to a Roblox place via the Open Cloud API, and (optionally) publishes the new version so players see it. nevermore test is built on the same pipeline, so everything that runs in a Roblox place goes through this command.
This guide walks you from zero to your first uploaded place. For advanced features (merging with a Studio-authored base place, smoke tests, batch deploys, CI), see Integration Testing.
When should I use this?
For most Roblox projects, especially solo work and small teams, you don't need this. Open Roblox Studio, click Save to Roblox or Publish to Roblox, and you're done. That's normally the right answer.
You probably want nevermore deploy when:
- Your code lives in a git repository and Studio is just where you preview it. Deploys should come from the same commit history that reviews and tests run against, not from whichever developer's Studio happens to be open.
- More than one programmer ships to the same place. Studio's publish flow is last-writer-wins, and a CLI deploy from CI gives you one path from "merge to main" to "live in game", traceable to a specific commit.
- You want one config to drive both tests and deploys.
nevermore deployandnevermore testboth readdeploy.nevermore.json, so the place you smoke-test against on every PR is configured exactly like the one you ship to. See Test Infrastructure. - You want CI to gate releases. Batch deploys plug into PR checks, so a deploy only runs after lint, tests, and smoke tests pass, and shows up as a PR comment instead of an ad-hoc Studio session.
If none of those apply, stick with Studio Publish. Come back when you outgrow it.
What deploy actually does
When you run nevermore deploy run:
- Reads
deploy.nevermore.jsonin your current directory and resolves the target you asked for (default:test). - Runs
rojo buildon the target'sprojectfile to produce an.rbxlplace file in a temp directory. - Uploads the
.rbxlto the configureduniverseId/placeIdover Open Cloud. - Saves the new version as a draft. If
--publishis passed, it is also published as the live version.
That's the whole pipeline. There are no deploy hooks or post-processing steps to register.
Prerequisites
- Node.js v18+ and the Nevermore CLI installed. The Install guide walks through both. The short version is
npm install -g @quenty/nevermore-cli, or usenpx nevermore ...from any package that depends on it. - Rojo v7+ on your
PATH. - A Roblox universe and place you own. You can create both at create.roblox.com/dashboard/creations.
- A Roblox Open Cloud API key. See Logging in below.
Logging in
nevermore deploy authenticates against Open Cloud with an API key. Create one at create.roblox.com/dashboard/credentials and grant it these scopes for the universe you want to deploy to:
| Scope | Used for |
|---|---|
universe-places:write | Uploading new place versions |
universe.place.luau-execution-session:write | Running scripts (used by nevermore test and smoke tests) |
universe.place.luau-execution-session:read | Reading script execution results |
legacy-asset:manage | Downloading a base place (only needed if you use basePlace) |
Save the key to your machine once:
nevermore login
This stores the key at ~/.nevermore/credentials.json (mode 0700) after validating it against Open Cloud. Other useful flags:
nevermore login --forceswaps the stored key.nevermore login --clearremoves it.nevermore login --statusshows what's loaded and re-validates it.
How the CLI finds your key
The CLI resolves credentials in this order (first match wins):
- The
--api-keyCLI flag - The
ROBLOX_OPEN_CLOUD_API_KEYenvironment variable - The
ROBLOX_UNIT_TEST_API_KEYenvironment variable (kept for backwards compatibility) ~/.nevermore/credentials.json(fromnevermore login)
In CI, set ROBLOX_OPEN_CLOUD_API_KEY as a secret. nevermore login is for local developer machines.
Setting up a package for deploy
deploy.nevermore.json is the only file the CLI needs to know about. The fastest way to create one is the interactive init wizard.
nevermore deploy init
From inside the directory you want to deploy from (a package under src/, a game under games/, or any directory with a package.json):
nevermore deploy init
The wizard:
- Detects a
test/default.project.jsonif one exists and offers it as the default Rojo project. - Detects
test/scripts/Server/ServerMain.server.lua(or.luau) and offers it as the default script template. - Walks up the filesystem looking for a parent
deploy.nevermore.jsonwith auniverseIdand reuses it. Once you have one game configured, sibling packages can inherit the universe automatically. - Lists every existing place in the universe so you can pick one, or offers to create a new place.
- Prints the resulting config and asks you to confirm before writing it.
Non-interactive setup
Pass --yes to skip prompts. You must supply enough flags for the wizard to resolve everything without asking:
nevermore deploy init --yes \
--universe-id 12345 \
--place-id 67890 \
--project default.project.json \
--target test
If you have the universe but no place yet, use --create-place to create one. Place creation is not exposed in Open Cloud, so this uses your .ROBLOSECURITY cookie instead. It only works on a machine that's logged in to Roblox.
nevermore deploy init --yes \
--universe-id 12345 \
--create-place \
--project default.project.json
Other flags:
| Flag | Description |
|---|---|
--target <name> | Name of the target to create (default: test) |
--script-template <path> | Set the Luau script template that nevermore test will run |
--force | Overwrite an existing deploy.nevermore.json |
The deploy.nevermore.json schema
{
"targets": {
"test": {
"universeId": 12345,
"placeId": 67890,
"project": "default.project.json",
"scriptTemplate": "test/scripts/Server/ServerMain.server.lua",
"basePlace": {
"universeId": 12345,
"placeId": 11111
}
}
}
}
| Field | Required | Description |
|---|---|---|
targets | yes | Map of target name to deploy config. Most packages start with a single test target. |
targets.<name>.universeId | yes | Roblox universe ID to deploy into. |
targets.<name>.placeId | yes | Roblox place ID. The build is uploaded here as a new version. |
targets.<name>.project | yes | Path to the Rojo project file, relative to the package directory. |
targets.<name>.scriptTemplate | no | Luau file nevermore test executes via Open Cloud after upload. Not used by nevermore deploy itself. |
targets.<name>.basePlace | no | Universe/place to download and merge with the rojo build before uploading. See Merging with an existing place. |
You can declare any number of targets. A common setup is one test target for CI and a separate production or staging target for live deploys:
{
"targets": {
"test": { "universeId": 1, "placeId": 10, "project": "test/default.project.json" },
"production": { "universeId": 1, "placeId": 20, "project": "default.project.json" }
}
}
Running a deploy
From the directory containing deploy.nevermore.json:
# Build + upload to the default "test" target as a saved (draft) version
nevermore deploy run
# Same, but publish so the new version is live for players
nevermore deploy run --publish
# Deploy a specific target
nevermore deploy run production --publish
nevermore deploy run and the bare nevermore deploy <target> form are equivalent. run is the default subcommand.
On success you'll see one of:
Saved v42 — not yet live.
Published v42 — live in game.
A "saved" version is uploaded but not visible to players. You can publish it later from the Roblox dashboard, or re-run with --publish to publish a fresh build. That version number matches what you'll see on the place page.
Run flags
| Flag | Description |
|---|---|
--publish | Publish the new version (default: save only) |
--api-key <key> | Open Cloud API key (overrides credential lookup) |
--universe-id <id> | Override the target's universeId |
--place-id <id> | Override the target's placeId |
--place-file <path> | Skip the rojo build and upload an existing .rbxl instead |
--output <path> | Write a JSON record of the deploy result to this path |
Global flags (available on every nevermore command):
| Flag | Description |
|---|---|
--yes | Non-interactive (fails fast instead of prompting) |
--dryrun | Print what would happen without doing it |
--verbose | Verbose logging (rojo output, upload details) |
Overriding the configured place
--universe-id and --place-id let you redirect a single deploy without editing the config. This is useful when you want to push the same build to a personal staging place for a one-off test:
nevermore deploy run --universe-id 999 --place-id 8888
Uploading a pre-built place
If you already have a .rbxl (for example, one produced by rojo build in an upstream CI step), skip the rebuild:
nevermore deploy run --place-file ./build/my-place.rbxl
The project field in deploy.nevermore.json is ignored when --place-file is set, but universeId and placeId are still required.
Batch deploys
If you want to deploy every game affected by a code change (for example, on every PR), use nevermore batch deploy instead. It scans the pnpm workspace for packages with a matching deploy target, uses pnpm ls --filter to figure out which ones changed since origin/main, and runs them in parallel.
See Integration Testing → Batch deploy for the full flag list and CI usage.
Common workflows
First-time setup for a new game
If you're starting from a clean directory, nevermore init scaffolds a working Nevermore game template: a default.project.json, server/client entry scripts, and the default packages (loader, servicebag, binder, etc.). After that, nevermore deploy init only needs your universe and place IDs.
mkdir my-game && cd my-game
nevermore init # scaffold a Nevermore game template
nevermore deploy init # configure the deploy target (interactive)
nevermore deploy run # first upload, draft only
nevermore deploy run --publish # publish when ready
The wizard auto-detects the project file nevermore init creates, so you only answer prompts for universe and place. See Install for the full breakdown of what nevermore init produces, and Integration Testing → Setting up a new integration game if you're building a game inside the Nevermore/Raven monorepo instead.
Promoting a tested build to production
If you have separate test and production targets, the common pattern is to run the test target on every PR and only deploy production from main:
# In CI on main
nevermore deploy run production --publish
There is no separate "promote" command. You deploy the same code to whichever target you want.
Debugging a failed deploy
- Pass
--verboseto see the rojo build output and the raw Open Cloud responses. - Pass
--dryrunto confirm which target, universe, place, and project the CLI would use without uploading anything. - Check
nevermore login --statusif you suspect a credential problem. - Place uploads can fail with HTTP 403 when the API key is missing the
universe-places:writescope for that specific universe. The scope is granted per-universe, not globally.
See also
- Intro — Why Nevermore, the major packages, and how the library is organized.
- Install — Setting up Node, Rojo, the Nevermore CLI, and scaffolding a project with
nevermore init. - Integration Testing —
basePlacemerging, smoke tests, batch deploys, CI integration. - Test Infrastructure — How
nevermore testreuses the deploy config to run Jest specs in Open Cloud.