clockworklabs/SpacetimeDB · error · TypeError
HTTP route for path ' ' refers to a handler that was not…
Error message
HTTP route for path '${route.path}' refers to a handler that was not exported. What it means
When `resolveHttpRoutes` runs, every route registered on a Router must point at a handler object that was registered as an HTTP handler export — the lookup is `httpHandlerExports.get(route.handler)`, which is only populated when the handler's `[registerExport]` runs during module export registration. If you passed the raw function, a handler built by a different (or no) schema registration, or never exported the handler, the route cannot be resolved and the build fails with 'refers to a handler that was not exported'.
Solutions
- Pass the exact object returned by `spacetimedb.httpHandler(...)` to the router methods, and export that same object from the module: `export const listUsers = spacetimedb.httpHandler(...); router.get('/users', listUsers)`.
- Do not wrap the handler in another function or pass `handler[httpHandlerFn]`-style internals.
- Check that the export name in the module matches a real `export` statement; registration only happens for exported names (the `default` export name is skipped).
Example fix
// before
function listUsers(ctx) { ... }
export default new Router().get('/users', listUsers); // raw fn, not an export
// after
export const listUsers = spacetimedb.httpHandler(schema, undefined, ctx => { ... });
export default new Router().get('/users', listUsers); Defensive patterns
Strategy: validation
Validate before calling
// Verify every router route points at a real handler export before publishing.
const handlerExports = new Set<unknown>([listUsers, getUser]);
function assertRouterHandlers(router: Router) {
for (const route of router.intoRoutes()) {
if (!handlerExports.has(route.handler)) {
throw new Error(`route ${route.path} uses an unexported handler`);
}
}
} Type guard
function isHttpHandlerExport(x: unknown, schemaExports: Set<unknown>): x is Function {
return typeof x === 'function' && schemaExports.has(x);
} Prevention
- Always pass the object returned by `spacetimedb.httpHandler(...)` to router methods — never the bare function.
- `export const` every handler in the module entry; check the list before publish.
- Remember the `default` export name is skipped by export registration — handlers must be named exports.
When it happens
Trigger: Passing the inner function instead of the export object to `router.post(path, fn)`; creating a handler with `spacetimedb.httpHandler(...)` but not including it in the module's `export`s; passing a handler created lazily/inside a function so registration never happened when routes resolved.
Common situations: Refactoring a handler into a helper that returns the fn, then wiring the fn into the router; forgetting `export const` on the handler; handlers created in a loop where the export object is discarded.
Related errors
- HTTP handler ' ' was exported more than once
- Route conflict for
- There is already an HTTP handler with the name
- cannot serialize refs without a typespace
- exporting something that is not a spacetime export
AI-assisted analysis of clockworklabs/SpacetimeDB@eddf9f5014 (2026-08-20).
Data as JSON: /api/errors/04de59dd5e0defaa.
Report an issue: GitHub.
Appendix: source
Thrown at crates/bindings-typescript/src/server/schema.ts:212
throw new TypeError(
`Table ${tableName} defines multiple schedules: ${existingFunctionName} and ${functionName}. A schedule table can only be used by one reducer or procedure.`
);
}
scheduledTables.set(tableName, functionName);
this.moduleDef.schedules.push({
sourceName: undefined,
tableName,
scheduleAtCol,
functionName,
});
}
}
resolveHttpRoutes() {
for (const route of this.pendingHttpRoutes) {
const handlerFunction = this.httpHandlerExports.get(route.handler);
if (handlerFunction === undefined) {
throw new TypeError(
`HTTP route for path '${route.path}' refers to a handler that was not exported.`
);
}
this.moduleDef.httpRoutes.push({
handlerFunction,
method: route.method,
path: route.path,
});
}
}
}
type PendingSchedule = {
table: UntypedTableSchema;
tableName?: string;
scheduleAtCol?: number;
reducer?: () => UntypedScheduledFunctionExport;
functionName?: string;View on GitHub (pinned to eddf9f5014)