clockworklabs/SpacetimeDB · error · TypeError
HTTP handler ' ' was exported more than once
Error message
HTTP handler '${exportName}' was exported more than once What it means
An HTTP handler created with `spacetimedb.httpHandler(...)` carries a hidden `[registerExport]` hook. When the module's exports are registered, the export object is added to a module-level `exportedHttpHandlerObjects` WeakSet; if that exact object is already in the set, this TypeError fires. In other words, one handler export object may be registered under exactly one export name, ever, per module build.
Solutions
- Keep exactly one `export` for each handler object and delete any aliased or re-exported duplicates.
- If you need two names to serve the same logic, create a second handler with `spacetimedb.httpHandler(...)` wrapping the same underlying function, and export each object once.
- Search the module entry (and its re-export chain) for the handler name from the error message to find the duplicate `export` statement.
Example fix
// before
export const handleThing = spacetimedb.httpHandler(...);
export { handleThing as handleThingV2 }; // second registration -> TypeError
// after
export const handleThing = spacetimedb.httpHandler(...);
const thingFn = handleThing; // plain alias, not re-exported via `export` Defensive patterns
Strategy: validation
Validate before calling
// In tests or a build script: verify no export object appears under two names.
const seen = new Map<object, string>();
for (const [name, exp] of Object.entries(moduleExports)) {
if (typeof exp === 'function' || (typeof exp === 'object' && exp !== null)) {
const prev = seen.get(exp);
if (prev !== undefined && prev !== name) {
throw new Error(`handler exported twice: ${prev} and ${name}`);
}
seen.set(exp, name);
}
} Prevention
- Never alias-export handlers (`export { h as h2 }`); wrap the underlying fn in a new httpHandler instead.
- Avoid `export *` barrel chains for the module entry; list exports explicitly.
- Add a CI check that fails when an entry-module export object is reachable under two export names.
When it happens
Trigger: Re-exporting the same handler object under two names (`export { handleThing }; export { handleThing as handleThingAlias };`); exporting the same handler from a shared file and re-exporting it in the module entry; registering the same handler object in two places that both run `registerExport` during one publish.
Common situations: Barrel-file re-exports (`export * from './handlers'` plus a manual `export { handleThing }`); aliasing an export for backwards compatibility after a rename; sharing one handler between a library submodule and the app entry module.
Related errors
- exporting something that is not a spacetime export
- HTTP route for path ' ' refers to a handler that was not…
- Route conflict for
- There is already an HTTP handler with the name
- invalid sequence type
AI-assisted analysis of clockworklabs/SpacetimeDB@eddf9f5014 (2026-08-20).
Data as JSON: /api/errors/26ec7e36aa46b93a.
Report an issue: GitHub.
Appendix: source
Thrown at crates/bindings-typescript/src/server/http_handlers.ts:375
throw new TypeError(`Route conflict for \`${path}\``);
}
return new Router([...this.#routes, candidate]);
}
}
export function makeHttpHandlerExport<S extends UntypedSchemaDef>(
ctx: SchemaInner,
opts: HttpHandlerOpts | undefined,
fn: HandlerFn<S>
): HttpHandlerExport<S> {
const handlerExport: HttpHandlerExport<S> = Object.assign(
(...args: Parameters<HandlerFn<S>>) => fn(...args),
{
[httpHandlerFn]: fn,
[exportContext]: ctx,
[registerExport](ctx: SchemaInner, exportName: string) {
if (exportedHttpHandlerObjects.has(handlerExport)) {
throw new TypeError(
`HTTP handler '${exportName}' was exported more than once`
);
}
exportedHttpHandlerObjects.add(handlerExport);
registerHttpHandler(ctx, exportName, fn, opts);
ctx.httpHandlerExports.set(
handlerExport as HttpHandlerExport<UntypedSchemaDef>,
exportName
);
},
}
);
return handlerExport;
}
export function makeHttpRouterExport(
ctx: SchemaInner,
router: RouterView on GitHub (pinned to eddf9f5014)