# Use Transformations in Cloud, Device, and Hybrid Mode

This guide explains how to use transformations with destinations connected in different RudderStack [connection modes]({{< ref "destinations/rudderstack-connection-modes.md" >}}).

## Transform a single event {#apply-transformation-on-single-event}

While using a transformation, RudderStack applies the `transformEvent` function on each event that takes the following two arguments:

| Argument | Description |
| :----------| :-----------|
| `event`     | The input event. |
| `metadata` | The function to access the event's metadata. See [Access event metadata](#access-event-metadata) for more information. |

After the transformation is complete, the `transformEvent` function returns the final event to be sent to the destination.

{{< warning >}}
Make sure your transformation logic adheres to the memory and time limits mentioned in the [Limitations](#limitations) section below.
{{< /warning >}}

### Access event metadata

The `metadata` function is passed as the second argument to `transformEvent` and `transformBatch` — you can use it to access event metadata such as `sourceId`, `destinationId`, `messageId`, and connection mode.

See [Runtime Functions in Transformations]({{< ref "transformations/runtime-functions.md" >}}) for more information on using the `metadata` function.

### Make external API requests

You can make external API requests in your transformation functions and use the fetched responses to enrich your events.

#### JavaScript

JavaScript transformations use the [`fetch`]({{< ref "transformations/runtime-functions.md#fetch" >}}) or [`fetchV2`]({{< ref "transformations/runtime-functions.md#fetchv2" >}}) functions to make these requests — see See [Runtime Functions in Transformations]({{< ref "transformations/runtime-functions.md" >}}) for more information.

{{< warning >}}
For JavaScript transformations, RudderStack imposes a strict [4 second execution timeout limit](#limitations). This limit **does not include** the time it takes for the `fetch` or `fetchV2` calls to complete. RudderStack recommends using [batch requests](#apply-transformation-on-batch-of-events) instead of a separate request per event when possible.
{{< /warning >}}

#### Python

Python transformations use the `requests` package to fetch response properties while making the external API calls:

```python
import requests

def transformEvent(event, metadata):
    res = requests.get("url")
    if res.status_code == 200:
        event["response"] = res.json();
    return event
```

#### IP allowlisting {#ip-allowlisting}

Depending on your use case and the API endpoints you are trying to access via `fetch` or `fetchV2` calls, you will need to allowlist certain RudderStack IP addresses.

{{< customreadfile "/includes/transformations-iplist.md" >}}

## Transform batch of events {#apply-transformation-on-batch-of-events}

You can perform any aggregation or roll-up operation on a batch of events using the `transformBatch` function instead of [`transformEvent`](#apply-transformation-on-single-event) function:

{{< tabs tabTotal="2" >}}
{{% tab tabName="JavaScript" %}}

```javascript
export function transformBatch(events, metadata) {
    return events;
}
```

{{% /tab %}}
{{% tab tabName="Python" %}}

```python
def transformBatch(events, metadata):
    return events
```

{{% /tab %}}
{{< /tabs >}}


{{< danger >}}
To ensure event ordering when using the `transformBatch` function, make sure you pass the `messageId` from the input event to the output event. Without the `messageId`, RudderStack **does not** guarantee event ordering.

**It is highly recommended to use `transformEvent` as much as possible, as it ensures event ordering.**
{{< /danger >}}

## Cloud mode

When you [add a transformation]({{< ref "transformations/create.md#adding-a-transformation" >}}) and connect it to a destination in cloud mode, RudderStack does the following:

1. Tracks and collects events at the source.
2. Applies the user transformation logic to your events.
3. Transforms the events in the destination-specific format. This is done [internally]({{< ref "get-started/rudderstack-open-source/rudderstack-architecture.md#transformation-module" >}}) and requires no user intervention.
4. Forwards the transformed events to your destination.

{{< image src="images/features/transformations-workflow-new.webp" alt="Transformations workflow" >}}

### Connect cloud mode destination

{{< info >}}
You can connect only one transformation to a destination. However, one transformation can be used by multiple destinations.
{{< /info >}}

There are two ways to connect a transformation to a destination in [cloud mode]({{< ref "destinations/rudderstack-connection-modes.md#cloud-mode" >}}):

#### From transformation

1. Click the **Connections** tab of your transformation and click **Connect Destination**. You will see a list of **all** the destinations and the transformations connected to them.
2. Scroll to the destination you want to connect to the transformation and click **Connect**. 

{{< image src="images/features/transformations/adding-transformation-destination.webp" alt="Connecting a transformation to a destination" >}}

3. Enable the **Connect to transformation** toggle and click **Save**.

{{< image src="images/features/transformations/connect-disconnect-transformation.webp" alt="Enable transformation" >}}

#### From destination

1. Go to the destination in the dashboard.  Click the **Transformation** tab and click **Add a transformation**:

{{< image src="images/features/transformations/adding-transformation-existing-destination-1.webp" alt="Connecting a transformation to existing destination" >}}

2. Select the transformation to connect to the destination and click **Choose**.

{{< image src="images/features/transformations/adding-transformation-existing-destination-2.webp" alt="Connecting a transformation to existing destination" >}}

## Device mode

{{< info >}}
Device mode transformations is a beta feature and available for the [Enterprise](https://www.rudderstack.com/enterprise-quote/) plan users only.
{{< /info >}}

You can use transformations with destinations supporting [device mode]({{< ref "destinations/rudderstack-connection-modes.md#device-mode" >}}), like Firebase, Hotjar, and so on.

Device mode transformations leverage our Client Transformation service to transform events, then send the transformed events directly to the device mode destination through their native SDK. 

{{< image src="images/features/device-mode-transformations.webp" alt="Transformations workflow" >}}

Learn more about the architecture in the [Client Transformations Service architecture](#client-transformation-service-architecture) section below. 

{{< info >}}
**Important considerations**

- The following SDKs support device mode transformations (DMT):

  - [JavaScript]({{< ref "sources/event-streams/sdks/rudderstack-javascript-sdk/_index.md" >}})
  - [Android (Java)]({{< ref "sources/event-streams/sdks/rudderstack-android-sdk/" >}})
  - [iOS (Obj-C)]({{< ref "sources/event-streams/sdks/rudderstack-ios-sdk/_index.md" >}})
- The [Android (Kotlin)]({{< ref "sources/event-streams/sdks/kotlin-sdk/" >}}) and [iOS (Swift)]({{< ref "sources/event-streams/sdks/swift-sdk/" >}}) SDKs **do not support** DMT.
- You can write device mode transformations only in JavaScript — Python is not supported currently.
- Device mode transformations must adhere to the [RudderStack message schema]({{< ref "event-spec/standard-events/common-fields.md" >}}). Otherwise, the event will be dropped.
{{< /info >}}

### Connect device mode destination

You can enable device mode transformations only from the **Transformations** tab.

#### Prerequisites

Before you set up a device mode transformation:

- Choose the connection mode as **Device mode** while configuring your destination.

{{< figure src="images/features/device-mode-setting.webp" alt="Device mode option" >}}

- In some cases, setting up a device mode destination involves enabling the **Use device mode to send events** toggle while configuring the destination.

{{< figure src="images/features/device-mode-toggle.webp" alt="Device mode toggle" >}}

- You can also change the connection mode after the destination is set up. Go to the destination's **Configuration** tab and click **Edit configuration**. 

{{< figure src="images/features/destination-edit-configuration.webp" alt="Edit configuration" >}}

- Some destinations support only device mode. See the [destination-specific documentation]({{< ref "destinations/streaming-destinations/_index.md" >}}) for the supported connection modes.

#### Connect transformation to device mode destination

1. [Write the transformation]({{< ref "transformations/create.md#adding-a-transformation" >}}) to connect to your device mode destination.
2. Set up your device mode destination in RudderStack.
3. In the [RudderStack dashboard](https://app.rudderstack.com), go to **Collect** > **Transformations** in the left sidebar. Then, select the transformation to connect to this destination.
4. Click the **Connections** tab. If you don't have any destinations connected to it, click **Connect Destination**. If the transformation already has connected destinations, click **Manage destinations**.

{{< image src="images/features/transformations/manage-transformations-1.webp" alt="Manage destinations button" >}}

Depending on whether the destination you set up (in **Step 1**) is already connected to some transformation, there are two ways to add a device mode transformation to it:

- If your destination is **not connected** to any transformation click **Connect**. You will see a fly window with these options:

{{< image src="images/features/device-mode-transformation-1.webp" alt="Device mode transformation options" >}}

- If your destination is **already connected** to some other transformation, click the **Edit connections** button to switch your transformation. See [Switch transformation]({{< ref "transformations/manage.md#switch-transformation" >}}) for more information. Once you switch the transformation, you will see the above options to enable the device mode transformation.

5. Configure the following settings under **Device Mode**:

  - Enable the **Connect to transformation** toggle.
  - Enable the **Propagate errors** toggle depending on your requirement.

{{< warning >}}
Note that:

- Under **Cloud Mode**, the **Connect to transformation** toggle is enabled by default. **Do not** disable this toggle as it will not allow you to configure any device mode transformation settings.
- If **Propagate errors** is enabled, RudderStack sends the events to the destination without transforming the events, for any transformation errors. This is helpful in preventing data loss in case of transformation code or runtime errors.
- If your transformation involves hashing PII, enabling this setting will send the untransformed event data in case of any transformation errors.
{{< /warning >}}

6. Click **Save** to confirm the settings and for the changes to take effect.

To confirm that the device mode transformation is connected, go to the **Connections** tab of your transformation and check the **Connection Mode** column:

{{< image src="images/features/device-mode-transformation-2.webp" alt="Confirm device mode transformation connection" >}}

### SDK setup

After [adding a transformation]({{< ref "transformations/create.md#adding-a-transformation" >}}) and [connecting it to a device mode destination](#connect-device-mode-destination), you can connect your SDK source to it.

Follow the below steps for setting up the required SDK to use device mode transformations:

{{< tabs tabTotal="3" >}}
{{% tab tabName="JavaScript" %}}

The detailed steps to set up the JavaScript SDK are mentioned in the [Quickstart guide]({{< ref "/sources/event-streams/sdks/rudderstack-javascript-sdk/quickstart.md" >}}).

{{< info >}}
The JavaScript SDK loads the `DeviceModeTransformation` plugin by default if there is at least one device mode destination connected to the transformation.

However, if you are explicitly [specifying the plugins]({{< ref "sources/event-streams/sdks/rudderstack-javascript-sdk/load-js-sdk.md#plugins" >}}) while [loading the SDK]({{< ref "sources/event-streams/sdks/rudderstack-javascript-sdk/load-js-sdk.md#loading-options" >}}), make sure to include the `DeviceModeTransformation` plugin in the list. Otherwise, device mode transformations will not work correctly.
{{< /info >}}

The following example shows how to load the `DeviceModeTransformation` plugin:

```javascript
rudderanalytics.load(WRITE_KEY, DATA_PLANE_URL, {
  plugins: ["DeviceModeTransformation", 
            "DeviceModeDestinations", 
            "NativeDestinationQueue",
            "XhrQueue"
            // ... other plugins
           ]
});
```

{{% /tab %}}
{{% tab tabName="Android (Java) — Legacy" %}}

Install {{< legacy-tooltip "android-java" >}}  following the detailed steps mentioned in the [documentation]({{< ref "/sources/event-streams/sdks/rudderstack-android-sdk/_index.md#installing-the-sdk" >}}) section. Then, use the following dependency in the Android app level `build.gradle`:

```groovy
implementation 'com.rudderstack.android.sdk:core:1.16.0'
```

{{% /tab %}}
{{% tab tabName="iOS (Obj-C) — Legacy" %}}

{{< warning >}}
The iOS SDK v2 (Legacy) **does not support** device mode transformations. Use the iOS (Obj-C) SDK (stable version) instead.
{{< /warning >}}

Install the {{< legacy-tooltip "ios-objc" >}} following the detailed steps mentioned in [documentation]({{< ref "sources/event-streams/sdks/rudderstack-ios-sdk/_index.md#installing-the-rudderstack-ios-sdk" >}}) section. Then, use the following dependency in `Podfile`:

```ruby
pod 'Rudder', '1.17.0'
```

{{% /tab %}}
{{< /tabs >}}

### Tokenization

RudderStack provides the tokenization feature in device mode transformations to add an extra layer of security. With this feature, you can validate the requests using the token that you set in the client-side SDK, thereby ensuring the event request is valid and not made by any bad actor.

{{< info >}}
Note that:

- The token generation and its management is up to the user. RudderStack recommends rotating these tokens frequently to avoid any misuse.
- The token data is included in the request **only if** you explicitly set it in the client-side SDKs.
{{< /info >}}

#### Use case

Suppose you have a transformation that enriches the event data with additional data by making an external API call. In such cases, you would want to ensure that the request is not made by any bad actor to take undue advantage of those resources. 

You can use the tokenization feature to [check](#access-token) if the token is present in the event metadata, as it can only be available otherwise in the event requests coming from the client-side SDK (where you have set the token).

#### Access token

The token is available as a metadata in the transformation. A sample code to access the token in the transformation is shown:

```javascript
export function transformEvent(event, metadata) {
  const dmtToken = metadata(event)["Custom-Authorization"];
  //verify the token
  ...
  return event;
}
```

#### Attach token to SDK

You can use the following methods to attach the token (in the string format) to the SDK:

{{< tabs tabTotal="3" >}}
{{% tab tabName="JavaScript" %}}

```javascript
rudderanalytics.setAuthToken("my-token");
```

{{% /tab %}}
{{% tab tabName="Android (Java) — Legacy" %}}

```java
RudderClient.putAuthToken("my-token")
```

{{% /tab %}}
{{% tab tabName="iOS (Obj-C) — Legacy" %}}

```objectivec
[RSClient putAuthToken:@"my-token"];
```

{{% /tab %}}
{{< /tabs >}}

You can call these methods multiple times. 

#### Clear token

RudderStack automatically clears the token when the user logs out, that is, when the `reset()` API is called. 

This is because the `reset()` API clears all the data persisted in the SDK, including the token.

### Client Transformation service architecture

RudderStack provides the Client Transformation service to facilitate transformations for device mode destinations. It ensures that the event ingestion is unaffected, ensuring minimum response time from the RudderStack backend.

{{< image src="images/features/device-mode-transformations-workflow-new.webp" alt="Transformations workflow" >}}

#### Workflow

When you add a transformation and connect it to a device mode destination supporting the [device mode]({{< ref "/destinations/rudderstack-connection-modes.md#device-mode" >}}):

1. RudderStack SDK sends the event to the Client Transformation service. It multiplexes the event for different destinations and connects with the transformations attached to these device mode destinations.
2. Client Transformation service sends the event to the RudderStack Transformation service to transform the event according to the functions defined in the attached transformations.
3. After the transformation is applied, the transformed event is returned to the Client Transformation service.
4. Client Transformation service responds to the SDK with the transformed events for all the destinations.
5. Finally, the SDK parses the transformed events and forwards them to the specified device-mode destinations.

{{< info >}}
If the request to the Client Transformation service fails, the RudderStack SDK makes three retry attempts. If it fails, the SDK forwards the untransformed events to the destination or drops them - based on the **Propagate errors** toggle while [configuring the device mode transformation](#connect-device-mode-destination) (see Step 6).
{{< /info >}}

### Debugging

Check the **Network** tab in the browser's developer tools for any `transform` network requests to debug errors in your device mode transformations. These requests can help you determine if:

- The transformation has run as expected.
- The responses are as expected, based on the sent batch or otherwise.

{{< image src="images/features/transformations/dmt-debugging.webp" alt="Debugging device mode transformations" >}}

### Performance considerations

RudderStack makes a network call for each event (in case of JavaScript SDK) or a batch of events (in case of mobile SDKs) when you use transformations with device mode destinations. Hence, there can be an added performance cost.

## Hybrid mode

While using transformations with destinations connected in [hybrid mode]({{< ref "destinations/rudderstack-connection-modes.md#hybrid-mode" >}}) like [GA4]({{< ref "destinations/streaming-destinations/google-analytics-4/_index.md" >}}), [Braze]({{< ref "destinations/streaming-destinations/braze/_index.md" >}}), [Leanplum]({{< ref "destinations/streaming-destinations/leanplum.md" >}}), or [Rockerbox]({{< ref "destinations/streaming-destinations/rockerbox/_index.md" >}}), you can choose to transform: 

- **Events sent in both cloud and device mode**: By default, the transformation is applied to both the cloud and device mode events.
- **Only device mode events**: Access the `mode` field in your transformation's [event metadata]({{< ref "transformations/usage.md#access-event-metadata" >}}) and check if its value is `deviceMode`. Then, specify the transformation logic to be applied to the device mode events:

```javascript
export function transformEvent(event, metadata) {

  const mode = metadata(event)["mode"];
  if(mode === "deviceMode") {
      //some transformation
      return event;
    }

    return event;
}
```

- **Only cloud mode events**: Return the device mode events as it is using the `mode` field and specify the transformation logic for the rest of the events (cloud mode events), as shown:

```javascript
export function transformEvent(event, metadata) {

  const mode = metadata(event)["mode"];
  if(mode === "deviceMode") {
      return event;
    }

    ... //some transformation
    return event;
}
```

## Limitations

A transformation connected in either [cloud mode]({{< ref "/destinations/rudderstack-connection-modes.md#cloud-mode" >}}) or [device mode]({{< ref "/destinations/rudderstack-connection-modes.md#device-mode" >}}) must follow the below memory and time limits. It fails if these limits are exceeded:

| Parameter    | Limit     |
| :----------- | :-------- |
| Memory limit | 128 MB      |
| Execution time limit   | 4 seconds |

{{< warning >}}
For JavaScript transformations, the execution timeout limit of 4 seconds **does not include** the time taken for any [`fetch`]({{< ref "transformations/runtime-functions.md#fetch" >}}) or [`fetchV2`]({{< ref "transformations/runtime-functions.md#fetchv2" >}}) call to be completed.

RudderStack recommends ensuring that your transformation code and any external API calls that you make are as performant as possible. If these take too long to execute, your pipelines could be impacted and events can start to pile up.
{{< /warning >}}

{{< warning >}}
JavaScript transformations run in an isolated environment — no Node.js modules (like `fs`, `require`) or browser APIs (like `window`, `btoa`) are available. Only standard JavaScript features are supported.
{{< /warning >}}

The following limitations are applicable when invoking a transformation in [device mode]({{< ref "/destinations/rudderstack-connection-modes.md#device-mode" >}}):

- The native destination SDKs might persist some captured data, mainly identifiers and traits — you cannot transform these fields using device mode transformations.
- Network unavailability can lead to higher latency than expected while sending the events to the destinations. RudderStack doesn't lose data due to the network loss and stores the events in the client device until they are successfully delivered to the destination.
- The {{< legacy-tooltip "ios-objc" >}} does not support background processing of an event when the app is closed. However, it sends the pending events the next time the app is opened.

<br />
