AI Shopify migration with a coding agent: the Graftport playbook
How to run an AI Shopify migration end to end with Claude Code, Cursor, Windsurf, or Copilot driving the Graftport CLI, with the costly steps gated by a human.
How to run an AI Shopify migration end to end with Claude Code, Cursor, Windsurf, or Copilot driving the Graftport CLI, with the costly steps gated by a human.
A Shopify replatform is mostly iteration. You pull a sample of products, notice a custom attribute that does not fit a Shopify product field, edit a mapping expression, validate it against more rows, find a new edge case, edit again. Repeat for products, customers, orders, redirects, pages. This is the kind of work an AI coding agent is unusually good at — short loops, structured feedback, and a clear definition of "this passes." This guide is the playbook for handing that loop to your agent and keeping the costly steps on a human.
If you have not yet created the migration itself in the Graftport app, read Setting up your first Graftport migration first. The CLI takes over after the wizard, not instead of it.
The phrase gets used loosely. In Graftport, it means something narrow and concrete: an AI coding agent — Claude Code, Cursor, Windsurf, Copilot, or anything else with a shell tool — drives the Graftport CLI through the iterative phase of a migration. The agent investigates source data, drafts JSONata mapping expressions, validates them against real rows, publishes new mapping versions, and runs dry-runs to inspect the projected output. You stay in the loop for the steps that cost real money or are destructive: real loads, run cancels, record retries.
What the agent is not doing:
yes and prints a live
cost estimate to stderr first.What the agent is doing is the labour-intensive middle: turning
"there is a weight_in_kg attribute on Magento products and Shopify
wants grams" into a JSONata expression that handles the unit
conversion, the missing values, and the outliers — and proving the
expression works against 50 sampled rows before it ships.
Plenty of "AI for X" claims age badly. Migration mapping ages well for a specific reason: every iteration has a sharp, structured signal.
mappings validate call against a sample
of rows returns in seconds. The agent edits a JSONata file, re-runs
validation, and gets a deterministic pass/fail per row.mappings publish is a new immutable
version. The agent can ship freely because rolling back to a prior
version is one command away.The combination matters. The work is boring, repetitive, and benefits from someone who never gets tired. The signal is sharp enough that the agent does not drift. And the rail keeps it from doing anything expensive without you.
Every iteration the agent runs follows the same shape. The bundled skill spells it out so the agent does not improvise.
The agent pulls real source rows for the resource it is working on. No schema diagram, no second-hand description — the raw JSON the source store actually returns:
graftport source rows <migration_id> product --limit 5
This is the part you cannot skip. Every source store has a long tail of custom attributes, half-filled fields, and historical oddities. The agent sees them on the first call.
The agent reads the current mapping for that resource and forks it to a local file it can edit:
graftport mappings show <mapping_id> --raw > current.jsonata
Then it edits current.jsonata to handle whatever the samples revealed
— a unit conversion, a status enum mismatch, a tag list that needs
splitting on commas.
Before publishing anything, the agent validates the new expression against a fresh sample of source rows:
graftport mappings validate <mapping_id> --jsonata current.jsonata --limit 50 --pretty
The validator returns structured error codes per failing row. The skill teaches the agent which JSONata pattern fixes each one. Iterate until the sample passes.
When the sample is clean, the agent publishes the new mapping version and starts a dry-run to see the end-to-end projected output:
graftport mappings publish <mapping_id> --jsonata current.jsonata --notes "fix AMOUNT_MISMATCH"
graftport runs start <migration_id> --dry-run
Both of these are agent-allowed. The publish is a metadata write; the dry-run computes payloads without pushing to Shopify. Zero load cost in either direction.
For the deeper mechanical detail — what JSONata patterns the skill suggests for each error code, how to compose the validate / publish cycle for a multi-resource migration — see Automating JSONata mappings with AI.
You do not need to know every command to drive the loop above, but it helps to know how the surface area is shaped. The CLI groups its commands by domain:
| Group | Commands |
|---|---|
auth | login / status / logout |
migrations | list / show / resources |
mappings | list / show / validate / publish |
runs | list / show / status / estimate / start / cancel |
records | failures / errors / show / loaded / retry |
source | rows / raw |
skill | (bare) / install |
Every command emits JSON on stdout by default; pass --pretty for
human-readable output. Exit codes are stable: 0 ok, 1 error, 2
usage, 3 a human-gated action was declined. The 3 code is what
lets your wrapping scripts (and the agent) tell "you said no" apart
from "something broke."
The skill group is the install step: graftport skill install
auto-detects the coding agents present on your machine and writes the
skill document to the right place for each. There is no separate API
to configure. For the install details and per-agent paths, see
Using the Graftport CLI with Claude
Code.
It is easier to trust an agent when the boundary is explicit. The Graftport CLI splits state-changing actions into two tiers, and the bundled skill makes the boundary the agent's first rule.
The agent does, without asking:
mappings publish — a metadata write. It flags downstream records
for re-load on the next run, which is itself gated.runs start --dry-run — computes the full payload Shopify would
receive, but stops short of pushing it. Zero load cost.You do, after the agent presents the command:
runs start without --dry-run — the real load. Costs Shopify API
calls and platform credits per record.runs cancel — destructive; may lose in-flight work on records
already partway through.records retry — re-loads one record; small but real cost.The gated commands print the resolved action to stderr, including a
live cost estimate for runs start, then block on a yes. The
bundled skill explicitly forbids the agent from ever passing --yes
to bypass the gate. The agent's job is to compose the right command,
explain why, and stop. Your job is to press enter.
The trust angle is covered in depth in Human approval gates for AI Shopify migrations — including why this matters more for migrations than for general coding work.
The CLI does not care which agent you use. Anything with a shell tool can call it. The skill — the Markdown document that teaches the agent how to use the CLI — has different on-disk conventions per product, so the install command handles those for you.
| Agent | Install |
|---|---|
| Claude Code | Auto-detected; lands at ~/.claude/skills/graftport-migration-engineer/ |
| Claude Desktop | Auto-detected; shares the path with Claude Code |
| Windsurf | Auto-detected; merged into ~/.codeium/windsurf/memories/global_rules.md |
| Cursor | Manual paste into Settings → Rules → User Rules |
| GitHub Copilot | Manual drop into .github/copilot-instructions.md |
| Aider / AGENTS.md / custom | Manual paste into your agent's instructions file |
For the agents that need a manual paste, run graftport skill > skill.md
and place the file where your agent expects it. Cursor only reads User
Rules from its Settings UI; Copilot reads per-repo instructions; AGENTS.md
is project-scoped by definition. None of these are second-class — they
read the same skill text — they just need one extra step.
Concrete example. You have a Magento migration created in the app, the
source connected, the destination Shopify connected, and the default
resource templates seeded. You open Claude Code in any working
directory (it does not need to be a repo — the CLI's state lives in
~/.graftport/config.json, not in the project).
You ask the agent: "Take a look at the product mapping for migration
mig_8f2a and clean up any field issues you find against a sample of
50 rows."
The agent runs graftport mappings list mig_8f2a to find the product
mapping ID, graftport source rows mig_8f2a product --limit 5 to see
real samples, graftport mappings show <map_id> --raw > current.jsonata
to fork the current expression, and graftport mappings validate <map_id> --jsonata current.jsonata --limit 50 to see what fails. It
edits the file, re-validates, and once the sample passes, it runs
graftport mappings publish ... --notes "fix weight unit + status enum".
Then it tells you what it changed and proposes graftport runs start mig_8f2a --dry-run to verify end-to-end. You hit yes (or just let it
run — dry-run is agent-allowed and costs nothing). You inspect the
result in the Data tab in the app. Cycle complete.
For the worked example of setting up exactly this in Claude Code, including the headless / CI install path, read Using the Graftport CLI with Claude Code.
Two things stay the same as a hand-run Graftport migration.
The first is the wizard. You still create the migration at
app.graftport.com/migrations/new, connect credentials, pick which
resource types to seed. The CLI does not replace that step and does
not try to.
The second is the run model. Every load is still idempotent — records are tracked by their source identity, so anything already loaded is skipped. Re-running a migration on go-live night picks up only the delta, agent-driven or not. The dry-run safety net works the same way. For the launch-night playbook, see Re-running a migration on go-live — none of it changes when an agent is the one composing the commands.
What the agent adds is leverage on the part of the project that used to eat a week per source: the mapping phase.
Ready to point your agent at a real migration? Sign up at app.graftport.com and create the project, then jump to /ai-migrations for the install commands and the agent-by-agent setup.
Connect a source store, dry-run a migration, see the exact Shopify result before a single record lands. The same platform your team will use on go-live night.