clockworklabs/SpacetimeDB · error · TypeError

Table defines a schedule, but it does not have a ScheduleAt…

Error message

Table ${tableName} defines a schedule, but it does not have a ScheduleAt column.

What it means

A scheduled table must declare which column holds the scheduled timestamp. Resolution order is `knownScheduleAtCol` (legacy schedule API) then `table.scheduleAtCol`, which is set when the row type contains a `t.scheduleAt()` column. If neither exists, the build fails with `Table ${tableName} defines a schedule, but it does not have a ScheduleAt column.`

Solutions

  1. Add `scheduledAt: t.scheduleAt()` to the scheduled table's row type.
  2. Do not substitute other timestamp builders — only `t.scheduleAt()` marks the column for scheduling.
  3. If you use the legacy `table({... schedule ...})` API, set `scheduleAtCol` to the column's index explicitly.

Example fix

// before
const scheduledMessages = table({}, {
  id: t.u64().primaryKey().autoInc(),
  text: t.string(), // no scheduleAt column
});

// after
const scheduledMessages = table({}, {
  id: t.u64().primaryKey().autoInc(),
  scheduledAt: t.scheduleAt(),
  text: t.string(),
});
Defensive patterns

Strategy: validation

Validate before calling

// For dynamically assembled rows: require a scheduleAt builder on scheduled tables.
function assertScheduleRow(row: Record<string, any>) {
  const hasScheduleAt = Object.values(row).some(c => c?.__scheduleAt === true);
  if (!hasScheduleAt) throw new Error('scheduled table row is missing t.scheduleAt()');
}

Type guard

type HasScheduleAt<R> = R extends { scheduledAt: infer C } ? (C extends { __scheduleAt: true } ? true : false) : false;

Prevention

When it happens

Trigger: Building a table for a scheduled reducer without `scheduledAt: t.scheduleAt()` in its row; using a plain `t.timestampNtz()`/`t.string()` column instead of the dedicated `scheduleAt()` builder; constructing the table dynamically so `scheduleAtCol` is never recorded.

Common situations: Copying a normal table row into a scheduled context and swapping the primary key but forgetting the schedule column; assuming any timestamp column qualifies; upgrading from a legacy schedule API that inferred the column differently.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@eddf9f5014 (2026-08-20). Data as JSON: /api/errors/1269a181f670a7c5. Report an issue: GitHub.

Appendix: source

Thrown at crates/bindings-typescript/src/server/schema.ts:178

      let tableName = knownTableName;
      if (tableName === undefined) {
        const tableNames = this.tableSourceNames.get(table);
        if (tableNames !== undefined && tableNames.length > 1) {
          throw new TypeError(
            'Schedule target table is registered more than once in this schema. Use a distinct table handle for each scheduled table.'
          );
        }
        tableName = tableNames?.[0];
      }
      if (tableName === undefined) {
        throw new TypeError(
          'Schedule target table is not part of this schema.'
        );
      }

      const scheduleAtCol = knownScheduleAtCol ?? table.scheduleAtCol;
      if (scheduleAtCol === undefined) {
        throw new TypeError(
          `Table ${tableName} defines a schedule, but it does not have a ScheduleAt column.`
        );
      }

      const functionName =
        knownFunctionName ??
        (reducer === undefined
          ? undefined
          : this.functionExports.get(reducer()));
      if (functionName === undefined) {
        const msg = `Table ${tableName} defines a schedule, but it seems like the associated function was not exported.`;
        throw new TypeError(msg);
      }
      const existingFunctionName = scheduledTables.get(tableName);
      if (existingFunctionName !== undefined) {
        throw new TypeError(
          `Table ${tableName} defines multiple schedules: ${existingFunctionName} and ${functionName}. A schedule table can only be used by one reducer or procedure.`
        );

View on GitHub (pinned to eddf9f5014)