Skip to content

Handlers / remote functions

Handlers (also called remote functions in older docs) are typed server entrypoints authored in tenant workspace source. They run in the pod runtime with the same collection API and request scope as hooks and automations.

Tenant authoring path
Handlers live in remote/*.handler.ts and register through invoke: on defineWorkspace. Core's internal remote_functions/*.remote.ts and defineRemoteQuery are host-only — do not use them in tenant templates.

Source layout

remote/
├── holiday.handler.ts      # defineQueryHandler
└── notify.handler.ts       # defineCommandHandler

Definition

Import defineQueryHandler or defineCommandHandler from @norbital-ai/pod/authoring. Query handlers are read-only; command handlers may mutate via the handler api argument.

import { defineQueryHandler } from '@norbital-ai/pod/authoring';
import { z } from '@norbital-ai/pod/zod';

export const holidayFeed = defineQueryHandler({
  schema: z.object({ country_code: z.string() }),
  handler: async ({ country_code }, api) => {
    // read collections via api, call external services, return typed output
    return { country_code, holidays: [] };
  }
});

Registration

Export handlers from workspace.api.ts under invoke: when calling defineWorkspace:

const { api, ws } = defineWorkspace(schema, {
  collections: [/* … */],
  invoke: {
    holidayFeed,
    computeEntitlements: computeEntitlementsRemote
  },
  automations: [statutoryDriftCheck]
});

export { api, ws };

The invoke key becomes the client method name. Tenant apps import api from workspace source and call:

const result = await api.invoke.holidayFeed({ country_code: 'SG' });

Workspace Studio

Registered handlers appear in Workspace Studio → Manifest → Handlers with a function-square icon. Use Manifest to confirm registration without opening each source file.

When to use handlers

  • Integrations that do not map cleanly to collection CRUD
  • Aggregations or reports that span multiple collections
  • Server actions invoked from tenant apps via api.invoke

Prefer collection hooks for mutation side effects, automations for scheduled or background work, and SDK collection reads for UI data — handlers when you need a dedicated typed server entrypoint.