# Functions



A Clusterbase function is a JavaScript or TypeScript module that exports a `handler` function. It receives a standard [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) and returns a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response).

## Basic Handler [#basic-handler]

```ts
export function handler(request: Request): Response {
  return new Response("Hello from Clusterbase!");
}
```

## Async Handler [#async-handler]

Handlers can be async — use `await` for `fetch()` calls, crypto operations, or any async work:

```ts
export async function handler(request: Request): Promise<Response> {
  const data = await fetch("https://api.example.com/data");
  const json = await data.json();
  return Response.json(json);
}
```

## Routing [#routing]

Route matching is done in your handler code using the `URL` and `Request` APIs:

```ts
export async function handler(request: Request): Promise<Response> {
  const url = new URL(request.url);

  if (url.pathname === "/api/hello" && request.method === "GET") {
    return Response.json({ message: "Hello!" });
  }

  if (url.pathname === "/api/time") {
    return Response.json({ time: new Date().toISOString() });
  }

  return Response.json({ error: "Not found" }, { status: 404 });
}
```

You can also use `URLPattern` for more complex routing:

```ts
const pattern = new URLPattern({ pathname: "/users/:id" });

export function handler(request: Request): Response {
  const match = pattern.exec(request.url);
  if (match) {
    const userId = match.pathname.groups.id;
    return Response.json({ userId });
  }
  return new Response("Not found", { status: 404 });
}
```

## Environment Variables [#environment-variables]

Access environment variables via `process.env`:

```ts
export function handler(request: Request): Response {
  const apiKey = process.env.API_KEY;
  return new Response(`Key starts with: ${apiKey?.slice(0, 4)}...`);
}
```

See [Environment Variables](/docs/ccp/environment-variables) for how to set them.

## Organizations [#organizations]

Functions belong to organizations. When you first deploy or run `ccp init`, you select which organization to deploy to. A single organization can have many functions.

## Managing Functions [#managing-functions]

```bash
# Context-aware listing:
#   inside a linked project  -> that function's deployments
#   outside one              -> every function in the organization
ccp list        # or: ccp ls
ccp ls --org-id <org>   # outside a project: choose the org explicitly
ccp ls --all            # show every row, not just the newest 20
ccp ls --json           # full ids, exact timestamps, complete URLs — for scripts

# Delete the linked function
ccp remove      # or: ccp rm

# Link current directory to an existing function
ccp link
```

`ccp remove` reads the linked App's identity directly — it doesn't build or
initialize a project first. Run it outside a linked directory and it fails
immediately, telling you to run `ccp link`.

`ccp ls` adapts to where you run it, the same way `ccp compute ls` does:

* **Inside a linked project** (a `.ccp/config.json` with an `app_id`) it lists that function's deployments, headed `Deployments for <org>/<function>`.
* **Outside a project** it lists every serverless function in the organization — name, canonical URL (shown only once a function has a live production deployment), custom domains, cron, and last update — headed `Functions in <org>`. The organization is resolved from `--org-id`, then the project config, then the `CCP_ORG_ID` env var, then an interactive picker (auto-selected when you belong to a single org; in headless/non-interactive mode with multiple orgs and no `--org-id` or `CCP_ORG_ID`, it errors rather than prompting).

Each row prints on one line, sized to fit the terminal; long values elide in the middle and timestamps show as ages (`6d`) rather than full RFC 3339. A row's URL is carried as a clickable OSC 8 hyperlink on its label rather than printed — unless output is piped or redirected, in which case links are dropped in favor of a plain trailing `URL` column so the address stays copyable. Only the newest 20 rows are shown by default; pass `--all` to see everything, or `--json` for the full, unstyled, script-friendly payload.

## Reading Logs [#reading-logs]

`ccp logs` prints a deployed function's logs — its runtime `console.*` output — the serverless counterpart to `ccp compute logs`:

```bash
ccp logs [FUNCTION_ID] [-n LIMIT] [--level info,warn,error] [--deployment DEP_ID] [-f]
```

* `FUNCTION_ID` falls back to the current directory's `.ccp/config.json`, the same resolution as the other function-scoped commands. Pass it explicitly to read logs from outside a linked project.
* `-n` / `--limit` — number of **most recent** lines to print. Default 100, max 1000 (clamped client-side with a warning).
* `--level` — comma-separated level filter: `info`, `warn`, `error`, `debug`.
* `--deployment` — restrict to a single deployment id.
* `-f` / `--follow` — stream new lines as they arrive until you press Ctrl-C.

Output is one plain line per entry — `<iso-8601-utc> <LEVEL> <message>`, with no colors or decoration, so it pipes cleanly into `grep`. The snapshot returns the *newest* `LIMIT` lines.

```bash
ccp logs                      # last 100 lines for the linked function
ccp logs -n 50 --level error  # last 50 error lines
ccp logs <FUNCTION_ID>        # from outside the project dir
ccp logs -f                   # live tail
```

If the function can't be found you get a single actionable line pointing at `ccp ls`; an expired session points at `ccp auth login`.

## What's Not Available [#whats-not-available]

Clusterbase runs V8 isolates, not Node.js. There are no Node.js built-in modules:

* No `fs`, `path`, `os`, `child_process`, `net`, `http`
* No `Buffer` (use `Uint8Array` and `TextEncoder`)
* No `require()` (use ES modules — the bundler handles modules for you)

Use `fetch()` for HTTP requests to external services. See the full [Runtime API reference](/docs/ccp/runtime).
