nextjs-cms

selectMultipleField

Pick several options from a static list, a table, or another section. Stored as a list or as rows in a join table.

import { selectMultipleField } from 'nextjs-cms/core/fields'

Basic usage

selectMultipleField({
    name: 'features',
    options: [
        { value: 'wifi', label: 'Wi-Fi' },
        { value: 'parking', label: 'Parking' },
        { value: 'pool', label: 'Pool' },
    ],
})

Selected values are stored comma-separated in one column: wifi,pool.

With every option

selectMultipleField({
    name: 'collections',
    label: { en: 'Collections', ar: 'المجموعات' },
    required: true,
    order: 6,
    localized: false,
    conditionalRules: [{ field: isListed, condition: 'equals', value: true }],

    // one source:
    section: collectionsSection,
    // options: [...],
    // db: { table, identifier, label, orderBy, identifierType, labelType },

    checkValuesExist: true,
    destinationDb: {
        table: 'product_collections',
        itemIdentifier: 'product_id',
        selectIdentifier: 'collection_id',
    },
})

Options

OptionTypeDefaultDescription
options{ value, label }[]noneStatic options.
sectiona has-items or category sectionnoneAnother section's items as options.
db{ table, identifier, label, orderBy?, identifierType?, labelType? }noneOptions from a table. Same as selectField db.
checkValuesExistbooleantrueVerify every submitted value is a real option. On by default, unlike selectField.
destinationDb{ table, itemIdentifier, selectIdentifier }noneOne row per selected value in a join table. Recommended for relationships.

Plus the common options. Exactly one of options, section, or db.

Two storage modes

Without destinationDb: a comma-separated column

selectMultipleField({ name: 'features', options: [/* ... */] })
properties
id │ title          │ features
───┼────────────────┼──────────────────
 1 │ Seaside villa  │ wifi,pool,parking
 2 │ City studio    │ wifi

Simple, and fine for short static lists you only display. But the column is VARCHAR(255), so long selections can overflow it, and "all properties with a pool" needs FIND_IN_SET('pool', features), which can't use an index.

With destinationDb: a join table

selectMultipleField({
    name: 'features',
    options: [/* ... */],
    destinationDb: { table: 'property_features', itemIdentifier: 'property_id', selectIdentifier: 'feature' },
})
property_features
property_id │ feature
────────────┼─────────
 1          │ wifi
 1          │ pool
 1          │ parking
 2          │ wifi
  • update-sections creates the table. Its primary key is (itemIdentifier, selectIdentifier), and itemIdentifier has the same type as the section's identifier.
  • The section's own table gets no features column.
  • On every save, the item's rows are replaced with the current selection. Deleting the item deletes its rows.
  • If the field is localized, the join table gets a locale column and each language has its own selection.

This is the right choice for real relationships: tags, categories, related products.

destinationDb sub-options

Sub-optionDescription
tableJoin table name. Must not end in _locales.
itemIdentifierColumn holding this item's identifier (post_id).
selectIdentifierColumn holding the selected option's value (tag_id).

Examples

Blog posts in several topics

import topicsSection from './topics.section'

selectMultipleField({
    name: 'topics',
    section: topicsSection,
    required: true,
    destinationDb: { table: 'post_topics', itemIdentifier: 'post_id', selectIdentifier: 'topic_id' },
})

Query on your website:

import { eq } from 'drizzle-orm'

// All posts in topic 7
const rows = await db
    .select({ post: PostsTable })
    .from(PostTopicsTable)
    .innerJoin(PostsTable, eq(PostsTable.id, PostTopicsTable.postId))
    .where(eq(PostTopicsTable.topicId, 7))

Languages spoken (static, comma-separated)

selectMultipleField({
    name: 'languages',
    options: [
        { value: 'en', label: 'English' },
        { value: 'ar', label: 'Arabic' },
        { value: 'tr', label: 'Turkish' },
    ],
})
// Website: row.languages?.split(',') ?? []

Storage

ModeColumnValue
DefaultVARCHAR(255)Comma-separated values: a,b,c
destinationDbnoneOne row per value in the join table