Schedule rules

Enforce policies (max hours, rest periods, required tags) with WARN or BLOCK actions.

Schedule rules are IF→THEN policies that Timely evaluates whenever you save a schedule. They let you encode "we never schedule more than 40 hours a week" or "every closing shift needs at least one Manager-on-Duty" once, and have the system enforce them automatically.

Pro plan and up.

Where rules live

/dashboard/rules (sidebar nav). Each row is a rule with:

  • A condition (the IF — what trigger to look for)
  • An action (the THEN — WARN or BLOCK)
  • A scope (which locations, employment types, or tags it applies to)
  • An enabled toggle

Disabled rules stay configured but don't fire — useful for seasonal policies or experiments.

The conditions

Condition What it triggers on
Weekly hours exceed N An employee's total scheduled hours for the week go over N
Daily hours exceed N An employee's total hours on a single day go over N
Shift longer than N hours Any single shift's duration exceeds N
Consecutive work days exceed N An employee works more than N days in a row
Less than N hours rest between shifts The gap between two consecutive shifts is less than N
Tag missing on shift A scope-matching shift doesn't have a required tag
Tag count below minimum A day/shift slot has fewer than N employees with the required tag

You can chain conditions with AND/OR by creating multiple rules — each rule is evaluated independently.

WARN vs. BLOCK

  • WARN — the violation shows a yellow banner on the schedule grid. The save still goes through. Use this for soft preferences.
  • BLOCK — the violation prevents saving. The Save button is disabled until the conflict is resolved. Use this for hard requirements (legal, safety, operational).

Most rules start as WARN — you want to see how often they trigger before flipping to BLOCK.

Scoping

By default a rule applies to every employee at every location. To narrow:

  • Locations — only fires for shifts at the selected locations
  • Employment types — only applies to full_time / part_time / contractor as selected
  • Tags — only applies to employees with at least one of the selected tags

Scoping is "include only" — there's no exclude option. To exclude, scope by everything except the thing you want excluded.

Examples

Hard cap on overtime

  • Condition: Weekly hours exceed 40
  • Action: BLOCK
  • Scope: All hourly employees (employment_type: hourly)

This prevents anyone hourly from being scheduled into overtime. Salaried employees are exempt because of the scope.

Coverage requirement

  • Condition: Tag count below minimum (Closer, ≥ 2)
  • Action: WARN
  • Scope: All locations, evening shifts (you'd narrow this in the rule's day/time picker)

Warns if any closing shift doesn't have at least 2 closers scheduled.

Rest period

  • Condition: Less than 10 hours rest between shifts
  • Action: BLOCK
  • Scope: All employees

A common labor-law-driven rule — prevents back-to-back closing-into-opening shifts.

When rules evaluate

Server-side, on every save. The flow:

  1. Manager edits a shift → clicks Save
  2. The schedule API runs the rules engine over the entire week's grid
  3. Any WARN violations come back in the response and surface as banners
  4. Any BLOCK violations cause the save to fail with a list of unresolved issues

The evaluation is fast (typically <100ms per save), so it doesn't slow editing.

Editing rules

Edits take effect on the next save — they don't retroactively flag existing schedules. If you want to retroactively check a past week against a new rule, open that week's grid and trigger any save (e.g., toggle and re-toggle the SMS alert flag); the engine will re-evaluate against the current rule set.

Audit log

Every triggered violation is logged for the org admin's audit history (visible to platform staff at /admin/communications for support purposes). This helps with "did Sarah get scheduled into overtime last quarter?" questions.

Limits

Plan Active rules
Pro 10
Business 50
Enterprise Unlimited

Disabled rules don't count.

Found a typo or something missing? Let us know.