Skip to main content
Version: 2.0.0

Schedule Tables

Tables can trigger reducers or procedures at specific times by including a special scheduling column. This allows you to schedule future actions like sending reminders, expiring items, or running periodic maintenance tasks.

Scheduling Procedures

Procedures use the same scheduling pattern as reducers: pass the onSchedule option in TypeScript, or reference the procedure name in the scheduled attribute in other languages. This is particularly useful when you need scheduled tasks that make HTTP requests or perform other side effects. See Scheduling Procedures for an example.

Defining a Schedule Table

Why "scheduled" in the code?

The table attribute uses scheduled (with a "d") because it refers to the scheduled reducer - the function that will be scheduled for execution. The table itself is a "schedule table" that stores schedules, while the reducer it triggers is a "scheduled reducer".

In TypeScript, declare the binding on the reducer with the onSchedule option:

import { schema, table, t } from 'spacetimedb/server';

const reminder = table(
  { name: 'reminder' },
  {
    scheduledId: t.u64().primaryKey().autoInc(),
    scheduledAt: t.scheduleAt(),
    message: t.string(),
  }
);

const spacetimedb = schema({ reminder });
export default spacetimedb;

export const sendReminder = spacetimedb.reducer(
  { onSchedule: reminder },
  { arg: reminder.rowType },
  (_ctx, { arg }) => {
    // Invoked automatically by the scheduler
    // arg.message, arg.scheduledAt, arg.scheduledId
  }
);

onSchedule registers the reducer as the schedule table's target. Because the table definition does not reference the reducer, the table and the reducer can live in separate files without a circular import. A schedule table can be bound to at most one reducer or procedure; binding a second one is a schema error.

The same option works on procedures, provided the procedure's return type is t.unit(). See Scheduling Procedures.

Legacy scheduled table option

Older code declares the binding on the table instead, using a thunk that forward-references the scheduled reducer:

const reminder = table(
  { name: 'reminder', scheduled: (): any => sendReminder },
  {
    scheduledId: t.u64().primaryKey().autoInc(),
    scheduledAt: t.scheduleAt(),
    message: t.string(),
  }
);

export const sendReminder = spacetimedb.reducer({ arg: reminder.rowType }, (_ctx, { arg }) => {
  // Invoked automatically by the scheduler
});

This form still works, but the forward reference forces the table and reducer into the same file and defeats type inference (hence the (): any => cast). Prefer onSchedule in new code.

Inserting Schedules

To schedule an action, insert a row into the schedule table with a scheduled_at value. You can schedule actions to run:

  • At intervals - Execute repeatedly at fixed time intervals (e.g., every 5 seconds)
  • At specific times - Execute once at an absolute timestamp

Scheduling at Intervals

Use intervals for periodic tasks like game ticks, heartbeats, or recurring maintenance:

TypeScript: ScheduleAt import

ScheduleAt is imported from 'spacetimedb', not from 'spacetimedb/server'. Use: import { ScheduleAt } from 'spacetimedb';

import { ScheduleAt } from 'spacetimedb';
import { schema } from 'spacetimedb/server';
const spacetimedb = schema({ reminder }); // reminder table defined above
export default spacetimedb;

export const schedulePeriodicTasks = spacetimedb.reducer((ctx) => {
  // Schedule to run every 5 seconds (5,000,000 microseconds)
  ctx.db.reminder.insert({
    scheduledId: 0n,
    scheduledAt: ScheduleAt.interval(5_000_000n),
    message: "Check for updates",
  });

  // Schedule to run every 100 milliseconds
  ctx.db.reminder.insert({
    scheduledId: 0n,
    scheduledAt: ScheduleAt.interval(100_000n), // 100ms in microseconds
    message: "Game tick",
  });
});

Scheduling at Specific Times

Use specific times for one-shot actions like sending a reminder at a particular moment or expiring content:

import { ScheduleAt } from 'spacetimedb';
import { schema } from 'spacetimedb/server';
const spacetimedb = schema({ reminder }); // reminder table defined above
export default spacetimedb;

export const scheduleTimedTasks = spacetimedb.reducer((ctx) => {
  // Schedule for 10 seconds from now
  const tenSecondsFromNow = ctx.timestamp.microsSinceUnixEpoch + 10_000_000n;
  ctx.db.reminder.insert({
    scheduledId: 0n,
    scheduledAt: ScheduleAt.time(tenSecondsFromNow),
    message: "Your auction has ended",
  });

  // Schedule for a specific Unix timestamp (microseconds since epoch)
  const targetTime = 1735689600_000_000n; // Jan 1, 2025 00:00:00 UTC
  ctx.db.reminder.insert({
    scheduledId: 0n,
    scheduledAt: ScheduleAt.time(targetTime),
    message: "Happy New Year!",
  });
});

How It Works

  1. Insert a row with a ScheduleAt value
  2. SpacetimeDB monitors the schedule table
  3. When the time arrives, the specified reducer/procedure is automatically called with the row as a parameter
  4. The row is typically deleted or updated by the reducer after processing

Row Lifecycle

SpacetimeDB passes the schedule row to the scheduled reducer or procedure as an argument. One-shot schedule rows are removed at different times depending on the kind of function being called:

  • Scheduled procedures delete the row before execution, so schedule_table.find(scheduled_id) returns null and .update() fails.
  • Scheduled reducers delete the row after execution, so the row is visible in the schedule table while the reducer runs.
  • Interval schedules are never deleted automatically. Only one-shot schedules are removed after they run.

Use Cases

  • Reminders and notifications - Schedule messages to be sent at specific times
  • Expiring content - Automatically remove or archive old data
  • Delayed actions - Queue up actions to execute after a delay
  • Periodic tasks - Schedule repeating maintenance or cleanup operations
  • Game mechanics - Timer-based gameplay events (building completion, energy regeneration, etc.)

Next Steps

  • Learn about Reducers to handle scheduled actions
  • Explore Procedures for scheduled execution patterns