Alinea: a Git-based headless CMS where content entries are JSON files in your repo
Content management, streamlined
At a glance
- What is it?
- Alinea is a headless CMS for Next.js where the content model is plain TypeScript, every entry is a JSON file committed to your repository, and publishing is a git commit. There is no separate content database to operate, query types come from your schema without a code generation step, and the editing dashboard ships inside your own app.
- Who is it for?
- Adopt Alinea when your content editors are comfortable with a git workflow, when review and rollback of content changes matter more than a polished SaaS dashboard, and when you want to avoid running a separate content database. Do not adopt it if content must be edited by people who will never touch version control, or if you need content editing to work without a code deployment pipeline nearby.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the project is and where the content lives
Alinea is described as an open source, Git-based headless CMS for Next.js. The summary of the model is three sentences: you define your content model in TypeScript, editors work in a dashboard that ships with your app, and every entry is stored as a JSON file in your repository.
That last sentence is the design decision everything else follows from. A CMS that stores content in git gives you, for free, the things a content team expects from a versioned system: review, branching and rollback of content changes like code. The README lists it as content in git, where every entry is a JSON file in your repository, and you can review, branch and roll back content like code.
Two consequences follow from the same decision. The first is that there is no database server for content: content is indexed in SQLite and bundled with your site, with full text search in the dashboard. The SQLite index is a derived cache for the dashboard's queries, not the source of truth, and the source of truth is the files in the repository.
The second is how publishing works. Every publish is a git commit, and deployed sites pick up new content without waiting for a rebuild. That last clause is the significant one: the content change is a commit, and the deployed site reads the content at request time rather than having it baked into a build artifact, so an editor publishing a correction does not wait for a deploy pipeline.
The licence is MIT and the project site is alineacms.com, with documentation, a demo and a hosted Alinea Cloud option.
The schema in TypeScript, and why there is no code generation step
Types and fields are plain TypeScript in a file called cms.ts, and the README is explicit that query types come straight from your schema with no codegen.
That is the claim worth interrogating, because most headless CMSes in this space make you write a schema and then run a generator to produce TypeScript types. Alinea inverts that, and the mechanism is visible in the package layout. The schema file is ordinary TypeScript that imports Config and Field from the package:
// cms.ts
import {Config, Field} from 'alinea'
import {createCMS} from 'alinea/next'
export const BlogPost = Config.document('Blog post', {
fields: {
publishDate: Field.date('Publish date'),
cover: Field.image('Cover image'),
body: Field.richText('Body')
}
})Because the schema is a module that other modules import, the types flow through the ordinary TypeScript type system. A document type is a value produced by Config.document, and when you pass it to a query the result type is derived from it. There is no intermediate representation and no generated file to drift out of date.
Every document gets a title and a path, and the rest is fields. The field types shown are date, image and richText, and the document structure nests with contains, so a Blog contains BlogPost entries.
The configuration object ties it together with a schema map, workspaces, baseUrl for development and production, a handlerUrl pointing at the API route the dashboard talks to, and an adminPath. Workspaces hold roots, and a root declares which document types live under it, along with a media root for uploaded files.
The practical upshot is that a content editor's changes are visible to developers in a type system they already use, and there is no build step between changing the schema and the types updating.
Querying content and what select actually does
Content is queried in server components, and the results are typed from the schema. The example is a blog index page:
// app/blog/page.tsx
import {Blog, BlogPost, cms} from '@/cms'
import {Query} from 'alinea'
export default async function BlogPage() {
const blog = await cms.get({
type: Blog,
select: {
title: Query.title,
posts: Query.children({
type: BlogPost,
select: {title: Query.title, url: Query.url, date: BlogPost.publishDate},
orderBy: {desc: BlogPost.publishDate}
})
}
})The select argument returns exactly the shape you ask for, including related entries. That is the part that matters for a headless CMS: a query does not return a whole document and leave you to pick fields out of it, it returns the shape you specified. In the example, the blog returns its title and a posts array, and each post returns its title, url and the publish date pulled from the BlogPost.publishDate field.
Query.children is how relationships are expressed, and it nests a query on a related type with its own select and its own orderBy. The orderBy here sorts descending by the publish date field, which is a field reference on the document type rather than a string.
Because select is what determines the shape, the type of blog.posts is known at compile time from the schema, so a typo in a field name is a type error rather than an undefined at runtime. That is the practical benefit of typed queries without codegen, and it is what the second bullet in the feature list is claiming.
The result is then rendered as ordinary React, with no special components and no data-fetching wrapper beyond the await on cms.get.
Installation, the init command and the Next.js wrapper
The requirements are stated plainly: Alinea requires Node.js 24 or higher, React 19 and the Next.js App Router. The Node floor is enforced in the package metadata as engines node >=24.0.0.
Installation in a Next.js project is two commands:
npm install alinea@preview
npx alinea@preview initNote the channel. Both commands use the preview tag rather than a version, so what you install is the preview build rather than the last release, which is v1.7.0 from 2026-06-26.
The init command creates four things: cms.ts with your schema and settings, the API route the dashboard talks to, which lives at app/(alinea)/api/cms/route.ts, a first entry in content/pages, and it rewrites your dev and build scripts to run through Alinea.
Then the Next.js config is wrapped:
// next.config.ts
import {withAlinea} from 'alinea/next'
export default withAlinea({
// Your Next.js options
})After that you start the dev server with npm run dev and open the dashboard at http://localhost:3000/admin. The adminPath in your configuration is what determines that URL, so it is configurable rather than fixed.
The repository layout shows a workspace monorepo with apps/dev and apps/web, a src/ directory, a patches/ directory, a build.ts and a dev.js, and the toolchain choices are visible in the configuration files: bun.lock and bunfig.toml alongside a .yarnrc.yml and a yarn.mjs, oxlint and oxfmt configuration files, a .knip.json for unused dependency detection, a .madgerc for circular dependency analysis, and a .ladle/ directory for component development. The Playwright directory points at end-to-end testing.
The dashboard, live previews and the coding agent integration
Two of the features are worth describing in detail because they are where the design decisions are most visible.
Live previews work by adding one component to your layout, and pages preview drafts as editors type, rendered by your server components. Since the dashboard and the page share the same Next.js app, the preview is the real rendering path rather than a screenshot or an approximation. The dashboard screenshot in the README shows it directly, an editing form on the left and a live preview of the page on the right.
The dashboard itself is described as accessible, built on React Aria Components, with dark mode, entry history, roles and permissions, and per-field translations. Accessibility there means the keyboard and screen reader behaviour of the components, which matters because a CMS is where non-engineers spend their day.
The coding agent integration is the more unusual piece. The npm package includes the documentation of its version as Markdown in node_modules/alinea/docs/, starting at index.md, and the website lists every page in an /llms.txt file. The init command adds a marked section to your AGENTS.md pointing agents at both, and registers an MCP server in .mcp.json:
{
"mcpServers": {
"alinea": {"command": "npx", "args": ["alinea", "mcp"]}
}
}While alinea dev runs, agents use that server to read your schema and to create, edit and publish entries through the same save path as the dashboard. The last part is the detail that matters: the agent is not writing files behind the CMS's back, it is going through the same save path, so validation, history and permissions apply to an agent-authored change exactly as they do to a human one.
Self-hosting, the cloud option, and the upgrade path from 1.x
The hosting choice is a single switch in the same way the database is. You can self-host or use Cloud: run the backend on your own database, or let Alinea Cloud handle sign-in and publishing.
The distinction is worth being precise about, because it is not all-or-nothing. The content model, the JSON files, the git repository and the dashboard code are all in your project either way. What the hosted option removes is the operational part of running the backend and dealing with authentication, and what it costs is that you are depending on somebody else's service for the write path.
That is a smaller decision than it sounds for a git-based CMS, because the failure mode is recoverable. If the hosted publishing service is unavailable, the content is still in the repository and the self-hosted path still works. The reverse is not true: if you are self-hosting and your own setup breaks, nothing writes commits until you fix it.
The README points at an upgrade guide for coming from 1.x, along with a changelog and a separate changelog.v0 file in the repository. The current release line is v1.7.0, and the presence of an upgrade guide for the 1.x transition tells you the major version has moved recently enough that the migration is worth documenting separately.
One thing the README does not discuss is what happens to content when the schema changes. Since entries are JSON files with fields described by your TypeScript schema, a field rename or a type change is a migration over files in your repository. That is tractable, and it is the sort of work that fits a pull request, but it is work, and it is the main operational consequence of choosing a schema-first, files-on-disk model.
Limits: the preview channel, the Next.js coupling and the editing model
The limits are visible in what the README requires and in what it assumes.
The first is the coupling to a specific stack. Node.js 24 or higher, React 19 and the Next.js App Router are hard requirements, and the query API is a React server component call. A project on the Pages Router, on React 18, or not on Next.js at all is outside what this supports. The Next.js integration is not a plugin you can remove later without rewriting the content layer.
The second is that you install from the preview channel. Both the install and the init command use alinea@preview, while the last tagged release is v1.7.0 from June 2026 and the last push was on 2026-10-15. Whatever you adopt, you should expect the version you pin to differ from the version the documentation describes, since the docs ship inside the package at the version you installed.
The third is the editing model. This is a dashboard for people who work alongside a git workflow, with roles and permissions and entry history. It is not a consumer-facing WYSIWYG page builder, and the per-field translations are a translation of content fields rather than a localisation system for a whole site.
The fourth is that content publication depends on the git write path. Every publish is a commit, which means the hosting model for your repository is part of your CMS deployment: branch protection, commit hooks, and who may push are all live concerns. A team that wants a separate editorial branch is a natural fit. A team that wants editors to publish without any possibility of touching the code branch is not.
The fifth is that the dashboard ships with your app, so it inherits your app's deployment, its runtime limits and its attack surface. That is a good default for a git-based CMS because there is no separate admin host to secure, and it is a cost for an organisation that wants the editing surface isolated from the public site.
Alinea against a hosted headless CMS and against a plain git content directory
Two comparisons make the choice clearer.
The first is against a hosted headless CMS, the kind where content lives in someone else's database and you call their API. The difference in approach is where the truth lives. In a hosted CMS your content is behind an API, which means the editor experience, the API contract, the permissions model and the export path all belong to the vendor. In Alinea the content is a set of JSON files in your repository, which means the same content can be read by a script, migrated by a pull request, served from your own build, and read by an AI agent without an API key.
What you give up is the polish and the ecosystem. A hosted CMS has a mature media library, a scheduling system, localisation workflows, and a support team, and its dashboard has had years of user testing behind it. Alinea's dashboard is newer and narrower, built on React Aria Components with dark mode, entry history, roles and per-field translations, which is a solid feature list for something young but not the surface area of a decade-old product.
The second comparison is against just putting Markdown files in the repository and building a small editor. That is genuinely close to what Alinea does, and for a small site with one or two editors it may be enough. The difference is the layer above: a schema in TypeScript that gives typed queries, a dashboard with roles and history, live preview wired into your own server components, and a media library. You can write those yourself, and for some sites that is a day of work rather than a dependency.
So the honest framing is that Alinea is the middle path. It is more than a file format and less than a vendor platform, and its appeal is strongest for teams that already live in a git and Next.js workflow and want the editing surface to feel like part of the same system rather than a third-party website.
Editorial conclusion
Adopt Alinea when your content editors are comfortable with a git workflow, when review and rollback of content changes matter more than a polished SaaS dashboard, and when you want to avoid running a separate content database. Do not adopt it if content must be edited by people who will never touch version control, or if you need content editing to work without a code deployment pipeline nearby. Verify first that your Node version is 24 or newer and that you are on the Next.js App Router, that the preview channel is acceptable for your review process, and that the per-field translation and role model cover what your editorial team needs.
Frequently asked questions
How does Alinea store content?
Every entry is a JSON file in your repository, so content is reviewed, branched and rolled back like code. There is no database server for content: it is indexed in SQLite and bundled with your site, with full text search in the dashboard. Every publish is a git commit, and deployed sites pick up new content without waiting for a rebuild.
How do I set up Alinea in a Next.js project?
It requires Node.js 24 or higher, React 19 and the Next.js App Router. Run npm install alinea@preview and npx alinea@preview init, which creates cms.ts with your schema, the API route at app/(alinea)/api/cms/route.ts, a first entry in content/pages, and rewrites your dev and build scripts. Then wrap your Next.js config with withAlinea from alinea/next, start npm run dev and open the dashboard at /admin.
Can AI agents edit content in Alinea?
Yes. The package ships its documentation as Markdown in node_modules/alinea/docs/, the website lists every page in /llms.txt, and alinea init adds a marked section to your AGENTS.md plus an MCP server entry in .mcp.json. While alinea dev runs, agents use that server to read your schema and create, edit and publish entries through the same save path as the dashboard.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/alineacms-alinea)Was this useful?