Skip to main content
Oxygen Functions are TypeScript handlers that ship inside your SDK app and run server-side on Oxygen’s managed V8 isolate runtime, with direct access to your project’s data plane. Reach for them whenever the browser shouldn’t do the work itself:
  • a parameterized or privileged warehouse query,
  • a warehouse write (insert / upsert / exec),
  • kicking off an Airway ELT run,
  • an outbound API call to a third-party service.
No separate backend to stand up — a function is versioned and deployed with your frontend by oxyc publish.
An Oxygen Function is a bundled TypeScript handler — not a YAML Automation task or a Data App task. It runs with the app’s project-level access, not the invoking visitor’s — with one exception, warehouse writes.

Authoring a function

A function is a file at functions/<name>.ts that exports a default async (req, ctx) => Response, declared in the app’s oxy-app.json:
The status you return is the status the caller sees. useFunction’s invoke resolves on a 2xx and rejects on anything else, with a FunctionError carrying status, the parsed body, and any logs the run captured — so return Response.json({ error: "…" }, { status: 409 }) is a rejection the caller can branch on, not a value it has to inspect.
Requires @oxy-hq/sdk 2.10 or later. Before that the status was discarded and every response resolved.
functions/daily-rollup.ts
oxy-app.json
  • req is { body: string } — the raw POST body. Response / Response.json(...) are polyfilled by the runtime.
  • ctx is the data plane:
    • ctx.query / ctx.queryStream — read-only, row-capped SQL
    • ctx.semantic.query — a semantic-model query
    • ctx.warehouse.{insert, exec, upsert} — writes, allowlisted to project databases
    • ctx.airway.run — kick off an ELT pipeline
    • ctx.fetch — outbound HTTP, SSRF-allowlisted, HTTPS-only, size/time-capped
    • ctx.env — per-app secrets
    • ctx.user, ctx.log

Who a write runs as

Reads always run with the app’s project-level access. Writes to an Oxygen-managed warehouse are the one place the invoker matters: the connection is opened with a short-lived credential whose role follows the workspace role of the person who called the function. A denied write surfaces as a permission error on the statement (Reader role cannot execute Update statements) while queries in the same function keep working — that combination means the caller’s role, not a broken destinations allowlist. Databases you configure with your own credentials (BigQuery, Snowflake, ClickHouse, …) are unaffected — they always connect as the credentials in the project config — so a function that must write on a schedule should target one of those.

Invocation modes

A function can be reached three ways, each with its own identity and default timeout (a ceiling; max 300s): The route endpoint (POST /customer-apps/<org>/<slug>/fn/<name>) is POST-only (405 otherwise), rate-limited per (user, app, function), and every invocation is audited.

Calling a route function from React

Use the useFunction hook to invoke a route function imperatively — e.g. from a button:
  • invoke(body?, { idempotencyKey? }) returns the function’s JSON response.
  • rollup.data holds the last successful result; rollup.error the last error.
  • Concurrent identical invokes are deduplicated into one request.

Caching

Results are not cached by default — functions are often side-effectful. A read-only function can opt into a server result cache by adding "cache": { "ttlSeconds": N } to its manifest entry; the cache is keyed per (build, user, body), invalidated by the next publish, and bypassed with ?refresh.

Next steps

Hooks & components

The rest of the SDK surface

SDK overview

Scaffold, wire up, and deploy an app