Show navigationHide navigation

Select

Dropdown (single choice) or checkbox list (multiple choices) from a fixed set of options. Use for categories, statuses, roles, and any constrained value.

Usage#

typescript
category: {
  label: 'Category',
  format: 'select',
  options: [
    { label: 'Design', value: 'design' },
    { label: 'Engineering', value: 'engineering' },
    { label: 'Marketing', value: 'marketing' },
  ] as const,
},

Use as const on the options array for literal type inference — query() will return 'design' | 'engineering' | 'marketing' instead of string.

Options#

OptionTypeDefaultDescription
optionsreadonly { label, value }[]Required. At least one option. Values must be unique.
multiplebooleanfalseEnable multiselect (checkbox list instead of dropdown)
requiredbooleanfalseMust have a selection
defaultOptionstringDefault value for new entries (single select only)
defaultOptionsstring[]Default values for new entries (multiselect only)
hintstringHelper text below the field

When single select is optional (not required), the dropdown includes a empty option.

Multiselect#

Set multiple: true for a checkbox list where editors can pick zero or more values:

typescript
features: {
  label: 'Features',
  format: 'select',
  multiple: true,
  options: [
    { label: 'Dark Mode', value: 'dark-mode' },
    { label: 'Search', value: 'search' },
    { label: 'RSS Feed', value: 'rss' },
    { label: 'Analytics', value: 'analytics' },
  ] as const,
  defaultOptions: ['search'],
},

Example: Blog post categories#

Schema — posts with a required category and optional feature tags:

typescript
// cms/octocms.config.ts
post: {
  label: 'Post',
  hasMany: true,
  fields: {
    title: { label: 'Title', format: 'string', entryTitle: true, required: true },
    category: {
      label: 'Category',
      format: 'select',
      required: true,
      options: [
        { label: 'Tutorial', value: 'tutorial' },
        { label: 'Case Study', value: 'case-study' },
        { label: 'News', value: 'news' },
      ] as const,
    },
    tags: {
      label: 'Tags',
      format: 'select',
      multiple: true,
      options: [
        { label: 'React', value: 'react' },
        { label: 'Next.js', value: 'nextjs' },
        { label: 'TypeScript', value: 'typescript' },
      ] as const,
    },
  },
},

Query + render:

tsx
// src/app/blog/page.tsx
import { query } from 'cms/__generated__/query';

export default async function BlogPage() {
  // Filter by category
  const tutorials = await query('post')
    .filter((p) => p.fields.category === 'tutorial')
    .toArray();

  // Filter by multiselect tag
  const reactPosts = await query('post')
    .filter((p) => p.fields.tags?.includes('react'))
    .toArray();

  return (
    <>
      <h2>Tutorials ({tutorials.length})</h2>
      {tutorials.map((p) => (
        <div key={p.sys.id}>
          <h3>{p.fields.title}</h3>
          <span className="badge">{p.fields.category}</span>
          {p.fields.tags?.map((tag) => (
            <span key={tag} className="tag">{tag}</span>
          ))}
        </div>
      ))}
    </>
  );
}

Storage#

Single select — plain string matching an option value:

json
{ "category": "tutorial" }

Multiselect — native string[] array:

json
{ "tags": ["react", "typescript"] }

Query result#

Single select: string. Multiselect: string[]. With as const on options, the type is a literal union (e.g. 'tutorial' | 'case-study' | 'news').