Functions reference
Files and names
Section titled “Files and names”| Rule | Value |
|---|---|
| Directory | functions/ at the root of the project. If the project sets a root directory, inside that |
| Entry points | Every .js or .ts file directly inside functions/. One file is one function |
| Name | The file name without its extension. Lowercase letters, digits and hyphens; starts and ends with a letter or digit; at most 128 characters |
| Subdirectories | Shared code. Never deployed on their own, bundled into whatever imports them |
| Imports | Relative imports and npm packages your build installed are bundled with esbuild into one file per function. Node built-ins stay external |
| Build step | None. TypeScript types are stripped, not checked |
| When | Production deploys only: a push to the production branch, or a promotion. Preview builds skip the directory |
| Bundle size | 900 KB per function. Over that the build fails |
| Removal | Deleting the file and deploying removes the function. Rolling back to an earlier deployment restores the functions that deployment had |
A file whose name breaks the rule fails the build with [functions] Build failed: invalid function name, and so does a .js and a .ts file sharing one name. Nothing is deployed from a failed build, so the previous deployment, functions included, stays live.
Handler
Section titled “Handler”export default async function handler(request: Request, context: Context): Promise<Response>request is a Web API Request. The return value is a Response, or a promise of one. Both are Node 22 natives, as are Headers, ReadableStream, AbortSignal and fetch; there are no polyfills and no deploybase-specific types.
Four export shapes are accepted, resolved in this order. We recommend the first.
| Shape | Example |
|---|---|
| Default export function | export default function handler(request, context) {} |
Default export object with fetch |
export default { fetch(request, context) {} } |
Named export fetch |
export function fetch(request, context) {} |
| CommonJS assignment | module.exports = function (request, context) {} |
request.url is the function’s own public URL, https://{name}-{subdomain}.fn.deploybase.eu/, plus whatever query string the caller sent, on both doors. Headers arrive lower-cased; host is the function’s hostname.
context has two members:
| Member | What it does |
|---|---|
context.env |
An object of environment variables. Empty of anything you set until environment variables ship |
context.waitUntil(promise) |
Lets work outlive the response on the HTTPS door: the reply is sent and the promise keeps running, with a rejection logged rather than thrown. On a queued call it does nothing, because the container exits when the handler returns. Work that must complete belongs before the return |
Streaming response bodies work on the HTTPS door, including text/event-stream, and multiple Set-Cookie headers are preserved. On a queued call the body is read in full before it is stored, and Set-Cookie headers are joined into one.
Runtime: Node 22 on both doors.
Hostname
Section titled “Hostname”| Item | Value |
|---|---|
| Pattern | https://{name}-{subdomain}.fn.deploybase.eu |
{subdomain} |
The project’s subdomain, the first label of {subdomain}.sites.deploybase.eu. Shown in project settings |
| TLS | Automatic. Plain HTTP redirects to HTTPS |
| Length | {name}-{subdomain} together must fit in 63 characters, the DNS label limit. A longer combination is refused at deploy time with a line in the build log |
| Unknown hostname | 404 |
| Custom domains | Not available. A function answers at its own hostname only; there is no way to route a path on your site’s domain to it |
Every request goes through the component that scales instances. It adds an x-keda-http-cold-start header to the reply, true when the call waited for an instance to start.
Limits
Section titled “Limits”One row per limit. The closed-beta column is what every enrolled team gets today, whatever its plan.
| Limit | Closed beta | After the beta |
|---|---|---|
| Memory per instance | 512 MB | Per plan, see pricing |
| Concurrent requests per instance | 10, then a second instance starts | Per plan |
| Instances per function | Up to 3 | Per plan |
| Request budget | 60 seconds | Per plan |
| Request body, HTTPS door | 10 MiB, then 413 |
Same |
| Request body, queued door | 1 MB | Same |
| Warm instances | 0. Every function scales to zero after about two minutes without traffic | Per plan |
| Cold start | 2 to 5 seconds, measured on small handlers. A start that also has to bring up a node takes longer | Same |
| Requests per month | Not enforced. Fair use; abusive traffic may be throttled | Per plan |
| Outbound network | HTTPS to the public internet. Port 80, private ranges and the cloud metadata address are blocked | Same |
The request budget is not configurable during the beta.
Timeouts
Section titled “Timeouts”The 60-second budget bounds what the caller sees, not what your code does. When it expires:
- If nothing has been sent yet, the caller gets a
504. - If the response had started, the connection is cut mid-body.
Either way the handler keeps running until it finishes or the instance is replaced, because JavaScript has no way to cancel a running promise from outside. Side effects after the deadline still happen: a database write, an email, a third-party call. Do not rely on a timeout to cancel work; bound it inside the handler with an AbortSignal or a deadline of your own.
On a queued call the same budget applies. The container is stopped when it expires, the invocation ends as timed_out, and the poll reply carries the timeout message listed under queued calls.
Errors your callers see
Section titled “Errors your callers see”| Status | Cause |
|---|---|
404 |
No function at this hostname: unknown name, unknown subdomain, team not in the beta, or not deployed yet |
413 |
Request body over 10 MiB |
500 |
The handler threw, returned a rejected promise, or returned something that is not a Response |
504 |
The request budget expired before any byte was sent |
No stack trace or error text reaches the caller in any of these. A 500 is logged with the error on our side; the dashboard does not show HTTPS-call logs yet, so to read the error reproduce the call through the queued door.
Queued calls
Section titled “Queued calls”The queued door runs the same file in a fresh container per call. Both endpoints are public and need no authentication. The API reference holds the full schemas: invoke and poll.
| Endpoint | Status | Reply |
|---|---|---|
POST /api/v1/public/functions/{subdomain}/{name} |
202 |
Body {"data": {"invocation_id", "status": "pending", "poll_url"}} |
GET /api/v1/public/functions/{subdomain}/{name}/invocations/{id} |
200 |
Body {"data": {"id", "status", "started_at", "finished_at", "response_status", "response_headers", "response_body", "error_message"}} |
Base URL https://api.deploybase.eu. The poll_url is relative to it. The method, query string, headers and body of the POST are what the handler receives as its Request.
| Rule | Value |
|---|---|
| Statuses | pending, running, then one of completed, failed, timed_out |
| Result fields | response_status, response_headers and response_body (base64) appear once the status is terminal. A failed call whose handler threw still carries the handler’s own 500 response |
error_message |
Present on failed and timed_out. One of the sentences below, never the error itself |
| Request body | 1 MB. A larger body is cut at 1 MB today rather than refused; this will become a 413 |
| Rate limit | 20 requests per minute per IP address across both endpoints, then 429 |
| In flight | 10 queued calls per team at once, then 429 with Too many concurrent invocations |
| Memory | 512 MB |
| Retention | Request, result and console output are kept for 7 days, then deleted |
| Access | Not gated by the beta. Any team’s functions can be queued |
The error_message sentences, exactly as they appear:
| Ended as | error_message |
|---|---|
timed_out |
The function did not finish within its timeout of 60s. |
failed |
The function ran out of memory. |
failed |
The function returned an error. Check the invocation logs. |
failed |
The function did not return a valid response. |
failed |
This function is not deployed. Redeploy the project and try again. |
failed |
The function could not be run. This is a platform issue; please retry. |
Two more appear only when a call is reclaimed after a platform restart: The function did not finish before its timeout. and The function was never started. This is a platform issue; please retry.
Everything a queued call wrote to console.log and console.error is shown on the function’s page in the dashboard under Invocations, for members of the team only; it is never part of the public poll reply. Logs for HTTPS calls are not visible yet.
Environment
Section titled “Environment”Environment variables for functions are not available yet. context.env exists so that code written against it keeps working when they ship, but it holds nothing you set: the project environment variables reach builds only, and a function has no access to them at request time. Watch the changelog. Until then, a value a function needs has to be in the code, which means in the repository.
| Data | Where and for how long |
|---|---|
| Queued calls | The request headers and body, the result and the console output are stored in Paris for 7 days |
| HTTPS calls | Not recorded. No request or response is stored |
| Bundles | Stored in a private storage zone in Germany, deleted with the deployment |
| Client addresses | The queued endpoints are rate limited per IP address, so the address is kept for that bookkeeping and cleaned up on a schedule. Beyond the request headers themselves, nothing about the caller of a queued call is stored |
Our DPA covers personal data your functions process. Functions are provided under the beta terms at app.deploybase.eu/functions-beta.