---
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",
});
```
