> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mercurjs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Build your own block

> Author a reusable feature as a block — backend, panel UI, and docs — build it into a registry, and install it into any Mercur project.

Blocks are how features travel between Mercur projects: not as npm packages you depend on, but as **source code copied into the target project**. Anything you've built once — a module, workflows, routes, 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.

<Info>
  **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.
</Info>

## What you'll build

An `announcements` 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 a `type` 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

<Steps>
  <Step title="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:

    ```
    my-registry/
    ├── registry.json
    └── src/
        └── announcements/
            ├── modules/announcements/     # data model, service, index
            ├── api/vendor/announcements/  # route.ts, validators.ts
            └── vendor/routes/announcements/
                └── page.tsx               # vendor portal page
    ```

    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](/rc/resources/tutorials/custom-panel-page): a default export plus a `config` for the sidebar entry.
  </Step>

  <Step title="Declare it in registry.json">
    ```json registry.json theme={null}
    {
      "$schema": "https://registry.mercurjs.com/registry.json",
      "name": "@my-org",
      "homepage": "https://my-org.com",
      "items": [
        {
          "name": "announcements",
          "description": "Marketplace announcements with a vendor portal feed.",
          "dependencies": [],
          "registryDependencies": [],
          "docs": "## Setup\n\nRegister the module in `medusa-config.ts`, then run `bunx medusa db:generate announcements && bunx medusa db:migrate`, and finally `bunx @mercurjs/cli@rc codegen`.",
          "categories": ["module", "api", "vendor"],
          "files": [
            { "path": "announcements/modules/announcements/index.ts", "type": "registry:module" },
            { "path": "announcements/api/vendor/announcements/route.ts", "type": "registry:api" },
            { "path": "announcements/vendor/routes/announcements/page.tsx", "type": "registry:vendor" }
          ]
        }
      ]
    }
    ```

    Two fields do the heavy lifting: **`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.
  </Step>

  <Step title="Build the registry">
    ```bash theme={null}
    bunx @mercurjs/cli@rc build
    ```

    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`.
  </Step>

  <Step title="Host it">
    Serve the `r/` directory from any static host (GitHub Pages, Vercel, S3 — anything that makes `{name}.json` publicly reachable).
  </Step>

  <Step title="Install it into a project">
    In a consumer project, register your registry in `blocks.json` and install:

    ```json blocks.json theme={null}
    {
      "registries": {
        "@my-org": "https://my-registry.example.com/r/{name}.json"
      }
    }
    ```

    ```bash theme={null}
    bunx @mercurjs/cli@rc add @my-org/announcements
    ```

    The CLI fetches the JSON, maps each file's `type` to the consumer's aliases, rewrites imports, and prints your `docs` instructions.
  </Step>
</Steps>

## Verify

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@rc diff @my-org/announcements` reports no changes — the installed copy matches the registry.

## FAQ

<AccordionGroup>
  <Accordion title="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.
  </Accordion>

  <Accordion title="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](/rc/learn/registry).
  </Accordion>

  <Accordion title="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.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Registry" href="/rc/learn/registry">
    The full registry.json schema, auth, and block dependencies.
  </Card>

  <Card title="Blocks" href="/rc/learn/blocks">
    What blocks can contain and how consumers manage them.
  </Card>
</CardGroup>
