cli
@bedrock-core/cli scaffolds a complete Minecraft Bedrock addon project — Regolith build, TypeScript, ESLint, the full bedrock-core stack, and three working example screens — in one command.
@bedrock-core/cli is in beta: the API can change between releases. Pin exact versions and read the changelog before upgrading.
Prerequisites
- Node.js 22.18+ — https://nodejs.org/
- Regolith — https://regolith-docs.readthedocs.io/en/stable
- npm
- yarn
- pnpm
npx @bedrock-core/cli
yarn dlx @bedrock-core/cli
pnpm dlx @bedrock-core/cli
Usage
- npm
- yarn
- pnpm
npx @bedrock-core/cli [project-name]
yarn dlx @bedrock-core/cli [project-name]
pnpm dlx @bedrock-core/cli [project-name]
| Argument | Required | Description |
|---|---|---|
[project-name] | no | The project directory. Passing it skips the first prompt |
| Flag | Description |
|---|---|
-a, --author <name> | Author name. Skips the author prompt |
-d, --description <text> | Project description. Skips the description prompt |
-p, --package-manager <manager> | Install with yarn, npm, pnpm, or none to skip |
-V, --version | Print the CLI version |
-h, --help | Print usage |
There is one complete template. Choose npm, yarn, pnpm, or none when you want to install
later. Yarn and pnpm record their selected version in package.json; each manager creates its own lockfile, which should be committed with the project.
Prompts
Three text prompts and a package-manager selection, all with defaults you can accept with Enter.
[project-name], --author, --description, and --package-manager each skip their own prompt,
so a fully flagged invocation runs with no prompts at all:
| Prompt | Default | Validation |
|---|---|---|
Project name: | my-addon | Must be a valid new npm package name (this becomes the directory and package.json name) |
Author name: | Your Name | — |
Description: | A Minecraft Bedrock addon with custom UI | — |
Install dependencies with: | yarn (recommended) | yarn, npm, pnpm, or install later |
Ctrl-C prints ✖ Operation cancelled and exits cleanly. The CLI refuses to write into a directory that already exists and is not empty.
What gets scaffolded
The template scaffolds the whole bedrock-core stack, not just a UI: server runtime registration, the addon catalog, the shared config UI, typed translations, MDX guides, JSON generation from TypeScript, a GameTest suite, and three themed example screens covering navigation, a navigated param and a native modal form.
my-addon/
├── config.json Regolith: `core` plus six declared stages, four profiles
├── package.json scripts: regolith-install / build / build:test / watch / watch:test / lint
├── tsconfig.json JSX + the two @bedrock-core/generated aliases
├── tsconfig.test.json extends tsconfig.json; entry is scripts/gametest.ts
├── eslint.config.mjs
├── .vscode/ launch.json wired to the Minecraft debugger (port 19144)
├── core-ui-<UI version>.mcpack render pack, downloaded for you
└── packs/
├── BP/
│ ├── manifest.json
│ ├── manifest.test.json extends manifest.json, adds @minecraft/server-gametest
│ ├── blocks/tutorial.block.ts generator sample — one file, many blocks
│ ├── entities/training_dummy.entity.ts generator sample — single file
│ ├── texts/{en_US.lang, languages.json}
│ └── scripts/
│ ├── main.ts core.register(...) + events
│ ├── gametest.ts the test-profile entry: imports ./main and ./tests
│ ├── config.ts typed config schema
│ ├── tests/index.ts GameTest suite, tagged "example"
│ └── UI/
│ ├── screens/
│ │ ├── home.screen.tsx ore-styled screen, navigation and local state
│ │ ├── plan.screen.tsx reads a navigated param, `back` button
│ │ └── profile_form.screen.tsx a native modal form
│ └── i18n.ts createI18n(bundle)
├── RP/
│ ├── manifest.json
│ └── texts/{en_US.lang, languages.json}
└── data/
├── guides/en_US/ intro page + a category + an admonition
│ ├── intro.mdx
│ └── getting-started/
│ ├── _category_.json
│ └── first-steps.mdx
├── i18n/en_US.ts meta.* + interpolation + a plural leaf
└── generated/mc/ Minecraft document types (gitignored, rebuilt)
The Regolith pipeline
The template pins core and all six stages in filterDefinitions. Run regolith install-all
before its first build: Regolith installs only declared filters, and does not automatically
install the stages that core invokes.
config.json runs one core filter in every profile, which chains the six stages in dependency order — manifest, generator, guides, i18n, ui-compiler, bundler — with the namespace declared once as shared.namespace:
| Setting | Stage | What it does |
|---|---|---|
generator.include / exclude | generator | Widened to every .ts under the packs, excluding BP/scripts and data |
manifest.manifestPath | manifest | BP/manifest.test.json in the test and build-test profiles only |
ui-compiler.stamp | ui-compiler | true in default, drawing the build-stamp HUD hook |
bundler.debug | bundler | false in build; true everywhere else |
bundler.tsConfigPath | bundler | tsconfig.test.json in test and build-test, whose entry is gametest.ts |
Templates are checked against Mojang's official JSON Schemas. The generator
stage writes document types into packs/data/generated/mc/, so a template is a
plain export with a satisfies on the end:
export default {
'format_version': '1.21.0',
'minecraft:entity': { description: { identifier: 'my_addon:training_dummy' } },
} satisfies Entity;
Block, Entity, Item, LootTable, Recipe, Particle and 33 more are
global, so nothing is imported and nothing reaches the runtime — satisfies is
erased at build time. The generated types are gitignored, so run the build once
after scaffolding.
Profiles
| Profile | Script | Export | Notes |
|---|---|---|---|
build | build | read-only, local | Minified, bundler.debug: false — the release build |
default | watch | writable, development | Laid-out JSON, debug bundle, build-stamp HUD, redeploys on change |
test | watch:test | writable, development | Same as default, resolving manifest.test.json and bundling gametest.ts |
build-test | build:test | writable, ./build/test/BP and ./build/test/RP | The gametest manifest and entry, for bds-runner |
GameTests
packs/BP/scripts/tests/index.ts registers one GameTest tagged example through @minecraft/server-gametest, a beta module only manifest.test.json declares. gametest.ts — the entry tsconfig.test.json names — imports ./main then ./tests, so a release build (main.ts) never pulls in the test suite or the beta module. The build:test script produces the pack bds-runner runs the suite against.
tsconfig
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@bedrock-core/ui",
"moduleResolution": "bundler",
"paths": {
"@bedrock-core/generated/i18n": ["./packs/data/i18n/i18n.generated.json"],
"@bedrock-core/generated/guides": ["./packs/data/guides/guides.generated.json"]
}
},
"include": ["packs/BP/scripts/**/*", "packs/BP/blocks/**/*", "packs/BP/entities/**/*", "packs/data/**/*"]
}
Both generated files are produced by the filters, so a freshly scaffolded project does not typecheck until the build script has run at least once. This is expected — run the build before hunting for missing modules.
A second tsconfig, tsconfig.test.json, extends this one and names packs/BP/scripts/gametest.ts as its sole entry — the test and build-test profiles point the bundler at it. See GameTests.
Manifests
Five UUIDs are generated per project (crypto.randomUUID()), one per manifest header and module, plus BP/manifest.test.json, which extends the BP manifest and adds a dependency on @minecraft/server-gametest. Every manifest is format_version: 3, so every version field is a SemVer string: both packs use pack_scope: "world" and min_engine_version: "1.26.50". Neither bakes its name and description into the manifest: both headers point at pack.name / pack.description — the only two keys Bedrock resolves for a manifest header — which you author once as meta.name / meta.description in packs/data/i18n/<locale>.ts. The i18n filter emits those aliases into each pack's own texts/<locale>.lang — a manifest key resolves only from the pack it belongs to — so the pack list reads in the player's language, and the template's texts/en_US.lang files hold nothing but a comment pointing back at packs/data/i18n/.
The BP also declares a dependency on @minecraft/server and @minecraft/server-ui, and on the render pack by its fixed UUID — already wired in, not something you add later.
Identifiers
Two of your answers become Minecraft-safe identifiers — lowercased, non-alphanumerics collapsed to _:
| Variable | From | Used for |
|---|---|---|
CREATOR_ID | Author name | core.register({ manifest: { creator } }), the addon namespace |
PACK_ID | Project name | core.register({ manifest: { pack } }), generated identifiers |
Together they form the addon namespace <creator>_<pack> that the i18n filter derives for .lang keys and that the guides filter takes as its namespace setting.
The generated main.ts
const { config } = core.register({
manifest: {
creator: 'your_name',
pack: 'my_addon',
packName: i18n.key($ => $.meta.name),
creatorName: i18n.key($ => $.meta.creator),
version: '1.0.0',
description: i18n.key($ => $.meta.description),
},
catalog: registerCatalog(),
config: registerConfig(configDef),
guides: registerGuides(),
});
Display fields are i18n keys, not literals — that is what lets a catalog render your addon's name in each player's own language. core.register() publishes them to the registry and, on the first tick, the bundle they resolve from. The three app fields register <ns>:catalog, <ns>:config and <ns>:guide under your namespace and serve the show methods another realm asks on; nothing runs after the call.
A playerSpawn handler greets the player with an interpolated translation (gated on a config value), and a buttonPush handler renders the Home screen — push a stone button in-game to see it, then follow its navigation to the plan screen and the profile form.
After scaffolding
The CLI copies the template, substitutes your answers, downloads the render pack matching UI
0.12.1 (core-ui-0.12.1.mcpack), then installs dependencies with the package manager you chose.
If the asset download fails, it prints the matching release link instead. Choosing none creates
the files without running a package manager.
It prints the commands for your selected manager:
- npm
- yarn
- pnpm
cd my-addon
npm run regolith-install
npm run build
npm run watch
npm run lint
cd my-addon
yarn regolith-install
yarn build
yarn watch
yarn lint
cd my-addon
pnpm regolith-install
pnpm build
pnpm watch
pnpm lint
The output also explains that the first build writes the generated Minecraft types, points to the starter screens, and tells you how to import the matching render pack into the client.
When the render pack download fails, the last line under "Render pack:" is replaced with a link to
the UI 0.12.1 release instead of a filename — non-fatal, so scaffolding still finishes.
| Script | What it runs |
|---|---|
regolith-install | regolith install-all — fetches every filter declared in config.json |
build | regolith run build — the read-only local export profile |
build:test | regolith run build-test — the writable gametest export, for bds-runner |
watch | regolith watch — the development profile, redeploying on change |
watch:test | regolith watch test — the development profile, resolving the gametest manifest and entry |
lint | eslint . |
loopback / loopback:preview | Windows loopback exemption for the Minecraft debugger |
When Git is available, the generated directory is initialized as a repository without creating a
commit. Yarn and pnpm choices run corepack enable once, then invoke the selected manager without
a corepack prefix. npm runs
directly. The selected manager creates its own lockfile; commit it and use that manager's frozen/immutable install mode in CI. If Corepack
cannot write its shims, the generated project is preserved and the CLI prints the manual command.
Yarn uses nodeLinker: node-modules; pnpm uses nodeLinker: hoisted in
pnpm-workspace.yaml, so the build sees the conventional node_modules layout with either
manager.
Next steps
- Render pack — what the
.mcpackis and how versioning works - i18n — the translations the template already wires up
- catalog — the addon browser the template installs
- config — the settings app the template installs
- guides — the in-game guide the template seeds
- ore-styled — the components the starter screens use
- server-runtime — the
core.register()half of the generatedmain.ts