Self-hosted service
strapi/strapi avatar
strapi/strapi

Strapi: a TypeScript monorepo where the content model is the API contract

Self-hosted headless CMS in JavaScript and TypeScript that turns visually defined content models into REST and GraphQL APIs, with roles, media library, and i18n built in.

73,251 stars9,879 forksTypeScriptLicense varies

At a glance

What is it?
Strapi generates REST and GraphQL endpoints from content types you design in the admin UI, which is its main convenience and its main hazard. There are no official Docker images, the default branch is develop, and the license field is a pointer rather than an identifier.
Who is it for?
Strapi fits teams that want content editors to own structure and developers to own the frontend, and the generated REST and GraphQL surface is the reason it saves time. Two things decide whether that holds.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

Cloning the repository gives you develop, not a release

The default branch of strapi/strapi is `develop`, and the published releases are tagged separately: v5.55.1 on 2026-09-24, v5.55.0 on 2026-09-23 and v5.54.0 on 2026-09-16. The last push to the repository was 2026-09-29, so the default branch is moving faster than the tags.

The consequence bites anyone who reads the repository to understand the product rather than to install it. What you see on `develop` is work in progress, and the documentation site can describe behaviour that has not shipped in the tag you are running. If you are pinning a Strapi 5.x release for a deployment, the tag is the reference, not the branch, and the migration guides are the documented route between versions.

There are no official Docker images, so you build your own

Strapi does not ship official Docker images. You construct one from your own project, and the fastest documented route is a community CLI:

bash
npx @strapi-community/dockerize@latest

That tool generates a `Dockerfile` and a `docker-compose.yml` tailored to your project, and it comes from the strapi-tool-dockerize repository rather than from the Strapi team. The repository does carry `docker-compose.dev.yml` and `docker-compose.test.yml` at the top level, but those are development and test compositions, not a production image.

The consequence is that everything between a cloned project and a running production container is your responsibility: base image, Node version, build steps, migrations, and the upgrade path when the Strapi tag changes. A generated starting point from a community tool saves an afternoon and then becomes the file your team owns and must maintain, which is a reasonable trade for a self-hosted deployment and a poor surprise for anyone expecting a vendor-supported image.

Both APIs are generated from the content type, so the model is the contract

Content structures are designed visually in the Content-Type Builder, with no code required, and Strapi then generates a REST API and a GraphQL API for every content type. Both databases and clients come out of the same declaration: SQLite, PostgreSQL, MySQL and MariaDB are the database options, and the output is meant to be consumed from any frontend, mobile app or IoT device.

This is where the convenience turns into a decision you have to make deliberately. Because the endpoints are derived from the model, renaming a field is an API change for every consumer at once, and turning on the GraphQL plugin adds another surface to keep in step with the REST one. The sharper edge is that the model can be edited from the admin interface by someone who is not a developer, so a schema change can land without appearing in a pull request. If your process depends on reviewing schema changes as code, the admin UI is a surface your review has to include.

Every request walks four layers in a fixed order

Strapi describes its request handling as a layered backend architecture, and the order is stated plainly: Routes, then Middlewares, then Controllers, then Services. Customizing a request means deciding which of those four to hook, and the documentation keeps backend customization in its own section for that reason.

The consequence of a fixed pipeline is that the same behaviour can be implemented in several places. Access control exists as a first-class feature, with granular Roles and Permissions out of the box, alongside built-in Media Library, Internationalization and Draft and Publish. Adding a check at the wrong layer tends to duplicate something the pipeline already does or to sit outside it, and finding out which happens only shows up as a route that behaves differently from its neighbours. Read the layer list before writing a hook, not after.

Eight workspace globs and three test runners for one change

This is a monorepo in the full sense. The workspaces list covers `packages/*`, `packages/*/*`, `examples/*`, `examples/plugins/*`, `examples/*/src/plugins/*`, `.github/actions/*` and `scripts/*`, and the build is driven by Nx with `nx run-many --targets build:code,build:types --nx-ignore-cycles`, with `nx.json` and `lerna.json` both present and Yarn Berry managing the lockfile. Examples are checked in as `examples/complex/`, `examples/empty/`, `examples/getstarted/`, `examples/kitchensink/`, `examples/kitchensink-ts/` and `examples/plugins/`.

Testing is where the cost shows, because three runners coexist: Jest with separate unit, front, api and cli configurations, `vitest.config.ts`, and `playwright.base.config.js` for browser work. Around them sit husky, lint-staged, commitlint, prettier, an oxlint script, codecov, sonar and a syncpack config that keeps dependency versions consistent. The consequence for a contributor is that identifying which runner owns your change, and getting a local build across the affected packages, is real setup work before you write a line of code.

The license field is a pointer, not an identifier

package.json declares the license as `SEE LICENSE IN LICENSE`, with a LICENSE file at the top level of the repository, and GitHub's own classification of the project comes back as NOASSERTION rather than a standard identifier. The author and maintainer fields both name Strapi Solutions SAS, and the same organisation runs Strapi Cloud, described as the official managed hosting platform with a built-in database, media library and CDN.

The consequence is practical rather than dramatic. Any tooling that keys off SPDX identifiers, from licence allowlists to compliance reports, gets a string it cannot resolve, and the terms have to be read out of the file by hand. It also means the open-source repository and the commercial hosting product are two things from one vendor, so questions about cost and questions about licence are separate questions with separate answers. Teams evaluating this should read LICENSE directly rather than trusting a scanner.

The quickstart hands you a project with five features already enabled

Creating a project is one command:

bash
npx create-strapi@latest my-project

What it produces is not an empty project. The generated application comes with authentication, permissions, content management, the content type builder and file upload already in place, and the CLI documentation covers further options including TypeScript and a quickstart variant. Hardware and software requirements, covering the operating system, Node.js and the supported databases, live in a separate requirements document rather than in the command output.

The consequence is that your first decisions are subtractive. Those five defaults are a reasonable starting point and they are also five pieces of surface area you now own, with the authentication and permissions pieces deciding who can reach the API and the admin interface. Teams who wanted a bare content API spend their first hour removing what they did not ask for, and teams who wanted an editorial workflow get it without writing any code.

Editorial conclusion

Strapi fits teams that want content editors to own structure and developers to own the frontend, and the generated REST and GraphQL surface is the reason it saves time. Two things decide whether that holds. A model edited in the admin UI is a schema change, so code review has to cover a surface most teams forget, and production packaging is entirely yours because no official Docker image exists. Anyone self-hosting should also read the LICENSE file directly, since package.json carries a pointer rather than an SPDX identifier and automated compliance checks will come back empty. Before you commit, verify three things: which Strapi 5.x tag you are pinning, how your schema changes reach version control, and who maintains the Dockerfile you generate.

Frequently asked questions

What is Strapi used for?

Strapi is a self-hosted headless CMS for building content APIs. You design content types visually in the Content-Type Builder and Strapi generates a REST API and a GraphQL API for each one, consumed by a frontend, mobile app or IoT device, while content creators work in the admin interface.

Can Strapi be self-hosted?

Self-hosting is the default mode, and Strapi Cloud is offered as the managed alternative. One thing to know is that Strapi ships no official Docker images, so you build your own from your project, with `npx @strapi-community/dockerize@latest` generating a Dockerfile and a docker-compose.yml as a starting point.

How do I install Strapi?

Run `npx create-strapi@latest my-project`. The generated project includes authentication, permissions, content management, the content type builder and file upload, and the CLI documentation covers additional options such as TypeScript. Supported databases are SQLite, PostgreSQL, MySQL and MariaDB.

How do I use the Strapi API?

Every content type gets a REST API and a GraphQL API generated for it, and requests flow through a fixed backend pipeline of Routes, then Middlewares, then Controllers, then Services. Because the endpoints come from the model, a field rename changes the API for every consumer at the same time.

Is Strapi free or paid?

The code is in a public repository with a LICENSE file at the top level, though package.json records the license as `SEE LICENSE IN LICENSE` and GitHub classifies it as NOASSERTION rather than a standard identifier, so scanners will not resolve it. Strapi Cloud, the official managed hosting platform with a built-in database, media library and CDN, is a separate product from the same vendor, Strapi Solutions SAS.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
For maintainers

Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/strapi-strapi.svg)](https://hysenlabs.com/projects/strapi-strapi)
Community notes

Community notes