Core Plugins Reference
SonicJS includes several essential core plugins that provide fundamental functionality for your CMS. These plugins are automatically installed and configured when you set up SonicJS.
Overview
Core plugins extend SonicJS with critical features like authentication, media management, caching, and database administration. They follow the same plugin architecture as custom plugins, making them excellent examples for plugin development.
Available Core Plugins
Example Plugin
Bundled reference plugin — routes, collections, settings, hooks, and data seeding in one working example
Authentication
Better Auth session management with RBAC, JWT API tokens, and magic links
Transactional email sending via Resend or Cloudflare Email with pre-built templates
Magic Link Auth
Passwordless authentication using secure email magic links
Media
Media file management with R2 storage and image processing
Cache
Three-tier caching system with memory, KV, and database layers
Database Tools
Database administration, migrations, and query tools
QR Code Generator
Generate styled QR codes with logo embedding and scan tracking
Redirect Management
URL redirects with CSV import/export and Cloudflare edge sync
Example Plugin
Every new SonicJS app scaffolded with npm create sonicjs@latest ships with a working Example plugin. It's a complete, production-pattern reference for every major plugin extension point — routes, collections, settings, hooks, and data seeding.
What It Does
- Public JSON API at
/example,/example/:name,/example/moods - Admin page at
/admin/examplewith automatic sidebar entry - Moods collection managed via
/admin/content?model=example - Three default moods seeded at first boot (idempotent COUNT guard)
- Configurable greeting text, default name, and mood selector via the settings form
- Lifecycle hook subscriptions for
content:after:createandauth:registration:completed
Key Patterns to Copy
| Pattern | Where |
|---|---|
/example/* routes instead of /api/* | Avoids the core /api/:collection catch-all |
Factory function createExampleApiRoutes(options) | Passes live config by reference from onBoot |
configSchema object | Auto-renders settings form — no custom handler needed |
Settings merge: { ...defaults, ...existingSettings } | Saved values win; preserves _routes/_adminPath |
| COUNT guard before seeding | Idempotent across isolate restarts |
registerCollections([moodsCollection]) | Code-only collection — no DB table |
Removing It
When you're ready to ship your own plugin:
// src/index.ts — remove these lines:
import { examplePlugin } from './plugins/example'
import { moodsCollection } from './plugins/example/collections/moods.collection'
// remove moodsCollection from registerCollections([...])
// remove examplePlugin from plugins.register: [...]
Then delete src/plugins/example/.
Authentication Plugin
The authentication plugin provides comprehensive user authentication and session management functionality, built on Better Auth — a TypeScript-first authentication framework designed for modern full-stack applications.
Why Better Auth?
SonicJS evaluated several auth libraries and chose Better Auth for a few reasons that matter specifically in the Cloudflare Workers context:
Edge-compatible by design. Most auth libraries assume a Node.js runtime. Better Auth is built from the ground up to run on edge runtimes — no fs, no crypto polyfills, no Node-specific dependencies. It runs natively on Cloudflare Workers without shims.
TypeScript-first. Better Auth exposes fully typed APIs for sessions, users, and plugins. This aligns with SonicJS's TypeScript-first philosophy and lets the compiler catch auth bugs at build time.
Works with D1 / SQLite. Better Auth ships a database adapter that works with any SQL database. SonicJS wires it directly to Cloudflare D1 — sessions and user records live in the same D1 instance as your content, no separate auth service required.
Pluggable auth strategies. Email/password, OAuth providers, and magic links are all first-class citizens in Better Auth's plugin model. SonicJS's Magic Link Auth plugin is a thin wrapper on top — the session lifecycle is handled by Better Auth, not by custom JWT logic.
Active maintenance and security posture. Better Auth follows a responsible-disclosure process and releases patches quickly. Rolling your own session management in a CMS is a high-risk trade-off; delegating it to a focused library reduces the attack surface.
Features
- Session Authentication - Cookie-based sessions via Better Auth (
better-auth.session_token) - RBAC - Role-based access control seeded into
rbac_role/rbac_verb/rbac_user_roles - JWT API Tokens - Signed tokens for programmatic API access (separate from the session cookie)
- Rate Limiting - Protect authentication endpoints from abuse
- User Context - Inject authenticated user data into request context via
c.get('user')
Configuration
Plugin Configuration
// Auth is configured via env vars, not plugin settings:
// JWT_SECRET — signing secret for API tokens
// JWT_EXPIRES_IN — token TTL (default: '1h')
// ADMIN_EMAIL — bootstrap admin email
// ADMIN_PASSWORD — bootstrap admin password
//
// Better Auth session uses its own cookie (better-auth.session_token).
// RBAC roles are seeded by RbacService into rbac_role/rbac_verb/rbac_user_roles.
API Endpoints
POST /api/auth/login Authenticate a user and receive JWT tokens
Login Request
curl -X POST http://localhost:8787/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "password123"
}'
POST /api/auth/logout Invalidate current session
GET /api/auth/me Get current authenticated user information
POST /api/auth/refresh Refresh an expired access token
Auth Context
SonicJS injects the authenticated user into every request via c.get('user'):
Auth Context
// In any route handler:
const user = c.get('user')
// user: { userId: string, email: string, role: string } | null
// Better Auth also exposes its full API via the auth instance:
import { auth } from '@sonicjs-cms/core'
// Verify a session from within a Worker:
const session = await auth.api.getSession({ headers: request.headers })
Session management (sign-in, sign-out, session tokens) is handled by Better Auth. See the Better Auth docs for the full API surface.
Hooks
auth:login- Triggered when user attempts loginauth:logout- Triggered when user logs outrequest:start- Injects authentication status into requests
Admin Pages
- /admin/auth/sessions - View and manage active Better Auth sessions
Email Plugin
The email plugin provides transactional email functionality using either Resend or Cloudflare Email, with pre-built templates for common authentication and notification scenarios.
Features
- Resend Integration - Send emails via the Resend API (API key required)
- Cloudflare Email - Send via the Cloudflare
send_emailWorkers binding (no API key needed) - Email Templates - Pre-built templates for registration, verification, password reset, and one-time codes
- Template Customization - Code-based templates that can be customized
- Logo Support - Add your company logo to email templates
- Settings Management - Admin UI for configuring email settings
- Test Email - Send test emails to verify configuration
- Console Fallback - Zero-config dev mode logs emails to the console instead of delivering them
Providers
| Provider | How to enable |
|---|---|
| Resend | Set RESEND_API_KEY env var, or configure in Admin → Email settings |
| Cloudflare Email | Add an EMAIL Workers binding (send_email) in wrangler.toml |
| Console | Automatic fallback when no provider is configured (dev only) |
Provider resolution order at boot:
RESEND_API_KEYenv var → Resend (wins regardless of DB setting)- DB
provider=resend+ saved API key → Resend EMAILWorkers binding present, provider not forced to resend → Cloudflare Email- Auto-detect from env, then Console fallback
Configuration
Resend — env var (recommended)
# wrangler.toml or .dev.vars
RESEND_API_KEY=re_...
Cloudflare Email — wrangler.toml binding
[[send_email]]
name = "EMAIL"
Admin UI settings shape
// POST /admin/email/settings
{
provider: 'resend' | 'cloudflare',
resendApiKey: 're_...', // Resend API key (if not in env)
fromEmail: 'noreply@yourdomain.com',
fromName: 'Your App Name',
replyTo: 'support@yourdomain.com', // optional
logoUrl: 'https://yourdomain.com/logo.png', // optional
cfAccountId: '...', // optional — for delivery reconciliation
cfEmailApiToken: '...', // optional — for delivery reconciliation
}
Setup
Option A — Resend
- Sign up at resend.com and get your API key
- Set
RESEND_API_KEYin your env, or navigate to/admin/email/settingsand enter it there - Verify your sending domain in Resend
- Send a test email from
/admin/email/settings
Option B — Cloudflare Email
- Add a
send_emailbinding namedEMAILinwrangler.toml - Configure Cloudflare Email Routing in your Cloudflare dashboard
- No API key required — the Workers binding handles auth
- Send a test email from
/admin/email/settings
Included Email Templates
Registration Confirmation Sent when a new user registers an account
Email Verification Sent to verify user email addresses
Password Reset Sent when users request to reset their password
One-Time Code (OTP/2FA) Sent for one-time passwords and two-factor authentication codes
API Usage
Sending Emails
import { getEmailService } from '@sonicjs-cms/core'
// Send a custom email
await getEmailService().send({
to: 'user@example.com',
subject: 'Welcome to SonicJS',
html: '<h1>Welcome!</h1><p>Thanks for signing up.</p>',
flow: 'welcome', // recorded in email_log for observability
})
// Send to multiple recipients
await getEmailService().send({
to: ['alice@example.com', 'bob@example.com'],
subject: 'Team notification',
html: '<p>Hello team!</p>',
})
Admin Pages
- /admin/email/settings - Configure provider, API keys, and sender identity
Magic Link Auth
Magic link authentication is powered by Better Auth's native magicLink plugin — it is always active when the Email plugin is configured. No separate plugin activation is required.
Users receive a one-time sign-in link via email. Clicking it creates a full Better Auth session (cookie: better-auth.session_token).
Prerequisites
- Better Auth secret — set
BETTER_AUTH_SECRETviawrangler secret put BETTER_AUTH_SECRET(or in.dev.varsfor local dev). This is required for all Better Auth features. - Email plugin configured — go to Admin → Plugins → Email Plugin and enter a valid Resend API key and verified sender address. Without a working email service, magic links are logged to the console only.
No plugin activation step is needed. Once the two prerequisites above are met, magic link is live.
How It Works
- User submits their email to
POST /auth/sign-in/magic-link - Better Auth generates a secure one-time token and stores it in
auth_verification - Email is delivered via the Email plugin (Resend)
- User clicks the link —
GET /auth/magic-link/verify?token=... - Better Auth validates the token, marks it used, and creates a session
- User is redirected with
better-auth.session_tokencookie set
API Endpoints
POST /auth/sign-in/magic-link
Send a magic link to an email address.
Request Magic Link
curl -X POST http://localhost:8787/auth/sign-in/magic-link \
-H "Content-Type: application/json" \
-d '{ "email": "user@example.com" }'
Better Auth does not reveal whether the email belongs to an existing user — the response is always { status: true } for any valid email format.
GET /auth/magic-link/verify?token=...&callbackURL=/admin/dashboard
Better Auth handles this automatically. On success it creates a session and redirects to callbackURL (defaults to /). On failure it redirects with an ?error= param.
Better Auth Client (recommended for frontend)
Better Auth Client
import { createAuthClient } from 'better-auth/client'
import { magicLinkClient } from 'better-auth/client/plugins'
const client = createAuthClient({
baseURL: 'https://your-app.workers.dev',
plugins: [magicLinkClient()]
})
Plain HTML Example
Magic Link Form
<form id="magicLinkForm">
<label>Email Address</label>
<input type="email" name="email" required />
<button type="submit">Send Magic Link</button>
</form>
<script>
document.getElementById('magicLinkForm').addEventListener('submit', async (e) => {
e.preventDefault()
const email = new FormData(e.target).get('email')
const response = await fetch('/auth/sign-in/magic-link', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email })
})
if (response.ok) {
alert('Check your email for a magic link!')
}
})
</script>
Security
- One-time use — token is invalidated immediately after first click
- 15-minute expiry — configurable via
expiresIninauth/config.ts - No user enumeration — always returns
{ status: true }for valid email format - Better Auth session — creates a proper session entry, not a stateless JWT
- No custom DB table — token state lives in
auth_verificationmanaged by Better Auth
Extending the Configuration
Magic link options live in packages/core/src/auth/config.ts inside the plugins array. To change expiry or add custom email templates, extend via config.auth.extendBetterAuth in createSonicJSApp():
createSonicJSApp({
auth: {
extendBetterAuth: (opts) => ({
...opts,
plugins: opts.plugins?.map((p: any) =>
p.id === 'magic-link'
? { ...p, options: { ...p.options, expiresIn: 30 * 60 } } // 30 min
: p
)
})
}
})
Magic link requires the Email plugin (Admin → Plugins → Email Plugin) to be configured with a valid Resend API key. In local dev without email configured, the link is printed to the Workers console instead of being sent.
Media Plugin
The media plugin provides comprehensive media file management with cloud storage integration.
Features
- File Upload - Upload images, videos, and documents to R2 storage
- Media Library - Browse and search uploaded media files
- Image Processing - Resize, compress, and transform images
- Folder Organization - Organize files into folders
- Metadata Extraction - Automatic extraction of file metadata
- Thumbnail Generation - Auto-generate thumbnails for images
- Bulk Operations - Move, delete, and process multiple files
Configuration
Plugin Configuration
{
name: 'core-media',
version: '1.0.0-beta.1',
settings: {
maxFileSize: 10485760, // Max file size (10MB)
allowedTypes: [ // Allowed MIME types
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
'video/mp4',
'application/pdf'
],
thumbnailSize: { width: 300, height: 300 },
storageProvider: 'r2', // Storage backend
autoGenerateThumbnails: true
}
}
API Endpoints
GET /api/media List media files with pagination and filtering
List Media
curl "http://localhost:8787/api/media?page=1&limit=20&type=image"
POST /api/media/upload Upload a new media file
Upload File
const formData = new FormData()
formData.append('file', fileInput.files[0])
formData.append('folder', 'images')
const response = await fetch('http://localhost:8787/api/media/upload', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`
},
body: formData
})
const { data } = await response.json()
// data: { id, filename, url, size, type }
GET /api/media/:id Get media file information and metadata
DELETE /api/media/:id Delete a media file
POST /api/media/process Process media (resize, compress, transform)
POST /api/media/create-folder Create a new folder for organization
POST /api/media/bulk-move Move multiple files to a folder
Services
Media Service
const mediaService = {
// Upload file to storage
uploadFile(file: File, options?: UploadOptions): Promise<MediaFile>
// Delete file from storage
deleteFile(id: string): Promise<boolean>
// Process image with operations
processImage(id: string, operations: Operation[]): Promise<Job>
// Extract file metadata
getMetadata(id: string): Promise<Metadata>
}
Database Schema
Media Files Table
CREATE TABLE media_files (
id INTEGER PRIMARY KEY,
filename TEXT NOT NULL,
original_name TEXT NOT NULL,
mime_type TEXT NOT NULL,
size INTEGER NOT NULL,
url TEXT NOT NULL,
thumbnail_url TEXT,
metadata TEXT,
uploaded_by INTEGER,
tags TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER
)
Hooks
media:upload- Triggered when file is uploaded (auto-generates thumbnails)media:delete- Triggered when file is deleted (cleanup related files)content:save- Tracks media references in content
Admin Pages
- /admin/media - Media library browser
- /admin/media/upload - File upload interface
- /admin/media/settings - Media processing configuration
Cache Plugin
The cache plugin implements a sophisticated three-tier caching system for optimal performance.
Features
- Three-Tier Caching - Memory, Cloudflare KV, and D1 database
- Automatic Invalidation - Event-based cache invalidation
- Cache Namespaces - Separate caches for different data types
- Performance Metrics - Hit rates, latency, and usage statistics
- Cache Warming - Pre-populate cache with frequently accessed data
- Pattern Invalidation - Invalidate by wildcard patterns
- TTL Management - Configurable time-to-live per namespace
Architecture
Request
↓
Memory Cache? ──Yes──→ Return (< 1ms)
↓ No
KV Cache? ──Yes──→ Populate Memory → Return (10-50ms)
↓ No
Database ──→ Populate KV → Populate Memory → Return (100-200ms)
Configuration
Cache Configuration
{
name: 'cache',
version: '1.0.0-alpha.1',
settings: {
memoryEnabled: true, // Enable in-memory cache
kvEnabled: false, // Enable KV cache
defaultTTL: 3600, // Default TTL (1 hour)
maxMemorySize: 50, // Max memory cache size (MB)
namespaces: {
content: { ttl: 3600 }, // Content cache (1 hour)
user: { ttl: 900 }, // User cache (15 minutes)
api: { ttl: 300 } // API response cache (5 minutes)
}
}
}
API Endpoints
GET /admin/cache/stats Get detailed cache statistics for all namespaces
Cache Stats
curl http://localhost:8787/admin/cache/stats
POST /admin/cache/clear Clear all cache entries across all tiers
POST /admin/cache/invalidate Invalidate cache entries matching a pattern
Invalidate Cache
curl -X POST http://localhost:8787/admin/cache/invalidate \
-H "Content-Type: application/json" \
-d '{
"pattern": "content:*",
"namespace": "content"
}'
GET /admin/cache/browser Browse and inspect individual cache entries
POST /admin/cache/warm Pre-populate cache with frequently accessed data
Usage in Code
Using Cache Service
import { getCacheService } from '@sonicjs-cms/core'
// Get cache service for your namespace
const cache = getCacheService({
namespace: 'my-plugin',
ttl: 600,
memoryEnabled: true,
kvEnabled: false,
invalidateOn: ['my-plugin:update'],
version: 'v1'
})
// Get value from cache
const data = await cache.get('key')
// Set value in cache
await cache.set('key', data)
// Get or set pattern
const result = await cache.getOrSet('key', async () => {
return await fetchExpensiveData()
})
// Invalidate by pattern
await cache.invalidate('user:*')
// Get statistics
const stats = cache.getStats()
Cache Namespaces
content - Content and pages (TTL: 1 hour) user - User data and profiles (TTL: 15 minutes) api - API response data (TTL: 5 minutes) media - Media file metadata (TTL: 1 hour) auth - Authentication tokens (TTL: 5 minutes)
Hooks
content:update- Invalidates content cachecontent:delete- Invalidates content cacheuser:update- Invalidates user cacheauth:login- Invalidates user cache
Database Tools
The database tools plugin provides administrative utilities for database management and development.
Features
- Migration Runner - Execute database migrations
- Schema Browser - View tables, columns, and indexes
- Query Console - Execute SQL queries directly
- Table Inspector - View table data and statistics
- Backup and Restore - Database backup utilities
- Performance Metrics - Query performance analysis
Configuration
Plugin Configuration
{
name: 'database-tools-plugin',
version: '1.0.0',
settings: {
enableQueryConsole: true, // Enable SQL query console
maxQueryRows: 1000, // Max rows returned
enableBackups: true, // Enable backup functionality
autoMigration: true // Auto-run migrations on startup
}
}
API Endpoints
GET /admin/database/tables List all database tables
GET /admin/database/tables/:name View table schema and data
POST /admin/database/query Execute a SQL query
Execute Query
curl -X POST http://localhost:8787/admin/database/query \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${token}" \
-d '{
"query": "SELECT * FROM content LIMIT 10"
}'
POST /admin/database/migrations/run Run pending migrations
GET /admin/database/migrations/status Check migration status
Services
Database Service
const databaseService = {
// List all tables
getTables(): Promise<Table[]>
// Get table schema
getTableSchema(tableName: string): Promise<Schema>
// Execute query
executeQuery(sql: string, params?: any[]): Promise<Result>
// Run migrations
runMigrations(): Promise<MigrationResult[]>
// Create backup
createBackup(): Promise<Backup>
}
Admin Pages
- /admin/database - Database dashboard
- /admin/database/tables - Table browser
- /admin/database/query - Query console
- /admin/database/migrations - Migration manager
The database tools plugin is restricted to admin users only for security. Never expose database query capabilities to untrusted users.
QR Code Generator
The QR Code Generator plugin provides a complete QR code management system with customizable styling, logo embedding, and scan tracking.
Features
- Custom Styling — Colors, corner shapes (square/rounded/dots/extra-rounded), dot shapes (square/rounded/dots/diamond), and eye colors
- Logo Embedding — Embed logos with automatic error correction upgrade to Level H
- Scan Tracking — Track scans via short URL redirects (
/qr/{shortCode}) with analytics - Export Formats — SVG and PNG at 72, 150, or 300 DPI
- Admin UI — Full CRUD interface with live preview, search, and pagination
Admin Pages
- /admin/qr-codes — QR code management interface
Redirect Management
The Redirect Management plugin handles URL redirects with sub-millisecond lookups, CSV bulk operations, and Cloudflare edge integration.
Features
- Flexible Matching — Exact, wildcard, and regex URL matching
- Status Codes — 301, 302, 307, 308, and 410 (Gone)
- Analytics — Hit counts and last-hit timestamps for every redirect
- CSV Import/Export — Bulk operations with validation and duplicate handling strategies
- Cloudflare Sync — Offload eligible redirects to Cloudflare Bulk Redirects for edge-level processing
- LRU Cache — Sub-millisecond redirect lookups with 1000-entry cache
Admin Pages
- /admin/redirects — Redirect management interface