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:
www/src/db/schema.ts— declaresconfiguration: Parametersusing a generic Parameters type for type-safe JSON storage [source]www/src/db/corsair-schema.ts— creates theconfigjsonb column with a default empty object [source]
This structure enables:
- Per-account isolation — each account row carries its own
configblob - 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:
- A unique plugin key
- Runtime implementation
- Optional default configuration
- 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:
- Environment variables (highest priority for secrets)
- Programmatic options passed to
createCorsair - 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
getPluginConfigandupdatePluginConfigprovide 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →