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

# Actions

> Side-effect functions: session, cookies, redirects, response control, headers, and template variables.

Action functions let your backend script perform side-effects — set session data, cookies, redirect the visitor, inject template variables, or send custom responses.

## Session

Read and write values in the visitor's server-side session.

```javascript theme={null}
session.set('my_key', 'my_value');
session.set('visit_count', 5);

var count = session.get('visit_count');  // 5
var missing = session.get('nonexistent'); // null
```

Session values set here are immediately available to funnel script rules that run after the backend script.

<Warning>
  Session keys and values should be simple types (strings, numbers, booleans). Complex objects are serialized as JSON internally.
</Warning>

<Note>
  For security, some session keys are reserved and cannot be read or written via `session.get()` / `session.set()`. Use your own key names (e.g. `my_key`, `visit_count`) for custom data.
</Note>

### `session.setCustomer(fields)`

Establish an authenticated customer session from a custom login flow — magic link, external OTP, SSO callback, or any mechanism outside the built-in purchase pipeline. Writes the runtime's canonical customer fields (`is_customer`, `email`, `customer_id`, `customer`) so downstream pages, template variables, and access rules see the visitor as logged in.

```javascript theme={null}
// Verify a one-time code your app generated...
var code = getVariable('submitted_code');
var email = getVariable('submitted_email');
var ok = verifyMyOtp(email, code);

if (ok) {
  session.setCustomer({ email: email, name: 'Jane Doe' });
  redirect('/dashboard');
}
```

| Field        | Type   | Required | Description                                                             |
| ------------ | ------ | -------- | ----------------------------------------------------------------------- |
| `email`      | string | yes      | The customer's email (must contain `@`).                                |
| `name`       | string | no       | Full display name. If omitted, derived from `first_name` + `last_name`. |
| `first_name` | string | no       | First name. Merged into `session.customer.first_name`.                  |
| `last_name`  | string | no       | Last name. Merged into `session.customer.last_name`.                    |
| `id`         | any    | no       | Internal customer id. When set, also writes `session.customer_id`.      |

Returns `true` on success, `false` on invalid input or quota exhaustion.

<Note>
  `setCustomer` merges over any existing `session.customer` object, so tracking fields such as `total`, `currency`, or `order_ids` set by earlier checkout flows are preserved.
</Note>

### `session.getToken(ttlSeconds?)` / `session.setFromToken(token)`

Mint and consume short-lived encrypted tokens that carry the current customer identity across brand-owned domains. Used for cross-domain session continuity — see [Cross-domain session handoff](/guides/cross-domain-session-handoff) for the full protocol and a worked example.

```javascript theme={null}
// On the source domain: mint a 60-second token
var token = await session.getToken();        // null if no customer
var url = 'https://app.example.com/welcome?ef_session=' + encodeURIComponent(token);
redirect(url);

// On the destination domain: most of the time you don't need this — the
// EF runtime auto-consumes `?ef_session=<token>` on every request. Call
// setFromToken() manually only if you need to consume a token from a
// header, body, or non-query-string location.
var ok = await session.setFromToken(headerToken);
```

`session.getToken(ttlSeconds?)` returns the token string (or `null` if the session has no customer). `ttlSeconds` defaults to `60` and is capped at `600`.

`session.setFromToken(token)` returns `true` on success, `false` on any failure (tamper, expiry, replay, wrong brand). It never throws.

## Cookies

Set or delete cookies on the visitor's browser.

```javascript theme={null}
cookie.set('promo_seen', 'true', { maxAge: 3600, path: '/' });
cookie.delete('old_cookie');
```

### `cookie.set(name, value, options?)`

| Option   | Type   | Description                    |
| -------- | ------ | ------------------------------ |
| `maxAge` | number | Cookie lifetime in **seconds** |
| `path`   | string | Cookie path (default: `'/'`)   |

### `cookie.delete(name)`

Clears the cookie by name.

<Note>
  Some cookie names are reserved and cannot be set or deleted. Use your own cookie names for custom data.
</Note>

## Redirect

Redirect the visitor to a different URL. This **stops** page rendering — the visitor is immediately sent to the new URL.

```javascript theme={null}
redirect('/login');           // 302 temporary redirect
redirect('/new-page', 301);   // 301 permanent redirect
redirect('https://example.com/page'); // external redirect
```

<Note>
  When `redirect()` is called, the entire funnel flow and page rendering are skipped. The visitor receives only the redirect response.
</Note>

<Warning>
  Only relative paths (`/path`) and `http://` / `https://` URLs are allowed. Other schemes (e.g., `javascript:`, `data:`) are blocked for security.
</Warning>

## Template Variables

Inject values into the template engine. These become available as `{{ var.key }}` in page HTML.

```javascript theme={null}
setVariable('discount', '50%');
setVariable('show_banner', true);
setVariable('user_tier', 'premium');
setVariable('user', { name: 'Sam', email: 'sam@example.com' });
setVariable('categories', [
  { slug: 'marketing', name: 'Marketing', courses: [...] },
  { slug: 'design', name: 'Design', courses: [...] },
]);
```

In your page HTML:

```html theme={null}
<h1>Get {{ var.discount }} off!</h1>

@if(var.show_banner)
  <div class="banner">Premium member</div>
@endif

<!-- Objects work too -->
<p>Welcome, {{ var.user.name }}</p>

<!-- Debug: dump the full object as JSON -->
<pre>{{ dump(var.user) }}</pre>
```

<Warning>
  `setVariable('name', value)` stores the value under the `var` namespace. Always access it as **`var.name`** in templates — e.g. `{{ var.name }}`, `{{ dump(var.name) }}`, `@if(var.name)`. Using `{{ name }}` or `{{ dump(name) }}` without the `var.` prefix will **not** find the variable.
</Warning>

### Scoping inside `@foreach`

When you iterate over a variable with `@foreach`, the **loop variable** is its own scope — it does **not** use the `var.` prefix:

```html theme={null}
<!-- var.categories needs the var. prefix (it came from setVariable) -->
@foreach(cat in var.categories)
  <!-- cat is a loop variable — access its properties directly, no var. prefix -->
  <h2>{{ cat.name }}</h2>
  <p>{{ cat.slug }}</p>

  <!-- nested arrays on the loop variable also work directly -->
  @foreach(course in cat.courses)
    <div>{{ course.title }}</div>
  @endforeach
@endforeach
```

In short: `var.` is only for the **top-level** reference to variables set by `setVariable`. Once a value is assigned to a loop variable (`cat`, `course`, etc.), you access its properties directly.

Variables set with `setVariable` take precedence over brand variables with the same key.

## Response Headers

Set custom HTTP response headers.

```javascript theme={null}
setHeader('X-Custom-Header', 'my-value');
setHeader('Cache-Control', 'no-store');
```

<Note>
  For security, certain response headers cannot be set (e.g. those that control cookies, CORS, or connection). Header values are sanitized.
</Note>

## Custom Response

Send a custom response instead of the rendered page. These **stop** page rendering.

### `response.status(code)`

Set the HTTP status code (used with `response.send()` or `response.json()`, or with the normal page render).

```javascript theme={null}
response.status(403);
response.send('<h1>Access Denied</h1>');
```

### `response.json(data)`

Send a JSON response. Skips page rendering.

```javascript theme={null}
response.json({ error: false, user: customer.email });
```

### `response.send(html)`

Send raw HTML. Skips page rendering.

```javascript theme={null}
response.send('<html><body><h1>Maintenance</h1></body></html>');
```

## Logging and errors

`console.log`, `console.info`, `console.warn`, and `console.error` are captured and stored in execution logs. Use them for debugging.

```javascript theme={null}
console.log('Visitor IP:', request.ip);
console.log('Customer:', customer);
```

Logs and errors are visible in the [Debug Window](/debugging/debug-window) (Page Events tab) for the page request. If your script throws an error or times out, the visitor still receives the normal page (no error message is shown); the error is logged there and may be reported to admins.
