KE

Keystone

Headless CMS framework with a powerful GraphQL API

Content Management Systems ★ 10k stars Medium setup MIT

Keystone is an open-source headless CMS and Node.js framework that lets developers define schemas in code and automatically get a GraphQL API and admin UI. It is well suited to content-driven applications.

Key features

  • Schema-as-code data modeling
  • Auto-generated GraphQL API
  • Customizable admin UI
  • Flexible field types

Pros & cons

Strengths

  • Strong TypeScript support
  • Flexible and extensible

Trade-offs

  • Headless only
  • Smaller community than Strapi

Keystone replaces

Last reviewed Aug 26, 2026 · 784 words

There is no Keystone container to pull. Keystone is a Node.js framework: you write a schema in TypeScript, it generates the database tables (through Prisma), a GraphQL API and an admin interface, and the thing you deploy is your own application with Keystone inside it. That one fact decides whether it belongs in your stack. If "self-hosting a CMS" means installing a product and handing the login to an editor, look elsewhere. If it means owning a content backend you can version, test and type-check, Keystone is the most pleasant way I know to do it.

The schema file is the whole product

Everything flows from one keystone.ts. A minimal blog backend looks like this:

import { config, list } from '@keystone-6/core';
import { allowAll } from '@keystone-6/core/access';
import { text, timestamp, relationship } from '@keystone-6/core/fields';

export default config({
  db: { provider: 'postgresql', url: process.env.DATABASE_URL! },
  lists: {
    Post: list({
      access: allowAll,
      fields: {
        title: text({ validation: { isRequired: true } }),
        body: text({ ui: { displayMode: 'textarea' } }),
        publishedAt: timestamp(),
        author: relationship({ ref: 'User' }),
      },
    }),
    User: list({ access: allowAll, fields: { name: text() } }),
  },
});

From that, Keystone produces migrations, a typed GraphQL schema with queries and mutations for every list, and an admin UI with a form per list. Add a field, restart, and the API, the types and the editing screen all update. Access control is written as functions next to the fields, which is a better place for it than a permissions matrix nobody audits. The project dates from 2013 and the current generation, Keystone 6, has been stable long enough that the 9,963 stars represent an established tool rather than a launch spike.

Deploying it means building it

npm create keystone-app@latest scaffolds a project; keystone dev runs it locally on port 3000 with the database schema pushed automatically. For a server you do the same thing you would with any Node app: keystone build, then run migrations, then keystone start, all inside a Dockerfile you write. The catalogue's "docker" deployment tag refers to that, not to an image with a version number on it. SQLite works and is fine for a personal site; PostgreSQL is the right answer for anything with more than one editor, and the one Postgres for everything pattern fits well because Keystone only needs a database URL. Budget the 512 MB the catalogue lists for the Node process plus whatever the database needs, and put it behind the reverse proxy you already have. Sessions, password auth and an admin login come from @keystone-6/auth, which you wire in with a few lines rather than switch on.

Headless only, with no plugin store to lean on

Keystone serves content over GraphQL and nothing else. There is no theme, no public page rendering, no marketplace of plugins; the front end is whatever you build with Next.js, Astro or plain fetch calls. This is the second catalogue con and it is accurate, but it is also the point: the framework refuses to make front-end decisions for you. The cost is that features other CMSs ship as toggles (image transforms, scheduled publishing, localisation workflows) are things you implement with hooks and custom fields. Document fields with rich text and a component-block system exist and are good, but there is more code in a Keystone project than a Strapi one, always.

Versus Strapi, Directus and Payload

Strapi lets editors design content types in the admin UI and has a far larger community and plugin marketplace; it wins when the person defining the schema is not a developer. Directus wraps an existing database, ships as a real container, and gives REST and GraphQL over any table; it is the one to install if "self-host a CMS this afternoon" is the actual goal. Payload is Keystone's closest cousin: code-first, TypeScript, with tighter Next.js integration and faster recent momentum. Keystone's edge over Payload is a simpler mental model and a longer track record; Payload's edge is richer built-in features. If you are leaving Contentful with an existing developer team, any of the three code-first options will feel like coming home.

What I'd do

Choose Keystone when a developer owns the content model and the front end is already a separate app. Run it on PostgreSQL from day one, commit the generated migrations, build a small Docker image with keystone build and deploy it next to your other Node services. If instead the CMS must be usable by someone who will never open keystone.ts, install Directus and keep Keystone for the project where the schema is the product.

Compare Keystone

7 head-to-head comparisons.

Similar content management systems apps