Crons

A cron is a self-contained directory in your content root, named for the cron. It holds the code the server runs and the settings that say when. Nothing about the schedule lives in the directory name; it all lives in the settings file.

<cronhoster-config-contentRoot>/
  example/                        one cron, named "example"
    cronhoster-cron-handler.js      the entry point the server runs
    cronhoster-cron-settings.json   its runs and concurrency
A cron is a directory with a handler and a settings file.

Contents

A cron directory holds two required files, plus any modules the handler imports.

NameTypeDescription
cronhoster-cron-handler.jsfileThe entry point. The server runs it as a fresh process on each matching run, with the matched time on the command line.
cronhoster-cron-settings.jsonfileThe schedule and settings: the named runs, and how many handlers may run at once.
anything elsefile or directoryRides along with the cron and is imported by the handler; never run on its own.

cronhoster-cron-settings.json

The settings file names one or more runs. Each run is a named schedule: five time fields, each a number or *. The server reads the UTC clock once a second and fires a run when all five of its fields match (a number equals that field; * matches any value).

{
  "cronhoster-cron-setting-maxConcurrent": 1,
  "cronhoster-cron-setting-runs": [
    {
      "cronhoster-cron-setting-run-name": "top",
      "cronhoster-cron-setting-run-month": "*",
      "cronhoster-cron-setting-run-day": "*",
      "cronhoster-cron-setting-run-hour": "*",
      "cronhoster-cron-setting-run-minute": "*",
      "cronhoster-cron-setting-run-second": 0
    },
    {
      "cronhoster-cron-setting-run-name": "half",
      "cronhoster-cron-setting-run-month": "*",
      "cronhoster-cron-setting-run-day": "*",
      "cronhoster-cron-setting-run-hour": "*",
      "cronhoster-cron-setting-run-minute": "*",
      "cronhoster-cron-setting-run-second": 30
    }
  ]
}
Two runs: one at :00 and one at :30 of every minute.
FieldValueMeaning
cronhoster-cron-setting-maxConcurrentinteger, optionalHow many of this cron's handlers may run at once. Defaults to 1. A run that matches while the cron is at its ceiling is skipped, never queued.
cronhoster-cron-setting-runsarrayThe named schedules. Each fires independently when the clock matches it.
cronhoster-cron-setting-run-nametextA name for the run, distinct within the cron. It is passed to the handler and shown in the Cron Logs tool.
cronhoster-cron-setting-run-month1–12 or *The month it fires in.
cronhoster-cron-setting-run-day1–31 or *The day of the month.
cronhoster-cron-setting-run-hour0–23 or *The hour, UTC.
cronhoster-cron-setting-run-minute0–59 or *The minute.
cronhoster-cron-setting-run-second0–59 or *The second.

Every field of a run is required. A missing field is an error, because it is ambiguous whether it means a wildcard or a real value. Times are always UTC. The full grammar is on the Settings reference.

cronhoster-cron-handler.js

The handler runs as a fresh Node process, with its own directory as the working directory, so it resolves the modules beside it. It receives the matched time on the command line: the cron name, the run name, then the year, month, day, hour, minute and second.

const [cron, run, year, month, day, hour, minute, second] = process.argv.slice(2);
console.log(`${cron} / ${run} fired at ${year}-${month}-${day} ${hour}:${minute}:${second}`);
The example handler: it logs the run it is and the time it fired.

With the settings above, this cron fires twice a minute (at :00 and :30) and logs a line each time, for example example / half fired at 2026-07-25 14:05:30. Its exit code, stdout and stderr are captured per run and shown in the Cron Logs tool. The handler contract is on the Scripts reference.

Publishing and versioning

Author the cron locally, then push it with the local scripts. A cron directory is uploaded whole the first time it appears and is then never re-synced, so a running cron's files never change underneath it. To change a cron you publish a new directory under a new name: stage it as _example (a leading _ keeps it local while you test), then drop the underscore to go live and remove the old one.