HighX
Reference

Configuration

highx.config.ts, the lock file, and what each field is for.

The config declares what analysis cannot know. It does not list bindings — those are inferred from your code.

highx/highx.config.ts
import { defineConfig } from "highx";
import { cloudflare } from "@highx/cloudflare/preset";

export default defineConfig({
  adapter: cloudflare(),
  name: "notes",
});

That is a complete configuration. Bindings come from your code, resource IDs from the lock, and everything platform-specific from the adapter. What remains is what nothing else can tell you:

export default defineConfig({
  adapter: cloudflare(),
  name: "notes",

  entry: "index.ts",
  functions: "functions.ts",
  output: "../.highx",

  vars: { LOG_LEVEL: "debug" },
  resources: { GLOBAL_DB: "notes-global", SESSION_KV: "notes-sessions" },
  retention: { subscriptionHistoryLimit: 32, sagaRetentionDays: 30, idempotencyRetentionDays: 7 },
  stages: { production: {} },
});

Note what is not here: no compatibility date, no binding list, no resource kinds, no file format. Those are inferred, defaulted by the adapter, or the adapter's business entirely — which is what will let a second platform be added without this file changing shape.

Fields

adapter

The platform you deploy to, and the source of everything platform-specific: how to recognize its bindings, how to reach its secret store, how to verify credentials, and how to write its configuration file.

adapter: cloudflare(),                                    // defaults are enough
adapter: cloudflare({ compatibilityDate: "2025-01-01" }), // unless you disagree

cloudflare() accepts compatibilityDate, compatibilityFlags, and observability. Without an adapter, bindings can be found but not classified, and doctor fails with config:adapter rather than guessing.

name

The deployment's name, and the default prefix for resource names. The only field with no sensible default.

entry

The entry the compiler analyses. Defaults to index.ts.

functions

The module declaring your descriptors.

output

Where the generated API is written, relative to the config. Defaults to .highx beside it; the scaffold sets ../.highx so a frontend imports it as ../.highx/api rather than reaching down through the server directory.

output: "../app/src/generated",

The generated client module is string literals only, so importing it can never pull a handler into a browser bundle.

resources

Account-level names, keyed by binding name. The kind is not repeated, because your code already established it:

resources: { GLOBAL_DB: "notes-global" },

Anything unnamed falls back to <name>-<binding>. An entry in the lock beats both — a resource that exists is not silently repointed because a name changed.

retention

Bounds on operational storage. Declare it here and hand it to createTenantObject: this field is what doctor reads, the tenant object is what sweeps. → Retention

vars

Plain, committed configuration values. Never secrets — a vars entry is in source control, visible in the dashboard, and shared by every environment.

stages

Named deployment targets, each with its own resources. --stage selects one; without it, commands act on the base stage.

bindings

An escape hatch for the two cases inference cannot cover: a binding reached through indirection the compiler cannot follow, and a kind it guessed wrong.

bindings: [
  { name: "LEGACY_KV", kind: "kv" },
  { name: "ANALYTICS", kind: "queue", isOptional: true },
],

An entry here wins over what was inferred. isOptional makes a missing binding a warning rather than a failure.

classify

If you wrap a binding in a class of your own, name its constructor and anything passed to it is classified:

classify: { MyVectorStore: "r2", TelemetrySink: "queue" },

statefulClasses

Only needed when you export more than one Durable Object class, since the pairing is otherwise unambiguous:

statefulClasses: { ACCOUNT_DO: "AccountDO", BILLING_DO: "BillingDO" },

highx.lock.json

Records which real resource each binding resolved to:

{
  "version": 1,
  "resources": {
    "GLOBAL_DB": { "kind": "d1", "name": "highx-global", "id": "8f1b3c2d-…" }
  }
}

Commit it. It exists because the platform configuration is generated, and a generated file cannot be where a real resource ID lives — the next generation would erase it, and a D1 database ID cannot be derived again.

A malformed lock is an error rather than being treated as empty, because an empty lock quietly means "provision everything again".

On this page