# Schedules

A schedule tells cronpad when a job fires. You can write it in plain English (`every weekday at 9am`), standard cron (`0 9 * * 1-5`), Quartz cron (`0 0 9 ? * MON-FRI`), an iCalendar RRULE (`FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;BYHOUR=9`), or as a single moment (`2026-12-25T07:00:00+05:30`). Every schedule is evaluated in the job's timezone.

## Formats

| Format | Example | Best for | Page |
|---|---|---|---|
| Natural language | `second tuesday of every month at 9am` | Readable schedules | [Natural language](natural-language.md) |
| Cron | `*/15 * * * *` | Familiar, compact | [Cron and Quartz](cron-and-quartz.md) |
| Quartz | `0 0 18 L * ?` | "Last day", "nearest weekday", "third Friday" | [Cron and Quartz](cron-and-quartz.md) |
| RRULE | `FREQ=MONTHLY;BYDAY=-1FR;BYHOUR=17` | Calendar-style rules, generated by code | [RRULE and one-off](rrule-and-one-off.md) |
| One-off | `in 20 minutes`, `2026-12-25T07:00:00Z` | Run once | [RRULE and one-off](rrule-and-one-off.md) |

## Automatic detection

By default (`"kind": "auto"`) cronpad works out the format, in this order:

1. An RFC 3339 timestamp → **one-off**.
2. Five or six space-separated cron fields, or an `@` descriptor (`@daily`) → **cron**.
3. Contains `?`, `L`, `W` or `#`, or has seven fields → **Quartz**.
4. Contains `FREQ=` → **RRULE**.
5. Anything else → **natural language**.

Set `kind` explicitly (`cron`, `quartz`, `rrule`, `natural`, `once`) when you want an error instead of a guess.

## Validate and preview before saving

The public [cron schedule validator](/cron) previews any supported expression
without signing in. Its expression and timezone are stored in the URL so you can
share a preview, and the preview updates as you type.

```schedule
every weekday at 9am
```


Every place you can enter a schedule shows what it means and when it will fire next:

```sh
curl -sS https://app.cronpad.com/api/v1/schedules/preview \
  -H "Content-Type: application/json" \
  -d '{"expr": "2nd tuesday of every month at 9am", "timezone": "Asia/Kolkata", "count": 3}'
```

```json
{
  "kind_detected": "natural",
  "canonical": "FREQ=MONTHLY;BYDAY=2TU;BYHOUR=9;BYMINUTE=0;BYSECOND=0",
  "description": "At 09:00 on the second Tuesday of every month",
  "timezone": "Asia/Kolkata",
  "next": ["2026-11-10T03:30:00Z", "2026-12-08T03:30:00Z", "2027-01-12T03:30:00Z"]
}
```

The same check works offline with the CLI: `cronpad schedule next "2nd tuesday of every month at 9am" --tz Asia/Kolkata -n 3`. See [Schedules API](../api/schedules.md) and [CLI](../cli/index.md).

When a schedule can't be parsed, the error says where and suggests a fix:

```json
{
  "valid": false,
  "errors": [{"kind": "natural", "position": 6, "message": "unknown word \"tuesdy\"", "suggestion": "every tuesday at 9am"}]
}
```

## Bounds

Any schedule can be limited with these job fields:

| Field | Meaning |
|---|---|
| `starts_at` | Don't fire before this time. |
| `ends_at` | Don't fire after this time; the job becomes `completed`. |
| `count` | Fire at most this many times; the job becomes `completed`. |

## Related

- [Timezones and DST](timezones-and-dst.md): what happens at 02:30 on the night clocks change.
- [Misfires and overlap](misfires-and-overlap.md): what happens to runs missed during downtime, and to runs that would overlap.
- [Limits](../platform/limits.md): the minimum interval between runs on cronpad Cloud.