clockworklabs/SpacetimeDB · error · syn::Error
spacetimedb table must be a struct
Error message
spacetimedb table must be a struct
What it means
`#[spacetimedb::table]` derives the item's SATS type and matches on `SatsTypeData::Product`; only a struct yields Product data. Applying the macro to an enum fails this check and reports that a table must be a struct, at call site. (A union fails even earlier, in sats.rs, with 'unions not supported'.) Value types such as enums belong on columns via `SpacetimeType`, not on tables.
Source
Thrown at crates/bindings-macro/src/table.rs:838
let mut set = GENERATED_STRUCTS.lock().expect("mutex poisoned");
set.insert(struct_name.to_string())
}
pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::Result<TokenStream> {
let vis = &item.vis;
let sats_ty = sats::sats_type_from_derive(item, quote!(spacetimedb::spacetimedb_lib))?;
let original_struct_ident = sats_ty.ident;
let table_ident = &args.accessor;
let explicit_table_name = args.name.as_ref().map(|s| s.value());
let view_trait_ident = format_ident!("{}__view", table_ident);
let query_trait_ident = format_ident!("{}__query", table_ident);
let query_cols_struct = format_ident!("{}Cols", original_struct_ident);
let query_ix_cols_struct = format_ident!("{}IxCols", original_struct_ident);
let table_name = table_ident.unraw().to_string();
let sats::SatsTypeData::Product(fields) = &sats_ty.data else {
return Err(syn::Error::new(Span::call_site(), "spacetimedb table must be a struct"));
};
for param in &item.generics.params {
let err = |msg| syn::Error::new_spanned(param, msg);
match param {
syn::GenericParam::Lifetime(_) => {}
syn::GenericParam::Type(_) => return Err(err("type parameters are not allowed on tables")),
syn::GenericParam::Const(_) => return Err(err("const parameters are not allowed on tables")),
}
}
let table_id_from_name_func = quote! {
fn table_id() -> spacetimedb::TableId {
static TABLE_ID: std::sync::OnceLock<spacetimedb::TableId> = std::sync::OnceLock::new();
*TABLE_ID.get_or_init(|| {
spacetimedb::table_id_from_name(<Self as spacetimedb::table::TableInternal>::TABLE_NAME)
})
}View on GitHub (pinned to 524b4487d9)
Solutions
- Change the table to a struct
- If it is a value type, derive `SpacetimeType` on the enum and use it as a column in a real table struct
- Model enum-keyed rows as a struct with an enum-typed column plus an index
Example fix
// before
#[spacetimedb::table(accessor = status)]
enum Status {
Active,
Banned,
}
// after
#[derive(spacetimedb::SpacetimeType)]
enum Status {
Active,
Banned,
}
#[spacetimedb::table(accessor = user)]
struct User {
#[primary_key]
#[auto_inc]
id: u64,
status: Status,
} Defensive patterns
Strategy: validation
Validate before calling
# tables must be structs: flag non-struct items carrying the table attribute grep -rn -A1 'spacetimedb::table' src/ | grep -E 'enum|union' || echo OK
Prevention
- Tables are structs; value types are enums with #[derive(SpacetimeType)] used as columns
- Keep schema declarations in one reviewable module section
- CI cargo check surfaces these before publish
When it happens
Trigger: `#[spacetimedb::table(...)]` applied to an `enum` item.
Common situations: Trying to use an enum as a lookup table; new users applying the table attribute to every schema type they define.
Related errors
- must specify table accessor, e.g. `#[spacetimedb::table(acce
- unions not supported
- a direct index must be paired with a `#[unique] constraint
- not a column of the table
- invalid combination: auto_inc, unique index or primary key c
AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16).
Data as JSON: /api/errors/d4078636bbe8c5dd.
Report an issue: GitHub.