See Agility CMS in action. Watch a product demo
Building Apps with AI Coding Tools
You can build a full Agility CMS app in a single sitting with an AI coding tool. Not a toy. A real, deployable integration with custom fields, modals, API calls, and proper error handling.
I know because I did it. The CommerceTools App for Agility CMS was built almost entirely with Claude Code. Product picker field, search, infinite scroll, the whole thing. It's in production.
This guide walks you through how to do the same for your own integration. I'll reference Claude Code because that's what I used, but the approach works with any AI coding tool: Cursor, GitHub Copilot, Codex, Windsurf, whatever you prefer. The underlying principle is the same. Give the AI the right context about the Agility Apps SDK, and it will build production-quality code.
What You're Building
An Agility App is a Next.js micro-site that runs inside iframes in the CMS. It uses the @agility/app-sdk to communicate with the host CMS. Your app can provide custom fields, sidebars, dashboards, modals, and more.
The key architectural pieces are:
- A
/.well-known/agility-app.jsonmanifest file that tells Agility what your app can do - React components for each "surface" (field, sidebar, modal, etc.) using the Agility Apps SDK
- API routes for any server-side work (auth, third-party API calls)
- A deployment target (Vercel, Netlify, whatever you want)
Step 1: Write Your AGENTS.md
Every AI coding tool reads a project instruction file at the start of each session. The tool-specific file names differ (Claude Code reads CLAUDE.md, Cursor reads .cursorrules, Copilot reads .github/copilot-instructions.md), but there's now an open standard that works across almost all of them: AGENTS.md.
AGENTS.md is stewarded by the Linux Foundation's Agentic AI Foundation. It's supported natively by GitHub Copilot, Cursor, Windsurf, Codex, Google Jules, Gemini CLI, and a growing list of others. Claude Code reads CLAUDE.md by default, but you can configure it to fall back to AGENTS.md, or just symlink one to the other.
The point is: write it once, and every tool your team uses gets the same context.
The AGENTS.md for Agility CMS Apps
Here's what I'd recommend for any Agility CMS app project. Drop this in your project root:
# Agility CMS App
This is an Agility CMS App built with Next.js and the @agility/app-sdk v2.
It integrates [YOUR SERVICE] with Agility CMS.
## Tech Stack
- Next.js (App Router)
- TypeScript
- Tailwind CSS
- @agility/app-sdk v2
- @agility/plenum-ui (for consistent CMS styling)
## Agility Apps SDK Patterns
### App Definition
The app manifest lives at `public/.well-known/agility-app.json`.
It defines capabilities (fields, modals, sidebars), config values, and connections.
Only include the capabilities the app actually implements.
### Surface Routing
Each SDK surface maps to a file path:
- Custom fields: `app/fields/{field-name}/page.tsx`
- Modals: `app/modals/{modal-name}/page.tsx`
- Content item sidebar: `app/content-item-sidebar/page.tsx`
- Content list sidebar: `app/content-list-sidebar/page.tsx`
- Page sidebar: `app/page-sidebar/page.tsx`
- Home dashboard: `app/home-dashboard/page.tsx`
- Content dashboard: `app/content-dashboard/page.tsx`
- Pages dashboard: `app/pages-dashboard/page.tsx`
### Core SDK Hooks and Methods
- Always use `useAgilityAppSDK()` hook for initialization
- Always check `initializing` before rendering anything
- Use `useResizeHeight()` on every surface to auto-size the iframe
- Use `contentItemMethods.setFieldValue()` to update field values
- Use `openModal()` / `closeModal()` for modal interactions
- Field values are stored as serialized JSON strings
- Clean up field listeners on unmount
### API Routes
Server-side logic goes in `pages/api/` or `app/api/` route handlers.
API credentials must never be exposed to the client.
Proxy all third-party API calls through your own routes.
## Build & Test
- `npm run dev` to start local development
- `npm run build` to build for production
## Coding Standards
- TypeScript strictly
- Tailwind for all styling
- No inline styles
- Keep components small and focused
- Use SWR or React Query for data fetching
Why This Matters
Without this file, your AI tool doesn't know that Agility Apps use a specific file routing convention. It doesn't know that field values need to be serialized as JSON strings. It doesn't know about useResizeHeight(). You'll spend half your session correcting things that should have been right from the start.
The ETH Zurich study on agent instruction files found that the highest-value content is stuff the agent can't discover on its own: custom commands, non-obvious conventions, and things that look wrong to an outsider but are intentional. The Agility Apps SDK routing convention (app/fields/{name}/page.tsx) is exactly that kind of thing. An agent can read your package.json and figure out you're using Next.js. It can't figure out the Agility-specific file path convention without being told.
Making It Work with Claude Code
If you're using Claude Code specifically, you have two options:
Option A: Symlink. Create your AGENTS.md as the source of truth, then symlink it:
ln -s AGENTS.md CLAUDE.md
Option B: Use both files. Keep AGENTS.md for the universal stuff and add a CLAUDE.md with Claude Code-specific instructions (hooks, MCP server config, etc.). Claude Code loads both.
Step 2: Scaffold with Your AI Tool
Open your AI coding tool and give it a clear, specific prompt. Here's a real example based on what I did for the CommerceTools app:
Create an Agility CMS app that integrates with [YOUR SERVICE].
The app should provide:
1. A custom field that lets editors pick a [resource] from [SERVICE]
2. A modal for browsing/searching [resources]
3. Server-side API routes that proxy requests to [SERVICE API]
The custom field should:
- Show a "Browse [Resources]" button when empty
- Display the selected [resource] details (image, name, ID) when populated
- Store the selected [resource] as a JSON string in the field value
- Allow removing the selection
The modal should:
- Fetch and display [resources] from [SERVICE API]
- Support search
- Support pagination or infinite scroll
- Let the user click a [resource] to select it
Start with the agility-app.json manifest, then build the field,
then the modal, then the API routes.
Your AI tool will generate the file structure, the manifest, the components, and the API routes. It will follow the patterns from your AGENTS.md.
Step 3: Iterate in Small Chunks
Don't try to build everything in one massive prompt. Work in focused iterations:
- Manifest + field component first. Get something rendering in the CMS.
- Modal next. Get the browse/search UI working.
- API integration after that. Wire up the real third-party API.
- Polish: error states, loading indicators, edge cases.
In Claude Code, use /clear between chunks to reset the context. In Cursor, start a new Composer session. In Copilot, open a fresh chat thread. The principle is the same: each chunk starts clean, with the codebase and your AGENTS.md as the only context.
The Real Workflow
This is what it actually looked like when I built the CommerceTools app:
Session 1: "Create the agility-app.json and the basic field component"
Session 2: "Build the product selection modal with infinite scroll"
Session 3: "Add the CommerceTools API client and product listing endpoint"
Session 4: "Wire everything together and add search support"
Session 5: "Add error handling, loading states, and the remove product action"
Each session produces a working increment. Each session starts clean. Five sessions, one deployable app.
Step 4: The App Manifest
This is the file Agility reads to understand your app. Your AI tool will generate it, but you should understand the structure so you can review it.
Here's what the CommerceTools app manifest looks like:
{
"name": "Agility Commerce Tools App",
"version": "1.0.0",
"__sdkVersion": "2.0.0",
"configValues": [
{
"label": "Project Key",
"name": "projectKey",
"type": "string"
},
{
"label": "Client ID",
"name": "clientId",
"type": "string"
},
{
"label": "Client Secret",
"name": "clientSecret",
"type": "string"
}
],
"capabilities": {
"fields": [
{
"name": "commercetools-product",
"label": "Commerce Tools Product",
"description": "A product from your Commerce Tools account."
}
],
"modals": [
{
"name": "select-commercetools-product",
"label": "Select Product",
"description": "Browse and select a product."
}
]
}
}
The configValues define what admins enter when they install the app (API keys, project settings, etc.). The capabilities define what surfaces your app provides. You only include what you need. If you're building a sidebar-only app, you don't need fields or modals in here.
Step 5: Project Structure
Your AI tool will generate something close to this. This is the actual structure of the CommerceTools app:
.
├── AGENTS.md # AI coding instructions
├── app/
│ ├── fields/
│ │ └── commercetools-product/
│ │ └── page.tsx # The custom field UI
│ ├── modals/
│ │ └── select-commercetools-product/
│ │ └── page.tsx # The product picker modal
│ └── app-details/
│ └── page.tsx # App info page
├── components/
│ ├── ProductListing.tsx
│ ├── ProductRow.tsx
│ ├── EmptySection.tsx
│ └── Loader.tsx
├── hooks/
│ └── useProductDetails.ts
├── lib/
│ ├── commercetools-client.ts
│ └── getProductListing.ts
├── pages/api/ # API proxy routes
├── types/
│ └── Product.ts
├── public/
│ └── .well-known/
│ └── agility-app.json
├── DOCUMENTATION.md # User-facing docs
├── README.md # Developer docs
└── package.json
The file path conventions matter. app/fields/{field-name}/page.tsx maps directly to the name property in your manifest's capabilities.fields array. Same for modals, sidebars, and dashboards. This is the kind of thing your AGENTS.md needs to spell out explicitly.
Step 6: Key SDK Patterns to Know
These are the patterns your AI tool needs to get right. If you have them in your AGENTS.md, it will. If you don't, you'll be correcting it.
Field Component Pattern
"use client"
import {
useAgilityAppSDK,
contentItemMethods,
useResizeHeight,
openModal
} from "@agility/app-sdk"
export default function MyField() {
const { initializing, fieldValue, appInstallContext } = useAgilityAppSDK()
const containerRef = useResizeHeight(10)
if (initializing) return null
const parsedValue = fieldValue ? JSON.parse(fieldValue as string) : null
return (
<div ref={containerRef}>
{parsedValue ? (
<div>
{/* Display selected resource */}
<button onClick={() =>
contentItemMethods.setFieldValue({ value: "" })
}>
Remove
</button>
</div>
) : (
<button onClick={() => openModal({
name: "my-modal",
title: "Select Resource",
props: { config: appInstallContext?.configuration },
callback: (result) => {
if (result) {
contentItemMethods.setFieldValue({
value: JSON.stringify(result)
})
}
}
})}>
Browse
</button>
)}
</div>
)
}
Modal Pattern
"use client"
import { useAgilityAppSDK, closeModal } from "@agility/app-sdk"
export default function SelectResourceModal() {
const { initializing, modalProps } = useAgilityAppSDK()
if (initializing) return null
const handleSelect = (resource: any) => {
closeModal({
id: resource.id,
name: resource.name,
// whatever data the field needs
})
}
return (
<div>
{/* Browse/search UI here */}
{/* Call handleSelect when user picks something */}
</div>
)
}
API Route Pattern
// pages/api/resources.ts
import type { NextApiRequest, NextApiResponse } from "next"
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { clientId, clientSecret, projectKey } = req.query
// Authenticate with third-party API
// Fetch resources
// Return results
res.status(200).json({ results })
}
Step 7: Testing Locally
Agility apps run inside iframes. You can't use http://localhost:3000 directly. See our local development guide for two workarounds: ngrok for new apps, and hosts file spoofing for existing apps.
Step 8: Deploy and Register
Deploy to Vercel (or wherever you host). Then register the app in Agility:
- Go to your Org settings in Agility
- Navigate to Apps
- Add a new Private App
- Enter your deployed URL
- Agility reads your
/.well-known/agility-app.jsonand registers the capabilities
Tips From Building the CommerceTools App
Let the AI write your types first. Define your types/Product.ts (or whatever your resource is) early. AI coding tools write much better components when they have concrete types to work with.
Use @agility/plenum-ui for styling. It gives your app the same look and feel as the rest of the CMS. Mention it in your AGENTS.md and your tool will use it.
Keep API credentials server-side. The configValues from the app manifest are available client-side via appInstallContext.configuration. That's fine for non-sensitive config. But API secrets should be proxied through your API routes, not sent to the browser.
Store the minimum viable data in the field value. The CommerceTools app stores the product ID, name, SKU, image URL, and a few other fields as JSON. Not the entire product object. Your frontend can use the ID to fetch fresh details at render time.
Use SWR or React Query for data fetching in modals. The CommerceTools app uses SWR. It makes caching, revalidation, and infinite scroll pagination much cleaner. Any AI coding tool will wire this up correctly if you ask for it.
Write a DOCUMENTATION.md alongside your README. The README is for developers. The DOCUMENTATION.md is for Agility users who install the app. Your AI tool will generate both if you ask. The CommerceTools app has both.
Write it yourself. An ETH Zurich study found that AI-generated instruction files actually reduced task success rates by about 3% and increased inference costs by over 20%. The value of your AGENTS.md comes from encoding what YOU know about the project. The stuff that's obvious to you but invisible to a tool reading the code for the first time.
The Bottom Line
The Agility Apps SDK is well-structured, the patterns are consistent, and the surface area is bounded. That's exactly the kind of problem AI coding tools excel at.
The key is the instruction file. Write an AGENTS.md with the right Agility-specific context and any tool, Claude Code, Cursor, Copilot, whatever, will build a production-quality app. Skip it and you'll fight the tool instead of using it.
Get Started
- Agility Apps SDK docs
- App Types in Agility
- CommerceTools app source code (a real working example)
- Example SDK v2 app
- AGENTS.md specification