# Recurrence

> Build recurring events in dash-mui-scheduler with EventCalendarPremium using RRULE strings, RRULE objects and exception dates.

**Site index:** [https://muischeduler.2plot.dev/llms.txt](https://muischeduler.2plot.dev/llms.txt) — every page on this site, as Markdown.  
**Network index:** [https://2plot.dev/llms.txt](https://2plot.dev/llms.txt) — The 2plot network; start here to discover sibling sites.  
**Sibling sites:** 13 more in The 2plot network — listed in the site index above.  
**Sitemap:** https://muischeduler.2plot.dev/sitemap.xml  


---



### Recurrence (Premium)

Recurring events are a **Premium** feature. Use `dms.EventCalendarPremium`
instead of `dms.EventCalendar` — it is identical to the Community calendar but
adds a recurrence engine, a Recurrence tab in the edit dialog, and two new
per-event keys: `rrule` and `exDates`.


    `EventCalendarPremium` requires a MUI X Premium license key. Pass it via
    `licenseKey=os.environ.get("MUI_X_LICENSE_KEY", "")`. Without a valid key
    the calendar still renders and is fully interactive, but a MUI watermark is
    shown over it. That is expected — supply a real key to remove it.

The data boundary is unchanged from the Community calendar. `events` is a list
of plain dicts and is **both input and output**: the component writes the full
array back on every create, move, resize or delete. Dates are ISO **strings**
(for example `"2024-01-15T10:00:00"` for wall time, or a trailing `Z` for UTC),
never Python `datetime` objects. Recurring series are stored as a *single*
event dict carrying an `rrule`; the calendar expands it into occurrences for
display only.

### Recurrence as an RRULE string

The simplest form sets `event["rrule"]` to an RFC-5545 RRULE string. The event
below repeats every week on Monday, Wednesday and Friday:

```text
FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,FR
```

`FREQ` is one of `DAILY`, `WEEKLY`, `MONTHLY`, `YEARLY`. `BYDAY` codes are
`MO TU WE TH FR SA SU` (for monthly rules you may prefix an ordinal, e.g.
`2TU` = second Tuesday, `-1FR` = last Friday). You can also add `COUNT`,
`UNTIL` (an ISO string), `BYMONTHDAY` (1–31) and `BYMONTH` (1–12).



```python
# File: docs/recurrence/recurrence_string.py

import os

from dash import Input, Output, callback
import dash_mantine_components as dmc
import dash_mui_scheduler as dms

# Premium events may carry an `rrule`. The RRULE string form follows RFC-5545:
# this event recurs every week on Monday, Wednesday and Friday at 10:00.
events = [
    {
        "id": "standup",
        "title": "Team Standup",
        "start": "2024-01-15T10:00:00",
        "end": "2024-01-15T10:30:00",
        "color": "blue",
        "rrule": "FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,FR",
    },
    {
        "id": "review",
        "title": "Sprint Review",
        "start": "2024-01-19T15:00:00",
        "end": "2024-01-19T16:00:00",
        "color": "purple",
    },
]

component = dmc.Stack(
    [
        dms.EventCalendarPremium(
            id="recurrence-string-cal",
            licenseKey=os.environ.get("MUI_X_LICENSE_KEY", ""),
            events=events,
            defaultView="week",
            defaultVisibleDate="2024-01-15",
            height=600,
        ),
        dmc.Text("Last action:", fw=600, size="sm"),
        dmc.Code(id="recurrence-string-action", block=True),
    ],
    gap="sm",
)


@callback(
    Output("recurrence-string-action", "children"),
    Input("recurrence-string-cal", "lastAction"),
)
def show_action(last_action):
    if not last_action:
        return "No action yet — drag, create, resize or delete an occurrence."
    return f"{last_action.get('type')} @ {last_action.get('event_timestamp')}"
```


### Recurrence as an object + exception dates

Instead of a string, `rrule` may be an object. This is convenient when you are
building the rule programmatically:

```python
{"freq": "WEEKLY", "interval": 1, "byDay": ["MO", "WE", "FR"], "count": 10}
```

The keys mirror the RRULE parts: `freq`, `interval`, `byDay`, `byMonthDay`,
`byMonth`, `count` and `until`. To remove individual occurrences from a series
without breaking the rule, add `exDates` — a list of ISO strings naming the
start times to skip:

```python
"exDates": ["2024-01-17T08:00:00", "2024-01-19T08:00:00"]
```

The example below recurs every weekday for ten occurrences, with two dates
excluded. Note that even though the calendar shows many occurrences, the
`events` output still contains a single definition dict.



```python
# File: docs/recurrence/recurrence_object.py

import os

from dash import Input, Output, callback
import dash_mantine_components as dmc
import dash_mui_scheduler as dms

# `rrule` may also be an object instead of a string. Here the event recurs
# every weekday morning for a total of 10 occurrences, but two specific
# dates are removed from the series via `exDates` (ISO strings).
events = [
    {
        "id": "yoga",
        "title": "Morning Yoga",
        "start": "2024-01-15T08:00:00",
        "end": "2024-01-15T08:45:00",
        "color": "green",
        "rrule": {
            "freq": "WEEKLY",
            "interval": 1,
            "byDay": ["MO", "TU", "WE", "TH", "FR"],
            "count": 10,
        },
        "exDates": [
            "2024-01-17T08:00:00",
            "2024-01-19T08:00:00",
        ],
    },
]

component = dmc.Stack(
    [
        dms.EventCalendarPremium(
            id="recurrence-object-cal",
            licenseKey=os.environ.get("MUI_X_LICENSE_KEY", ""),
            events=events,
            defaultView="week",
            defaultVisibleDate="2024-01-15",
            height=600,
        ),
        dmc.Text("Occurrence count in events output:", fw=600, size="sm"),
        dmc.Code(id="recurrence-object-count", block=True),
    ],
    gap="sm",
)


@callback(
    Output("recurrence-object-count", "children"),
    Input("recurrence-object-cal", "events"),
)
def show_count(events_out):
    # The component writes the full event array back on every edit. The
    # recurring definition stays a single dict with `rrule`/`exDates`;
    # the UI expands it into occurrences for display only.
    return f"{len(events_out or [])} stored event definition(s)"
```


### Reading edits back

As with the Community calendar, you do not need a callback for the calendar to
be interactive — drags, creates and deletes round-trip through Dash on their
own. Add a callback only to *display* outputs. The `lastAction` output is
`{type, event, event_timestamp}`, where `type` is one of `create`, `update`,
`delete`, `move`, `resize` or `change`. The first example above wires
`lastAction` into a code block so you can watch edits as they happen.

### EventCalendarPremium props

`EventCalendarPremium` accepts every `EventCalendar` prop plus `licenseKey`,
and its `events` may include `rrule` and `exDates`.


#### `EventCalendarPremium` props

| prop | type | description |
|---|---|---|
| `id` | string; optional | The id used to identify this component in Dash callbacks. |
| `areEventsDraggable` | boolean; optional | Allow drag-to-reschedule. Default True. |
| `areEventsResizable` | boolean \| a value equal to: 'start', 'end'; optional | Allow resize (bool, or restrict to "start"/"end"). Default True. |
| `canDragEventsFromTheOutside` | boolean; optional | Allow external events to be dragged in. Default False. |
| `canDropEventsToTheOutside` | boolean; optional | Allow events to be dragged out of the calendar. Default False. |
| `className` | string; optional | CSS class applied to the wrapping div. |
| `defaultPreferences` | dict; optional | Uncontrolled initial preferences. `defaultPreferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `defaultView` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Uncontrolled initial view. Default "week". |
| `defaultVisibleDate` | string; optional | Uncontrolled initial visible date (ISO string). Default today. |
| `defaultVisibleResources` | dict; optional | Uncontrolled initial resource visibility map. Default {} (all visible). |
| `displayTimezone` | string; optional | Render timezone: IANA name, or "default"/"locale"/"UTC". Default "default". |
| `eventColor` | a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional | Default color palette for all events (overridable). Default "teal". |
| `eventCreation` | dict; optional | Configures event creation. `False` disables it; `True` enables defaults; an object sets {interaction, duration (minutes)}. `eventCreation` is a boolean \| dict with keys: - interaction (a value equal to: 'click', 'double-click'; optional) - duration (number; optional) |
| `eventDialogTopOffset` | number; optional | On desktop, inset the event drawer this many px from the top — e.g. set it to your fixed app header's height so the drawer lines up with a sidebar instead of covering the header. Default 0. |
| `eventDialogVariant` | a value equal to: 'drawer', 'dialog'; default 'drawer' | How the event editor is presented. "drawer" (default) restyles the built-in dialog into a responsive drawer — right-anchored on desktop, an 80%-height bottom sheet on mobile (below `mobileBreakpoint`), with a scrollable body and pinned header/actions. "dialog" keeps the library's default floating, draggable dialog. |
| `events` | list of dicts; optional | The events to render. Each event is a dict with at least `id`, `title`, `start`, `end` (ISO strings). Premium events may also carry `rrule` (recurrence) and `exDates`. INPUT + OUTPUT (round-trips on every change). `events` is a list of dicts with keys: - id (string \| number; required) - title (string; required) - start (string; required) - end (string; required) - description (string; optional) - timezone (string; optional) - resource (string \| number; optional) - rrule (string \| dict; optional): Recurrence rule — RFC-5545 RRULE string ("FREQ=WEEKLY;INTERVAL=2;BYDAY=TH") or an object {freq:"DAILY\|WEEKLY\|MONTHLY\|YEARLY", interval, byDay, byMonthDay, byMonth, count, until}. - exDates (list of strings; optional): Exception dates (ISO strings) excluded from the recurrence. - allDay (boolean; optional) - readOnly (boolean; optional) - color (a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional) - draggable (boolean; optional) - resizable (boolean \| a value equal to: 'start', 'end'; optional) - className (string; optional) - extractedFromId (string \| number; optional) |
| `height` | number \| string; default 600 | Height of the wrapping container (the calendar fills it). Default 600. |
| `lastAction` | dict; optional | Convenience OUTPUT describing the most recent change to `events`: {type, event, event_timestamp}. `lastAction` is a dict with keys: - type (string; optional) - event (dict; optional) - event_timestamp (number; optional) |
| `licenseKey` | string; optional | MUI X Premium license key. Set once to remove the watermark and unlock Premium features (recurrence). Read from an environment variable on the server and pass it in. |
| `localeText` | dict; optional | Override UI label strings (a partial map of translation keys). |
| `mobileBreakpoint` | number; default 768 | Width (px) below which the UI switches to its mobile layout. Default 768. |
| `preferences` | dict; optional | Controlled user preferences. Also an OUTPUT. `preferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `preferencesMenuConfig` | dict; optional | Which items appear in the preferences menu, or `False` to hide it. `preferencesMenuConfig` is a a value equal to: false \| dict with keys: - toggleWeekendVisibility (boolean; optional) - toggleWeekNumberVisibility (boolean; optional) - toggleAmpm (boolean; optional) - toggleEmptyDaysInAgenda (boolean; optional) - toggleWeekStartsOn (boolean; optional) |
| `readOnly` | boolean; optional | Global read-only mode (disables create / drag / resize / dialog). |
| `resources` | list of dicts; optional | Resources events can be assigned to (supports nested `children`). |
| `responsiveSidePanel` | boolean; default True | When True (default), the side panel starts open on wide screens and collapsed below `mobileBreakpoint` on first render — unless you pin `isSidePanelOpen` via `preferences` / `defaultPreferences`. |
| `scrollToCurrentTime` | boolean; default False | In the day / week views, scroll the time grid on first render (and on view change) so the current-time indicator is centered in view. Pairs with `showCurrentTimeIndicator`. Default False. |
| `shouldEventRequireResource` | boolean; optional | Require every event to be assigned to a resource. Default False. |
| `showCurrentTimeIndicator` | boolean; optional | Show the current-time indicator line in time views. Default True. |
| `sx` | dict; optional | MUI `sx` styling object applied to the calendar (object form only). |
| `view` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Controlled active view. Also an OUTPUT. |
| `views` | list of a value equal to: 'day', 'week', 'month', 'agenda's; optional | Which views are offered. Default ["day","week","month","agenda"]. |
| `visibleDate` | string; optional | Controlled visible date (ISO string). Also an OUTPUT. |
| `visibleResources` | dict; optional | Controlled resource visibility map {resourceId: bool}. Also an OUTPUT. |


---

*Source: /recurrence*
