clockworklabs/SpacetimeDB · critical

default export is not a Schema object

Error message

default export is not a Schema object

What it means

The JS module's default export was found and is neither null nor undefined, but v2::get_hooks_from_default_export could not interpret it as a Schema object — it does not carry the structure ABI-v2 bindings attach to exported schemas. The host therefore cannot extract reducer/table/lifecycle hooks and startup fails. A default export exists, but it is the wrong kind of value.

Source

Thrown at crates/core/src/host/v8/syscall/mod.rs:145

) -> Result<Option<HookFunctions<'scope>>, ErrorOrException<ExceptionThrown>> {
    // We only set RECV_SLOT_INDEX in set_registered_hooks, which is only called in
    // the v2 code path. Set it to undefined ahead of time so it's not a garbage value.
    scope
        .get_current_context()
        .set_embedder_data(hooks::RECV_SLOT_INDEX, v8::undefined(scope).into());
    if let Some(hooks) = get_registered_hooks(scope) {
        return Ok(Some(hooks));
    }

    let default = super::str_from_ident!(default).string(scope);
    let default_export = exports_obj
        .get(scope, default.into())
        .ok_or_else(exception_already_thrown)?;
    if default_export.is_null_or_undefined() {
        return Ok(None);
    }
    let hooks = v2::get_hooks_from_default_export(scope, default_export, exports_obj)?
        .ok_or_else(|| anyhow::anyhow!("default export is not a Schema object"))?;
    Ok(Some(hooks))
}

/// Process the thrown exception value into an `ExecutionError`.
pub(super) fn process_thrown_exception(
    scope: &mut PinScope<'_, '_>,
    hooks: &HookFunctions<'_>,
    exc: Local<'_, v8::Value>,
) -> ExcResult<Option<ExecutionError>> {
    match hooks.abi {
        AbiVersion::V1 => Ok(None),
        AbiVersion::V2 => v2::process_thrown_exception(scope, hooks, exc),
    }
}

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Default-export exactly the schema value produced by the generated bindings.
  2. Upgrade or regenerate the JS bindings so the exported object matches the host's ABI v2 expectations.
  3. Add a pre-deploy smoke check that imports the built bundle and validates the default export.
  4. Diff against a freshly scaffolded module to see the expected export shape.

Example fix

// before: default export is not the schema object
export default { reducers, tables };

// after: default-export the schema the bindings generate
const schema = buildSchema({ reducers, tables });
export default schema;
Defensive patterns

Strategy: type-guard

Validate before calling

// pre-deploy check: the default export must look like the generated Schema
const mod = await import('./dist/module.js');
if (!isSchemaObject(mod.default)) {
    throw new Error('default export is not a Schema object');
}

Type guard

export function isSchemaObject(v: unknown): v is { reducers: object; tables: object } {
  return typeof v === 'object' && v !== null && 'reducers' in v && 'tables' in v;
}

Prevention

When it happens

Trigger: Default-exporting a plain object, class, or function instead of the generated schema: `export default {}`, `export default MyComponent`, or a schema value produced by a different (older/newer) bindings generation than the host's ABI v2 expects.

Common situations: Hand-written entry points exporting the wrong symbol; modules built with bindings whose Schema shape predates ABI v2; refactors where an intermediate builder value is exported instead of the finished schema.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16). Data as JSON: /api/errors/b897b646667d1b64. Report an issue: GitHub.