Skip to main content
Use getNestModules for webhooks, public probes, OAuth callbacks, or long-lived services. Use getAuthExcludedRoutes to skip UCAN auth on specific paths.
1

Write a NestJS controller and module

A plugin’s module is a regular Nest module. Use a static register(opts) returning a DynamicModule if you want to pass config into DI.
Canonical source: weather.module.ts.
2

Return the module from getNestModules(ctx)

ctx is optional but useful for reading typed config when registering the module.
Plain module classes also work: return [SlackModule]. The runtime spreads the returned modules into RuntimeAppModule.imports. See getNestModules in weather.plugin.ts.
3

Opt routes out of UCAN auth (optional)

By default every plugin route goes through AuthHeaderMiddleware. For webhooks, OAuth callbacks, or public probes, return them from getAuthExcludedRoutes.
Match by the full path the controller mounts at (weather/now, not now). method defaults to RequestMethod.ALL. See getAuthExcludedRoutes in weather.plugin.ts.
4

Verify the endpoint

Boot the oracle with pnpm dev, then curl the route without an auth header.
Other routes still 401 — the exclusion list only opts these specific paths out.

What to know before shipping

  • The same hook handles long-lived services. Slack, BullMQ workers, polling clients all ship as Nest modules with OnModuleInit lifecycle.
  • Plugin modules have full DI access to the runtime’s services (Sessions, Messages, Secrets, UCAN, …).
  • Host code can also opt routes out of auth via createOracleApp({ authExcludedRoutes }). Plugin and host lists merge with built-in exclusions (/health, /docs).
  • Leading slash on path is optional. The matcher mirrors NestJS’s MiddlewareConsumer.exclude(...).
  • Plugin route paths are namespaced by the controller’s @Controller('weather') decorator — keep them under your plugin’s name to avoid collisions.

Create your oracle

Host-level Nest modules and authExcludedRoutes.

API endpoints

The framework’s always-on HTTP/WS surface.