Skip to main content

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.

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".

const reminder = table(
  { name: 'reminder', scheduled: 'send_reminder' },
  {
    scheduled_id: t.u64().primaryKey().autoInc(),
    scheduled_at: t.scheduleAt(),
    message: t.string(),
  }
);

spacetimedb.reducer('send_reminder', { arg: reminder.rowType }, (_ctx, { arg }) => {
  // Invoked automatically by the scheduler
  // arg.message, arg.scheduled_at, arg.scheduled_id
});

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:

import { ScheduleAt } from 'spacetimedb';

// Schedule to run every 5 seconds (5,000,000 microseconds)
ctx.db.reminder.insert({
  scheduled_id: 0n,
  scheduled_at: ScheduleAt.interval(5_000_000n),
  message: "Check for updates",
});

// Schedule to run every 100 milliseconds
ctx.db.reminder.insert({
  scheduled_id: 0n,
  scheduled_at: 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';

// Schedule for 10 seconds from now
const tenSecondsFromNow = ctx.timestamp.microseconds + 10_000_000n;
ctx.db.reminder.insert({
  scheduled_id: 0n,
  scheduled_at: 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({
  scheduled_id: 0n,
  scheduled_at: 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

Security Considerations

Scheduled Reducers Are Callable by Clients

Scheduled reducers are normal reducers that can also be invoked by external clients. If a scheduled reducer should only execute via the scheduler, add authentication checks.

spacetimedb.reducer('send_reminder', { arg: Reminder.rowType }, (ctx, { arg }) => {
  if (!ctx.senderAuth.isInternal) {
    throw new SenderError('This reducer can only be called by the scheduler');
  }
  // Process the scheduled reminder
});

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