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

  1. Keep exactly one `export` for each handler object and delete any aliased or re-exported duplicates.
  2. 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.
  3. 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

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


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: Router

View on GitHub (pinned to eddf9f5014)