Skip to content

Plugins

A plugin packages procedures, raw routes, and H3 middleware behind one .use() call. Built-in features (.auth(), .admin(), .files(), .mcp(), .openapi()) stay as they are — plugins are the extension point for third-party and app-specific features.

Write a plugin

An OuterPlugin has a unique name and optional hooks:

import type { OuterPlugin } from "@outerjs/server";

export const pingPlugin: OuterPlugin = {
  name: "ping",
  build(ctx) {
    return {
      procedures: {
        ping: ctx.base.handler(() => "pong"),
      },
    };
  },
};

Register it anywhere in the chain before .start() / .build():

const server = await new Outer({ db: pglite() }).schema(v1_0).use(pingPlugin).start();

POST /rpc/ping is now mounted. Double-registering the same name throws.

Lifecycle hooks

HookWhenPurpose
configureAt .use() timeFail fast on bad config. Store options on the plugin object.
validateAt the start of build, after Outer’s built-in checksReject the build — for example if .auth() is missing. Pure validation, no side effects.
buildAfter built-in procedures (admin, files) are assembledReturn procedures, routes, and/or middleware to mount.
const needsAuth: OuterPlugin = {
  name: "needs-auth",
  validate(ctx) {
    if (!ctx.resources.auth) throw new Error("This plugin requires .auth()");
  },
  build(ctx) {
    return {
      procedures: {
        "secure.ping": ctx.base.handler(() => "pong"),
      },
      routes: [
        {
          method: "get",
          path: "/plugin-health",
          handler: () => ({ ok: true }),
        },
      ],
      middleware: [
        async (event, next) => {
          event.res.headers.set("x-plugin", "needs-auth");
          return next();
        },
      ],
    };
  },
};

What build can return

FieldShapeBehavior
proceduresRecord<string, AnyProcedure>Registered like .procedure() — keys are dot-names ("webhook.receive"POST /rpc/webhook/receive).
routes{ method, path, handler }[]Mounted like .route(), before /rpc/**.
middleware(event, next) => Promise<unknown>[]Runs after CORS and rate-limit middleware, before routes.

build receives a PluginContext:

FieldDescription
resourcesRead-only snapshot of the resources bag (auth, storage, cors, …).
baseThe oRPC base builder with the current context type — chain .handler() on it.
schemasSchema versions registered so far.
nameThe Outer instance name, if any.

Prefer returning procedures over mutating resources. Routes and middleware from plugins are collected and mounted by Outer.

Next steps