Upgrading Spree
Every Spree upgrade is the same three stages:
- Bump the gems — the gems.
- Migrate the schema — copy new migrations out of the gems () and run .
- Backfill data — runs the version-specific data tasks listed in the upgrade manifests shipped inside .
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 |
|---|
| (Docker + , Rails app in — older projects: ) | |
| 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)
bash
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
prompts before
and before migrations (answer No to skip one).
skips the prompts for non-interactive use. The rake backfills always run.
It needs the
ejected dev stack. Fresh projects run the prebuilt
image with no source bind mount, so
inside it can't touch your
and copied migrations never land in
. Run
first (switches to the build-from-source compose), then upgrade.
The CLI refuses to run in a monorepo edge project (
set in
) — use the
scripts from the monorepo root there.
Native (classic Rails app, or no CLI)
bash
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:
→
,
→
,
→
.
How works
- Manifests live in the gem at
spree_core/lib/spree/upgrades/<from>_to_<to>/manifest.yml
(dots become underscores: ). Each lists ordered steps: , , (a rake task), , and optionally (skipped when the task isn't defined, e.g. a provider gem you don't have).
- A normal run walks every manifest whose is ≤ 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. , migrations, cron/recurring jobs and code changes are never in them.
List what the installed gem ships:
bash
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
bash
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
≤ the installed minor, or ≤
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):
bash
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 disambiguate. Some tasks take their own env knobs (
,
, …) — they're documented in each step's
; 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 —
/
— which is handy for tasks that aren't in any manifest.
Cap the version
Eligible manifests become those whose
≤ 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. bumps every installed gem (it lists them with inside 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 or won't resolve.
- Recurring jobs / config. New scheduled jobs (Solid Queue , 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 command in this plugin does a read-only readiness pass.
- SDKs. Frontend packages (, , ) are bumped through your own package manager — the CLI prints the version in its "Next steps" panel as a reminder.
In production
Don't run
against production — it's a dev CLI. Your deploy already does
and
(the starter's Docker entrypoint runs
on boot). Add the backfills as one more command:
bash
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
.
Checklist
- Back up the database.
- Read the upgrade guide for the hop:
node_modules/@spree/docs/dist/developer/upgrades/<from>-to-<to>.md
or https://spreecommerce.org/docs/developer/upgrades.
- Bump gems (and extensions), run migrations.
- → read every step's notes.
- Run the backfills; re-run failed steps with .
- Do the manual items from the guide; run your test suite; bump SDKs.
- Ship with
bundle exec rake spree:upgrade
in the release phase.
Gotchas
- printing nothing (or only old manifests) means the gems aren't bumped yet — the plan comes from the installed .
- fails in the container → you're on the prebuilt image; first, or the bundle is out of sync ().
- 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
, ) 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 version'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 guides
node_modules/@spree/docs/dist/developer/cli/quickstart.md
— reference
- https://spreecommerce.org/docs/developer/upgrades
- Related skills: spree-upgrade-5-to-6, spree-cli, spree-deployment, spree-extensions