Your Next.js app is already a CMS.
Easy CMS runs inside your Next.js or Nuxt app. Define content in TypeScript, and you get an admin at /admin, a typed API and REST, on one deployment and your own database.
npm create easy-cms@latest

const cms = await getEasyCMS(config)
const { docs } = await cms.find('posts')
// docs: Post[], typed from your config- Next.js 15+
- Nuxt
- PostgreSQL
- SQLite · Turso
- PGlite
- S3 · R2 · MinIO
- Vercel · Netlify
No second server to run, pay for or keep in sync.
A typical headless CMS is another service with its own database and API keys. Easy CMS is an npm package your app mounts, so content ships with the code that renders it.
Two apps, two databases
- Schema lives in a web UI, not in your repo
- Types generated in a separate step
- A network hop on every page render
One app, one database
ecms_ tables- Content model in
easy-cms.config.ts, reviewed in PRs - Types come straight from the config, no build step
- Deploys wherever your app already runs
Three files and your app has a CMS.
Describe collections and fields in TypeScript. The admin forms, validation, access rules and the typed API are built from that one file.
import { defineConfig } from '@easy-cms/core'
import { postgres } from '@easy-cms/db-postgres'
import { seoPlugin } from '@easy-cms/plugin-seo'
export default defineConfig({
secret: process.env.EASY_CMS_SECRET!,
db: postgres({ url: process.env.DATABASE_URL! }),
localization: { locales: ['en', 'th'], defaultLocale: 'en' },
collections: [
{
slug: 'posts',
drafts: true, versions: true, schedule: true,
useAsTitle: 'title',
fields: [
{ name: 'title', type: 'text', required: true, localized: true },
{ name: 'slug', type: 'slug', from: 'title' },
{ name: 'body', type: 'richText', localized: true },
],
},
],
plugins: [seoPlugin({ collections: ['posts'] })],
})// app/admin/[[...path]]/route.ts
import { createAdminRouteHandlers } from '@easy-cms/next'
import config from '@/easy-cms.config'
export const { GET, HEAD } = createAdminRouteHandlers(config)
// app/api/cms/[[...path]]/route.ts
import { createRouteHandlers } from '@easy-cms/next'
import config from '@/easy-cms.config'
export const { GET, HEAD, POST, PATCH, PUT, DELETE, OPTIONS } =
createRouteHandlers(config)
// next.config.ts
import { withEasyCMS } from '@easy-cms/next/config'
export default withEasyCMS({ /* your Next config */ })import { getEasyCMS } from '@easy-cms/next'
import config from '@/easy-cms.config'
export const dynamic = 'force-dynamic'
export default async function Posts() {
const cms = await getEasyCMS(config)
const { docs } = await cms.find('posts', { limit: 10 })
return (
<ul>
{docs.map((post) => (
// post.title: string, typed from your config
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}What editors expect, what developers can review.
Drafts and version history
Edit a published post without touching the live page. Compare versions and restore any of them.
Live preview
Your real page sits next to the form and updates as editors type, before anything is published.
Localized content
One value per language, with translation status in every list. The admin speaks English and Thai.
Roles and single sign-on
Tick what each role may do in the admin, down to fields. Sign in with Google, Microsoft or GitHub.
A media library with folders
Folders, many files at once, private files with signed links, and storage on S3, R2 or Vercel Blob.
Audit log and backups
A signed record of who changed what, and database backups you start or schedule from the admin.
Scheduled publishing
Publish or unpublish at a set time. Due jobs run every minute on the server you already have.
SEO out of the box
Sitemaps, hreflang, JSON-LD and llms.txt from the content you already have.
Typed API, REST and GraphQL
A typed Local API in your server code, REST for any client, and GraphQL and MCP as plugins.
Add a feature with one line in plugins.
Each plugin adds its own collections, admin screens and API routes, and ships its migration. Write your own with the same hooks.
@easy-cms/plugin-seoMeta fields, sitemaps, hreflang and JSON-LD.
seo@easy-cms/plugin-form-builderEditors build forms; submissions are validated, stored and exported as CSV.
forms@easy-cms/plugin-redirects301 to 308 redirects, created automatically when a page moves.
seo@easy-cms/plugin-nested-docsPages inside pages, with paths and breadcrumbs per language.
pages@easy-cms/plugin-multi-tenantSeveral sites or clients in one CMS, each with its own members and content.
0.44@easy-cms/plugin-ecommerceProducts, carts and checkout with Stripe, PromptPay or bank transfer.
0.46@easy-cms/plugin-graphqlA GraphQL API with the same access rules as REST.
0.43@easy-cms/plugin-mcpAn MCP server so AI assistants can work with your content.
aiAn admin your editors will actually like.
Generated from your config, in light and dark, English and Thai. Nothing to design or host.


Latest releases and guides
The new admin
Easy CMS 0.47 rebuilds the admin around a growing site: menu groups, ⌘K, badges, and edit pages with tabs and fields that show when they apply.
Multi-tenant sites and a shop
Two new plugins: run several sites or clients from one CMS, and sell from your site with carts, Stripe, PromptPay or bank transfer.
A media library for real sites
Folders, several files at a time, private folders and files too big for your host's request limit: the media library grew up in 0.38 to 0.41.
Ship content with the code that renders it.
Scaffold a Next.js or Nuxt project with Easy CMS in about a minute. It is MIT licensed and runs on your own infrastructure.