> ## 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.

# How to Build a Block

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

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.

<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 is the trade: you give up automatic upgrades, and users gain full ownership. Design blocks so they read cleanly after install.
</Info>

## 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`:

| 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">
    Add one entry per block to the `items` array.

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

  <Step title="Build the registry">
    Run the CLI build from the registry root.

    ```bash Terminal theme={null}
    bunx @mercurjs/cli@latest 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, or 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`.

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

    Then install the block.

    ```bash Terminal theme={null}
    bunx @mercurjs/cli@latest 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

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

<AccordionGroup>
  <Accordion title="How do I depend on another block or an npm package?">
    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.
  </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>
