{"record":{"id":"23838e9fe7a83f7c","repo":"clockworklabs/SpacetimeDB","slug":"error-table-s-did-not-register-as-a-complex-ty","errorCode":null,"errorMessage":"ERROR: Table '%s' did not register as a complex type\\n","messagePattern":"ERROR: Table '(.+?)' did not register as a complex type\\\\n","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/bindings-cpp/src/internal/v9_builder.cpp","lineNumber":101,"sourceCode":"                                  const std::type_info* cpp_type,\n                                  bool is_public,\n                                  const std::vector<uint16_t>& primary_key,\n                                  const std::vector<RawIndexDefV9>& indexes,\n                                  const std::vector<RawConstraintDefV9>& constraints,\n                                  const std::vector<RawSequenceDefV9>& sequences,\n                                  const std::optional<RawScheduleDefV9>& schedule) {\n    \n    // Register the table type using the unified system\n    // Use empty string to let the system extract the struct name from cpp_type\n    AlgebraicType registered_type = registerType(table_type, \"\", cpp_type);\n    \n    // Extract the typespace index from the registered type\n    uint32_t type_ref;\n    if (registered_type.get_tag() == AlgebraicType::Tag::Ref) {\n        type_ref = registered_type.get<0>();\n    } else {\n        // This shouldn't happen for a table type - tables should always be complex types\n        fprintf(stderr, \"ERROR: Table '%s' did not register as a complex type\\n\", table_name.c_str());\n        type_ref = 0;\n    }\n    \n    // RegisterTable now handles all constraint and index generation,\n    // so we just use what was passed in directly\n    \n    // Create the table definition\n    RawTableDefV9 table_def;\n    table_def.name = table_name;\n    table_def.product_type_ref = type_ref;\n    table_def.primary_key = primary_key;\n    table_def.indexes = indexes;  // Use indexes passed from RegisterTable\n    table_def.constraints = constraints;  // Use constraints passed from RegisterTable\n    table_def.sequences = sequences;\n    table_def.schedule = schedule;\n    table_def.table_type = TableType::User;  // User-defined table\n    table_def.table_access = is_public ? TableAccess::Public : TableAccess::Private;\n    ","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/6dee26c6efc2856793e12b148a59742964f5d783/crates/bindings-cpp/src/internal/v9_builder.cpp#L83-L119","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: row type registers as a non-complex/inlined type, so registerType()\n// returns e.g. a dummy U8 and AddV9Table falls into the error branch\n// (table_def.product_type_ref = 0 -> broken schema)\nstruct Row; // never registered as a named complex type\n\n// after: declare the table via the macro so the named struct is registered\n// into the typespace and registerType() returns a Ref\n#include \"spacetimedb/table.hpp\"\nstruct Row { uint64_t id; std::string name; };\nSPACETIMEDB_TABLE(Row, public);","handlingStrategy":"type-guard","validationCode":"// before AddV9Table / RegisterTable, confirm the row type registers as a\n// typespace entry and no registration error is pending:\nAlgebraicType probe = registerType(bsatn_row_type, \"\", &typeid(Row));\nif (probe.get_tag() != AlgebraicType::Tag::Ref) {\n    // report at preinit: row type is inlined or unnamed (check\n    // getModuleTypeRegistration().error_message_ for the root cause)\n    report_and_abort(\"table row type did not register as a complex type\");\n}","typeGuard":"bool isComplexTypeRef(const AlgebraicType& t) {\n    return t.get_tag() == AlgebraicType::Tag::Ref;\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["spacetimedb","cpp","schema","table-registration","type-registration"],"backgroundTag":"schema-type-registration","analyzedSha":"6dee26c6efc2856793e12b148a59742964f5d783","analyzedAt":"2026-08-20T06:08:37.179Z","contentChangedAt":"2026-08-20T06:08:37.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}