---
title: Functions
slug: functions
docTags: 
createdAt: 2025-05-29T19:12:43.114Z
---

:::hint{type="info"}
Due to the technical nature of this feature, functions are only available to Chord users who are assigned a **Team Admin** or **Developer&#x20;**&#x70;latform role.&#x20;

For additional support, please reach out to [help@chord.co](mailto\:help@chord.co).&#x20;
:::

# Introduction

![](https://api.archbee.com/api/optimize/VPxpEoMPxDsnmxOyjOdWV-Z-iOdsI4OZ8xa0W0Q2dqS-20250911-211622.png)

Functions are customizable data workflows that process, transform, or enrich the raw event data flowing through the CDP. Functions are written in JavaScript and run before data is sent to destinations. They let you tailor how data is handled—whether it's standardizing event names, enriching user profiles, or formatting data for downstream tools—so you can keep your insights clean, consistent, and actionable.

In other words...

- For **Marketers**, CDP functions are more like custom-built control panels. They give your team the flexibility to reshape data in real time, enrich it with external info, or even decide what gets sent where—perfect for brands needing more control, precision, and adaptability.
- For **Developers**, CDP functions offer broader execution power. They act like real-time serverless functions with support for external API calls, persistent key-value storage, and conditional logic. You can reshape event streams on the fly, enrich payloads, or build highly dynamic routing logic—ideal for engineering teams looking for full control over their pipeline behavior.

Some additional use cases for functions include:&#x20;

- **Filtering** - Exclude unwanted events, such as dropping events from test sources or internal traffic
- **Normalizing** - Standardize event data (e.g., "page\_view", "pageview", "pageView" → "page") to keep downstream schemas clean
- **Enrichment** - Add geo-location data, parse user agents, or pull additional data from external APIs
- **Routing** - Dynamically change which table or destination an event is sent to based on event properties

## Function Anatomy

Every CDP function follows this structure:

```javascript
export default async function(event, context) {
  // Your transformation logic here
  return event; // or "drop" to filter out the event
}
```

## Parameters

### event (Object)

The event object being processed. This is the raw event data coming from your sources. Common properties include:

- event (string) - The event name (e.g., "Page Viewed", "Product Added")
- userId (string) - The unique identifier for the user
- anonymousId (string) - Anonymous identifier if userId is not available
- timestamp (string) - ISO 8601 timestamp of when the event occurred
- properties (object) - Custom properties associated with the event
- context (object) - Contextual information about the event (IP, user agent, etc.)
- type (string) - The event type (e.g., "track", "identify", "page")

### context (Object)

The function context provides access to various services:

**Logging**

- context.log.debug(message) - Debug-level logging
- context.log.info(message) - Info-level logging
- context.log.warn(message) - Warning-level logging
- context.log.error(message) - Error-level logging

**Storage**

- context.store - Persistent key-value storage that maintains data between function calls

**Network & External Services**

- context.fetch(url, options) - Standard Fetch API for making HTTP requests to external services

### Return Values

The function's return value controls what happens to the event:

- **Return an object** - The returned object becomes the new event sent to destinations
- **Retun an array of objects -&#x20;**&#x4F;nly the final function in a function chain can return an array of objects. Each object becomes a new event sent to destinations.
- **Return "drop"** - The event is filtered out and won't be forwarded to the downstream destination
- **Return nothing/undefined** - The original event (or its modified copy) is used

# Create a Function

To create a function, follow the steps below:&#x20;

1. Navigate to **CDP**.&#x20;
2. Navigate to the **Functions** tab.
3. Click the **"+ New Function"** button.
4. Click into the function name field at the top left and give your function a name. Click the ✔️ icon to save the function name.
5. Add your function code into the WYSIWYG editor.&#x20;
6. Click the **"Save"** button to save your function.

# Debugging Functions

Chord CDP comes with a debugger and editor that allows you to run a function on sample data.&#x20;

Within a function, you have access to **Logs** which will emit debug / information messages. Here, you can instrument your JavaScript to print out event contents, branching logic, and intermediate values to help trace what the function is doing.

Additionally, you can check the **Last Run Result** tab within a specific Function to see what the function returned - whether it dropped the event, modified it, or forwarded it. This tab will include logs, errors, or additional metadata about that run. You will be able to see exactly how the function behaved for the latest event and catch transform/filter mistakes.

# Edit Functions

1. Navigate to **CDP**.&#x20;
2. Navigate to the **Functions** tab.
3. Find the existing function you'd like to edit.&#x20;
4. Click the ✏️ icon under the **Actions** column next to the function you want to edit.

# Delete Functions

1. Navigate t&#x6F;**&#x20;CDP**.&#x20;
2. Navigate to the **Functions** tab.
3. Find the existing function you'd like to delete.&#x20;
4. Click on the three vertical dots under the **Actions** column next to the function you want to delete.
5. Click the 🗑️ icon to delete the desired function.

# Connecting / Environment Variables

Functions chains are created on connections between sources and destinations.

1. Navigate t&#x6F;**&#x20;CDP**.&#x20;
2. For a given destination, click the three dots next to the destination title then select **"View Connected Streams"**.
3. Select the ✏️ icon under **Actions** to edit the connection.
4. Functions can be added to the pipeline from the functions library. Functions will run in the order specified in this interface.
5. Click **"Save changes"**.

Environment variables are configured on a **per connection basis**. Define envrionment variable names and values for the connection below the function library in the **"Edit Connection"** drawer.

Environment variables may be referenced in code like so:

```javascript
process.env.VARIABLE_NAME
```

# Persistent Storage for Custom Functions

Persistent Storage allows functions to cache data between invocations, enabling use cases like rate limiting, deduplication, enrichment caching, and maintaining state across events.

When enabled, your CDP functions have access to a `store` object that provides a key-value cache with TTL (time-to-live) support. The store is backed by an in-memory cache and persists data across function invocations within the same workspace.

## Using the Store in Custom Functions

### Basic Operations

The store provides the following methods:

```javascript
// Get a value (returns undefined if not found or expired)
const value = await store.get("my-key");

// Set a value with default TTL (31 days)
await store.set("my-key", { foo: "bar" });

// Set a value with custom TTL
await store.set("my-key", { foo: "bar" }, "1h");      // 1 hour
await store.set("my-key", { foo: "bar" }, "7d");      // 7 days
await store.set("my-key", { foo: "bar" }, 3600);      // 3600 seconds
await store.set("my-key", { foo: "bar" }, "inf");     // No expiration

// Delete a value
await store.del("my-key");

// Get TTL remaining (in seconds)
const ttl = await store.ttl("my-key");
// Returns: positive number = seconds remaining, -1 = no expiry, -2 = key doesn't exist

// Get value with TTL info
const result = await store.getWithTTL("my-key");
// Returns: { value: any, ttl: number } or undefined
```

### Complete Example

```javascript
export default async function(event, { log, fetch, store }) {
  let key = "my-key";
  let ttl = "10s";

  // Retrieve the value from the cache
  let value = await store.get(key);

  // If the value was cached, display it.
  // Else, put the value in the cache that will expire in 10s.
  if (value) {
    log.info(`Cache hit! Value is ${value}`);
  } else {
    let newValue = Math.random().toString(36).substring(2)
    log.info(`Cache miss! Adding ${newValue} to cache; TTL ${ttl}.`);
    await store.set(key, newValue, ttl);
  }
}
```

## TTL Format

The TTL parameter accepts several formats:

| Format          | Example                         | Description             |
| --------------- | ------------------------------- | ----------------------- |
| Number          | `3600`                          | Seconds                 |
| Duration string | `"30s"`, `"5m"`, `"2h"`, `"7d"` | Human-readable duration |
| Infinite        | `"inf"`                         | No expiration           |
| Default         | (omitted)                       | 31 days                 |

Maximum TTL is approximately 68 years (2,147,483,647 seconds).

## Cache Availability and Graceful Degradation

The Persistent Storage feature is designed to be resilient. If the cache becomes temporarily unavailable:

- **Functions continue to work**: Your custom functions will not fail or be retried due to cache issues
- **Cache operations degrade gracefully**:
  - `get()` returns `undefined` (treated as a cache miss)
  - `set()` silently fails (data won't be cached)
  - `del()` silently fails (key may not be deleted)
  - `ttl()` returns `-2` (key doesn't exist)
- **Warnings are logged**: Cache failures are logged for monitoring, but don't interrupt event processing

This means your functions should be written to handle cache misses gracefully, as they may occur due to:

1. Key not existing (normal cache miss)
2. Key expired (TTL reached)
3. Cache temporarily unavailable (graceful degradation)

### Writing Resilient Functions

Always write your functions assuming the cache might be empty:

```javascript
// Good: Handles cache miss gracefully
const cached = await ctx.store.get("my-key");
if (!cached) {
  // Regenerate or fetch the data
  const freshData = await fetchData();
  await ctx.store.set("my-key", freshData, "1h");
  return processEvent(event, freshData);
}
return processEvent(event, cached);

// Avoid: Assuming cache always has data
const cached = await ctx.store.get("my-key");
return processEvent(event, cached.someProperty); // May fail if cached is undefined
```

### Key Naming and Uniqueness

**Important:** Storage keys are shared across all functions in your CDP workspace. This means:

- A key set by one function can be read, modified, or deleted by any other function in the same workspace
- You must ensure your keys are unique to avoid unintended collisions between functions

### Best Practices for Key Naming

**Use function-specific prefixes** to namespace your keys:

```javascript
// Good: Namespaced keys
await store.set("rate-limiter:user:123", count);
await store.set("enrichment-cache:company:456", data);

// Avoid: Generic keys that might collide
await store.set("user:123", count);
await store.set("cache:456", data);
```

**Use environment variables** when you have multiple versions of the same function (e.g., development and production):

```javascript
// Set FUNCTION_ENV as an environment variable: "dev" or "prod"
const keyPrefix = process.env.FUNCTION_ENV || "dev";

await store.set(`${keyPrefix}:rate-limit:${userId}`, count);
// Results in "dev:rate-limit:123" or "prod:rate-limit:123"
```

# Code Examples

### Basic Filter: Drop Events Without User IDs

```javascript
export default async function(event, context) {
  // Filter out any events that don't have a userId
  if (!event.userId) {
    context.log.debug(`Dropped event without userId: ${event.event}`);
    return "drop";
  }
  
  return event;
}
```

### Allowlist Filter: Only Forward Specific Events

```javascript
export default async function(event, context) {
  // Define which events should be forwarded
  const allowedEvents = [
    "Order Completed",
    "Product Viewed",
    "Cart Updated"
  ];
  
  if (!allowedEvents.includes(event.event)) {
    context.log.debug(`Dropped disallowed event: ${event.event}`);
    return "drop";
  }
  
  return event;
}
```

### External API Enrichment

```javascript
export default async function(event, context) {
  // Enrich user data with information from an external API
  // Uses environment variables for secure credential management
  try {
    const userId = event.userId;
    
    if (!userId) {
      return event; // Skip enrichment if no userId
    }
    
    // Access environment variables via context.props
    // These are configured in the Chord CDP UI for each function
    const apiKey = process.env.API_KEY;
    const apiBaseUrl = process.env.API_BASE_URL;
    
    if (!apiKey) {
      context.log.warn('API_KEY not configured, skipping enrichment');
      return event;
    }
    
    const response = await context.fetch(
      `${apiBaseUrl}/users/${userId}`,
      {
        headers: {
          'Authorization': `Bearer ${apiKey}`,
          'Content-Type': 'application/json'
        }
      }
    );
    
    if (response.ok) {
      const userData = await response.json();
      
      // Add enriched data to event properties
      event.properties.lifetimeValue = userData.ltv;
      context.log.info(`Enriched user ${userId} with external data`);
    } else {
      context.log.warn(`API returned ${response.status}: ${response.statusText}`);
    }
  } catch (error) {
    context.log.error(`Failed to enrich event: ${error.message}`);
    // Continue processing even if enrichment fails
  }
  
  return event;
}
```

## Support

For questions or assistance with CDP Functions, please contact [help@chord.co](mailto\:help@chord.co).
