agntcms
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.

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 — the SectionDefinition export via defineSection({ ... }).

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 from public/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.ts without 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.
SubpathPurposeMain exports
@agntcms/nextRoot import for declaring section schemas.defineSection, TextField, RichTextField, ImageField, VideoField, LinkField, ReferenceField, NumberField, BooleanField, SelectField, ButtonField, ListField, types ImageValue, LinkValue, hrefOf, isExternalLink
@agntcms/next/serverServer-side runtime. Import in server components.getContent, getGlobal, PageRenderer, SectionRenderer, GlobalSlot
@agntcms/next/clientClient-side runtime. Import in section components ('use client').EditableText, EditableRichText, EditableImage, EditableLink, EditableButton, read, isSlotInPreview, type EditableSlot
@agntcms/next/handlersRoute handlers for admin / agent API. Used only inside the frozen zone.createAdminHandler, createAgentHandler
@agntcms/next/configProject configuration.defineConfig

agntcms/config.ts#


import { defineConfig } from '@agntcms/next/config'
import { Hero } from './sections/Hero'
// ...

export default defineConfig({
  sections: [Hero, /* ... */],
})

KeyTypePurpose
sectionsSectionDefinition[]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#

FieldImportJSON valuePreview behaviour
TextFieldconststringinline edit, click-into-field
RichTextFieldconststring (markdown)inline edit with a markdown modal
ImageFieldconstImageValue ({ filename, alt, ... })click → upload plus alt text
VideoFieldconstVideoValuevideo upload
LinkFieldconstLinkValue ({ href, label, external? })inline edit for label, separate dialog for href
ReferenceFieldconst{ slug }page picker from the list
NumberFieldconstnumberinline number input
BooleanFieldconstbooleantoggle
SelectField(options)factorystring (one of options)dropdown
ButtonField(opts)factory{ label, action }edited as a button with an action
ListField(schema)factoryarray of items by schemalist editor with insert / delete / reorder

Value-type details — through TypeScript IntelliSense on the types from @agntcms/next.