Ship your first function
This guide starts from a project you have already deployed and ends at a function you can call from anywhere. The files it shows are the ones in codeberg.org/deploybase/functions-example, which you can also deploy as-is.
Check your team is in the beta
Section titled “Check your team is in the beta”Open Billing in the dashboard. The functions card says Your team is in. when the grant is active. If it says the team has applied, we have not answered yet; if it offers to apply, nobody on the team has.
Without the grant everything below still works up to the HTTPS call: the function is bundled, registered and can be queued, but the hostname answers 404 and the build log says so. A team admin can apply at app.deploybase.eu/functions-beta.
Add functions/hello.js
Section titled “Add functions/hello.js”Create a functions/ directory at the root of your project (inside the root directory if your project sets one) and add this file:
export default async function handler(request) { const name = new URL(request.url).searchParams.get("name") ?? "world"; return new Response(JSON.stringify({ message: `Hello, ${name}!` }), { headers: { "content-type": "application/json", "access-control-allow-origin": "*" }, });}The default export is the handler. It receives a standard Request and returns a standard Response; nothing here is specific to deploybase. The access-control-allow-origin header is there so a page on your site, which lives on a different hostname, can call the function from the browser. deploybase adds no CORS headers of its own.
A .ts file works the same way; types are stripped when the function is bundled, with no tsconfig and no type-check step. Files in a subdirectory of functions/ are shared code, never functions of their own, so functions/_lib/greeting.js can be imported by functions/hello.js and is bundled into it.
Push to your production branch
Section titled “Push to your production branch”Commit and push. Functions are discovered and deployed on production deploys only: a push to the production branch, or a promotion. A preview branch builds the site as usual and skips the functions/ directory entirely.
The build log shows the bundle and the registration:
[functions] Bundled 1 function(s): hello (1208 bytes)⚡ Registered function "hello" (http, 60s timeout)If the team is not in the beta yet, the second line is replaced by a warning that names the function and says it was not deployed as an HTTPS endpoint. Fix access, and the function comes up within a few minutes without another deploy.
Call it
Section titled “Call it”Every function gets its own hostname: https://{name}-{subdomain}.fn.deploybase.eu. {name} is the file name without its extension, and {subdomain} is your project’s subdomain, the first label of your {subdomain}.sites.deploybase.eu address, shown in Settings. The examples below use example-site in its place.
curl "https://hello-example-site.fn.deploybase.eu/?name=Ada"{"message":"Hello, Ada!"}The first call after a quiet spell takes a few seconds. Functions scale to zero when nobody is calling, and that first request starts an instance before it is answered, usually in 2 to 5 seconds. Calls after that are answered directly. If you see a header named x-keda-http-cold-start in the reply, that is the component that scales instances up and down reporting whether this call paid for a start.
Queue it instead
Section titled “Queue it instead”The same file can run in the background. A POST to the queued address answers straight away with 202 Accepted and a URL to poll:
curl -X POST "https://api.deploybase.eu/api/v1/public/functions/example-site/hello?name=Ada"{ "data": { "invocation_id": "01234567-89ab-cdef-0123-456789abcdef", "status": "pending", "poll_url": "/api/v1/public/functions/example-site/hello/invocations/01234567-89ab-cdef-0123-456789abcdef" }}Poll https://api.deploybase.eu plus the poll_url until status is no longer pending or running. A finished call carries the function’s status code, headers and body, with the body base64 encoded:
curl "https://api.deploybase.eu/api/v1/public/functions/example-site/hello/invocations/01234567-89ab-cdef-0123-456789abcdef"{ "data": { "id": "01234567-89ab-cdef-0123-456789abcdef", "status": "completed", "response_status": 200, "response_headers": { "access-control-allow-origin": "*", "content-type": "application/json" }, "response_body": "eyJtZXNzYWdlIjoiSGVsbG8sIEFkYSEifQ==", "started_at": "2026-09-14T09:20:28Z", "finished_at": "2026-09-14T09:20:36Z" }}Decoding response_body gives {"message":"Hello, Ada!"}. The queued door hands your handler the same Request the HTTPS door would have: the function’s own URL with the query string, the headers you sent, and the body. A queued call takes several seconds end to end because each one starts a fresh container, and the result stays available for 7 days. The reference has the limits and the error messages.
See it in the dashboard
Section titled “See it in the dashboard”Open the project’s Functions tab. Each function has a page with both doors: Call it shows the HTTPS address and whether an instance is serving, Queue it shows the queued address, and both have a copy-as-curl button. Below them, Invocations lists every queued call with its status, response and anything the function wrote to the console. HTTPS calls are not listed there yet.
That is the whole loop: a file, a push, a URL.
When something is off
Section titled “When something is off”| You see | Likely cause | What to do |
|---|---|---|
No [functions] line in the build log |
The deploy was a preview, or functions/ is not at the project root |
Push to the production branch; move the directory |
Bundled 1 function(s) but your function is missing |
The file is in a subdirectory | Move it to the top of functions/. Subdirectories are shared code |
[functions] Build failed: invalid function name |
The file name has uppercase letters, underscores or other characters outside a-z, 0-9 and - |
Rename the file |
[functions] Build failed |
A bundle is over 900 KB, an import could not be resolved, or two files share a name | Trim dependencies; the log names the file |
404 at the hostname |
The team is not in the beta, the function is not deployed yet, or {name}-{subdomain} is longer than 63 characters |
Check the billing card and the build log; shorten the name |
504 |
The handler took longer than 60 seconds | See timeouts. The code keeps running after the 504 |
500 with no detail |
The handler threw, or returned something that is not a Response |
The reply carries no detail on purpose. Reproduce with a queued call to read the console output |
413 |
Request body over 10 MiB on the HTTPS door | Send less, or stream from storage instead |
| Browser says the request was blocked by CORS | The handler did not set access-control-allow-origin |
Add the header, as hello.js does. Keep browser calls to a plain GET with no custom headers so no preflight is sent |
Example repository: codeberg.org/deploybase/functions-example. Verified on 14 September 2026 against deploybase v2026.9.16.