Skip to main content
The schema is the single source of truth for a config file. You declare your config shape once with defineConfig(), and TypeScript infers all value types automatically — no manual type annotations, no any casting. The schema also drives runtime validation, keyring routing, and SQLite column generation.

defineConfig()

defineConfig() accepts a plain object where each key maps to a schema value. It returns the object unchanged, but its TypeScript overload enforces that keyring IDs are unique at the type level and validates both array shapes and duplicate IDs at runtime.
import { defineConfig, keyring, optional } from "tauri-plugin-configurate-api";

const appSchema = defineConfig({
  theme: String,
  language: String,
  fontSize: optional(Number),
  database: {
    host: String,
    password: keyring(String, { id: "db-password" }),
  },
});
defineConfig() does two things at runtime:
  1. Calls validateSchemaArrays() — ensures every array field contains exactly one element descriptor.
  2. Collects all keyring() IDs across the full schema tree and throws if any ID appears more than once.
defineConfig() is required to get TypeScript inference. Passing a plain object literal directly to Configurate without wrapping it in defineConfig() will still work at runtime, but you lose the duplicate-ID check and the inferred generic type.

Schema value types

Every key in a schema object maps to one of the following value types.

Primitive constructors

Use the built-in JavaScript constructors as type markers.
ConstructorTypeScript typeNotes
StringstringAny string value
NumbernumberMust be finite — NaN and Infinity fail validation
Booleanbooleantrue or false
const schema = defineConfig({
  name: String,
  count: Number,
  enabled: Boolean,
});

keyring(typeCtor, )

Marks a field as keyring-protected. The value is never written to disk — it is stored in the OS keyring and returned as null when loading without .unlock().
import { keyring } from "tauri-plugin-configurate-api";

const schema = defineConfig({
  apiKey: keyring(String, { id: "api-key" }),
  secretScore: keyring(Number, { id: "secret-score" }),
});
The id parameter:
  • Must be a non-empty string.
  • Must not contain / (it is used as a path segment in the OS keyring user string: {account}/{id}).
  • Must be unique across the entire schema, including nested objects and array elements.

optional(schema)

Marks a field as not required. An optional field may be absent from the stored config object. When absent, the inferred TypeScript type includes undefined. optional() can wrap any other schema value:
import { optional, keyring } from "tauri-plugin-configurate-api";

const schema = defineConfig({
  fontSize: optional(Number),                              // optional primitive
  proxy: optional({ host: String, port: Number }),        // optional nested object
  tags: optional([String]),                               // optional array
  token: optional(keyring(String, { id: "opt-token" })), // optional keyring field
});

Nested objects

Any plain object in the schema is treated as a nested config object. Nesting can be arbitrarily deep.
const schema = defineConfig({
  server: {
    host: String,
    port: Number,
    tls: {
      enabled: Boolean,
      certPath: optional(String),
    },
  },
});

Arrays

An array schema is a single-element tuple — a one-element array containing the element descriptor. This signals “an array of values of this type.”
const schema = defineConfig({
  tags: [String],           // string[]
  ports: [Number],          // number[]
  servers: [{ host: String, port: Number }], // object array
});
defineConfig() throws at runtime if an array contains anything other than exactly one element descriptor. Arrays of keyring() fields are also supported:
const schema = defineConfig({
  tokens: [keyring(String, { id: "multi-token" })],
});
The id in a keyring() array element is a base ID. At runtime, each array element gets a unique storage key derived from the base ID and its index: {id}::{encodedDotpath}. This means array keyring IDs are still subject to the uniqueness constraint — the base ID must be unique across all keyring() calls in the schema.

Type inference

defineConfig() returns the schema typed as S extends SchemaObject. Two utility types then derive TypeScript types from S.

InferUnlocked<S>

InferUnlocked<S> is the full data type with keyring fields resolved to their actual TypeScript types. Use this type when you have called .unlock(keyringOpts) or when writing data to a create() / save() call.
import type { InferUnlocked } from "tauri-plugin-configurate-api";

const schema = defineConfig({
  theme: String,
  password: keyring(String, { id: "pw" }),
  fontSize: optional(Number),
});

type AppConfig = InferUnlocked<typeof schema>;
// {
//   theme: string;
//   password: string;
//   fontSize: number | undefined;
// }

InferLocked<S>

InferLocked<S> is the type returned by .load().run() — all keyring-protected fields are replaced with null. This is the “safe” view of the data, suitable for display or logging because it contains no secrets.
import type { InferLocked } from "tauri-plugin-configurate-api";

type LockedAppConfig = InferLocked<typeof schema>;
// {
//   theme: string;
//   password: null;       // keyring field → null
//   fontSize: number | undefined;
// }
Both types handle optional fields, arrays, and nested objects recursively:
const schema = defineConfig({
  servers: [{ host: String, secret: keyring(String, { id: "srv-secret" }) }],
});

type Locked = InferLocked<typeof schema>;
// { servers: { host: string; secret: null }[] }

type Unlocked = InferUnlocked<typeof schema>;
// { servers: { host: string; secret: string }[] }

Duplicate keyring ID detection

Every keyring() call must use a unique id within the schema. defineConfig() enforces this at two levels:
  1. Type level — The TypeScript overload uses conditional types to detect duplicate IDs at compile time. If two keyring() calls share the same id string literal, the call to defineConfig() produces a type error.
  2. Runtime leveldefineConfig() calls collectKeyringIds() to traverse the full schema tree (including nested objects, array element schemas, and optional wrappers) and checks for duplicate IDs in a Set. A duplicate triggers an explicit Error:
Error: Duplicate keyring id: 'my-id'. Each keyring() call must use a unique id within the same schema.
This is caught immediately at module initialization time, not on first use.
// This throws at runtime AND produces a TypeScript error:
const bad = defineConfig({
  a: keyring(String, { id: "same" }),
  b: keyring(String, { id: "same" }), // duplicate!
});

Build docs developers (and LLMs) love