Skip to main content
← Back to Table of Contents

Summary

Weekly availability / opening-hours editor with per-day toggles, from/to time slots, optional break entries, copy-to-weekdays, searchable timezone selector, and locked days. From/To use the shared FLEX Select control. Default control size is sm; the timezone selector always renders at md.

Basic usage

Filament resource example:

State format

Day keys use three-letter English abbreviations: mon, tue, wed, thu, fri, sat, sun. Times are HH:MM strings in 24-hour format (09:00, 17:30). On save, invalid slot entries are dropped and times are zero-padded.

Minimal example

Split shift with lunch break

Overnight slots

Add "overnight": true when a work slot crosses midnight (e.g. 22:00 → 06:00). The UI exposes an Overnight toggle on work slots; overlap validation uses the same ScheduleV2 / interval-engine rules as PHP.
When timezone(null) hides the selector, omit the timezone key from state — it is not required or validated.

Export & audit helpers


Default state

The field default calls ScheduleField::defaultSchedule():
Static helper signature:
Examples:

Configuration API

All methods accept Closure for dynamic configuration.

days(array|Closure $days)

Which days to render. Invalid day codes are ignored. Empty array falls back to all seven days. Default: ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'].

timezone(string|Closure|null $timezone)

Default timezone identifier when the selector is shown, or null to hide the timezone block entirely. Default: 'UTC' (selector visible).
When shown, the selector uses the shared TimezoneField UI (search, UTC offset badge). It always renders at md size regardless of the schedule field size().

timeStep(int|Closure $minutes)

Minute step for FlexTimeSegmentsField dropdowns (From / To). Clamped to 1–60. Default: 5.

minSlots(int|Closure $count) / maxSlots(int|Closure $count)

Per-day slot limits (includes both work slots and breaks). Defaults: 1 / 10.

requireSlotsForEnabledDays(bool|Closure $condition = true)

When true (default), each enabled day must have at least minSlots() valid entries. When false, an enabled day may have zero slots (useful for “open but hours TBD” flows — use carefully).

allowCopyToWeekdays(bool|Closure $condition = true)

Shows Copy to weekdays on the copy source day. Default: true.

copySourceDay(string|Closure $day = 'mon')

Which day row displays the copy button. Must be one of mon … sun. Default: mon.

workdays(array|Closure $days)

Target days for Copy to weekdays. Invalid entries are filtered. Empty after filter falls back to Mon–Fri. Default: ['mon', 'tue', 'wed', 'thu', 'fri'].
Copy replaces slots on target workdays with a clone of the source day’s slots. It does not change enabled flags.

lockedDays(array|Closure $days)

Days that cannot be toggled on/off. Shows a lock icon instead of the switch. Locked days still display their schedule when enabled in state. Only days present in days() are kept.

variant(string|Closure $variant)

Visual container style.

size(string|ControlSize|Closure $size)

Size of time inputs and day rows. Default: sm. Timezone selector remains md.

readOnly(bool|Closure $condition = true) / disabled(bool|Closure $condition = true)

Inherited from Filament. Disables toggles, time pickers, copy, and slot toolbar actions.

focusOutline(bool|Closure $condition = true)

Inherited from HasFieldFocusOutline. Default: false. Adds focus ring on time input shells when enabled.

Public helper methods


Validation

Built-in rule runs on submit (and when Livewire validates). Custom Filament required is mapped to nullable at rule level — emptiness is checked inside the schedule validator when required() is set. Real-time UI validation mirrors server messages via Alpine (from_before_to, min_slots, max_slots, overlap). Disabled days skip slot validation entirely.

Model & persistence

Loading existing state:
Reading normalized state in PHP:

Recipe: restaurant — weekdays + lunch break

Users can add breaks in the UI via Add break.

Recipe: support desk — weekdays only, no weekends in UI

Recipe: always-closed weekends (locked)

Recipe: no timezone selector (local hours)

Recipe: 30-minute steps, larger time inputs

Recipe: read-only preview on view page


Editing a schedule

Enable a day to reveal its periods. From and To use the FLEX Select control with your configured time step. Add working periods or breaks, reorder them, or remove periods while respecting your slot limits. Overnight periods can finish on the next day. Copying a day’s periods to your chosen workdays keeps their existing open/closed status.

Responsive layout

The editor adapts to its own width. Inside a narrow card or on a phone, From and To use two equal columns with their labels above the controls. Large controls retain their size without forcing the card wider than its container. Text direction follows your page unless you set rtl() explicitly.

Table column

Use the same JSON attribute in a table. Only enabled days containing a working slot appear as circular badges. Hover a badge or focus it with the keyboard to see the day, all periods and timezone. A closed schedule shows “Closed” without inactive badges.

Infolist entry

By default the entry lists all seven days and shows the saved timezone when present. Hide closed days or timezone independently:
Both displays accept days() in the order you want, locale(), hourCycle(12|24) and showBreaks(false). Configuration values can be closures for per-record settings. Breaks appear with a separate label by default; a day containing only breaks does not count as open. Overnight periods include (+1) for the following day. Multiple periods are retained. Heading and description are optional and escaped as text. Use the array model cast for opening_hours; JSON strings and empty values are also handled. The table and infolist are read-only presentations. Configure editing and validation with ScheduleField. They share the saved days/timezone structure. They do not infer holiday exceptions or perform availability/reservation checks.

Week order, language and text direction

Use weekStartsOn(0) for Sunday first or weekStartsOn(1) for Monday first. Without this option, days() preserves your chosen order. The option works on the field, table column and infolist entry. It changes presentation, not the stored day keys.
Columns translate both the short weekday labels and the full names in their tooltips. Entries translate full weekday names. The field uses your application’s translation language. locale() changes the language of a display; it does not change the stored schedule. rtl() enables right-to-left layout, rtl(false) sets left-to-right, and rtl(null) inherits the page direction. Hour values and timezone identifiers remain readable in either direction. Table tooltips are centered badges without an arrow. They wrap long schedules, stay within the screen width, and appear on keyboard focus as well as hover. Only enabled days containing working slots produce day circles.

Validation when saving

Use the normal Filament form save flow (getState()), which validates the submitted schedule before returning the value to save. Supported checks include required availability, timezone identifiers, missing days, hour and minute bounds, period order, per-day slot limits, overlapping periods, and overnight overlaps between adjacent days, including Sunday to Monday. Submitted times must be hour/minute values such as 09:00 or 9:00. Relative expressions, numeric timestamps and arbitrary text are rejected. Slot data must be a list; a slot type is slot or break. Invalid flags and malformed retained slots on closed days are rejected too. Closed days do not require a period. requireSlotsForEnabledDays(false) makes the minimum optional for enabled days; maxSlots() still applies.
lockedDays() protects each selected day’s enabled/closed status both in the interface and during validation. Its time periods remain editable when the day is enabled. On edit, the protected status comes from the record’s attribute; on create, it comes from the field default. For a custom fill, nested data or storage outside the record attribute, supply the original schedule from your trusted storage:
Keep that resolver independent of the submitted form data. Custom business rules can be added with Filament’s rules() API. Model authorization, preventing concurrent reservation conflicts and validating writes made outside the Filament form remain responsibilities of your application.
Last modified on October 16, 2026