Amplitude supports streaming transformed data to your destinations, including Custom Events, Derived Properties, Transformed Events, and Transformed Properties. You can select any transformation from your Amplitude taxonomy when configuring event streaming.

## Setup

1. In Amplitude Data, click **Catalog** and select the **Destinations** tab.
2. In the Event Streaming section, click on any streaming destination tile.
3. Enter a sync name, then click **Create Sync**.
4. Toggle *Status* from **Disabled** to **Enabled**.
5. Paste your destination's Server Secret Key.
6. Toggle **Send events** to enable event streaming.
7. In *Select and filter events*, choose which events to send. Select only the events needed for your downstream destination. The dropdown includes any transformed events from your taxonomy.
8. (*optional*) In *Select additional properties*, select any event properties (including transformed event properties) to include. By default, Amplitude doesn't send any additional properties unless you explicitly select them.
9. (*optional*) In *Select additional user properties*, select any user properties to include. By default, Amplitude doesn't send any additional user properties unless you explicitly select them.
10. When satisfied with your configuration, click **Save**.

## Example use cases

### Streaming renamed events to AppsFlyer

AppsFlyer requires unique event names for attribution and doesn't support event properties. Previously, you had to create custom events in your backend and resend them to Amplitude. You can now use Amplitude Data to rename events before streaming them to AppsFlyer, which reduces duplicate ingestion.

### Streaming derived properties to Braze

To improve campaign effectiveness, you can stream derived properties from Amplitude to Braze. Select derived properties in your sync filters and include them when configuring additional properties. This enables more targeted campaigns using enriched event data from Amplitude.

## Considerations

Note the following when streaming transformations from Amplitude:

* Amplitude sends selected event and user properties along with the event.
* Amplitude targets an end-to-end p95 latency of 60 seconds. Amplitude streams 95% of events within 60 seconds. Amplitude has internal processes, monitors, and alerts in place to meet this target.

## Transformation payload structure

The streaming payload includes transformations as nested JSON fields. Understanding this structure is essential when using custom FTL (FreeMarker Template Language) templates or configuring how your destination receives data.

### Selection requirement

You must explicitly select transformations in your sync configuration to include them in the streaming payload. You can select transformations in:

* *Select and filter events*: For example, filtering events where a derived property isn't `null`.
* *Select additional properties*: To include specific transformed properties.
* *Map properties to destination*: When mapping properties to your destination's schema (if applicable).

The streaming payload only includes transformations you explicitly select.

### JSON structure

The payload includes transformations as nested JSON objects. The top-level field name depends on the transformation type:

| Transformation Type | Top-level JSON Field Name |
|---------------------|---------------------------|
| Merged properties | N/A (replaced in original field) |
| Derived properties | `derived_properties` |
| Channel properties | `derived_properties` |
| Lookup properties | `lookup_properties` |

Field names within these objects match the transformation names displayed in the Amplitude UI.

### Example payload

If you select a derived property called `sample_derived_property_key1`, the streaming payload looks like this:

```json
{
  "event_type": "Button Clicked",
  "user_id": "12345",
  "derived_properties": {
    "sample_derived_property_key1": "whatever_value"
  }
}
```

### Using transformations with custom FTL

If your destination uses custom FTL templates, you can access transformation data using these patterns.

**Example 1: Using FtlUtils to serialize derived properties**

```ftl
<#assign UtilClass=statics['com.amplitude.integrations.connector.utils.FtlUtils']>
{
  "version": "derived_properties_sample_ftl1",
  "derived_properties": ${UtilClass.toJson(input.derived_properties)}
}
```

**Example 2: Manually iterating over derived properties**

```ftl
<#assign UtilClass=statics['com.amplitude.integrations.connector.utils.FtlUtils']>
{
  "version": "derived_properties_sample_ftl2",
  "derived_properties": {
    <#list input.derived_properties?keys as key>
      "${key}": "${input.derived_properties[key]}"<#sep>,</#sep>
    </#list>
  }
}
```

## Customize payloads with FreeMarker (FTL)

Event streaming destinations that expose a custom payload editor use [Apache FreeMarker](https://freemarker.apache.org/) (FTL) to turn the Amplitude event into the JSON body your destination expects. The editor in the connector builder provides autocomplete, hover documentation, and inline validation for the fields and helpers below. For the FreeMarker language itself, refer to the [guide to creating templates](https://freemarker.apache.org/docs/dgui.html).

### The input object

`input` is the event (or user) Amplitude forwards. Access its fields with dot notation, wrapped in `${ }` to output the value:

```ftl
"${input.user_id}"
"${input.event_type}"
"${input.event_time}"
```

Commonly available fields include `event_type`, `user_id`, `device_id`, `time`, `event_time`, `session_id`, `platform`, `event_properties`, `user_properties`, `group_properties`, and `groups`, plus any transformations you select (`derived_properties`, `lookup_properties`). For the complete shapes, refer to the [event format](/docs/apis/analytics/export) and [user (Identify) format](/docs/apis/analytics/identify).

{% callout type="warning" title="Fields aren't guaranteed" %}
`input` fields depend on how each user instruments events, and a selected event property might not be present on every event you stream. Reference fields defensively (refer to [Handle missing fields](#handle-missing-fields)), and keep required-field usage to common fields like `user_id`, `event_type`, and `event_time`. The payload editor flags a referenced property that isn't present on all of the events you selected.
{% /callout %}

### Dot and bracket notation

Use dot notation when a property name is a valid identifier, and bracket notation when the name contains spaces or other special characters:

```ftl
${input.event_properties.plan}
${input.event_properties["Plan Type"]}
```

### Handle missing fields

Because fields aren't guaranteed, guard against missing values:

* `??` tests whether a value exists: `<#if input.user_id??>…</#if>`.
* `!` supplies a default when a value is missing: `${input.user_properties.email!}` (empty string) or `${input.revenue!0}`.

```ftl
{
  <#if input.user_id??>
  "external_id": "${input.user_id}",
  </#if>
  "email": "${input.user_properties.email!}"
}
```

### Useful built-ins

FreeMarker built-ins (`?name`) transform values. Common ones for JSON payloads:

| Built-in | Use |
|----------|-----|
| `?c` | Format a number for computers, with no locale grouping or scientific notation. Use for numeric JSON values: `${input.revenue?c}` |
| `?json_string` | Escape a string for safe use inside a JSON string: `"${value?json_string}"` |
| `?keys` | Get an object's keys to iterate with `<#list>` |
| `?is_number`, `?is_boolean`, `?is_string` | Test a value's type before serializing it |

### Iterate over properties

Use `<#list>` with `?keys`, and `<#sep>` to place commas between items only:

```ftl
"user_properties": {
  <#list input.user_properties?keys as key>
    "${key}": ${UtilClass.toJsonString(input.user_properties[key])}<#sep>,</#sep>
  </#list>
}
```

### Helper methods (FtlUtils)

Amplitude exposes helper methods for serializing values and other common transforms, so you don't have to hand-write the logic. Assign the helper once at the top of the template, then call its methods:

```ftl
<#assign UtilClass=statics['com.amplitude.integrations.connector.utils.FtlUtils']>
{
  "user_properties": ${UtilClass.toJson(input.user_properties)}
}
```

| Method | Returns | Description |
|--------|---------|-------------|
| `toJson(value)` | string | Serializes a value to JSON. Maps, lists, and JSON values become JSON; scalars become a quoted JSON string (for example, `42` becomes `"42"`). |
| `toJsonString(value)` | string | Alias of `toJson`. |
| `dateStringToIso(dateTime)` | string | Converts an Amplitude datetime string to ISO 8601. |
| `dateStringToEpochSeconds(dateTime)` | number | Converts an Amplitude datetime string to epoch seconds. |
| `dateStringToEpochMillis(dateTime)` | number | Converts an Amplitude datetime string to epoch milliseconds. |
| `epochSecondsToIso(seconds)` | string | Converts epoch seconds to ISO 8601. |
| `epochSecondsTextToIso(text)` | string | Converts epoch seconds provided as text to ISO 8601. |
| `epochMillisToIso(millis)` | string | Converts epoch milliseconds to ISO 8601. |
| `isValidEmail(value)` | boolean | Returns whether the value is a valid email address. |
| `sha256(value)` | string | Returns the SHA-256 hash of the value. If the value is already a 64-character hash, returns it unchanged. |
| `cleanJsonPropertyName(name)` | string | Replaces whitespace with underscores and removes other non-alphanumeric characters. |
| `getDisjointMap(source, filter)` | map | Returns the entries of `source` whose keys aren't present in `filter`. |

{% callout type="note" title="Destination-specific helpers" %}
Some destinations provide additional helper methods tailored to that destination. You can use those helpers only in that destination's template.
{% /callout %}

### Examples

Each example shows the **input** event Amplitude forwards, the **template** you write, and the **result** Amplitude sends to your destination.

#### Simple: rename and pick fields

Map a few input fields into the shape your destination expects.

Input event:

```json
{
  "event_type": "Song Played",
  "user_id": "user-123",
  "event_time": "2024-01-15T09:30:00.000",
  "event_properties": {
    "song_id": "abc-987"
  }
}
```

Template:

```ftl
{
  "external_id": "${input.user_id}",
  "name": "${input.event_type}",
  "song": "${input.event_properties.song_id}"
}
```

Result:

```json
{
  "external_id": "user-123",
  "name": "Song Played",
  "song": "abc-987"
}
```

#### Complex: guards, bracket notation, and iteration

Guard an optional field with `??`, read a property whose name has a space with bracket notation, format a number with `?c`, and serialize all user properties by looping with `<#list>`, emitting numbers and booleans raw and everything else through `toJsonString`.

Input event:

```json
{
  "event_type": "Purchase Completed",
  "user_id": "user-123",
  "revenue": 12.5,
  "event_properties": {
    "Item Name": "Pro Plan"
  },
  "user_properties": {
    "email": "alex@example.com",
    "plan": "pro",
    "age": 30
  }
}
```

Template:

```ftl
<#assign UtilClass=statics['com.amplitude.integrations.connector.utils.FtlUtils']>
{
  "event": "${input.event_type}",
  <#if input.user_id??>
  "user_id": "${input.user_id}",
  </#if>
  "item": "${input.event_properties["Item Name"]}",
  "revenue": ${input.revenue?c},
  "traits": {
    <#list input.user_properties?keys as key>
    <#assign value = input.user_properties[key]>
    "${key}": <#if value?is_number || value?is_boolean>${value}<#else>${UtilClass.toJsonString(value)}</#if><#sep>,</#sep>
    </#list>
  }
}
```

Result:

```json
{
  "event": "Purchase Completed",
  "user_id": "user-123",
  "item": "Pro Plan",
  "revenue": 12.5,
  "traits": {
    "email": "alex@example.com",
    "plan": "pro",
    "age": 30
  }
}
```

## Supported custom events

Amplitude supports streaming custom events that meet specific criteria. When you create custom events in the Amplitude taxonomy, you can select them for event streaming if they have:

* **Supported properties:** User properties and event properties only.
* **Supported operators:** `is`, `is not`, `contains`, and `does not contain`.

Custom events that use other property types or operators aren't available for selection in event streaming configurations.

## Limitations

Streaming transformations from Amplitude has some limitations:

* When you rename custom events or derived properties, update any existing sync configurations that reference them. Syncs require current event and property names to work properly. Note that changing the underlying definition of a transformation doesn't affect syncing. Only name changes require sync updates.
* **Lookup properties**: You can stream lookup properties by requesting access from Amplitude. Lookup properties map existing event or user properties to new properties using a CSV file upload and can enrich already-ingested events at query time.
  * Lookup property files with over 1000 rows don't display in the streaming setup.
  * After you save a lookup property file, it can take up to one hour to populate into the streaming system.
* **Channel classifiers**: To stream channel classifiers, request access from Amplitude. Channels act like derived properties applied in real time during queries. Marketers primarily use channels to define acquisition channels based on UTM and referrer data. By default, you can't select channel properties in sync configuration (event filters or additional properties) unless Amplitude has already enabled this feature for your organization.
* You can stream transformations to all streaming destinations except Data Warehouse destinations.
* The streaming setup doesn't support the following transformation types:
  * Custom events that don't meet the criteria in the [Supported custom events](#supported-custom-events) section.
  * Group properties.
  * Cart properties.
  * Nested properties (for example, derived properties that rely on other derived properties). Exception: the UI allows selecting nested properties based on merged or cart properties, but they don't work in practice.

## FAQ


{% accordion title="Will this impact my event volume streaming limit?" %}
Yes, streaming transformations count towards your existing event streaming volume limit. Check your event streaming limit by navigating to *Settings > Plans & Billing*.
{% /accordion %}


{% accordion title="Can I select both raw and transformed events/properties?" %}
Yes, you can select both raw and transformed versions in your streaming sync. For example, if a transformation merges three event types into one transformed event, all four event types (the three originals plus the merged version) appear in the selection dropdown.
{% /accordion %}


{% accordion title="How does Amplitude handle custom events and transformed properties during streaming?" %}
Custom events and transformed properties follow the configurations set in your Amplitude Data taxonomy. Amplitude applies transformations before it streams the data to the destination.
{% /accordion %}


{% accordion title="How can I enable channel classifiers for my event stream?" %}
Amplitude can enable channel classifier selection in event streaming sync configurations on request. Email integrations@amplitude.com with your organization ID and app IDs to request access.
{% /accordion %}
