Primitive Functions: Deploy, Route, and Manage
Scaffold, deploy, redeploy, test, and route Primitive Functions from the terminal, and manage their secrets and logs with the primitive functions-* command group.
Primitive Functions are serverless email-handler functions: a single ESM module whose default export implements async fetch(request, env). Use the primitive functions-* command group to scaffold, deploy, test, route, and manage secrets and logs for them from the terminal, without leaving your shell. For the platform concept (routes, secrets, logs, test runs as first-class resources), see Primitive Functions and Recipient Routing.
Scaffold a function#
Generate a starter project from an approved template before you write any handler code.
primitive functions templates
primitive functions init my-fn --template email-reply
cd my-fn && npm install && npm run build
functions templates lists the templates available to install. functions init scaffolds a project directory containing package.json and src/; npm run build produces the bundled handler your deploy commands consume.
Deploy from a bundled file#
primitive functions deploy creates and deploys a function from a pre-built ESM bundle on disk, as the agent-grade shortcut for the generic functions:create-function operation. It reads the bundle straight off disk instead of forcing you to shell-escape a multi-line JSON body:
primitive functions deploy --name my-fn --file ./dist/handler.js
- 1
Build a self-contained ESM bundle#
esbuild src/handler.ts --bundle --format=esm --outfile=dist/handler.jsThe handler must be a single self-contained module;
functions deployuploads exactly the file you point--fileat. - 2
Deploy the bundle#
primitive functions deploy --name my-fn --file ./dist/handler.js--nameis a slug: lowercase letters, digits, hyphens, underscores, 1-64 characters, unique within your org. Add--source-map-file ./dist/handler.js.mapto attach a source map so function-log stack traces symbolicate back to your original source. - 3
Confirm the deploy landed#
The command prints the created (or, when secrets were seeded, the redeployed) function record as JSON on stdout, including its
id,name, anddeploy_status.
Deploying is idempotent by name when you use --source (a project directory) instead of --file. Rerunning primitive functions deploy --name my-fn --source . on every push either creates the function or redeploys it, whichever applies.
With secrets present, deploy fans out to multiple API calls instead of one: create-function, then set-secret for each pair in order, then a final update-function redeploy so the running handler actually picks up the new bindings.
Deploy from a source directory (managed build)#
Pass --source <dir> instead of --file to upload a project directory and let the server install dependencies, bundle for the Workers runtime, and deploy:
primitive functions deploy --name triage --source .
Provide exactly one of --file or --source; passing both, or neither, is rejected before any network call.
Wait for the deploy to finish#
primitive functions deploy --name my-fn --file ./dist/handler.js --wait
--wait polls until the deploy reaches deployed or failed, writing progress to stderr while stdout stays the final JSON payload. Control timing with --timeout <seconds> (pass 0 to wait forever) and --poll-interval <seconds>. A timeout exits with status 2; a failed deploy exits with status 1 and prints deploy_error on stderr.
Seed secrets in the same deploy#
Pass one or more secret-source flags to primitive functions deploy to seed encrypted env bindings without a separate functions set-secret round trip:
primitive functions deploy --name my-fn --file ./dist/handler.js \
--secret OPENAI_KEY=sk-... \
--secret OWNER_EMAIL=me@example.com
| Flag | Source |
|---|---|
--secret KEY=VALUE | Inline value on the command line |
--secret-from-env KEY | process.env.KEY |
--secret-from-file KEY=PATH | Full contents of a UTF-8 file |
--secret-from-env-file FILE:KEY | A dotenv-style file |
--secret-from-stdin KEY | Piped stdin (not repeatable; trailing newline stripped) |
Each flag is repeatable except --secret-from-stdin, which consumes stdin once. Every secret KEY must match ^[A-Z_][A-Z0-9_]*$ and may only appear once per command.
primitive functions deploy --name my-fn --file ./dist/handler.js \
--secret-from-env OPENAI_KEY \
--secret-from-env-file .env.local:OWNER_EMAIL
With secrets present, the CLI fans out to multiple API calls: create the function (or, for --source against an existing name, look up its id), write each secret in order, then redeploy the same bundle so the running handler picks up the new bindings.
If a secret write fails partway through, the function row carries whatever bindings landed but the redeploy has not happened yet, so the running handler does not have the new bindings. The command's stderr hint names which keys succeeded, which key failed, and which keys were never attempted:
Function my-fn (<function-id>) was created, but writing secret OWNER_EMAIL failed;
succeeded keys so far: OPENAI_KEY; keys not yet attempted: (none). The redeploy
is NOT yet live. Re-run `primitive functions set-secret` for each of
[OWNER_EMAIL], then `primitive functions redeploy --id <function-id> --file <bundle>`
to push them live.
Re-run primitive functions set-secret for the missing keys, then primitive functions redeploy --id <function-id> --file <bundle> to push them live.
Redeploy and test#
primitive functions redeploy pushes new code to an existing function id, and primitive functions test runs a synthetic inbound invocation against it.
primitive functions redeploy --id <function-id> --file ./dist/handler.js
primitive functions test --id <function-id> --wait --show-sends
functions redeploy also accepts a new source map. functions test --wait --show-sends waits for the invocation to finish and shows any reply or send the handler triggered, so you can verify a handler end to end before real inbound mail depends on it.
Inspect logs for a deployed function:
primitive functions logs --id <function-id>
Bind a route#
A deployed function receives no inbound mail until a recipient routing rule points at it, so bind one after a fresh deploy:
primitive functions route-set --id <function-id> --domain <domain-id>
# or, to catch everything not otherwise routed:
primitive functions route-set --id <function-id> --fallback
functions deploy prints a route-status hint after every successful deploy: "Route bound. Function will receive inbound mail." or a warning that no route is bound yet, with the exact functions route-set command to run. Don't skip this step. A function with deploy_status: deployed still receives zero inbound mail until a route exists.
Inspect the routing topology (which addresses and domains resolve to which function or endpoint) with the function-routing inspection commands, or manage recipient-level rules directly with primitive routes.
Manage secrets outside of deploy#
primitive functions set-secret writes one secret binding on an existing function without re-sending its code.
primitive functions set-secret --help
Run the command with --help for its exact flags, then re-run primitive functions redeploy --id <function-id> --file <bundle> so the running handler picks up the new binding.
For secrets shared across every function in your org rather than scoped to one function, use org-wide secrets instead.
Command reference#
| Command | Purpose |
|---|---|
functions templates | List installable function templates |
functions init <name> --template <template> | Scaffold a project directory |
functions deploy --name <name> (--file <path> | --source <dir>) | Create and deploy a function |
functions redeploy --id <id> --file <path> | Push new code to an existing function |
functions test --id <id> --wait --show-sends | Run a synthetic inbound test |
functions logs --id <id> | View function logs |
functions set-secret | Write one function secret |
functions route-set --id <id> (--domain <id> | --fallback) | Bind a routing target |
Run primitive functions <command> --help for the full flag list of any subcommand, and primitive functions --help for the complete command group.
Next steps#
Understand the platform model: functions, routes, secrets, and logs as API resources.
Managing Domains and Recipient RoutesConfigure inbound domain DNS and manage the routes command group directly.
Org Secrets ManagementSet secrets shared across every function in your org instead of one function.
What is the Primitive CLI?See how the functions command group fits into the CLI's full command surface.
Was this page helpful?