Global Variables Plugin

Define reusable key-value variables and reference them as inline tokens in your content using {variable_key} syntax. Variables are resolved server-side on every content read, so updates propagate instantly.


Overview

The Global Variables plugin lets you store dynamic content values (company name, phone number, pricing, legal text, etc.) in a central location and reference them throughout your site content. When content is fetched, all {variable_key} tokens are automatically replaced with their current values.

🔄

Dynamic Content

Change a value once and it updates everywhere it is referenced

🏷️

Inline Tokens

Use simple {variable_key} syntax in any text field

In-Memory Cache

Variables are cached for fast resolution with a configurable TTL

📂

Categories

Organize variables into categories for easier management

Use Cases

  • Branding - Company name, tagline, copyright year
  • Contact info - Phone numbers, email addresses, office locations
  • Pricing - Plan prices, discount codes, trial durations
  • Legal - Policy version numbers, compliance text
  • Feature flags - Simple on/off text swaps

Setup

Step 1: Register the Plugin

Add the Global Variables plugin to your SonicJS configuration:

Register Plugin

import { globalVariablesPlugin } from '@sonicjs-cms/core/plugins'

export default {
  plugins: [
    globalVariablesPlugin,
    // ... other plugins
  ],
}

The plugin automatically creates the global_variables database table on activation. No manual migration is needed.

Step 2: Create Variables

Use the API or Admin UI to create your first variables. See the sections below for details on both methods.


Defining Variables

Each variable has the following fields:

FieldTypeRequiredDescription
keystringYesUnique identifier. Lowercase letters, numbers, and underscores only. Max 100 characters.
valuestringYesThe replacement text. Max 10,000 characters.
descriptionstringNoHuman-readable note about the variable's purpose. Max 500 characters.
categorystringNoGrouping label (e.g. branding, contact, pricing). Max 50 characters.
isActivebooleanNoWhether the variable is resolved in content. Defaults to true.

Key Format Rules

Variable keys must match the pattern ^[a-z0-9_]+$:

  • Lowercase letters (a-z)
  • Numbers (0-9)
  • Underscores (_)
  • No spaces, hyphens, or uppercase letters

Valid and Invalid Keys

# Valid keys
company_name
support_email
price_pro_monthly
copyright_year_2026

# Invalid keys
Company-Name     (uppercase and hyphens)
support email    (spaces)
price.pro        (dots)

Using Variables in Content

Reference any active variable in your content by wrapping its key in curly braces:

Token Syntax

Welcome to {company_name}! Contact us at {support_email}.

Our Pro plan starts at {price_pro_monthly}/month.

When this content is read through the SonicJS API, the tokens are replaced with their stored values:

Resolved Output

Welcome to Acme Corp! Contact us at support@acme.com.

Our Pro plan starts at $49/month.

Resolution Behavior

  • Tokens are resolved server-side on every content read via the content:read hook.
  • Unresolved tokens are left as-is. If {unknown_key} has no matching variable, it remains literally as {unknown_key} in the output. This makes it easy to spot missing or misspelled keys.
  • Inactive variables are not resolved. Setting isActive to false effectively disables a variable without deleting it.
  • Resolution is recursive -- it processes all string values in nested objects and arrays.

API Reference

All endpoints are mounted at /api/global-variables and require authentication.

List All Variables

GET /api/global-variables

# List all variables
curl -X GET https://your-site.com/api/global-variables \
  -H "Authorization: Bearer YOUR_TOKEN"

# Filter by category
curl -X GET "https://your-site.com/api/global-variables?category=branding" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Filter by active status
curl -X GET "https://your-site.com/api/global-variables?active=true" \
  -H "Authorization: Bearer YOUR_TOKEN"

Query Parameters:

ParameterTypeDescription
categorystringFilter by category name
active"true" or "false"Filter by active status

Response:

List Response

{
  "success": true,
  "data": [
    {
      "id": 1,
      "key": "company_name",
      "value": "Acme Corp",
      "description": "The company display name",
      "category": "branding",
      "isActive": true,
      "createdAt": 1712649600,
      "updatedAt": 1712649600
    }
  ]
}

Get Resolved Key-Value Map

Returns a flat object of all active variables. Useful for client-side rendering.

GET /api/global-variables/resolve

curl -X GET https://your-site.com/api/global-variables/resolve \
  -H "Authorization: Bearer YOUR_TOKEN"

Response:

Resolve Response

{
  "success": true,
  "data": {
    "company_name": "Acme Corp",
    "support_email": "support@acme.com",
    "price_pro_monthly": "$49"
  }
}

Get Single Variable

GET /api/global-variables/:id

curl -X GET https://your-site.com/api/global-variables/1 \
  -H "Authorization: Bearer YOUR_TOKEN"

Create a Variable

POST /api/global-variables

curl -X POST https://your-site.com/api/global-variables \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "company_name",
    "value": "Acme Corp",
    "description": "The company display name",
    "category": "branding",
    "isActive": true
  }'

Returns 201 on success. Returns 409 if a variable with the same key already exists.

Update a Variable

PUT /api/global-variables/:id

curl -X PUT https://your-site.com/api/global-variables/1 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "value": "Acme Corporation"
  }'

Only the fields you include in the request body are updated. Omitted fields remain unchanged.

Delete a Variable

DELETE /api/global-variables/:id

curl -X DELETE https://your-site.com/api/global-variables/1 \
  -H "Authorization: Bearer YOUR_TOKEN"

Configuration

The plugin manifest exposes the following settings:

Plugin Settings

interface GlobalVariablesSettings {
  enableResolution: boolean  // Enable/disable token resolution (default: true)
  cacheEnabled: boolean      // Enable in-memory variable cache (default: true)
  cacheTTL: number           // Cache time-to-live in seconds (default: 300)
}

Permissions

PermissionDescription
global-variables:manageCreate, update, and delete variables
global-variables:viewView variables in the admin UI and API

Caching

Variables are cached in memory to avoid a database query on every content read.

  • TTL: 5 minutes (300 seconds) by default.
  • Invalidation: The cache is automatically invalidated whenever a variable is created, updated, or deleted through the API.
  • Scope: The cache is per-worker instance. In a multi-worker deployment, changes propagate to other workers after their local cache expires.

Admin UI

The plugin adds a Global Variables menu item to the SonicJS admin panel at /admin/global-variables. The admin page displays all variables in a table with the following columns:

  • Token - The {key} syntax for easy copy-paste
  • Value - The current replacement value
  • Description - The human-readable note
  • Category - The grouping label
  • Active - A green or gray dot indicating active status

Variables are sorted by category and then by key.


Advanced Usage

Direct Resolver Usage

You can use the variable resolver directly in your own code without going through the content hook:

Direct Resolution

import {
  resolveVariables,
  resolveVariablesInObject,
} from '@sonicjs-cms/core/plugins'

// Resolve tokens in a single string
const variables = new Map([
  ['company_name', 'Acme Corp'],
  ['year', '2026'],
])

const resolved = resolveVariables(
  'Copyright {year} {company_name}',
  variables
)
// => "Copyright 2026 Acme Corp"

// Resolve tokens in a nested object
const data = {
  title: 'Welcome to {company_name}',
  meta: {
    description: 'Since {year}',
  },
  tags: ['{company_name}', 'cms'],
}

const resolvedData = resolveVariablesInObject(data, variables)
// => { title: "Welcome to Acme Corp", meta: { description: "Since 2026" }, tags: ["Acme Corp", "cms"] }

Database-Backed Resolution

For resolving variables in custom route handlers using the database:

Database Resolution

import { resolveContentVariables } from '@sonicjs-cms/core/plugins'

app.get('/api/custom-endpoint', async (c) => {
  const db = c.env.DB

  const content = {
    heading: 'Contact {company_name}',
    body: 'Email us at {support_email}',
  }

  // Fetches variables from DB (with caching) and resolves tokens
  const resolved = await resolveContentVariables(content, db)

  return c.json(resolved)
})

Programmatic Cache Control

Cache Management

import { invalidateCache } from '@sonicjs-cms/core/plugins'

// Force the variable cache to refresh on the next content read
invalidateCache()

Troubleshooting

Tokens Not Being Replaced

Check the key format:

  • Keys must be lowercase alphanumeric with underscores only (^[a-z0-9_]+$)
  • The token {My-Variable} will never resolve because the key contains uppercase and a hyphen

Check the variable is active:

  • Inactive variables are skipped during resolution
  • Verify via GET /api/global-variables?active=true

Check spelling:

  • Unresolved tokens are left as-is, so {comapny_name} (typo) will not match company_name

Variables Not Updating

Cache TTL:

  • After updating a variable through the API, the change takes effect immediately for the current worker
  • Other workers will pick up the change within 5 minutes (or the configured TTL)
  • If you updated the database directly, wait for the cache to expire

Verify the update succeeded:

Verify Update

curl -X GET https://your-site.com/api/global-variables/resolve \
  -H "Authorization: Bearer YOUR_TOKEN"

Duplicate Key Error (409)

Variable keys must be unique. If you receive a 409 Conflict response when creating a variable, a variable with that key already exists. Use the PUT endpoint to update it instead.

Table Not Found Errors

The global_variables table is created automatically when the plugin activates. If you see errors related to the table not existing:

  1. Verify the plugin is registered in your configuration
  2. Check the application logs for [GlobalVariables] Table created/verified
  3. Ensure the database binding (DB) is correctly configured

Next Steps

Was this page helpful?