/csharp-server
SpacetimeDB C# server module SDK reference. Use when writing tables, reducers, or module logic in C#.
$ npx -y skills add clockworklabs/spacetimedb --skill csharp-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
/csharp-server
Context preview
The summary Claude sees to decide when to auto-load this skill.
SpacetimeDB C# server module SDK reference. Use when writing tables, reducers, or module logic in C#.
SKILL.md
csharp-server.SKILL.mdname: csharp-server
description: SpacetimeDB C# server module SDK reference. Use when writing tables, reducers, or module logic in C#.
license: Apache-2.0
metadata:
author: clockworklabs
version: "2.0"
role: server
language: csharp
cursor_globs: "**/*.cs"
cursor_always_apply: true
SpacetimeDB C# SDK Reference
Module Structure
Reducers are static methods in a `static partial class`; tables are `public partial struct`s. This reference keeps everything in one `public static partial class Module`, which needs only `using SpacetimeDB;`:
using SpacetimeDB;
public static partial class Module
{
[SpacetimeDB.Table(Accessor = "ScoreRecord", Public = true)]
public partial struct ScoreRecord
{
[PrimaryKey]
[AutoInc]
public ulong Id;
public Identity Owner;
public uint Value;
}
[SpacetimeDB.Reducer]
public static void AddRecord(ReducerContext ctx, uint value)
{
ctx.Db.ScoreRecord.Insert(new ScoreRecord { Id = 0, Owner = ctx.Sender, Value = value });
}
}Tables
`[SpacetimeDB.Table(...)]` on a `public partial struct`. `Accessor` should be PascalCase:
[SpacetimeDB.Table(Accessor = "Entity", Public = true)]
public partial struct Entity
{
[PrimaryKey]
[AutoInc]
public ulong Id;
public Identity Owner;
public string Name;
public bool Active;
}Options: `Accessor = "PascalCase"` (recommended), `Public = true`, `Scheduled = nameof(ReducerFn)`, `ScheduledAt = nameof(field)`, `Event = true`
`ctx.Db` accessors use the `Accessor` name: `ctx.Db.Entity`, `ctx.Db.ScoreRecord`.
Column Types
| C# type | Notes | |---------|-------| | `byte` / `ushort` / `uint` / `ulong` | unsigned integers | | `U128` / `U256` | large unsigned integers (SpacetimeDB types) | | `sbyte` / `short` / `int` / `long` | signed integers | | `I128` / `I256` | large signed integers (SpacetimeDB types) | | `float` / `double` | floats | | `bool` | boolean | | `string` | text | | `List<T>` | list/array | | `Identity` | user identity | | `ConnectionId` | connection handle | | `Timestamp` | server timestamp (microseconds since epoch) | | `TimeDuration` | duration in microseconds | | `Uuid` | UUID |
Optional columns: nullable types (`string? Nickname`, `uint? HighScore`)
Column Attributes
The complete set of column attributes:
[PrimaryKey] // primary key
[AutoInc] // auto-increment (use 0 as placeholder on insert)
[Unique] // unique constraint; indexes the column, enables .Find()
[SpacetimeDB.Index.BTree] // btree index (enables .Filter() on this column)
Indexes
Write the index attribute fully qualified: `[SpacetimeDB.Index.BTree]`. Prefer inline for single-column; multi-column uses struct-level:
// Inline (preferred for single-column):
[SpacetimeDB.Index.BTree]
public ulong AuthorId;
// Access: ctx.Db.Post.AuthorId.Filter(authorId)
// Multi-column (struct-level):
[SpacetimeDB.Table(Accessor = "Membership")]
[SpacetimeDB.Index.BTree(Accessor = "ByGroupUser", Columns = new[] { nameof(GroupId), nameof(UserId) })]
public partial struct Membership { public ulong GroupId; public Identity UserId; ... }Prefer a multi-column index over filtering by one column and looping.
Reducers
[SpacetimeDB.Reducer]
public static void CreateEntity(ReducerContext ctx, string name, int age)
{
ctx.Db.Entity.Insert(new Entity { Owner = ctx.Sender, Name = name, Age = age, Active = true });
}
// No arguments:
[SpacetimeDB.Reducer]
public static void DoReset(ReducerContext ctx) { ... }DB Operations
var row = ctx.Db.Entity.Insert(new Entity { Name = "Sample" }); // Insert; returns the row with AutoInc fields assigned
ctx.Db.Entity.Id.Find(entityId); // Find by PK → Entity? (nullable)
ctx.Db.Entity.Identity.Find(ctx.Sender); // Find by unique column → Entity?
if (ctx.Db.Entity.Id.Find(entityId) is { } entity) { ... } // unwrap Entity? before member access
ctx.Db.Item.AuthorId.Filter(authorId); // Filter by index → IEnumerable<Item>
ctx.Db.Entity.Iter(); // All rows → IEnumerable<Entity>
ctx.Db.Entity.Count; // Count rows
ctx.Db.Entity.Id.Update(existing with { Name = newName }); // Update by PK
ctx.Db.Entity.Id.Delete(entityId); // Delete by PKNote: Filter/Iter return enumerables. Use `.ToList()` if you need to sort or mutate.
The pattern is `ctx.Db.{Accessor}.{ColumnName}.{Method}(value)` for all indexed column operations.
Lifecycle Hooks
[SpacetimeDB.Reducer(ReducerKind.Init)]
public static void OnInit(ReducerContext ctx) { ... }
[SpacetimeDB.Reducer(ReducerKind.ClientConnected)]
public static void OnConnect(ReducerContext ctx) { ... }
[SpacetimeDB.Reducer(ReducerKind.ClientDisconnected)]
public static void OnDisconnect(ReducerContext ctx) { ... }Views
// Anonymous view (same result for all clients):
[SpacetimeDB.View(Accessor = "ActiveUsers", Public = true)]
public static List<Entity> ActiveUsers(AnonymousViewContext ctx)
{
return ctx.Db.Entity.Iter().Where(e => e.Active).ToList();
}
// Per-user view:
[SpacetimeDB.View(Accessor = "MyProfile", Public = true)]
public static Entity? MyProfile(ViewContext ctx)
{
return ctx.Db.Entity.Identity.Find(ctx.Sender) as Entity?;
}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)
throw new Exception("unauthorized");
// Server timestamp (deterministic per reducer call)
ctx.Db.Item.Insert(new Item { CreatedAt = ctx.Timestamp, .. });
// Timestamp arithmetic
var expiry = ctx.Timestamp + nRead more
name: csharp-server description: SpacetimeDB C# server module SDK reference. Use when writing tables, reducers, or module logic in C#. license: Apache-2.0 metadata: author: clockworklabs version: "2.0" role: server language: csharp cursor_globs: "**/*.cs" cursor_always_apply: true
SpacetimeDB C# SDK Reference
Module Structure
Reducers are static methods in a `static partial class`; tables are `public partial struct`s. This reference keeps everything in one `public static partial class Module`, which needs only `using SpacetimeDB;`:
using SpacetimeDB;
public static partial class Module
{
[SpacetimeDB.Table(Accessor = "ScoreRecord", Public = true)]
public partial struct ScoreRecord
{
[PrimaryKey]
[AutoInc]
public ulong Id;
public Identity Owner;
public uint Value;
}
[SpacetimeDB.Reducer]
public static void AddRecord(ReducerContext ctx, uint value)
{
ctx.Db.ScoreRecord.Insert(new ScoreRecord { Id = 0, Owner = ctx.Sender, Value = value });
}
}Tables
`[SpacetimeDB.Table(...)]` on a `public partial struct`. `Accessor` should be PascalCase:
[SpacetimeDB.Table(Accessor = "Entity", Public = true)]
public partial struct Entity
{
[PrimaryKey]
[AutoInc]
public ulong Id;
public Identity Owner;
public string Name;
public bool Active;
}Options: `Accessor = "PascalCase"` (recommended), `Public = true`, `Scheduled = nameof(ReducerFn)`, `ScheduledAt = nameof(field)`, `Event = true`
`ctx.Db` accessors use the `Accessor` name: `ctx.Db.Entity`, `ctx.Db.ScoreRecord`.
Column Types
| C# type | Notes | |---------|-------| | `byte` / `ushort` / `uint` / `ulong` | unsigned integers | | `U128` / `U256` | large unsigned integers (SpacetimeDB types) | | `sbyte` / `short` / `int` / `long` | signed integers | | `I128` / `I256` | large signed integers (SpacetimeDB types) | | `float` / `double` | floats | | `bool` | boolean | | `string` | text | | `List<T>` | list/array | | `Identity` | user identity | | `ConnectionId` | connection handle | | `Timestamp` | server timestamp (microseconds since epoch) | | `TimeDuration` | duration in microseconds | | `Uuid` | UUID |
Optional columns: nullable types (`string? Nickname`, `uint? HighScore`)
Column Attributes
The complete set of column attributes:
[PrimaryKey] // primary key [AutoInc] // auto-increment (use 0 as placeholder on insert) [Unique] // unique constraint; indexes the column, enables .Find() [SpacetimeDB.Index.BTree] // btree index (enables .Filter() on this column)
Indexes
Write the index attribute fully qualified: `[SpacetimeDB.Index.BTree]`. Prefer inline for single-column; multi-column uses struct-level:
// Inline (preferred for single-column):
[SpacetimeDB.Index.BTree]
public ulong AuthorId;
// Access: ctx.Db.Post.AuthorId.Filter(authorId)
// Multi-column (struct-level):
[SpacetimeDB.Table(Accessor = "Membership")]
[SpacetimeDB.Index.BTree(Accessor = "ByGroupUser", Columns = new[] { nameof(GroupId), nameof(UserId) })]
public partial struct Membership { public ulong GroupId; public Identity UserId; ... }Prefer a multi-column index over filtering by one column and looping.
Reducers
[SpacetimeDB.Reducer]
public static void CreateEntity(ReducerContext ctx, string name, int age)
{
ctx.Db.Entity.Insert(new Entity { Owner = ctx.Sender, Name = name, Age = age, Active = true });
}
// No arguments:
[SpacetimeDB.Reducer]
public static void DoReset(ReducerContext ctx) { ... }DB Operations
var row = ctx.Db.Entity.Insert(new Entity { Name = "Sample" }); // Insert; returns the row with AutoInc fields assigned
ctx.Db.Entity.Id.Find(entityId); // Find by PK → Entity? (nullable)
ctx.Db.Entity.Identity.Find(ctx.Sender); // Find by unique column → Entity?
if (ctx.Db.Entity.Id.Find(entityId) is { } entity) { ... } // unwrap Entity? before member access
ctx.Db.Item.AuthorId.Filter(authorId); // Filter by index → IEnumerable<Item>
ctx.Db.Entity.Iter(); // All rows → IEnumerable<Entity>
ctx.Db.Entity.Count; // Count rows
ctx.Db.Entity.Id.Update(existing with { Name = newName }); // Update by PK
ctx.Db.Entity.Id.Delete(entityId); // Delete by PKNote: Filter/Iter return enumerables. Use `.ToList()` if you need to sort or mutate.
The pattern is `ctx.Db.{Accessor}.{ColumnName}.{Method}(value)` for all indexed column operations.
Lifecycle Hooks
[SpacetimeDB.Reducer(ReducerKind.Init)]
public static void OnInit(ReducerContext ctx) { ... }
[SpacetimeDB.Reducer(ReducerKind.ClientConnected)]
public static void OnConnect(ReducerContext ctx) { ... }
[SpacetimeDB.Reducer(ReducerKind.ClientDisconnected)]
public static void OnDisconnect(ReducerContext ctx) { ... }Views
// Anonymous view (same result for all clients):
[SpacetimeDB.View(Accessor = "ActiveUsers", Public = true)]
public static List<Entity> ActiveUsers(AnonymousViewContext ctx)
{
return ctx.Db.Entity.Iter().Where(e => e.Active).ToList();
}
// Per-user view:
[SpacetimeDB.View(Accessor = "MyProfile", Public = true)]
public static Entity? MyProfile(ViewContext ctx)
{
return ctx.Db.Entity.Identity.Find(ctx.Sender) as Entity?;
}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)
throw new Exception("unauthorized");
// Server timestamp (deterministic per reducer call)
ctx.Db.Item.Insert(new Item { CreatedAt = ctx.Timestamp, .. });
// Timestamp arithmetic
var expiry = ctx.Timestamp + nRepo: 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 - /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 - /rust-server
SpacetimeDB Rust server module SDK reference. Use when writing tables, reducers, or module logic in Rust.
Open skill

