Skip to Content
πŸ–₯️ Self-HostingExtension Points & Hooks

Extension Points & Hooks

Sitepins Core is designed with a clean extension architecture. If you are building custom enterprise wrappers, adding proprietary authentication systems, or integrating with internal CRM/logging platforms, you can plug into Core’s extension seams without modifying the core source code.


Backend Extension Seams

Core exports programmatic extension hooks located in api/src/lib/:

  1. lib/lifecycleHooks.ts: User lifecycle events and audit handlers.
  2. lib/extensionGuards.ts: Request guards and response decorators, for custom quotas or policies.
  3. lib/mailer.ts: Custom transactional mail providers.
  4. lib/authIssuers.ts: External JWT issuer registration for machine-to-machine integrations.
  5. lib/authExtensions.ts: Better Auth plugin registration, for adding custom auth endpoints.
  6. startServer() in api/src/server.ts: Allows an external script to register extensions before binding HTTP listeners.
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Custom Server Wrapper / Startup Script β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ 1. setMailSender(customMailRelay) β”‚ β”‚ β”‚ β”‚ 2. onUserRegistration(syncWithInternalCRM) β”‚ β”‚ β”‚ β”‚ 3. registerJwtIssuer({ issuer, secret }) β”‚ β”‚ β”‚ β”‚ 4. startServer() β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ Bootstraps β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Sitepins Core Backend β”‚ β”‚ Express API β€’ Hocuspocus β€’ Socket.IO β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

1. Lifecycle & Event Hooks (lib/lifecycleHooks.ts)

You can attach custom handlers to key user actions:

import { onUserRegistration, onUserUpdate, onUserDeletion, onAuthEvent, } from "./lib/lifecycleHooks";

Event Hook Signatures

HookInvocation TriggerPayload DataError Handling
onUserRegistration(fn)Fired immediately after an account is created and verified (email OTP or OAuth).{ user: { id, email, full_name, provider } }Isolated in a try/catch block. Hook failure will not prevent user registration.
onUserUpdate(fn)Fired when a user updates their profile (country or email).{ type: "country", email, country } or { type: "email", userId, oldEmail, newEmail }Isolated in a try/catch block.
onUserDeletion(fn)Fired during permanent account deletion.{ userId, user, session }Runs inside the Mongoose client session transaction. Pass session to your models to join the rollback transaction.
onAuthEvent(fn)Fired on user logins and password reset completions.{ type: "login" | "password_reset", userId, ip, date }Isolated in a try/catch block.

Quotas & Policies (lib/extensionGuards.ts)

Core never limits anything. To enforce your own project or organization quotas, register guards β€” throw an error (such as ApiError with status 400) to reject the request:

  • setRequestGuard(event, fn): Runs before "organization:create", "organization:member:add", or "project:create".
  • setProjectMutationGuard(fn): Runs before a project’s visibility or status changes.
  • setOrganizationDecorator(fn) / setProjectDecorator(fn): Post-process organization and project responses, for example to attach your own metadata.

2. Custom Transactional Mailers (lib/mailer.ts)

By default, Core uses Nodemailer SMTP (or terminal console fallback in dev). You can replace the mail transport completely:

import { setMailSender, type CustomMailSender } from "./lib/mailer"; const customMailer: CustomMailSender = async ({ to, kind, params }) => { // kind: "welcome" | "otp" | "password_reset" | "delete_account" | "org_member_added" | etc. console.log(`Sending ${kind} email to ${to}`); // Deliver via your preferred API (AWS SES API, SendGrid, Postmark, etc.) await sendViaProvider({ to, template: kind, variables: params, }); }; // Register before starting server setMailSender(customMailer);

3. Registering External JWT Issuers (lib/authIssuers.ts)

If internal administrative dashboards or microservices need to invoke Core API routes using Bearer tokens:

import { registerJwtIssuer } from "./lib/authIssuers"; registerJwtIssuer({ issuer: "internal-backoffice", secret: process.env.INTERNAL_BACKOFFICE_JWT_SECRET, });

When an incoming request carries an Authorization: Bearer <token> header, Core’s authentication middleware iterates through all registered issuers and accepts the first token that validates.


4. Registering Better Auth Plugins (lib/authExtensions.ts)

To add authentication endpoints without forking core:

import { registerAuthPlugin } from "./lib/authExtensions"; registerAuthPlugin(myPlugin());

Registered plugins are collected and passed to Better Auth when the auth instance is first constructed, so every call must happen before the auth handler serves its first request β€” register at startup, alongside your other extensions.

Core ships no partner or SaaS signup logic of its own; the hosted edition uses this seam to add POST /api/v1/auth/custom-signup.


5. Programmatic Server Startup

Core exports startServer() in api/src/server.ts and only executes automatically if invoked directly as the process entrypoint:

// Example custom bootstrap script (server-wrapper.ts) import { setMailSender } from "./lib/mailer"; import { onUserRegistration } from "./lib/lifecycleHooks"; import { startServer } from "./server"; async function bootstrap() { // 1. Attach custom hooks onUserRegistration(async ({ user }) => { console.log("New user registered:", user.email); }); // 2. Launch the Express + Hocuspocus + Socket.IO server await startServer(); console.log("Sitepins Core initialized with custom extensions."); } bootstrap();

This clean architecture ensures your custom extensions survive upstream Git pulls and updates without merge conflicts.

Last updated on