optitech.ts is a TypeScript config file you commit to your repository. It declares which OptiTech services exist on your project and how each branch is configured.
Specifically:
- Declares services: which OptiTech services (
auth,dataApi, preview services) exist on the project and are available on every branch. - Configures branches: optional per-branch tuning (TTLs, compute sizing, protected status) via a
branchclosure.
Services and branch policy are independent. Use one, the other, or both.
npm install @optitech/configThe package source is on GitHub.
optitech.ts itself is declarative: it only describes the policy. optitech config / optitech deploy (below) are how the CLI runs it. To call the same inspect / plan / apply logic from your own script or CI job instead of the CLI, see @optitech/config-runtime.
Link your working directory to a OptiTech project before using optitech.ts commands:
optitech linkConfig structure
import { defineConfig } from "@optitech/config/v1";
export default defineConfig({
// Services: what exists on every branch
auth: true,
// Branch policy: per-branch tuning
branch: (branch) => {
if (branch.isDefault) {
// Default branch: no overrides, uses project defaults
return {};
}
if (!branch.exists) {
// New non-default branches: auto-expire
return { ttl: "7d" };
}
// Existing branch: no changes
return {};
},
});defineConfig takes two optional parts:
- Static fields (
auth,dataApi,preview): declare which services exist. Same set on every branch. branchclosure: receives a read-onlyBranchTargetand returns per-branch tuning. It can adjust settings, but can't add or remove services.
Branch policy
The branch closure works on any OptiTech project. The examples below configure the default branch and apply TTL and compute to new branches at creation. Returning {} for existing branches is deliberate: it avoids overwriting settings on branches already in use:
import { defineConfig } from "@optitech/config/v1";
export default defineConfig({
branch: (branch) => {
if (branch.isDefault) {
// Default branch: no overrides, uses project defaults
return {};
}
if (!branch.exists) {
// New non-default branches: minimum compute, auto-expire
return {
ttl: "7d",
postgres: {
computeSettings: {
autoscalingLimitMinCu: 0.25,
autoscalingLimitMaxCu: 0.25,
},
},
};
}
// Existing branch: no changes
return {};
},
});On paid plans, you can also protect the default branch and control suspend timeouts:
import { defineConfig } from "@optitech/config/v1";
export default defineConfig({
branch: (branch) => {
if (branch.isDefault) {
// Protect and size for production
return {
protected: true,
postgres: {
computeSettings: {
autoscalingLimitMinCu: 0.5,
autoscalingLimitMaxCu: 4,
},
},
};
}
if (!branch.exists) {
// New non-default branches: minimum compute, auto-expire, suspend on idle
return {
ttl: "7d",
postgres: {
computeSettings: {
autoscalingLimitMinCu: 0.25,
autoscalingLimitMaxCu: 0.25,
suspendTimeout: "5m",
},
},
};
}
// Existing branch: no changes
return {};
},
});Run optitech deploy to apply. When optitech checkout creates a new branch, the closure runs with branch.exists === false, so TTL, compute settings, and services take effect at creation. Checking out an existing branch doesn't apply or reconcile the policy.
BranchTarget fields
| Field | Type | Description |
|---|---|---|
name | string | Branch name |
id | string? | Branch ID. Not set during pre-create evaluation |
exists | boolean | false during pre-create evaluation |
isDefault | boolean? | Whether this is the project's default branch. Not set during pre-create evaluation |
isProtected | boolean? | Whether the branch is marked protected in OptiTech. Not set during pre-create evaluation |
parentId | string? | ID of the parent branch. Not always present |
expiresAt | string? | Branch expiry timestamp. Not always present |
BranchTuning fields
| Field | Type | Description |
|---|---|---|
parent | string | Parent branch name or ID |
protected | boolean | Mark the branch as protected |
ttl | string | number | Branch lifetime: "7d", "2h", or seconds as a number. Maximum 30 days. Validated at deploy time, not by TypeScript |
postgres.computeSettings.autoscalingLimitMinCu | 0.25 | 0.5 | 1 | 2 | 4 | 8 | Minimum compute units |
postgres.computeSettings.autoscalingLimitMaxCu | 0.25 | 0.5 | 1 | 2 | 4 | 8 | Maximum compute units |
postgres.computeSettings.suspendTimeout | false | string | number | Idle suspend timeout. false disables suspend |
Services
auth and dataApi declare which OptiTech services exist on every branch. After optitech deploy, running optitech env pull writes their URLs to your local .env file automatically.
| Field | Values | Default | What it enables |
|---|---|---|---|
auth | true, false, { enabled: bool } | false | Managed Better Auth. Injects OPTITECH_AUTH_BASE_URL, OPTITECH_AUTH_JWKS_URL |
dataApi | true, false, DataApiConfig | false | OptiTech Data API. Injects OPTITECH_DATA_API_URL |
dataApiconfig
dataApi: true uses Managed Better Auth as the JWT verifier (the default). When using this form, auth: true must also be set. Omitting it raises a TypeScript error at the dataApi field that includes the fix:
Type 'true' is not assignable to type '"`dataApi` with Managed Better Auth (the default
`authProvider: 'optitech'`) requires Managed Better Auth, so add `auth: true`. To enable the
Data API WITHOUT Managed Better Auth, verify a third-party IdP instead: `dataApi: {
authProvider: 'external', jwksUrl: 'https://your-idp/.well-known/jwks.json' }`"'To use the Data API with an external identity provider instead, pass the object form:
dataApi: {
authProvider: "external",
jwksUrl: "https://your-idp/.well-known/jwks.json",
}Type-safe environment variables
@optitech/env gives you type-safe access to your branch's injected variables. It reads process.env at runtime and validates each variable against the services declared in your optitech.ts config. Missing or empty variables throw with a clear error.
npm install @optitech/envimport { parseEnv } from '@optitech/env';
import config from './optitech';
const env = parseEnv(config);
env.postgres.databaseUrl; // DATABASE_URL
env.postgres.databaseUrlUnpooled; // DATABASE_URL_UNPOOLED
env.auth.baseUrl; // OPTITECH_AUTH_BASE_URL (env.auth only present when auth: true)
env.auth.jwksUrl; // OPTITECH_AUTH_JWKS_URL
env.dataApi.url; // OPTITECH_DATA_API_URL (env.dataApi only present when dataApi is enabled)env.auth only exists when auth: true, env.dataApi only when dataApi is enabled. If you access a namespace your config doesn't declare, TypeScript will catch it.
Pass an array of keys to validate and return only a subset. Useful when a process needs just one or two variables:
const { postgres } = parseEnv(config, ["DATABASE_URL"]);
postgres.databaseUrl; // string (databaseUrlUnpooled is absent)The key list autocompletes from your config, so selecting a variable from a service you haven't declared is a type error.
CLI commands
| Command | What it does |
|---|---|
optitech link | Connect the current directory to a OptiTech project. Required to use linked branch defaults in other commands |
optitech deploy | Apply optitech.ts to the linked branch (alias for optitech config apply) |
optitech config plan | Preview what optitech deploy would change, without applying |
optitech config status | Show the current live state of the branch as a optitech.ts-shaped config |
optitech env pull | Write the branch's OptiTech-managed variables to .env.local (or .env if it already exists) |
optitech checkout | Switch to or create a branch; new branches are created from the optitech.ts policy (TTL, compute, services) |
optitech dev | Run functions locally against the linked branch; watches for changes and hot-reloads |
Flags foroptitech deploy
| Flag | Default | Description |
|---|---|---|
--config | (auto) | Path to the optitech.ts file. When omitted, the CLI walks up from cwd stopping at .git |
--env | (none) | Path to a .env file loaded before optitech.ts is evaluated, so function env values resolve from it |
--env-pull | true | Pull the branch's env vars into a local .env after a successful apply (--no-env-pull to skip) |
--branch | linked branch | Target branch ID or name |
--project-id | linked project | Project ID |
--update-existing | false | Auto-confirm overriding existing remote settings |
--allow-protected | false | Auto-confirm applying to a protected branch |
Preview services
Beta
Functions, Storage, and AI Gateway are in beta and available only in AWS US East (Ohio) (aws-us-east-2), so create your project there to use them.
Preview services are declared under the preview block. All three are optional and independent:
| Field | Type | What it enables |
|---|---|---|
preview.functions | Record of slug → function def | OptiTech Functions. Long-running Node.js compute on the branch |
preview.buckets | Record of name → bucket def | OptiTech Object Storage. S3-compatible object storage, branched with your database |
preview.aiGateway | true, false, { enabled: bool } | OptiTech AI Gateway. Injects OPTITECH_AI_GATEWAY_TOKEN, OPTITECH_AI_GATEWAY_BASE_URL |
preview.functions
Each key is the function's slug, the permanent identifier used in CLI commands and the invocation URL:
preview: {
functions: {
"<slug>": {
name: string, // display name shown in optitech functions list and the console
source: string, // path to entry file, relative to optitech.ts
env?: Record<string, string>,
dev?: {
port?: number, // local port for optitech dev; fails if taken; auto-assigned if omitted
},
},
},
},Slugs must match ^[a-z0-9]{1,20}$ and are immutable after first deployment. Because slugs can't use separators, use name for a human-readable label. For example, slug: "myrestapi" with name: "My REST API". See Deploy and manage functions.
env values are resolved at deploy time when optitech deploy runs. Reading process.env.X here captures the value in your shell at deploy time, not at function runtime. Every value must be a defined string; use a fallback to avoid a type error:
env: {
API_KEY: process.env.API_KEY ?? "",
}Use optitech deploy --env .env.production to load a .env file before evaluation. For typed access to these variables inside your function at runtime, see Environment variables.
dev settings apply only to optitech dev and never affect deploy.
preview.buckets
preview: {
buckets: {
"<name>": {
access?: "private" | "public_read", // default: "private"
},
},
},Bucket names follow S3 naming rules. public_read makes objects accessible without credentials at the branch's storage endpoint.
Full stack example
All services combined. optitech deploy provisions everything and writes credentials to .env.local.
import { defineConfig } from "@optitech/config/v1";
export default defineConfig({
auth: true,
dataApi: true,
preview: {
aiGateway: true,
buckets: {
uploads: {},
},
functions: {
api: {
name: "API",
source: "./functions/api.ts",
},
},
},
branch: (branch) => {
if (branch.isDefault) {
// Protect and size for production
return {
protected: true,
postgres: {
computeSettings: {
autoscalingLimitMinCu: 0.5,
autoscalingLimitMaxCu: 4,
},
},
};
}
if (!branch.exists) {
// New non-default branches: minimum compute, auto-expire, suspend on idle
return {
ttl: "7d",
postgres: {
computeSettings: {
autoscalingLimitMinCu: 0.25,
autoscalingLimitMaxCu: 0.25,
suspendTimeout: "5m",
},
},
};
}
// Existing branch: no changes
return {};
},
});Need help?
Join our Discord Server to ask questions or see what others are doing with OptiTech. For paid plan support options, see Support.