nextjs-cms

dateRangeField

A start date and an end date picked together, stored in two columns.

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

Basic usage

dateRangeField is the one field without a name. Name its two columns instead:

dateRangeField({ startName: 'starts_on', endName: 'ends_on' })

Editors pick a range in one control; the table gets two DATE columns, starts_on and ends_on.

With every option

dateRangeField({
    startName: 'sale_starts_at',
    endName: 'sale_ends_at',
    label: { en: 'Sale period', ar: 'فترة التخفيضات' },
    required: true,
    order: 5,
    conditionalRules: [{ field: onSale, condition: 'equals', value: true }],

    format: 'datetime',
    minDate: 'now',
    maxDate: new Date('2027-12-31'),
    defaultStartValue: new Date('2026-11-27T00:00:00Z'),
    defaultEndValue: new Date('2026-11-30T23:59:59Z'),
})

Options

OptionTypeDefaultDescription
startNamestringrequiredColumn for the start of the range.
endNamestringrequiredColumn for the end of the range.
format'date' | 'datetime''date'Column type for both columns.
timezonestringCMS dates.timezone, then UTCIANA timezone shared by both ends.
minDateDate | 'now'noneEarliest allowed value, applied to both ends.
maxDateDate | 'now'noneLatest allowed value, applied to both ends.
defaultStartValueDatenoneInitial start date.
defaultEndValueDatenoneInitial end date.

Plus the common options, except name (replaced by startName/endName) and defaultValue (replaced by the two defaults above). When label is omitted, it's generated from startName.

Both ends follow the date field timezone rules: date-only strings are preserved, datetime values use UTC storage and the configured zone for input/display, and defaults and bounds use that zone. Datetime client values are UTC ISO strings. The calendar preserves each existing endpoint's local time when its date changes; a new endpoint starts at midnight in the configured zone.

Options in detail

required

A required range needs both dates. A non-required range can be left empty (both columns NULL).

Referencing the field

Internally the field's name is "<startName>|<endName>". You don't normally need it, but it's what appears if you inspect the field config. Hook values contain the two real column names:

hooks: {
    beforeCreate: ({ values }) => {
        if (values.sale_starts_at && values.sale_ends_at && values.sale_ends_at < values.sale_starts_at) {
            throw new Error('The sale must end after it starts.')
        }
    },
}
Good to know:

The field checks each date against minDate and maxDate, but doesn't check that the end is after the start. Add a guard like the one above when it matters.

Examples

Event dates

dateRangeField({ startName: 'starts_at', endName: 'ends_at', format: 'datetime', required: true, label: 'When' })

Job application window

dateRangeField({ startName: 'opens_on', endName: 'closes_on', label: 'Application window', minDate: 'now' })

Currently active items on your website

import { and, gte, lte } from 'drizzle-orm'

const today = new Date().toISOString().slice(0, 10)

const activePromotions = await db
    .select()
    .from(PromotionsTable)
    .where(and(lte(PromotionsTable.startsOn, today), gte(PromotionsTable.endsOn, today)))

Storage

ColumnsTwo columns, startName and endName, DATE or DATETIME, nullable
ValuesUTC, YYYY-MM-DD or YYYY-MM-DD HH:mm:ss