# Theme workflow
The CLI treats your storefront as a directory you can edit, diff and commit.
```bash
mercemur theme pull
```
Writes your templates and builder configuration into `theme/`.
```bash
mercemur theme dev
```
Prints a preview link, then uploads each save as a draft. Refresh the link to see
it. Nothing reaches a shopper.
```bash
mercemur theme publish
```
Promotes the draft to live.
## What lands on disk
```
theme/
├── templates/
│ ├── home.liquid
│ ├── product.liquid
│ └── ...
├── online-store.json
└── .mercemur-state.json
```
`templates/` holds one file per page kind. `online-store.json` is your builder
document: page layout and design tokens together, written indented so its diffs are
readable in a pull request.
`.mercemur-state.json` records the version of each resource as of your last pull. It is
**per checkout**, so do not commit it. Add it to `.gitignore`:
```
theme/.mercemur-state.json
```
If you lose it, run `mercemur theme pull` again. The CLI refuses to push without it
rather than guessing, because guessing means overwriting somebody else's work.
## Draft and live are separate
Every command in this workflow writes to a **draft**. Shoppers see the **published**
copy. Only `publish` moves one to the other.
That split is deliberate, and it is why `push` and `publish` are two commands rather
than one flag. A draft save is private to you and cheap to get wrong. A publish changes
what every visitor to your store sees. Collapsing them would put both behind one line in
your audit log and one entry in a retry policy.
## Pushing safely
Before sending anything, see where you stand:
```bash
mercemur theme diff
```
That compares your files against the server's current draft, so it catches the case a
dry run cannot: somebody else changed the store while you were working.
```bash
mercemur theme push --dry-run
```
A dry run sends nothing and prints exactly what a real push would do, including whether
each file updates something or creates it:
```
would save template home (2481 bytes, expecting 2026-08-17T10:00:00Z)
would save template cart (612 bytes, new here, will be created)
would save the builder config (18422 bytes, schema_version 2, expecting 2026-08-17T10:05:00Z)
```
Before sending anything, push checks every version it recorded against what the server
holds now. If any of them moved, it refuses having written **nothing**:
```
changed on the server since you pulled, so nothing was written (cart, home):
run `mercemur theme pull` into a clean directory and re-apply your edits,
rather than pushing again
```
Retrying will not help, which is why the message says so. Pull into a clean directory,
look at what changed, and re-apply.
That check is not the only guard. Every individual write still carries the version you
pulled, because somebody can always land a change in the gap between the check and the
write. If that happens the push stops part-way and says so, rather than claiming nothing
was written:
```
template cart changed on the server while this push was running, so the push
stopped part-way: run `mercemur theme pull` into a clean directory and
re-apply your edits, rather than pushing again
```
The fix is the same either way. The difference is only whether some templates already
landed, and the message tells you which case you are in.
## Publishing
`publish` promotes the draft **the server holds**, not the files on your disk. Those are
usually the same thing, and when they are not it refuses:
```
template home differs from the draft on the server, so publishing would ship
the older copy: run `mercemur theme push` first
```
Without that check, editing a file and forgetting to push would publish the previous
version and report success.
Templates publish before the builder configuration, because the configuration refers to
them. The other order would briefly leave a live layout pointing at markup shoppers
cannot see yet.
## Working with git
The two content files are meant for version control:
```bash
mercemur theme pull
git add theme/templates theme/online-store.json
git commit -m "storefront: new product page layout"
```
Branch, review and revert your storefront the way you do the rest of your code. To roll
back, check out the older files and push them.
A `git revert` changes your files, not your store. Run `mercemur theme push` and then
`mercemur theme publish` to make the rollback live.
## Command reference
Downloads every template and the builder configuration, and records their versions.
| Flag | Default | |
|---|---|---|
| `--path` | `theme` | Directory to write into |
| `--json` | off | Machine-readable output |
Overwrites local files. Commit or stash first if you have unpushed edits.
Uploads local files as a draft, then records the new versions so a second push needs
no re-pull.
| Flag | Default | |
|---|---|---|
| `--path` | `theme` | Directory to read from |
| `--dry-run` | off | Print the plan, send nothing |
| `--json` | off | Machine-readable output |
Exits `4` if anything changed on the server since your pull.
Mints a preview link and pushes each save as a draft.
| Flag | Default | |
|---|---|---|
| `--path` | `theme` | Directory to watch |
| `--debounce` | `400ms` | How long to wait for edits to settle |
`--json` is refused here rather than ignored. A watch session is a stream for a
person; for scripted output run `mercemur theme push --json` in your own loop.
Editors write a file several times per save, so changes are debounced into one upload.
A failed upload prints and the session continues; press ctrl-c to stop.
The preview link lasts 48 hours and renders your **draft**. Anyone holding the link
can see it, so treat it as you would a shared document.
Makes the pushed draft live for shoppers.
| Flag | Default | |
|---|---|---|
| `--path` | `theme` | Directory holding the theme |
| `--json` | off | Machine-readable output |
Refuses if your local files differ from the draft on the server, or if any version is
unknown.
Compares your local files against the draft on the server. Reads only, so it is safe
to run at any time.
| Flag | Default | |
|---|---|---|
| `--path` | `theme` | Directory holding the theme |
| `--json` | off | Machine-readable output |
```
changed home
new cart (push will create it)
only on the server search (pull to get it; push will not remove it)
```
This answers something `push --dry-run` cannot: whether the **server** moved since your
pull. A dry run only knows what is on your disk.
With `--json` it reports `in_sync`, so a pipeline can check whether the storefront
matches the repository.
Lists past versions of the builder configuration, newest first.
```
osr_01K2R8... Mon, 17 Aug 2026 09:00:00 UTC
osr_01K2P4... Sun, 16 Aug 2026 09:00:00 UTC
```
One is recorded each time the configuration is published. Templates are not listed
here, because they live in your own git history; the builder document keeps
server-side revisions because the dashboard edits it too.
| Flag | Default | |
|---|---|---|
| `--show` | none | Print one revision's configuration instead of the list |
| `--json` | off | Machine-readable output |
Read a revision before restoring it. A list of ids and timestamps tells you *when*
something changed, never *what* it changed to:
```bash
mercemur theme revisions --show osr_01K2R8...
```
Copies a past revision back into the **draft**.
| Flag | Default | |
|---|---|---|
| `--revision` | required | Id from `mercemur theme revisions` |
| `--path` | `theme` | Directory holding the theme |
| `--json` | off | Machine-readable output |
Nothing changes for shoppers until you publish, so a restore is as reversible as any
other edit until then. Afterwards run `mercemur theme pull` to bring the restored
document down, then publish when you are happy with it.
Like every other write, it presents the version you pulled and refuses if somebody
changed the configuration in the dashboard meanwhile.
## In CI
```bash
export MERCEMUR_STORE=acme.mercemur.com
export MERCEMUR_TOKEN="$MERCEMUR_API_KEY"
mercemur theme push --json
mercemur theme publish --json
```
Check the exit code rather than parsing the message. `4` means somebody changed the
store while your pipeline was running, and the right response is to fail the build and
look, not to retry.