Beta
The OptiTech Functions is in Beta. Share your feedback on Discord or via the OptiTech Console.
Deploy withoptitech.ts
If your project has a optitech.ts config, this is the recommended way to deploy. optitech deploy reads the config and applies the entire branch policy in one step: services, per-branch tuning, and every function it declares:
optitech deploy| Flag | Default | Description |
|---|---|---|
--config | walks up from cwd | Path to the optitech.ts policy |
--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 on the branch |
--allow-protected | false | Auto-confirm applying to a branch marked protected on OptiTech |
optitech deploy is an alias for optitech config apply. To preview what a deploy would change without applying it, run optitech config plan.
Note that --env here takes a path to a .env file. The --env flag on optitech functions deploy below takes KEY=VALUE pairs instead.
Deploy withoptitech functions deploy
To deploy one function directly, without a optitech.ts config:
optitech functions deploy <slug> [--src <dir-or-entry-file>] [--env KEY=VALUE] [--wait]The CLI bundles with esbuild, zips the output, and uploads it. The first deploy creates the function; subsequent deploys update it. See the optitech functions reference for the full command surface.
esbuild not found
The optitech CLI ships esbuild for most platforms. If bundling fails with an esbuild not found error, install it (npm install -g esbuild) or set OPTITECH_ESBUILD_PATH to an esbuild binary. OPTITECH_ESBUILD_PATH is read by the CLI's own bundler, not by buildFunctionBundle in @optitech/config-runtime; when calling that package directly, pass a custom bundleFunction instead.
| Flag | Default | Description |
|---|---|---|
--src | (none) | Function source: a directory containing index.ts, index.mjs, or index.js, or a path to the entry file |
--env KEY=VALUE | (none) | Set an environment variable. Repeatable. Stored with the deployment. Takes KEY=VALUE pairs, not a .env file path like optitech deploy --env |
--runtime | nodejs24 | Function runtime. nodejs24 is the only valid value |
--branch | linked branch | Target branch. Defaults to the branch in .optitech |
--wait | true | Poll until completed or failed, up to 10 minutes |
Examples:
optitech functions deploy hello --src functions/hello.tsoptitech functions deploy hello --src . --env RESEND_API_KEY=re_...optitech functions deploy hello --src functions/hello.ts --branch feat/my-featureThe CLI doesn't support a config-only deploy. Every optitech functions deploy call bundles and uploads source, whether you pass --src or let it default to the current directory, so there's no way to change just an environment variable without also pointing at valid source, as in the example above.
For a deploy that skips bundling and updates only the environment or runtime, use the API, which accepts config-only updates.
Deploy with the API
Bundle with esbuild, zip the output, then POST to the deploy endpoint.
1. Bundle:
esbuild functions/hello.ts --bundle --platform=node --target=node24 --format=esm \
--banner:js="import{createRequire as ___cr}from'module';import{fileURLToPath as ___f}from'url';import{dirname as ___d}from'path';const require=___cr(import.meta.url);const __filename=___f(import.meta.url);const __dirname=___d(__filename);" \
--outfile=dist/index.mjsnote
--format=esm is required: esbuild doesn't infer ESM output from the .mjs extension alone, and without it the runtime fails with module is not defined in ES module scope. The --banner line restores require, __filename, and __dirname for bundled CommonJS dependencies that reference them internally (as pg does); without it, the runtime fails with Dynamic require of "<module>" is not supported. buildFunctionBundle below applies the same banner automatically.
2. Zip:
zip -j function.zip dist/index.mjsThe archive's entry file must be named index.mjs or index.js; the runtime looks for those names.
From Node.js, buildFunctionBundle from @optitech/config-runtime does both steps in one call and produces exactly the archive the deploy endpoint expects. See the @optitech/config-runtime reference for the rest of that package's programmatic API (inspect, plan, apply), useful for calling a deploy from a custom CI step instead of the CLI:
import { buildFunctionBundle } from "@optitech/config-runtime/v1";
const zip = await buildFunctionBundle({
slug: "hello",
name: "My first function",
source: "./functions/hello.ts",
env: {},
runtime: "nodejs24",
});3. Deploy:
curl -X POST \
"https://console.optitech.com/api/v2/projects/{project_id}/branches/{branch_id}/functions/{slug}/deployments" \
-H "Authorization: Bearer $OPTITECH_API_KEY" \
-F "zip=@function.zip" \
-F 'environment={"MY_SECRET":"value"}'| Field | Type | Required | Description |
|---|---|---|---|
zip | binary | When code changes | ZIP of the bundled function. Omit only to update environment or runtime on an existing deployment |
runtime | string | No | nodejs24 is the only valid value |
environment | string | No | JSON-encoded string-to-string map |
The deploy endpoint accepts multipart/form-data. Use a zip part for code deploys and a single environment part containing the JSON-encoded map; don't send bracketed fields such as environment[KEY]=value. The first deployment for a function must include zip; later deployments can omit it for config-only changes.
The API returns immediately for code deploys. Poll the get endpoint (see Check status) until the deployment completes. Config-only deployments can complete synchronously because they reuse the latest bundle. Builds have an absolute 2-minute budget from the time the deploy is accepted; if the build can't complete within that window, the deployment fails.
Deploy with@optitech/sdk
The beta @optitech/sdk client includes a optitech.functions namespace for branch-scoped function management:
import { createOptiTechClient } from '@optitech/sdk';
import { readFile } from 'node:fs/promises';
const optitech = createOptiTechClient({ apiKey: process.env.OPTITECH_API_KEY! });
const projectId = process.env.OPTITECH_PROJECT_ID!;
const branchId = process.env.OPTITECH_BRANCH_ID!;
const zipBytes = await readFile('function.zip');
const { data: deployment } = await optitech.functions.deploy(projectId, branchId, 'hello', {
zip: new File([zipBytes], 'function.zip', { type: 'application/zip' }),
runtime: 'nodejs24',
environment: JSON.stringify({ MY_SECRET: 'value' }),
});optitech.functions.deploy uses the same multipart API fields as the raw endpoint. list, get, update, and delete are also available under optitech.functions.
Slugs
The slug is assigned at first deploy: either the key in optitech.ts or the positional argument to optitech functions deploy. It becomes part of the invocation URL and can't be changed afterward. Slugs must match ^[a-z0-9]{1,20}$: lowercase letters and digits only, 1 to 20 characters, no hyphens.
Deployment states
| State | Meaning |
|---|---|
pending | Queued, not yet building |
building | Source is being compiled and bundled |
completed | Function is live and accepting requests |
failed | Build or deployment error |
Manage functions
Check status
optitech functions get helloThe response includes invocation_url, the public URL for your function:
https://<branch_id>-<slug>.compute.<cell>.us-east-2.aws.optitech.comList functions
optitech functions listDelete a function
optitech functions delete helloNeed help?
Join our Discord Server to ask questions or see what others are doing with OptiTech. For paid plan support options, see Support.