Components
Define reusable field groups that can be shared across collections and singles.
Components are reusable field-group templates. Define a set of fields once as a component, then embed it in any number of collections or singles. Each usage creates a separate data instance — components are schemas, not shared documents.
Source of truth: the
ComponentConfiginterface lives inpackages/nextly/src/components/config/types.ts. ThedefineComponent()helper is inpackages/nextly/src/components/config/define-component.ts.
Key characteristics:
- Templates, not documents — components define a field structure; each embed creates its own data row.
- Own database table — each component type gets a table prefixed with
comp_(e.g.comp_seo). - Nesting — a component's fields can include
componentfields referencing other components (max depth: 3 levels). - Slug uniqueness — component slugs must be unique across components, collections, and singles.
- Dual creation — define in code with
defineComponent()or visually in the Visual Schema Builder.
Use defineComponent() from nextly to create components in TypeScript:
import {
defineComponent,
text,
textarea,
upload,
} from "nextly";
export default defineComponent({
slug: "seo",
label: { singular: "SEO Metadata" },
admin: {
category: "Shared",
icon: "Search",
description: "Search engine optimization metadata",
},
fields: [
text({ name: "metaTitle", required: true, label: "Meta Title" }),
textarea({
name: "metaDescription",
label: "Meta Description",
maxLength: 160,
}),
upload({ name: "ogImage", relationTo: "media", label: "OG Image" }),
text({ name: "canonicalUrl", label: "Canonical URL" }),
],
});Components can also be created visually in the admin UI:
- Navigate to Components in the sidebar.
- Click Create Component.
- Name it and add fields using the drag-and-drop Schema Builder.
- Save — the component is immediately available for use.
Builder-created components work identically to code-defined ones; both produce the same database schema and API behavior.
Component options
Only slug and fields are required.
| Option | Type | Required | Description |
|---|---|---|---|
slug | string | Yes | Unique identifier across components, collections, and singles. Used as the DB table prefix (comp_{slug}). Must be URL-friendly. |
fields | FieldConfig[] | Yes | Array of field definitions. |
label | { singular: string } | No | Display name in the admin UI. Auto-generated from slug if omitted (e.g. social-link → Social Link). |
admin.category | string | No | Category that groups components in the sidebar and selector modal (e.g. "Shared", "Blocks"). |
admin.icon | string | No | Lucide icon name shown in sidebar and component selector. |
admin.description | string | No | Help text in the component selector modal. |
admin.hidden | boolean | No | Hide from admin navigation. Still usable in code and via API. |
admin.imageURL | string | No | Preview image URL shown in the component selector. |
dbName | string | No | Custom database table name (overrides comp_{slug}). |
description | string | No | General description; falls back to admin.description. |
custom | Record<string, unknown> | No | Arbitrary metadata for plugins or custom code. Not persisted. |
Components do not have hooks or access control of their own. They inherit hook execution and access checks from the collection or single they're embedded in.
Using components in collections and singles
Once defined, embed a component in any collection or single using the component field helper. There are three usage modes.
Single component (fixed type)
Embed exactly one instance of a specific component type:
import { defineCollection, component, text, richText } from "nextly";
export default defineCollection({
slug: "pages",
fields: [
text({ name: "title", required: true }),
richText({ name: "content" }),
component({ name: "seo", component: "seo" }),
],
});Dynamic zone (multiple component types)
Let editors choose from several component types — ideal for flexible page builders:
import { defineCollection, component, text } from "nextly";
export default defineCollection({
slug: "pages",
fields: [
text({ name: "title", required: true }),
component({
name: "layout",
components: ["hero", "cta", "content-block", "image-gallery"],
repeatable: true,
}),
],
});Repeatable single component
An array of the same component type — for example, a list of feature cards:
import { defineCollection, component, text } from "nextly";
export default defineCollection({
slug: "landing-pages",
fields: [
text({ name: "title", required: true }),
component({
name: "features",
component: "feature-card",
repeatable: true,
minRows: 1,
maxRows: 12,
}),
],
});The component field's full option reference lives in Fields → Component.
Example: hero section component
import {
defineComponent,
text,
upload,
select,
option,
} from "nextly";
export default defineComponent({
slug: "hero",
label: { singular: "Hero Section" },
admin: {
category: "Blocks",
icon: "Image",
description: "Full-width hero banner with heading and CTA",
},
fields: [
text({ name: "heading", required: true, label: "Heading" }),
text({ name: "subheading", label: "Subheading" }),
upload({
name: "backgroundImage",
relationTo: "media",
label: "Background Image",
}),
text({ name: "ctaText", label: "CTA Button Text" }),
text({ name: "ctaLink", label: "CTA Button Link" }),
select({
name: "alignment",
label: "Content Alignment",
options: [option("Left"), option("Center"), option("Right")],
defaultValue: "center",
}),
],
});Component nesting
Components can embed other components using the component field type, up to 3 levels deep. defineConfig() rejects circular references and configurations that exceed the depth limit at startup.
import { defineComponent, text, component } from "nextly";
export default defineComponent({
slug: "faq-item",
label: { singular: "FAQ Item" },
fields: [
text({ name: "question", required: true }),
text({ name: "answer", required: true }),
component({ name: "cta", component: "cta" }),
],
});Next steps
- Fields — all field types available inside components
- Collections — where components are most commonly used
- Singles — embed components in single-document content