Skip to content

Custom Calendar Triggers

Joseph Huckaby edited this page Jun 30, 2026 · 3 revisions

Run Events Only on Business Days with a Custom Calendar Trigger

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 .ics calendars.

How It Works

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:

  1. Configure the event or workflow to run at every time it could possibly run.
  2. Add a Plugin trigger modifier.
  3. Let the Trigger Plugin inspect the scheduled launch date.
  4. Return launch: true for allowed dates and launch: false for 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.

Option 1: Simple Calendar Parameter

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.

Step 1: Create the Trigger Plugin

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.

Step 2: Add the Calendar Parameter

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.

Step 3: Add the Script

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.

Step 4: Add the Normal Schedule

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.

Step 5: Add the Plugin Trigger Modifier

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.

Step 6: Test the Behavior

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.js

On 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.

Option 2: Use the iCal Blackout Calendar Plugin

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:

  1. Install iCal Blackout Calendar from the Marketplace.
  2. Add a normal Schedule trigger to your event or workflow.
  3. Add a Plugin trigger modifier that points at iCal Blackout Calendar.
  4. Choose Blackout or Whiteout.
  5. Paste or upload your iCal / ICS source.
  6. 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.

Manual Runs

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.

Things to Keep in Mind

  • 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.dargs is already in the selected trigger timezone.
  • The simple Node.js example only checks whole dates in YYYY-MM-DD format.
  • Each event can provide its own calendar text through the calendar Plugin 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 .ics support.
  • Manual runs bypass Trigger Plugins unless you remove or disable the Manual Run trigger.

Clone this wiki locally