# Rudder Transformations

> Creates and manages RudderStack transformations and libraries with local testing. Use when creating, editing, or managing RudderStack transformations and transformation libraries using the Rudder CLI

- **Type:** Skill
- **Install:** `agentstack add skill-rudderlabs-rudder-agent-skills-rudder-transformations`
- **Verified:** Pending review
- **Seller:** [rudderlabs](https://agentstack.voostack.com/s/rudderlabs)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [rudderlabs](https://github.com/rudderlabs)
- **Source:** https://github.com/rudderlabs/rudder-agent-skills/tree/main/plugins/rudder-cli/skills/rudder-transformations
- **Website:** https://www.rudderstack.com/

## Install

```sh
agentstack add skill-rudderlabs-rudder-agent-skills-rudder-transformations
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# RudderStack Transformations with Rudder CLI

## Overview

RudderStack transformations allow real-time event manipulation using JavaScript or Python. Libraries provide reusable code shared across transformations. Both are managed as code using YAML specs and the Rudder CLI.

## Recommended Workflow

Follow this loop for every change — authoring a new transformation or library, or editing existing code. Testing locally with fixtures is the key step that distinguishes transformations work from other CLI workflows.

```dot
digraph transformations_workflow {
    rankdir=TB;
    "rudder-cli workspace info" [shape=box];
    "Authenticated?" [shape=diamond];
    "rudder-cli auth login" [shape=box];
    "Edit YAML / JS / fixtures" [shape=box];
    "rudder-cli validate -l ./" [shape=box];
    "Validation errors?" [shape=diamond];
    "Fix errors" [shape=box];
    "rudder-cli transformations test --all -l ./" [shape=box];
    "Tests pass?" [shape=diamond];
    "Fix code or expected output" [shape=box];
    "rudder-cli apply --dry-run -l ./" [shape=box];
    "Diff matches intent?" [shape=diamond];
    "Adjust specs" [shape=box];
    "rudder-cli apply -l ./" [shape=box];
    "Done" [shape=doublecircle];

    "rudder-cli workspace info" -> "Authenticated?";
    "Authenticated?" -> "rudder-cli auth login" [label="no"];
    "rudder-cli auth login" -> "rudder-cli workspace info";
    "Authenticated?" -> "Edit YAML / JS / fixtures" [label="yes"];
    "Edit YAML / JS / fixtures" -> "rudder-cli validate -l ./";
    "rudder-cli validate -l ./" -> "Validation errors?";
    "Validation errors?" -> "Fix errors" [label="yes"];
    "Fix errors" -> "rudder-cli validate -l ./";
    "Validation errors?" -> "rudder-cli transformations test --all -l ./" [label="no"];
    "rudder-cli transformations test --all -l ./" -> "Tests pass?";
    "Tests pass?" -> "Fix code or expected output" [label="no"];
    "Fix code or expected output" -> "rudder-cli validate -l ./";
    "Tests pass?" -> "rudder-cli apply --dry-run -l ./" [label="yes"];
    "rudder-cli apply --dry-run -l ./" -> "Diff matches intent?";
    "Diff matches intent?" -> "Adjust specs" [label="no"];
    "Adjust specs" -> "rudder-cli validate -l ./";
    "Diff matches intent?" -> "rudder-cli apply -l ./" [label="yes"];
    "rudder-cli apply -l ./" -> "Done";
}
```

**Steps:**
1. **Verify auth** — `rudder-cli workspace info`; re-auth with `rudder-cli auth login` if needed.
2. **Validate** — `rudder-cli validate -l ./` catches YAML schema errors, missing files, camelCase `import_name` violations.
3. **Test locally** — `rudder-cli transformations test --all -l ./` runs each transformation against its `tests/input/*.json` fixtures and diffs against `tests/output/*.json`. Use `--modified` for faster iteration or pass a specific transformation id.
4. **Dry run** — `rudder-cli apply --dry-run -l ./` shows the diff that would be applied. Check for unexpected deletions.
5. **Apply** — `rudder-cli apply -l ./` publishes libraries and transformations atomically (correct order handled by the CLI).

For the broader validate → apply cycle that applies to all CLI-managed resources, see the `rudder-cli-workflow` skill. This skill specializes it with the local-test step.

### Worked example

A complete end-to-end project — library, transformation, fixtures, and README — lives at `examples/transformations-workflow/` in this repo. It includes a ported Base64 library (`base64-lib`), a sample transformation that uses it, and input/output test fixtures. Use it as a scaffold for new transformations or as a reference for `tests/` structure.

## Directory Structure

```
transformations/
  my-transformation.yaml         # Transformation spec
  my-library.yaml                # Library spec
  javascript/
    my-transformation.js         # JavaScript code
    my-library.js               # Library code
  python/
    my-transformation.py         # Python code
    my-library.py               # Library code
  tests/
    input/
      event1.json               # Test input events
    output/
      event1.json               # Expected outputs
```

## YAML Schemas

### Transformation Spec

```yaml
version: "rudder/v1"
kind: "transformation"
metadata:
  name: "transformations"
  # Optional: import section for linking to existing workspace resources
  import:
    workspaces:
      - workspace_id: "your-workspace-id"
        resources:
          - remote_id: "existing-transformation-id"
            urn: "transformation:my-transformation"
spec:
  id: "my-transformation"           # Unique identifier (used as external ID)
  name: "My Transformation"         # Human-readable name
  description: "Description"        # Optional
  language: "javascript"            # "javascript" or "python"
  file: "javascript/my-transformation.js"  # Path to code (relative to YAML)
  # OR inline:
  # code: |
  #   export function transformEvent(event) { return event; }
  tests:                            # Optional - local testing only
    - name: "basic-test"
      input: "./tests/input"        # Directory with JSON events
      output: "./tests/output"      # Directory with expected results
```

**Note:** The `metadata.import` section is auto-generated when importing existing resources from a workspace. It links local files to remote resources.

### Library Spec

```yaml
version: "rudder/v1"
kind: "transformation-library"
metadata:
  name: "transformation-libraries"
spec:
  id: "my-library"                  # Unique identifier
  name: "My Library"                # Human-readable name
  description: "Reusable utilities" # Optional
  language: "javascript"            # "javascript" or "python"
  import_name: "myLibrary"          # MUST be camelCase of name field!
  file: "javascript/my-library.js"  # Path to code
```

**IMPORTANT:** `import_name` must be the exact camelCase conversion of `name`:
- "My Library" → "myLibrary"
- "Base64 Library" → "base64Library"
- "URL Parser Utils" → "urlParserUtils"

## Code Patterns

### Transformation Code (JavaScript)

```javascript
// transformEvent is the entry point - REQUIRED
// metadata is a FUNCTION that takes the event and returns metadata
export function transformEvent(event, metadata) {
    // Modify event
    event.context = event.context || {};
    event.context.transformed = true;

    // Access metadata - call as function with event
    const meta = metadata(event);
    event.metadata = meta;

    // Return event (or null/undefined to drop the event)
    return event;
}
```

**Key points:**
- `metadata` is a function, not an object - call it as `metadata(event)`
- Return `null` or `undefined` to drop/filter out an event
- The function must be exported with `export`

### Library Code (JavaScript)

```javascript
// Export functions for use in transformations
export function encode(str) {
    // Implementation
    return encodedStr;
}

export function decode(str) {
    // Implementation
    return decodedStr;
}
```

### Importing Libraries in Transformations

```javascript
// Import using the library's import_name (camelCase of name)
// If library name is "My Library", import_name is "myLibrary"
import { encode, decode } from "myLibrary";

export function transformEvent(event, metadata) {
    event.properties.encoded = encode(event.properties.data);
    return event;
}
```

**Remember:** The import string must exactly match the library's `import_name` field.

## CLI Commands

| Command | Description |
|---------|-------------|
| `rudder-cli validate` | Validate YAML specs and code |
| `rudder-cli plan` | Show planned changes |
| `rudder-cli apply` | Apply changes to workspace |
| `rudder-cli transformations test ` | Test single transformation |
| `rudder-cli transformations test --all` | Test all transformations |
| `rudder-cli transformations test --modified` | Test only modified |
| `rudder-cli import` | Import existing from workspace |
| `rudder-cli export` | Export to YAML files |

## Workflow: Adding a New Library

1. **Create library YAML spec:**
   ```yaml
   version: "rudder/v1"
   kind: "transformation-library"
   metadata:
     name: "transformation-libraries"
   spec:
     id: "base64-lib"
     name: "Base64 Library"
     description: "Base64 encoding/decoding"
     language: "javascript"
     import_name: "base64Library"  # camelCase of "Base64 Library"
     file: "javascript/base64-lib.js"
   ```

2. **Create library code file** at `javascript/base64-lib.js`

3. **Validate:** `rudder-cli validate -l ./`

4. **Dry-run:** `rudder-cli apply --dry-run -l ./`

5. **Apply:** `rudder-cli apply -l ./`

**See `rudder-cli-workflow` skill for detailed iteration workflow.**

## Workflow: Using Library in Transformation

1. **Import the library** using its `import_name`:
   ```javascript
   import { encode } from "base64Library";
   ```

2. **Dependencies auto-detected:** The CLI parses imports and creates dependency graph

3. **Batch publish:** Libraries and transformations publish atomically

## Porting External Libraries

When porting npm/external libraries to RudderStack:

1. **Remove UMD/CommonJS wrappers** - Use ES modules only
2. **Remove Node.js-specific code** - No `Buffer`, `require()`, etc.
3. **Export functions directly** - Not as object methods
4. **Keep dependencies minimal** - RudderStack sandbox is limited
5. **Use polyfills for browser APIs** - `btoa`/`atob` may need polyfills
6. **Convert internal object methods to standalone functions**

### Example: Porting a Library

**Original (UMD with Buffer):**
```javascript
(function(global, factory) {
    typeof exports === 'object' ? module.exports = factory() : ...
})(this, function() {
    var _hasBuffer = typeof Buffer === 'function';
    var encode = _hasBuffer
        ? (s) => Buffer.from(s).toString('base64')
        : (s) => btoa(s);
    return { encode: encode };
});
```

**Ported (ES Module with polyfill):**
```javascript
// Polyfill for environments without btoa
const btoaPolyfill = (bin) => {
    const b64chs = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
    let asc = '';
    // ... pure JS implementation
    return asc;
};

const _btoa = typeof btoa === 'function' ? (s) => btoa(s) : btoaPolyfill;

// Export as standalone function
export function encode(s) {
    return _btoa(s);
}
```

### Porting Checklist

- [ ] Remove IIFE/UMD wrapper
- [ ] Remove `module.exports`, `exports`, `define()` calls
- [ ] Replace `require()` with `import`
- [ ] Replace `Buffer` usage with pure JS (Uint8Array, manual encoding)
- [ ] Add polyfills for `btoa`/`atob` if used
- [ ] Convert class methods to exported functions if needed
- [ ] Test with various input types (UTF-8, special chars, edge cases)

## Common Mistakes

| Mistake | Fix |
|---------|-----|
| Missing `import_name` in library | Add unique `import_name` to library spec |
| `import_name` not matching `name` | **MUST be camelCase of `name`** (e.g., "Base64 Library" → "base64Library") |
| Using `require()` in code | Use ES `import` syntax |
| Using Node.js `Buffer` | Use pure JavaScript implementations |
| Relative imports like `./mylib` | Use library's `import_name` |
| Forgetting to export functions | Add `export` keyword |
| Inline code AND file both set | Use one or the other, not both |

**Critical:** The `import_name` field is validated to be the exact camelCase conversion of the `name` field. This is enforced by `rudder-cli validate`.

## RudderStack Built-in Libraries

Access built-in libraries with `@rs/` prefix:

```javascript
import { get } from "@rs/lodash/v1";
import { v4 as uuidv4 } from "@rs/uuid/v1";
```

## Testing

Test files use JSON format:

**Input (`tests/input/event1.json`):**
```json
{
  "type": "track",
  "event": "Test Event",
  "properties": {
    "data": "hello"
  }
}
```

**Expected Output (`tests/output/event1.json`):**
```json
{
  "type": "track",
  "event": "Test Event",
  "properties": {
    "data": "hello",
    "encoded": "aGVsbG8="
  }
}
```

## Quick Reference

| Element | Location | Required Fields |
|---------|----------|-----------------|
| Transformation spec | `transformations/*.yaml` | id, name, language, file/code |
| Library spec | `transformations/*.yaml` | id, name, language, import_name, file/code |
| JS code | `transformations/javascript/*.js` | `export function transformEvent` |
| Python code | `transformations/python/*.py` | `def transform_event` |
| Test input | `tests/input/*.json` | Valid event JSON |
| Test output | `tests/output/*.json` | Expected result JSON |

## Publish Order

The CLI handles publish order automatically:
1. Workspace credentials (secrets)
2. Libraries (dependencies)
3. Transformations (depend on libraries)

All published atomically - no partial updates.

## Credential Security

- **Never hardcode API keys in transformation code** - use workspace credentials/secrets instead
- **Store workspace tokens in environment variables** - never commit `RUDDER_ACCESS_TOKEN` to git
- **Add `.env` to `.gitignore`** - if using dotenv files for local development
- **Use CI/CD secrets** - for automated deployments, use repository secrets
- **Reference secrets via workspace credentials** - transformation code can access configured secrets securely

## Handling External Content

When processing events in transformations:

- **Validate event structure** - check for expected fields before processing
- **Sanitize string inputs** - escape or validate user-generated content in event properties
- **Don't eval dynamic content** - never use `eval()` or `Function()` on event data
- **Extract only expected fields** - access known properties, don't iterate over unknown keys
- **Log safely** - avoid logging full event payloads that may contain PII

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [rudderlabs](https://github.com/rudderlabs)
- **Source:** [rudderlabs/rudder-agent-skills](https://github.com/rudderlabs/rudder-agent-skills)
- **License:** MIT
- **Homepage:** https://www.rudderstack.com/

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** yes

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: flagged — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-rudderlabs-rudder-agent-skills-rudder-transformations
- Seller: https://agentstack.voostack.com/s/rudderlabs
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
