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:
- Key store: /docs/adapters/keystore
- Rate limit store: /docs/adapters/ratelimit-store
- Analytics: /docs/adapters/analytics
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
, optionalrandomUUID
,randomBytes
, and optionalwebcrypto
):import nodeCrypto from "node:crypto"; const uk = usefulkey({ crypto: nodeCrypto });
-
Web Crypto-like object (has
getRandomValues
, optionalrandomUUID
):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