TL;DR: Functions are server-side code you call by slug. Two kinds: seven builtins that are already live on every backend โ set any required secret and invoke, no deploy โ and custom functions you author in TypeScript and run in a sandboxed Deno runtime. Invoke either with lingcode.functions.invoke(slug, body?) or by POSTing to /api/cloud/be/<backend-id>/functions/<slug>. Use functions for what the browser can't safely do: hold a secret key, call a vendor API, run trusted logic.
A function exists for one reason: there are things the browser can't do safely. It can't hold a Stripe secret key. It can't be trusted to compute a price or grant access. It can't call a vendor API that needs a credential. Functions are the small slice of your app that runs on the server, behind your backend, where a secret stays a secret. This page covers both flavours โ the builtins that ship turned on, and the custom functions you write yourself โ and how to call them.
Everything in the client SDK โ from('table').select(), auth, storage โ runs against a thin gateway with the public anon key, gatekept by row-level security. That covers most of an app. But some logic has to live somewhere the user can't see or tamper with:
You reach a function by its slug โ a short name. There are two ways to get one: use a builtin (already live, just set its secret) or write a custom function (your own TypeScript). Both are invoked exactly the same way.
From the SDK, call functions.invoke with the slug and an optional body. Like every SDK call it returns a Result โ { data, error } โ so you never have to wrap it in try/catch:
const { data, error } = await lingcode.functions.invoke('echo', {
hello: 'world'
});
// data -> { hello: 'world', timestamp: '2026-06-11Tโฆ' }
Or call it over REST โ handy from a server, a curl smoke test, or a non-JS client. POST to the function endpoint with an { input } body, authorized by the anon key or a signed-in user's token:
POST https://lingcode.dev/api/cloud/be/<backend-id>/functions/<slug>
Authorization: Bearer <anon-key-or-user-token>
Content-Type: application/json
{ "input": { "hello": "world" } }
unknown_function means the name is wrongIf an invoke returns unknown_function, the slug is misspelled or that function doesn't exist on this backend โ it does not mean functions are unavailable. Check the slug against the builtins below, or against the slug you gave your custom function. (For the one case where the runtime itself is down, see Availability โ that surfaces as 503 functions_runtime_unavailable, and only for custom functions.)
These are live on every backend with no deploy step. For each one you only need to set its required secret (if any) in the Secrets vault, then invoke it by slug. Start with echo to confirm your wiring works.
| Slug | What it does | Input | Required secrets |
|---|---|---|---|
echo |
Returns your input plus a timestamp. Use it to smoke-test that invoke works. | anything | none |
send-email |
Sends email via LingCode's managed sender. | { to, subject, html } |
none |
elevenlabs-tts |
Text-to-speech. Returns base64 audio. | { text, voice_id?, model_id? } โ { audio_base64, content_type } |
ELEVENLABS_API_KEY |
twilio-sms |
Sends an SMS via Twilio. | { to, body, from?, messaging_service_sid? } |
TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, plus one of TWILIO_FROM or TWILIO_MESSAGING_SERVICE_SID |
resend-byo |
Sends email via your own Resend account. | { from, to, subject, html?, text? } |
RESEND_API_KEY |
stripe-checkout |
Creates a Stripe Checkout Session. | { price_id, success_url, cancel_url, mode?, quantity?, customer_email?, metadata? } โ { id, url } |
STRIPE_SECRET_KEY |
http-fetch |
Makes an outbound HTTPS request to an allow-listed host. | { url, method?, headers?, body? } |
none of its own โ but header/body strings may reference vault secrets as {{SECRET_NAME}} |
echoThe very first thing to run on a new backend. If this returns your input back, invoke is wired and your URL + key are correct.
const { data } = await lingcode.functions.invoke('echo', { ping: 1 });
console.log(data); // { ping: 1, timestamp: '2026-06-11Tโฆ' }
Set STRIPE_SECRET_KEY in the Secrets vault first โ it never touches the browser. Then call the builtin and redirect the user to the returned URL:
const { data, error } = await lingcode.functions.invoke('stripe-checkout', {
price_id: 'price_123',
success_url: 'https://yourapp.com/thanks',
cancel_url: 'https://yourapp.com/pricing',
mode: 'subscription'
});
if (error) throw error;
window.location.href = data.url; // hosted Stripe Checkout
http-fetch: secret-aware, host-restrictedhttp-fetch has no secret of its own, but any {{SECRET_NAME}} token in a header or body string is substituted from the vault server-side โ so you can call an API that needs a bearer token without the token ever reaching the client. The destination host must be on the backend's fetch-hosts allow-list; calls to hosts not on the list are refused. This is the deliberate, locked-down way to call third-party APIs without writing any code. See the tutorial Call external APIs from your backend.
When no builtin fits, write your own. You author custom functions in the Cloud console / IDE โ you give the function a slug and its TypeScript source (this is a console authoring step, not something an agent tool does for you). They run in a sandboxed Deno runtime, invoked by the same functions.invoke(slug, body?) call as the builtins.
Your source must export default a handler. Whatever you return becomes the invoke result's data. TypeScript is supported natively โ Deno runs it directly, no build step:
export default async function handler(input, ctx) {
// ctx.secrets.MY_KEY โ your vault secrets, server-side only
// ctx.backendId โ this backend's id
// ctx.gateway โ the backend's own data-API base URL
return { ok: true }; // becomes the invoke result's `data`
}
The second argument ctx is your server-side context:
ctx = {
secrets: { /* every secret set in the vault, by name */ },
backendId: string,
gateway: string // call your own data API through this
}
Reads a secret from the vault and returns JSON. The secret never leaves the server:
export default async function handler(input, ctx) {
const key = ctx.secrets.WEATHER_API_KEY;
const city = input.city ?? 'NYC';
const res = await fetch(
`https://api.example.com/weather?city=${city}&key=${key}`
);
const json = await res.json();
return { city, tempC: json.temp_c };
}
Invoke it from the client exactly like a builtin โ by its slug:
const { data, error } = await lingcode.functions.invoke('get-weather', {
city: 'Tokyo'
});
if (error) throw error;
console.log(data.tempC);
Custom functions run in Deno with deny-by-default permissions. This is a feature, not a restriction to fight:
ctx.gateway). A function can call your data API โ but it can't exfiltrate to arbitrary hosts. (Need to reach a vendor? Use the http-fetch builtin's allow-list, or have your function call back through the gateway.)| Limit | free | pro | max_pro |
|---|---|---|---|
| Timeout (wall-clock, SIGKILL past it) | 3s | 10s | 30s |
Custom functions (maxFunctions) | 2 | 10 | 50 |
| Source size | up to 256 KB | ||
| Memory | 128 MB | ||
Functions are short-lived. Past the wall-clock timeout the process is SIGKILLed โ there's no graceful extension. Don't put anything that runs longer than ~30 seconds in a function.
Custom functions need the Deno runtime, which is installed and live in production โ custom functions work today. If a deployment ever lacks the runtime, custom invokes return 503 functions_runtime_unavailable while the builtins keep working (they don't depend on the Deno sandbox). So a 503 on a custom function is a runtime-availability signal; a builtin failing is something else. And remember โ unknown_function is always a wrong-slug error, never an availability one.
stripe-checkout builtin only creates Checkout Sessions; it does not receive webhooks.