selectField
A dropdown with one choice. Options can be a fixed list, rows from any table, or items from another section.
import { selectField } from 'nextjs-cms/core/fields'A select gets its options from exactly one of three sources:
| Source | Option | Column | Use for |
|---|---|---|---|
| Static list | options | ENUM(...) | Statuses, types, sizes: values known in advance |
| Another section | section | INT or VARCHAR, matching that section's identifier | Categories, authors, brands managed in the CMS |
| A lookup table | db | INT or VARCHAR | Lists maintained outside the CMS (countries, currencies) |
Basic usage
selectField({
name: 'status',
options: [
{ value: 'draft', label: 'Draft' },
{ value: 'published', label: 'Published' },
],
})With every option
selectField({
name: 'brand_id',
label: { en: 'Brand', ar: 'العلامة التجارية' },
required: true,
order: 3,
localized: false,
conditionalRules: [{ field: hasBrand, condition: 'equals', value: true }],
// one source:
section: brandsSection,
// options: [...],
// db: { table, identifier, label, orderBy, identifierType, labelType },
checkValueExists: true,
destinationDb: undefined, // or { table, itemIdentifier, selectIdentifier }
})Options
| Option | Type | Default | Description |
|---|---|---|---|
options | { value: string | number; label: string }[] | none | Static options (at least one). |
section | a has-items or category section | none | Use another section's items as options. |
db | { table, identifier, label, orderBy?, identifierType?, labelType? } | none | Read options from a table. |
checkValueExists | boolean | false | On save, verify the submitted value is a real option. |
destinationDb | { table, itemIdentifier, selectIdentifier } | none | Store the value in a join table instead of a column. |
defaultValue | string | number | none | Pre-selected value. |
Plus the common options. Exactly one of options, section, or db is required; TypeScript rejects combinations.
Options in detail
options: static list
selectField({
name: 'size',
required: true,
defaultValue: 'm',
options: [
{ value: 's', label: 'Small' },
{ value: 'm', label: 'Medium' },
{ value: 'l', label: 'Large' },
],
})The column is an ENUM('s', 'm', 'l'), so MySQL itself rejects anything else. Adding an option later changes the ENUM: run update-sections. Removing an option that rows still use makes the ALTER fail until you update those rows.
Labels are plain strings. Store the stable value and translate labels on your website if needed.
section: another section's items
import brandsSection from './brands.section'
selectField({ name: 'brand_id', label: 'Brand', section: brandsSection })- Options are the rows of
brands: the label is itsheadingField, the value its identifier. - Order follows the section's
orderByField(or its identifier). - Column matches the identifier type:
INTfor the defaultid,VARCHAR(255)for a text identifier. - Works with has-items and category sections. For nested categories the form shows one dropdown per level; pair it with
destinationDbto store every level.
Don't point db at a table owned by another section. update-sections refuses it and tells you to use section instead.
db: a lookup table
selectField({
name: 'country_code',
db: {
table: 'countries',
identifier: 'code', // stored value
label: 'name', // shown text
orderBy: 'name',
identifierType: 'text', // 'text' (default) | 'number'
labelType: 'text',
},
})| Sub-option | Description |
|---|---|
table | Table with the options. |
identifier | Column stored as the value. |
label | Column shown to editors. |
orderBy | Column to sort options by. Optional. |
identifierType | 'text' or 'number'. Sets the lookup table's identifier column type and this field's column type (VARCHAR(255) or INT). Default 'text'. |
labelType | 'text' or 'number'. Type of the label column. Default 'text'. |
update-sections creates the lookup table if it doesn't exist (with the identifier, label, and orderBy columns), so you can fill it with a seed script or SQL. It's not editable in the admin; if editors should manage the list, make it a section and use section instead.
checkValueExists
By default, the server trusts that the submitted value came from the dropdown. With checkValueExists: true, it verifies the value exists among the options before saving, and rejects tampered or stale values:
selectField({ name: 'plan_id', section: plansSection, checkValueExists: true })Static options are already protected by the ENUM column. Turn this on for section and db selects where a wrong id would silently corrupt data.
destinationDb
Stores the selection in a separate table with one row per value, and gives the section's table no column for this field.
selectField({
name: 'category',
section: categoriesSection,
destinationDb: {
table: 'product_category', // created by update-sections
itemIdentifier: 'product_id', // this item's identifier
selectIdentifier: 'category_id', // the chosen option's value
},
})For a single select, this mostly matters with nested categories (one row per chosen level). For many-to-many, see selectMultipleField and the Relationships guide.
Examples
Status with a default and dependent field
const status = selectField({
name: 'status',
required: true,
defaultValue: 'draft',
options: [
{ value: 'draft', label: 'Draft' },
{ value: 'scheduled', label: 'Scheduled' },
{ value: 'published', label: 'Published' },
],
})
const publishAt = dateField({
name: 'publish_at',
format: 'datetime',
required: true,
conditionalRules: [{ field: status, condition: 'equals', value: 'scheduled' }],
})Author picked from a team section
import teamSection from './team.section'
selectField({ name: 'author_id', label: 'Author', section: teamSection, required: true, checkValueExists: true })Numeric options
selectField({
name: 'rating',
options: [1, 2, 3, 4, 5].map((n) => ({ value: n, label: '★'.repeat(n) })),
})Storage
| Source | Column | Value |
|---|---|---|
options | ENUM('a','b',...) | The option's value |
section | INT or VARCHAR(255) | The item's identifier |
db | INT or VARCHAR(255) (from identifierType) | The identifier column's value |
with destinationDb | no column; rows in the destination table |