Skip to main content
Every Configurate operation returns a typed result object that wraps the config data. Understanding these types helps you handle the locked/unlocked distinction correctly and work with batch results and event callbacks.

LockedConfig<S>

Returned by .run() on create, load, save, and reset operations. Keyring-protected fields are always null in the data property.
class LockedConfig<S extends SchemaObject> {
  readonly data: InferLocked<S>;
  unlock(opts: KeyringOptions): Promise<UnlockedConfig<S>>;
}
data
InferLocked<S>
required
The config data with keyring fields replaced by null. Access this property to read non-secret fields without touching the OS keyring.
unlock(opts)
Promise<UnlockedConfig<S>>
Fetches keyring values from the OS keyring and returns an UnlockedConfig with all fields populated. Requires configurate:allow-unlock in your capability file.
// Load returns a LockedConfig when using .run()
const locked = await config.load().run();
console.log(locked.data.theme);             // "dark"
console.log(locked.data.database.password); // null (keyring field)

// Unlock later when you need the secret
const unlocked = await locked.unlock(KEYRING);
console.log(unlocked.data.database.password); // "secret"

UnlockedConfig<S>

Returned by .unlock(keyringOpts) on create, load, save, and reset operations, or by calling locked.unlock(opts). Provides access to all fields including keyring-protected ones.
class UnlockedConfig<S extends SchemaObject> {
  get data(): InferUnlocked<S>;
  lock(): void;
}
data
InferUnlocked<S>
required
The config data with all keyring fields populated with their actual values. Accessing data after calling lock() throws an error.
lock()
void
Revokes access to the decrypted data through this instance. After calling lock(), accessing .data throws. This is an API-level access guard — it does not zero-clear memory.
const unlocked = await config.load().unlock(KEYRING);
console.log(unlocked.data.database.password); // "secret"

// Revoke access when done
unlocked.lock();
console.log(unlocked.data.database.password); // throws: "Cannot access data after lock() has been called"
lock() does not zero-clear the underlying memory. JavaScript’s garbage collector manages memory reclamation and immediate clearing cannot be guaranteed. Treat lock() as an API-level access guard, not a cryptographic wipe.

PatchedConfig<S>

Returned by .run() on patch operations. Contains the partial data that was merged — not the full config.
class PatchedConfig<S extends SchemaObject> {
  readonly data: Partial<InferLocked<S>>;
}
data
Partial<InferLocked<S>>
required
The partial config data that was submitted to the patch. Keyring fields in the patch are null. This is the patch payload, not a full reload of the stored config.
const patched = await config.patch({ theme: "light" }).run();
console.log(patched.data.theme); // "light"
// Other fields (e.g. database.host) are not present — this is only the patched portion

BatchRunResult

Returned by .run() on Configurate.loadAll(), Configurate.saveAll(), and Configurate.patchAll(). Contains per-entry results keyed by the id strings you provided.
interface BatchRunResult {
  results: Record<string, BatchRunEntryResult>;
}
results
Record<string, BatchRunEntryResult>
required
A map from entry ID to its individual result. Each value is either a success or a failure — a single entry failing does not abort the rest of the batch.
const result = await Configurate.loadAll([
  { id: "app", config: appConfig },
  { id: "theme", config: themeConfig },
]).run();

// Check each entry individually
if (result.results.app.ok) {
  console.log(result.results.app.data);
} else {
  console.error(result.results.app.error.message);
}

BatchRunEntryResult

The type of each value inside BatchRunResult.results. A discriminated union on the ok field.
type BatchRunEntryResult =
  | { ok: true; data: unknown }
  | { ok: false; error: { kind: string; message: string } };
ok
boolean
required
true if the operation for this entry succeeded; false if it failed.
data
unknown
Present when ok is true. The loaded or saved config data for this entry.
error.kind
string
Present when ok is false. A short machine-readable error kind (e.g. "schema_validation", "unlock_failed", "payload_build_failed").
error.message
string
Present when ok is false. A human-readable description of what went wrong.

KeyringOptions

Passed to .lock(), .unlock(), delete(), exportAs(), and importFrom() to identify where secrets are stored in the OS keyring.
interface KeyringOptions {
  service: string;
  account: string;
}
service
string
required
The keyring service name. Typically your app name. Used to namespace secrets within the OS keyring.
account
string
required
The keyring account name. Can be a user identifier or a constant like "default". Combined with service to form the keyring entry key.
const KEYRING: KeyringOptions = {
  service: "my-app",
  account: "default",
};

await config.create({ theme: "dark", database: { host: "localhost", password: "secret" } })
  .lock(KEYRING)
  .run();

ConfigChangeEvent

Passed to the callback registered with config.onChange() and config.watchExternal().
interface ConfigChangeEvent {
  fileName: string;
  operation: string;
  targetId: string;
}
fileName
string
required
The config file name that was changed (e.g. "app.json").
operation
string
required
The kind of change. One of "create", "save", "patch", "delete", "reset", "import", or "external_change". "external_change" is emitted by watchExternal only.
targetId
string
required
An opaque internal identifier that uniquely describes the storage target (file name, base directory, provider, and path options). Used internally to filter events to the correct Configurate instance.
const unlisten = await config.onChange((event: ConfigChangeEvent) => {
  console.log(`Operation: ${event.operation}, file: ${event.fileName}`);
  // e.g. "Operation: patch, file: app.json"
});

MigrationStep

Used with the version and migrations constructor options to define schema versioning. Each step upgrades data from one schema version to the next.
interface MigrationStep<TData extends Record<string, unknown>> {
  version: number;
  up: (data: TData) => TData;
}
version
number
required
The schema version this migration upgrades from. A step with version: 1 transforms data stored at version 1 into version 2 data.
up
(data: TData) => TData
required
A pure transform function. Receives data at version and must return data at version + 1. Migrations run automatically on load(), and migrated data is auto-saved back to storage.
const config = new Configurate({
  schema,
  fileName: "app.json",
  baseDir: BaseDirectory.AppConfig,
  provider: JsonProvider(),
  version: 2,
  migrations: [
    {
      version: 0,
      up: (data) => ({ ...data, language: "en" }),         // v0 → v1: add language field
    },
    {
      version: 1,
      up: (data) => {
        const { oldKey, ...rest } = data as typeof data & { oldKey?: unknown };
        return { ...rest, newKey: oldKey ?? "default" };   // v1 → v2: rename field
      },
    },
  ],
});
Migrations run in order from the stored version up to the current version. If the loaded data is migrated, it is automatically saved back to storage so the next load does not re-run the same steps.

Build docs developers (and LLMs) love