Skip to content

Admin UI

The admin renders from your schema at runtime. There are no generated page files: add a field and it appears.

Configure which columns appear in the list view via views in the collection definition:

defineCollection({
slug: "posts",
views: {
list: {
columns: ["title", "category", "_status", "_updatedAt"],
defaultSort: { field: "_updatedAt", direction: "desc" },
},
},
fields: { ... },
});

Fields go to the content area by default. Set admin.position: "sidebar" to place a field in the sidebar:

fields: {
title: fields.text({ required: true }), // → content
body: fields.richText(), // → content
slug: fields.slug({ admin: { position: "sidebar" } }), // → sidebar
category: fields.text({ admin: { position: "sidebar" } }), // → sidebar
}
Position Description
"content" Main area (left column on desktop), default
"sidebar" Side panel (right column on desktop)

Set admin.group to render fields inside a titled panel in the edit form. Consecutive fields sharing the same group become one panel; fields without a group render loose, as before. Grouping only changes the form; field order and storage are unchanged.

fields: {
heroHeading: fields.text({ label: "Heading", admin: { group: "Hero" } }),
statValue: fields.text({ label: "Value", admin: { group: "Stats" } }),
statLabel: fields.text({ label: "Label", admin: { group: "Stats" } }),
notes: fields.text(), // ungrouped
}

Combine groups with short labels to turn a long flat form (for example a fixed-slot landing page with many sections) into labeled panels instead of a wall of prefixed field names.

The object form makes a panel collapsible (rendered as a native <details> element). collapsible: true starts open, "collapsed" starts closed. Once an editor toggles a panel, the browser remembers that state per collection and group (localStorage), overriding the schema default on later visits. Fields in the same run may mix the string and object forms; the first collapsible declaration wins:

fields: {
statValue: fields.text({ admin: { group: { label: "Stats", collapsible: true } } }),
statLabel: fields.text({ admin: { group: "Stats" } }),
archiveNote: fields.text({ admin: { group: { label: "Archive", collapsible: "collapsed" } } }),
}

Collections with pathPrefix get a Preview link automatically. For collections without a prefix, add preview: true. For singletons, set preview to the URL:

// Automatic: pathPrefix enables preview
defineCollection({ slug: "posts", pathPrefix: "blog", ... });
// Explicit: no pathPrefix, needs opt-in
defineCollection({ slug: "pages", preview: true, ... });
// Singleton: set the URL directly
defineCollection({ slug: "front-page", singleton: true, preview: "/", ... });

The Preview link opens the public page in a new tab, showing draft content. While that tab is open, edits in the form update it live without saving. The page needs data-cms attributes on the elements it renders; see Live preview.

Create a React component in src/cms/fields/ and reference it by name:

// In your collection definition
color: fields.text({
admin: { component: "ColorPicker" },
});
src/cms/fields/ColorPicker.tsx
import type { CustomFieldProps } from "@kidecms/core";
export default function ColorPicker({
name,
value,
readOnly,
}: CustomFieldProps) {
return (
<input
type="color"
name={name}
defaultValue={value || "#000000"}
disabled={readOnly}
/>
);
}

The component receives name (form field name), field (field config), value (serialized value), and readOnly — the CustomFieldProps type. It renders with client:load and must include an input with the name prop so the form can read its value.

The integration scans src/cms/fields/ for .tsx files and registers each file’s default export under its file name, so ColorPicker.tsx is admin.component: "ColorPicker". Adding a file needs a dev-server restart; the directory is optional and can be absent.

Built-in admin.component variants, by field type:

Field type Component Renders
select "radio" Options as radio buttons instead of a dropdown
text "taxonomy-select" Term picker fed by a taxonomy — see Taxonomies
text "color" Palette swatch picker (what fields.color() sets)
json "repeater" Sortable rows; typed with itemFields — see Fields
json "link" Internal/external link control (what fields.link() sets)
json "menu-items" Nested menu tree editor — see Menus
json "taxonomy-terms" Nested term tree editor — see Taxonomies

Any other name is looked up in src/cms/fields/.

Logged-in editors see an “Edit this page” chip in the corner of public pages that links straight to the document’s edit form. It is on by default; turn it off with admin.editBar: false.

A page opts in by marking the document it renders with data-cms-doc="<collection>:<id>" — typically on <body> in the layout:

<body data-cms-doc={`pages:${doc._id}`}>

Anonymous visitors make no extra requests: the client only calls /api/cms/edit-bar when a kide-editor hint cookie (set on admin visits) is present, and the endpoint checks the session and read access. The chip renders in a shadow root, so site CSS doesn’t affect it.

Set AI_PROVIDER=openai and AI_API_KEY to enable AI buttons in the admin (AI_MODEL defaults to gpt-4o-mini; openai is the only provider):

  • Alt text on asset detail pages
  • SEO descriptions on edit forms
  • Translation: per-field “Translate from EN” buttons, for plain and rich text

Without these variables the buttons are hidden and no AI code loads.

Add custom pages to the admin sidebar via admin.nav in your CMS config:

export default defineConfig({
admin: {
nav: [
{ label: "Dashboard", href: "/dashboard", icon: "Home", weight: 10 },
{ label: "Analytics", href: "/analytics", icon: "BarChart", weight: 20 },
{ label: "Settings", href: "/settings", icon: "Settings" },
],
},
collections: [...],
});
Option Type Description
label string Display text in the sidebar
href string Link URL
icon string Lucide icon name (optional, defaults to grid)
weight number Sort order within the Custom group (optional, default 50)

The sidebar sorts by group first, then by weight within each group. Groups render in a fixed order:

Order Group Contents
0 Content Content collections
10 Library Assets and related library items
20 Team Users and team management
100 Custom Your admin.nav items

Custom nav items always land in the Custom group, after every built-in group; weight only orders them within it.

Collections set their own placement with the collection-level admin options; see Admin sidebar.

The linked pages are regular Astro pages you create in your app. To use the admin layout, import it from @kidecms/core:

---
import AdminLayout from "@kidecms/core/admin/layouts/AdminLayout.astro";
---
<AdminLayout title="Analytics | Admin">
<h1>Analytics</h1>
<!-- your content -->
</AdminLayout>

Available icons: BarChart, Bell, Bookmark, Calendar, Clock, Database, FileText, FolderTree, Globe, Home, Image, Inbox, Key, Layers, LayoutGrid, Link, Link2, Lock, Mail, Menu, MessageSquare, Package, Palette, PencilRuler, Search, Settings, Shield, Star, Tag, Terminal, Users, Zap.

These options go under admin in cms.config.ts. Every admin option is listed in Configuration.

export default defineConfig({
admin: {
uploads: {
allowedTypes: ["image/jpeg", "image/png", "image/webp", "application/pdf", "application/zip"],
maxFileSize: 100 * 1024 * 1024, // 100 MB
},
rateLimit: {
maxAttempts: 10,
windowMs: 5 * 60 * 1000, // 5 minutes
},
},
collections: [...],
});
Option Type Default Description
allowedTypes string[] Images, PDF, MP4, WebM Allowed MIME types
maxFileSize number 52428800 (50 MB) Max file size in bytes

Default allowed types: image/jpeg, image/png, image/gif, image/webp, image/avif, application/pdf, video/mp4, video/webm.

SVG is not allowed by default because it can run script. Read Uploads and files before adding it to allowedTypes.

Option Type Default Description
maxAttempts number 5 Login attempts before blocking
windowMs number 900000 Time window in ms (default 15 min)

List views page at a fixed 10 items; the page size is not configurable.

admin.colors is the palette offered by every color field; see Colors.

Control how dates and times display throughout the admin.

Option Type Default Description
dateFormat string "en-US" BCP-47 locale for date/time display (e.g. "en-GB", "fi-FI")
timeZone string browser IANA time zone (e.g. "Europe/Helsinki"). Overrides each viewer’s zone
dateTimeFormat Intl.DateTimeFormatOptions Overrides merged over the defaults (numeric date + 2-digit HH:mm)
dateTimePattern string Explicit token pattern; wins over dateFormat/dateTimeFormat
admin: {
dateFormat: "fi-FI",
timeZone: "Europe/Helsinki",
dateTimePattern: "d.M.yyyy HH:mm", // → 1.7.2026 14:30
}

dateFormat is a locale, not a pattern — it controls ordering and 12/24-hour conventions. For finer control use dateTimeFormat (e.g. { hour12: false } for 24-hour time, { second: "2-digit" } to show seconds) or dateTimePattern for an exact layout.

Pattern tokens: yyyy yy · MM M · dd d · HH H (24-hour) · hh h (12-hour) · mm m · ss s · a (AM/PM). Wrap literal text in single quotes:

Pattern Output
d.M.yyyy HH:mm 1.7.2026 14:30
dd.MM.yyyy 'klo' HH:mm 01.07.2026 klo 14:30
h:mm a 2:30 PM

timeZone still applies to pattern output.

All settings are optional — defaults apply when omitted.