> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ollie.shop/llms.txt
> Use this file to discover all available pages before exploring further.

# function dev (local functions)

> Run your functions on your machine against a real upstream, replay a request on every save, and invoke a function by hand with curl

`ollieshop function dev` is a local mini-hub: it builds every function in `functions/`, loads your `.env`, and runs the same request/response pipeline the production hub runs, with the upstream call going to the real platform URL you give it. Nothing is deployed and no login is needed.

```bash theme={"system"}
npx ollieshop function dev
npx ollieshop function dev --port 4002 --env-file .env.local --no-tui
```

| Flag | Meaning |
| - | - |
| `--port <n>`, `-p` | Port to listen on. Default **4001**. Use another port when [`start`](/ollie-shop/cli/start) is running, which uses 4001 internally |
| `--env-file <path>` | Variables to expose to the functions as `process.env`. Default `.env` in the current folder; a missing default is ignored, a missing explicit file is an error |
| `--stage <name>`, `-s` | Only used to show the store id of that stage in the banner |
| `--no-tui` | Plain log lines instead of the terminal UI |

## Project layout

```
functions/
└── fraud-check/
    ├── index.ts        # export const handler = async ({ req, res }) => { … }
    └── function.json   # how the function is wired, see below
```

`function.json` mirrors the record's settings in the Admin, so the local pipeline matches what production does:

```json function.json theme={"system"}
{
  "invocation": "response",
  "trigger": {
    "url": "https://acme.vtexcommercestable.com.br/api/checkout/pub/orderForm",
    "expression": "method in [\"GET\"]"
  },
  "onError": "throw",
  "priority": 0,
  "dev": {
    "request": {
      "url": "https://acme.vtexcommercestable.com.br/api/checkout/pub/orderForm",
      "method": "GET"
    }
  }
}
```

| Key | Meaning |
| - | - |
| `invocation` | `request` or `response` |
| `trigger` | `url` and the JSONata `expression`, as in the Admin. Without a trigger the function can be invoked directly but is skipped by pipeline runs |
| `onError` | `throw` (default) or `skip` |
| `priority` | Integer ≥ 0, default 0 |
| `dev.request` | Optional. A request the server **replays through the pipeline after every rebuild**, so you see the effect of a change without typing anything |

A folder without `function.json` is still built; the terminal tells you it is only reachable by direct invocation.

## What you see

```text theme={"system"}
Ollie Functions · local mini-hub · http://localhost:4001
  pipeline  POST /run
  invoke    POST /invoke?name=<function>
  manifest  GET  /manifest.json
  stage prod · store 7f3c9a1e-…

fn fraud-check [response] → https://acme.vtexcommercestable.com.br/api/checkout/pub/orderForm
built in 142ms
200 POST /run (318ms)
replay fraud-check 200 · fraud-check success · 301ms
● listening · 1 builds · q quit · r rebuild
```

Build errors are shown as `✗ [fraud-check] message (line N)` and do not stop the server.

## Running the pipeline

`POST /run` takes a request description, runs every function whose trigger matches, performs the real upstream call, and returns everything that happened:

```bash theme={"system"}
curl -s -X POST http://localhost:4001/run \
  -H 'content-type: application/json' \
  -d '{"url":"https://acme.vtexcommercestable.com.br/api/checkout/pub/orderForm","method":"GET"}'
```

```json theme={"system"}
{
  "ok": true,
  "response": { "status": 200, "headers": { … }, "body": "…" },
  "session": null,
  "executions": [
    { "name": "fraud-check", "type": "response", "trigger": true, "status": "success", "durationMs": 12, "output": { … } }
  ]
}
```

`executions[].status` is `success`, `error`, `error:skipped` (failed, but `onError` is `skip`) or `no-match`. With `onError: throw`, the first failure stops the pipeline and `error` carries the function id and message. Functions run in priority order, lowest first, then by name.

The body can also carry `headers` (an object), `body` and `session` (the initial session storage). Requests to localhost, private networks and `.local`/`.internal` hosts are refused with `Destination is not allowed`.

## Invoking one function

`POST /invoke?name=<folder or id>` runs a single function with an event you build yourself, without the pipeline or the upstream call. Handy for unit-style checks:

```bash theme={"system"}
curl -s -X POST 'http://localhost:4001/invoke?name=fraud-check' \
  -H 'content-type: application/json' \
  -d '{"type":"request","req":{"url":"https://example.com/","method":"GET","body":null,"headers":[]},"res":null,"session":null}'
```

Here `headers` is an array of `[name, value]` pairs. The reply contains the resulting `req`, `res` and `session`.

## What is not emulated

* Secrets come from your `.env`, not from the store's [Environment Variables](/ollie-shop/configuration/env-vars.md). Copy the values you need locally.
* The session storage cookie, cookie forwarding to the browser, and the hub's timeouts are approximated; verify cookie behaviour on a deployed function.
* The checkout is not involved. You drive the pipeline with `curl` or the replay request, not by clicking in a store.

## Related

* [Functions](/ollie-shop/functions.md): the handler contract, triggers, session storage and cookies
* [deploy](/ollie-shop/cli/deploy.md): shipping the function once it works locally


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.