▸ Agent Skills
4 min read

Version: 2.0.0

On this page

Every reducer receives a special context parameter as its first argument. This context provides read-write access to the database, information about the caller, and additional utilities like random number generation.

The reducer context is required for accessing tables, executing database operations, and retrieving metadata about the current reducer invocation.

Accessing the Database

The primary purpose of the reducer context is to provide access to the module’s database tables.

  • TypeScript
  • C#
  • Rust
  • C++
import { schema, table, t } from 'spacetimedb/server';

const user = table(
  { name: 'user', public: true },
  {
    id: t.u64().primaryKey().autoInc(),
    name: t.string(),
  }
);

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

export const create_user = spacetimedb.reducer({ name: t.string() }, (ctx, { name }) => {
  ctx.db.user.insert({ id: 0n, name });
});
using SpacetimeDB;

public static partial class Module
{
    [SpacetimeDB.Table]
    public partial struct User
    {
        [SpacetimeDB.PrimaryKey]
        [SpacetimeDB.AutoInc]
        public ulong id;
        public string name;
    }

    [SpacetimeDB.Reducer]
    public static void CreateUser(ReducerContext ctx, string name)
    {
        ctx.Db.User.Insert(new User { id = 0, name = name });
    }
}

C# table accessors

Table accessors use PascalCase: ctx.Db.User, ctx.Db.Player. The codegen derives these from table names.

Required for Reducers

Every reducer that uses ctx.db.*.insert(), .iter(), .get_by_id(), etc. must include Table in its imports: use spacetimedb::{..., Table}; Without it you get: no method named 'insert' found.

use spacetimedb::{table, reducer, ReducerContext, Table};

#[table(accessor = user)]
pub struct User {
    #[primary_key]
    #[auto_inc]
    id: u64,
    name: String,
}

#[reducer]
fn create_user(ctx: &ReducerContext, name: String) {
    ctx.db.user().insert(User { id: 0, name });
}
#include <spacetimedb.h>
using namespace SpacetimeDB;

struct User {
    uint64_t id;
    std::string name;
};
SPACETIMEDB_STRUCT(User, id, name);
SPACETIMEDB_TABLE(User, user, Public);
FIELD_PrimaryKeyAutoInc(user, id);

SPACETIMEDB_REDUCER(create_user, ReducerContext ctx, std::string name) {
    ctx.db[user].insert(User{0, name});
    return Ok();
}

Caller Information

The context provides information about who invoked the reducer and when.

Sender Identity

Every reducer invocation has an associated caller identity.

  • TypeScript
  • C#
  • Rust
  • C++
import { schema, table, t } from 'spacetimedb/server';

const player = table(
  { name: 'player', public: true },
  {
    identity: t.identity().primaryKey(),
    name: t.string(),
    score: t.u32(),
  }
);

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

export const update_score = spacetimedb.reducer({ newScore: t.u32() }, (ctx, { newScore }) => {
  // Get the caller's identity
  const caller = ctx.sender;
  
  // Find and update their player record
  const existingPlayer = ctx.db.player.identity.find(caller);
  if (existingPlayer) {
    ctx.db.player.identity.update({
      ...existingPlayer,
      score: newScore,
    });
  }
});
using SpacetimeDB;

public static partial class Module
{
    [SpacetimeDB.Table]
    public partial struct Player
    {
        [SpacetimeDB.PrimaryKey]
        public Identity Identity;
        public string Name;
        public uint Score;
    }

    [SpacetimeDB.Reducer]
    public static void UpdateScore(ReducerContext ctx, uint newScore)
    {
        // Get the caller's identity
        Identity caller = ctx.Sender;
        
        // Find and update their player record
        if (ctx.Db.Player.Identity.Find(caller) is Player player)
        {
            player.Score = newScore;
            ctx.Db.Player.Identity.Update(player);
        }
    }
}
use spacetimedb::{table, reducer, ReducerContext, Identity, Table};

#[table(accessor = player)]
pub struct Player {
    #[primary_key]
    identity: Identity,
    name: String,
    score: u32,
}

#[reducer]
fn update_score(ctx: &ReducerContext, new_score: u32) {
    // Get the caller's identity
    let caller = ctx.sender();
    
    // Find and update their player record
    if let Some(mut player) = ctx.db.player().identity().find(caller) {
        player.score = new_score;
        ctx.db.player().identity().update(player);
    }
}
#include <spacetimedb.h>
using namespace SpacetimeDB;

struct Player {
    Identity identity;
    std::string name;
    uint32_t score;
};
SPACETIMEDB_STRUCT(Player, identity, name, score);
SPACETIMEDB_TABLE(Player, player, Public);
FIELD_PrimaryKey(player, identity);

SPACETIMEDB_REDUCER(update_score, ReducerContext ctx, uint32_t new_score) {
    // Get the caller's identity
    auto caller = ctx.sender();
    
    // Find and update their player record
    if (auto player = ctx.db[player_identity].find(caller)) {
        player->score = new_score;
        ctx.db[player_identity].update(*player);
    }
    return Ok();
}

Connection ID

The connection ID identifies the specific client connection that invoked the reducer. This is useful for tracking sessions or implementing per-connection state.

note

The connection ID may be absent for reducers invoked by the system (such as scheduled reducers or lifecycle reducers) or when called via the CLI without specifying a connection. In TypeScript modules, ctx.connectionId is ConnectionId | null.

Timestamp

The timestamp indicates when the reducer was invoked. This value is consistent throughout the reducer execution and is useful for timestamping events or implementing time-based logic.

Random Number Generation

The context provides access to a random number generator that is deterministic and reproducible. This ensures that reducer execution is consistent across all nodes in a distributed system.

warning

Never use external random number generators (like Random in C# without using the context). These are non-deterministic and will cause different nodes to produce different results, breaking consensus.

Use the context-provided random API for any reducer logic that needs random values:

  • TypeScript
  • C#
  • Rust
  • C++
const fraction = ctx.random();                         // [0.0, 1.0)
const roll = ctx.random.integerInRange(1, 6);          // inclusive
const bytes = ctx.random.fill(new Uint8Array(16));
double fraction = ctx.Rng.NextDouble();  // [0.0, 1.0)
int roll = ctx.Rng.Next(1, 7);           // [1, 7)
use spacetimedb::rand::Rng;

let value: u32 = ctx.random();
let roll: u32 = ctx.rng().gen_range(1..=6);
auto& rng = ctx.rng();
int32_t roll = rng.gen_range(1, 6);  // inclusive

Module Identity

The context provides access to the module’s own identity, which is useful when a reducer needs to refer to the database itself.

Scheduled reducers and procedures are private by default in SpacetimeDB 2.x, so you do not need to compare the sender against the module identity to prevent ordinary clients from calling them directly. If you need both a scheduled function and a client-callable entry point, keep the scheduled function private and define a separate public reducer that wraps the shared logic.

  • TypeScript
  • C#
  • Rust
  • C++
import { schema, table, t } from 'spacetimedb/server';

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

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

export const sendReminder = spacetimedb.reducer(
  { onSchedule: scheduledTask },
  { arg: scheduledTask.rowType },
  (_ctx, { arg }) => {
    console.log(`Reminder: ${arg.message}`);
  }
);
using SpacetimeDB;

public static partial class Module
{
    [SpacetimeDB.Table(Accessor = "ScheduledTask", Scheduled = nameof(SendReminder), ScheduledAt = nameof(ScheduledAt))]
    public partial struct ScheduledTask
    {
        [SpacetimeDB.PrimaryKey]
        [SpacetimeDB.AutoInc]
        public ulong TaskId;
        public ScheduleAt ScheduledAt;
        public string Message;
    }

    [SpacetimeDB.Reducer]
    public static void SendReminder(ReducerContext _ctx, ScheduledTask task)
    {
        Log.Info($"Reminder: {task.Message}");
    }
}
use spacetimedb::{table, reducer, ReducerContext, ScheduleAt};

#[table(accessor = scheduled_task, scheduled(send_reminder))]
pub struct ScheduledTask {
    #[primary_key]
    #[auto_inc]
    task_id: u64,
    scheduled_at: ScheduleAt,
    message: String,
}

#[reducer]
fn send_reminder(_ctx: &ReducerContext, task: ScheduledTask) {
    spacetimedb::log::info!("Reminder: {}", task.message);
}
#include <spacetimedb.h>
using namespace SpacetimeDB;

struct ScheduledTask {
    uint64_t task_id;
    ScheduleAt scheduled_at;
    std::string message;
};
SPACETIMEDB_STRUCT(ScheduledTask, task_id, scheduled_at, message);
SPACETIMEDB_TABLE(ScheduledTask, scheduled_task, Private);
FIELD_PrimaryKeyAutoInc(scheduled_task, task_id);

// Register the table for scheduling (column 1 = scheduled_at field, 0-based index)
SPACETIMEDB_SCHEDULE(scheduled_task, 1, send_reminder);

SPACETIMEDB_REDUCER(send_reminder, ReducerContext _ctx, ScheduledTask task) {
    LOG_INFO("Reminder: " + task.message);
    return Ok();
}

Context Properties Reference

  • TypeScript
  • C#
  • Rust
  • C++
PropertyTypeDescription
dbDbViewAccess to the module’s database tables
senderIdentityIdentity of the caller
senderAuthAuthCtxAuthorization context for the caller (includes JWT claims and internal call detection)
connectionId`ConnectionIdnull`
timestampTimestampTime when the reducer was invoked
randomRandomRandom number generator (deterministic, seeded by SpacetimeDB)
PropertyTypeDescription
DbDbViewAccess to the module’s database tables
SenderIdentityIdentity of the caller
SenderAuthAuthCtxAuthorization context for the caller (includes JWT claims and internal call detection)
ConnectionIdConnectionId?Connection ID of the caller, if available
TimestampTimestampTime when the reducer was invoked
RngRandomRandom number generator
DatabaseIdentityIdentityThe module’s identity
PropertyTypeDescription
dbLocalAccess to the module’s database tables
senderIdentityIdentity of the caller
connection_idOption<ConnectionId>Connection ID of the caller, if available
timestampTimestampTime when the reducer was invoked

Methods:

  • database_identity() -> Identity - Get the module’s identity
  • rng() -> &StdbRng - Get the random number generator
  • random\<T\>() -> T - Generate a single random value
  • sender_auth() -> &AuthCtx - Get authorization context for the caller (includes JWT claims and internal call detection)
PropertyTypeDescription
db[table]Table<T>Access to a specific table’s operations
senderIdentityIdentity of the caller
timestampTimestampTime when the reducer was invoked
connection_idstd::optional<ConnectionId>Connection ID of the caller, if available

Methods:

  • database_identity() -> Identity - Get the module’s identity
  • rng() -> StdbRng& - Get the random number generator (deterministic and reproducible)
  • sender_auth() -> const AuthCtx& - Get authorization context for the caller (includes JWT claims and internal call detection)

note

C++ uses the std::optional type for the connection_id to represent values that may not be present. The rng() method returns a deterministic random number generator that is seeded consistently across all nodes.


Last updated Oct 08, 2026