You're reading docs for Nextly Alpha. APIs may change between releases.

Configuration

Field Groups

Define reusable field groups that can be shared across collections and singles.

Field groups are reusable field structures. Define a set of fields once as a field group, then embed it in any number of collections or singles. Each usage creates a separate data instance — field groups are schemas, not shared documents.

Source of truth: the FieldGroupConfig interface lives in packages/nextly/src/field-groups/config/types.ts. The defineFieldGroup() helper is in packages/nextly/src/field-groups/config/define-field-group.ts.

Key characteristics:

  • Templates, not documents — field groups define a field structure; each embed creates its own data row.
  • Own database table — each field group gets a table derived from its slug and prefixed with comp_ (e.g. comp_seo). The name is always derived; it cannot be overridden.
  • Nesting — a field group's fields can include component fields referencing other field groups (max depth: 3 levels).
  • Slug uniqueness — field group slugs must be unique across field groups, collections, and singles.
  • Dual creation — define in code with defineFieldGroup() or visually in the Visual Schema Builder.

Use defineFieldGroup() from nextly to create field groups in TypeScript:

src/field-groups/seo.ts
import {
  defineFieldGroup,
  text,
  textarea,
  upload,
} from "nextly";

export default defineFieldGroup({
  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" }),
  ],
});

Field groups can also be created visually in the admin UI:

  1. Navigate to Field Groups in the sidebar.
  2. Click Create Field Group.
  3. Name it and add fields using the drag-and-drop Schema Builder.
  4. Save — the field group is immediately available for use.

Builder-created field groups work identically to code-defined ones; both produce the same database schema and API behavior.

Field group options

Only slug and fields are required.

OptionTypeRequiredDescription
slugstringYesUnique identifier across field groups, collections, and singles. Used as the DB table prefix (comp_{slug}). Must be URL-friendly.
fieldsFieldConfig[]YesArray of field definitions.
label{ singular: string }NoDisplay name in the admin UI. Auto-generated from slug if omitted (e.g. social-linkSocial Link).
admin.categorystringNoCategory that groups field groups in the sidebar and selector modal (e.g. "Shared", "Blocks").
admin.iconstringNoLucide icon name shown in sidebar and field group selector.
admin.descriptionstringNoHelp text in the field group selector modal.
admin.hiddenbooleanNoHide from admin navigation. Still usable in code and via API.
admin.imageURLstringNoPreview image URL shown in the field group selector.
descriptionstringNoGeneral description; falls back to admin.description.
customRecord<string, unknown>NoArbitrary metadata for plugins or custom code. Not persisted.

Field groups 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 field groups in collections and singles

Once defined, embed a field group in any collection or single using the fieldGroup() field helper. There are three usage modes.

Single field group (fixed type)

Embed exactly one instance of a specific field group:

src/collections/pages.ts
import { defineCollection, fieldGroup, text, richText } from "nextly";

export default defineCollection({
  slug: "pages",
  fields: [
    text({ name: "title", required: true }),
    richText({ name: "content" }),
    fieldGroup({ name: "seo", component: "seo" }),
  ],
});

Dynamic zone (multiple field group types)

Let editors choose from several field group types, which suits flexible page builders:

src/collections/pages.ts
import { defineCollection, fieldGroup, text } from "nextly";

export default defineCollection({
  slug: "pages",
  fields: [
    text({ name: "title", required: true }),
    fieldGroup({
      name: "layout",
      components: ["hero", "cta", "content-block", "image-gallery"],
      repeatable: true,
    }),
  ],
});

Repeatable single field group

An array of the same field group, for example a list of feature cards:

src/collections/landing-pages.ts
import { defineCollection, fieldGroup, text } from "nextly";

export default defineCollection({
  slug: "landing-pages",
  fields: [
    text({ name: "title", required: true }),
    fieldGroup({
      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 field group

src/field-groups/hero.ts
import {
  defineFieldGroup,
  text,
  upload,
  select,
  option,
} from "nextly";

export default defineFieldGroup({
  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",
    }),
  ],
});

Field group nesting

Field groups can embed other field groups using the component field type, up to 3 levels deep. defineConfig() rejects circular references and configurations that exceed the depth limit at startup.

src/field-groups/faq-item.ts
import { defineFieldGroup, text, fieldGroup } from "nextly";

export default defineFieldGroup({
  slug: "faq-item",
  label: { singular: "FAQ Item" },
  fields: [
    text({ name: "question", required: true }),
    text({ name: "answer", required: true }),
    fieldGroup({ name: "cta", component: "cta" }),
  ],
});

Next steps

  • Fields — all field types available inside field groups
  • Collections — where field groups are most commonly used
  • Singles — embed field groups in single-document content