UsefulKey

Configuration

How to configure UsefulKey.

UsefulKey is configured when you create an instance with usefulkey(config, { plugins }). This page explains the available options, sensible defaults, and common setup patterns.

The config object

import type { UsefulKeyConfig } from "betterkey";

// All fields are optional; safe defaults are applied.
const config: UsefulKeyConfig = {
  // Key formatting
  keyPrefix: "uk",            // default: "uk"
  disablePrefix: false,        // default: false
  defaultKeyKind: KEY.URLSafe(40),

  // Infrastructure adapters (storage, rate limits, analytics)
  adapters: {
    keyStore: new MemoryKeyStore(),          // in-memory by default
    rateLimitStore: new MemoryRateLimitStore(),
    analytics: new ConsoleAnalytics(),
  },

  // Crypto provider and custom behaviors
  crypto: globalThis.crypto,                           // or Node's crypto
  secret: process.env.UK_SECRET,                       // enable keyed hashing (HMAC-SHA256)
  customHashKey: (key) => key,                         // override hashing entirely (takes precedence over secret)
  customGenerateKey: (kind) => "my-custom-key",        // override generator
  customIdGenerator: () => "my_custom_id",             // override id
  // Cleanup behavior
  autoDeleteExpiredKeys: false,                        // default: false; hard-delete expired keys on access when true
};

Defaults applied at runtime:

  • keyPrefix: "uk"
  • defaultKeyKind: KEY.URLSafe(40)
  • adapters: in-memory key store, in-memory rate limit store, console analytics
  • autoDeleteExpiredKeys: false

Note: the rendered key's prefix is controlled by keyPrefix and disablePrefix.

See the list of supported adapters in Adapters, and plugin features in Plugins.

Minimal setup

import { usefulkey } from "betterkey";

const uk = usefulkey({});

You get an instance backed by in-memory adapters, suitable for local development, tests, and examples.

Typical development setup (in-memory to start)

import { usefulkey, KEY, MemoryKeyStore, MemoryRateLimitStore, ConsoleAnalytics } from "betterkey";

export const uk = usefulkey({
  keyPrefix: "uk",
  defaultKeyKind: KEY.URLSafe(40),
  adapters: {
    keyStore: new MemoryKeyStore(),
    rateLimitStore: new MemoryRateLimitStore(),
    analytics: new ConsoleAnalytics(),
  },
});

Centralized instance in lib/usefulkey.ts

We recommend a centralised instance of UsefulKey in lib/usefulkey.ts and import it where needed:

// lib/usefulkey.ts
import { usefulkey, KEY, MemoryKeyStore, MemoryRateLimitStore, ConsoleAnalytics } from "betterkey";
import { ratelimit } from "usefulkey/plugins/rate-limit/global";
import { ipAccessControlStatic } from "usefulkey/plugins";
import { usageLimitsPerKeyPlugin } from "usefulkey/plugins/usage-limits-per-key";

export const uk = usefulkey(
  {
    keyPrefix: "uk",
    defaultKeyKind: KEY.URLSafe(40),
    adapters: {
      keyStore: new MemoryKeyStore(),
      rateLimitStore: new MemoryRateLimitStore(),
      analytics: new ConsoleAnalytics(),
    },
  },
  {
    plugins: [
      ratelimit({ default: { kind: "fixed", limit: 100, duration: "1m" } }),
      ipAccessControlStatic({ allow: [], deny: [] }),
      usageLimitsPerKeyPlugin(),
    ],
  }
);

Then elsewhere:

import { uk } from "./lib/usefulkey";

const created = await uk.createKey({ userId: "u1", metadata: { plan: "pro" } });
const verified = await uk.verifyKey({ key: created.result!.key, identifier: "127.0.0.1" });

Configuring adapters (production)

Swap in persistent adapters when you deploy to production.

import { usefulkey, PostgresKeyStore, RedisRateLimitStore, ConsoleAnalytics } from "betterkey";

const pgClient = new Pool({
  connectionString: process.env.DATABASE_URL!,
});

const redisClient = new Redis({
  url: process.env.REDIS_URL!,
});

const uk = usefulkey({
  adapters: {
    keyStore: new PostgresKeyStore({
      pgClient
    }),
    rateLimitStore: new RedisRateLimitStore({
      redisClient
    }),
    analytics: new ConsoleAnalytics(),
  },
});

For a complete list and details, see the adapter pages:

Disabling analytics

UsefulKey emits analytics events by default using the Console adapter. To disable analytics entirely, supply the Noop adapter:

import { usefulkey } from "betterkey";

const uk = usefulkey({
  adapters: {
    analytics: new NoopAnalytics(),
  },
});

Adding plugins

Plugins are provided as the second argument to the config object and can enforce policies or extend the instance with helpers.

import { usefulkey } from "betterkey";
import { enableDisablePlugin, permissionsScopesPlugin } from "usefulkey/plugins";
import { ratelimit } from "usefulkey/plugins/rate-limit/global";

const uk = usefulkey(
  {},
  {
    plugins: [
      enableDisablePlugin(),
      permissionsScopesPlugin({ metadataKey: "scopes" }),
      ratelimit({ default: { kind: "fixed", limit: 100, duration: "1m" } }),
      usageLimitsPerKeyPlugin(),
    ],
  }
);

See Plugins for all built-ins and usage examples.

Config Options

defaultKeyKind

Choose the default key format.

import { KEY } from "betterkey";
const uk = usefulkey({ defaultKeyKind: KEY.URLSafe(56) });

keyPrefix and disablePrefix

Control the rendered key prefix.

const uk = usefulkey({ keyPrefix: "acme", disablePrefix: false });
// e.g. acme_xxxxxxxxxx

customGenerateKey(kind?)

Fully override how plaintext keys are generated.

const uk = usefulkey({
  customGenerateKey: (kind) => `${Date.now()}-${Math.random().toString(36).slice(2)}`,
});

secret

When set, UsefulKey uses HMAC-SHA256 for hashing/verification. Improves resistance to offline guessing if your DB leaks. See the Security section for more information on how to generate and manage the secret key.

const uk = usefulkey({ secret: process.env.UK_SECRET! });

customHashKey(key)

Override hashing entirely. Takes precedence over secret. Hashes must be stable and collision-resistant.

import crypto from "node:crypto";

const uk = usefulkey({
  secret: process.env.UK_SECRET!,
  customHashKey: (key) => crypto.createHash("sha256").update(key).digest("hex"),
});

customIdGenerator()

Override the default UUID used for key ids.

const uk = usefulkey({ customIdGenerator: () => `k_${crypto.randomUUID()}` });

autoDeleteExpiredKeys

Automatically delete expired keys on access.

const uk = usefulkey({ autoDeleteExpiredKeys: true });

Crypto provider

You can pass one of the following as config.crypto:

  • Node-style object (has createHash, optional randomUUID, randomBytes, and optional webcrypto):

    import nodeCrypto from "node:crypto";
    const uk = usefulkey({ crypto: nodeCrypto });
  • Web Crypto-like object (has getRandomValues, optional randomUUID):

    const uk = usefulkey({ crypto: globalThis.crypto });
  • Partial provider to override specific primitives:

    import { sha256 } from "js-sha256";
    
    const uk = usefulkey({
      crypto: {
        hashSha256: (input: string) => sha256(input),
        getRandomValues: (arr: Uint8Array) => globalThis.crypto.getRandomValues(arr),
        randomUUID: () => globalThis.crypto.randomUUID(),
      },
    });

UsefulKey's provider surface expects these primitives when you pass a partial:

type CryptoProvider = {
  hashSha256(input: string): string;
  getRandomValues(array: Uint8Array): Uint8Array;
  randomUUID(): string;
}

Setup readiness

Some plugins and adapters perform async setup. You can optionally await uk.ready before first use:

await uk.ready; // resolves once all plugin setup hooks finish