# How to Define SpacetimeDB Tables: Rust Macros and TypeScript SDK Guide

> Learn to define SpacetimeDB tables using Rust macros or the TypeScript SDK. Effortlessly register schemas and generate type-safe accessors for seamless data management. Get started today!

- Repository: [Clockwork Labs/SpacetimeDB](https://github.com/clockworklabs/SpacetimeDB)
- Tags: how-to-guide
- Published: 2026-03-09

---

**SpacetimeDB tables are defined using the `#[spacetimedb::table]` attribute macro in Rust or the `table()` function in TypeScript, both of which register schemas with the runtime while generating type-safe accessors for queries and reducers.**

SpacetimeDB tables persist rows inside the database and serve as the foundation for reactive multiplayer applications. In the `clockworklabs/SpacetimeDB` repository, you define these tables declaratively using compile-time macros in Rust or schema-building functions in TypeScript. Understanding how to define SpacetimeDB tables correctly ensures your data layer generates efficient indexes and accessible APIs for client SDKs.

## Rust Table Definitions via Procedural Macros

The Rust SDK provides two primary ways to declare tables: the standard attribute macro for production code and a convenience macro for rapid testing.

### Using the #[spacetimedb::table] Attribute

The canonical approach uses the procedural macro defined in **[`crates/spacetimedb-macros/src/table.rs`](https://github.com/clockworklabs/SpacetimeDB/blob/main/crates/spacetimedb-macros/src/table.rs)**. Apply `#[spacetimedb::table(...)]` to a `pub struct` to register it with the database runtime and generate accessor methods.

```rust
#[spacetimedb::table(
    accessor = person,
    public,
    index(accessor = age_index, btree(columns = [age]))
)]
pub struct Person {
    #[primary_key]
    id: u64,
    name: String,
    age: u32,
}

```

The macro expands your struct to implement the `spacetimedb::table::Table` trait. The **accessor** parameter defines the method name used to reach the table from reducer contexts (e.g., `ctx.db.person()`). The **public** flag makes the table readable by any connected client, while **private** restricts access to server-side reducers. You can declare multi-column **B-tree indexes** using the `index` option to optimize query performance.

Additional configuration options include:

- **`scheduled(<reducer>)`** – Automatically invokes the specified reducer when a row's timestamp column is reached
- **`event`** – Creates an event table where rows are emitted as transient events rather than persisted state

See the real-world implementation in [`demo/Blackholio/server-rust/src/lib.rs`](https://github.com/clockworklabs/SpacetimeDB/blob/main/demo/Blackholio/server-rust/src/lib.rs), which demonstrates public, private, scheduled, and event table definitions side-by-side.

### Convenience Testing with define_tables!

For rapid prototyping and integration tests, the repository provides the **`define_tables!`** macro located in **[`modules/sdk-test/src/lib.rs`](https://github.com/clockworklabs/SpacetimeDB/blob/main/modules/sdk-test/src/lib.rs)** (lines 55-73). This macro simultaneously declares a table and generates basic reducers for CRUD operations.

```rust
define_tables! {
    Person {
        insert insert_person,
        delete delete_person,
        update_by update_person = update_by(id)
    }
    #[primary_key] id: u64,
    name: String,
    age: u32,
}

```

The macro expands to a `#[spacetimedb::table]` struct definition plus reducer functions matching the signatures you specify. For example, `insert insert_person` generates a reducer that accepts the field types and calls `ctx.db.person().insert(Person { ... })`. This scaffolding appears frequently in the Rust test client at [`sdks/rust/tests/test-client/src/simple_test_table.rs`](https://github.com/clockworklabs/SpacetimeDB/blob/main/sdks/rust/tests/test-client/src/simple_test_table.rs).

## TypeScript Table Definitions

When using the TypeScript SDK, tables are defined programmatically using the **`table`** function exported from **[`crates/bindings-typescript/src/lib/table.ts`](https://github.com/clockworklabs/SpacetimeDB/blob/main/crates/bindings-typescript/src/lib/table.ts)** (lines 7-22).

### The table() Schema Builder

Import `table` and the type builders from `spacetimedb/server` to construct a `TableSchema` object that the runtime uses to create the table:

```typescript
import { table, t } from "spacetimedb/server";

export const schema = table(
    {
        name: "user",
        public: true,
        indexes: [
            { accessor: "email_index", btree: { columns: ["email"] } },
        ],
    },
    {
        id: t.u64().primaryKey(),
        email: t.string(),
        name: t.string(),
    }
);

```

The function performs **compile-time validation** of column constraints. For instance, it prevents conflicting declarations like `default()` combined with `primaryKey()`, emitting descriptive type errors before runtime. The returned schema object provides methods for inserting rows and executing queries within your reducer logic.

## Advanced Configuration Options

Both SDKs support sophisticated table behaviors beyond simple row storage.

### Multi-Column Indexes and Visibility

Define composite indexes to accelerate queries filtering on multiple fields:

```rust
#[spacetimedb::table(
    accessor = log,
    index(accessor = timestamp_user_index, btree(columns = [timestamp, user_id]))
)]
pub struct Log {
    #[primary_key]
    id: u64,
    user_id: u64,
    timestamp: u64,
    message: String,
}

```

In TypeScript, pass an array of index definitions to the `indexes` option using the same `btree` configuration object.

### Scheduled Reducers

Register tables with the runtime scheduler to trigger reducers based on temporal conditions:

```typescript
import { table, t, schedule } from "spacetimedb/server";

export const taskTable = table(
    {
        name: "task",
        public: true,
        scheduled: () => schedule.send_scheduled_message,
    },
    {
        id: t.u64().primaryKey(),
        description: t.string(),
        due_at: t.timestamp(),
    }
);

```

When a row's `due_at` timestamp is reached, the runtime automatically invokes the `send_scheduled_message` reducer with the row data. The Rust equivalent uses the `scheduled(<reducer_name>)` option within the `#[spacetimedb::table]` attribute.

## Summary

- **Rust attribute macro**: Use `#[spacetimedb::table(...)]` on structs, implemented in [`crates/spacetimedb-macros/src/table.rs`](https://github.com/clockworklabs/SpacetimeDB/blob/main/crates/spacetimedb-macros/src/table.rs), to define tables with compile-time code generation.
- **Rust test macro**: The `define_tables!` macro in [`modules/sdk-test/src/lib.rs`](https://github.com/clockworklabs/SpacetimeDB/blob/main/modules/sdk-test/src/lib.rs) generates both tables and reducers for testing scenarios.
- **TypeScript function**: Call `table(opts, rowDef)` from [`crates/bindings-typescript/src/lib/table.ts`](https://github.com/clockworklabs/SpacetimeDB/blob/main/crates/bindings-typescript/src/lib/table.ts) to create validated table schemas.
- **Primary keys**: Required for update operations, declared with `#[primary_key]` in Rust or `.primaryKey()` in TypeScript.
- **Indexes**: Define multi-column B-tree indexes declaratively in both languages to optimize query paths.
- **Visibility**: Mark tables `public` to expose them to clients or omit the flag (Rust) or set `false` (TypeScript) for private server-side storage.

## Frequently Asked Questions

### What is the difference between public and private tables in SpacetimeDB?

Public tables allow any connected client to read their contents through the client SDK, while private tables are only accessible from server-side reducers within the same module. In Rust, add the `public` argument to the attribute macro; in TypeScript, set `public: true` in the table options object.

### Can I define multi-column indexes in SpacetimeDB?

Yes. In Rust, use `index(accessor = <name>, btree(columns = [<col1>, <col2>]))` inside the `#[spacetimedb::table]` attribute. In TypeScript, include an object with `btree: { columns: ["col1", "col2"] }` in the `indexes` array. These indexes optimize queries that filter on the specified column combinations.

### Where are the table definition macros implemented in the SpacetimeDB source code?

The Rust procedural macro is implemented in **[`crates/spacetimedb-macros/src/table.rs`](https://github.com/clockworklabs/SpacetimeDB/blob/main/crates/spacetimedb-macros/src/table.rs)**, which parses the attribute arguments and generates the accessor methods. The TypeScript `table` function lives in **[`crates/bindings-typescript/src/lib/table.ts`](https://github.com/clockworklabs/SpacetimeDB/blob/main/crates/bindings-typescript/src/lib/table.ts)** and handles runtime schema validation and registration.

### How do I create a table that automatically triggers reducers at specific times?

Use the **scheduled table** feature. In Rust, add `scheduled(<reducer_name>)` to your `#[spacetimedb::table]` arguments. In TypeScript, include `scheduled: () => schedule.<your_reducer>` in the table options. The runtime monitors the specified timestamp column and invokes the reducer when each row's time is reached.