Core / Creating sections
Creating sections
Creating a new section type is the most frequent developer operation. This page shows the recommended path through a skill, then what happens under the hood and how to do it by hand if needed.
Recommended path: ask Claude Code#
In Claude Code:
create a Testimonials section with fields: quote (richtext), author name (text), role (text), avatar (image).Or more directly:
run /agntcms-section-new — section Testimonials, fields: quote, author, role, avatar.The skill creates
agntcms/sections/Testimonials/ with three files, generates the section code from the schema, adds the import and registration to agntcms/config.ts, and asks about a preview image. After that the section is available in the admin and can be used on pages.This is the normal, recommended, daily path. Not for people who do not want to write code — for everyone.
The two-step ritual#
Under the hood the skill performs exactly two steps. They are worth knowing because sometimes you will want to do something non-standard.
Step 1: the folder
agntcms/sections/<Name>/ with three files:schema.ts— the field-type map.component.tsx— the React component.index.ts— theSectionDefinitionexport viadefineSection({ ... }).
Step 2: registration in
agntcms/config.ts — import plus an entry in the sections array.Step 1 alone: the section will not appear in the admin (the framework does not scan folders). Step 2 alone: TypeScript will not compile (the import does not resolve). The skill guarantees both steps land atomically.
A minimal example by hand#
A simple
Quote section with two fields — quote text and author.agntcms/sections/Quote/schema.ts:import { RichTextField, TextField } from '@agntcms/next'
export const schema = {
quote: RichTextField,
author: TextField,
}agntcms/sections/Quote/component.tsx:'use client'
import { EditableRichText, EditableText } from '@agntcms/next/client'
import type { EditableSlot } from '@agntcms/next/client'
interface Props {
readonly quote: EditableSlot<'richText', string>
readonly author: EditableSlot<'text', string>
}
export function QuoteComponent({ quote, author }: Props) {
return (
<blockquote className="border-l-4 border-ink-3 pl-6 py-4">
<EditableRichText field={quote} className="text-xl italic" />
<EditableText field={author} className="mt-3 text-sm text-ink-2" />
</blockquote>
)
}agntcms/sections/Quote/index.ts:import { defineSection } from '@agntcms/next'
import { schema } from './schema'
import { QuoteComponent } from './component'
export const Quote = defineSection({
name: 'Quote',
category: 'Content',
schema,
component: QuoteComponent,
})agntcms/config.ts — two changes:import { Quote } from './sections/Quote'
export default defineConfig({
sections: [
/* ...existing sections... */
Quote,
],
})Done. The section is available in the admin.
Slot — what it is and why#
The section component receives
props.quote not as a string but as EditableSlot<'richText', string>. The slot carries both the value and the metadata about where the field lives (page, section, path to the field).To render the value, wrap it in
EditableRichText, EditableText, EditableImage, or the matching component. For instance, <EditableRichText field={quote} />. If you need to read the value without rendering (for conditional logic, say), use read(field).If you try to use a slot as a plain string (
<div>{quote}</div>), TypeScript refuses. That is the compile-time guarantee that, in preview mode, the UI knows where every editable field lives and a click on the text actually works.Available field types#
Built into v0.5:
TextField— single-line text.RichTextField— markdown with inline formatting.ImageField— image frompublic/assets/.LinkField— link (URL plus label plus internal/external).ListField<T>— array of items of one type.SelectField<options>— choice from a fixed set.BooleanField,NumberField— standard.ReferenceField— link to another page in the project.
Details — Reference.
When to write by hand, when to use the skill#
Through
agntcms-section-new — almost always. Especially at the start, when you do not yet remember the shape of defineSection.By hand — only for non-standard component logic that is hard to explain to the agent on the first try (custom state, a specific animation, complex composition of nested slots). Even then it is cheaper to scaffold with the skill and edit the result.
Do not:
- Create only the folder without registering in
config.ts(the section will not appear in the admin). - Register in
config.tswithout the folder (TypeScript will not compile). - Look for a separate CLI to generate a section — there is none, and there will not be.
- Hard-code content in the component instead of using fields from the schema (it will not surface in the admin; the editor will not be able to change it).
@agntcms/next subpath exports#
Five public entry points. This is the public contract — changing any of these counts as a breaking change.
| Subpath | Purpose | Main exports |
|---|---|---|
@agntcms/next | Root import for declaring section schemas. | defineSection, TextField, RichTextField, ImageField, VideoField, LinkField, ReferenceField, NumberField, BooleanField, SelectField, ButtonField, ListField, types ImageValue, LinkValue, hrefOf, isExternalLink |
@agntcms/next/server | Server-side runtime. Import in server components. | getContent, getGlobal, PageRenderer, SectionRenderer, GlobalSlot |
@agntcms/next/client | Client-side runtime. Import in section components ('use client'). | EditableText, EditableRichText, EditableImage, EditableLink, EditableButton, read, isSlotInPreview, type EditableSlot |
@agntcms/next/handlers | Route handlers for admin / agent API. Used only inside the frozen zone. | createAdminHandler, createAgentHandler |
@agntcms/next/config | Project configuration. | defineConfig |
agntcms/config.ts#
import { defineConfig } from '@agntcms/next/config'
import { Hero } from './sections/Hero'
// ...
export default defineConfig({
sections: [Hero, /* ... */],
})| Key | Type | Purpose |
|---|---|---|
sections | SectionDefinition[] | The full registry of section types. Without an explicit entry, a section is not visible in the admin. |
In v0.5 this is the only required key. Storage and asset adapters default to the filesystem and do not require configuration.
Field types — full table#
| Field | Import | JSON value | Preview behaviour |
|---|---|---|---|
TextField | const | string | inline edit, click-into-field |
RichTextField | const | string (markdown) | inline edit with a markdown modal |
ImageField | const | ImageValue ({ filename, alt, ... }) | click → upload plus alt text |
VideoField | const | VideoValue | video upload |
LinkField | const | LinkValue ({ href, label, external? }) | inline edit for label, separate dialog for href |
ReferenceField | const | { slug } | page picker from the list |
NumberField | const | number | inline number input |
BooleanField | const | boolean | toggle |
SelectField(options) | factory | string (one of options) | dropdown |
ButtonField(opts) | factory | { label, action } | edited as a button with an action |
ListField(schema) | factory | array of items by schema | list editor with insert / delete / reorder |
Value-type details — through TypeScript IntelliSense on the types from
@agntcms/next.