-
-
Notifications
You must be signed in to change notification settings - Fork 558
Custom Calendar Triggers
Sometimes an event or workflow should run on its normal schedule, but only when your business calendar says it is allowed. For example, you may want a daily workflow to skip company holidays, regional bank holidays, maintenance freeze days, or any other non-business dates that your team maintains separately from xyOps.
You can solve this with a Trigger Plugin. A Trigger Plugin is a schedule modifier. Your event still has a normal schedule, such as every weekday at 8:00 AM, but xyOps asks the Trigger Plugin for a final launch decision before each scheduled run. The plugin can say yes for business days and no for holidays.
This guide shows two approaches:
- A very small custom Node.js Trigger Plugin that reads holiday dates from a Plugin Parameter.
- The iCal Blackout Calendar Marketplace plugin, which handles full
.icscalendars.
Trigger Plugins do not create launches by themselves. They modify launches that already came from a normal recurring schedule trigger.
The pattern looks like this:
- Configure the event or workflow to run at every time it could possibly run.
- Add a Plugin trigger modifier.
- Let the Trigger Plugin inspect the scheduled launch date.
- Return
launch: truefor allowed dates andlaunch: falsefor blocked dates.
Trigger Plugins run on the primary conductor before the job is launched and before a target server is chosen. xyOps communicates with them using the xyOps Wire Protocol, which is JSON over STDIO. For this use case, xyOps sends the plugin a JSON payload on STDIN, including an items array of pending scheduled launches. Each item includes dargs, which contains the scheduled date and time already parsed in the event timezone.
That means your plugin does not have to rebuild timezone logic. It can read item.dargs.year, item.dargs.month, and item.dargs.day directly.
Use this option if your calendar is just a list of blocked dates, and you want to store it directly on the Plugin trigger attached to each event or workflow.
This simple version expects one holiday per line:
2026-01-01 - New Year's Day
2026-07-04 - 4th of July
2026-10-31 - Halloween
2026-12-25 - Christmas
Only the leading YYYY-MM-DD date is used by the plugin. The holiday name is there for humans.
Note
If your weekend pattern is fixed, you can usually handle weekends directly in the normal Schedule UI by choosing the weekdays that should run. Then use the Trigger Plugin only for holidays and other special blocked dates. If your weekend pattern changes by calendar, add those blocked dates to the calendar parameter.
Open Plugins in the xyOps sidebar and click New Plugin.
Use these basic settings:
| Field | Value |
|---|---|
| Plugin Title | Holiday Calendar |
| Plugin Enabled | ✅ Checked |
| Type | Trigger Plugin |
| Executable | node |
Pick any icon you like, such as calendar-x, calendar-alert, or calendar-clock.
Save the plugin.
The plugin needs one parameter to hold the plain-text calendar. Add a parameter with these settings:
| Field | Value |
|---|---|
| Param ID | dates |
| Label | Holiday Dates |
| Control Type | Text Box |
| Default Value | Paste your calendar dates, or leave blank |
| Required | ✅ Checked |
Use this format in the default value, or paste it later on the event trigger:
2026-01-01 - New Year's Day
2026-07-04 - 4th of July
2026-10-31 - Halloween
2026-12-25 - Christmas
Only the leading YYYY-MM-DD date is used. The text after the date is ignored, so you can use it for the holiday name or notes.
Save the plugin again.
Click Edit Script and paste this source code:
function parseCalendar(text) {
// Parse the plain-text calendar into a set of YYYY-MM-DD dates.
// Expected line format: YYYY-MM-DD - Holiday Name
return new Set(
String(text || '')
.split(/\n/)
.map( line => line.trim().match(/^(\d{4}-\d{2}-\d{2})\b/) )
.filter(Boolean)
.map( match => match[1] )
);
}
async function main() {
// Read xyOps JSON payload from STDIN.
const chunks = [];
for await (const chunk of process.stdin) chunks.push(chunk);
const data = JSON.parse( Buffer.concat(chunks).toString('utf8') );
data.items.forEach( function(item) {
// Each item may have a different calendar parameter value.
const holidays = parseCalendar( item.params && item.params.dates );
// dargs are already parsed in the event timezone.
const today = [
item.dargs.year,
String(item.dargs.month).padStart(2, '0'),
String(item.dargs.day).padStart(2, '0')
].join('-');
// Launch on normal days, block on holidays.
item.launch = !holidays.has(today);
} );
process.stdout.write( JSON.stringify(data) + "\n" );
}
main().catch( function(err) {
process.stderr.write("Holiday trigger failed: " + err.message + "\n");
process.exit(1);
} );Save the plugin.
Open the event or workflow that should run only on business days.
Add a normal Schedule trigger for every date and time the job could run. For example:
- Every weekday at
8:00 AM, if weekends are always blocked. - Every day at
8:00 AM, if the calendar parameter also contains weekends or other custom blocked dates. - Every hour during business hours, if the workflow should check frequently but only proceed on allowed days.
The schedule trigger decides when xyOps should ask the question. The Trigger Plugin decides whether the answer is yes or no for that specific scheduled launch.
On the same event or workflow, add a Plugin trigger modifier.
Use these settings:
| Field | Value |
|---|---|
| Plugin | Holiday Calendar |
| Holiday Calendar | Paste the blocked dates for this event |
| Enabled | ✅ Checked |
Save the event or workflow.
The safest test is to create a temporary event with the same schedule and Trigger Plugin, then paste a nearby blocked date into the Holiday Calendar parameter on the Plugin trigger modifier.
For example, if you are testing a July 4, 2026 launch, add:
2026-07-04 - Test Holiday
Then schedule the event for that date and confirm that xyOps skips the launch. Remove the test date from the trigger parameter or choose a normal business date and confirm that the launch proceeds.
You can also test the script locally by piping a sample trigger payload into it:
echo '{"xy":1,"type":"trigger","items":[{"timezone":"America/Los_Angeles","now":1783180800,"dargs":{"year":2026,"month":7,"day":4,"weekday":6,"hour":9,"minute":0},"params":{"calendar":"2026-01-01 - New Years Day\n2026-07-04 - 4th of July\n2026-12-25 - Christmas"},"job":{"id":"emexample","title":"Daily Import"}}]}' | node holiday-trigger.jsOn a blocked date, the output should include:
{"xy":1,"type":"trigger","items":[{"timezone":"America/Los_Angeles","now":1783180800,"dargs":{"year":2026,"month":7,"day":4,"weekday":6,"hour":9,"minute":0},"params":{"calendar":"2026-01-01 - New Years Day\n2026-07-04 - 4th of July\n2026-12-25 - Christmas"},"job":{"id":"emexample","title":"Daily Import"},"launch":false}]}On a normal date, launch should be true.
If your calendar is already in .ics format, or you need recurring events, exceptions, recurrence overrides, timezones, or uploaded calendar text, use the iCal Blackout Calendar Marketplace plugin.
This is the xyplug-calendar plugin. It is a Trigger Plugin that lets you paste or upload iCal / ICS content and use the events as schedule gates.
It supports two modes:
| Mode | Behavior |
|---|---|
Blackout |
Jobs run normally except when the scheduled launch time is inside an iCal event. |
Whiteout |
Jobs only run when the scheduled launch time is inside an iCal event. |
Blackout mode is a good fit for company holidays, maintenance windows, release freezes, and other blocked periods. Whiteout mode is useful when your calendar represents approved execution windows, such as business hours.
To use it:
- Install iCal Blackout Calendar from the Marketplace.
- Add a normal Schedule trigger to your event or workflow.
- Add a Plugin trigger modifier that points at
iCal Blackout Calendar. - Choose
BlackoutorWhiteout. - Paste or upload your iCal / ICS source.
- Save and test the event.
For all-day holidays, use date-only iCal events, such as:
DTSTART;VALUE=DATE:20260704
DTEND;VALUE=DATE:20260705
iCal DTEND is exclusive, so this example covers July 4 only.
Trigger modifiers only apply to scheduled launches. Manual launches from the UI or API bypass schedule modifiers, including Trigger Plugins.
That is helpful while testing, but it also means a user could manually launch the event on a blocked holiday. If the calendar rule needs to be strict, consider disabling the Manual Run trigger on the event or workflow.
- A Trigger Plugin is a schedule modifier, so the event or workflow still needs a normal Schedule or Interval trigger.
- Schedule the event for every time it could run, then let the plugin block the dates it should skip.
-
item.dargsis already in the selected trigger timezone. - The simple Node.js example only checks whole dates in
YYYY-MM-DDformat. - Each event can provide its own calendar text through the
calendarPlugin Parameter. - Put weekends in the normal Schedule trigger when the pattern is fixed.
- Put weekends in the calendar parameter when the pattern changes by calendar.
- Use the Marketplace iCal plugin when you need recurring calendar events, exception dates, timezones, or
.icssupport. - Manual runs bypass Trigger Plugins unless you remove or disable the Manual Run trigger.