Skip to content

Calendar Layout Component

The calendar layout component renders entities of your modules as events in a calendar (month, week, day, year and list views). Each event type in options.events maps one date field of one module to events: a module appointments with a dateRange field becomes one event type, a module employees with a birthdate field another. The user can switch event types on and off in a sidebar, click an event to open the entity, drag and resize events to change the date, and click an empty day to create a new entity.

Like every other component it lives in a column of a layout:

{
"type": "calendar",
"options": {
"events": [
{
"identifier": "appointments",
"module": "appointments",
"field": "period",
"recipes": {
"title": "this.title"
}
}
]
}
}

This is the smallest useful calendar: one event type, all other options at their defaults. identifier, module and field should always be set. The title recipe tells the calendar which text to print on the event (see Event titles and recipes).

All options are set in options of the component. Option names are snake_case.

OptionTypeRequiredDefaultDescription
eventsarray<EventType>yes*noneThe event types shown in the calendar, see Event types. *Without events the component falls back to the legacy mode, see Legacy mode.
available_viewsarray<string>no["year","month","week","day","list"]Views the user can switch between. The view switcher is hidden when only one view is listed. Besides the five names above, resourceWeek and resourceMonth (see Resource view) and the keys of custom_views are accepted; any other value is passed to FullCalendar as a view name.
default_viewstringno"month"View that is shown first. Same values as available_views. A view the user has picked before is remembered in the browser (per module of the surrounding layout) and takes precedence.
custom_viewsobjectno{}Additional FullCalendar view definitions, keyed by view name. Add the key to available_views to offer it, or set it as default_view.
editablebooleannotrueWhether events can be moved and resized by dragging. false makes the whole calendar read-only for dates. Can be overridden per event type.
heightstring or numberno"auto"Height of the calendar grid, e.g. "50vh", 600 or "auto" (grows with the content).
week_numbersbooleannofalseShow calendar week numbers in the day grid views.
nav_buttonsbooleannotrueShow the navigation buttons (previous, today, next, previous/next year, …). The title and the view switcher stay visible.
filter_sidebarbooleannotrueShow the sidebar button that lets the user toggle event types, filter them and create an iCalendar link. With false all event types are always visible.
fetch_buffer_unitsnumbernolength of the active rangeHow far beyond the visible range events are loaded, counted in units of the current view (months in month view, weeks in week view, days in day view). Default: the active range of the current view is loaded once more before and after, so the next click on “next” needs no new request. 0 loads no extra range.

The component also accepts the options every layout element has, such as hidden_from_frontend, show_in and recipes (see Layout overview).

Every entry in options.events is one event type. The calendar loads the entities of module that fall into the visible date range and draws one event per entity.

OptionTypeRequiredDefaultDescription
identifierstringyesUnique name of the event type within this calendar. Used as key for the sidebar selection and as translation key calendar.<identifier> for the label in the sidebar.
modulestringyesIdentifier of the module whose entities become events. If the module or the field does not exist in the frontend (for example because the user may not see the module), the event type is silently skipped.
fieldstringyesIdentifier of the date field of that module. Supported field types: date, datetime, dateRange. For dateRange the start and end of the range are the start and end of the event.
colorstring (CSS color)noFullCalendar default; #1890ff for the sidebar checkboxBackground color of the events and of the sidebar checkbox.
text_colorstring (CSS color)noFullCalendar defaultText color of the events.
iconstringnoIcon name shown at the right edge of the event.
titlestringnoemptyStatic title for all events of this type. Normally you use recipes.title instead.
quick_createbooleannofalseAllow creating an entity of this module by clicking an empty day. See Interactions.
editablebooleannothe calendar’s editable (true)Whether events of this type can be moved and resized.
pre_filtersarray<PreFilter>noRestricts which entities are loaded, see Filters and permissions.
recurrencestring or objectnoRepeat every event of this type, e.g. "yearly". See Recurring events. Usually set with a recipe.
recurrence_fieldstringnoIdentifier of a field of the module that holds the repeat frequency. Needed so that repeating entities are found regardless of the visible range, see Recurring events.
recipesobject<string, Recipe>noRecipes that compute event attributes per entity, see Event titles and recipes.

recipes maps an event attribute to a recipe. Each recipe is evaluated once per entity. Inside the recipe, this is the entity (all fields of the module are available, relations as far as they are loaded in the module’s table response). The entity of the surrounding layout, if there is one, is available as this._context.

The result of the recipes overrides the static value of the same name in the event type. These attributes are read by the calendar:

AttributeTypeMeaning
titlestringText shown on the event.
colorstringBackground color.
text_colorstringText color.
iconstringIcon name.
htmlstringHTML that replaces the whole content of the event. Make sure it contains no unescaped user input.
start_timestring (HH:mm)For date fields: start time of the event on that day.
end_timestring (HH:mm)For date fields: end time of the event on that day.
full_daybooleanForce the event to be an all-day event.
displaystringFullCalendar display mode, default block.
editablebooleanWhether this single event can be moved or resized.
recurrencestring or objectRepeat rule, see below.
exdatearray<string>Dates on which a recurring event is skipped.

Example: show the name of the employee next to a fixed text and color holidays by status:

{
"identifier": "holidays",
"module": "holidays",
"field": "period",
"recipes": {
"title": "'Holiday: ' + this.employee.name",
"color": "this.approved ? '#52c41a' : '#faad14'"
}
}

Event types without a title recipe or title option show events without text, so set one.

Set recurrence to a frequency string ("daily", "weekly", "monthly", "yearly") to repeat an event forever, starting at the date of the entity. For more control pass an rrule object, for example {"freq": "weekly", "interval": 2, "byweekday": ["MO", "WE"], "until": "2027-12-31"}; dtstart is set from the entity automatically. exdate lists dates to skip.

Usually every entity brings its own frequency, stored in a field. Combine both options:

{
"identifier": "recurring_tasks",
"module": "tasks",
"field": "due",
"recurrence_field": "repeat",
"recipes": {
"title": "this.name",
"recurrence": "this.repeat"
}
}

recurrence_field is required for this to work correctly. A repeating entity may start long before the visible range, so the calendar must load it regardless of its date. It does so by also loading every entity whose recurrence_field has one of the values daily, weekly, monthly, quarterly or yearly and whose date is not after the visible range. Entities without a value in recurrence_field are loaded only when their own date is in the range. Without recurrence_field, a repeating entity is loaded only when its first date lies in the loaded range, so later repetitions do not appear once you navigate away from it.

The built-in view names are year, month, week, day and list. They are used in available_views and default_view. For a view with other settings, define it in custom_views and use its key as view name:

{
"type": "calendar",
"options": {
"available_views": ["next7days", "month"],
"default_view": "next7days",
"custom_views": {
"next7days": {
"type": "list",
"duration": {"days": 7}
}
},
"events": []
}
}

The calendar uses the language of the user. The month names, weekday names and week start follow the locale.

The views resourceWeek and resourceMonth show a timeline with one row per entity of another module (for example per employee or room). They are driven by two options that are read by the component but are not part of the published layout types, so treat them as advanced and test them in your system:

  • options.resource: { "module": "<module identifier>", "groupBy": "<field>", "pre_filters": [...] } selects the entities that become rows. The row title is the brezel_name of the entity; the row id is the value of groupBy, or the entity id when groupBy is not set.
  • On every event type, groupBy is the field of the event entity whose value picks the row.

These views use the FullCalendar scheduler. The frontend needs a scheduler license key (VITE_SCHEDULER_LICENSE_KEY at build time of the SPA); without it FullCalendar shows a license notice.

  • Click on an event opens the entity in a drawer at the bottom of the screen, in the module’s normal show layout. The drawer’s layout context contains the date range of the clicked event in calendar.range and, for a repeated event, the rule in calendar.recurrence.
  • Drag or resize an event (if editable) writes the new date into the configured field of the entity with a normal update request. For date fields the new start day is saved, for datetime the new start, for dateRange start, end and the full-day flag. While saving, the event is shown semi-transparent; on failure an error toast appears and the value is not changed. For a repeated event, the date of the entity itself is moved by the amount you dragged.
  • Click on an empty day opens a drawer with the create form of the module, with the clicked date prefilled into field. This only works if at least one event type has quick_create: true. If several have it, the drawer offers a selection of the type to create. Without any quick_create, clicking on a day does nothing.
  • Click on the day number or the week number in the month view switches to the day or week view.
  • Sidebar (filter_sidebar): per event type a checkbox to show or hide it (remembered in the browser), plus a filter button that opens the normal filter UI of the module; these filters are added to the date range and pre_filters and apply to this user only. A button creates a personal iCalendar link for the currently selected event types.

pre_filters use the same format as in other components such as resource_table: a list of conditions that all must hold, or a list of lists (outer list OR, inner list AND). A condition has a column (column or field), an operator (=, !=, <, <=, >, >=, in, not in, like, is, is not) and either a value or a recipe that is evaluated in the browser. A condition with a recipe that returns nothing (or an empty list) is dropped. An optional if recipe removes the condition when it returns false. In the recipe, this is the entity of the surrounding layout and user() the current user, as in other layouts.

"pre_filters": [
{"column": "employee.id", "operator": "=", "recipe": "user().id"}
]

Filters only decide what is shown. The calendar loads entities with the same requests as the table of the module and saves with the same update requests as the entity form, so the normal module permissions and policies apply: users only see entities they may read, drag and drop fails with an error for entities they may not update, and creating needs create permission on the module. Do not rely on pre_filters to hide data from a user, use policies for that.

A system with a module appointments (fields: title string, period dateRange, status select with confirmed and tentative, owner relation to users) and a module employees with a birthdate date field and a repeat field. The layout puts a full width calendar into a tab:

{
"tabs": [
{
"identifier": "calendar",
"rows": [
{
"cols": [
{
"span": 24,
"components": [
{
"type": "calendar",
"options": {
"default_view": "week",
"available_views": ["day", "week", "month", "list"],
"week_numbers": true,
"height": "75vh",
"events": [
{
"identifier": "appointments",
"module": "appointments",
"field": "period",
"color": "#1890ff",
"icon": "team",
"quick_create": true,
"recipes": {
"title": "this.title",
"color": "this.status == 'confirmed' ? '#1890ff' : '#faad14'"
}
},
{
"identifier": "my_appointments",
"module": "appointments",
"field": "period",
"color": "#722ed1",
"editable": false,
"pre_filters": [
{"column": "owner.id", "operator": "=", "recipe": "user().id"}
],
"recipes": {
"title": "this.title"
}
},
{
"identifier": "birthdays",
"module": "employees",
"field": "birthdate",
"color": "#4adeff",
"text_color": "rgba(0, 0, 0, 0.85)",
"editable": false,
"recurrence_field": "repeat",
"recipes": {
"title": "'Birthday: ' + this.first_name + ' ' + this.family_name",
"recurrence": "this.repeat"
}
}
]
}
}
]
}
]
}
]
}
]
}

To label the sidebar entries, add translations for the event type identifiers, for example under calendar.appointments, calendar.my_appointments and calendar.birthdays in your translation files.

For the birthdays to repeat, each employee needs repeat set to yearly.

If a calendar component has no events option, it works in the older mode of the built-in Calendar page: event types come from the field options show_in_calendar, calendar_color, calendar_title, calendar_recurrence, quick_create_in_calendar and create_in_calendar of the date fields, and a “Create” button is shown above the calendar. New layouts should use events.

  • The calendar is empty. Check that module and field are spelled exactly like the identifiers in the module, that the field is of type date, datetime or dateRange, and that your user may read the module. The sidebar shows only event types that were set up successfully.
  • Events have no text. Add a title recipe.
  • Repeating events disappear when navigating. Set recurrence_field, see Recurring events.
  • Option has no effect. Options are snake_case (default_view, not defaultView).