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

> Discover how Corsair manages configuration across environment variables, PostgreSQL database storage, and typed plugin options. Learn about its robust three-layer system for seamless development.

- Repository: [corsairdev/corsair](https://github.com/corsairdev/corsair)
- Tags: internals
- Published: 2026-09-01

---

**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:

```typescript
// 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`](https://github.com/corsairdev/corsair/blob/main/www/src/db/schema.ts) — declares `configuration: Parameters` using a generic Parameters type for type-safe JSON storage [[source](https://github.com/corsairdev/corsair/blob/main/www/src/db/schema.ts#L102)]
- [`www/src/db/corsair-schema.ts`](https://github.com/corsairdev/corsair/blob/main/www/src/db/corsair-schema.ts) — creates the `config` jsonb column with a default empty object [[source](https://github.com/corsairdev/corsair/blob/main/www/src/db/corsair-schema.ts#L12)]

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

```typescript
// 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:

```typescript
// 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`](https://github.com/corsairdev/corsair/blob/main/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`](https://github.com/corsairdev/corsair/blob/main/www/src/server/entry.ts) |
| Init | `createCorsair` merges env vars with plugin defaults | [`packages/corsair/index.ts`](https://github.com/corsairdev/corsair/blob/main/packages/corsair/index.ts) |
| Persist | Defaults written to `config` jsonb column if absent | [`packages/corsair/db/orm.ts`](https://github.com/corsairdev/corsair/blob/main/packages/corsair/db/orm.ts) |
| Startup | Server begins with merged configuration | [`www/src/server/corsair.ts`](https://github.com/corsairdev/corsair/blob/main/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`](https://github.com/corsairdev/corsair/blob/main/www/src/db/schema.ts) | Defines `configuration: Parameters` jsonb column structure |
| [`www/src/db/corsair-schema.ts`](https://github.com/corsairdev/corsair/blob/main/www/src/db/corsair-schema.ts) | Database-level `config` column with empty object default |
| [`packages/corsair/index.ts`](https://github.com/corsairdev/corsair/blob/main/packages/corsair/index.ts) | `createCorsair` factory, configuration merging logic |
| [`packages/corsair/db/orm.ts`](https://github.com/corsairdev/corsair/blob/main/packages/corsair/db/orm.ts) | Type-safe database reads/writes for jsonb configuration |
| [`packages/corsair/tests/plugin-auth.test.ts`](https://github.com/corsairdev/corsair/blob/main/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.