# Writing and deploying a function

Source: https://cloud.synteq.com/docs/writing-and-deploying-a-function
Updated: 2026-10-19T04:06:18.086036+00:00

---
# Writing and deploying a function

A function is a handler, not a server. Synteq owns the HTTP listener and calls your handler once per request with an event and a context. Deploy it from the browser editor, the CLI, or a GitHub folder.

## The handler contract

| Event field | Description |
| --- | --- |
| `method` | The HTTP method. |
| `path` | The request path, without the query string. |
| `query` | The query string, parsed into an object of strings. |
| `headers` | Request headers, lowercase keys. |
| `body` | The body decoded as UTF-8 text. |
| `rawBody` | The body as bytes, for binary payloads or signature checks. |

The context has `functionName` and `requestId`. The Logs tab searches on the request id, so log it to find a run later.

The return value becomes the response:

| Return value | Response |
| --- | --- |
| Nothing or `null` | `204`, empty. |
| A string or bytes | `200`, `text/plain`. |
| An object with `statusCode` (or `status`), `headers`, `body` | Sent as-is. A non-string body is sent as JSON. `isBase64Encoded: true` sends a base64 string as raw bytes. Any one of these keys makes the object a response, so wrap data that has its own `status` field in `body`. |
| Any other object or array | `200`, JSON. |
| A `Response` (Node.js only) | Sent as-is. |

A thrown error returns `500` with the body `Internal error`. The stack trace goes to the run's log lines.

### Node.js 22

Export the handler as the default export or as `handler`. The entry file is the first of `index.mjs`, `index.js`, `handler.mjs`, `handler.js`, `main.mjs`, `main.js`.

```
export default async function handler(event, context) {
  return { hello: "world", path: event.path };
}

```

```
export default async function handler(event) {
  if (event.method !== "POST") {
    return { statusCode: 405, body: "POST only" };
  }
  const payload = JSON.parse(event.body || "{}");
  return { statusCode: 201, body: { received: payload } };
}

```

Add a `package.json` to declare dependencies. If it has no start script, Synteq adds one that runs your handler. If it has one, Synteq treats the function as your own server.

Dependencies are installed once per distinct set and attached to the shared Node.js runtime, so a function with a `package.json` cold-starts as fast as one without. This applies to packages from the npm registry. A dependency from git, a URL, or a local path gives the function its own image instead. Packages that ship prebuilt native binaries, such as `sharp`, work. Commit a `package-lock.json` to pin the exact versions Synteq installs.

### Python 3.12

Define a module-level `handler` (or `main`). The entry file is the first of `handler.py`, `main.py`, `index.py`, `app.py`. The event and context are dicts with the same keys. Add a `requirements.txt` for dependencies; they are installed for Python 3.12 once per distinct set and attached to the shared runtime, so they add nothing to your cold start. This applies to packages from PyPI. A URL, VCS, or local-path requirement gives the function its own image instead. Packages with compiled extensions work.

```
def handler(event, context):
    return {"hello": "world", "path": event["path"]}

```

### Your own server or Dockerfile

An upload with a `Dockerfile` is built as-is, in any language. A recognised project file (`package.json` with a start script, `pyproject.toml`, `go.mod`, `Cargo.toml`, and others) is built with the same zero-config builder Apps use. In both cases your server listens on `PORT` (default `8080`), sees raw HTTP, and the handler contract does not apply.

Use a Dockerfile when a dependency needs a system library the shared runtime does not carry. `pyvips` needs `libvips`, for example, and a `requirements.txt` cannot install it; a Dockerfile that runs `apt-get install libvips42` before `pip install pyvips` can. A function built from a Dockerfile runs on its own image rather than the shared pool, so its cold start is the time your image takes to start listening, typically 1–2 seconds.

The `.synteq/` folder name is reserved. Uploads containing it are refused.

## Create a function in the portal

1. On the **Functions** page, click **New Function**.

2. Enter a name. The hostname preview updates as you type. The first available location is preselected, and the runtime size is chosen for you when your plan pins one.

3. Choose how to ship: **Write code** (opens the editor with a starter handler), **Connect GitHub**, or **Use the CLI** (shows the commands to run once the function exists). Click **Skip** to create an empty function.

4. Pick a **Runtime** if your files do not imply one. `index.mjs` implies Node.js; `handler.py` implies Python.

5. Optionally add environment variables and cron triggers.

6. Click **Done**. The button reads **Skip and finish** when you add no triggers.


If the organization has no payment method, the wizard asks for one. If you edited the starter code, the first deploy starts when you finish. An unchanged starter is not deployed.

## Deploy from the editor

The **Overview** tab is an editor with a file tree.

| Control | Description |
| --- | --- |
| Files | Create, rename, and import files and folders; download the set as a zip. The entry file cannot be deleted. |
| Drafts | Edits autosave as a draft ("Draft saved") that everyone on the team sees. It does not go live until you deploy. **Discard changes** reloads the deployed version. |
| **Show diff** | Compares working files with the deployed version, file by file. |
| **Deploy** | Builds from the working files. Disabled, with the reason, when nothing changed, every file is empty, or the source is over the limits. One build runs at a time. |

A deploy moves through the build (pending, building, succeeded) and then the release (starting, health gate, live). A failed attempt stays on the **Versions** tab with its build log.

## Deploy from the CLI

From an empty directory:

```
synteq login
synteq function init my-fn
synteq function deploy

```

`function init` creates the function (or reuses one with that name), writes a starter handler if there is no entry file or Dockerfile, and links the directory so later commands need no `--name`. Use `--runtime python312`, `--location dfw`, or `--no-scaffold` to link without writing a file.

`function deploy` uploads the directory and follows the build and release. Ctrl-C stops watching; the deploy continues. Use `--no-wait` in scripts; it returns the build as soon as it starts. With `-o json`, the command prints the build and the release as JSON.

The CLI uploads text files only and prints every skipped file with its reason:

- Files with a NUL byte in the first 8 KB are treated as binary.
- `node_modules`, `.git`, `.synteq`, `__pycache__`, `.venv`, `venv`, `dist`, `build`, `target`, `.DS_Store`, and `*.log` are skipped at any depth.
- `.env` files are never uploaded. Set variables with `synteq function env set` instead.
- Patterns in a `.synteqignore` file (gitignore syntax) are skipped.
- Paths are limited to 255 characters, with no `..` segments. Symlinks are not followed.

Uploads over the source limits are refused, naming the largest files. Put large assets behind a URL or use a Dockerfile. See CLI reference for all flags.

## Deploy from GitHub

A function can follow one folder of a GitHub repository. Set it up from **Connect GitHub** in the wizard, or under **Settings** → **GitHub**.

1. Click **Connect GitHub** and install the Synteq GitHub App on the account that owns the repository. If a repository is missing later, use **Repository missing? Grant the app access**.
2. Pick the account, repository, and branch.
3. Pick the folder that holds the handler. Folders with no recognised entry file are flagged.
4. Click **Connect and deploy**. The branch head deploys right away.

Each push that changes something inside the folder deploys a new version; other pushes are ignored. With **Auto-deploy** off, a push lands in the editor as a draft, and you deploy it from there. The **Activity** tab records each push deploy, and why a push could not deploy. Disconnecting keeps the current version.

## Environment variables

The **Environment** tab holds the variables your handler reads from `process.env` or `os.environ`.

Each variable has a name, a value, a kind, and an optional note. Values are encrypted at rest. Renaming a variable keeps its value. A change rolls out to running instances after about 2 minutes without further edits, or at once when you click **Roll out now**.

| Kind | Description |
| --- | --- |
| Secret | Reveal shows the last 4 characters. |
| Config | Reveal shows the full value. |

Neither kind appears in a list or in the CLI's `env list` output.

```
synteq function env set KEY=value              # secret by default
synteq function env set KEY=value --kind config
synteq function env list
synteq function env reveal KEY
synteq function env rm KEY

```

`env set` takes `--rollout auto|now|deploy|none`. `auto` (the default) rolls out after the quiet window, `now` replaces running instances right away, `deploy` starts a new deploy, and `none` leaves the change for your next `synteq function deploy`.

## Execution settings

**Settings** → **Execution** holds the settings for a running function. Each is bounded by the runtime size, and the ceiling shows beside the field. Defaults and ranges are in Functions overview, under Limits.

| Setting | Description |
| --- | --- |
| **Runtime** | The handler's language. Binds at the next deploy. |
| **Concurrency** | Requests served per instance at once. |
| **Timeout** | Seconds a run may take before it is killed with `504`. |
| **Max instances** | How far the function scales out. |

Saving applies the change immediately, without a redeploy. The **Activity** tab records which settings changed, not the values. How these settings drive dispatch and scaling is in Functions overview, under How functions run.

## What next?

- Triggers, operations, and billing: invoke the function, schedule it, and read its logs.
- Functions overview: scaling, cold starts, metering, and limits.
- CLI reference: every `synteq function` flag.
