
Version 0.2.0
Calendar Generator is a UXP panel for Adobe InDesign that builds fully paginated, date-driven documents — wall calendars, planners, diaries — from a date range, a set of labeled template frames, and (optionally) CSV data and placed PDFs. This document describes every setting in the panel, how the generation engine interprets them, and the underlying data model.
A step-by-step video tutorial is coming soon. Stay tuned!
1. How it fits together
At a high level, a single generation run does the following:
- Builds a list of calendar days from your date range (and Hijri/Julian/ISO-week derivatives for each day).
- Groups those days into pages (or multi-day spreads), inserting alignment blanks and month-boundary insert pages as configured.
- For each page, picks a Parent/Master using a priority order (month start → weekend → odd/even → default).
- Applies that Parent to the page, then walks every labeled item on the Parent and overrides it onto the page, replacing
{{variable}}placeholders, filling in Mini Month tables, and placing CSV/PDF files. - Marks every generated page and item with hidden metadata so a later Regenerate can find and safely replace exactly this run — without disturbing anything else in your document.
Everything you configure in the panel is simply the input to this pipeline.
2. Calendar Setup tab
Date range
- From / To —
YYYY-MM-DD, inclusive on both ends. - Render only the first day of each month — collapses the range to one entry per month (day 1 only). Useful for month-overview layouts rather than daily pages.
Number of days in Spread
Controls how many consecutive calendar days are grouped onto one logical unit: - 1 (default) — classic one-day-per-page. - 2+ — that many consecutive days share one InDesign spread. This only produces genuine left/right page grouping when the document uses Facing Pages; otherwise the panel warns that all ordered days must be designed on a single physical page. - Each day within the group is addressed by a Day order (1 to N), which maps to label/placeholder suffixes: order 1 has no suffix (date), order 2+ appends #N (date#2, date#3…). - Month-insert pages (see below) always use one day per page — order 1 for a "before" page, the month's last day for an "after" page — even when the main calendar uses multi-day spreads.
Start month on...
- Any page / Odd page / Even page. When set, the generator inserts a single blank page immediately before a month's first day whenever needed to force that day onto the requested page parity. This setting is ignored once multiple days per spread are combined into two-page spreads — in that case, which physical page a month lands on is controlled entirely by where its Day-order label sits on the Parent spread.
Weekend days
Checkboxes for any combination of days. This feeds two things: - The {{is_weekend}} variable ("true"/"false" per day). - The Weekend Parent rule (see §9).
Generation
- Append new calendar — always adds new pages.
- Regenerate Calendar — finds the most recently generated ("managed") run in the document and replaces it in place, reusing the same page range. Regeneration requires the previous run's pages to still be contiguous (and, for spread mode, to occupy whole spreads); otherwise it refuses and tells you to use Append.
Insert variable
This is the panel's interface for building your template: - Pick an entry in the dropdown — built-in date variables, PDF source targets, or CSV @file targets. For multi-day spreads, a Day order selector appears so you can target date#2, date#3, etc. - With the text cursor inside a text frame: inserting places a literal {{variable}} placeholder at the cursor, and the frame is auto-labeled calendar_mix (a frame can mix several placeholders and static text this way). - With one or more frames selected: inserting assigns a Script Label directly to the frame(s) (all selected objects must be the same acceptable type — text frames for text/Mini Month variables, graphic frames for PDF/CSV-file targets). - Mini Month labels can only be assigned to a selected text frame, never inserted mid-text as a placeholder. - The selection status line below shows the Script Label(s) of whatever is currently selected, live-updating as your InDesign selection changes.
Run behavior
- Cancellable — the run yields control between page/item operations so the Cancel button stays responsive, and shows a live progress bar. InDesign records normal step-by-step undo.
- Single-step Undo — runs the whole generation inside one
doScripttransaction (UndoModes.ENTIRE_SCRIPT), collapsing it to a single Undo entry. The panel cannot respond to Cancel while this transaction is running, so no progress bar/cancel button is available.
Run preflight
Validates the current configuration without generating anything. See §12 for what it checks.
3. Localization tab (Custom translations)
Each translation you create defines 12 month names and 7 day names, and automatically exposes two variables named after its suffix (a slugified identifier derived from the translation's name unless you set it explicitly):
{{month_<suffix>}} {{day_<suffix>}}
These resolve per-day exactly like the built-in {{month}} / {{day}} variables. Blank month/day entries are allowed but flagged as warnings by preflight; a missing or duplicate suffix (colliding with another translation or a built-in variable name) is a hard error.
Custom translations are also selectable as the Mini Month locale (see §5), letting the mini calendar table render entirely in a language you define.
4. Data tab (CSV)
- Choose CSV parses the file (delimiter auto-detected among comma, semicolon, and tab by sampling the first non-empty line) and builds a column model.
- Calendar date column — the column whose value is parsed as each row's date. Accepted input formats:
YYYY-MM-DD,DD/MM/YYYY, or anythingDatecan parse as a fallback. The panel guesses a starting choice by looking for a column nameddate,calendar_date,date_iso, orday_date, falling back to the first non-@column. - Detected variables — every non-
@column becomes a variable namedcustom_<normalized_header>, usable like any built-in variable:{{custom_event}}. - File placement columns — any header starting with
@(e.g.@photo) is treated as a file-placement column instead of a text variable. Its cell values are file paths (absolute, or relative to the CSV's own location) that get placed into a graphic frame carrying the Script Label after the@(here,photo). Each file column has its own fitting mode: Fit proportionally, Fill proportionally, Fit content to frame, Center without scaling, or Use frame fitting options. - A file-placement label cannot end in a reserved
#numberday-order suffix (that syntax is reserved for multi-day spreads), and two@columns cannot target the same frame label. - Rows whose date cell doesn't parse are silently skipped (and counted in the stats line); rows with a valid date but a blank/duplicate date across rows can also produce a preflight warning.
5. Mini Month tab
A self-contained mini calendar table generator, addressed on the template via the labels mini_month, mini_month:-1, mini_month:+1, or any other mini_month:±N offset (N between -120 and 120 months from the page's own date).
Layout
- Columns / date slots: 7, 14, 21, 28, or 31.
- Preferred date rows: Auto, or a fixed 1–6. "Auto" computes the minimum rows needed to fit the month, honoring the settings below.
- Align day 1 to the weekday grid — on: standard month-grid behavior, day 1 lands under its real weekday. Off: a sequential strip starting immediately at date 1 (e.g., 31 columns × 1 row = one continuous month strip).
- First day of week — any day, used for grid alignment.
- Show adjacent-month dates — fills leading/trailing empty cells with the previous/next month's dates instead of leaving them blank.
- Compress a 6-week month into 5 rows when possible — for a week-aligned 7-column, 5-requested-row layout where the month actually needs 6 weeks, the sixth week's dates are folded into the unused weekday cells at the start of row 1 instead of expanding to a 6th row. This is automatically disabled whenever adjacent-month dates are shown (those cells are needed for the adjacent dates instead), and is otherwise the only thing preventing an automatic 6th-row expansion.
Content
- Month title toggle, with format options:
January 2027,January,Jan 2027,Jan,01/2027. - Weekday names: full, short, narrow (first character), or hidden.
- Language/translation: English, French, Arabic, or any custom translation you've created (§3) — each renders its own month/day names inside the table.
- Highlight the generated page date — marks the cell matching the page's own calendar day (only meaningful for the
mini_monthlabel with offset 0). - Highlight configured weekend days — uses the same weekend-day set from the Calendar Setup tab.
- Show week numbers: adds a leading "Wk" column before every 7-day block. Only available when the layout is week-aligned and the column count is a multiple of 7 (7/14/21/28) — a 31-column strip or a non-aligned layout can't show them.
- Week-number system:
- ISO 8601 — weeks start Monday; week 1 is the first week with ≥4 days in the new year.
- Sunday-based — weeks start Sunday; the week containing January 1 is week 1.
- Custom — weeks start on the Mini Month's own "first day of week" setting, with a configurable minimum-days-in-first-week threshold (1–7).
Profiles by Parent/Master
The settings above are the default Mini Month configuration. You can save a distinct snapshot per Parent/Master ("Save current settings for Parent"), so different templates (e.g. a cover page vs. a daily page) render their Mini Month differently without conflicting. During generation, a page looks up its own applied Parent's profile first and falls back to the default settings if none exists.
Mini Month table/cell/paragraph styles are auto-created and scoped per Parent (an internal hash of the Parent name is appended to each style name), so each Parent's Mini Month gets independently editable styles.
6. Assets tab (PDF sources)
Each PDF source defines: - Name — a label for your own reference. - Target frame Script Label — the graphic frame label this source places into (assigned via the same "Insert / label selection" flow on the Calendar Setup tab, choosing this PDF as the variable target). - Placement mode: - PDF page by calendar day-of-year — places PDF page (day-of-year + offset) for every day. - One PDF page per month — repeat daily — places PDF page (month index + 1 + offset) on every day of that month. - One PDF page per month — first day only — same page number, but only placed on the month's first day. - PDF page offset — a whole number (positive or negative) added to the computed page number. - Fitting — same five options as CSV file columns.
Two PDF sources cannot share a target frame label, and a PDF target label cannot collide with a CSV @file label. Missing/unavailable PDF files are flagged by preflight; you can re-choose the file at any time from that source's row.
7. Layout & Rules tab
Default Calendar Parent / Master
The fallback Parent applied to any page that doesn't match a more specific rule.
Pages before / after month
- Pages before month (0 disables) — inserted immediately before a month's day-1 page, using their own dedicated Parent. Each such page receives and renders day 1's data.
- Pages after month (0 disables) — inserted immediately after the month's final day, using their own dedicated Parent, rendering the final day's data.
- With multiple days per spread, enabling these keeps day-grouping aligned to month boundaries — the last spread of a month may end up with fewer than the configured days-per-spread so the insert pages land exactly at the boundary.
- Regenerate safely tracks and replaces these inserted pages along with the rest of the run.
Parent rules
Priority order used to choose a Parent for every calendar page (see §9 for the exact algorithm):
- Month-insert pages → their own dedicated before/after Parent.
- First day of month → First day of month Parent (if set).
- Weekend → Weekend Parent (if set). Only day-order-1 of a spread is checked, since one InDesign page can only carry one Parent.
- Odd/even document position → Odd / Even Parent (if set).
- Otherwise → the default Calendar Parent.
- Alignment blank pages (inserted only to force odd/even month starts) use the separate Inserted blank page Parent instead of this chain.
Refresh Parents
Re-scans the active document's master spreads to repopulate every Parent dropdown in the panel — use this after adding/renaming masters.
Managed info
Shows how many pages belong to the most recently generated run, if any is currently tracked in the open document.
8. Presets tab
Presets snapshot the entire configuration: calendar settings, Mini Month defaults and per-Parent profiles, CSV column mapping, PDF source list, and custom translations.
- Predefined presets (built in, always available):
- Daily Calendar — 1 day per spread.
- Weekly 7-Day Spread — 7 days per spread. Applying one overlays just its calendar-shape fields onto your current settings.
- Save current as preset — stores a full snapshot under a name you choose, in the browser's local storage.
- Export — writes a
.calendarpreset.jsonfile (formatcalendar-generator-preset, version 1). CSV and PDF file access tokens are deliberately stripped before export — the preset remembers column mappings and frame labels, but not a working link to the actual files. CSV/PDF files must be re-linked on the receiving computer. - Import preset — reads such a file back in, auto-renaming on a name collision (
Name (2),Name (3), …). - Reset all settings — restores every panel field to its defaults. Saved presets are not affected.
9. Variable reference
Built-in (Gregorian, Julian, Hijri, and utility) variables
| Variable | Example | Notes |
|---|---|---|
date | 16 | Day of month, no leading zero |
date_2 | 06 | Day of month, zero-padded |
day / day_short | Monday / Mon | English |
day_f / day_f_short | Lundi / Lun. | French |
day_a | (Arabic) | Arabic |
day_symbol | — | Single symbol glyph per weekday |
month / month_short | January / Jan | English |
month_number | 01 | Zero-padded |
month_f / month_f_short | Janvier / Janv. | French |
month_a / month_a2 / month_a3 | (Arabic variants) | Three different Arabic month-name conventions |
year / year_2 | 2027 / 27 | |
week / week_2 | 6 / 06 | ISO week number |
day_of_year / days_gone | 126 | Same value; days_gone is an alias |
days_left | 239 | Days remaining in the year |
days_in_month | 31 | |
quarter | 2 | |
is_weekend | true/false | Based on configured weekend days |
is_leap_year | true/false | |
date_iso | 2027-05-06 | |
date_eu | 06/05/2027 | DD/MM/YYYY |
date_us | 05/06/2027 | MM/DD/YYYY |
date_julian / month_julian / year_julian / year_2_julian | — | Julian-calendar equivalents (offset from Gregorian) |
hijri_date / hijri_month / hijri_month_a / hijri_year | — | Computed via an Umm al-Qura lookup-table conversion |
mini_month, mini_month:-1, mini_month:+1 | — | Special: assigned to a frame, not inserted as {{...}} text; renders a full Mini Month table (§5) |
Dynamic variables
- Custom translations add
month_<suffix>andday_<suffix>(§3). - CSV columns (non-
@) addcustom_<normalized_header>(§4). - CSV
@filecolumns and PDF sources don't appear as{{}}text variables — they're assigned directly to graphic frame Script Labels.
With multi-day spreads, any of the frame-label forms above can carry a #N suffix to target day order N within the spread (e.g. date#3, mini_month#2). Text placeholders inside a calendar_mix frame use the same {{key#N}} syntax.
10. Preflight & validation reference
Running preflight (or generating, which always preflights first) checks, among other things:
- A default Calendar Parent is chosen and exists.
- Every referenced Parent (default, rules, month-insert) exists in the document.
- Each scanned Parent's labeled items are recognized: unknown Script Labels, unknown
{{placeholders}}insidecalendar_mixframes, labels on the wrong item type (e.g. a Mini Month label on a non-text-frame), and day-order suffixes exceeding the configured days-per-spread are all reported. - A Parent expected to carry calendar content but containing no recognized calendar labels is an error if it's the default Parent, a warning otherwise.
- Custom translation suffix collisions/blanks (§3).
- Mini Month settings validity, plus contextual warnings (e.g. 31-column layouts with extra unused rows, 6→5 compression conflicting with adjacent-month display).
- Mini Month per-Parent profiles referencing a Parent no longer in the document.
- CSV: valid date column chosen,
@filecolumns have labels, no duplicate@filetargets, no#number-suffixed@filelabels, and a count of rows with unparseable/duplicate dates. - PDF sources: unique target labels, no
#number-suffixed labels, file still resolvable, whole-number offsets (negative offsets are a warning, not an error). - No frame label is claimed by both a PDF source and a CSV
@filecolumn. - A PDF or CSV
@fileframe label missing from any Parent that's expected to carry calendar content is a warning (not an error) — placement is simply skipped on pages using that Parent. - Multi-day-spread-specific checks: Facing Pages enabled, the default Parent's spread having at least two Parent pages, and a note that "Start month on odd/even page" is ignored once spreads are in play.
11. Generation behavior & managed runs
Every generated page and every item placed on it is tagged with hidden metadata (via InDesign's label/extract-label mechanism):
- Document level: the ID of the latest managed run, and the panel version that created it.
- Page level: which run it belongs to, whether it's a "day" or "blank" page, and its date key.
- Item level: which run placed it, and the source label it came from.
This is what makes Regenerate safe: it locates every page belonging to the previous run, verifies they're still contiguous (and, in spread mode, form complete spreads), builds the new content on either the same physical pages or freshly inserted ones, then removes only the old run's tracked pages/items — leaving any of your own manual edits elsewhere in the document untouched. If anything goes wrong mid-run (including a Cancel), any newly created pages are rolled back and previously existing managed pages are restored to their pre-run label state.
Append mode ignores any previous run entirely and always creates a brand-new block of pages at the end of the document (or, if the document's very first page is a still-blank untouched page, reuses it as page one).
12. Data persistence (technical notes)
- Panel settings are auto-saved to
localStorageunder the keycalendarGenerator.settings.v3on essentially every change, and reloaded automatically when the panel starts. - Saved presets live under
calendarGenerator.presets.v1, separate from the live settings. - CSV and PDF files are referenced via UXP persistent file tokens, so a chosen file is remembered (and silently reopened) across sessions in the same document/session — but tokens are never included in exported presets, by design, since they aren't portable to another machine.
- Reopening a CSV automatically re-detects its columns and re-validates the previously chosen date column, falling back to a guessed column if the old one no longer exists.
13. Quick troubleshooting
| Symptom | Likely cause |
|---|---|
| "Choose a valid default Calendar Parent / Master first." | No default Parent selected, or the selected one no longer exists — use Refresh Parents. |
| "Mini Month must be assigned to the text frame itself." | You tried to insert a mini_month placeholder mid-text instead of labeling the whole frame. Select the frame and insert again. |
| Regenerate refuses and asks you to use Append | The previously generated pages were split up, reordered, or partially deleted since the last run. |
| Week numbers option is disabled | Your Mini Month layout isn't week-aligned, or its column count isn't a multiple of 7. |
| 6→5 week compression isn't happening | "Show adjacent-month dates" is on — the two options are mutually exclusive. |
A CSV @file or PDF frame silently doesn't get filled on some pages | That Parent's template is missing a frame with the matching Script Label — check the preflight warnings, which list exactly which Parents are missing which label. |
| Imported preset works but no images/PDFs appear | Expected — presets never carry file access. Re-choose the CSV and each PDF source after import. |