# Code

Every stage file in a Destination is an async function that receives a `params` object. This page is the reference for what's in `params` for each file and what the function should do with it.

All files are optional: leave a file empty and that stage is skipped. You never write wrappers, imports, or exports — only the function body.

## `edge/tag`

Runs on the CDN edge for every tracked event. This is where most Destination logic lives.

Available on `params`:

* `payload` — the event: `eventName`, `eventId`, `pageUrl`, `pageTitle`, `referrer`, `locale`, `data` (the event's standard fields, e.g. `currency`, `value`, `orderId`, `contents`).
* `userId` — the EdgeTag user ID for this visitor.
* `user` — hashable PII (`email`, `phone`, `firstName`, `lastName`, `city`, `state`, `zip`, `country`, …), if available.
* `customData` — any custom tags you attached via the SDK.
* `providerData` — click IDs and cookies captured per provider (e.g. `fbp`, `fbc`, `gclid`).
* `hostData` — request metadata (`userAgent`, `ip`, `country`, `city`, `region`, `timezone`, `postalCode`).
* `origin`, `domain`, `platform` — where the event came from.
* `secrets` — your [Variables](/playground/destination/variables.md) as a plain object.
* `infra` — your [Infrastructure](/playground/destination/infrastructure.md) bindings (D1, KV, R2, Analytics Engine).
* `requestHandler(url, config)` — make outbound HTTP requests.
* `userSave`, `userGet`, `providerSave` — read and write the EdgeTag identity graph.
* `logger.log(...)`, `logger.error(...)` — visible in Simulation output.

{% code title="edge/tag — forward Purchase to a third-party API with SHA-256-hashed email" %}

```javascript
if (params.payload.eventName !== 'Purchase') {
  return
}

const email = params.user?.email?.toLowerCase().trim()
let hashedEmail
if (email) {
  const bytes = await crypto.subtle.digest(
    'SHA-256',
    new TextEncoder().encode(email)
  )
  hashedEmail = Array.from(new Uint8Array(bytes))
    .map((b) => b.toString(16).padStart(2, '0'))
    .join('')
}

await params.requestHandler('https://api.example.com/conversions', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${params.secrets.API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    orderId: params.payload.data.orderId,
    value: params.payload.data.value,
    currency: params.payload.data.currency,
    hashedEmail,
    country: params.hostData.country,
  }),
})

params.logger.log('Forwarded Purchase', params.payload.data.orderId)
```

{% endcode %}

## `edge/init`

Runs once the browser SDK loads and invokes it. Use it to capture click IDs from URL parameters or cookies, detect new users, and read or write the identity graph before the first event fires.

Available on `params`:

* `userId`, `isNewUser`
* `session` — `{ isNewSession, sessionId }` or `null`
* `cookies` — raw cookie string from the request
* `hostData`, `secrets`, `infra`
* `requestHandler`, `userSave`, `userGet`, `providerSave`, `logger`

```javascript
if (params.isNewUser) {
  params.logger.log('New user detected', params.userId)
}
```

## `edge/user`

Runs on the edge when user identity is sent (login, signup, profile update). Use it to sync identity to a CRM or resolve the user against an external system.

Available on `params`:

* `payload` — the user fields being saved
* `userId`, `user`, `customData`
* `hostData`, `secrets`, `infra`
* `requestHandler`, `userSave`, `userGet`, `providerSave`, `logger`

```javascript
await params.requestHandler('https://crm.example.com/identify', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${params.secrets.CRM_TOKEN}` },
  body: JSON.stringify({ userId: params.userId, ...params.payload }),
})
```

## `edge/scheduled`

Runs periodically on a cron schedule (every 5 minutes). Use it for batch exports, periodic syncs, or cleanup jobs.

Available on `params`:

* `scheduledTime` — a `Date` for the scheduled run
* `secrets`, `infra`, `logger`, `requestHandler`
* `reporting.saveInDatabase(sql, bindings)` — insert a row
* `reporting.saveBatchInDatabase(sql, bindingsArray)` — batch insert
* `reporting.getFromDatabase(sql, bindings)` — read
* `userKey.byRouteKey(key)` — look up users by a routing key (email or user Id)

```javascript
const since = new Date(params.scheduledTime.getTime() - 24 * 60 * 60 * 1000)
const rows = await params.reporting.getFromDatabase(
  'SELECT order_id, value FROM orders WHERE created_at > ?',
  [since.toISOString()]
)

params.logger.log(`Exporting ${rows?.results.length ?? 0} rows`)
```

## `browser/init`

Runs once in the browser on page load. Use it to load third-party scripts and initialize pixels, gated by consent.

Available on `params`:

* `userId`, `isNewUser`, `session`
* `baseUrl` — EdgeTag CDN base URL
* `manifest.variables` — your client-side [Variables](/playground/destination/variables.md)
* `manifest.package`, `manifest.tagName`, `manifest.geoRegions`
* `keyName`, `destination`
* `consentData.consent`, `consentData.categories`, `consentData.consentSettings`
* `sendTag(event)` — forward an event to your own `browser/tag`
* `sendEdgeData(data, providers, options?)` — persist data through the edge
* `getEdgeData(keys, callback)` — read previously saved edge data
* `executionContext` — a `Map` shared across calls in the same page load

```javascript
if (!params.consentData.categories.advertising) return

const script = document.createElement('script')
script.src = `https://cdn.example.com/pixel.js?id=${params.manifest.variables.PIXEL_ID}`
script.async = true
document.head.appendChild(script)
```

## `browser/tag`

Runs in the browser for every tracked event. Use it to call the third-party pixel's tracking API with the event data.

Available on `params`:

* `userId`, `sessionId`, `eventId`, `eventName`, `data`
* `manifestVariables` — your client-side [Variables](/playground/destination/variables.md)
* `destination`
* `sendTag`, `getEdgeData`, `executionContext`

```javascript
if (window.fbq) {
  window.fbq('track', params.eventName, {
    value: params.data.value,
    currency: params.data.currency,
  })
}
```

## `browser/user`

Runs in the browser when the user identity changes. Use it to update a third-party pixel with the latest user data.

Available on `params`:

* `userId`, `data`, `manifestVariables`, `destination`

## CDN API handlers

Each CDN API endpoint is a handler function. CDN APIs are browser-callable — no authentication — and are the right place for first-party endpoints your site needs to hit directly (for example `/api/subscribe` or `/api/consent-preferences`).

Available on `params`:

* `endpoint` — the endpoint name
* `method` — `GET`, `POST`, `PUT`, or `DELETE`
* `request` — the standard `Request` object
* `secrets`, `infra`, `logger`

Return a standard `Response`.

```javascript
const body = await params.request.json()

await params.infra.SUBSCRIBERS_DB
  .prepare('INSERT INTO subscribers (email, created_at) VALUES (?, ?)')
  .bind(body.email, new Date().toISOString())
  .run()

return new Response(JSON.stringify({ status: 'ok' }), {
  headers: { 'Content-Type': 'application/json' },
})
```

## Server API handlers

Server APIs are EdgeTag backend authenticated server-only endpoints. They receive everything a CDN API does, plus `params.currentUser` with the authenticated user (`userId`, `email`, `role`, `teamId`, …). Use these for admin-only or server-to-server integrations.

## Available web APIs

Inside edge files, you have the Workers runtime, which includes:

* `crypto.subtle.digest(...)`, `crypto.randomUUID()`
* `URL`, `URLSearchParams`
* `TextEncoder`, `TextDecoder`
* `atob`, `btoa`
* `structuredClone`

Browser files have the full browser API surface (`window`, `document`, `fetch`, `localStorage`, `navigator`, and so on).

## Return values

Most stage files don't need to return anything — they're fire-and-forget.

* `edge/*` and `browser/*` — returning nothing is fine. Browser files can return `{ skipEvent: true }` to stop EdgeTag from continuing with this event.
* **CDN APIs and Server APIs** — must return a `Response`.

{% hint style="info" %}
Throwing an exception in a stage file doesn't break the event pipeline for other channels. The error is captured and surfaced in [Simulation](/playground/destination/simulation.md) output and your EdgeTag logs.
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.edgetag.io/playground/destination/code.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
