# Cloud Functions

> How Firenook runs your Functions: the triggers it delivers, Node 22, 24 and 26, codebases, TypeScript, reload on save, and functions:invoke.

Firenook runs your Cloud Functions code in its own runtime, one Node worker
per codebase, and delivers HTTP requests and events from the other emulators
to it. Read this page to see which triggers it delivers, how it loads and
reloads your code, and how to call any function by hand.

## Set it up

Functions start when `firebase.json` has a `functions` section (or
`extensions`, which run on the same runtime):

```json
{
  "functions": {
    "source": "functions",
    "codebase": "default",
    "ignore": ["node_modules", ".git", "firebase-debug.log", "*.local"]
  },
  "emulators": {
    "functions": { "port": 5001 }
  }
}
```

`functions` can also be an array of codebases; each one gets its own worker.
Firenook finds your functions through the `firebase-functions` SDK already in
the codebase's `node_modules`, as the official CLI does, so install your
dependencies first (`cd functions && npm install`). Nothing else is needed: no
firebase-tools and no Java.

Only JavaScript and TypeScript compiled to JavaScript run; Python and Dart
functions do not. For TypeScript, `--build` compiles your code before it
loads and again on every save ([TypeScript functions](https://firenook.dev/docs/functions/typescript)).

Client apps reach callable functions with
`connectFunctionsEmulator(functions, '127.0.0.1', 5001)`, and HTTP functions
at `http://127.0.0.1:5001/<project>/<region>/<name>`.

### Node 22, 24 or 26

Your functions run on the Node that runs Firenook, as the official emulator
runs them on its own Node. Firenook is tested on Node 22, 24 and 26:

- On Node 22, 24 or 26 the emulators start.
- On an older Node, `emulators:start` and `emulators:exec` stop at once and
  say how to switch:

  ```text
  Firenook: the emulators need Node 22, 24 or 26, since your Functions run on it; this is Node 20.20.2. Switch to Node 24 (with nvm: nvm install 24) and run it again
  ```

- On an odd or a newer major (23, 25, 27 and up), the emulators start with a
  warning, since nobody has tested that Node.

A codebase says which Node it deploys on with `engines.node` in its
`package.json`, or `runtime` (such as `nodejs22`) on its entry in
`firebase.json`, which comes first. When the Node running the emulators is
another one, the start says so, as the official emulator does, and goes on:

```text
firenook functions: functions/package.json asks for Node 22 in engines.node, but these functions run on Node 24.20.0, the Node running the emulators; start the emulators with Node 22 to run them on the Node they ask for (with nvm: nvm use 22)
```

In a terminal it is one of the start-up warnings, which `w` lists. A reload
that leaves the codebase asking for the same Node does not repeat it.

## Triggers it delivers

| Trigger | Delivered |
| --- | --- |
| HTTP (`onRequest`), callable (`onCall`) and streaming callable | Yes |
| Firestore (`onDocumentCreated`, …, and first-generation `document.*`) | Yes, when Firestore runs |
| Storage (`onObjectFinalized`, `onObjectDeleted`, `onObjectMetadataUpdated`) | Yes, when Storage runs |
| Auth blocking (`beforeUserCreated`, `beforeUserSignedIn`) and `user.create`, `user.delete` | Yes, when Auth runs |
| Pub/Sub (`onMessagePublished`) | Yes, when Pub/Sub runs |
| Scheduled (`onSchedule`) | Yes, on their schedule ([Scheduled functions](https://firenook.dev/docs/functions/scheduled)) |
| Task queues (`onTaskDispatched`) | Yes ([Cloud Tasks](https://firenook.dev/docs/services/tasks)) |
| Eventarc custom events (`onCustomEventPublished`) | Yes ([Eventarc](https://firenook.dev/docs/services/eventarc)) |
| Realtime Database and Firebase Alerts | No: Firenook emulates neither |

A function whose service the suite does not run, or does not emulate, is
loaded and listed but receives no event. The first load says why, with the
official emulator's line, as a warning on the terminal screen and in the
plain log. Started with `--only functions,firestore`:

```text
firenook functions[us-central1-onUpload]: function ignored because the storage emulator does not exist or is not running.
firenook functions[us-central1-nightlyReport]: function ignored because the pubsub emulator does not exist or is not running.
```

Firenook warns this way for Firestore, Pub/Sub (and so schedules), Storage,
Auth and the Realtime Database; the official emulator warns for Firestore,
Pub/Sub and the Realtime Database only. The console and `functions:invoke`
still call such a function directly.

## HTTP, callable and event functions

With the starter `helloWorld` and a callable `addMessage`, both answer on the
Functions port:

```console
$ curl http://127.0.0.1:5001/demo-shop/us-central1/helloWorld
Hello from Firebase!
$ curl -X POST -H 'Content-Type: application/json' \
    --data '{"data":{"text":"hi"}}' \
    http://127.0.0.1:5001/demo-shop/us-central1/addMessage
{"result":{"echo":"hi","uid":null}}
```

An event function runs when its service changes. This one writes an audit
document for every new order:

```js
const { onDocumentCreated } = require('firebase-functions/firestore');
const logger = require('firebase-functions/logger');
const { initializeApp } = require('firebase-admin/app');
const { getFirestore } = require('firebase-admin/firestore');

initializeApp();

exports.onOrderCreated = onDocumentCreated('orders/{orderId}', async (event) => {
  const order = event.data.data();
  logger.info('order created', { orderId: event.params.orderId, total: order.total });
  await getFirestore().doc(`audit/${event.params.orderId}`).set({ seen: true, total: order.total });
});
```

Writing `orders/o42` runs it, and the log shows the call:

```text
firenook functions: Beginning execution of "us-central1-onOrderCreated"
firenook functions: {"orderId":"o42","total":42,"severity":"INFO","message":"order created"}
firenook functions: Finished "us-central1-onOrderCreated" in 2.9ms
```

Inside a worker, the Admin SDK finds every emulator by itself: Firenook sets
`FIRESTORE_EMULATOR_HOST`, `FIREBASE_AUTH_EMULATOR_HOST`,
`FIREBASE_STORAGE_EMULATOR_HOST`, `PUBSUB_EMULATOR_HOST`,
`CLOUD_TASKS_EMULATOR_HOST` and `CLOUD_EVENTARC_EMULATOR_HOST`. The worker
holds demo credentials, never yours: a call that asks for an access token
first, such as an Eventarc publish or a task enqueue, gets the emulators'
token without leaving the machine. [Environment variables and
secrets](https://firenook.dev/docs/functions/environment) covers your own `.env` and
`.secret.local` files, and params with their defaults.

## Reload on save

Firenook watches each codebase and reloads it when you save. The other
emulators keep running, and so does your data:

```text
firenook functions: Loaded functions definitions from source: helloWorld, addMessage, onOrderCreated, onUpload, nightlyReport, sendReceipt, onShipped.
firenook functions[us-central1-helloWorld]: http function initialized (http://127.0.0.1:5001/demo-shop/us-central1/helloWorld).
firenook functions[us-central1-onShipped]: eventarc function initialized.
firenook functions routing refreshed: 7 registered functions
```

Trimmed: every function gets its `initialized` line. Functions that do not
load leave every emulator running, the version loaded before keeps
answering, and the next save loads them again; [if a save seems to change
nothing](https://firenook.dev/docs/functions/debugging#if-a-save-seems-to-change-nothing), read
the terminal screen or the console. In a terminal, `r` reloads by hand.

In 0.2.0, a function you remove, rename or move to another region (a
`region` param changed in `.env`, for one) keeps its old route until the
emulators restart. A call to the old route answers `500`, and the log says:

```text
firenook functions[us-central1-params]: the source no longer exports this function (record kept from an earlier load)
```

Restart the emulators after you change a function's name or region.
[Debugging and reload on save](https://firenook.dev/docs/functions/debugging) covers
`--inspect-functions` and attaching a debugger.

## Call any function with functions:invoke

`firenook functions:invoke` calls a function on the running suite. Without
`--event-data` it sends an HTTP request, or a callable body with `--data`:

```console
$ npx firenook functions:invoke addMessage --data '{"text":"hi"}'
200 OK http://127.0.0.1:5001/demo-shop/us-central1/addMessage
{"result":{"echo":"hi","uid":null}}
```

With `--event-data` it builds the event the official `functions:shell` would
and delivers it, for any background trigger:

```console
$ npx firenook functions:invoke onOrderCreated --resource orders/o99 \
    --event-data '{"status":"paid","total":99}'
Firenook: firestore event google.cloud.firestore.document.v1.created for onOrderCreated (us-central1, gcfv2) via trigger key us-central1-onOrderCreated-0
200 OK http://127.0.0.1:5001/functions/projects/demo-shop/triggers/us-central1-onOrderCreated-0
{"status":"acknowledged"}
```

A Pub/Sub event is published through the Pub/Sub emulator, so every
subscriber sees it, and a task goes through the Cloud Tasks emulator, so
retries and rate limits apply. `--auth` fills the event's auth, `--params`
fills path wildcards, `--event-type` sets an Eventarc type, and `--json`
prints the whole record. [CLI commands](https://firenook.dev/docs/reference/cli) lists every flag.
`functions:shell` itself does not exist: Firenook never starts a second
Functions runtime.

`functions:invoke` finds the running suite the way the official CLI does,
through a file named after the project id in your temporary folder, and each
Firenook suite also records its own folder. Run in a project's folder, the
command reaches the suite started there, even when another suite runs with the
same project id. Elsewhere, with two such suites running, it asks for
`--hub-port` to pick one.

## Every call, kept

Firenook keeps each invocation with the request or event that caused it, its
answer, its timing and the log lines it printed: the last 2,000 calls, at most
64 MiB. The console's Functions page shows them, and your coding agent reads
them through [MCP](https://firenook.dev/docs/tools/mcp), with the error and stack trace of a call
that failed.

## What it does not do

- **No network sandbox.** A `demo-` project never reaches a real Firebase
  project, and workers get demo credentials, never yours, but your code can
  still call any external API it is written to call.
- **No Secret Manager.** Put secret values in `.secret.local`.
- **No Python or Dart runtimes.**
- **Headers named `x-firenook-*`** are the engine's own and are removed before
  your function sees a request.

## Next

- [Scheduled functions](https://firenook.dev/docs/functions/scheduled)
- [Environment variables and secrets](https://firenook.dev/docs/functions/environment)
- [TypeScript functions](https://firenook.dev/docs/functions/typescript)
- [Debugging and reload on save](https://firenook.dev/docs/functions/debugging)
- [Extensions](https://firenook.dev/docs/services/extensions), which run on the same runtime
