UNPKG

@servicenow/sdk

Version:
368 lines (288 loc) 13.9 kB
--- tags: [scheduled-script, scheduled-job, cron, periodic, background-job, maintenance, automation, sysauto_script, condition, conditional execution, sandbox] --- # Scheduled Script Guide Create ServiceNow Scheduled Script Executions (`sysauto_script`) to run server-side logic on a time-based schedule. This guide covers all 11 frequency types, script file patterns, timezone handling, conditional execution, business calendar integration, and best practices. ## When to Use - Running server-side logic at a fixed time every day, week, month, or year - Recurring background maintenance tasks (data cleanup, archiving, cache refresh) - Periodic data synchronization with external systems - Generating scheduled reports or summary notifications - Executing a script exactly once at a future date/time - Running a script manually on-demand (no automated trigger) - Scheduling jobs that align with business calendar entries - Repeating at a custom interval (e.g., every 4 hours, every 30 minutes) ## Do NOT Use For - **Structured bulk data imports** (CSV, LDAP, REST sources with transform maps) - **Multi-step orchestrations** with branching conditions and discrete actions on a timer ## Core Principles 1. **Choose `frequency` first:** This determines which companion fields are required and which are ignored. 2. **Use `Now.include` for scripts:** The ScheduledScript API only accepts strings, so reference an external `.js` file with `Now.include('./path-to-file.js')`. The script must use an IIFE: `(function() { ... })();`. 3. **`timeZone`:** All times in `executionStart`, `executionEnd`, and `executionTime` are interpreted in the `timeZone` you set. Use `'floating'` to always match the platform's current system timezone. Avoid setting unless explicitly specified by the user. 4. **`executionInterval` is exclusive to `periodically`:** Setting it on any other frequency is a build error. 5. **`daysOfWeek` is required for `weekly`:** Omitting it is a build error. 6. **`businessCalendar` is required for calendar types:** Both `business_calendar_start` and `business_calendar_end` require it. 7. **`executionEnd` must allow at least one execution:** For recurring jobs, must be at least one full interval after `executionStart`. ## Quick Decision Guide | User says | Use `frequency` | Required extra fields | |-----------|-----------------|----------------------| | "every day at X", "nightly", "daily" | `daily` | `executionTime` | | "every Monday", "on Tuesdays and Fridays" | `weekly` | `daysOfWeek` (mandatory; array of lowercase names) | | "first of the month", "every month on day X" | `monthly` | `dayOfMonth` (1-31; 31 = month-end) | | "every N hours/minutes", "periodically" | `periodically` | `executionInterval` (mandatory), `executionStart` recommended | | "once", "one-time", "migration" | `once` | `executionStart` | | "manually", "on demand" | `on_demand` | (none) | | "every year on [month] [day]" | `day_and_month_in_year` | `month` (1-12), `dayOfMonth` (1-31) | | "second Monday of every month" | `week_in_month` | `weekInMonth` (1-6), `dayOfWeek` | | "third Tuesday of March every year" | `day_week_month_year` | `dayOfWeek`, `weekInMonth`, `month` | | "when business period starts" | `business_calendar_start` | `businessCalendar` | | "when business period ends" | `business_calendar_end` | `businessCalendar` | ### Do I need `executionTime`? - Recommended for: `daily`, `weekly`, `monthly`, `day_and_month_in_year`, `week_in_month`, `day_week_month_year` - Not applicable: `periodically` (uses `executionInterval`), `once` (uses `executionStart`), `on_demand` ### Do I need `executionStart`? - Recommended: `periodically`, `once` - Optional: all other types (use to define when the first execution is eligible) - If you do not have date/time context from the user, omit it -- Fluent will auto-populate with the current date/time during build. ## API Reference For the full property reference, see the `scheduledscript-api` topic. ### Valid Frequency Values `'daily'`, `'weekly'`, `'monthly'`, `'periodically'`, `'once'`, `'on_demand'`, `'day_and_month_in_year'`, `'day_week_month_year'`, `'week_in_month'`, `'business_calendar_start'`, `'business_calendar_end'` ## Run Type Details - **`daily`** -- Runs once per day at `executionTime`. - **`weekly`** -- Runs on specific weekdays. Requires `daysOfWeek` (array). - **`monthly`** -- Runs on a fixed day. Requires `dayOfMonth`. If the day does not exist in a month (e.g., 31 in February), it runs on the last day instead. So `dayOfMonth: 31` effectively becomes a month-end job. - **`periodically`** -- Repeats at a fixed interval. Requires `executionInterval`. Avoid seconds or single-digit minutes for performance. If a run is still executing when the next fires, the platform skips that trigger silently. - **`once`** -- Runs once at `executionStart`, then stops. - **`on_demand`** -- Never runs automatically; triggered manually. - **`day_and_month_in_year`** -- Runs annually on a fixed date. Requires `month` + `dayOfMonth`. - **`week_in_month`** -- Runs on a specific week ordinal. Requires `weekInMonth` + `dayOfWeek`. - **`day_week_month_year`** -- Runs on a specific weekday in a specific week in a specific month each year. - **`business_calendar_start`** / **`business_calendar_end`** -- Fires when calendar entries begin/end. Requires `businessCalendar`. ## Script File Pattern The ScheduledScript API only accepts strings for its `script` property and should use `Now.include` to import the javascript code, see the `now-include` topic for more information The script file must use an IIFE (Immediately Invoked Function Expression): ```javascript (function () { // your logic here })(); ``` Reference it from the `script` field: ```typescript fluent import { ScheduledScript } from '@servicenow/sdk/core' ScheduledScript({ $id: Now.ID['my-job'], name: 'My Job', script: Now.include('../../server/scheduled-scripts/my-job.js'), frequency: 'daily', executionTime: Time({ hours: 2, minutes: 0 }), }) ``` > **Note:** Unlike BusinessRule and ScriptAction, the ScheduledScript API does not accept function types. Module imports will produce a TypeScript error. Use `Now.include()` or inline strings. ### Script Best Practices 1. Always use an IIFE wrapper 2. Add try-catch blocks for error handling 3. Use `gs.info()` for success, `gs.error()` for failures, `gs.debug()` for tracing 4. Keep scripts focused -- one task per scheduled script 5. Use `setLimit()` when processing large datasets 6. Return early on failed preconditions 7. Add JSDoc comments explaining the script's purpose ## Timezone Handling - `timeZone` -- Controls how `executionStart`, `executionEnd`, and `executionTime` are interpreted. Platform stores UTC internally. - `'floating'` -- Always uses the platform's currently configured system timezone. - `userTimeZone` -- Controls timezone for `GlideDateTime` calculations inside the script. Defaults to the `runAs` user's profile timezone. ## Conditional Execution Set `conditional: true` and provide a `condition` expression for dynamic per-cycle skipping. > **Important — Condition field restrictions:** The `condition` field is evaluated in the server-side script sandbox, which only allows **single expressions**. The following are **not allowed** in conditions: > > - Variable declarations (`var`, `let`, `const`) > - Control flow (`if`, `for`, `while`, `switch`, `return`) > - Assignment operators (`=`) > - Function declarations or IIFEs > - Multiple statements (semicolons) > - Object literals > > If your condition requires complex logic (multiple checks, GlideRecord queries, aggregates), create a **`sandboxCallable` script include** and call it from the condition as a single expression. See the `script-include-guide` topic for the sandbox callable pattern. Simple single-expression condition: ```typescript fluent ScheduledScript({ $id: Now.ID["conditional-job"], name: "Weekday-Only Sync", script: Now.include("./conditional-job.server.js"), frequency: "daily", executionTime: Time({ hours: 6, minutes: 30 }), conditional: true, condition: "gs.getDayOfWeekLocalTime() >= 2 && gs.getDayOfWeekLocalTime() <= 6", }); ``` Complex condition using a `sandboxCallable` script include: ```typescript fluent ScheduledScript({ $id: Now.ID["conditional-sync"], name: "Conditional Data Sync", script: Now.include("./conditional-sync.server.js"), frequency: "daily", executionTime: Time({ hours: 9, minutes: 0 }), conditional: true, condition: "new global.SyncConditionChecker().shouldRun()", }); ``` The companion script include handles the complex logic (property check + weekday check + GlideRecord query) in normal server-side context where `var`, `if`, loops, and GlideRecord are fully supported. See the `script-include-guide` topic for how to create `sandboxCallable` script includes. Common use cases: weekend/holiday checks, data-driven execution (system property flags), time-specific logic. ## Protection - `protectionPolicy: 'read'` -- Others can view but not modify - `protectionPolicy: 'protected'` -- Others cannot view or modify - Omit to allow full customization ## Business Calendar Best Practices - Always reference business calendar entries by sys_id from the `business_calendar` table - The platform provides standard entries for weeks, months, quarters, and years - If the user provides a specific calendar name or sys_id, use that instead ## Examples ### Basic Daily Job ```typescript fluent import "@servicenow/sdk/global"; import { ScheduledScript } from "@servicenow/sdk/core"; ScheduledScript({ $id: Now.ID["close-resolved-incidents"], name: "Close Resolved Incidents", script: Now.include("./close-resolved-incidents.server.js"), frequency: "daily", executionTime: Time({ hours: 2, minutes: 0 }), runAs: "6816f79cc0a8016401c5a33be04be441", }); ``` **Script file** (`close-resolved-incidents.server.js`): ```javascript (function () { var gr = new GlideRecord("incident"); gr.addEncodedQuery("state=6^sys_updated_onRELATIVELT@dayofweek@ago@30"); gr.query(); var count = 0; while (gr.next()) { gr.state = 7; gr.update(); count++; } gs.info("Closed " + count + " resolved incidents older than 30 days"); })(); ``` ### Weekly Job ```typescript fluent import "@servicenow/sdk/global"; import { ScheduledScript } from "@servicenow/sdk/core"; ScheduledScript({ $id: Now.ID["weekly-sla-report"], name: "Weekly SLA Report", script: Now.include("./weekly-sla-report.server.js"), frequency: "weekly", daysOfWeek: ["monday", "friday"], executionTime: Time({ hours: 7, minutes: 0 }), }); ``` ### Periodically (Every 4 Hours) ```typescript fluent import "@servicenow/sdk/global"; import { ScheduledScript } from "@servicenow/sdk/core"; ScheduledScript({ $id: Now.ID["periodic-sync"], name: "Periodic Data Sync", script: Now.include("./periodic-sync.server.js"), frequency: "periodically", executionInterval: Duration({ hours: 4 }), advanced: true, }); ``` ### One-Time Migration ```typescript fluent import "@servicenow/sdk/global"; import { ScheduledScript } from "@servicenow/sdk/core"; ScheduledScript({ $id: Now.ID["one-time-migration"], name: "Data Migration Script", script: Now.include("./one-time-migration.server.js"), frequency: "once", executionStart: "2026-06-15 02:00:00", }); ``` ### On-Demand ```typescript fluent import "@servicenow/sdk/global"; import { ScheduledScript } from "@servicenow/sdk/core"; ScheduledScript({ $id: Now.ID["manual-cache-refresh"], name: "Manual Cache Refresh", script: Now.include("./manual-cache-refresh.server.js"), frequency: "on_demand", }); ``` ### Monthly (Last Day) ```typescript fluent import "@servicenow/sdk/global"; import { ScheduledScript } from "@servicenow/sdk/core"; ScheduledScript({ $id: Now.ID["monthly-report"], name: "Monthly Report Generator", script: Now.include("./monthly-report.server.js"), frequency: "monthly", dayOfMonth: 31, executionTime: Time({ hours: 23, minutes: 0 }), }); ``` ### Annual Job ```typescript fluent import "@servicenow/sdk/global"; import { ScheduledScript } from "@servicenow/sdk/core"; ScheduledScript({ $id: Now.ID["annual-license-review"], name: "Annual License Review", script: Now.include("./annual-license-review.server.js"), frequency: "day_and_month_in_year", month: 1, dayOfMonth: 15, executionTime: Time({ hours: 9, minutes: 0 }), }); ``` ### Week-in-Month Job ```typescript fluent import "@servicenow/sdk/global"; import { ScheduledScript } from "@servicenow/sdk/core"; ScheduledScript({ $id: Now.ID["second-monday-report"], name: "Second Monday Report", script: Now.include("./second-monday-report.server.js"), frequency: "week_in_month", weekInMonth: 2, dayOfWeek: "monday", executionTime: Time({ hours: 8, minutes: 0 }), }); ``` ### Daily with Inline Script ```typescript fluent import "@servicenow/sdk/global"; import { ScheduledScript } from "@servicenow/sdk/core"; ScheduledScript({ $id: Now.ID["daily-notification"], name: "Daily Morning Notification", script: `(function() { gs.eventQueue('x_myapp.daily.notification', null, gs.nowDateTime(), gs.getUserID()); gs.info('Daily notification sent at ' + gs.nowDateTime()); })()`, frequency: "daily", executionTime: Time({ hours: 9, minutes: 0 }), }); ``` ### Daily with Bounded Execution Period ```typescript fluent import "@servicenow/sdk/global"; import { ScheduledScript } from "@servicenow/sdk/core"; ScheduledScript({ $id: Now.ID["q1-daily-report"], name: "Q1 Daily Status Report", script: Now.include("./q1-daily-report.server.js"), frequency: "daily", executionTime: Time({ hours: 8, minutes: 0 }), executionStart: "2026-01-01 08:00:00", executionEnd: "2026-03-31 23:59:59", }); ```