# Config with milo (/docs/build/config-with-milo)



App config is data: screens, blocks, navigation, theme. milo lets you edit it as code. Pull a draft theme's config to JSON files on disk, edit them (or let your agent), and push the result back. [Set up milo](/docs/start/milo-setup) first if you haven't.

<Callout type="error" title="You can never edit the live theme">
  milo refuses every write against the live theme — this is enforced in the
  CLI, not just convention. All work happens on drafts; going live is a
  deliberate release step from the Appbrew dashboard.
</Callout>

## The loop [#the-loop]

```bash
pnpm exec milo theme list                     # see live / draft themes
pnpm exec milo theme create "promo banner"    # new draft, cloned from live
pnpm exec milo theme use <themeId>            # make it the active target
pnpm exec milo config pull                    # → .milo/sync/<themeId>/draft/…
```

Edit the JSON under `.milo/sync/<themeId>/draft/`, one file per entity, organized by kind. Then:

```bash
pnpm exec milo config status                  # what's dirty, offline
pnpm exec milo config commit screens <entityId>   # save one entity
pnpm exec milo config push                    # merge the draft into its theme
```

Rules that keep you out of trouble:

* **Writable kinds**: `theme`, `screens`, `navigation`, `settings`. **Read-only**: `integrations`, `store`; commits against them are refused.
* Each file has a server-managed envelope (`version`, `baseVersion`, timestamps) beside the payload. &#x2A;*Edit payload fields only.**
* `commit` saves one entity at a time; `--new` creates one. `config create-screen "<name>"` scaffolds a screen.
* The `snapshot/` directory is milo's diff baseline. Never hand-edit it.

If `push` reports conflicts (the base theme moved underneath you), refresh the conflicting entities and re-apply:

```bash
pnpm exec milo config refresh screens <entityId> --force
```

## Placing your custom block [#placing-your-custom-block]

This is the second half of the [custom blocks](/docs/build/custom-blocks) story. The block *component* lives in your repo; the block *placement* is config:

1. Build and register the block in code (`register-blocks.ts`).
2. `config pull` a draft, add a block entry with your `componentId` to the target screen's JSON, set its `source`/`style`/`options` per your documented schema.
3. `commit` the screen, `push`, and check it in the app ([Run your app](/docs/start/run-your-app)); dev builds follow config changes on reload.

## Going live [#going-live]

Pushing merges your draft into its theme. It does **not** put anything in front of shoppers. Release remains a deliberate dashboard action (usually by the merchant's operator). Preview a draft anytime with `milo theme preview`, which prints a preview link and QR code.

## Beyond config [#beyond-config]

The same CLI reads and manages [integrations](/docs/reference/integrations) (`milo integration catalog|info|add`), sends test [push notifications](/docs/reference/cli) (`milo push test`), queries analytics, and runs read-only Shopify Admin GraphQL (`milo shopify query`) — the full surface is in the [CLI reference](/docs/reference/cli).
