Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1591763
Fix getting started docs links
clockwork-labs-bot May 5, 2026
d01d936
Document server-issued token reconnect behavior
clockwork-labs-bot May 21, 2026
29444aa
Clarify Unity WebGL token guard
clockwork-labs-bot May 21, 2026
cf4cc3c
Explain Unity WebGL preprocessor guard
clockwork-labs-bot May 21, 2026
ce19fdc
docs: sync C++ skills coverage
May 21, 2026
591ede5
docs: clarify Unreal connection ticking
May 22, 2026
f515bac
docs: use csharp generate language
May 24, 2026
444ca40
docs: clarify Unreal client ticking
May 25, 2026
c2b081a
docs: update C# connection signatures
May 26, 2026
2777b47
docs: sync C++ language coverage
May 27, 2026
3d55733
docs: use deterministic C# schedule time
May 28, 2026
0a5972e
docs: clarify client frame ticking guidance
Jun 2, 2026
0ee1529
docs: fix generate usage for Unreal bindings
Jun 2, 2026
7e3906e
docs: address Unreal generate usage review
Jun 2, 2026
063b566
docs: regenerate CLI reference
Jun 2, 2026
4cbb7d3
docs: fix TypeScript view cheat sheet syntax
Jun 3, 2026
0514f32
docs: fix auth reference details
Jun 3, 2026
41d4e61
docs: update TypeScript framework integration reference
Jun 4, 2026
3a2c802
Merge branch 'master' into docs/consolidated-docs-prs
cloutiertyler Jun 4, 2026
07211db
docs: fix TypeScript identity table builder
Jun 5, 2026
f5da9ee
Merge branch 'master' into docs/consolidated-docs-prs
cloutiertyler Jun 5, 2026
3e26780
docs: fix TypeScript view context examples
Jun 6, 2026
f38031c
docs: add agent discovery metadata
Jun 6, 2026
bdea1f9
docs: align skill snippets with current APIs
Jun 7, 2026
055ed14
docs: remove obsolete scheduled reducer identity checks
Jun 8, 2026
5060f22
Merge branch 'master' into docs/consolidated-docs-prs
cloutiertyler Jun 8, 2026
87c85a7
docs: fix TypeScript count example
Jun 9, 2026
e1e3f9d
docs: fix reducer error handling identity examples
Jun 10, 2026
3a17aec
docs: fix TypeScript reducer examples
Jun 13, 2026
e7f0571
Merge remote-tracking branch 'origin/master' into docs/consolidated-d…
Jun 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# Generated files
.docusaurus
.cache-loader
static/.well-known/agent-skills/

# Misc
.DS_Store
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -495,10 +495,10 @@ A view can be written in a TypeScript module like so:
```typescript
export const my_player = spacetimedb.view(
{ name: 'my_player', public: true },
t.option(players.row()),
t.option(players.rowType),
(ctx) => {
const row = ctx.db.players.identity.find(ctx.sender);
return row ?? null;
return row ?? undefined;
}
);
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ export const create_user = spacetimedb.reducer({ name: t.string(), email: t.stri

// Modify tables
ctx.db.user.insert({
id: 0, // auto-increment will assign
id: 0n, // auto-increment will assign
name,
email
});
});
```

The first argument is the reducer name, the second defines argument types, and the third is the handler function taking `(ctx, args)`.
The exported const name becomes the reducer name. Pass an argument type object followed by a handler function taking `(ctx, args)`. For reducers with no arguments, pass only the handler function.

</TabItem>
<TabItem value="csharp" label="C#">
Expand Down Expand Up @@ -127,8 +127,8 @@ SPACETIMEDB_REDUCER(create_user, ReducerContext ctx, std::string name, std::stri
}

// Modify tables
User user{0, name, email}; // 0 for id - auto-increment will assign
ctx.db[user].insert(user);
User new_user{0, name, email}; // 0 for id - auto-increment will assign
ctx.db[user].insert(new_user);

return Ok();
}
Expand Down Expand Up @@ -160,7 +160,7 @@ Reducers have full read-write access to all tables (both public and private) thr

```typescript
ctx.db.user.insert({
id: 0, // auto-increment will assign
id: 0n, // auto-increment will assign
name: 'Alice',
email: 'alice@example.com'
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,15 +274,15 @@ Never use external random number generators (like `Random` in C# without using t

## Module Identity

The context provides access to the module's own identity, which is useful for distinguishing between user-initiated and system-initiated reducer calls.
The context provides access to the module's own identity, which is useful when a reducer needs to refer to the database itself.

This is particularly important for [scheduled reducers](./00300-reducers.md) that should only be invoked by the system, not by external clients.
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.

<Tabs groupId="server-language" queryString>
<TabItem value="typescript" label="TypeScript">

```typescript
import { schema, table, t, SenderError } from 'spacetimedb/server';
import { schema, table, t } from 'spacetimedb/server';

const scheduledTask = table(
{ name: 'scheduled_task', scheduled: (): any => send_reminder },
Expand All @@ -296,12 +296,7 @@ const scheduledTask = table(
const spacetimedb = schema({ scheduledTask });
export default spacetimedb;

export const send_reminder = spacetimedb.reducer({ arg: scheduledTask.rowType }, (ctx, { arg }) => {
// Only allow the scheduler (module identity) to call this
if (ctx.sender != ctx.identity) {
throw new SenderError('This reducer can only be called by the scheduler');
}

export const send_reminder = spacetimedb.reducer({ arg: scheduledTask.rowType }, (_ctx, { arg }) => {
console.log(`Reminder: ${arg.message}`);
});
```
Expand All @@ -325,14 +320,8 @@ public static partial class Module
}

[SpacetimeDB.Reducer]
public static void SendReminder(ReducerContext ctx, ScheduledTask task)
public static void SendReminder(ReducerContext _ctx, ScheduledTask task)
{
// Only allow the scheduler (module identity) to call this
if (ctx.Sender != ctx.Identity)
{
throw new Exception("This reducer can only be called by the scheduler");
}

Log.Info($"Reminder: {task.message}");
}
}
Expand All @@ -354,12 +343,7 @@ pub struct ScheduledTask {
}

#[reducer]
fn send_reminder(ctx: &ReducerContext, task: ScheduledTask) {
// Only allow the scheduler (module identity) to call this
if ctx.sender() != ctx.identity() {
panic!("This reducer can only be called by the scheduler");
}

fn send_reminder(_ctx: &ReducerContext, task: ScheduledTask) {
spacetimedb::log::info!("Reminder: {}", task.message);
}
```
Expand All @@ -383,12 +367,7 @@ 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) {
// Only allow the scheduler (module identity) to call this
if (ctx.sender() != ctx.identity()) {
return Err("This reducer can only be called by the scheduler");
}

SPACETIMEDB_REDUCER(send_reminder, ReducerContext _ctx, ScheduledTask task) {
LOG_INFO("Reminder: " + task.message);
return Ok();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const init = spacetimedb.init((ctx) => {
console.log('Database initializing...');

// Set up default data
if (ctx.db.settings.count === 0) {
if (ctx.db.settings.count() === 0n) {
ctx.db.settings.insert({
key: 'welcome_message',
value: 'Hello, SpacetimeDB!'
Expand Down Expand Up @@ -133,7 +133,7 @@ export const init = spacetimedb.init((ctx) => {
<TabItem value="csharp" label="C#">

```csharp
[SpacetimeDB.Table(Name = "Config")]
[SpacetimeDB.Table(Accessor = "Config")]
public partial struct Config
{
[SpacetimeDB.PrimaryKey]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ Throw a `SenderError`:
import { SenderError } from 'spacetimedb/server';

export const transfer_credits = spacetimedb.reducer(
{ to_user: t.u64(), amount: t.u32() },
{ to_user: t.identity(), amount: t.u32() },
(ctx, { to_user, amount }) => {
const fromUser = ctx.db.users.id.find(ctx.sender);
const fromUser = ctx.db.users.identity.find(ctx.sender);
if (!fromUser) {
throw new SenderError('User not found');
}
Expand Down Expand Up @@ -60,9 +60,9 @@ Throw an exception:

```csharp
[SpacetimeDB.Reducer]
public static void TransferCredits(ReducerContext ctx, ulong toUser, uint amount)
public static void TransferCredits(ReducerContext ctx, Identity toUser, uint amount)
{
var fromUser = ctx.Db.User.Id.Find(ctx.Sender);
var fromUser = ctx.Db.User.Identity.Find(ctx.Sender);
if (fromUser == null)
{
throw new InvalidOperationException("User not found");
Expand All @@ -86,13 +86,13 @@ Return an error:
#[reducer]
pub fn transfer_credits(
ctx: &ReducerContext,
to_user: u64,
to_user: Identity,
amount: u32
) -> Result<(), String> {
let from_balance = ctx.db.users().id().find(ctx.sender.identity)
.ok_or("User not found");
let from_user = ctx.db.users().identity().find(ctx.sender())
.ok_or("User not found")?;

if from_balance.credits < amount {
if from_user.credits < amount {
return Err("Insufficient credits".to_string());
}

Expand Down
8 changes: 4 additions & 4 deletions docs/docs/00200-core-concepts/00200-functions/00500-views.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const my_player = spacetimedb.view(
{ name: 'my_player', public: true },
t.option(players.rowType),
(ctx) => {
const row = ctx.db.players.identity.find(ctx.sender());
const row = ctx.db.players.identity.find(ctx.sender);
return row ?? undefined;
}
);
Expand Down Expand Up @@ -245,7 +245,7 @@ struct Player {
};
SPACETIMEDB_STRUCT(Player, id, identity, name)
SPACETIMEDB_TABLE(Player, player, Public)
FIELD_PrimaryKeyAuto(player, id)
FIELD_PrimaryKeyAutoInc(player, id)
FIELD_Unique(player, identity)

struct PlayerLevel {
Expand Down Expand Up @@ -294,7 +294,7 @@ Views can return `std::optional<T>` for at-most-one row, `std::vector<T>` for mu

Views use one of two context types:

- **`ViewContext`**: Provides access to the caller's `Identity` through `ctx.sender()`. Use this when the view depends on who is querying it.
- **`ViewContext`**: Provides access to the caller's `Identity` through the context's sender field or method. Use this when the view depends on who is querying it.
- **`AnonymousViewContext`**: Does not provide caller information. Use this when the view produces the same results regardless of who queries it.

Both contexts provide read-only access to tables and indexes through `ctx.db`.
Expand All @@ -305,7 +305,7 @@ The choice between `ViewContext` and `AnonymousViewContext` has significant perf

**Anonymous views can be shared across all subscribers.** When a view uses `AnonymousViewContext`, SpacetimeDB knows the result is the same for every client. The database can materialize the view once and serve that same result to all subscribers. When the underlying data changes, it recomputes the view once and broadcasts the update to everyone.

**Per-user views require separate computation for each subscriber.** When a view uses `ViewContext` and invokes `ctx.sender()`, each client potentially sees different data. SpacetimeDB must compute and track the view separately for each subscriber. With 1,000 connected users, that's 1,000 separate view computations and 1,000 separate sets of change tracking.
**Per-user views require separate computation for each subscriber.** When a view uses `ViewContext` and reads the caller's sender identity, each client potentially sees different data. SpacetimeDB must compute and track the view separately for each subscriber. With 1,000 connected users, that's 1,000 separate view computations and 1,000 separate sets of change tracking.

**Prefer `AnonymousViewContext` when possible.** Design your views to be caller-independent when the use case allows. For example:

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/00200-core-concepts/00300-tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ Use idiomatic naming conventions for each language:
| Language | Convention | Example Table | Example Accessor |
|----------|------------|---------------|------------------|
| **TypeScript** | snake_case | `'player_score'` | `ctx.db.playerScore` |
| **C#** | PascalCase | `Name = "PlayerScore"` | `ctx.Db.PlayerScore` |
| **C#** | PascalCase | `Accessor = "PlayerScore"` | `ctx.Db.PlayerScore` |
| **Rust** | lower_snake_case | `name = player_score` | `ctx.db.player_score()` |
| **C++** | lower_snake_case | `player_score` | `ctx.db[player_score]` |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ export const register_document = spacetimedb.reducer({
storageUrl: t.string(),
}, (ctx, { filename, mimeType, sizeBytes, storageUrl }) => {
ctx.db.document.insert({
id: 0, // auto-increment
id: 0n, // auto-increment
ownerId: ctx.sender,
filename,
mimeType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,9 @@ Reducers receive a `ReducerContext` which provides full read-write access to all
<TabItem value="typescript" label="TypeScript">

```typescript
export const example = spacetimedb.reducer({}, (ctx) => {
export const example = spacetimedb.reducer((ctx) => {
// Insert
ctx.db.user.insert({ id: 0, name: 'Alice', email: 'alice@example.com' });
ctx.db.user.insert({ id: 0n, name: 'Alice', email: 'alice@example.com' });

// Read: iterate all rows
for (const user of ctx.db.user.iter()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ When inserting or updating multiple rows, batch them in a single reducer call ra
export const spawn_enemies = spacetimedb.reducer({ count: t.u32() }, (ctx, { count }) => {
for (let i = 0; i < count; i++) {
ctx.db.enemy.insert({
id: 0, // auto_inc
id: 0n, // auto_inc
health: 100,
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The SpacetimeDB client for C# contains all the tools you need to build native cl
If you are **writing a SpacetimeDB module** (tables and reducers), use these patterns:

- **Module class**: `public static partial class Module`
- **Tables**: `[SpacetimeDB.Table(Accessor = "table_name", Public = true)]` on `partial struct` (or `partial class`) — `Accessor` controls generated API names, and canonical SQL names are derived unless `Name` is explicitly set
- **Tables**: `[SpacetimeDB.Table(Accessor = "TableName", Public = true)]` on `partial struct` (or `partial class`) — `Accessor` controls generated API names, and canonical SQL names are derived unless `Name` is explicitly set
- **Primary key**: Define `[SpacetimeDB.PrimaryKey]` on one column when you need key-based lookups or updates
- **Reducers**: `[SpacetimeDB.Reducer]` on static methods with `ReducerContext ctx` as first parameter
- **Required**: `using SpacetimeDB;` and `partial` on all table structs and the Module class
Expand Down
26 changes: 26 additions & 0 deletions docs/docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,32 @@ const config: Config = {
],

headTags: [
{
tagName: 'link',
attributes: {
rel: 'alternate',
type: 'text/markdown',
title: 'SpacetimeDB docs for agents',
href: '/docs/llms.txt',
},
},
{
tagName: 'link',
attributes: {
rel: 'alternate',
type: 'text/markdown',
title: 'Full SpacetimeDB docs for agents',
href: '/docs/llms-full.txt',
},
},
{
tagName: 'link',
attributes: {
rel: 'sitemap',
type: 'application/xml',
href: '/docs/sitemap.xml',
},
},
{
tagName: 'link',
attributes: {
Expand Down
2 changes: 2 additions & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
"scripts": {
"docusaurus": "docusaurus",
"dev": "docusaurus start",
"prebuild": "node scripts/sync-agent-skills.mjs",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"generate-cli-docs": "node scripts/generate-cli-docs.mjs",
"generate-llms": "docusaurus build && node scripts/generate-llms.mjs",
"sync-agent-skills": "node scripts/sync-agent-skills.mjs",
"rewrite-links": "node scripts/rewrite-doc-links.mjs",
"rewrite-links:write": "node scripts/rewrite-doc-links.mjs --write",
"write-translations": "docusaurus write-translations",
Expand Down
Loading
Loading