# Set up with an AI agent

Source: https://v2.alineacms.com/docs/ai-setup

Let a coding agent such as Claude Code, Cursor or Codex add Alinea to your Next.js project. Paste the prompt below into your agent: it reads this guide as Markdown from [/start.md](https://v2.alineacms.com/start.md), sets up Alinea step by step, then helps you model your content and create the first entries.

The rest of this page is written for the agent. You are adding Alinea, a git-based headless CMS, to the user's Next.js project. Content is stored as JSON files in the repository and the schema is TypeScript in `cms.ts`. Follow the steps in order, run the commands yourself, check each result before moving on and ask the user whenever a step needs a decision. The complete documentation is available as one file at [/llms-full.txt](https://v2.alineacms.com/llms-full.txt), fetch it when you need an API that is not covered here. Once Alinea is installed, `node_modules/alinea/docs/` holds the docs of the installed version as a Markdown file per page, start at `index.md`.

## 1. Check the project

- Confirm this is a Next.js project that uses the App Router: `next` is listed in the `dependencies` of `package.json` and there is an `app/` or `src/app/` directory. Alinea's Next.js integration needs the App Router. If there is no project yet, ask the user whether to create one with `npx create-next-app@latest`.
- Detect the package manager from the lockfile: `package-lock.json` (npm), `pnpm-lock.yaml` (pnpm), `yarn.lock` (yarn), `bun.lock` or `bun.lockb` (bun). Use it for every command in this guide. The examples use npm: replace `npx alinea` with `pnpm alinea`, `yarn alinea` or `bun alinea`, and `npm run` with `pnpm`, `yarn` or `bun run`.
- Check that `node --version` is 24 or higher and that the project uses React 19, the Alinea CLI refuses to run otherwise.
- If a `cms.ts`, `cms.tsx`, `src/cms.ts` or `src/cms.tsx` file already exists, Alinea is already set up: check steps 4 and 5, then continue at step 6.
- Suggest committing any pending changes first, so the user can review what you change.

## 2. Install Alinea

Install the package in the project root.

```shellscript
npm install alinea@preview
```

With other package managers: `pnpm add alinea@preview`, `yarn add alinea@preview` or `bun add alinea@preview`.

## 3. Run alinea init

```shellscript
npx alinea@preview init
```

In a Next.js project `alinea init` creates:

- `cms.ts`, in `src/` if the project has a `src` directory: an example schema and workspace, `baseUrl`, `handlerUrl: '/api/cms'` and `adminPath: '/admin'`.
- `app/(alinea)/api/cms/route.ts`, also under `src/` when it exists: the API route the dashboard talks to. It is only created when `next` is listed in `dependencies`.
- `content/pages/welcome.json` and `content/media/`: a first entry and the media folder.
- `.gitignore` entries for `/public/admin.html` and `/public/admin/`, where `alinea build` writes the dashboard.
- `AGENTS.md`: a short Alinea section that points coding agents to the bundled docs and the MCP server. It is added to an existing `AGENTS.md` once, or creates the file.
- It prefixes the `dev` and `build` scripts in `package.json` with `alinea dev --` and `alinea build --`.

Read the generated files. The route imports `@/cms`: check that `tsconfig.json` maps `@/*` to the folder that holds `cms.ts` (create-next-app sets this up), otherwise fix the import.

## 4. Wire up Next.js

Wrap the existing Next.js config with `withAlinea` and keep all of its options. It serves the dashboard at `/admin` and keeps the generated content package out of the server bundle.

File: next.config.ts
```tsx
import {withAlinea} from 'alinea/next'
import type {NextConfig} from 'next'

const nextConfig: NextConfig = {
  // The existing options
}

export default withAlinea(nextConfig)
```

In a CommonJS `next.config.js` use `const {withAlinea} = require('alinea/next')` and `module.exports = withAlinea(nextConfig)`.

Check that the API route exists, and create it if `alinea init` did not:

File: app/(alinea)/api/cms/route.ts
```tsx
import {cms} from '@/cms'
import {createHandler} from 'alinea/next'

// This handler responds to the API requests of the dashboard
const handler = createHandler({cms})

export const GET = handler
export const POST = handler
```

Check the scripts in `package.json`. Keep any flags the user had after `next dev` or `next build`.

```json
{
  "scripts": {
    "dev": "alinea dev -- next dev",
    "build": "alinea build -- next build"
  }
}
```

`alinea build` writes the dashboard to `public/admin.html` and `public/admin/`. Check that `.gitignore` lists `/public/admin.html` and `/public/admin/` (`alinea init` adds them).

## 5. Enable live previews

Set `preview: true` in the `createCMS` options in `cms.ts` (`alinea init` leaves it commented out). Then render the preview component in the root layout, add only this line to the existing `app/layout.tsx`:

File: app/layout.tsx
```tsx
import {cms} from '@/cms'

export default function RootLayout({children}: {children: React.ReactNode}) {
  return (
    <html lang="en">
      <body>
        {children}
        <cms.previews widget />
      </body>
    </html>
  )
}
```

The dashboard now shows the page next to the editor and updates it while the user types. Remove the `widget` prop to hide the small preview toolbar on the page. `baseUrl.production` in `cms.ts` is read from `NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL`, which Vercel sets during the build. Ask the user where the site is deployed: if it is not on Vercel, ask for its production URL and set it as `baseUrl.production`.

## 6. Start the dev server

Start the dev script in the background and keep it running for the rest of this guide.

```shellscript
npm run dev
```

`alinea dev` compiles `cms.ts`, starts the local CMS and then starts `next dev`. Read the output:

- `MCP server: http://localhost:4500/mcp` is the MCP server for the next step. The port moves to 4501 and up when 4500 is taken, always use the printed url.
- `- Alinea CMS: http://localhost:3000/admin` is the dashboard. Ask the user to open it and have a look around.

If the output shows an error in `cms.ts`, fix it before continuing. The config is recompiled whenever you save it.

## 7. Connect to the Alinea MCP server

While `alinea dev` runs it also serves a Model Context Protocol server that reads the schema and creates, edits and publishes entries through the same write path as the dashboard. It fills in internal fields such as ids, ordering keys and rich text nodes, so use it for all content changes instead of writing JSON files. It only runs during `alinea dev` and only accepts requests from localhost.

In Claude Code, add it with the url from the dev server output:

```shellscript
claude mcp add --transport http alinea http://localhost:4500/mcp
```

For other agents add an HTTP MCP server named `alinea` with the same url, for example in a project `.mcp.json` (Cursor reads `.cursor/mcp.json`):

```json
{
  "mcpServers": {
    "alinea": {
      "type": "http",
      "url": "http://localhost:4500/mcp"
    }
  }
}
```

Most agents only load MCP servers when a session starts. If the tools do not show up, ask the user to restart or reload the session, and to point you back to [/start.md](https://v2.alineacms.com/start.md) to continue at this step.

The tools are `describe_schema`, `find_entries`, `get_entry`, `create_entry`, `update_entry`, `publish_entry`, `delete_entry`, `move_entry` and `upload_file`. Call `describe_schema` first. The [MCP server](https://v2.alineacms.com/docs/mcp) page describes every tool, its parameters and the value formats it accepts. If the MCP server cannot be connected, edit the content JSON files following [Working with AI agents](https://v2.alineacms.com/docs/ai-agents) instead.

## 8. Model the content

Ask the user what the site needs before you write any schema: which pages and sections, which content repeats (posts, products, team members, events), what is shared across pages (navigation, footer, settings) and whether the site is translated. Propose a schema in plain words and wait for confirmation.

Then define the types in `cms.ts`, or in separate files imported there:

- `Config.document(label, {fields, contains})` for entries that have their own page. Title, path (the url slug) and SEO metadata fields are added automatically, `contains` lists the types allowed as children.
- `Config.type(label, {fields})` for blocks in lists or rich text, and for entries without a page of their own such as site settings.
- Fields such as `Field.text`, `Field.richText`, `Field.image`, `Field.link`, `Field.entry`, `Field.list`, `Field.select`, `Field.check`, `Field.date` and `Field.number`, see [Fields](https://v2.alineacms.com/docs/fields).
- Register every type in `schema` and list the types each root accepts in `contains`. Pages that must always exist, such as the homepage, can be seeded with `Config.page` in the `children` of a root.

File: cms.ts
```tsx
import {Config, Field} from 'alinea'
import {createCMS} from 'alinea/next'

export const Page = Config.document('Page', {
  fields: {
    intro: Field.text('Intro', {multiline: true}),
    body: Field.richText('Body')
  }
})

export const Blog = Config.document('Blog', {
  contains: ['BlogPost'],
  fields: {
    intro: Field.text('Intro', {multiline: true})
  }
})

export const BlogPost = Config.document('Blog post', {
  fields: {
    publishDate: Field.date('Publish date'),
    image: Field.image('Image'),
    body: Field.richText('Body')
  }
})

export const cms = createCMS({
  schema: {Page, Blog, BlogPost},
  workspaces: {
    main: Config.workspace('Main', {
      source: 'content',
      mediaDir: 'public/media',
      roots: {
        pages: Config.root('Pages', {
          contains: ['Page', 'Blog']
        }),
        media: Config.media()
      }
    })
  },
  baseUrl: {
    development: 'http://localhost:3000',
    production:
      process.env.NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL ??
      'http://localhost:3000'
  },
  preview: true,
  handlerUrl: '/api/cms',
  adminPath: '/admin'
})
```

The example `content/pages/welcome.json` entry has type `Page`. If you remove or rename that type, delete the entry with `delete_entry`. Save `cms.ts`, check the dev server output for errors and call `describe_schema` again to confirm the new types.

## 9. Create the first entries

Create the starting content with the MCP tools: find the parent with `find_entries`, then call `create_entry` with the type, the parent and the field values. Rich text fields accept Markdown, link fields accept entry ids, and `upload_file` adds images to the media library and returns the id for image fields. Entries are published by default. Write real starting content with the user rather than placeholder text, then ask them to review it in the dashboard. Every entry is a JSON file under `content/`.

## 10. Render the content

Query content in server components with the `cms` instance: `cms.find` returns an array, `cms.first` returns the first match or null, `cms.get` returns one entry and throws when nothing matches. An entry's url is built from the paths of its parents, a post with path `hello` below a blog with path `blog` is at `/blog/hello`.

File: app/blog/page.tsx
```tsx
import {Query} from 'alinea'
import Link from 'next/link'
import {BlogPost, cms} from '@/cms'

export default async function BlogPage() {
  const posts = await cms.find({
    type: BlogPost,
    select: {id: Query.id, title: Query.title, url: Query.url}
  })
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>
          <Link href={post.url}>{post.title}</Link>
        </li>
      ))}
    </ul>
  )
}
```

File: app/blog/[slug]/page.tsx
```tsx
import {Query} from 'alinea'
import {RichText} from 'alinea/ui'
import {notFound} from 'next/navigation'
import {BlogPost, cms} from '@/cms'

interface PostPageProps {
  params: Promise<{slug: string}>
}

export default async function PostPage({params}: PostPageProps) {
  const {slug} = await params
  const post = await cms.first({type: BlogPost, url: `/blog/${slug}`})
  if (!post) notFound()
  return (
    <article>
      <h1>{post.title}</h1>
      <RichText doc={post.body} />
    </article>
  )
}

export async function generateStaticParams() {
  const paths = await cms.find({type: BlogPost, select: Query.path})
  return paths.map(slug => ({slug}))
}
```

Open the pages in the browser and in the dashboard preview. Filtering, ordering, relations and child queries are described in [Querying content](https://v2.alineacms.com/docs/query).

## 11. Verify and hand over

Run a production build and fix every error it reports.

```shellscript
npm run build
```

Then give the user a short summary: the files you changed, how to open the dashboard (`npm run dev`, then http://localhost:3000/admin) and what to do next. Publishing from a deployed site needs a backend for login, drafts and git commits, see [Deploying](https://v2.alineacms.com/docs/deploy).

Further reading:

- Docs of the installed version: `node_modules/alinea/docs/index.md`
- [Complete documentation in one file](https://v2.alineacms.com/llms-full.txt)
- [Index of all docs pages as Markdown](https://v2.alineacms.com/llms.txt)
- [Configuration](https://v2.alineacms.com/docs/configuration)
- [Schema](https://v2.alineacms.com/docs/schema) and [fields](https://v2.alineacms.com/docs/fields)
- [Querying content](https://v2.alineacms.com/docs/query)
- [Live previews](https://v2.alineacms.com/docs/live-previews)
- [MCP server](https://v2.alineacms.com/docs/mcp)
- [Working with AI agents](https://v2.alineacms.com/docs/ai-agents)
- [CLI](https://v2.alineacms.com/docs/cli)
- [Deploying](https://v2.alineacms.com/docs/deploy)
