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.







