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

📧

Email

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/example with 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:create and auth:registration:completed

Key Patterns to Copy

PatternWhere
/example/* routes instead of /api/*Avoids the core /api/:collection catch-all
Factory function createExampleApiRoutes(options)Passes live config by reference from onBoot
configSchema objectAuto-renders settings form — no custom handler needed
Settings merge: { ...defaults, ...existingSettings }Saved values win; preserves _routes/_adminPath
COUNT guard before seedingIdempotent 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 login
  • auth:logout - Triggered when user logs out
  • request: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_email Workers 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

ProviderHow to enable
ResendSet RESEND_API_KEY env var, or configure in Admin → Email settings
Cloudflare EmailAdd an EMAIL Workers binding (send_email) in wrangler.toml
ConsoleAutomatic fallback when no provider is configured (dev only)

Provider resolution order at boot:

  1. RESEND_API_KEY env var → Resend (wins regardless of DB setting)
  2. DB provider=resend + saved API key → Resend
  3. EMAIL Workers binding present, provider not forced to resend → Cloudflare Email
  4. 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

  1. Sign up at resend.com and get your API key
  2. Set RESEND_API_KEY in your env, or navigate to /admin/email/settings and enter it there
  3. Verify your sending domain in Resend
  4. Send a test email from /admin/email/settings

Option B — Cloudflare Email

  1. Add a send_email binding named EMAIL in wrangler.toml
  2. Configure Cloudflare Email Routing in your Cloudflare dashboard
  3. No API key required — the Workers binding handles auth
  4. 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 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

  1. Better Auth secret — set BETTER_AUTH_SECRET via wrangler secret put BETTER_AUTH_SECRET (or in .dev.vars for local dev). This is required for all Better Auth features.
  2. 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

  1. User submits their email to POST /auth/sign-in/magic-link
  2. Better Auth generates a secure one-time token and stores it in auth_verification
  3. Email is delivered via the Email plugin (Resend)
  4. User clicks the link — GET /auth/magic-link/verify?token=...
  5. Better Auth validates the token, marks it used, and creates a session
  6. User is redirected with better-auth.session_token cookie 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

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 expiresIn in auth/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_verification managed 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
      )
    })
  }
})

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 cache
  • content:delete - Invalidates content cache
  • user:update - Invalidates user cache
  • auth: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

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

Next Steps

Was this page helpful?