Quiverblocks chooses Diggama to deliver 200+ articles every month

Guide · 33 min read

Build a website with Claude Code, Codex or Cursor your team edits

TL;DR

To build a website with an AI coding agent such as Claude Code, Codex or Cursor and keep it editable, let the agent write the code but keep every headline, image and post in a headless CMS that the site reads through a REST API at build time. Host the build on Cloudflare and have the CMS call a deploy hook on every change, so editors publish without a prompt or a developer. Give the agent one AGENTS.md file with the content rules and connect it to the CMS over MCP, so it codes against the real content model instead of guessing.

By Diggama Team Updated Tested with TypeScript 7.0Node.js 22 View as Markdown

This guide is the map for the whole series. It walks through every step from an empty folder to a live site that your marketing team edits without you, and explains the decisions at each step. It works with any major AI coding agent: Claude Code, OpenAI Codex and Cursor first, Gemini CLI and GitHub Copilot in agent mode too. The Astro tutorial and the Next.js tutorial then build the same site, file by file.

The example site throughout is Northwind Studio, a small company website with a homepage, a blog, a team page and a contact form.

Why are people leaving Webflow and Framer for AI coding agents?

People leave visual builders for AI coding agents because an agent removes the main reason they used a builder: not wanting to write and maintain the code by hand. With Claude Code, Codex or Cursor you describe a page, the agent writes the components, and you own a normal repository with no per-seat editor pricing, no platform-specific interactions, and no ceiling on what the site can do.

Webflow and Framer are good products. Their visual canvas is fast, designers can work without a developer, and the hosting, forms and CMS come in one box. Those are real strengths, and for a team of designers who never want to touch code they remain a sensible choice.

The trade-off is that the site lives inside the platform. Webflow’s own documentation states that CMS content is not included when you export a site’s code: collection lists render their empty state on an exported site. A developer who wants a custom integration, a different framework or a specific hosting setup hits the edge of the builder sooner or later.

Coding agents change the economics of that trade. Writing an Astro or Next.js site by hand is days of developer work. An agent that reads your repository, runs your build and fixes its own errors turns most of that work into prompting and reviewing.

What breaks after you switch to a site built with an AI agent?

The thing that breaks is content editing. A site generated by an AI coding agent typically has every headline, paragraph and image path written directly into components, so the only way to change a word is to edit code, which means a developer, or a prompt to the agent, followed by a redeploy.

It shows up in three ways within the first month:

  • Copy is hardcoded. The hero headline sits in src/components/Hero.astro, the team bios in a TypeScript array, the blog posts in Markdown files. Nothing is wrong with the code; it just has no editing surface.
  • Marketers are locked out. The people who used to fix typos in the Webflow Editor now have to file a request. They have no access to the repository, and you do not want them editing JSX.
  • Every typo costs a prompt and a deploy. Changing “Get a quote” to “Request a quote” means opening the agent, prompting it, reviewing a diff, committing and waiting for the build. That is fine once. It is not fine twelve times a week.

The fix is not to stop using the agent. It is to separate the two things that a visual builder bundled together: the code that renders the site, and the content that the team edits. The agent owns the first, a headless CMS owns the second.

What does the architecture of an AI-built website with a CMS look like?

The architecture has three parts with one job each: a Git repository holds the code your agent writes, Diggama holds the content your team edits, and Cloudflare builds and hosts the site. The site reads content from Diggama’s REST API at build time, and Diggama triggers a rebuild whenever content changes.

flowchart TD
  dev["Developer + AI agent"] -->|git push| repo["Git repository"]
  dev -.->|"MCP (dev only)"| cms["Diggama"]
  team["Marketing team"] -->|"edit, publish"| cms
  repo -->|push starts a build| cf["Cloudflare Workers Builds"]
  cms -->|"Workflow: webhook"| cf
  cf -->|"REST API (build)"| cms
  cf -->|wrangler deploy| site["Live site"]
  site -->|"POST contact-submissions"| cms

Who owns what, and how changes reach production:

LayerLives inChanged byReaches production by
Layout, components, stylesGit repositoryDeveloper with an AI coding agentgit push, then Cloudflare build
Copy, posts, images, teamDiggamaEditors in the dashboardPublish, then webhook, then Cloudflare build
Content model (blueprints)DiggamaDeveloperDashboard change, then matching code change
Form submissionsDiggama (read-only blueprint)Site visitorsServer-side POST at request time, with a create-only token

Two choices in this design are deliberate.

Content is fetched at build time, not in the browser. The output is static HTML, so pages load fast, the API token never reaches a visitor, and an API outage cannot take the site down. The cost is a rebuild after each publish, which step 5 automates.

The agent talks to the CMS over MCP during development only. The production site uses plain HTTP requests with a read-only token. MCP is how the agent discovers the content model and seeds test content while it writes the code.

Which framework should you ask your agent to use?

Pick Astro for a content site that is mostly pages and posts, and Next.js if the site will grow into an application with logged-in areas or heavy interactivity. Both work well with AI coding agents because they are widely used, well documented and have first-class Cloudflare deployment paths.

FrameworkBest forRendering for CMS contentCloudflare deploymentTutorial in this series
AstroMarketing sites, blogs, docsStatic by default, server routes on demandStatic output needs no adapter; @astrojs/cloudflare for server routesYes
Next.jsSites that become appsStatic generation, server components, ISRAdapter required: @opennextjs/cloudflare or vinextYes
NuxtVue teamsStatic generation or server renderingNitro’s Cloudflare presetsNot written yet
SvelteKitSvelte teams, small bundlesPrerendering or server rendering@sveltejs/adapter-cloudflareNot written yet
Plain HTMLOne-page sitesA small Node script that fetches and writes HTMLStatic assets onlyNot written yet

A few facts behind the table. Astro’s documentation says that a static Astro site needs no adapter, and that the current Cloudflare adapter targets Workers and no longer supports Cloudflare Pages. For Next.js, the OpenNext adapter supports all of Next.js 16 and the latest minors of 14 and 15, while Cloudflare’s own Next.js guide now recommends vinext, a Vite plugin that reimplements the Next.js API and is still in beta.

Whatever you pick, do not fetch CMS content from the browser with a token in client-side JavaScript. Anyone can read that token from the page source. Keep tokens in the build and in server code.

Step 1: How do you model the content before the agent writes any code?

Model the content first, because the content model is the contract between the code and the editors. In Diggama the model is a set of blueprints: each blueprint is a content type with typed fields, and its field keys become the keys of the attributes object the API returns.

Blueprints are created under Configuration › Blueprints in the dashboard. Diggama has three blueprint types: a resource (many records, like posts), a singular resource (exactly one record, like the homepage), and a read-only resource (records created through the API but not editable in the dashboard, like form submissions).

Here is the Northwind Studio model. Every tutorial in the series uses these exact blueprint and field keys.

BlueprintTypeField keyField typeNotes
homepageSingular resourcehero-titleTextMain headline
hero-subtitleTextOne sentence under the headline
hero-imageImageReturned as an absolute URL
introRich textReturned as an HTML string
postsResourcetitleTextRequired
slugSlugRequired; the URL segment, e.g. our-design-process
excerptTextUsed on the blog index and in meta tags
coverImageAbsolute URL
contentRich textHTML string
team-membersResourcenameTextRequired
roleText
photoImageAbsolute URL
bioRich textHTML string
contact-submissionsRead-only resourcenameTextWritten by the form endpoint
emailEmailValidated by Diggama as an email
messageText

Posts have no date field. Publication is handled by Diggama itself: every record carries a system published_at value that is null for a draft and a date once published, so you sort and filter on published_at instead of inventing your own. Only standard blueprints have that cycle: the singular homepage and the read-only submissions are never published, and a view token sees them whatever their published_at.

Field keys are derived from field names

When you add a field in the dashboard, Diggama derives its key from the name: “Hero title” becomes hero-title, with a hyphen, and the key cannot be changed after the field exists. Name fields with that in mind. In TypeScript, hyphenated keys are read with bracket notation: home.attributes['hero-title'].

Let the agent propose the model, then create it yourself

If you are migrating an existing site, a coding agent is good at turning pages into a content model. Ask for a proposal, review it, then create the blueprints in the dashboard. You can also sketch one with the content model generator.

Prompt for your AI agent
Read every page in src/pages and every component in src/components. List each piece of text, image and repeated item that a non-developer would want to change. Group them into Diggama blueprints and output one Markdown table per blueprint with: field name, field type, required or not, and an example value from the current site.

Use only these Diggama field types: text, rich-text, slug, email, number, boolean, date, datetime, image, file, link, enum, multi-enum, reference, references.

Use a singular resource for one-off pages like the homepage and a read-only resource for anything visitors submit. Do not change any code yet.

Create the tokens

Tokens are created under Configuration › API Tokens (Developer role or higher), and each one is scoped per blueprint and per ability. The value is shown once, so copy it straight into your password manager. Create one token per job so that a leaked token can do as little as possible.

TokenAbilitiesBlueprintsUsed by
Buildviewhomepage, posts, team-membersCloudflare build, production
Previewpreviewhomepage, posts, team-membersPreview build that shows drafts
Formcreatecontact-submissionsForm endpoint at runtime
Agent (dev)preview, optionally create and updatehomepage, posts, team-membersMCP server in your coding agent, created in step 2

The number of API keys depends on the plan: one on Starter, three on Growth, five on Enterprise (see pricing). On Starter, remember that a token’s abilities are set per blueprint, so a single token can hold view on the three content blueprints and create on contact-submissions. Use it for the build and the form and keep it server-side. A token created on the API Tokens page works over MCP too, so the agent can share it, at the cost of seeing published records only. Split it into dedicated tokens when you upgrade. We have not confirmed whether an MCP connection counts toward a plan’s API-key limit, so check with Diggama before you plan around it.

Step 2: How do you set up your AI coding agent for a CMS-backed website?

Set up your agent with two things before the first prompt: an AGENTS.md file that states where content comes from, and the Diggama MCP server so the agent can read the real blueprints. Without both, any agent will hardcode copy, because that is the shortest path to a page that renders.

Install the agent you use. The rest of this guide is the same for all of them.

AgentInstallStartNotes
Claude Codecurl -fsSL https://claude.ai/install.sh | bash (setup)claudeNeeds a paid Claude plan or a Console account; the free claude.ai plan does not include it
OpenAI Codexnpm install -g @openai/codex (CLI docs)codex, then Sign in with ChatGPTCLI, IDE extension and ChatGPT desktop app share one config
CursorThe editor, or the CLI: curl https://cursor.com/install -fsS | bash (install)agent in the terminalEditor and CLI share one mcp.json
Gemini CLInpm install -g @google/gemini-cli (README)geminiFree tier with a Google account
GitHub CopilotVS Code with Copilot, in agent modeChat panelAgent mode and MCP are listed on every plan, including Free (plans)

Write one AGENTS.md that forbids hardcoded content

AGENTS.md at the repository root is the one instructions file most agents read without any setup. Write the content rules there once, rather than keeping a CLAUDE.md, a GEMINI.md and a Cursor rule in sync by hand.

AgentReads AGENTS.md?What to do
CodexYes, from the Git root down to the working directory (docs)Nothing. Keep it short: Codex reads at most 32 KiB of combined instructions by default
CursorYes, at the root and in subdirectories (rules)Nothing. .cursor/rules stays available for file-specific rules
GitHub Copilot (VS Code)Yes, chat.useAgentsMdFile is on by default (instructions)Nothing
Claude CodeYes since v2.1.277, but only when there is no CLAUDE.md or CLAUDE.local.md in the folder or above it (memory)If a CLAUDE.md exists, put @AGENTS.md on its first line
Gemini CLINo, it reads GEMINI.md (docs)Add "context": { "fileName": ["AGENTS.md", "GEMINI.md"] } to .gemini/settings.json

Here is the file for Northwind Studio. It names the MCP server diggama, which is the name every config below uses.

AGENTS.md
# Northwind Studio website

Company website. Code lives in this repo; all content lives in Diggama (headless CMS).

## Commands
- `npm run dev`: local dev server
- `npm run build`: production build (fetches content from Diggama)

## Content rules (non-negotiable)
- Never hardcode user-facing copy, images or lists in components. Every headline,
  paragraph, image, post and team member comes from Diggama.
- All Diggama calls go through `src/lib/diggama.ts`. Pages never call fetch() on
  the API directly.
- Content is fetched at build time. Never send a Diggama token to the browser.
- Allowed hardcoded strings: navigation labels, form labels, legal footer.
- If a page needs content that has no blueprint field yet, stop and tell me
  which field to add. Do not invent a fallback string.

## Diggama
- API: https://api.diggama.com/v2, header `Authorization: Bearer $DIGGAMA_TOKEN`
- Blueprints: `homepage` (singular), `posts`, `team-members`,
  `contact-submissions` (read-only, written by the form endpoint only)
- Field keys use hyphens: read them as `attributes['hero-title']`.
- Images are absolute URLs; rich text fields are HTML strings.
- Use the `diggama` MCP server to check field keys before writing code against
  a blueprint. Do not guess keys. MCP returns flattened records; the REST API
  nests fields under `attributes`. Site code uses the REST shape.
- MCP writes are allowed only for draft test content, and only after showing me
  a dry run.

## Environment variables
- `DIGGAMA_TOKEN`: view token, build time only
- `DIGGAMA_FORM_TOKEN`: create-only token on contact-submissions, server runtime only
- `DIGGAMA_MCP_TOKEN`: the agent's own token, in my shell only, never in the repo

If the project already has a CLAUDE.md, for example one your framework generated, make Claude Code read both files with an import at the top. Per Anthropic’s memory documentation, Claude reads the imported file first, then anything you add below the import.

CLAUDE.md
@AGENTS.md

A longer, annotated version of the rules is in the AGENTS.md template for websites.

Connect the Diggama MCP server

Diggama runs a remote MCP server at https://api.diggama.com/mcp that authenticates with a project token sent as a bearer header, with no OAuth (MCP docs). The quickest way to get a token for it is Configuration › Connect to AI in the dashboard: pick Customise per content type, tick preview on homepage, posts and team-members, name the connection after your agent, and copy the token, which is shown once. Then put it in your shell profile, not in the repository:

export DIGGAMA_MCP_TOKEN="your-dev-token"

Why preview rather than view: without it, the agent only sees published records, so it cannot read the drafts it seeds in step 3 or the posts editors are still writing. The preset called Read only has view alone. Avoid Full access for day-to-day coding: it includes permanent deletion.

Every agent below can send that header. Only the place the config lives and the way it reads the token differ.

AgentWhere the server is configuredHow the token is readCheck it with
Claude Codeclaude mcp add, or a project .mcp.jsonWritten into the config, or ${DIGGAMA_MCP_TOKEN} in .mcp.json/mcp or claude mcp list
Codexcodex mcp add, stored in ~/.codex/config.tomlbearer_token_env_var/mcp or codex mcp list
Cursor.cursor/mcp.json (project) or ~/.cursor/mcp.json${env:DIGGAMA_MCP_TOKEN}agent mcp list
Gemini CLI.gemini/settings.json (project) or ~/.gemini/settings.json${DIGGAMA_MCP_TOKEN}/mcp list or gemini mcp list
GitHub Copilot.vscode/mcp.jsonA password prompt, stored by VS CodeMCP: List Servers

Claude Code. The user scope stores the server in ~/.claude.json in your home directory, so nothing is committed (MCP docs). Your shell expands $DIGGAMA_MCP_TOKEN when you run the command, so it is the token value that gets stored there. For a team, a committed .mcp.json with "Authorization": "Bearer ${DIGGAMA_MCP_TOKEN}" works too: Claude Code expands the variable, and asks each person to approve the server in an interactive session before using it.

claude mcp add --transport http diggama https://api.diggama.com/mcp \
  --scope user \
  --header "Authorization: Bearer $DIGGAMA_MCP_TOKEN"

Codex. The flag names an environment variable whose value is sent as the bearer token, so the token itself never lands in the config (CLI reference). The CLI, the IDE extension and the ChatGPT desktop app share this configuration. Codex cloud tasks have no documented MCP support, so run this part locally.

codex mcp add diggama --url https://api.diggama.com/mcp --bearer-token-env-var DIGGAMA_MCP_TOKEN

The same server written by hand (Codex MCP docs):

~/.codex/config.toml
[mcp_servers.diggama]
url = "https://api.diggama.com/mcp"
bearer_token_env_var = "DIGGAMA_MCP_TOKEN"

Cursor. The editor and the agent CLI read the same file, and ${env:NAME} is expanded in headers (Cursor MCP docs), so the project file is safe to commit.

.cursor/mcp.json
{
  "mcpServers": {
    "diggama": {
      "url": "https://api.diggama.com/mcp",
      "headers": { "Authorization": "Bearer ${env:DIGGAMA_MCP_TOKEN}" }
    }
  }
}

Gemini CLI. Use httpUrl, not url: in Gemini CLI, url means the older SSE transport (MCP docs). The same file also tells it to read AGENTS.md. If you turn Folder Trust on, an untrusted folder ignores this file and connects no MCP server.

.gemini/settings.json
{
  "context": { "fileName": ["AGENTS.md", "GEMINI.md"] },
  "mcpServers": {
    "diggama": {
      "httpUrl": "https://api.diggama.com/mcp",
      "headers": { "Authorization": "Bearer ${DIGGAMA_MCP_TOKEN}" }
    }
  }
}

GitHub Copilot in VS Code. VS Code asks for the token once and stores it, so the file holds no secret (MCP configuration). VS Code’s documentation now describes .vscode/mcp.json as deprecated and kept for compatibility, and prefers a portable .mcp.json for new servers; the inputs prompt shown here is the documented way to keep the token out of the file.

.vscode/mcp.json
{
  "inputs": [
    { "type": "promptString", "id": "diggama-token", "description": "Diggama API token", "password": true }
  ],
  "servers": {
    "diggama": {
      "type": "http",
      "url": "https://api.diggama.com/mcp",
      "headers": { "Authorization": "Bearer ${input:diggama-token}" }
    }
  }
}

Leave tool approval on while the agent builds the site. Cursor and Gemini CLI ask before each MCP tool call by default, VS Code keeps global auto-approval off by default (approvals), and Codex prompts only for tools that are not marked read-only when you add default_tools_approval_mode = "writes" under [mcp_servers.diggama] (Codex MCP docs).

If you change the token’s abilities or add a blueprint later, edit the token on the API Tokens page and reconnect the agent: clients cache the tool list, and a preset only covers the blueprints that existed when you created it. The chat apps are a different case. ChatGPT on the web cannot present a custom API key or bearer token to an MCP server, only a token from an OAuth flow (OpenAI auth docs), and Diggama does not offer OAuth. In claude.ai, custom request headers are a beta that only some organizations have, set by an Owner (Claude connectors). The MCP guide covers every tool, the write safeguards and those chat-app setups.

One trap to know before the agent writes code: MCP and REST return records in different shapes. Over MCP, a record is flattened (id, published, published_at, created_at, updated_at, then one key per field). Over REST, the fields sit under attributes. The agent should read field keys from MCP but write code against the REST shape.

Step 3: How do you get your AI agent to build the site?

Have your agent build the site in small, verifiable passes: first the data layer, then one page at a time, each pass ending with a build that succeeds against real content. Large “build my whole website” prompts work, but they produce more hardcoded fallbacks and more code to review at once.

Pass 1: the data layer

The single most important file is the fetch helper. Every page goes through it, so there is exactly one place that knows the API URL, the token, pagination and the publication rules.

Prompt for your AI agent
Use the diggama MCP server to describe the blueprints homepage, posts and team-members. Then create src/lib/diggama.ts that:
- exports TypeScript types for each blueprint's attributes, using the exact field keys from the MCP output (the REST API, unlike MCP, nests them under "attributes")
- exports createDiggama(token) returning homepage(), posts(), post(slug) and teamMembers()
- reads the singular homepage from data[0] of GET /v2/resources/homepage
- requests filter[published]=true on list endpoints and pages through all results with per_page=100
- throws an error with the status code and URL when the API does not answer 200
Do not create any page yet. Show me the file.

What you should get back looks like this. It is framework-agnostic: Astro passes import.meta.env.DIGGAMA_TOKEN, Next.js passes process.env.DIGGAMA_TOKEN.

src/lib/diggama.ts
// One place for every call to Diggama. Pages import the functions below,
// never fetch() the API themselves.
const API = 'https://api.diggama.com/v2';

export type DiggamaResource<A> = {
  id: string;
  type: 'resource';
  published_at: string | null;
  created_at: string;
  updated_at: string;
  attributes: A;
};

type ListResponse<A> = {
  data: DiggamaResource<A>[];
  meta: { current_page: number; last_page: number; per_page: number; total: number };
};

// Attribute keys are the field keys Diggama derived from the field names.
// Image fields come back as absolute URLs, rich text as HTML strings.
export type Homepage = {
  'hero-title': string | null;
  'hero-subtitle': string | null;
  'hero-image': string | null;
  intro: string | null;
};

export type Post = {
  title: string;
  slug: string;
  excerpt: string | null;
  cover: string | null;
  content: string | null;
};

export type TeamMember = {
  name: string;
  role: string | null;
  photo: string | null;
  bio: string | null;
};

export function createDiggama(token: string | undefined, options: { drafts?: boolean } = {}) {
  if (!token) throw new Error('Diggama token is missing: set DIGGAMA_TOKEN');

  async function request<T>(path: string, params: Record<string, string>): Promise<T> {
    const url = new URL(`${API}/${path}`);
    for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);

    const res = await fetch(url, {
      headers: { Accept: 'application/json', Authorization: `Bearer ${token}` },
    });
    if (!res.ok) {
      const body = await res.text();
      throw new Error(`Diggama ${res.status} on ${url.pathname}${url.search}: ${body.slice(0, 300)}`);
    }
    return (await res.json()) as T;
  }

  // Every page of a list, 100 records at a time (the API maximum).
  // Unless drafts are wanted, only records whose published_at has passed:
  // the API compares it to the current time, to the second, so a post
  // scheduled for later today stays out of the build.
  async function list<A>(blueprint: string, params: Record<string, string> = {}) {
    const records: DiggamaResource<A>[] = [];
    for (let page = 1; ; page++) {
      const query: Record<string, string> = { ...params, per_page: '100', page: String(page) };
      if (!options.drafts) query['filter[published]'] = 'true';

      const json = await request<ListResponse<A>>(`resources/${blueprint}`, query);
      records.push(...json.data);
      if (page >= json.meta.last_page) break;
    }
    return records;
  }

  // A singular blueprint holds exactly one record, which the API always
  // serves: it has no draft state, so it is read without the published
  // filter. V2 returns it as a one-item list.
  async function single<A>(blueprint: string) {
    const json = await request<ListResponse<A>>(`resources/${blueprint}`, { per_page: '1' });
    const record = json.data[0];
    if (!record) throw new Error(`Diggama: "${blueprint}" has no record yet`);
    return record;
  }

  return {
    homepage: () => single<Homepage>('homepage'),
    posts: () => list<Post>('posts', { sort: '-published_at' }),
    post: async (slug: string) => (await list<Post>('posts', { 'filter[slug][eq]': slug }))[0] ?? null,
    teamMembers: () => list<TeamMember>('team-members', { sort: 'name' }),
  };
}

Three details in that file are worth keeping whatever your agent produces:

  • It asks for published records explicitly with filter[published]=true (publishing docs). A view token already hides drafts and scheduled posts, but the filter means that a build run by mistake with a preview token still ships only published posts. The filter compares published_at to the current time, to the second, so a post scheduled for 18:00 today stays out of a build that runs at 9:00. A preview build passes { drafts: true } and gets everything.
  • It reads the homepage without that filter. A singular resource has no publication cycle, so the API serves it to a view token whatever its published_at, and filtering it would only risk an empty result.
  • It reads every page. Lists return 25 records by default and at most 100 per request (pagination). A blog that quietly stops at 25 posts is a classic bug in generated code.

Pass 2: the pages

With the helper in place, build one page per prompt and check each against real content.

Prompt for your AI agent
Build the homepage from the homepage blueprint, using createDiggama from src/lib/diggama.ts with the DIGGAMA_TOKEN environment variable. Render hero-title as the h1, hero-subtitle under it, hero-image as the hero image, and intro as HTML below the hero. The API returns the image as a bare URL with no dimensions, so set width and height attributes from the design's aspect ratio and use object-fit: cover. No hardcoded copy except the navigation. Then run npm run build and fix any error until it passes.
Prompt for your AI agent
Build the blog: an index page listing posts() with cover, title, excerpt and the published_at date, and one static page per post at /blog/[slug]/ generated from posts() at build time. Render content as HTML. Add a meta description from excerpt. Run npm run build and confirm one HTML file per published post in the output.
Prompt for your AI agent
Build the team page from teamMembers(): photo, name, role and bio for each member. Then search every file in src/ for user-facing strings that are not navigation labels, form labels or the legal footer, and list them with file and line. Do not fix them yet.

That last prompt is the habit that keeps the site editable. Run it after every feature.

Pass 3: the contact form

The form is the only part that runs at request time. A server endpoint receives the submission, validates it, and creates a record in the contact-submissions blueprint with the create-only token, using POST /v2/resources/contact-submissions (create resources). The token never leaves the server.

Prompt for your AI agent
Add a contact form on /contact/ with name, email and message, and a server endpoint that receives it. The endpoint validates that all three fields are present, that email looks like an email and that message is under 5000 characters, and silently drops any request that fills a hidden honeypot field. Then it POSTs {"attributes": {"name": ..., "email": ..., "message": ...}} as JSON to https://api.diggama.com/v2/resources/contact-submissions with "Authorization: Bearer" and the DIGGAMA_FORM_TOKEN environment variable, read at runtime on the server. Return a JSON success, a 400 with field errors, or a 502 if Diggama answers anything other than 201. Keep every other page static.

Validate in your endpoint rather than relying only on the CMS to reject bad input: you control the error messages, and spam never reaches your project. Diggama answers 201 Created on success and 422 VALIDATION_ERROR if a value does not match its field type or a required field is missing (errors). To have the team notified, add a Diggama Workflow with an Event type condition (Resource Created), a Resource type condition (contact-submissions) and the Send email action; the body can insert {{name}}, {{email}} and {{message}}.

Seeding content through MCP

Before editors fill the CMS, the agent can create test content so the pages have something to render. Give the agent’s token create on posts for this, and remove it afterwards.

Prompt for your AI agent
Using the diggama MCP server, call describe_blueprint on posts, then create three draft posts about Northwind Studio's design process, each with a title, a slug derived from the title, an excerpt, and content of about 300 words as HTML paragraphs. Leave cover empty. Show me a dry run of the first one before writing anything. Leave all three unpublished.

Records created over MCP start as drafts, and Diggama’s MCP write tools accept a dry_run flag that validates the payload and shows the result without writing. There is no upload tool, so image fields only accept the URL of a file already in Diggama: leave covers to the editors. Every real MCP write also fires your workflows, so a seeding run with the rebuild workflow from step 5 active will trigger builds. Writes over MCP are capped at 60 a minute per project.

Step 4: How do you deploy the site to Cloudflare?

Deploy by connecting the repository to a Cloudflare Worker with Workers Builds: every push to the production branch runs your build command, then npx wrangler deploy. The Diggama token is a build variable, because the content is fetched during the build; the form token is a runtime secret, because the form endpoint needs it on each request.

This series deploys to Workers rather than Cloudflare Pages, for two reasons: Astro’s Cloudflare adapter no longer supports Pages, and deploy hooks, which step 5 relies on, came to Workers Builds in April 2026.

The sequence, which the framework tutorials walk through with every file and command:

  1. Have your agent add the framework’s Cloudflare setup (an adapter if you use server routes, and a wrangler.jsonc). The Worker name in that file must match the Worker name in the dashboard, or the build fails.
  2. Push the repository to GitHub or GitLab.
  3. In the Cloudflare dashboard, open Workers & Pages, create a Worker from your repository, and set the build command to npm run build. The deploy command defaults to npx wrangler deploy.
  4. Under the build settings, add DIGGAMA_TOKEN as a build variable. Cloudflare’s documentation is explicit that build variables are not available at runtime, which is what you want for this token.
  5. Add DIGGAMA_FORM_TOKEN as a runtime secret under Settings › Variables & Secrets, or from your terminal:
npx wrangler secret put DIGGAMA_FORM_TOKEN
  1. Push a commit, watch the build log, and open the workers.dev URL. Add your domain once the content checks out.

Step 5: How do you rebuild the site when content is published?

Connect Diggama to Cloudflare with a deploy hook: Cloudflare gives you a URL that starts a build when it receives a POST, and a Diggama Workflow with the Send webhook action calls that URL whenever a record changes. Editors click Publish, and the site is up to date as soon as that build finishes.

Create the deploy hook in Cloudflare

In your Worker, go to Settings › Builds › Deploy Hooks, give the hook a name, select the production branch, and copy the URL. According to Cloudflare’s deploy hooks documentation:

  • the hook needs no authorization header, so the URL itself is the secret; keep it out of your repository
  • if the hook fires again while a build is still queued or initializing, no second build is created
  • hooks are rate limited to 10 builds per minute per Worker and 100 per minute per account

You can test it from a terminal before wiring Diggama:

curl -X POST "https://api.cloudflare.com/client/v4/workers/builds/deploy_hooks/YOUR_HOOK_ID"

Create the Workflow in Diggama

In the Diggama dashboard, open Configuration › Workflows and create a Workflow with the Event trigger type, one Resource type condition set to posts, and one action, Send webhook, with the deploy hook URL as the Destination URL. Add no Event type condition, so creates, updates and deletes all rebuild. Repeat for homepage and team-members.

One Workflow per blueprint is not an oversight. A Workflow’s conditions must all pass, so a single Workflow cannot match several blueprints. And a Workflow with no condition would also fire on every contact form submission, rebuilding the site each time a visitor writes to you.

What the webhook does, precisely (workflows docs):

  • It fires on three events: resource created, resource updated and resource deleted. Publishing and unpublishing count as updates.
  • The request is a POST with the body {"event": "resource_updated"} (or the matching event name) and nothing else. Cloudflare ignores the body, which is fine: the build fetches everything again.
  • Saving a draft also counts as an update and triggers a build. The build only includes published records, so nothing leaks, it just costs a build. Background autosaves in the editor do not fire workflows.
  • When a record scheduled for a future date reaches its publication time, Diggama fires an update event within about five minutes, so scheduled posts go live without anyone touching the dashboard.
  • Records created by a JSON import fire no event. After an import, trigger one build yourself with the curl command above.
  • Any 2xx answer counts as success. Diggama tries twice, with a 10-second timeout. If builds do not start, open the Workflow’s Execution history: a failed run shows the error the hook returned.

If a burst of edits arrives while a build is queued, Cloudflare’s deduplication absorbs it. If the build is already running, the next hook call queues one more build, so the last edit is always included.

Step 6: How do you hand the CMS over to the marketing team?

Hand over by giving each person a role that matches what they do, and by showing them the one loop they need: edit, preview, publish, wait for the build. Diggama has three roles, and each includes the permissions of the one before it.

RoleCan doGive it to
EditorCreate, edit, publish and delete content, upload imagesMarketers, writers
DeveloperEverything an Editor can, plus blueprints, workflows, API tokens and Connect to AIYou, and anyone who changes the code
AdministratorEverything a Developer can, plus members and project settingsThe site owner

Add people from Configuration › Users. Each person must already have a Diggama account: members are added by the email of an existing account and no invitation email is sent, so ask them to sign up first.

Editors cannot change blueprints. That protects the contract: a marketer cannot rename a field and break a page that reads it.

Drafts and previews

Posts and team members have a draft state. A draft is invisible to the production build, so editors can save work in progress at any time. For a full-page preview before publishing, run a second build:

  • a second Worker (or a non-production branch build) whose DIGGAMA_TOKEN is the preview token
  • a build flag that makes the site call createDiggama(token, { drafts: true })
  • a second deploy hook, called by the same Workflows, so the preview rebuilds on every save

Put the preview site behind access control, for example Cloudflare Access, since it shows unpublished content.

The homepage is a singular resource, with no draft or publish switch in the dashboard: every saved change goes live with the next build. Tell editors that explicitly.

A one-page handover note

Write the team a short note with: the dashboard URL, which blueprint drives which page, how long a rebuild takes on your site (time it once and write the real number down), the preview URL, and who to call when a page needs a field that does not exist yet. That last item is the only time they need a developer.

What does this stack cost to run?

The stack has three bills: a plan for the AI coding agent of whoever writes the code, a Diggama plan for the content, and Cloudflare for hosting and builds.

ServiceWhat you pay forStarting point
Claude CodeThe developer’s Claude subscription or Console usagePro at $20/month (Anthropic pricing)
OpenAI CodexA ChatGPT plan or API usageIncluded in every ChatGPT plan, from Free; Plus at $20/month (Codex pricing)
CursorA Cursor planHobby is free with limited agent requests; Individual from $20/month lists MCP (Cursor pricing)
Gemini CLI / GitHub CopilotA Google account / a Copilot planGemini CLI has a free tier of 1,000 requests a day (quotas); Copilot Pro is $10/month (plans)
DiggamaContent, users, blueprints, API keysStarter at 29.90 EUR/month: 3 users, 5 custom blueprints, 1 API key
Cloudflare WorkersHosting, builds, the form endpointA free plan exists; requests to static assets are free and unlimited (Workers pricing)

Diggama’s Growth plan (89 EUR/month) raises the limits to 6 users, 10 blueprints and 3 API keys, enough for dedicated build, form and preview tokens. Enterprise (249 EUR/month) adds 2FA and priority support with 12 users, 25 blueprints and 5 API keys. MCP, the AI Assistant and Workflows are included on every plan, and Diggama offers a three-month free trial with no credit card. There is no free plan. Details on the pricing page; for a full budget including design and development time, try the website cost calculator.

Launch checklist

Before you announce the site, check each line:

  • Every blueprint in the model exists in Diggama with the exact field keys the code uses
  • AGENTS.md states the content rules, every agent on the team reads it, and the last hardcoded-string audit came back clean
  • All API calls go through src/lib/diggama.ts; no token appears in client-side code or in the repository
  • The production build uses a view token; the form uses a create-only token on contact-submissions
  • DIGGAMA_TOKEN is a build variable; DIGGAMA_FORM_TOKEN is a runtime secret
  • List requests page through all results (per_page=100 and a loop until meta.last_page), so the blog does not stop at 25 posts
  • One Workflow per content blueprint calls the deploy hook; a test publish triggers a build
  • A test form submission appears in contact-submissions, and the team gets the notification email
  • Editors have the Editor role, and have published one change themselves end to end
  • The agent’s MCP token has lost any create, update or delete it was given for seeding
  • The preview site, if any, is behind access control
  • Images have width and height, pages have titles and meta descriptions; run the website grader on the live URL

What are the most common mistakes?

Most failures in AI-built websites with a CMS come from the boundary between code and content, not from the code itself. These are the ones we see most often, whichever agent wrote the code.

Letting the agent add fallback copy. A component that renders home.attributes['hero-title'] ?? 'Design that works' looks safe, but the fallback ships the day the field is empty and nobody knows where it came from. Forbid fallbacks in AGENTS.md and fail the build instead.

Guessing field keys. Agents will happily write heroTitle or hero_title when the key is hero-title, or read post.title (the flattened MCP shape) instead of post.attributes.title. Without types, the page renders nothing and no error tells you why. Make the agent read the blueprint over MCP before it writes code against it.

Instructions the agent never reads. Rules in a CLAUDE.md do nothing for Codex, and an AGENTS.md does nothing for Gemini CLI until you add it to context.fileName. Ask the agent to quote the content rules back at the start of a session if you are not sure.

Fetching from the browser. A token in client-side JavaScript is public. Even a read-only token there means anyone can query your project and spend your API quota.

Using one all-powerful token everywhere. The build needs view. The form needs create on one blueprint. The agent needs preview and, briefly, create. Three narrow tokens are safer than one broad one.

Forgetting pagination. Lists return 25 records by default. A blog index built from one request stops at 25 posts.

One Workflow without a resource type condition. Every contact form submission then rebuilds the whole site.

Giving MCP delete “just in case”. Deletion in Diggama is permanent, with no undo. Grant delete to the agent’s token only for a specific clean-up, then remove it.

Changing a blueprint without changing the code. Removing a field in the dashboard breaks the pages that read it at the next build. Treat blueprint changes like a database migration: change the model and the code together, and have the agent update the types from the MCP output.

Where do you go from here?

Pick your framework and follow the full build, with every file shown:

If you are coming from a visual builder, migrating from Webflow to an AI-built site covers moving the content and keeping your URLs, and Webflow vs Framer vs AI coding agents helps decide whether the switch is worth it for your team. For the reasoning behind this whole setup, read why AI-built websites need a CMS.

Still choosing a CMS? The best headless CMS in 2026 compares ten platforms on hosting, pricing and how well AI agents can work with them, and the web and CMS trends for 2027 covers where this way of building sites is heading.

To try the content side now, create a Diggama project and build the four blueprints from step 1; the API introduction has the first request.

FAQ

Frequently Asked Questions

Something else? Get in touch.

Can an AI coding agent build a complete website on its own?

Claude Code, Codex, Cursor and similar agents can scaffold the project, write every component and page, wire the data layer and run the build, all from prompts. What they cannot do is decide what your content model should be or keep the site editable for people who will never open a terminal. You still review the diff, own the repository and decide where content lives.

Which AI coding agent should I use: Claude Code, Codex or Cursor?

Any of them works for this setup, and so do Gemini CLI and GitHub Copilot in agent mode. All of them can read an AGENTS.md file and connect to a remote MCP server with a bearer token. Only the install, the plan and the config file differ, so pick the one your team already pays for or prefers to review code in.

Do I need to know how to code to build a website with AI?

You need to be comfortable with Git and with reading code well enough to review what the agent changes. You do not need to write the components yourself. The people who edit the site afterwards need no technical skill at all if the content lives in a CMS instead of in the code.

Why not keep the content in Markdown files in the repository?

Markdown in the repo works when only developers edit the site. As soon as a marketer needs to fix a headline or publish a post, every change becomes a Git commit, which means a developer or an agent prompt for each typo. A headless CMS gives non-developers a form, drafts, roles and a publish button, while the code stays in Git.

Can the agent read and write the content in the CMS directly?

Yes, if the CMS exposes an MCP server. Diggama's MCP server lets any MCP-capable agent list blueprints, read records and, when the token allows it, create, update or publish them. The tools the agent sees depend on the token's abilities, so a view-only token gives it no way to write at all.

Does the live site update as soon as someone clicks Publish?

Not instantly. Publishing in Diggama fires a workflow that calls the Cloudflare deploy hook, which queues a new build; the change is live when that build finishes. Records scheduled for a future date trigger a rebuild within about five minutes of their publication time.

Do I have to host on Cloudflare?

No. The architecture works on any host that can run a build and expose a deploy hook URL, including Vercel and Netlify. This series uses Cloudflare Workers because Workers Builds offers deploy hooks with built-in deduplication and the same platform runs the server endpoint for the contact form.

Keep reading

Related guides.

Your site, written by AI. Your content, managed in Diggama.

Model your content once, fetch it from Astro, Next.js or plain HTML, and let your team edit it without touching the code.

No credit card required · 3 months free trial · Cancel anytime