Plugins
Plugins extend the Radiant runtime with custom lifecycle hooks. They can override core behaviour (like storage), add global middleware (like request logging), or integrate external services.
The Plugin Interface
interface RadiantPlugin {
name: string;
onInit?: (app: RadiantRuntime) => void | Promise<void>;
beforeRequest?: (ctx: RadiantRequestContext) => void | Promise<void>;
afterRequest?: (ctx: RadiantRequestContext, response: Response) => void | Promise<void>;
onError?: (ctx: RadiantRequestContext, error: any) => Response | void | Promise<Response | void>;
}
| Hook | When it runs |
|---|---|
onInit | Once, during app.start(), before the server boots |
beforeRequest | Before every HTTP request (after rate limiting) |
afterRequest | After every HTTP request, before the response is sent |
onError | When an unhandled error occurs — can return a custom Response |
Registering Plugins
Pass plugins in the createRadiant() config, or push them onto app.plugins:
// In src/app.ts
import { createRadiant } from "../radiant/gen";
import { sqlite } from "@codesordinatestudio/radiant-plugin-sqlite";
const requestLogger = {
name: "request-logger",
beforeRequest: (ctx) => {
console.log(`${ctx.request.method} ${ctx.request.url}`);
},
};
export const app = createRadiant({
adapter: sqlite({ url: process.env.DATABASE_URL! }),
plugins: [requestLogger],
});
Or dynamically:
app.plugins.push({
name: "auth-guard",
beforeRequest: (ctx) => {
if (!ctx.user) throw new Error("Authentication required");
},
});
Built-in Plugin Uses
Request Logging
const logger = {
name: "logger",
beforeRequest: (ctx) => {
console.log(`→ ${ctx.request.method} ${new URL(ctx.request.url).pathname}`);
},
afterRequest: (ctx, res) => {
console.log(`← ${res.status} ${new URL(ctx.request.url).pathname}`);
},
};
Global Auth Guard
import { RadiantError } from "@codesordinatestudio/radiant-bun";
const authGuard = {
name: "auth-guard",
beforeRequest: (ctx) => {
const authHeader = ctx.request.headers.get("authorization");
if (!authHeader) throw RadiantError.Unauthorized("Token required");
// Verify token...
},
};
Error Handling
const errorHandler = {
name: "error-handler",
onError: (ctx, err) => {
console.error("Unhandled error:", err);
return new Response(JSON.stringify({ error: "Something went wrong" }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
},
};
Overriding Storage
Plugins can replace the default storage provider by setting app.storage during onInit:
import { s3Storage } from "@codesordinatestudio/radiant-plugin-s3";
const s3Plugin = {
name: "s3-storage",
onInit: (app) => {
app.storage = s3Storage({
bucket: "my-uploads",
region: "us-east-1",
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
});
},
};
Writing a Custom Plugin
A plugin is just an object with a name and any combination of lifecycle hooks:
// src/plugins/analytics.ts
import type { RadiantPlugin } from "@codesordinatestudio/radiant-bun";
export function analyticsPlugin(apiKey: string): RadiantPlugin {
return {
name: "analytics",
afterRequest: (ctx, res) => {
// Send request metrics to an analytics service
fetch("https://analytics.example.com/ingest", {
method: "POST",
headers: { "X-API-Key": apiKey },
body: JSON.stringify({
method: ctx.request.method,
path: new URL(ctx.request.url).pathname,
status: res.status,
userId: ctx.user?.id,
}),
}).catch(() => {}); // fire-and-forget
},
};
}
// src/app.ts
import { analyticsPlugin } from "./plugins/analytics";
export const app = createRadiant({
adapter: sqlite({ url: process.env.DATABASE_URL! }),
plugins: [
analyticsPlugin(process.env.ANALYTICS_API_KEY!),
],
});
Plugin Order
Plugins run in the order they're registered:
onInit— runs once, in registration orderbeforeRequest— runs in registration order before each requestafterRequest— runs in registration order after each requestonError— runs in registration order; the first plugin to return aResponsewins
First-Party Plugins
Radiant offers a suite of first-party plugins for databases, storage, email, and front-end integration. See their individual documentation pages for setup instructions:
Database Adapters
- SQLite Plugin — Default, blazing fast adapter using
bun:sqlite - PostgreSQL Plugin — Production-grade adapter optimized for high concurrency
- SurrealDB Plugin — Multi-model graph/document database adapter
Storage & Realtime
- S3 Storage Plugin — Route uploads to AWS S3, Cloudflare R2, or MinIO
- Durable Streams Plugin — Resilient, resumable SSE streams
Email Transports
- Resend Plugin — Fast delivery using the modern Resend REST API
- Nodemailer Plugin — Standard SMTP support for SendGrid, Mailgun, AWS SES, etc.
Frontend Integration
- RPC Client SDK — Fully-typed fetch wrapper for React, Vite, and other frontend apps
Related
- Storage — The
StorageProviderinterface and S3 plugin - Email — Email transport plugins
- Custom Endpoints — Adding routes