When domain logic doesn't fit into pre-built content types, KeystoneJS becomes the foundation. It's a code-first headless CMS where data schemas live in TypeScript, and the GraphQL API, admin panel, and Prisma schema are auto-generated. You don't waste time on CRUD wrappers: the system creates them, leaving you to focus on business logic. Within 7–10 days, you get a ready solution with custom fields, role-based access, and integration with any frontend—from Next.js to Angular. This approach reduces development cost and eliminates N+1 queries through a flexible API. Contact us to discuss your project.
Unlike admin-first CMS, KeystoneJS gives full control over data. You define relationships, hooks, computed fields—the system builds migrations via Prisma. We've used this approach in 10+ projects: from e-commerce stores with custom catalogs to corporate portals with multisite. According to our measurements, KeystoneJS accelerates backend creation by 1.5x compared to Strapi. Get a consultation from our engineer for a project estimate.
When to Choose KeystoneJS
KeystoneJS is suitable when:
- Full control over data schema and business logic is required
- The team is comfortable with Node.js and TypeScript
- Custom access logic, complex computed fields, and data-level hooks are needed
- GraphQL API is the primary interface for the frontend
It is not suitable if you need out-of-the-box hosting without DevOps or a rich UI for non-technical editors (then consider Strapi or Payload).
Why Choose KeystoneJS?
KeystoneJS outperforms traditional CMS due to its code-first approach. You are not limited by fixed structures: any relationships, computed fields, and hooks on create/update. GraphQL API provides the frontend with exactly the data it needs, without N+1 queries. According to the official documentation, KeystoneJS can reduce backend development time by 30–40% through auto-generation of CRUD operations.
| Criterion | KeystoneJS | Strapi | Payload CMS |
|---|---|---|---|
| Approach | Code-first | Admin-first | Code-first |
| Typing | TypeScript | JS/TS partial | TypeScript |
| GraphQL | Full auto-generation | REST + GraphQL | REST + GraphQL |
| Customization | Maximum | Medium | High |
| DevOps | Required | Built-in hosting | Required |
How We Set Up Access and Roles
In KeystoneJS, access rights are defined at the operation (create/read/update/delete) and field levels via access in each list. We implement a role model: admin, editor, author, anonymous. Built-in session support via cookie or JWT.
// Example: only author can edit their post
access: {
operation: {
query: allowAll,
create: ({ session }) => !!session,
update: ({ session, item }) => session?.data?.id === item.authorId,
delete: ({ session }) => session?.data?.role === 'admin',
},
},
Process
- Discovery: define models, relationships, roles, integrations
- Design: create TypeScript schema, configure storage (local/S3)
- Implementation: write lists, hooks, access rules, customize Admin UI
- Testing: GraphQL queries, unit tests, load testing
- Deployment: Prisma migrations, Docker container, CI/CD
Configuration and Launch
// keystone.ts
import { config } from '@keystone-6/core';
import { lists } from './schema';
import { withAuth, session } from './auth';
export default withAuth(
config({
db: {
provider: 'postgresql',
url: process.env.DATABASE_URL!,
enableLogging: true,
idField: { kind: 'uuid' }
},
lists,
session,
ui: {
isAccessAllowed: (context) => !!context.session?.data
},
server: {
cors: {
origin: [process.env.FRONTEND_URL!],
credentials: true
}
},
})
);
Example Model: Post
// lists/Post.ts
import { list } from '@keystone-6/core';
import { text, relationship, timestamp, select, image, document } from '@keystone-6/core/fields';
export const Post = list({
access: {
operation: {
query: () => true,
create: ({ session }) => !!session,
update: ({ session }) => !!session,
delete: ({ session }) => session?.data?.role === 'admin',
},
},
fields: {
title: text({ validation: { isRequired: true } }),
slug: text({ validation: { isRequired: true }, isIndexed: 'unique' }),
status: select({
options: [
{ label: 'Draft', value: 'draft' },
{ label: 'Published', value: 'published' },
{ label: 'Archived', value: 'archived' },
],
defaultValue: 'draft',
}),
content: document({ formatting: true, links: true }),
author: relationship({ ref: 'Author.posts' }),
featuredImage: image({ storage: 'local_images' }),
},
});
GraphQL API and Frontend Integration
After running npx keystone dev, GraphQL Playground opens at http://localhost:3000/api/graphql. Example query:
query GetPosts {
posts(
where: { status: { equals: "published" } }
orderBy: { publishedAt: desc }
) {
id
title
slug
author {
name
}
}
}In Next.js, use Apollo Client:
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
export const client = new ApolloClient({
link: createHttpLink({
uri: process.env.KEYSTONE_API_URL,
}),
cache: new InMemoryCache(),
}); What's Included
- Data schema creation (lists, relationships, hooks)
- Access and role configuration
- Frontend integration (Next.js, Nuxt 3, Gatsby)
- Storage configuration (local, S3)
- Deployment and migrations
- API and admin UI documentation
- Editor team training
- 1-month post-launch support
How KeystoneJS Accelerates Development
In practice, the code-first approach allows us to reduce initial setup time by 2–3x compared to admin-first CMS. We get a ready admin panel and API immediately after describing the schema. For example, in one e-commerce project, we saved 4 days just on the design phase, bringing significant cost savings to the client.
Typical Site Development Timeline
| Phase | Duration |
|---|---|
| Setup, DB config, base lists | 1 day |
| 3–5 models with relationships | 2–3 days |
| Access and role configuration | 1 day |
| Authentication + sessions | 0.5 day |
| Frontend integration | 2–3 days |
| Deployment + migrations | 0.5–1 day |
| Total for medium site | 7–10 days |
For complex projects, 3–5 weeks. Get a consultation from our engineer for a project estimate.







