▸ Agent Skills
6 min read

Version: 2.0.0

On this page

Columns define the structure of your tables. SpacetimeDB supports primitive types, composite types for complex data, and special types for database-specific functionality.

Representing Collections

When modeling data that contains multiple items, you have two choices: store the collection as a column (using Vec, List, or Array) or store each item as a row in a separate table. This decision affects how you query, update, and subscribe to that data.

Use a collection column when:

  • The items form an atomic unit that you always read and write together
  • Order is semantically important and frequently accessed by position
  • The collection is small and bounded (e.g., a fixed-size inventory)
  • The items are values without independent identity

Use a separate table when:

  • Items have independent identity and lifecycle
  • You need to query, filter, or index individual items
  • The collection can grow unbounded
  • Clients should receive updates for individual item changes, not the entire collection
  • You want to enforce referential integrity between items and other data

Consider a game inventory with ordered pockets. A Vec<Item> preserves pocket order naturally, but if you need to query “all items owned by player X” across multiple players, a separate inventory_item table with a pocket_index column allows that query efficiently. The right choice depends on your dominant access patterns.

Binary Data and Files

SpacetimeDB includes optimizations for storing binary data as Vec<u8> (Rust), List<byte> (C#), t.array(t.u8()) (TypeScript), or std::vector<uint8_t> (C++). You can store files, images, serialized data, or other binary blobs directly in table columns.

This approach works well when:

  • The binary data is associated with a specific row (e.g., a user’s avatar image)
  • You want the data to participate in transactions and subscriptions
  • The data size is reasonable (up to several megabytes per row)

For very large files or data that changes independently of other row fields, consider external storage with a reference stored in the table.

Type Performance

SpacetimeDB optimizes reading and writing by taking advantage of memory layout. Several factors affect performance:

Prefer smaller types. Use the smallest integer type that fits your data range. A u8 storing values 0-255 uses less memory and bandwidth than a u64 storing the same values. This reduces storage, speeds up serialization, and improves cache efficiency.

Prefer fixed-size types. Fixed-size types (u32, f64, fixed-size structs) allow SpacetimeDB to compute memory offsets directly. Variable-size types (String, Vec<T>) require additional indirection. When performance matters, consider fixed-size alternatives:

  • Use [u8; 32] instead of Vec<u8> for fixed-length hashes or identifiers
  • Use an enum with a fixed set of variants instead of a String for categorical data

Consider column ordering. Types require alignment in memory. A u64 aligns to 8-byte boundaries, while a u8 aligns to 1-byte boundaries. When smaller types precede larger ones, the compiler may insert padding bytes to satisfy alignment requirements. Ordering columns from largest to smallest alignment can reduce padding and improve memory density.

For example, a struct with fields (u8, u64, u8) may require 24 bytes due to padding, while (u64, u8, u8) requires only 16 bytes. This optimization is not something to follow religiously, but it can help performance in memory-intensive scenarios.

These optimizations apply across all supported languages.

Type Reference

  • TypeScript
  • C#
  • Rust
  • C++
CategoryTypeTypeScript TypeDescription
Primitivet.bool()booleanBoolean value
Primitivet.string()stringUTF-8 string
Primitivet.f32()number32-bit floating point
Primitivet.f64()number64-bit floating point
Primitivet.i8()numberSigned 8-bit integer
Primitivet.u8()numberUnsigned 8-bit integer
Primitivet.i16()numberSigned 16-bit integer
Primitivet.u16()numberUnsigned 16-bit integer
Primitivet.i32()numberSigned 32-bit integer
Primitivet.u32()numberUnsigned 32-bit integer
Primitivet.i64()bigintSigned 64-bit integer
Primitivet.u64()bigintUnsigned 64-bit integer
Primitivet.i128()bigintSigned 128-bit integer
Primitivet.u128()bigintUnsigned 128-bit integer
Primitivet.i256()bigintSigned 256-bit integer
Primitivet.u256()bigintUnsigned 256-bit integer
Compositet.object(name, obj){ [K in keyof Obj]: T<Obj[K]> }Product/object type for nested data. Use t.object, not t.struct (which does not exist).
Compositet.enum(name, variants)`{ tag: ‘variant’ }{ tag: ‘variant’, value: T }`
Compositet.array(element)T\<Element\>[]Array of elements
Compositet.option(value)`Valueundefined`
Compositet.unit(){}Zero-field product type
Specialt.identity()IdentityUnique identity for authentication
Specialt.connectionId()ConnectionIdClient connection identifier
Specialt.timestamp()TimestampAbsolute point in time (microseconds since Unix epoch)
Specialt.timeDuration()TimeDurationRelative duration in microseconds
Specialt.scheduleAt()ScheduleAtColumn type for scheduling reducer execution
CategoryTypeDescription
PrimitiveboolBoolean value
PrimitivestringUTF-8 string
Primitivefloat32-bit floating point
Primitivedouble64-bit floating point
Primitivesbyte, short, int, longSigned integers (8-bit to 64-bit)
Primitivebyte, ushort, uint, ulongUnsigned integers (8-bit to 64-bit)
PrimitiveSpacetimeDB.I128, SpacetimeDB.I256Signed 128-bit and 256-bit integers
PrimitiveSpacetimeDB.U128, SpacetimeDB.U256Unsigned 128-bit and 256-bit integers
Compositestruct with [SpacetimeDB.Type]Product type for nested data
CompositeTaggedEnum<Variants>Sum type (tagged union)
CompositeList<T>List of elements
CompositeT?Nullable/optional value
SpecialIdentityUnique identity for authentication
SpecialConnectionIdClient connection identifier
SpecialTimestampAbsolute point in time (microseconds since Unix epoch)
SpecialTimeDurationRelative duration in microseconds
SpecialScheduleAtWhen a scheduled reducer should execute
CategoryTypeDescription
PrimitiveboolBoolean value
PrimitiveStringUTF-8 string
Primitivef32, f64Floating point numbers
Primitivei8, i16, i32, i64, i128Signed integers
Primitiveu8, u16, u32, u64, u128Unsigned integers
Compositestruct with #[derive(SpacetimeType)]Product type for nested data
Compositeenum with #[derive(SpacetimeType)]Sum type (tagged union)
CompositeVec<T>Vector of elements
CompositeOption<T>Optional value
SpecialIdentityUnique identity for authentication
SpecialConnectionIdClient connection identifier
SpecialTimestampAbsolute point in time (microseconds since Unix epoch)
SpecialTimeDurationRelative duration in microseconds
SpecialScheduleAtWhen a scheduled reducer should execute
CategoryTypeDescription
PrimitiveboolBoolean value
Primitivestd::stringUTF-8 string
Primitivefloat32-bit floating point
Primitivedouble64-bit floating point
Primitiveint8_t, int16_t, int32_t, int64_tSigned integers (8-bit to 64-bit)
Primitiveuint8_t, uint16_t, uint32_t, uint64_tUnsigned integers (8-bit to 64-bit)
PrimitiveSpacetimeDB::i128, SpacetimeDB::i256Signed 128-bit and 256-bit integers
PrimitiveSpacetimeDB::u128, SpacetimeDB::u256Unsigned 128-bit and 256-bit integers
Compositestruct with SPACETIMEDB_STRUCTProduct type for nested data
CompositeSPACETIMEDB_ENUMSum type (tagged union)
Compositestd::vector<T>Vector of elements
Compositestd::optional<T>Optional value
SpecialIdentityUnique identity for authentication
SpecialConnectionIdClient connection identifier
SpecialTimestampAbsolute point in time (microseconds since Unix epoch)
SpecialTimeDurationRelative duration in microseconds
SpecialScheduleAtWhen a scheduled reducer should execute

Complete Example

The following example demonstrates a table using primitive, composite, and special types:

  • TypeScript
  • C#
  • Rust
  • C++
import { table, t } from 'spacetimedb/server';

// Define a nested object type for coordinates
const Coordinates = t.object('Coordinates', {
  x: t.f64(),
  y: t.f64(),
  z: t.f64(),
});

// Define an enum for status
const Status = t.enum('Status', {
  Active: t.unit(),
  Inactive: t.unit(),
  Suspended: t.object('SuspendedInfo', { reason: t.string() }),
});

const player = table(
  { name: 'player', public: true },
  {
    // Primitive types
    id: t.u64().primaryKey().autoInc(),
    name: t.string(),
    level: t.u8(),
    experience: t.u32(),
    health: t.f32(),
    score: t.i64(),
    is_online: t.bool(),

    // Composite types
    position: Coordinates,
    status: Status,
    inventory: t.array(t.u32()),
    guild_id: t.option(t.u64()),

    // Special types
    owner: t.identity(),
    connection: t.option(t.connectionId()),
    created_at: t.timestamp(),
    play_time: t.timeDuration(),
  }
);
using SpacetimeDB;

public static partial class Module
{
    // Define a nested struct type for coordinates
    [SpacetimeDB.Type]
    public partial struct Coordinates
    {
        public double X;
        public double Y;
        public double Z;
    }

    // Define an enum for status (must be partial record, not partial class)
    [SpacetimeDB.Type]
    public partial record Status : TaggedEnum<(
        Unit Active,
        Unit Inactive,
        string Suspended
    )> { }

    [SpacetimeDB.Table(Accessor = "Player", Public = true)]
    public partial struct Player
    {
        // Primitive types
        [SpacetimeDB.PrimaryKey]
        [SpacetimeDB.AutoInc]
        public ulong Id;
        public string Name;
        public byte Level;
        public uint Experience;
        public float Health;
        public long Score;
        public bool IsOnline;

        // Composite types
        public Coordinates Position;
        public Status Status;
        public List<uint> Inventory;
        public ulong? GuildId;

        // Special types
        public Identity Owner;
        public ConnectionId? Connection;
        public Timestamp CreatedAt;
        public TimeDuration PlayTime;
    }
}
use spacetimedb::{SpacetimeType, Identity, ConnectionId, Timestamp, TimeDuration};

// Define a nested struct type for coordinates
#[derive(SpacetimeType)]
pub struct Coordinates {
    x: f64,
    y: f64,
    z: f64,
}

// Define an enum for status
#[derive(SpacetimeType)]
pub enum Status {
    Active,
    Inactive,
    Suspended { reason: String },
}

#[spacetimedb::table(accessor = player, public)]
pub struct Player {
    // Primitive types
    #[primary_key]
    #[auto_inc]
    id: u64,
    name: String,
    level: u8,
    experience: u32,
    health: f32,
    score: i64,
    is_online: bool,

    // Composite types
    position: Coordinates,
    status: Status,
    inventory: Vec<u32>,
    guild_id: Option<u64>,

    // Special types
    owner: Identity,
    connection: Option<ConnectionId>,
    created_at: Timestamp,
    play_time: TimeDuration,
}
// Define a nested struct type for coordinates
struct Coordinates {
    double x;
    double y;
    double z;
};
SPACETIMEDB_STRUCT(Coordinates, x, y, z)

// Define unit types for enum variants
SPACETIMEDB_UNIT_TYPE(Active)
SPACETIMEDB_UNIT_TYPE(Inactive)

// Define an enum for status
SPACETIMEDB_ENUM(PlayerStatus,
    (Active, Active),
    (Inactive, Inactive),
    (Suspended, std::string)
)

struct Player {
    // Primitive types
    uint64_t id;
    std::string name;
    uint8_t level;
    uint32_t experience;
    float health;
    int64_t score;
    bool is_online;

    // Composite types
    Coordinates position;
    PlayerStatus status;
    std::vector<uint32_t> inventory;
    std::optional<uint64_t> guild_id;

    // Special types
    Identity owner;
    std::optional<ConnectionId> connection;
    Timestamp created_at;
    TimeDuration play_time;
};
SPACETIMEDB_STRUCT(Player, id, name, level, experience, health, score, is_online,
                   position, status, inventory, guild_id,
                   owner, connection, created_at, play_time)
SPACETIMEDB_TABLE(Player, player, Public)
FIELD_PrimaryKeyAutoInc(player, id)

Last updated Oct 08, 2026