Payload CMS v3 vs Keystatic vs Outstatic 2026
TL;DR
The Next.js developer CMS space has split into two clear camps: git-based CMS (content stored in your repo as MDX/YAML) and database-backed CMS (content in Postgres/MongoDB with a proper API). Payload CMS v3 is the database-backed power option — fully TypeScript, runs inside your Next.js app as a route handler (no separate server), with a schema-driven admin UI, access control, relationship fields, and REST+GraphQL APIs out of the box. Keystatic is the git-based local-first option from Thinkmill — content stored as YAML/JSON/MDX in your repo, edited through a beautiful local UI at /keystatic, zero backend required, supports GitHub mode for team editing. Outstatic is the simplest git-based option — a GitHub-connected CMS that commits content directly to your repo via GitHub API, dead simple to set up with zero infrastructure. For full-featured content management with auth, workflows, and complex data: Payload v3. For developer blogs and docs sites with content in git: Keystatic. For the simplest possible CMS without leaving GitHub: Outstatic.
Key Takeaways
- Payload v3 runs inside Next.js — no separate API server, routes handled by App Router
- Payload has access control — field-level, collection-level, and draft/publish workflows
- Keystatic is git-native — content is YAML/MDX files in your repo, versioned with your code
- Keystatic has two modes — local (dev machine) and GitHub (team via GitHub API)
- Outstatic uses GitHub API — no local setup, content commits go directly to your repo
- Payload uses a real database — Postgres (Drizzle ORM) or MongoDB in v3
- Keystatic is zero-backend — no database, no auth layer, content in files
Architecture Comparison
Payload CMS v3 Keystatic Outstatic
────────────────── ────────────────── ──────────────────
Database (Postgres) Git repo (files) Git repo (GitHub)
Next.js route handler Next.js route handler Next.js route handler
Admin UI at /admin UI at /keystatic UI at /outstatic
REST + GraphQL API Local file reads GitHub API
TypeScript schemas TypeScript schemas Config object
Auth + access control GitHub auth GitHub OAuth
Draft/publish None (git branch) None (git branch)
Relationships References (no joins) No relationships
Payload CMS v3: Database-Backed CMS in Next.js
Payload v3 rewrote the architecture to embed directly into Next.js App Router — the admin UI and API routes live inside your Next.js app with no separate Payload server.
Installation
npx create-payload-app@latest
# Or add to existing Next.js app:
npm install payload @payloadcms/next @payloadcms/db-postgres @payloadcms/richtext-lexical
Project Structure
my-app/
├── src/
│ ├── app/
│ │ ├── (payload)/
│ │ │ ├── admin/
│ │ │ │ └── [[...segments]]/
│ │ │ │ └── page.tsx # Admin UI
│ │ │ └── api/
│ │ │ └── [...slug]/
│ │ │ └── route.ts # REST + GraphQL API
│ │ └── (site)/
│ │ └── blog/
│ │ └── [slug]/page.tsx # Your frontend
│ ├── collections/
│ │ ├── Posts.ts
│ │ ├── Users.ts
│ │ └── Media.ts
│ └── payload.config.ts
Payload Config
// src/payload.config.ts
import { buildConfig } from "payload";
import { postgresAdapter } from "@payloadcms/db-postgres";
import { lexicalEditor } from "@payloadcms/richtext-lexical";
import { Posts } from "./collections/Posts";
import { Users } from "./collections/Users";
import { Media } from "./collections/Media";
export default buildConfig({
admin: {
user: Users.slug,
importMap: {
baseDir: path.resolve(dirname),
},
},
collections: [Posts, Users, Media],
editor: lexicalEditor({}),
secret: process.env.PAYLOAD_SECRET ?? "",
typescript: {
outputFile: path.resolve(dirname, "payload-types.ts"),
},
db: postgresAdapter({
pool: {
connectionString: process.env.DATABASE_URL,
},
}),
sharp,
});
Collection Definition
// src/collections/Posts.ts
import type { CollectionConfig } from "payload";
export const Posts: CollectionConfig = {
slug: "posts",
admin: {
useAsTitle: "title",
defaultColumns: ["title", "status", "publishedAt", "author"],
},
access: {
// Everyone can read published posts
read: ({ req }) => {
if (req.user) return true; // Admins see all
return { status: { equals: "published" } };
},
create: ({ req }) => Boolean(req.user), // Must be logged in
update: ({ req }) => Boolean(req.user),
delete: ({ req }) => req.user?.role === "admin",
},
fields: [
{
name: "title",
type: "text",
required: true,
},
{
name: "slug",
type: "text",
required: true,
unique: true,
admin: {
position: "sidebar",
},
},
{
name: "status",
type: "select",
options: ["draft", "published", "archived"],
defaultValue: "draft",
admin: {
position: "sidebar",
},
},
{
name: "publishedAt",
type: "date",
admin: {
position: "sidebar",
condition: (data) => data.status === "published",
},
},
{
name: "author",
type: "relationship",
relationTo: "users",
required: true,
},
{
name: "featuredImage",
type: "upload",
relationTo: "media",
},
{
name: "tags",
type: "array",
fields: [
{
name: "tag",
type: "text",
},
],
},
{
name: "content",
type: "richText",
},
{
name: "excerpt",
type: "textarea",
maxLength: 300,
},
],
hooks: {
beforeChange: [
({ data }) => {
if (data.status === "published" && !data.publishedAt) {
data.publishedAt = new Date().toISOString();
}
return data;
},
],
},
versions: {
drafts: true,
maxPerDoc: 10,
},
};
Querying Content in Next.js
// app/(site)/blog/page.tsx — Server Component
import { getPayload } from "payload";
import configPromise from "@payload-config";
export default async function BlogPage() {
const payload = await getPayload({ config: configPromise });
// Type-safe query — generated types from payload-types.ts
const posts = await payload.find({
collection: "posts",
where: {
status: { equals: "published" },
},
sort: "-publishedAt",
limit: 20,
depth: 1, // Resolve author relationship
});
return (
<div>
{posts.docs.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
<span>By {typeof post.author === "object" ? post.author.name : "Unknown"}</span>
</article>
))}
</div>
);
}
REST API
// Fetch from external services — REST API at /api/posts
const response = await fetch("https://mysite.com/api/posts?where[status][equals]=published&limit=10", {
headers: {
Authorization: `Bearer ${process.env.PAYLOAD_API_KEY}`,
},
});
const { docs } = await response.json();
Keystatic: Git-Based Local-First CMS
Keystatic stores all content as YAML/JSON/MDX files in your repository — no database, no separate service. Content is versioned alongside your code.
Installation
npm install @keystatic/core @keystatic/next
Keystatic Config
// keystatic.config.ts
import { config, collection, fields, singleton } from "@keystatic/core";
export default config({
storage: {
kind: "local", // or "github" for team editing
},
// Optional: GitHub mode for team collaboration
// storage: {
// kind: "github",
// repo: { owner: "your-org", name: "your-repo" },
// },
collections: {
posts: collection({
label: "Blog Posts",
slugField: "title",
path: "content/posts/*",
format: { contentField: "content" },
schema: {
title: fields.slug({ name: { label: "Title" } }),
publishedAt: fields.date({
label: "Published Date",
validation: { isRequired: true },
}),
status: fields.select({
label: "Status",
options: [
{ label: "Draft", value: "draft" },
{ label: "Published", value: "published" },
],
defaultValue: "draft",
}),
tags: fields.array(fields.text({ label: "Tag" }), {
label: "Tags",
itemLabel: (props) => props.fields.value.value,
}),
featuredImage: fields.image({
label: "Featured Image",
directory: "public/images/posts",
publicPath: "/images/posts",
}),
content: fields.markdoc({
label: "Content",
extension: "md",
}),
excerpt: fields.text({
label: "Excerpt",
multiline: true,
validation: { length: { max: 300 } },
}),
},
}),
authors: collection({
label: "Authors",
slugField: "name",
path: "content/authors/*",
schema: {
name: fields.slug({ name: { label: "Name" } }),
bio: fields.text({ label: "Bio", multiline: true }),
avatar: fields.image({
label: "Avatar",
directory: "public/images/authors",
publicPath: "/images/authors",
}),
twitter: fields.text({ label: "Twitter Handle" }),
},
}),
},
singletons: {
siteConfig: singleton({
label: "Site Config",
path: "content/site-config",
schema: {
siteName: fields.text({ label: "Site Name" }),
description: fields.text({ label: "Description", multiline: true }),
featuredPosts: fields.array(
fields.relationship({
label: "Post",
collection: "posts",
}),
{ label: "Featured Posts" }
),
},
}),
},
});
Next.js Route Handlers
// app/keystatic/layout.tsx
import KeystaticApp from "./keystatic";
export default function Layout() {
return <KeystaticApp />;
}
// app/keystatic/keystatic.tsx
"use client";
import { makePage } from "@keystatic/next/ui/app";
import config from "../../../keystatic.config";
export default makePage(config);
// app/api/keystatic/[...params]/route.ts
import { makeRouteHandler } from "@keystatic/next/route-handler";
import config from "../../../../keystatic.config";
export const { POST, GET } = makeRouteHandler({ config });
Reading Content in Next.js
// app/blog/page.tsx — Server Component
import { createReader } from "@keystatic/core/reader";
import keystaticConfig from "../../../keystatic.config";
const reader = createReader(process.cwd(), keystaticConfig);
export default async function BlogPage() {
const posts = await reader.collections.posts.all();
// Filter published posts
const published = posts.filter(
(post) => post.entry.status === "published"
);
return (
<div>
{published.map((post) => (
<article key={post.slug}>
<h2>{post.entry.title}</h2>
<p>{post.entry.excerpt}</p>
</article>
))}
</div>
);
}
// app/blog/[slug]/page.tsx
export default async function PostPage({ params }: { params: { slug: string } }) {
const post = await reader.collections.posts.read(params.slug);
if (!post || post.status !== "published") {
notFound();
}
// Render MDX content
const { content } = await post.content();
return (
<article>
<h1>{post.title}</h1>
<DocumentRenderer document={content} />
</article>
);
}
// Generate static params for SSG
export async function generateStaticParams() {
const posts = await reader.collections.posts.all();
return posts
.filter((p) => p.entry.status === "published")
.map((p) => ({ slug: p.slug }));
}
GitHub Mode (Team Editing)
// keystatic.config.ts — GitHub mode
export default config({
storage: {
kind: "github",
repo: { owner: "your-org", name: "your-repo" },
branchPrefix: "keystatic/",
},
// Team members authenticate via GitHub OAuth
// Content changes create pull requests
// ...rest of config
});
Outstatic: GitHub-Native Simplicity
Outstatic is the simplest path to a CMS — authenticate with GitHub, create collections, and content commits go directly to your repository via GitHub API.
Installation
npm install outstatic
Next.js Integration
// app/api/outstatic/[...ost]/route.ts
import { Outstatic } from "outstatic";
const handler = Outstatic();
export { handler as GET, handler as POST };
// app/outstatic/[[...ost]]/page.tsx
import { OstClient } from "outstatic/client";
export default function Page(props: { params: { ost: string[] } }) {
return <OstClient ostPath={props.params.ost} />;
}
Environment Variables
# .env.local
GITHUB_ID=your_github_app_id
GITHUB_SECRET=your_github_app_secret
OST_TOKEN_SECRET=random_secret_for_jwt
Reading Content
// app/blog/page.tsx
import { getCollections, getDocuments } from "outstatic/server";
export default async function BlogPage() {
// Documents stored as MDX files in outstatic/content/posts/*.md
const posts = await getDocuments("posts", [
"title",
"slug",
"description",
"coverImage",
"publishedAt",
"author",
"status",
]);
const published = posts.filter((post) => post.status === "published");
return (
<div>
{published.map((post) => (
<article key={post.slug}>
<h2>{post.title}</h2>
<p>{post.description}</p>
</article>
))}
</div>
);
}
// app/blog/[slug]/page.tsx
import { getDocumentSlugs, load } from "outstatic/server";
import { MDXRemote } from "next-mdx-remote/rsc";
export default async function PostPage({ params }: { params: { slug: string } }) {
const db = await load();
const post = await db
.find({ collection: "posts", slug: params.slug }, ["title", "content", "publishedAt"])
.first();
if (!post) notFound();
return (
<article>
<h1>{post.title}</h1>
<MDXRemote source={post.content} />
</article>
);
}
export async function generateStaticParams() {
const slugs = await getDocumentSlugs("posts");
return slugs.map((slug) => ({ slug }));
}
Outstatic Admin UI
Outstatic's admin UI at /outstatic provides:
- Field types: text, rich text, tags, date, media
- GitHub-backed authentication (no separate user system)
- Automatic slugs from titles
- Media uploaded to your GitHub repo or external provider
- Draft/published status
- No database, no infrastructure
Feature Comparison
| Feature | Payload CMS v3 | Keystatic | Outstatic |
|---|---|---|---|
| Storage | Postgres / MongoDB | Git files | Git (GitHub API) |
| Admin UI | /admin | /keystatic | /outstatic |
| Auth | Own user system | GitHub OAuth | GitHub OAuth |
| Access control | Field-level | Role-level (GitHub) | GitHub permissions |
| Drafts | ✅ Native | Git branches | Status field |
| Relationships | ✅ Database joins | References | No |
| Rich text | Lexical editor | Markdoc / prose | Tiptap |
| Media management | ✅ Built-in | Local files | GitHub / S3 |
| REST API | ✅ Auto-generated | No API | No API |
| GraphQL | ✅ Auto-generated | No | No |
| Real-time collab | No | No | No |
| Infrastructure | DB required | None | None |
| Offline editing | DB required | ✅ Local mode | No |
| GitHub stars | 22k | 2.5k | 4k |
| npm weekly | 40k | 15k | 10k |
| License | MIT | MIT | MIT |
| Hosting | Anywhere with DB | Vercel/edge | Vercel/edge |
When to Use Each
Choose Payload CMS v3 if:
- Need a full content platform — auth, access control, relationships, workflows
- Building a multi-user CMS where editors log in with email/password
- Content model has complex relationships (posts → categories → authors → media)
- Need a REST or GraphQL API for a mobile app or external consumers
- E-commerce, membership sites, or multi-tenant applications
- Draft/publish workflows with scheduled publishing
Choose Keystatic if:
- Developer blog, documentation site, or content that's code-adjacent
- Prefer content versioned in git alongside your code
- Want an excellent local editing experience in development
- Small team who can work through GitHub branches for review
- MDX content with custom components
- Zero infrastructure overhead is a priority
Choose Outstatic if:
- Simplest possible setup — minutes to working CMS
- Non-technical editors who are comfortable with GitHub
- Content is simple (title, rich text, date, image, tags)
- Want to avoid all database and auth infrastructure
- Personal blog or small marketing site where GitHub auth is fine
- Proof of concept or prototype before committing to a full CMS
Team Size and CMS Maturity
CMS selection often comes down to who is editing content and what their technical comfort level is. Payload CMS v3's admin panel is powerful but unfamiliar to non-developers — the field interface, relationship selects, and collection navigation require some onboarding. For editorial teams accustomed to WordPress or Contentful, the transition to Payload is manageable with a brief walkthrough, but it's not a zero-training situation.
Keystatic's GitHub-backed editing is both a strength and a constraint. Technical writers, developers, and technically literate content editors navigate it naturally. Traditional marketing or editorial staff who have never used GitHub may find the branch-and-PR workflow confusing, even in Keystatic's cloud mode which abstracts git commands. For teams with mixed technical backgrounds, a brief training session and clear documentation about the editing process is essential.
Outstatic's editing experience is the most accessible. The admin panel is clean, the GitHub auth flow is familiar, and the content editor is straightforward rich text. Non-technical clients who have a GitHub account — increasingly common since GitHub is used for all kinds of collaboration, not just code — can typically start editing content with minimal guidance. This accessibility is a genuine competitive advantage for agencies that deliver sites to non-technical clients.
Scaling content teams also favors Payload CMS for larger organizations. When you need audit logs of who changed what and when, role-based access that limits which collections different team members can edit, and approval workflows where content must be reviewed before publishing, Payload's user management and access control system handles these requirements natively. Keystatic and Outstatic rely on GitHub's access controls (who has write access to the repository) for permissions, which is coarser-grained than most enterprise content workflows require.
The total cost of ownership for each CMS also varies significantly. Payload CMS requires a database (MongoDB Atlas free tier or a managed PostgreSQL instance starting around $5-25/month), while Keystatic and Outstatic have no database cost — they store content in your GitHub repository. For small projects and startups, the Keystatic/Outstatic zero-infrastructure cost is a real advantage. For teams that already have a PostgreSQL database (common for Next.js SaaS applications), adding Payload to an existing database has near-zero incremental cost.
Ecosystem & Community
Payload CMS has grown dramatically since its v3 rewrite in late 2023. The shift to Next.js App Router integration opened the door to a much larger developer audience — any team already running Next.js could add Payload without standing up a separate service. The Payload Discord server has become one of the most active CMS communities in the JavaScript ecosystem, with consistent engagement from the core team. The GitHub repository crosses 22K stars and receives regular contributor pull requests beyond the core team.
Keystatic is maintained by Thinkmill, the Australian software consultancy that also maintains KeystoneJS and contributed significantly to the Markdoc specification. Thinkmill uses Keystatic on its own client projects, which means the library is production-tested on real sites. The GitHub issues tracker is responsive, and the documentation quality is high — Thinkmill has a track record of shipping developer-focused tools with excellent documentation.
Outstatic is smaller but targeted. The 4K stars and 10K weekly downloads represent a dedicated user base of developers who want the simplest possible CMS setup. The creator actively maintains it and the GitHub issues are addressed promptly. For its specific use case — the absolute minimum viable CMS for a Next.js project — it delivers reliably.
Real-World Adoption
Payload CMS v3 has been adopted by several high-profile companies for their own websites, including design agencies, product companies, and media organizations. The Payload team itself uses it to power their documentation and marketing site. More importantly, the framework's architecture has attracted enterprise teams who previously evaluated Contentful or Strapi but wanted a TypeScript-native, self-hosted option without a separate API server process.
Keystatic is used by Thinkmill's client roster, which includes Australian enterprises and government organizations. Outside of Thinkmill's ecosystem, it has attracted adoption among developer tools companies that want their documentation site's content in version control alongside their code — a natural fit for documentation systems where content and code versions need to stay in sync.
Outstatic's sweet spot is individual developers and small teams who need a quick CMS without the overhead of a full platform. Developers who have used it consistently praise how fast it is to go from "I need a CMS" to "the CMS is live and my client can edit content." That time-to-value advantage is Outstatic's core competitive differentiation.
The pattern that has emerged across successful Payload CMS v3 deployments is the "internal Next.js monolith" architecture: the Payload admin panel and the public-facing Next.js site are the same Next.js application, deployed together. This colocation means zero API latency for server-side data fetching (using Payload's Local API, which is a direct function call rather than an HTTP request), simplified deployment, and a single codebase for the entire content platform. Teams report that this architecture is significantly easier to maintain than the separate-services approach used by Strapi or other headless CMS platforms.
Keystatic has found an organic fit in the open-source documentation site ecosystem. Projects that maintain extensive technical documentation alongside their code — CLI tools, development frameworks, npm packages — use Keystatic because documentation and code naturally version together. When a new feature ships in v2.0 of your library, the documentation for that feature should be in the same commit or PR as the code. Keystatic's git-first approach makes this workflow natural rather than requiring documentation to be maintained in a separate system. For those considering broader headless CMS options alongside their build tooling decisions, see best TypeScript build tools 2026 for how Payload's TypeScript generation integrates with modern build pipelines.
Developer Experience Deep Dive
Payload v3's developer experience is excellent for TypeScript developers. The automatic generation of TypeScript types from your collection definitions means your code is always in sync with your schema — if you rename a field in your Payload config, TypeScript errors immediately surface everywhere that field is referenced. The Lexical rich text editor is genuinely good — it's the same editor used in Facebook's internal tools, and the block system lets you embed custom React components inside content.
Keystatic's local editing workflow is a standout experience. During development, the /keystatic UI reflects your content changes in real time, and saving content writes YAML/MDX files to your project. You can see your content changes in git diff and commit them alongside code changes. This workflow creates excellent coherence between code and content — useful for documentation sites where code examples and their surrounding explanation should change together.
Outstatic's developer experience is deliberately minimal. Setup involves three environment variables and two route handlers. There's nothing to configure beyond defining your collections. The trade-off is that you can't customize the editing experience beyond what Outstatic's admin UI provides.
Migration Guide
From Contentful or Sanity to Payload v3: Export your content via the source CMS's API, transform it to match Payload's collection schema, and import via Payload's local API. Payload's beforeChange hooks can normalize content during import. The migration effort scales with content model complexity — simple blog content migrates in a day; complex multi-collection models with relationships take more work.
From MDX files to Keystatic: If you're using Velite, Contentlayer, or raw MDX imports, Keystatic's createReader API can read the same files if you reorganize them to match Keystatic's path conventions. The key change is adding Keystatic's schema definitions on top of existing content structure.
From Keystatic to Payload: This migration usually happens when a site grows to require proper access control, multiple editors with different permissions, or API access from a mobile app. Export content from git as YAML/MDX files, transform to JSON, and import via Payload's REST API or local API.
Content architecture considerations during migration: The data model you choose — whether relational (Payload's MongoDB or PostgreSQL), document-based (Keystatic's YAML/JSON files), or simplified (Outstatic's GitHub Issues-backed approach) — shapes how you query content, how you migrate content as requirements change, and how you handle content relationships. Content architecture decisions made at the beginning of a project are difficult to reverse. When evaluating the migration cost, factor in not just the data transformation work but also the editor retraining time and any integrations that consume your CMS API. Payload CMS v3's relational database backing is its biggest architectural advantage for complex content models — articles that belong to authors, tagged with categories, and referenced by multiple pages are expressed naturally with foreign keys, not duplicated in every document. For teams evaluating headless CMS options beyond these three, the best CMS for Next.js 2026 comparison covers Sanity, Contentful, and other managed platforms with detailed migration path documentation.
Preserving SEO during CMS migration: URL structure, metadata, and canonical URLs are often disrupted during CMS migrations if not planned carefully. Payload CMS's slug field and custom endpoint support make it straightforward to match existing URL patterns. Keystatic's file-based routing naturally maps to URL paths when combined with Next.js App Router's dynamic segments. The key risk is generated metadata — ensure your generateMetadata function works correctly with the new CMS data format before switching production traffic. For authentication during the migration transition period — particularly when running both old and new CMS in parallel — see best Next.js auth solutions 2026 for how auth providers integrate with the Payload admin panel versus static CMS setups.
Final Verdict 2026
The right choice depends entirely on your infrastructure philosophy. Payload CMS v3 is the choice for teams building real products — multi-user content management, complex data models, external API consumers, and long-term scalability. Its integration with Next.js App Router makes it the most capable CMS for the most common JavaScript stack in 2026.
Keystatic is the right choice for developer blogs, documentation sites, and any context where content and code should evolve together in version control. Its local-first development experience and GitHub-based team editing model match how developers already work. If you're building a site where you're both the developer and the content author — or working with a small technical team — Keystatic's zero-infrastructure approach eliminates an entire category of operational complexity.
Outstatic earns its place for the fastest possible path to a working CMS. If the goal is to have non-technical clients editing content in the next hour, Outstatic achieves that with minimal setup.
CMS Selection for Content-Heavy Projects
Content architecture decisions made at the beginning of a project are difficult to reverse. The data model you choose — whether relational (Payload's MongoDB or PostgreSQL), document-based (Keystatic's YAML/JSON files), or simplified (Outstatic's GitHub Issues-backed approach) — shapes how you query content, how you migrate content as requirements change, and how you handle content relationships.
Payload CMS v3's relational database backing (supporting both MongoDB and PostgreSQL) is its biggest architectural advantage for complex content models. When content types have relationships — articles belong to authors, articles are tagged with categories, authors have profiles — a relational database expresses these relationships naturally with foreign keys and JOIN queries. The Payload Local API lets you query these relationships directly in server components with type safety: payload.find({ collection: 'articles', where: { author: { equals: authorId } }, depth: 2 }) fetches articles with populated author and category data in a single query. This relational richness is difficult to replicate in file-based CMS systems.
Keystatic's YAML/JSON content files are optimally suited for structured documents with bounded complexity. For a documentation site with pages that have frontmatter (title, description, order) and MDX body content, Keystatic's schema precisely captures the content structure without over-engineering. The editor experience is genuinely excellent — Keystatic's cloud mode gives non-technical editors a GitHub-backed editing UI without understanding git at all. They see a clean content editor, click save, and Keystatic handles the commit to a branch and the PR creation. The editorial workflow that results from this is production-ready for most content team sizes. For teams evaluating headless CMS options more broadly, the best CMS for Next.js 2026 comparison covers the full landscape including Sanity, Contentful, and other managed platforms.
Content Preview and Draft Workflow
Content preview — seeing how published content looks before it goes live — is a key editorial requirement that the three CMSes handle differently. Payload CMS v3 supports draft/published state at the document level and integrates with Next.js's draft mode API. The Payload admin panel has a preview button that opens the live Next.js site in draft mode, showing the unpublished content exactly as it will appear. This requires some Next.js configuration but the result is a seamless preview workflow for editors.
Keystatic's preview workflow is tied to its GitHub branch model. Content in a branch is effectively in draft — it won't be published until the branch is merged. Keystatic's Markdoc and MDX editors show a live rendering of the content as you type, which serves as immediate preview. For full-page preview in the context of your Next.js layout, you need to implement Next.js's draft mode with Keystatic's content reading APIs. This is documented but requires more setup than Payload's built-in preview integration.
Outstatic handles drafts through GitHub Issues status — content moves from draft issue to published status when you're ready. The preview workflow depends on your Next.js implementation. For teams that only need simple publish/unpublish without version history or complex draft workflows, this simplified approach is adequate. For editorial teams that need to preview content in staging, review scheduled publications, and manage multi-step approval workflows, Payload CMS's built-in draft workflow is the right tool. For content-heavy Next.js applications, the choice of authentication provider also matters — see best Next.js auth solutions 2026 for how Clerk, Auth.js, and other providers integrate with Payload's user management system.
Ecosystem & Community
Payload CMS has grown dramatically since its v3 rewrite in late 2023. The shift to Next.js App Router integration opened the door to a much larger developer audience — any team already running Next.js could add Payload without standing up a separate service. The Payload Discord server has become one of the most active CMS communities in the JavaScript ecosystem, with consistent engagement from the core team. The GitHub repository crosses 22K stars and receives regular contributor pull requests beyond the core team.
Keystatic is maintained by Thinkmill, the Australian software consultancy that also maintains KeystoneJS and contributed significantly to the Markdoc specification. Thinkmill uses Keystatic on its own client projects, which means the library is production-tested on real sites. The GitHub issues tracker is responsive, and the documentation quality is high — Thinkmill has a track record of shipping developer-focused tools with excellent documentation.
Outstatic is smaller but targeted. The 4K stars and 10K weekly downloads represent a dedicated user base of developers who want the simplest possible CMS setup. The creator actively maintains it and the GitHub issues are addressed promptly. For its specific use case — the absolute minimum viable CMS for a Next.js project — it delivers reliably.
Real-World Adoption
Payload CMS v3 has been adopted by several high-profile companies for their own websites, including design agencies, product companies, and media organizations. The Payload team itself uses it to power their documentation and marketing site. More importantly, the framework's architecture has attracted enterprise teams who previously evaluated Contentful or Strapi but wanted a TypeScript-native, self-hosted option without a separate API server process.
Keystatic is used by Thinkmill's client roster, which includes Australian enterprises and government organizations. Outside of Thinkmill's ecosystem, it has attracted adoption among developer tools companies that want their documentation site's content in version control alongside their code — a natural fit for documentation systems where content and code versions need to stay in sync.
Outstatic's sweet spot is individual developers and small teams who need a quick CMS without the overhead of a full platform. Developers who have used it consistently praise how fast it is to go from "I need a CMS" to "the CMS is live and my client can edit content." That time-to-value advantage is Outstatic's core competitive differentiation.
Developer Experience Deep Dive
Payload v3's developer experience is excellent for TypeScript developers. The automatic generation of TypeScript types from your collection definitions means your code is always in sync with your schema — if you rename a field in your Payload config, TypeScript errors immediately surface everywhere that field is referenced. The Lexical rich text editor is genuinely good — it's the same editor used in Facebook's internal tools, and the block system lets you embed custom React components inside content.
Keystatic's local editing workflow is a standout experience. During development, the /keystatic UI reflects your content changes in real time, and saving content writes YAML/MDX files to your project. You can see your content changes in git diff and commit them alongside code changes. This workflow creates excellent coherence between code and content — useful for documentation sites where code examples and their surrounding explanation should change together.
Outstatic's developer experience is deliberately minimal. Setup involves three environment variables and two route handlers. There's nothing to configure beyond defining your collections. The trade-off is that you can't customize the editing experience beyond what Outstatic's admin UI provides.
Migration Guide
From Contentful or Sanity to Payload v3: Export your content via the source CMS's API, transform it to match Payload's collection schema, and import via Payload's local API. Payload's beforeChange hooks can normalize content during import. The migration effort scales with content model complexity — simple blog content migrates in a day; complex multi-collection models with relationships take more work.
From MDX files to Keystatic: If you're using Velite, Contentlayer, or raw MDX imports, Keystatic's createReader API can read the same files if you reorganize them to match Keystatic's path conventions. The key change is adding Keystatic's schema definitions on top of existing content structure.
From Keystatic to Payload: This migration usually happens when a site grows to require proper access control, multiple editors with different permissions, or API access from a mobile app. Export content from git as YAML/MDX files, transform to JSON, and import via Payload's REST API or local API.
Final Verdict 2026
The right choice depends entirely on your infrastructure philosophy. Payload CMS v3 is the choice for teams building real products — multi-user content management, complex data models, external API consumers, and long-term scalability. Its integration with Next.js App Router makes it the most capable CMS for the most common JavaScript stack in 2026.
Keystatic is the right choice for developer blogs, documentation sites, and any context where content and code should evolve together in version control. Its local-first development experience and GitHub-based team editing model match how developers already work. If you're building a site where you're both the developer and the content author — or working with a small technical team — Keystatic's zero-infrastructure approach eliminates an entire category of operational complexity.
Outstatic earns its place for the fastest possible path to a working CMS. If the goal is to have non-technical clients editing content in the next hour, Outstatic achieves that with minimal setup.
Methodology
Data sourced from Payload CMS v3 documentation (payloadcms.com/docs), Keystatic documentation (keystatic.com/docs), Outstatic documentation (outstatic.com/docs), GitHub star counts as of February 2026, npm weekly download statistics as of February 2026, and community discussions from the Next.js Discord, the Payload CMS Discord, and developer blog comparisons.
Related: Strapi vs Directus vs Sanity for broader headless CMS options, or MDX vs Contentlayer vs Velite for git-based content processing pipelines.
Also: Best Next.js auth solutions in 2026 · Best React component libraries in 2026