About spree-upgrade
spree-upgrade is an upgrade guide for maintainers of Spree applications, covering dependency updates, database migrations, and version-specific data backfills. It documents CLI and native Rails workflows, planning and retrying backfills, and the manual work upgrades still require.
Key features
- 01Cover dependency, migration, and backfill stages
- 02Run upgrades with the CLI or Rails commands
- 03Preview backfill steps and cap version scope
- 04Retry an individual backfill step
- 05Explain production deployment and manual checks
Use cases
- 01Plan a Spree version upgrade
- 02Run data backfills during deployment
- 03Retry a selected step after failure
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ spree/ agent-skills --skill spree-upgradeAfter installing, type/spree-upgradein Claude Code to confirm it loaded.
Details
From the project READMEUpgrading Spree
Every Spree upgrade is the same three stages:
- Bump the gems —
bundle updatethespree*gems. - Migrate the schema — copy new migrations out of the gems (
spree:install:migrations) and rundb:migrate. - Backfill data —
rake spree:upgraderuns the version-specific data tasks listed in the upgrade manifests shipped insidespree_core.
Then a fourth, manual stage: review the upgrade guide for your hop (behavior changes, config moves, code that needs editing). The rake-runnable part never covers everything.
Going from 5.x to 6.0? That's a major, breaking hop with preconditions (you must be on 5.6 first, Rails 8.1, Gemfile swaps, roles-as-data). Read the spree-upgrade-5-to-6 skill before starting.
Pick your flavor
| Project | How you run it |
|---|---|
create-spree-app (Docker + @spree/cli, Rails app in server/ — older projects: backend/) | spree upgrade |
| Classic Rails app with Spree gems at the repo root (no Docker/CLI) | the native commands below |
| Production (either flavor) | your deploy pipeline + bundle exec rake spree:upgrade |
CLI (create-spree-app)
spree upgrade --plan # list the backfill steps a run would execute; runs nothing
spree upgrade # bundle update spree* → install migrations + db:migrate → rake spree:upgrade
spree upgrade prompts before bundle update and before migrations (answer No to skip one). --yes skips the prompts for non-interactive use. The rake backfills always run.
It needs the ejected dev stack. Fresh projects run the prebuilt ghcr.io/spree/spree image with no source bind mount, so bundle update inside it can't touch your server/Gemfile.lock and copied migrations never land in server/db/migrate/. Run spree eject first (switches to the build-from-source compose), then upgrade.
The CLI refuses to run in a monorepo edge project (SPREE_PATH set in .env) — use the pnpm server:* scripts from the monorepo root there.
Native (classic Rails app, or no CLI)
bundle update spree spree_core spree_api spree_emails # + spree_dashboard and any spree_* extensions you use
bin/rails spree:install:migrations db:migrate
DRY_RUN=1 bundle exec rake spree:upgrade # plan
bundle exec rake spree:upgrade # run the backfills
CLI flags map 1:1 to env vars on the rake task: --plan → DRY_RUN=1, --step <id> → STEP=<id>, --to <x.y> → TO=<x.y>.
How rake spree:upgrade works
- Manifests live in the gem at
spree_core/lib/spree/upgrades/<from>_to_<to>/manifest.yml(dots become underscores:5_6_to_6_0). Each lists ordered steps:id,name,task(a rake task),notes, and optionallyoptional: true(skipped when the task isn't defined, e.g. a provider gem you don't have). - A normal run walks every manifest whose
tois ≤ the installed minor version, oldest first. An app that skipped a backfill two versions ago catches up automatically. - Every step must be idempotent — re-running the whole task on an already-upgraded app is a safe no-op. This is what makes it safe as a release-phase command.
- Manifests only contain data backfills.
bundle update, migrations, cron/recurring jobs and code changes are never in them.
List what the installed gem ships:
spree exec sh -c 'ls "$(bundle info spree_core --path)/lib/spree/upgrades/"'
# classic: ls "$(bundle info spree_core --path)/lib/spree/upgrades/"
Plan first
spree upgrade --plan # or: DRY_RUN=1 bundle exec rake spree:upgrade
Prints every step (id, rake task, notes) that a real run would execute, in order — plan mode uses the same manifest selection as a real run (every manifest whose to ≤ the installed minor, or ≤ --to when given). Nothing is executed and the bundle/migrate pre-steps are skipped.
The plan reads manifests from the installed gem, so bump the gems first — a 5.6 bundle doesn't contain the 5.6 → 6.0 manifest.
Retry one step
After a partial failure, fix the cause and re-run only that step (skips bundle + migrate):
spree upgrade --step migrate_returns # or: STEP=migrate_returns bundle exec rake spree:upgrade
Step ids come from the plan output. If the same id exists in two manifests the task aborts and asks for TO= to disambiguate. Some tasks take their own env knobs (BATCH_SIZE, SKIP_INVALID_ROWS=true, …) — they're documented in each step's notes; pass them the same way (spree rake spree:upgrade STEP=… BATCH_SIZE=200, or spree exec env BATCH_SIZE=200 bin/rake …).
You can also invoke the underlying task directly — spree rake <task> / bundle exec rake <task> — which is handy for tasks that aren't in any manifest.
Cap the version
spree upgrade --to 5.6
Eligible manifests become those whose to ≤ the cap. Useful when you're landing on an intermediate minor before the next hop (5.x apps must land on 5.6 before going to 6.0).
What it does not do
- Extension upgrades.
spree upgradebumps every installedspree*gem (it lists them withbundle list --name-onlyinside the container), but each extension's own migrations, install generators and breaking changes are yours to run — check its CHANGELOG. Extensions must have a release compatible with the target Spree version orbundle updatewon't resolve. - Recurring jobs / config. New scheduled jobs (Solid Queue
config/recurring.yml, or your Sidekiq/cron setup), moved settings and new env vars are listed in the upgrade guide, not the manifest. - Your code. Decorators, subscribers, workflow hooks, dashboard plugins and storefront code referencing renamed or removed APIs have to be audited by hand. The
/spree:audit-upgradecommand in this plugin does a read-only readiness pass. - SDKs. Frontend packages (
@spree/sdk,@spree/admin-sdk,@spree/dashboard) are bumped through your own package manager — the CLI prints theapps/storefront@spree/sdkversion in its "Next steps" panel as a reminder.
In production
Don't run spree upgrade against production — it's a dev CLI. Your deploy already does bundle install and db:migrate (the starter's Docker entrypoint runs bin/rails db:prepare on boot). Add the backfills as one more command:
bundle exec rake spree:upgrade
Wire it as a release-phase command (Heroku), pre-deploy command (Render), init container / Job (Kubernetes) or post-deploy hook (Kamal, Capistrano). Because every step is idempotent, running it on every deploy is safe and cheap once the data is migrated.
Order matters: migrate → backfill → serve new code. Some steps are safety nets for the migration and some must run before later migrations (they say so in their notes). For big hops, run the backfills in a maintenance window or against a restored copy first to measure duration — several tasks honor BATCH_SIZE.
Checklist
- Back up the database.
- Read the upgrade guide for the hop:
node_modules/@spree/docs/dist/developer/upgrades/<from>-to-<to>.mdor https://spreecommerce.org/docs/developer/upgrades. - Bump gems (and extensions), run migrations.
spree upgrade --plan→ read every step's notes.- Run the backfills; re-run failed steps with
--step. - Do the manual items from the guide; run your test suite; bump SDKs.
- Ship with
bundle exec rake spree:upgradein the release phase.
Gotchas
--planprinting nothing (or only old manifests) means the gems aren't bumped yet — the plan comes from the installedspree_core.bundle updatefails in the container → you're on the prebuilt image;spree ejectfirst, or the bundle is out of sync (spree bundle install).- A step aborts on purpose. Several 6.0 tasks refuse to continue on data they can't convert faithfully (e.g.
migrate_users_to_customers,migrate_returns) and print the offending ids plus the env var that overrides. Read the message; don't blindly re-run. - Never skip ahead. Manifests assume their
fromversion's schema. Jumping minors is fine (the walk catches up); skipping a required landing version (5.6 before 6.0) is not.
Where to read further
node_modules/@spree/docs/dist/developer/upgrades/— per-hop upgrade guidesnode_modules/@spree/docs/dist/developer/cli/quickstart.md—spree upgradereference- https://spreecommerce.org/docs/developer/upgrades
- Related skills: spree-upgrade-5-to-6, spree-cli, spree-deployment, spree-extensions