/rust-server
SpacetimeDB Rust server module SDK reference. Use when writing tables, reducers, or module logic in Rust.
$ npx -y skills add clockworklabs/spacetimedb --skill rust-server --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/rust-server
Context preview
The summary Claude sees to decide when to auto-load this skill.
SpacetimeDB Rust server module SDK reference. Use when writing tables, reducers, or module logic in Rust.
SKILL.md
rust-server.SKILL.mdname: rust-server
description: SpacetimeDB Rust server module SDK reference. Use when writing tables, reducers, or module logic in Rust.
license: Apache-2.0
metadata:
author: clockworklabs
version: "2.0"
role: server
language: rust
cursor_globs: "**/*.rs"
cursor_always_apply: true
SpacetimeDB Rust SDK Reference
Imports
use spacetimedb::{
reducer, table, Identity, ReducerContext, SpacetimeType, Table,
ConnectionId, ScheduleAt, TimeDuration, Timestamp, Uuid,
};**`Table` is required.** Without it, `ctx.db.*.insert()`, `.iter()`, `.find()` etc. won't compile (`no method named 'insert' found`).
Tables
`#[spacetimedb::table(...)]` on a `pub struct`. `accessor` must be snake_case:
#[spacetimedb::table(accessor = entity, public)]
pub struct Entity {
#[primary_key]
#[auto_inc]
pub id: u64,
pub owner: Identity,
pub name: String,
pub active: bool,
}Options: `accessor = snake_case` (required), `public`, `scheduled(reducer_fn)`, `index(...)`
`ctx.db` accessors use the `accessor` name (snake_case).
Column Types
| Rust type | Notes | |-----------|-------| | `u8` / `u16` / `u32` / `u64` / `u128` | unsigned integers | | `i8` / `i16` / `i32` / `i64` / `i128` | signed integers | | `f32` / `f64` | floats | | `bool` | boolean | | `String` | text | | `Vec<T>` | list/array | | `Identity` | user identity | | `ConnectionId` | connection handle | | `Timestamp` | server timestamp (microseconds since epoch) | | `TimeDuration` | duration in microseconds | | `Uuid` | UUID | | `Option<T>` | nullable column |
Column Attributes
#[primary_key] // primary key
#[auto_inc] // auto-increment (use 0 as placeholder on insert)
#[unique] // unique constraint
#[index(btree)] // btree index (enables .filter() on this column)
Indexes
Prefer `#[index(btree)]` inline for single-column. Multi-column uses table-level:
// Inline (preferred for single-column):
#[index(btree)]
pub author_id: u64,
// Access: ctx.db.post().author_id().filter(author_id)
// Multi-column (table-level):
#[spacetimedb::table(accessor = membership, public,
index(accessor = by_group_user, btree(columns = [group_id, user_id]))
)]
pub struct Membership { pub group_id: u64, pub user_id: Identity, ... }
// Access: ctx.db.membership().by_group_user().filter((group_id, &user_id))When you frequently look up rows by multiple columns, prefer a multi-column index over filtering by one column and looping over the results.
Reducers
#[spacetimedb::reducer]
pub fn create_entity(ctx: &ReducerContext, name: String) {
ctx.db.entity().insert(Entity { id: 0, owner: ctx.sender(), name, active: true });
}
// Reducers can return Result<(), String> or Result<(), E> where E: Display
#[spacetimedb::reducer]
pub fn validate_entity(ctx: &ReducerContext, name: String) -> Result<(), String> {
if name.is_empty() {
return Err("Name cannot be empty".to_string());
}
ctx.db.entity().try_insert(Entity { id: 0, owner: ctx.sender(), name, active: true })?;
Ok(())
}Note: `insert()` panics on constraint violations. Use `try_insert()` with `?` when returning `Result`.
DB Operations
ctx.db.entity().insert(Entity { id: 0, name: "Sample".into() }); // Insert (0 for autoInc)
ctx.db.entity().id().find(entity_id); // Find by PK → Option<Entity>
ctx.db.entity().identity().find(ctx.sender()); // Find by unique column → Option<Entity>
ctx.db.item().author_id().filter(author_id); // Filter by index → iterator
ctx.db.entity().iter(); // All rows → iterator
ctx.db.entity().count(); // Count rows
ctx.db.entity().id().update(Entity { name: new_name, ..existing }); // Update (override + spread)
ctx.db.entity().id().delete(entity_id); // Delete by PK
ctx.db.entity().name().delete("Alice"); // Delete by indexed columnNote: `iter()` and `filter()` return iterators. Collect to Vec if you need `.sort()`, `.filter()`, `.map()`.
Range queries on btree indexes: `filter(18..=65)`, `filter(18..)`, `filter(..18)`.
Lifecycle Hooks
#[spacetimedb::reducer(init)]
pub fn init(ctx: &ReducerContext) { ... }
#[spacetimedb::reducer(client_connected)]
pub fn on_connect(ctx: &ReducerContext) { ... }
#[spacetimedb::reducer(client_disconnected)]
pub fn on_disconnect(ctx: &ReducerContext) { ... }Views
// Anonymous view (same result for all clients):
use spacetimedb::{view, AnonymousViewContext};
#[view(accessor = active_users, public)]
fn active_users(ctx: &AnonymousViewContext) -> Vec<Entity> {
ctx.db.entity().iter().filter(|e| e.active).collect()
}
// Per-user view (result varies by sender):
use spacetimedb::{view, ViewContext};
#[view(accessor = my_profile, public)]
fn my_profile(ctx: &ViewContext) -> Option<Entity> {
ctx.db.entity().identity().find(ctx.sender())
}Reducer Context API
`ReducerContext` (`ctx`) is the only source of sender identity, time, and randomness; stdlib clocks and RNG are unavailable in modules.
// Auth: ctx.sender() is the caller's Identity
if row.owner != ctx.sender() {
panic!("unauthorized");
// or: return Err(anyhow::anyhow!("unauthorized"));
}
// Server timestamp (deterministic per reducer call)
ctx.db.item().insert(Item { id: 0, owner: ctx.sender(), created_at: ctx.timestamp, .. });
// Timestamp arithmetic
let expiry = ctx.timestamp + TimeDuration::from_micros(delay_micros);
// Deterministic RNG: ctx.random() for a single value, ctx.rng() for the rand::Rng trait
use spacetimedb::rand::Rng;
let n: u32 = ctx.random(); // random u32
let roll: u32 = ctx.rng().gen_range(1..=6); // any rand::Rng method
// Client: Timestamp → milliseconds since epoch
timestamp.to_microsRead more
name: rust-server description: SpacetimeDB Rust server module SDK reference. Use when writing tables, reducers, or module logic in Rust. license: Apache-2.0 metadata: author: clockworklabs version: "2.0" role: server language: rust cursor_globs: "**/*.rs" cursor_always_apply: true
SpacetimeDB Rust SDK Reference
Imports
use spacetimedb::{
reducer, table, Identity, ReducerContext, SpacetimeType, Table,
ConnectionId, ScheduleAt, TimeDuration, Timestamp, Uuid,
};**`Table` is required.** Without it, `ctx.db.*.insert()`, `.iter()`, `.find()` etc. won't compile (`no method named 'insert' found`).
Tables
`#[spacetimedb::table(...)]` on a `pub struct`. `accessor` must be snake_case:
#[spacetimedb::table(accessor = entity, public)]
pub struct Entity {
#[primary_key]
#[auto_inc]
pub id: u64,
pub owner: Identity,
pub name: String,
pub active: bool,
}Options: `accessor = snake_case` (required), `public`, `scheduled(reducer_fn)`, `index(...)`
`ctx.db` accessors use the `accessor` name (snake_case).
Column Types
| Rust type | Notes | |-----------|-------| | `u8` / `u16` / `u32` / `u64` / `u128` | unsigned integers | | `i8` / `i16` / `i32` / `i64` / `i128` | signed integers | | `f32` / `f64` | floats | | `bool` | boolean | | `String` | text | | `Vec<T>` | list/array | | `Identity` | user identity | | `ConnectionId` | connection handle | | `Timestamp` | server timestamp (microseconds since epoch) | | `TimeDuration` | duration in microseconds | | `Uuid` | UUID | | `Option<T>` | nullable column |
Column Attributes
#[primary_key] // primary key #[auto_inc] // auto-increment (use 0 as placeholder on insert) #[unique] // unique constraint #[index(btree)] // btree index (enables .filter() on this column)
Indexes
Prefer `#[index(btree)]` inline for single-column. Multi-column uses table-level:
// Inline (preferred for single-column):
#[index(btree)]
pub author_id: u64,
// Access: ctx.db.post().author_id().filter(author_id)
// Multi-column (table-level):
#[spacetimedb::table(accessor = membership, public,
index(accessor = by_group_user, btree(columns = [group_id, user_id]))
)]
pub struct Membership { pub group_id: u64, pub user_id: Identity, ... }
// Access: ctx.db.membership().by_group_user().filter((group_id, &user_id))When you frequently look up rows by multiple columns, prefer a multi-column index over filtering by one column and looping over the results.
Reducers
#[spacetimedb::reducer]
pub fn create_entity(ctx: &ReducerContext, name: String) {
ctx.db.entity().insert(Entity { id: 0, owner: ctx.sender(), name, active: true });
}
// Reducers can return Result<(), String> or Result<(), E> where E: Display
#[spacetimedb::reducer]
pub fn validate_entity(ctx: &ReducerContext, name: String) -> Result<(), String> {
if name.is_empty() {
return Err("Name cannot be empty".to_string());
}
ctx.db.entity().try_insert(Entity { id: 0, owner: ctx.sender(), name, active: true })?;
Ok(())
}Note: `insert()` panics on constraint violations. Use `try_insert()` with `?` when returning `Result`.
DB Operations
ctx.db.entity().insert(Entity { id: 0, name: "Sample".into() }); // Insert (0 for autoInc)
ctx.db.entity().id().find(entity_id); // Find by PK → Option<Entity>
ctx.db.entity().identity().find(ctx.sender()); // Find by unique column → Option<Entity>
ctx.db.item().author_id().filter(author_id); // Filter by index → iterator
ctx.db.entity().iter(); // All rows → iterator
ctx.db.entity().count(); // Count rows
ctx.db.entity().id().update(Entity { name: new_name, ..existing }); // Update (override + spread)
ctx.db.entity().id().delete(entity_id); // Delete by PK
ctx.db.entity().name().delete("Alice"); // Delete by indexed columnNote: `iter()` and `filter()` return iterators. Collect to Vec if you need `.sort()`, `.filter()`, `.map()`.
Range queries on btree indexes: `filter(18..=65)`, `filter(18..)`, `filter(..18)`.
Lifecycle Hooks
#[spacetimedb::reducer(init)]
pub fn init(ctx: &ReducerContext) { ... }
#[spacetimedb::reducer(client_connected)]
pub fn on_connect(ctx: &ReducerContext) { ... }
#[spacetimedb::reducer(client_disconnected)]
pub fn on_disconnect(ctx: &ReducerContext) { ... }Views
// Anonymous view (same result for all clients):
use spacetimedb::{view, AnonymousViewContext};
#[view(accessor = active_users, public)]
fn active_users(ctx: &AnonymousViewContext) -> Vec<Entity> {
ctx.db.entity().iter().filter(|e| e.active).collect()
}
// Per-user view (result varies by sender):
use spacetimedb::{view, ViewContext};
#[view(accessor = my_profile, public)]
fn my_profile(ctx: &ViewContext) -> Option<Entity> {
ctx.db.entity().identity().find(ctx.sender())
}Reducer Context API
`ReducerContext` (`ctx`) is the only source of sender identity, time, and randomness; stdlib clocks and RNG are unavailable in modules.
// Auth: ctx.sender() is the caller's Identity
if row.owner != ctx.sender() {
panic!("unauthorized");
// or: return Err(anyhow::anyhow!("unauthorized"));
}
// Server timestamp (deterministic per reducer call)
ctx.db.item().insert(Item { id: 0, owner: ctx.sender(), created_at: ctx.timestamp, .. });
// Timestamp arithmetic
let expiry = ctx.timestamp + TimeDuration::from_micros(delay_micros);
// Deterministic RNG: ctx.random() for a single value, ctx.rng() for the rand::Rng trait
use spacetimedb::rand::Rng;
let n: u32 = ctx.random(); // random u32
let roll: u32 = ctx.rng().gen_range(1..=6); // any rand::Rng method
// Client: Timestamp → milliseconds since epoch
timestamp.to_microsRepo: clockworklabs/spacetimedb
Other skills on spacetimedb.
- /cli
SpacetimeDB CLI reference for initializing projects, building modules, publishing databases, querying data, and managing servers
Open skill - /concepts
Understand SpacetimeDB architecture and core concepts. Use when learning SpacetimeDB or making architectural decisions.
Open skill - /cpp-server
SpacetimeDB C++ server module SDK reference. Use when writing tables, reducers, or module logic in C++.
Open skill - /csharp-client
SpacetimeDB C#/.NET client SDK reference. Use when building C# clients that connect to SpacetimeDB (console, desktop, or any .NET app).
Open skill - /csharp-server
SpacetimeDB C# server module SDK reference. Use when writing tables, reducers, or module logic in C#.
Open skill - /mcp
Operate a running SpacetimeDB database through MCP tools rather than the CLI - list databases, read schemas, run SQL, and call reducers. Use when the client exposes spacetimedb MCP tools and the task is to inspect or change data in a live database.
Open skill

