How Corsair Handles Configuration: Environment, Database, and Plugin Layers Explained

Corsair uses a three-layer configuration system: environment variables via dotenv, persisted JSON-B storage in PostgreSQL, and typed plugin options with runtime accessors.

The configuration architecture in Corsair (corsairdev/corsair) separates global server settings from dynamic, per-plugin settings that users can modify at runtime. This design allows developers to bootstrap the system with secure secrets while enabling non-technical users to customize plugin behavior through a database-backed UI.

Environment Variables: Global Server Configuration

Corsair loads environment variables at startup using dotenv. The server entry points import dotenv/config automatically, populating process.env before any configuration logic executes.

This pattern appears in the web server initialization and authentication modules:

// www/src/server/corsair.ts and www/src/lib/auth.ts
import 'dotenv/config';

if (!process.env.DATABASE_URL) {
  throw new Error('DATABASE_URL must be set in environment');
}

Environment variables handle sensitive, deployment-specific values: database URLs, API keys, encryption secrets, and feature flags. These values are read-only at runtime and never persisted to the database.

Database-Backed JSON-B Storage: Dynamic Plugin Configuration

The core of Corsair's configuration system lives in PostgreSQL jsonb columns that store arbitrary JSON objects per account and per plugin.

The schema defines this in two key locations:

This structure enables:

  • Per-account isolation — each account row carries its own config blob
  • Schema flexibility — plugins define their own configuration shape without database migrations
  • Query performance — PostgreSQL indexes jsonb fields for fast lookups

Typed Plugin Options: Type-Safe Configuration Contracts

Every Corsair plugin exports a TypeScript configuration interface that the core validates and merges. The createCorsair factory accepts a plugins array where each entry provides:

  1. A unique plugin key
  2. Runtime implementation
  3. Optional default configuration
  4. A TypeScript type defining valid configuration properties
// Example: Initializing Corsair with plugin overrides
import { createCorsair } from 'corsair';
import { slackPlugin } from '@corsair/slack';

const corsair = createCorsair({
  baseURL: 'https://api.mycompany.com/corsair',
  plugins: [
    slackPlugin({
      token: process.env.SLACK_BOT_TOKEN!, // from environment
      config: { channel: '#general' }      // persisted defaults
    })
  ]
});

The core merges three configuration sources in priority order:

  1. Environment variables (highest priority for secrets)
  2. Programmatic options passed to createCorsair
  3. Persisted database values (lowest priority, user-editable)

Runtime Configuration Access

The Corsair instance exposes typed methods for reading and updating configuration during request handling:

// Read a plugin's current configuration
const slackConfig = await corsair.getPluginConfig('slack');
// → { token: 'xoxb-...', channel: '#general' }

// Update configuration (persists to database)
await corsair.updatePluginConfig('slack', {
  channel: '#random'
});

These methods route through the ORM layer in packages/corsair/db/orm.ts, which handles:

  • JSON serialization/deserialization
  • Type guards based on plugin-defined interfaces
  • Optimistic locking to prevent concurrent update conflicts

Configuration Flow: From Boot to Runtime

Understanding the complete lifecycle clarifies how settings propagate:

Phase Action Location
Boot dotenv loads .env into process.env www/src/server/entry.ts
Init createCorsair merges env vars with plugin defaults packages/corsair/index.ts
Persist Defaults written to config jsonb column if absent packages/corsair/db/orm.ts
Startup Server begins with merged configuration www/src/server/corsair.ts
Runtime Handlers read/write via type-safe accessors Plugin implementations

Key Source Files for Configuration Handling

File Path Responsibility
www/src/db/schema.ts Defines configuration: Parameters jsonb column structure
www/src/db/corsair-schema.ts Database-level config column with empty object default
packages/corsair/index.ts createCorsair factory, configuration merging logic
packages/corsair/db/orm.ts Type-safe database reads/writes for jsonb configuration
packages/corsair/tests/plugin-auth.test.ts Test fixtures showing programmatic configuration

Summary

  • Environment variables handle deployment secrets via dotenv, loaded before application code runs.
  • PostgreSQL jsonb columns store mutable, per-account plugin configuration with flexible schema.
  • TypeScript interfaces enforce configuration contracts at compile time and runtime.
  • Runtime accessors getPluginConfig and updatePluginConfig provide safe, typed manipulation of persisted settings.
  • Priority merging puts environment overrides above programmatic defaults above database values.

Frequently Asked Questions

How does Corsair load environment variables?

Corsair imports dotenv/config at the top of server entry files. This automatically loads variables from a .env file into process.env before any configuration logic executes, making them available to createCorsair and plugin constructors.

Can plugin configuration be changed without restarting the server?

Yes. The updatePluginConfig method writes changes directly to the PostgreSQL config jsonb column. Subsequent calls to getPluginConfig return the updated values immediately without requiring a process restart.

Where is plugin configuration type-safety enforced?

Type safety exists at three levels: TypeScript interfaces define valid configuration shapes, the ORM layer validates JSON structure before persistence, and runtime accessors use the plugin's exported type to return correctly typed objects to calling code.

What happens if a plugin's configuration is missing from the database?

The ORM returns the default configuration object provided when the plugin was registered with createCorsair. These defaults are merged with environment variables and programmatic options, ensuring plugins always receive a complete, valid configuration object.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →