Loading...
Loading...
SpacetimeDB C# server module SDK reference. Use when writing tables, reducers, or module logic in C#.
npx skill4agent add clockworklabs/spacetimedb csharp-serverspacetime init my-project --lang csharp --dotnet-version 10
# From the generated module directory:
dotnet build # managed DLL
dotnet publish -c Release -r wasi-wasm # NativeAOT-LLVM WASM
spacetime build # CLI selects WASM automaticallyRuntimeIdentifier=wasi-wasmPublishTrimmedSelfContainedMSBuildEnableWorkloadResolver=falseMicrosoft.DotNet.ILCompiler.LLVMruntime.$(NETCoreSdkPortableRuntimeIdentifier).Microsoft.DotNet.ILCompiler.LLVM10.0.0-*RuntimeIdentifier=wasi-wasmspacetime buildspacetime publish--native-aot--no-restore-f net10.0--native-aotSPACETIMEDB_WASM_AOTEXPERIMENTAL_WASM_AOTProjectReferencestatic partial classpublic partial structpublic static partial class Moduleusing SpacetimeDB;public staticusing 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 });
}
}[SpacetimeDB.Table(...)]public partial structAccessor[SpacetimeDB.Table(Accessor = "Entity", Public = true)]
public partial struct Entity
{
[PrimaryKey]
[AutoInc]
public ulong Id;
[SpacetimeDB.Index.BTree]
public Identity Owner;
public string Name;
[SpacetimeDB.Index.BTree]
public bool Active;
}Accessor = "PascalCase"Public = trueScheduled = nameof(ReducerFn)ScheduledAt = nameof(field)Event = truectx.DbAccessorctx.Db.Entityctx.Db.ScoreRecord| C# type | Notes |
|---|---|
| unsigned integers |
| large unsigned integers (SpacetimeDB types) |
| signed integers |
| large signed integers (SpacetimeDB types) |
| floats |
| boolean |
| text |
| list/array |
| user identity |
| connection handle |
| server timestamp (microseconds since epoch) |
| duration in microseconds |
| UUID |
string? Nicknameuint? HighScore[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)
[Default(true)] // migration-safe default for a newly appended field[Default(...)][SpacetimeDB.Index.BTree]// 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; ... }[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) { ... }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 PK.ToList()ctx.Db.{Accessor}.{ColumnName}.{Method}(value)[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) { ... }ctx.ConnectionIdConnectionId?// Anonymous view (same result for all clients):
[SpacetimeDB.View(Accessor = "ActiveUsers", Public = true)]
public static List<Entity> ActiveUsers(AnonymousViewContext ctx)
{
return ctx.Db.Entity.Active.Filter(true).ToList();
}
// Per-user view:
[SpacetimeDB.View(Accessor = "MyEntities", Public = true)]
public static List<Entity> MyEntities(ViewContext ctx)
{
return ctx.Db.Entity.Owner.Filter(ctx.Sender).ToList();
}CountFindFilterIter()ViewContextctx.FromIQuery<T>WhereRightSemijoinctx.From.Article().Where(article => article.Published.Eq(true));
ctx.From.Subscription().RightSemijoin(
ctx.From.Account(),
(subscription, account) => subscription.AccountId.Eq(account.Id)
);[SpacetimeDB.View(Accessor = "CatalogEntry", Public = true, PrimaryKey = nameof(CatalogRow.Sku))]ctx.Db.Shipment.DeliverBy.Filter((new Timestamp(1_000), new Timestamp(2_000)))[SpacetimeDB.View(Accessor = "EntityById", Public = true)]
public static Entity? EntityById(AnonymousViewContext ctx, ulong id) =>
ctx.Db.Entity.Id.Find(id);[ClientVisibilityFilter]
public static readonly Filter PrivateNoteFilter = new Filter.Sql(
"SELECT * FROM OwnedRow WHERE Owner = :sender"
);ReducerContextctx// 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 + new TimeDuration(delayMicros);
// Deterministic RNG
int roll = ctx.Rng.Next(1, 7); // [1, 7): inclusive 1, exclusive 7
double f = ctx.Rng.NextDouble(); // [0.0, 1.0)
// Timestamp → milliseconds since epoch
timestamp.MicrosecondsSinceUnixEpoch / 1000ProjectReference<Compile Remove="libraries/**/*.cs" />publicNamespace// Root Lib.cs, before namespace/type declarations:
[assembly: SpacetimeDB.Namespace(typeof(AuthLib.Marker), Accessor = "MyAuth", Name = "auth_data")]ctx.Db.MyAuth.Userctx.From.MyAuth.User()ctx.Db.Userctx.From.User()ctx.As.MyAuthtx.As.MyAuthWithTxctxInvalidOperationExceptionAccessorNameauth_data.<table>NameMyAuthmy_authNamepublicpublic[SpacetimeDB.Env]
public partial struct EnvironmentSchema
{
public string API_KEY;
[SpacetimeDB.EnvValues("development", "production")]
public string MODE;
public string? LOG_LEVEL;
}
// In a root entry point:
// ctx.Env.API_KEY: string; ctx.Env.LOG_LEVEL: string?
// ctx.Env.Get("LOG_LEVEL"): string?string?[SpacetimeDB.Env][EnvValues(...)]Get("KEY")Modulenameof(...)[SpacetimeDB.Table(
Accessor = "TickTimer",
Scheduled = nameof(Tick),
ScheduledAt = nameof(ScheduledAt),
Public = true
)]
public partial struct TickTimer
{
[PrimaryKey]
[AutoInc]
public ulong ScheduledId;
public ScheduleAt ScheduledAt;
}
[SpacetimeDB.Reducer]
public static void Tick(ReducerContext ctx, TickTimer timer)
{
// timer row is auto-deleted after this reducer runs
}
// One-time: fires once at a specific time
var at = new ScheduleAt.Time(ctx.Timestamp + new TimeDuration(10_000_000));
// Repeating: fires on an interval
var at = new ScheduleAt.Interval(TimeSpan.FromSeconds(5));
ctx.Db.TickTimer.Insert(new TickTimer { ScheduledId = 0, ScheduledAt = at });[SpacetimeDB.Reducer]ReducerKind.ScheduledScheduledConnectionIdConnectionId.From(bytes)#pragma warning disable STDB_UNSTABLEProcedureContext[SpacetimeDB.Type][SpacetimeDB.Type]
public partial struct ResultValue { public string Value; }
[SpacetimeDB.Procedure]
public static ResultValue Inspect(ProcedureContext ctx, string input) =>
new() { Value = input };ctx.Httpctx.WithTxvar result = ctx.Http.Get(uri);
var text = result.Match(
response => response.Body.ToStringUtf8Lossy(),
error => throw new Exception(error.Message)
);
ctx.WithTx(tx =>
{
tx.Db.ScoreRecord.Insert(new ScoreRecord { Id = 0, Owner = ctx.Sender, Value = 1 });
return 0;
});WithTxreturn 0;tx.Dbctx.DbHttpRequestvar result = ctx.Http.Send(new HttpRequest
{
Uri = uri,
Method = SpacetimeDB.HttpMethod.Post,
Headers = new() { new HttpHeader("content-type", "text/plain") },
Body = HttpBody.FromString(payload),
});HttpResponse.StatusCodeushortHttpHeaderValuebyte[]System.Text.Encoding.UTF8.GetString(header.Value)HttpBody.FromString(...)new HttpBody(bytes)ToBytes()ToStringUtf8Lossy()HttpBody.FromBytesToString()SpacetimeDB.HttpMethodSystem.Net.HttpScheduled[SpacetimeDB.Procedure]ProcedureContextctx.WithTxctx.WithTx[SpacetimeDB.HttpHandler]
public static HttpResponse Health(HandlerContext ctx, HttpRequest request) => new(
200, HttpVersion.Http11, new(), HttpBody.FromString("ok")
);
[SpacetimeDB.HttpRouter]
public static Router Routes() => SpacetimeDB.Router.New().Get("/health", Handlers.Health);Handlers[SpacetimeDB.HttpHandler]Handlers.MethodName[SpacetimeDB.Type]
public enum Status { Online, Away, Offline }
[SpacetimeDB.Type]
public partial struct Point { public float X; public float Y; }partial record[Type] partial struct[SpacetimeDB.Type]
public partial struct Circle { public int Radius; }
[SpacetimeDB.Type]
public partial struct Rectangle { public int Width; public int Height; }
[SpacetimeDB.Type]
public partial record Shape : SpacetimeDB.TaggedEnum<(Circle Circle, Rectangle Rectangle)> { }
// Construct variants via the generated nested constructors:
var a = new Shape.Circle(new Circle { Radius = 10 });
var b = new Shape.Rectangle(new Rectangle { Width = 4, Height = 6 });