Strapi 5 is the current major release of the open-source, headless Content Management System (CMS) built on Node.js, and it introduces significant changes: a new Document Service API, Vite as the default bundler, a flattened REST response format, and stricter security defaults. These improvements make Strapi more powerful, but they also mean the learning curve has shifted. Mistakes that were minor annoyances in v4 can now cause real headaches in Strapi 5.
Most of the struggles beginners face are not about complexity. They usually come from skipping foundational steps, carrying over v4 assumptions, or ignoring defaults that exist for good reasons. This guide covers the seven most common mistakes so you can sidestep them and focus on building.
In brief:
- Skipping the official docs and CLI leads to misconfigured projects and hours of avoidable debugging.
- Poor content modeling decisions are expensive to undo. Plan your Content-Types, components, and the new Document concept before you build.
- Ignoring security defaults, especially the v5.24+ HTTPS requirement, can block Admin Panel login in production.
- The
populate=*anti-pattern can inflate API response times by 5× and payloads by 320×. Targeted queries matter from day one.
Mistake 1: Skipping the Documentation and Quick Start Guide
The most common mistake is diving in without reading the official docs. Strapi 5 introduced breaking changes that affect project scaffolding, API response formats, and plugin compatibility. Assumptions from v4 tutorials or blog posts will lead you astray.
Use the Quick Start to Scaffold Your Project
The guide walks you through scaffolding a project with the correct command for Strapi 5:
npx create-strapi@latest my-strapi-projectThe older npx create-strapi-app@latest still works, but create-strapi is the preferred package in v5. Alternative package managers are also supported:
# yarn
yarn create strapi my-strapi-project
# pnpm
pnpm create strapi my-strapi-projectBefore running anything, verify your environment meets the installation guide. Strapi 5 requires Node.js Active LTS or Maintenance LTS versions: currently v20, v22, and v24. Odd-number releases like v23 or v25 are not supported. You also need Python if using SQLite as your development database.
After scaffolding, start the development server from the project root:
npm run develop
# or
yarn developThe Admin Panel opens automatically at http://localhost:1337/admin, where you create your first admin user.
Understand the project structure
Getting familiar with the layout saves significant debugging time. Here is what the directory looks like:
my-strapi-project/
├── .strapi/ # Auto-generated build artifacts; DO NOT modify manually
├── config/ # All application configuration files
│ ├── admin.ts/js
│ ├── api.ts/js # (optional)
│ ├── cron-tasks.ts/js
│ ├── database.ts/js
│ ├── middlewares.ts/js
│ ├── plugins.ts/js
│ └── server.ts/js
├── src/
│ ├── api/ # API-specific code
│ ├── admin/ # Admin Panel customizations
│ └── plugins/ # Custom plugins
├── public/ # Public assets and uploads
└── types/generated/ # Auto-generated TypeScript types (TS projects)Key Strapi 5 changes to be aware of: the REST API response format is flattened format, documentId replaces id as the primary identifier, Vite replaces Webpack as the default bundler, and i18n in core means internationalization is no longer a separate plugin.
All configuration files use the env() helper for secure environment variable access. For example, your server configuration in config/server.ts typically looks like this:
module.exports = ({ env }) => ({
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
app: {
keys: env.array('APP_KEYS'),
},
url: env('PUBLIC_URL', ''),
});Environment-specific overrides go in /config/env/{environment}/ subdirectories, controlled by the NODE_ENV variable.
Mistake 2: Not Using the Strapi CLI Effectively
The Strapi CLI is more than a scaffolding tool. It handles development workflows, production builds, data migration, and cloud deployment. Most beginners only use develop and start, which means they miss commands that solve real operational problems.
Key CLI Commands Every Beginner Should Know
Per the CLI docs, these are the core commands:
| Command | Purpose |
|---|---|
strapi develop | Start with auto-reloading, for development only |
strapi start | Start without auto-reloading, for production |
strapi build | Build the Admin Panel, required before production deployment |
A few things are worth keeping in mind. The Content-Type Builder is disabled in strapi start mode because it requires application restarts. Changes to server configuration require rebuilding the Admin Panel with yarn build or npm run build. In v5, the default package manager is no longer yarn. Prefix strapi with your project's package manager, for example yarn strapi build or npm run strapi build.
Some v4 commands were removed in v5. strapi new is replaced by npx create-strapi@latest, strapi install [plugin] is replaced by the npx command from the plugin's Marketplace page, and strapi watch-admin is now the default behavior of strapi develop with --no-watch-admin available to disable it.
Strapi 5 also introduces the @strapi/upgrade tool for managing version upgrades with automated codemods:
npx @strapi/upgrade major # Major version upgrade
npx @strapi/upgrade minor # Minor version upgrade
npx @strapi/upgrade patch # Patch version upgradeData Migration with strapi export, strapi import, and strapi transfer
The data commands handle backup, restoration, and cross-instance data transfer. They are useful when you are managing staging and production environments.
strapi export creates an encrypted, compressed archive of your data:
strapi export --file my-backup --no-encryptNote that admin users, API tokens, and media from third-party providers such as Cloudinary or AWS S3 are never included in exports.
strapi import restores from an archive, but this is where mistakes get expensive. Per the import docs, this command deletes all existing data in the database and uploads directory before importing:
strapi import --file my-backup.tar.gzstrapi transfer streams data directly between two Strapi instances using the transfer protocol. Both instances need matching schemas:
strapi transfer --to https://target-instance.com/admin --to-token your-transfer-tokenFor cloud deployments, the Cloud CLI adds commands like strapi deploy, strapi login, and strapi link to connect local projects with your Cloud environment.
Mistake 3: Poor Content Modeling and Data Structure
Content modeling decisions made early are expensive to change later. Most teams learn this the hard way when they need to restructure relationships or refactor components across dozens of Content-Types. Taking time to plan upfront pays off.
Plan Your Content-Types Before You Build
The Content-Type Builder manages three schema categories: Collection Types for multiple entries like blog posts or products, Single Types for one entry like a homepage or global settings, and Components for reusable structural elements embedded within Content-Types.
Before opening the Content-Type Builder, sketch out your data model. Define which entities are Collection Types versus Single Types. Identify repeating field groups that should be extracted into components. Map relationships between Content-Types. Refer to the modeling guide for naming conventions and modularity patterns.
Here is what a typical Collection Type schema looks like in schema.json:
{
"kind": "collectionType",
"collectionName": "articles",
"info": {
"displayName": "Article",
"singularName": "article",
"pluralName": "articles"
},
"attributes": {
"title": { "type": "string" },
"slug": { "type": "uid", "targetField": "title" },
"body": { "type": "richtext" },
"author": {
"type": "relation",
"relation": "manyToOne",
"target": "api::author.author"
},
"coverImage": { "type": "media", "multiple": false }
}
}Both singularName and pluralName are required. They determine your API endpoint routing. Also be aware of reserved names that cannot be used in Strapi 5 schemas: meta, status, locale, document, and anything prefixed with strapi, _strapi, or __strapi.
Using Components and Dynamic Zones Wisely
The difference between components vs Dynamic Zones comes down to structural predictability:
| Criteria | Components | Dynamic Zones |
|---|---|---|
| Structure | Same fields every time | Variable per entry |
| Component mixing | Single type only | Multiple types in any order |
| Best for | Address blocks, SEO metadata, author cards | Page builders, flexible landing pages, variable article layouts |
Use components when a fixed, predictable structure repeats across Content-Types. Use Dynamic Zones when editors need to select from multiple component types and arrange them in any sequence. The page builder pattern is a classic example.
A common anti-pattern is duplicating identical field groups across multiple Content-Types instead of extracting them into reusable components. This creates maintenance headaches and inconsistent data structures. Take the time to understand relations and define clear relationships between Content-Types to create a well-connected data model.
Understanding the Document Concept in Strapi 5
The Document concept is new in Strapi 5 and fundamental to how content works. A Document groups all variations of a single piece of content, including different locales and draft or published states, under one logical entity with a single documentId.
{
"documentId": "a1b2c3d4e5f6g7h8i9j0klm",
"publishedAt": "2024-03-14T15:40:45.330Z",
"locale": "en"
}The documentId is a 24-character alphanumeric string that persists across all locales and draft or published versions. This replaces the numeric id from v4. Every API call in Strapi 5 uses documentId instead of id.
Three document statuses exist: Draft, Modified, and Published. The Document Service API returns draft content by default. You need to pass status: 'published' explicitly to fetch live content in backend code. That safety-first default helps, but it still catches people.
Mistake 4: Ignoring Security Best Practices
Security gaps in a headless CMS can expose your content API to unauthorized access, data leaks, or worse. Strapi provides strong security primitives, but they only work if you configure them properly.
Environment Variables and Secrets Management
Strapi auto-generates five critical security variables during project creation, per the env vars docs:
| Variable | Purpose |
|---|---|
APP_KEYS | Signs cookies and secrets |
API_TOKEN_SALT | Salt for API token creation |
ADMIN_JWT_SECRET | JWT secret for admin authentication |
JWT_SECRET | JWT secrets for Users & Permissions |
TRANSFER_TOKEN_SALT | Salt for data transfer tokens |
Never commit your .env file to version control. Instead, commit a .env.example with placeholder values so teammates know which variables are required. Per the OWASP cheat sheet, plaintext .env files are acceptable for local development but should not be used in production. For production environments, use a dedicated secrets manager rather than plaintext .env files.
One important detail: changing API_TOKEN_SALT invalidates all existing API tokens. That can be useful as an emergency revocation mechanism, but accidental changes can break integrations.
Configuring Role-Based Access Control (RBAC)
Strapi's RBAC system lets you define granular permissions across Collection Types, Single Types, plugins, and settings. Configure roles via Settings > Administration panel > Roles in the Admin Panel.
The principle of least privilege applies here. Define permissions for each role so users have access only to the data and actions they actually need. For API tokens, prefer read-only tokens for public access, scope custom tokens to minimum permissions, and rotate long-lived tokens regularly.
For programmatic RBAC, you can register custom conditions in your bootstrap function:
// /src/index.js
module.exports = {
bootstrap() {
const condition = {
displayName: "Is Owner",
name: "is-owner",
category: "default",
handler: (user) => ({ createdBy: user.id }),
};
// Registration logic here
},
};HTTPS Requirements in Strapi v5.24+
This is the gotcha that catches almost everyone deploying to production for the first time. Per the Strapi FAQ, starting from v5.24.0, the Admin Panel stores session data in secure, HTTP-only cookies. Browsers refuse to store or transmit these cookies over plain HTTP, which means admin login cannot complete if the panel is served without HTTPS.
The exact error you will see is documented in GitHub issue #24508:
error: Failed to create admin refresh session Cannot send secure cookie over unencrypted connection
http: POST /admin/login (213 ms) 500Local development on http://localhost is exempt. The dev configuration does not mark cookies as secure. But production deployments require TLS termination at a reverse proxy layer such as Nginx, Caddy, Traefik, or a cloud load balancer. The critical fix is setting proxy: true in your server config so Strapi trusts X-Forwarded-Proto headers from your proxy:
// config/server.js
export default ({ env }) => ({
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
proxy: true,
url: env('URL', 'https://your-domain.com'),
app: { keys: env.array('APP_KEYS') },
});Without proxy: true, Strapi detects incoming requests as HTTP even when the external connection is HTTPS, which triggers the secure cookie error.
Keep Strapi and its dependencies updated as well. Regular updates protect your system against vulnerabilities and help with compatibility. Run npm audit --audit-level=moderate periodically, and consider integrating Dependency-Check into your CI/CD pipeline.
Mistake 5: Overlooking Performance from Day One
Performance problems in Strapi rarely announce themselves during development. They usually surface in production when traffic increases, payloads bloat, and database connections exhaust. Building with performance in mind from the start saves painful refactoring later.
Avoid populate=* in API Queries
This is the most common Strapi performance mistake, and the populate guide explicitly designates populate=* as an anti-pattern for production environments.
By default, Strapi 5's REST API returns only top-level scalar fields: no relations, media, components, or Dynamic Zones. This conservative default prevents accidental performance degradation. Wildcard population overrides that protection, returning everything one level deep without discrimination.
The numbers tell the story. Per benchmarks from React best practices:
| Metric | populate=* | Targeted Populate | Improvement |
|---|---|---|---|
| Payload size | 2.04 MB | 6.72 KB | 320× reduction |
| Response time | 801 ms | 160 ms | 5× speedup |
Use the qs library to construct targeted queries with explicit fields selection:
const qs = require('qs');
const query = qs.stringify({
populate: {
author: true,
coverImage: true,
},
fields: ['title', 'summary'],
}, { encodeValuesOnly: true });
const response = await fetch(`/api/articles?${query}`);In Strapi 5, there is an additional wrinkle: the shared population strategy for components and Dynamic Zones no longer works. You need explicit on fragments:
populate[content][on][text-block.text-block]=true
populate[content][on][image-block.image-block]=trueStrapi 5 also provides an interactive builder to construct these complex queries visually. It is a helpful tool while you are learning the syntax.
Also watch for the N+1 query pattern: multiple sequential API requests for related data instead of consolidated population. Per the performance analysis, this pattern can limit concurrent user capacity to approximately 200 users due to database connection pool exhaustion.
Caching Strategies and CDN Setup
Caching reduces server load and speeds up response times. The strapi-cache plugin on the Strapi Market provides production-grade caching with Redis backend support, route-based caching rules, automatic cache invalidation on content changes, and ETag support.
For media assets, configure a CDN to reduce bandwidth and improve global load times. The recommended approach is an S3-compatible storage provider with CloudFront or Cloudflare R2 in front of it. See the CDN guide for step-by-step setup.
Database Indexing and Query Optimization
Strapi 5 does not natively support declaring custom database indexes in Content-Type schemas. The sanctioned workaround is Knex migrations:
// ./database/migrations/20240101120000-add-slug-index.js
module.exports = {
async up(knex) {
await knex.schema.alterTable('articles', (table) => {
table.index(['slug'], 'idx_articles_slug');
});
},
};Note that PostgreSQL has a 63-character identifier limit, so specify short, unique index names manually.
For your production database, Strapi recommends PostgreSQL. SQLite's file-level locking causes overlapping admin actions, background tasks, and API writes to result in slow requests, timeouts, and intermittent bugs. SQLite is fine for local development, but production deployments should use PostgreSQL, or MySQL 8.0+/MariaDB 10.3+ as alternatives.
Mistake 6: Underutilizing the Plugin Ecosystem and Strapi Cloud
Not fully leveraging Strapi's ecosystem is a common oversight that leads to rebuilding functionality that already exists. Strapi offers an extensive plugin architecture, a dedicated marketplace, and managed hosting designed specifically for Strapi workloads.
Explore the Strapi Market for Ready-Made Plugins
The marketplace provides plugins that extend core functionality without custom development. Some notable examples include:
| Plugin | Purpose |
|---|---|
| REST Cache | HTTP response caching with auto-invalidation |
| Navigation | Menu builder with flat, tree, and Redux-compatible output |
| Email Designer 5 | Design email templates in admin, send programmatically |
| Blurhash | Generate image placeholders for better UX |
Before building something custom, check the Market first. Many common needs, including SEO management, sitemaps, analytics dashboards, and advanced media handling, already have community or official plugins available.
Building Custom Plugins with the Plugin SDK
Strapi 5 introduces a dedicated Plugin SDK (@strapi/sdk-plugin) that lets you scaffold and develop plugins independently of a Strapi project:
npx @strapi/sdk-plugin init my-strapi-pluginThe SDK provides commands for building (strapi-plugin build), watching (strapi-plugin watch), and validating (strapi-plugin verify) your plugin before publishing. This is a significant improvement over v4's plugin development workflow, and the plugin tutorial walks through the full process.
Be aware that some v4 plugin patterns have breaking changes in Strapi 5: the @strapi/helper-plugin package is removed in v5, injectContentManagerComponent() is replaced with getPlugin('content-manager').injectComponent(), and the Entity Service is replaced by the Document Service for server-side code.
Simplify Deployment with Strapi Cloud
Strapi Cloud is a Platform-as-a-Service (PaaS) designed specifically for Strapi projects. It includes managed PostgreSQL, an integrated CDN, automated deployments from GitHub or GitLab, and the same API behavior as self-hosted, where only the URLs differ.
Deploy directly from your terminal with the Cloud CLI:
strapi login # Authenticate via browser
strapi link # Link local folder to Cloud project
strapi deploy # Deploy to Strapi CloudStrapi Cloud is well-suited for teams that want to reduce operational overhead, but review the plan limits. API request quotas vary by tier and may be a factor for high-traffic applications. If you need full infrastructure control, custom database configurations, or specific compliance requirements, self-hosting remains an option with Strapi's open-source core.
Mistake 7: Neglecting Version Control and Development Workflow
Skipping version control and workflow practices during early development creates problems that compound over time. Most teams learn this the hard way when they need to roll back a breaking change or onboard a new developer.
Initialize Git from Day One
Set up Git immediately after scaffolding your project. This is not just about backup. It is about tracking Content-Type schema changes, configuration adjustments, and custom code evolution. The Git guide covers Strapi-specific workflow patterns.
Your .gitignore should exclude .env, node_modules/, .strapi/, and the public/uploads/ directory if using local file storage. Commit .env.example with placeholder values so team members know which variables are required.
Document Your Content Models and API Endpoints
Thorough documentation keeps your team aligned for future development and troubleshooting. Document your Content-Type schemas, the relationships between them, custom API endpoints, and any lifecycle hooks or middleware you have added. This gives new team members a reference point and saves time when something breaks.
The Documentation plugin on the Strapi Market can auto-generate API documentation. Additionally, Strapi v5.20+ generates OpenAPI specifications for your content APIs.
Use Draft and Publish and Content History for Safe Editing
Strapi 5 includes Draft & Publish as a per-content-type feature, which enables a safer editorial workflow where changes are staged as drafts before going live. This is especially useful on teams where multiple people manage content.
A critical API behavior to understand: the Document Service API in backend code returns draft content by default, while the REST and GraphQL APIs for frontend use return published content by default. Always specify status explicitly in Document Service calls to avoid accidentally exposing unpublished content:
// Explicitly fetch published content, safe for production
await strapi.documents('api::article.article').findOne({
documentId: 'a1b2c3d4e5f6g7h8i9j0klm',
status: 'published',
});Content History provides immutable snapshots of previous document versions with one-click restore from the Content Manager UI. Restoring a version overrides the current draft and sets the status to Modified. The restored content does not automatically become live, so editors still need to publish manually.
Note: Content History is available on Growth or Enterprise plans. Review Workflows, which add formal approval stages, are available on the Enterprise plan. Check current feature availability before planning workflows that depend on these capabilities.
Combining Git for code-level version control, Draft & Publish for content-level staging, and Content History for version recovery gives you a solid safety net across the project.
Get Started in Minutes
npx create-strapi-app@latest in your terminal and follow our Quick Start Guide to build your first Strapi project.