Skip to Content

Adapter Hooks

Plugins contribute Adapter hooks through OberonPluginAdapterHooks:

type OberonPluginAdapterHook<Key extends keyof OberonPluginAdapter> = (context: { getAdapter: () => Omit<OberonAdapter, Key> next: OberonPluginAdapter[Key] }) => OberonPluginAdapter[Key]

Each hook is initialized once with the final Adapter getter and the preceding next implementation, then returns an Adapter method with its original signature. Methods with input receive one object: updatePageData receives a Page, while putKV receives { namespace, key, value }. Methods without input remain zero-argument functions.

Call getAdapter() from the returned method when it executes, not while the hook factory initializes. The getter throws until composition is complete, then returns the same final augmented Adapter used by Handlers and actions. Its current method is omitted from the getter’s static return type to prevent accidental self-recursion. Use next to continue the current method’s middleware chain.

Later plugins are outermost. For [storage, tailwind, seo, framework], updatePageData enters framework, seo, tailwind, then storage, and unwinds in reverse. A direct provider ignores next and returns its implementation; middleware returns a function that calls next before or after its own behavior.

Bootstrap hooks

Lifecycle work used during setup and build is exposed as a top-level bootstrap hook, not as adapter middleware.

type OberonPlugin = ({ phase }: { phase: OberonPluginPhase }) => { bootstrap?: (context: { adapter: OberonAdapter }) => Promise<void> }

Use bootstrap for migrations or initialization required before build output. Enabled hooks are awaited once in configured plugin order. If one throws, later hooks and core Page/Site initialization do not run.

OberonDatabaseAdapter

Database plugins own the persistence surface.

Content and media methods

  • addPage
  • deletePage
  • getAllPages
  • getPageData
  • updatePageData
  • addImage
  • deleteImage
  • getAllImages

Site and key-value methods

  • getSite
  • updateSite
  • getKV
  • putKV
  • deleteKV

User methods

  • addUser
  • deleteUser
  • getAllUsers
  • changeRole

OberonCanAdapter

Permission and session helpers:

  • getCurrentUser
  • hasPermission
  • signIn
  • signOut

hasPermission receives user, action, and permission, and returns a boolean synchronously. The final Adapter also provides can, will, and whoWill. Actions use these helpers to enforce permissions without making persistence methods authorize themselves.

OberonAuthAdapter

Auth storage and extensions:

  • getAuthDatabase
  • getAuthPlugins

OberonRoutingAdapter

Framework integrations provide request and navigation behavior:

  • redirect
  • notFound
  • getRequestHeaders

OberonSendAdapter

Send plugins provide verification delivery:

  • sendVerificationRequest
type OberonSendAdapter = { sendVerificationRequest: (props: { email: string; token: string; url: string }) => Promise<void> }

Minimal custom implementations

Database-oriented custom plugin:

import { USE_DEVELOPMENT_DATABASE_PLUGIN, notImplemented, type OberonPlugin } from "@oberoncms/core" import { migrate } from "./database" export const databasePlugin: OberonPlugin = () => ({ name: "Custom Database Plugin", disabled: USE_DEVELOPMENT_DATABASE_PLUGIN, bootstrap: async ({ adapter }) => { await migrate(adapter) }, adapter: { addPage: () => notImplemented("addPage"), addImage: () => notImplemented("addImage"), addUser: () => notImplemented("addUser"), deletePage: () => notImplemented("deletePage"), deleteImage: () => notImplemented("deleteImage"), deleteKV: () => notImplemented("deleteKV"), deleteUser: () => notImplemented("deleteUser"), changeRole: () => notImplemented("changeRole"), getAllImages: () => notImplemented("getAllImages"), getAllPages: () => notImplemented("getAllPages"), getAllUsers: () => notImplemented("getAllUsers"), getKV: () => notImplemented("getKV"), getPageData: () => notImplemented("getPageData"), getSite: () => notImplemented("getSite"), putKV: () => notImplemented("putKV"), updatePageData: () => notImplemented("updatePageData"), updateSite: () => notImplemented("updateSite"), }, })

Send-oriented custom plugin:

import "server-cli-only" import { USE_DEVELOPMENT_SEND_PLUGIN, type OberonPlugin } from "@oberoncms/core" const EMAIL_FROM = process.env.EMAIL_FROM const SEND_SECRET = process.env.SEND_SECRET async function sendEmail( message: { from: string subject: string text: string to: string }, secret: string, ) { void message void secret throw new Error("sendEmail is not implemented") } export const sendPlugin: OberonPlugin = () => ({ name: "Custom Send", disabled: USE_DEVELOPMENT_SEND_PLUGIN, adapter: { sendVerificationRequest: () => async ({ email, token, url }) => { if (!SEND_SECRET) throw new Error("No SEND_SECRET configured") if (!EMAIL_FROM) throw new Error("No EMAIL_FROM configured") await sendEmail( { from: EMAIL_FROM, subject: "One time login to Oberon CMS", text: `Sign in with code\n\n${token}\n\n${url}\n\n`, to: email, }, SEND_SECRET, ) }, }, })
Last updated on