clockworklabs/SpacetimeDB · error
ERROR: Table '%s' did not register as a complex type\n
Error message
ERROR: Table '%s' did not register as a complex type\n
What it means
Printed by V9Builder::AddV9Table when the AlgebraicType returned by registerType() for a table's row type is not a Ref, i.e. it was not registered into the typespace as a named complex type. Tables must map to a typespace entry referenced by index; on this path the code logs the error and falls back to type_ref = 0, so the table definition points at whatever type occupies index 0 (or nothing), producing a broken schema. registerType() returns a non-Ref exactly when the row type was classified as an inlined kind (primitive/array/unit/special/option/result/ScheduleAt) or when an error path returned a dummy U8 (missing type name at module_type_registration.cpp:126-141, recursive-type detection at lines 143-152).
Source
Thrown at crates/bindings-cpp/src/internal/v9_builder.cpp:101
const std::type_info* cpp_type,
bool is_public,
const std::vector<uint16_t>& primary_key,
const std::vector<RawIndexDefV9>& indexes,
const std::vector<RawConstraintDefV9>& constraints,
const std::vector<RawSequenceDefV9>& sequences,
const std::optional<RawScheduleDefV9>& schedule) {
// Register the table type using the unified system
// Use empty string to let the system extract the struct name from cpp_type
AlgebraicType registered_type = registerType(table_type, "", cpp_type);
// Extract the typespace index from the registered type
uint32_t type_ref;
if (registered_type.get_tag() == AlgebraicType::Tag::Ref) {
type_ref = registered_type.get<0>();
} else {
// This shouldn't happen for a table type - tables should always be complex types
fprintf(stderr, "ERROR: Table '%s' did not register as a complex type\n", table_name.c_str());
type_ref = 0;
}
// RegisterTable now handles all constraint and index generation,
// so we just use what was passed in directly
// Create the table definition
RawTableDefV9 table_def;
table_def.name = table_name;
table_def.product_type_ref = type_ref;
table_def.primary_key = primary_key;
table_def.indexes = indexes; // Use indexes passed from RegisterTable
table_def.constraints = constraints; // Use constraints passed from RegisterTable
table_def.sequences = sequences;
table_def.schedule = schedule;
table_def.table_type = TableType::User; // User-defined table
table_def.table_access = is_public ? TableAccess::Public : TableAccess::Private;
View on GitHub (pinned to 6dee26c6ef)
Solutions
- Check the registration error state right before this line: ModuleTypeRegistration's error_message_/has_error_ (set at module_type_registration.cpp:128-137 and 143-152) states the real cause - fix that first (give the row type a resolvable name, or remove the recursion).
- Declare tables through the SpacetimeDB table macro (SPACETIMEDB_TABLE) so the row type is a top-level named struct and AddV9Table receives a properly named complex type.
- Ensure the row type is a named struct/enum, not a primitive, array, std::optional-wrapped type, or a two-variant Interval/Time sum (ScheduleAt look-alike), all of which registerType() inlines and returns non-Ref.
- On toolchains without __cxa_demangle, pass an explicit type name instead of relying on typeid demangling (see the SPACETIMEDB_HAS_CXA_DEMANGLE branch at line 129).
- After fixing, confirm registered_type.get_tag() == AlgebraicType::Tag::Ref and the emitted product_type_ref matches the row type's typespace index before publishing.
Example fix
// before: row type registers as a non-complex/inlined type, so registerType()
// returns e.g. a dummy U8 and AddV9Table falls into the error branch
// (table_def.product_type_ref = 0 -> broken schema)
struct Row; // never registered as a named complex type
// after: declare the table via the macro so the named struct is registered
// into the typespace and registerType() returns a Ref
#include "spacetimedb/table.hpp"
struct Row { uint64_t id; std::string name; };
SPACETIMEDB_TABLE(Row, public); Defensive patterns
Strategy: type-guard
Validate before calling
// before AddV9Table / RegisterTable, confirm the row type registers as a
// typespace entry and no registration error is pending:
AlgebraicType probe = registerType(bsatn_row_type, "", &typeid(Row));
if (probe.get_tag() != AlgebraicType::Tag::Ref) {
// report at preinit: row type is inlined or unnamed (check
// getModuleTypeRegistration().error_message_ for the root cause)
report_and_abort("table row type did not register as a complex type");
} Type guard
bool isComplexTypeRef(const AlgebraicType& t) {
return t.get_tag() == AlgebraicType::Tag::Ref;
} Prevention
- Declare all tables through the SpacetimeDB table macro so row types are top-level named structs that always register as complex types.
- Never use primitives, raw arrays, std::optional, or ScheduleAt-shaped two-variant sums as the table row type - registerType() inlines them and returns non-Ref.
- Avoid recursive row types (a struct containing an Option of itself); cycle detection returns a dummy U8 which lands in this error branch.
- After registering tables, check ModuleTypeRegistration's has_error_/error_message_ ('Missing type name for complex type', 'Recursive type reference detected') - it names the root cause behind this message.
- On toolchains without __cxa_demangle, give row types explicit registered names instead of relying on typeid demangling.
When it happens
Trigger: Calling AddV9Table/RegisterTable with a row type that is not a named struct or enum: a primitive-only 'table' type, a row type shaped like a special type or ScheduleAt sum that gets inlined, a row type whose name cannot be resolved (empty explicit_name plus a cpp_type that fails extractTypeName/demangling, or a toolchain without __cxa_demangle), or a row type that references itself so cycle detection returns the dummy U8. The fprintf at v9_builder.cpp:101 fires with the table name and product_type_ref is set to 0.
Common situations: Declaring a table without the SpacetimeDB table macro so the row type never gets a registered name; row types defined as typedefs/aliases or anonymous structs whose typeid does not demangle to a usable name; recursive row structs (e.g. containing an Option of themselves) tripping the cycle-detection dummy return; building with a toolchain lacking demangling support so 'Missing type name for complex type' is set on the registration object right before this error. Downstream symptom: publish/validation fails or the module schema references the wrong type at index 0, and a companion message ('Missing type name for complex type: ...' or 'Recursive type reference detected: ...') names the root cause.
Related errors
- [Warning] Unexpected type tag %d for '%s'\n
- ERROR: Multiple primary keys detected in table '%s'
- ERROR: Constraint registration failed [%s] %s
- [CIRCULAR REFERENCE ERROR] Module cleared and replaced with
- [CONSTRAINT ERROR] Module cleared and replaced with error t
AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20).
Data as JSON: /api/errors/23838e9fe7a83f7c.
Report an issue: GitHub.