douglance/spacetimedb/spacetimedb-csharp

Claude Code and agent skill by douglance/spacetimedb

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Build C# modules and Unity clients for SpacetimeDB. Covers server-side module development and client SDK integration.

Install

npx vskill@latest install douglance/spacetimedb/spacetimedb-csharp

SKILL.md

SpacetimeDB C# SDK

This skill provides comprehensive guidance for building C# server-side modules and Unity/C# clients that connect to SpacetimeDB.


HALLUCINATED APIs — DO NOT USE

These APIs DO NOT EXIST. LLMs frequently hallucinate them.

csharp
// WRONG — these do not exist
[SpacetimeDB.Procedure] // C# does NOT support procedures yet!
ctx.db.tableName // Wrong casing, should be PascalCase
ctx.Db.tableName.Get(id) // Use Find, not Get
ctx.Db.TableName.FindById(id) // Use index accessor: ctx.Db.TableName.Id.Find(id)
ctx.Db.table.field_name.Find(x) // Wrong! Use PascalCase: ctx.Db.Table.FieldName.Find(x)
Optional<string> field; // Use C# nullable: string? field
// WRONG — missing partial keyword
public struct MyTable { } // Must be "partial struct"
public class Module { } // Must be "static partial class"
// WRONG — non-partial types
[SpacetimeDB.Table(Name = "player")]
public struct Player { } // WRONG — missing partial!
// WRONG — sum type syntax (VERY COMMON MISTAKE)
public partial struct Shape : TaggedEnum<(Circle, Rectangle)> { } // WRONG: struct, missing names
public partial record Shape : TaggedEnum<(Circle, Rectangle)> { } // WRONG: missing variant names
public partial class Shape : TaggedEnum<(Circle Circle, Rectangle Rectangle)> { } // WRONG: class
// WRONG — Index attribute without full qualification
[Index.BTree(Name = "idx", Columns = new[] { "Col" })] // Ambiguous with System.Index!
[Index.BTree(Name = "idx", Columns = ["Col"])] // Collection expressions don't work in attributes!
CORRECT PATTERNS
csharp
// CORRECT IMPORTS
using SpacetimeDB;
// CORRECT TABLE — must be partial struct
[SpacetimeDB.Table(Name = "player", Public = true)]
public partial struct Player
{
[SpacetimeDB.PrimaryKey]
[SpacetimeDB.AutoInc]
public ulong Id;
public Identity OwnerId;
public string Name;
}
// CORRECT MODULE — must be static partial class
public static partial class Module
{
[SpacetimeDB.Reducer]
public static void CreatePlayer(ReducerContext ctx, string name)
{
ctx.Db.Player.Insert(new Player { Id = 0, OwnerId = ctx.Sender, Name = name });
}
}
// CORRECT DATABASE ACCESS — PascalCase, index-based lookups
var player = ctx.Db.Player.Id.Find(playerId);
var player = ctx.Db.Player.OwnerId.Find(ctx.Sender);
// CORRECT SUM TYPE — partial record with named tuple elements
[SpacetimeDB.Type]
public partial record Shape : TaggedEnum<(Circle Circle, Rectangle Rectangle)> { }
DO NOT
  • Forget `partial` keyword — required on all tables and Module class
  • Use lowercase table access — ctx.Db.Player not ctx.Db.player
  • Try to use procedures — C# does not support procedures yet
  • Use `Optional<T>` — use C# nullable syntax T? instead
  • Use struct for sum types — must be partial record

Common Mistakes Table

Server-side errors
WrongRightError
Missing partial keywordpublic partial struct TableGenerated code won't compile
ctx.Db.player (lowercase)ctx.Db.Player (PascalCase)Property not found
Optional<string>string?Type not found
ctx.Db.Table.Get(id)ctx.Db.Table.Id.Find(id)Method not found
Wrong .csproj nameStdbModule.csprojPublish fails silently
.NET 9 SDK.NET 8 SDK onlyWASI compilation fails
Missing WASI workloaddotnet workload install wasi-experimentalBuild fails
[Procedure] attributeReducers onlyProcedures not supported in C#
Missing Public = trueAdd to [Table] attributeClients can't subscribe
Using RandomAvoid non-deterministic codeSandbox violation
async/await in reducersSynchronous onlyNot supported
[Index.BTree(...)][SpacetimeDB.Index.BTree(...)]Ambiguous with System.Index
Columns = ["A", "B"]Columns = new[] { "A", "B" }Collection expressions invalid in attributes
partial struct : TaggedEnumpartial record : TaggedEnumSum types must be record
TaggedEnum<(A, B)>TaggedEnum<(A A, B B)>Tuple must include variant names
Client-side errors
WrongRightError
Wrong namespaceusing SpacetimeDB.ClientApi;Types not found
Not calling FrameTick()conn.FrameTick() in Update loopNo callbacks fire
Accessing conn.Db from background threadCopy data in callback, process elsewhereData races

Hard Requirements

  1. Tables and Module MUST be `partial` — required for code generation
  2. Use PascalCase for table access — ctx.Db.TableName, not ctx.Db.tableName
  3. Project file MUST be named `StdbModule.csproj` — CLI requirement
  4. Requires .NET 8 SDK — .NET 9 and newer not yet supported
  5. Install WASI workload — dotnet workload install wasi-experimental
  6. C# does NOT support procedures — use reducers only
  7. Reducers must be deterministic — no filesystem, network, timers, or Random
  8. Add `Public = true` — if clients need to subscribe to a table
  9. Use `T?` for nullable fields — not Optional<T>
  10. Pass `0` for auto-increment — to trigger ID generation on insert
  11. Sum types must be `partial record` — not struct or class
  12. Fully qualify Index attribute — [SpacetimeDB.Index.BTree] to avoid System.Index ambiguity

Server-Side Module Development

Table Definition (CRITICAL)

Tables MUST use `partial struct` or `partial class` for code generation.

csharp
using SpacetimeDB;
// WRONG — missing partial!
[SpacetimeDB.Table(Name = "player")]
public struct Player { } // Will not generate properly!
// RIGHT — with partial keyword
[SpacetimeDB.Table(Name = "player", Public = true)]
public partial struct Player
{
[SpacetimeDB.PrimaryKey]
[SpacetimeDB.AutoInc]
public ulong Id;
public Identity OwnerId;
public string Name;
public Timestamp CreatedAt;
}
// With indexes
[SpacetimeDB.Table(Name = "task", Public = true)]
public partial struct Task
{
[SpacetimeDB.PrimaryKey]
[SpacetimeDB.AutoInc]
public ulong Id;
[SpacetimeDB.Index.BTree]
public Identity OwnerId;
public string Title;
public bool Completed;
}
// Multi-column index
[SpacetimeDB.Table(Name = "score", Public = true)]
[SpacetimeDB.Index.BTree(Name = "by_player_game", Columns = new[] { "PlayerId", "GameId" })]
public partial struct Score
{
[SpacetimeDB.PrimaryKey]
[SpacetimeDB.AutoInc]
public ulong Id;
public Identity PlayerId;
public string GameId;
public int Points;
}
Field Attributes
csharp
[SpacetimeDB.PrimaryKey] // Exactly one per table (required)
[SpacetimeDB.AutoInc] // Auto-increment (integer fields only)
[SpacetimeDB.Unique] // Unique constraint
[SpacetimeDB.Index.BTree] // Single-column B-tree index
[SpacetimeDB.Default(value)] // Default value for new columns
Column Types
csharp
byte, sbyte, short, ushort // 8/16-bit integers
int, uint, long, ulong // 32/64-bit integers
float, double // Floats
bool // Boolean
string // Text
Identity // User identity
Timestamp // Timestamp
ScheduleAt // For scheduled tables
T? // Nullable (e.g., string?)
List<T> // Arrays
Insert with Auto-increment
csharp
// Insert returns the row with generated ID
var player = ctx.Db.Player.Insert(new Player
{
Id = 0, // Pass 0 to trigger auto-increment
OwnerId = ctx.Sender,
Name = name,
CreatedAt = ctx.Timestamp
});
ulong newId = player.Id; // Get actual generated ID
Module and Reducers

The Module class MUST be `public static partial class`.

csharp
using SpacetimeDB;
public static partial class Module
{
[SpacetimeDB.Reducer]
public static void CreateTask(ReducerContext ctx, string title)
{
// Validate
if (string.IsNullOrEmpty(title))
{
throw new Exception("Title cannot be empty"); // Rolls back transaction
}
// Insert
ctx.Db.Task.Insert(new Task
{
Id = 0,
OwnerId = ctx.Sender,
Title = title,
Completed = false
});
}
[SpacetimeDB.Reducer]
public static void CompleteTask(ReducerContext ctx, ulong taskId)
{
var task = ctx.Db.Task.Id.Find(taskId);
if (task is null)
{
throw new Exception("Task not found");
}
if (task.Value.OwnerId != ctx.Sender)
{
throw new Exception("Not authorized");
}
ctx.Db.Task.Id.Update(task.Value with { Completed = true });
}
[SpacetimeDB.Reducer]
public static void DeleteTask(ReducerContext ctx, ulong taskId)
{
ctx.Db.Task.Id.Delete(taskId);
}
}
Lifecycle Reducers
csharp
public static partial class Module
{
[SpacetimeDB.Reducer(ReducerKind.Init)]
public static void Init(ReducerContext ctx)
{
// Called once when module is first published
Log.Info("Module initialized");
}
[SpacetimeDB.Reducer(ReducerKind.ClientConnected)]
public static void OnConnect(ReducerContext ctx)
{
// ctx.Sender is the connecting client
Log.Info($"Client connected: {ctx.Sender}");
}
[SpacetimeDB.Reducer(ReducerKind.ClientDisconnected)]
public static void OnDisconnect(ReducerContext ctx)
{
// Clean up client state
Log.Info($"Client disconnected: {ctx.Sender}");
}
}
ReducerContext API
csharp
ctx.Sender // Identity of the caller
ctx.Timestamp // Current timestamp
ctx.Db // Database access
ctx.Identity // Module's own identity
ctx.ConnectionId // Connection ID (nullable)
Database Access
Naming Convention
  • Tables: Use PascalCase singular names in the Name attribute
  • [Table(Name = "User")] → ctx.Db.User
  • [Table(Name = "PlayerStats")] → ctx.Db.PlayerStats
  • Indexes: PascalCase, match field name
  • Field OwnerId with [Index.BTree] → ctx.Db.User.OwnerId
Primary Key Operations
csharp
// Find by primary key — returns nullable
if (ctx.Db.Task.Id.Find(taskId) is Task task)
{
// Use task
}
// Update by primary key
ctx.Db.Task.Id.Update(updatedTask);
// Delete by primary key
ctx.Db.Task.Id.Delete(taskId);
Index Operations
csharp
// Find by unique index — returns nullable
if (ctx.Db.Player.Username.Find("alice") is Player player)
{
// Found player
}
// Filter by B-tree index — returns iterator
foreach (var task in ctx.Db.Task.OwnerId.Filter(ctx.Sender))
{
// Process each task
}
Iterate All Rows
csharp
// Full table scan
foreach (var task in ctx.Db.Task.Iter())
{
// Process each task
}
Custom Types

Use `[SpacetimeDB.Type]` for custom structs/enums. Must be `partial`.

csharp
using SpacetimeDB;
[SpacetimeDB.Type]
public partial struct Position
{
public int X;
public int Y;
}
[SpacetimeDB.Type]
public partial struct PlayerStats
{
public int Health;
public int Mana;
public Position Location;
}
// Use in table
[SpacetimeDB.Table(Name = "player", Public = true)]
public partial struct Player
{
[SpacetimeDB.PrimaryKey]
public Identity Id;
public string Name;
public PlayerStats Stats;
}
Sum Types / Tagged Enums (CRITICAL)

Sum types MUST use `partial record` and inherit from `TaggedEnum<T>`.

csharp
using SpacetimeDB;
// Step 1: Define variant types as partial structs with [Type]
[SpacetimeDB.Type]
public partial struct Circle { public int Radius; }
[SpacetimeDB.Type]
public partial struct Rectangle { public int Width; public int Height; }

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Refreshed 17 Mar 2026, 7 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/douglance/spacetimedb/spacetimedb-csharp/badge)](https://verified-skill.com/skills/douglance/spacetimedb/spacetimedb-csharp)

Something wrong with this skill or its page? Tell us.