Deploy and manage OptiTech Functions

CLI and API reference for deploying and managing OptiTech Functions.

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
FlagDefaultDescription
--configwalks up from cwdPath 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-pulltruePull the branch's env vars into a local .env after a successful apply (--no-env-pull to skip)
--branchlinked branchTarget branch ID or name
--project-idlinked projectProject ID
--update-existingfalseAuto-confirm overriding existing remote settings on the branch
--allow-protectedfalseAuto-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.

FlagDefaultDescription
--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
--runtimenodejs24Function runtime. nodejs24 is the only valid value
--branchlinked branchTarget branch. Defaults to the branch in .optitech
--waittruePoll until completed or failed, up to 10 minutes

Examples:

optitech functions deploy hello --src functions/hello.ts
optitech functions deploy hello --src . --env RESEND_API_KEY=re_...
optitech functions deploy hello --src functions/hello.ts --branch feat/my-feature

The 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.mjs

note

--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.mjs

The 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"}'
FieldTypeRequiredDescription
zipbinaryWhen code changesZIP of the bundled function. Omit only to update environment or runtime on an existing deployment
runtimestringNonodejs24 is the only valid value
environmentstringNoJSON-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

StateMeaning
pendingQueued, not yet building
buildingSource is being compiled and bundled
completedFunction is live and accepting requests
failedBuild or deployment error

Manage functions

Check status

optitech functions get hello

The response includes invocation_url, the public URL for your function:

https://<branch_id>-<slug>.compute.<cell>.us-east-2.aws.optitech.com

List functions

optitech functions list

Delete a function

optitech functions delete hello

Need help?

Join our Discord Server to ask questions or see what others are doing with OptiTech. For paid plan support options, see Support.

Was this page helpful?