clockworklabs/SpacetimeDB · error

ERROR: Constraint registration failed [%s] %s

Error message

ERROR: Constraint registration failed [%s] %s

What it means

Generic constraint-registration failure reported by SetConstraintRegistrationError(code, details). V10Builder calls it with a machine-readable code for many schema mistakes: TABLE_NO_FIELD_DESCRIPTORS, NO_FIELD_DESCRIPTORS, FIELD_NOT_FOUND, TABLE_NOT_FOUND, MULTI_INDEX_EMPTY, DEFAULT_ON_PRIMARY_KEY and others (see include/spacetimedb/internal/v10_builder.h). The flag makes __preinit__99_validate_types replace the module with error type ERROR_CONSTRAINT_REGISTRATION_<code>, so the publish fails with that name on the server.

Source

Thrown at crates/bindings-cpp/src/internal/Module.cpp:93

    static std::string g_constraint_registration_error_code = "";
    static std::string g_constraint_registration_error_details = "";
    
    // External global flags for circular reference detection (defined in module_type_registration.cpp)
    extern bool g_circular_ref_error;
    extern std::string g_circular_ref_type_name;
    
    // Function to set the multiple primary key error flag
    void SetMultiplePrimaryKeyError(const std::string& table_name) {
        g_multiple_primary_key_error = true;
        g_multiple_primary_key_table_name = table_name;
        fprintf(stderr, "ERROR: Multiple primary keys detected in table '%s'\n", table_name.c_str());
    }

    void SetConstraintRegistrationError(const std::string& code, const std::string& details) {
        g_constraint_registration_error = true;
        g_constraint_registration_error_code = code;
        g_constraint_registration_error_details = details;
        fprintf(stderr, "ERROR: Constraint registration failed [%s] %s\n", code.c_str(), details.c_str());
    }
    
    // Register a reducer handler (called by V9Builder during registration)
    void RegisterReducerHandler(const std::string& name, 
                                std::function<void(ReducerContext&, BytesSource)> handler,
                                std::optional<Lifecycle> lifecycle) {
        g_reducer_handlers.push_back({name, handler, lifecycle});
    }
    
    // Register a view handler (called by V9Builder during registration)
    void RegisterViewHandler(const std::string& name,
                            std::function<std::vector<uint8_t>(ViewContext&, BytesSource)> handler) {
        g_view_handlers.push_back({name, handler});
    }
    
    // Register an anonymous view handler (called by V9Builder during registration)
    void RegisterAnonymousViewHandler(const std::string& name,
                                     std::function<std::vector<uint8_t>(AnonymousViewContext&, BytesSource)> handler) {

View on GitHub (pinned to 6dee26c6ef)

Solutions

  1. Read the bracketed code and details string: FIELD_NOT_FOUND -> correct the field name in the constraint/index macro to match the struct member
  2. TABLE_NOT_FOUND -> ensure the table registration macro for that table is compiled before/with the constraint macros (same header or TU)
  3. MULTI_INDEX_EMPTY -> give the multi-column index at least one column name
  4. NO_FIELD_DESCRIPTORS / TABLE_NO_FIELD_DESCRIPTORS -> make sure the table struct uses the SpacetimeDB table registration so field_registrar<T>::register_fields() populates descriptors
  5. DEFAULT_ON_PRIMARY_KEY -> remove the column default from the primary-key column

Example fix

// before
struct User { uint64_t user_id; std::string name; };
FIELD_Unique(User, usr_id); // typo -> FIELD_NOT_FOUND

// after
FIELD_Unique(User, user_id);
Defensive patterns

Strategy: validation

Validate before calling

// compile-time existence check next to each constraint macro: a typo'd
// member pointer fails to build before publish
static_assert(&User::user_id != nullptr,
              "User.user_id must exist for FIELD_Unique(User, user_id)");

Prevention

When it happens

Trigger: A FIELD_Unique/FIELD_Index macro naming a field that is not in the table's registered field descriptors (FIELD_NOT_FOUND); a multi-column index registered with an empty field list (MULTI_INDEX_EMPTY); a constraint applied before the table itself is registered (TABLE_NOT_FOUND); a table type whose field_registrar<T> produced no descriptors; a column default applied to the primary-key column (DEFAULT_ON_PRIMARY_KEY).

Common situations: Renaming a struct member without updating the adjacent FIELD_* macro; splitting table and constraint macros across translation units so preinit ordering registers constraints first; adding defaults to key columns when copying an ORM schema.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20). Data as JSON: /api/errors/7b1d39af5168e78d. Report an issue: GitHub.