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’s the trade: you give up automatic upgrades, users gain full ownership. Design blocks so they’re readable after install.What you’ll build
Anannouncements block containing a module (data model + service), a vendor API route, and a vendor portal page — built into registry JSON and installed into a Mercur project.
File types and where they land
Each file in a block carries atype that maps to an alias in the consumer’s blocks.json:
| Type | Lands at (default aliases) |
|---|---|
registry:module | the API package’s modules directory |
registry:workflow | the API package’s workflows directory |
registry:api | packages/api/src |
registry:link | the API package’s links directory |
registry:vendor | apps/vendor/src |
registry:admin | apps/admin/src |
registry:lib | shared lib directory |
Author and ship the block
Lay out the block source
A registry is a project with a 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
registry.json and block sources under src/. Each block follows the standard directory convention, one folder per concern:config for the sidebar entry.Declare it in registry.json
registry.json
type on each file decides where it lands, and docs is the markdown shown after install. Put every manual step in docs — module registration, migrations, middleware, codegen. It’s the only instruction the installer sees.Build the registry
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.Host it
Serve the
r/ directory from any static host (GitHub Pages, Vercel, S3 — anything that makes {name}.json publicly reachable).Verify
r/announcements.jsonexists after the build and embeds every file’s content.- In the consumer project, the files landed under the alias-mapped paths and imports resolve.
- After following your own
docssteps (module registration, migrations, codegen),bun run buildpasses and the vendor portal shows the Announcements page. bunx @mercurjs/cli@rc diff @my-org/announcementsreports no changes — the installed copy matches the registry.
FAQ
How do I depend on another block or an npm package?
How do I depend on another block or an npm package?
Other blocks go in
registryDependencies (e.g. @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.Can I make the registry private?
Can I make the registry private?
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.How do consumers get updates to my block?
How do consumers get updates to my block?
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.