Skip to main content
Version: 2.0.0

Lifecycle Reducers

Special reducers handle system events during the database lifecycle.

Init Reducer​

Runs once when the module is first published or when the database is cleared.

export const init = spacetimedb.init((ctx) => {
  console.log('Database initializing...');
  
  // Set up default data
  if (ctx.db.settings.count() === 0n) {
    ctx.db.settings.insert({
      key: 'welcome_message',
      value: 'Hello, SpacetimeDB!'
    });
  }
});

The init reducer:

  • Cannot take arguments beyond ReducerContext
  • Runs when publishing with spacetime publish
  • Runs when clearing with spacetime publish -c
  • Failure prevents publishing or clearing
Module Owner

In the init reducer, ctx.sender() is the module owner — the identity of the user who published the database. This is the only place where the owner identity is automatically provided, so if you need to reference it later (e.g. for authorization), store it in a table during init:

const config = table({ name: 'config' }, {
  ownerIdentity: t.identity().primaryKey(),
});

export const init = spacetimedb.init((ctx) => {
  ctx.db.config.insert({ ownerIdentity: ctx.sender });
});

You can then check ctx.sender() against the stored owner identity in other reducers to restrict admin-only operations.

Client Connected​

Runs when a client establishes a connection.

export const onConnect = spacetimedb.clientConnected((ctx) => {
  console.log(`Client connected: ${ctx.sender}`);
  
  // TypeScript exposes this as ConnectionId | null, so guard it before use.
  const connId = ctx.connectionId;
  if (connId === null) {
    throw new Error('client connection ID missing');
  }
  
  // Initialize client session
  ctx.db.sessions.insert({
    connectionId: connId,
    identity: ctx.sender,
    connectedAt: ctx.timestamp
  });
});

The client_connected reducer:

  • Cannot take arguments beyond ReducerContext
  • Receives the connection ID for the connection being opened. The API exposes it as nullable or optional, so guard or unwrap it before use.
  • Failure disconnects the client
  • Runs for each distinct connection (WebSocket, HTTP call)

Client Disconnected​

Runs when a client connection terminates.

export const onDisconnect = spacetimedb.clientDisconnected((ctx) => {
  console.log(`Client disconnected: ${ctx.sender}`);
  
  // TypeScript exposes this as ConnectionId | null, so guard it before use.
  const connId = ctx.connectionId;
  if (connId === null) {
    throw new Error('client connection ID missing');
  }
  
  // Clean up client session
  ctx.db.sessions.connectionId.delete(connId);
});

The client_disconnected reducer:

  • Cannot take arguments beyond ReducerContext
  • Receives the connection ID for the connection being closed. The API exposes it as nullable or optional, so guard or unwrap it before use.
  • Failure is logged but doesn't prevent disconnection
  • Runs when connection ends (close, timeout, error)

Scheduled Reducers​

Reducers can be triggered at specific times using schedule tables. See Schedule Tables for details on:

  • Defining schedule tables
  • Triggering reducers at specific timestamps
  • Running reducers periodically
  • Canceling scheduled executions
Scheduled Reducer Context

Scheduled reducer calls originate from SpacetimeDB itself, not from a client. Therefore:

  • ctx.sender() will be the module's own identity
  • The connection ID will be absent (null in TypeScript, null in C#, None in Rust, and std::nullopt in C++)