Strapi 5 migration guide
Migrating from Strapi 4 to Strapi 5 is not a dependency bump. The REST response format changes, numeric ids become documentId, the Entity Service you used in custom code is replaced by the Document Service, and every plugin written for v4 stops working. The official upgrade tool handles a good share of it, but the rest is manual and it is the manual part that decides whether you are done in three days or six weeks. We run Strapi 4 and Strapi 5 projects in production; this is the guide we wish we had before the first one, with the breaking changes ranked by how often they bite, the exact commands, and hour ranges for three project sizes.
Current version as of 30 September 2026: Strapi 5.56.
Why now
| Date | What happened |
|---|---|
| 24 September 2024 | Strapi 5 released |
| End of October 2025 | Bug fixes for Strapi 4 stopped |
| End of April 2026 | Security patches for Strapi 4 stopped. 4.x is end of life |
Your admin panel is a server where staff log in and upload files, and it now runs on software with no security fixes. That includes the frozen dependency tree: Koa, the React admin, upload libraries. Every new CVE stays open until you move.
Breaking changes, ranked by pain
The official list has around forty entries. These are the ones that cost hours on real projects, in the order they usually hit.
1. Flat REST responses
Strapi 4 wrapped every entry in data.attributes. Strapi 5 puts fields at the root:
// Strapi 4
{ "data": { "id": 14, "attributes": { "title": "Post", "cover": { "data": { "id": 3, "attributes": { "url": "/uploads/a.jpg" } } } } } }
// Strapi 5
{ "data": { "documentId": "clkgylmcc000008lcdd868feh", "title": "Post", "cover": { "documentId": "clkgylw7d000108lc4rw1bb6s", "url": "/uploads/a.jpg" } } }Every frontend that reads .attributes breaks. The transition header Strapi-Response-Format: v4 restores the old shape per request; for GraphQL set v4CompatibilityMode: true in the plugin config. We turn compatibility on for the cutover and remove it endpoint by endpoint as the frontend is rewritten.
2. documentId replaces id
Routes like /api/articles/14 no longer work; you need /api/articles/clkgylmcc000008lcdd868feh. Any external system that stored numeric ids (a CRM, a mobile app, old deep links, a search index) needs a mapping table. Sorting by id no longer guarantees chronological order, sort by createdAt.
3. Entity Service becomes Document Service
All custom server code (controllers, services, cron jobs, lifecycle hooks) using strapi.entityService moves to strapi.documents():
// Strapi 4
await strapi.entityService.findOne('api::article.article', id, { populate: ['cover'] });
await strapi.entityService.findMany('api::article.article', { publicationState: 'preview' });
// Strapi 5
await strapi.documents('api::article.article').findOne({ documentId, populate: ['cover'] });
await strapi.documents('api::article.article').findMany({ status: 'draft' });The codemod rewrites the calls, but wherever a numeric id was passed it leaves a __TODO__ marker, and every marker is a manual decision. publicationState became status; instead of writing publishedAt you call publish() and unpublish().
4. Plugins
Reasons v4 plugins fail: new Plugin SDK, @strapi/helper-plugin removed (imports move to @strapi/strapi/admin), Design System v2, Vite instead of webpack for the admin build. For every third-party plugin there are three outcomes: the author shipped a v5 version, there is a replacement, or you rewrite it. On a typical site with two to four community plugins at least one lands in the third bucket.
5. Admin customisation
Anything in src/admin (custom pages, injected components, webpack.config.js) moves to Vite: a vite.config, JSX files renamed to .jsx or .tsx, webpack aliases gone. The --bundler=webpack flag exists but prints a deprecation warning, treat it as a crutch, not a plan. Patching admin internals with patch-package no longer works because of the Vite and Rollup build.
6. Environment and database
- Node.js LTS only: 22, 24, 26.
- MySQL 5 unsupported, MySQL 8 minimum,
mysql2is the only client. PostgreSQL 14 or newer. - SQLite only through
better-sqlite3. - S3 upload provider settings move under
s3Options(the codemod does this). - Default log level is now
http; several env-only options moved toconfig/server, and proxy settings are consolidated underserver.proxy. - Yarn is no longer the default package manager for the CLI.
7. Populate, Draft & Publish, i18n, reserved names
- Dynamic zones need an explicit
onstrategy:populate[sections][on][sections.hero][populate][0]=image. Queries that relied onpopulate=deepfrom a community plugin get rewritten. On a site with dynamic sections this took us a full day. - Draft & Publish: one
documentIdnow holds a draft and a published version instead of two rows. - The i18n plugin is gone, localisation lives in core; locales of one entry share a
documentId. - Reserved attribute names are enforced:
status,locale,localizations,meta,document,entryId,then, and nothing starting withstrapi. Rename such fields before upgrading or the data is lost. Expect collisions outside the official list too: one public migration report lost hours to a collection calleddocumentsand a field calledfilters. - REST input validation is on by default: query parameters and bodies that v4 let through silently are now rejected.
The commands
Prerequisite: be on the latest 4.x. If not:
npx @strapi/upgrade minorThen, on a copy of the project with a copy of the production database:
npx @strapi/upgrade majorThe tool shows the planned changes, installs Strapi 5 dependencies and runs the codemods: lifecycle files, i18n plugin removal, React and styled-components upgrades, Entity Service to Document Service, S3 credentials, sqlite3 to better-sqlite3. To see or cherry-pick codemods:
npx @strapi/upgrade codemods ls
npx @strapi/upgrade codemods runAfter it finishes, grep for __TODO__ and work through the list before starting the server. The first npm run develop on Strapi 5 migrates the database schema and there is no automatic way back.
Our migration plan
- Audit. Version, plugin list with Marketplace status, size of custom code in
src/api,src/extensions,src/admin, and who consumes the API (site, app, integrations). This is the step that produces the estimate. - Copy. Clone the project, load production content with
strapi transferrather than an export archive (archives with large media have failed to import for us more than once). - Codemods. Run
npx @strapi/upgrade majoron the copy and read the diff. - Manual pass. Resolve
__TODO__, rewrite controllers and services that took numeric ids, fix dynamic zone populate, move admin customisation to Vite, replace or rewrite plugins. - First start. Let Strapi 5 migrate the database on the copy. Keep the production backup next to it.
- API diff. Call every endpoint the frontend and integrations use, compare v4 and v5 responses. Decide per endpoint whether to enable
Strapi-Response-Format: v4. - Frontend. Replace
data.attributesreads, id-based links, GraphQL queries. If another team owns the frontend they get the list of changed endpoints. - Cutover. Upgrade Node on the server, build the Strapi 5 image, switch with a backup and a rollback plan (restore database plus old image). Watch logs for two weeks.
How long it takes
| Project | What is inside | Duration | Where the time goes |
|---|---|---|---|
| Content site | Types built in the admin, official plugins, frontend reads REST | 2–5 working days | Codemods, API diff, flat format on the frontend |
| Site with logic | Custom controllers and services, dynamic zones, i18n, 2–4 community plugins | 1–3 weeks | Plus Document Service rewrite, populate, plugin replacements |
| Custom admin | Own plugins, custom admin pages, webpack config, integrations keyed by id | 3–6 weeks | Plus Vite and Plugin SDK, id mapping in external systems |
For scale: one public report of a heavily customised project came in around 200 hours, roughly 120 on plugins and admin, 40 on data, 40 on the frontend. Agencies in the top search results quote "1–2 weeks, price on request"; we give a fixed price after a one-day look at the repository. Nothing in between, because custom code is different on every project.
When migrating is the wrong move
If the frontend is already on Next.js, most of the project is custom logic rather than content, and Strapi is effectively just an admin panel, price a move to Payload CMS alongside the upgrade. Payload runs inside Next.js, and for that profile the migration often costs about the same as the Strapi 5 upgrade and leaves you with one codebase and one deployment. If the site is content-heavy and editors like the Strapi admin, upgrading is cheaper and you keep the Content-Type Builder.
Get an estimate
Send us a link to the repository, or just the admin URL if the code is somewhere you cannot share yet. We check the plugins, the custom code and the API consumers and reply within a day with a fixed price and a duration. Details of the service are on strapi.digital/strapi-5-migration.
FAQ
Is Strapi 4 still supported?
No. Bug fixes for the 4.x branch stopped at the end of October 2025 and security patches at the end of April 2026. Anything found in Strapi 4 or its frozen dependencies after that stays open until you upgrade.
Can I upgrade to Strapi 5 without touching the frontend?
Temporarily. Send the header Strapi-Response-Format: v4 with REST requests and Strapi 5 wraps responses in the old data.attributes format. For GraphQL there is v4CompatibilityMode: true in the plugin config. Both are transition modes: you still have to move to the flat format and documentId, but you can do it endpoint by endpoint after the backend is live.
Do Strapi 4 plugins work in Strapi 5?
Generally no. The plugin API changed, @strapi/helper-plugin was removed, the admin now builds with Vite and Design System v2. Official plugins ship v5 versions; for third-party ones check the Marketplace, and expect at least one on a typical project to need a replacement or a rewrite.
How long does a Strapi 4 to 5 migration take?
From a few days for a content site with standard plugins to several weeks for a project with custom admin, custom plugins and integrations keyed by numeric id. One public write-up of a heavily customised project reported about 200 hours. We give a fixed estimate a day after seeing the repository.
Can I roll back after upgrading?
Not automatically. On first start Strapi 5 migrates the database schema (adds documentId, restructures drafts and locales). The only way back is a database backup plus the old code branch, which is why the upgrade is rehearsed on a copy first.
Which Node.js version does Strapi 5 need?
Active or maintenance LTS only: 22, 24 or 26. Servers on Node 18 or 20 need a Node upgrade as part of the job.