Strapi 5 upgrade guide: migrating from Strapi 4 step‑by‑step
A complete, practical guide to moving a Strapi 4 site to Strapi 5 – backup, dependency updates, config changes, plugin compatibility, and testing with Next.js.
By the Revisionary team · · 5 min read
Upgrading a Strapi 4 project to Strapi 5 is a straightforward process if you follow a disciplined checklist: back up your data, align the runtime, update core and plugins, adjust configuration, migrate custom code, and verify the front‑end integration. When each step is completed, you keep the content history, edit‑locks, and audit logs intact, and your Next.js site continues to fetch the same API shape.
1. Prerequisites
| Requirement | Why it matters |
|---|---|
| Node 18‑24 | Strapi 5 runs on these versions; older Node can cause build failures. |
| Supported database (PostgreSQL, MySQL, SQLite) | No schema changes are required for the core upgrade, but the driver must match the Node version. |
| Git branch for the migration | Keep the original code on a separate branch to allow roll‑back if needed. |
| Access to the server’s filesystem | Needed for backup files and to edit config/plugins.ts. |
Make sure you have a clean working copy and that all local changes are committed.
2. Back up everything
Even though the migration does not touch the database schema, a full backup protects you from unexpected data loss.
# PostgreSQL example
pg_dump -U <user> -h <host> -Fc -f strapi_backup.dump <db_name>
# MySQL example
mysqldump -u <user> -p<password> <db_name> > strapi_backup.sqlIf you are using the Revisionary plugin, its audit log and trash are stored in the same database, so they are covered by the dump. Keep the dump file in a secure location and, if possible, test a restore on a staging server.
3. Update the project’s dependencies
3.1. Change the Strapi version in package.json
Replace the Strapi 4 packages with the Strapi 5 equivalents. Most core packages keep the same name, but the major version changes.
// package.json (excerpt)
"dependencies": {
"@strapi/strapi": "^5.0.0",
"@strapi/plugin-i18n": "^5.0.0",
"@strapi/plugin-users-permissions": "^5.0.0",
// other plugins …
}If you use community plugins, check their npm pages for a Strapi 5 compatible release. If none exists, you may need to replace the plugin or defer the upgrade.
3.2. Install the new dependencies
npm install # or yarn installDuring installation you may see peer‑dependency warnings. Resolve them by updating the indicated packages to their Strapi 5 versions.
4. Adjust configuration files
Strapi 5 introduces a new layout for config/plugins.ts. The old config/plugins.js format still works after a simple rename, but you should adopt the typed format to avoid runtime errors.
// config/plugins.ts (new format)
export default ({ env }) => ({
// Example: keep the Revisionary plugin configuration
revisionary: {
enabled: true,
// No breaking changes for the plugin yet, but keep an eye on future releases.
},
// other plugins …
});If you have custom environment variables, move them to the new config/env/{environment}.js structure as described in the Configuration guide (/docs/reference/configuration).
5. Review breaking changes in core APIs
5.1. Document Service API
The Document Service API now requires explicit locale handling for localized queries. If you previously called strapi.services.article.find(params), update the call to include the locale when needed.
// Before (Strapi 4)
const articles = await strapi.services.article.find({ _limit: 10 });
// After (Strapi 5)
const articles = await strapi.documents('api::article.article').find({
locale: 'en',
limit: 10,
});Read the full migration notes in the official Strapi documentation for the Document Service.
5.2. i18n changes
The i18n plugin now stores the default locale in config/i18n.js and expects the locale field on each entry. Adjust any custom queries that relied on the old default‑locale fallback.
// Example of a locale‑aware query
const post = await strapi.documents('api::post.post').findOne(id, {
locale: 'fr',
});5.3. Routes, Controllers, and Services
The auto‑generated route files moved from api/<name>/config/routes.json to src/api/<name>/routes/<name>.js. If you have custom routes, copy them to the new folder structure and rename the export to the new format:
// src/api/article/routes/article.ts
export default {
routes: [
{
method: 'GET',
path: '/articles',
handler: 'api::article.article.find',
},
],
};Controllers and services follow the same folder shift (src/api/<name>/controllers and src/api/<name>/services). Update any relative imports accordingly.
6. Migrate the Revisionary plugin data
Revisionary stores versioned entries in dedicated tables that are compatible with Strapi 5. After the core upgrade, run the plugin’s migration command (if the plugin provides one). At the time of writing, the plugin automatically detects the Strapi version and runs a silent migration on first start.
npm run build # builds the admin UI
npm run start # starts Strapi; the plugin migrates its tablesVerify that the Version history panel appears in the Content Manager and that the Audit log shows recent actions (/docs/everyday-use/history). If the panel is missing, consult the troubleshooting guide (/docs/troubleshooting).
7. Test the back‑end locally
- Start Strapi in development mode:
bash npm run develop - Open the admin UI (
http://localhost:1337/admin). - Check that all content types load, the Revisionary panel shows entries, and you can create a draft, publish, and restore a previous version.
- Run the built‑in health check endpoint (
/admin/health) to confirm the server reports no errors.
If any custom code throws ReferenceError: strapi.services is not defined, replace it with the new Document Service call pattern.
8. Verify the Next.js front‑end
Most Next.js projects consume Strapi via the REST or GraphQL endpoints. The REST shape is unchanged for basic fields, but the locale handling and the Document Service paths differ.
8.1. Update the API client
If you use fetch('/api/articles'), you may need to add the locale query parameter for multilingual sites.
// pages/api/articles.js (Next.js API route)
export default async function handler(req, res) {
const locale = req.query.locale || 'en';
const response = await fetch(`${process.env.STRAPI_URL}/api/articles?locale=${locale}`);
const data = await response.json();
res.status(200).json(data);
}For GraphQL, the endpoint stays /graphql, but the schema now includes the locale argument on each query field.
8.2. Run the front‑end locally
cd next-app
npm run devVisit the site and confirm that content loads, draft preview works (if you use the Draft & Publish preview mode), and navigation is unaffected.
9. Deploy to production
- Build the Strapi admin UI:
bash npm run build - Copy the built
build/folder to your production server or container image. - Ensure the environment variables (
DATABASE_URL,HOST,STRAPI_ADMIN_JWT_SECRET, etc.) match the production values. - Restart the Strapi process.
- Monitor the logs for any migration warnings.
If you deploy with Docker, update the Dockerfile to use a Node 18‑based base image and reinstall dependencies with the new version.
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:18-alpine AS runtime
WORKDIR /app
COPY --from=builder /app/build ./build
COPY package*.json ./
RUN npm ci --only=production
EXPOSE 1337
CMD ["node", "./build"]10. Post‑migration checklist
- All content types appear in the admin UI.
- Revisionary version history, trash, and audit log are functional.
- Custom plugins are either updated or disabled.
- Next.js front‑end displays content for every locale used.
- Automated tests (if any) pass against the new API.
- Backup files are stored safely, and a restore test has been performed.
Following this checklist gives you confidence that the migration will not disrupt editors or end users.
11. Common pitfalls and how to avoid them
| Symptom | Likely cause | Fix |
|---|---|---|
Cannot find module '@strapi/plugin-…' | Plugin not yet released for Strapi 5 | Either upgrade to the plugin’s 5.x version or temporarily disable it. |
| Missing fields in the admin UI | Content‑type JSON schema still uses the old attributes format | Run strapi generate:model again or manually migrate the schema to the new format described in the migration guide. |
| Locale not applied in API responses | Calls still use the old find service without locale | Switch to the Document Service API and pass the locale option. |
| Audit log empty after migration | Revisionary migration failed silently | Restart Strapi with DEBUG=revisionary:* npm run develop to see detailed logs and contact support via the Support page (/docs/reference/support). |
12. When to consider a fresh install instead of an in‑place upgrade
If your project relies heavily on plugins that have no Strapi 5 equivalents, or if the custom codebase is tightly coupled to Strapi 4 internals, a fresh Strapi 5 installation with a data import (using the Content Export/Import feature) may be faster. The export can be performed from the admin UI (Settings → Export data) and then imported into the new instance.
Conclusion
Migrating from Strapi 4 to Strapi 5 does not require a complete rewrite; it is a series of well‑defined steps: back up, update dependencies, adjust configuration, migrate custom code, verify the Revisionary plugin, and test the Next.js front‑end. By following the checklist above, you preserve content history, keep edit‑locks and audit logs intact, and take advantage of the newer Document Service API, improved i18n handling, and performance gains of Strapi 5.
The Revisionary team
Developers who build Revisionary, a version history plugin for Strapi 5. We write about Strapi, web development, AI and application security.
This article was written with the help of AI.