Skip to main content
Blocks are how features travel between Mercur projects. You ship them as source code copied into the target project, not as npm packages you depend on. Anything you build once, such as a module, workflows, routes, or panel pages, can be packaged as a block, published through a registry, and installed with mercurjs add. This tutorial builds a minimal “announcements” block and ships it through your own registry.
Blocks are source, not dependencies. When someone installs your block, they get the files: editable, diffable, theirs. Updates are opt-in via mercurjs diff and add --overwrite, never forced through a lockfile. That is the trade: you give up automatic upgrades, and users gain full ownership. Design blocks so they read cleanly after install.

What you’ll build

You build an announcements block containing a module (data model and service), a vendor API route, and a vendor portal page. Then you build it into registry JSON and install it into a Mercur project.

File types and where they land

Each file in a block carries a type that maps to an alias in the consumer’s blocks.json:

Author and ship the block

1

Lay out the block source

A registry is a project with a registry.json and block sources under src/. Each block follows the standard directory convention, one folder per concern:
Write the files exactly as they should land in a consumer’s project: real imports, real Medusa module definitions. The build step resolves imports and rewrites them to the consumer’s path aliases at install time. For the panel page, use the same conventions as any custom panel page: a default export plus a config for the sidebar entry.
2

Declare it in registry.json

Add one entry per block to the items array.
registry.json
Two fields do the heavy lifting. type on each file decides where the file lands, and docs is the markdown shown after install. Put every manual step in docs: module registration, migrations, middleware, codegen. It is the only instruction the installer sees.
3

Build the registry

Run the CLI build from the registry root.
Terminal
This reads registry.json, resolves each block’s imports, embeds file contents, and writes one JSON per block into r/: r/announcements.json, plus an index r/registry.json.
4

Host it

Serve the r/ directory from any static host (GitHub Pages, Vercel, S3, or anything that makes {name}.json publicly reachable).
5

Install it into a project

In a consumer project, register your registry in blocks.json.
blocks.json
Then install the block.
Terminal
The CLI fetches the JSON, maps each file’s type to the consumer’s aliases, rewrites imports, and prints your docs instructions.

Verify

Confirm the block built and installed correctly:
  1. r/announcements.json exists after the build and embeds every file’s content.
  2. In the consumer project, the files landed under the alias-mapped paths and imports resolve.
  3. After following your own docs steps (module registration, migrations, codegen), bun run build passes and the vendor portal shows the Announcements page.
  4. bunx @mercurjs/cli@latest diff @my-org/announcements reports no changes. The installed copy matches the registry.

FAQ

Other blocks go in registryDependencies (for example @my-org/reviews). The CLI installs them in order automatically. NPM packages go in dependencies. The build also auto-detects them from your imports, so you rarely list transitive ones by hand.
Yes. Use the object form with headers in the consumer’s blocks.json: { "url": "…/{name}.json", "headers": { "Authorization": "Bearer ${REGISTRY_TOKEN}" } }. The env var is resolved from the installer’s environment. See Registry.
They run mercurjs diff <block> to compare their local copy against your registry, then add --overwrite to take the new version. Because blocks are source, consumers with local edits merge deliberately rather than being force-upgraded.

Next steps

Registry

The full registry.json schema, auth, and block dependencies.

Blocks

What blocks can contain and how consumers manage them.