---
title: transformations
url: /docs/iac/concepts/resources/options/transformations/
---
The `transformations` resource option provides a list of transformations to apply to a resource and all of its children. This option is used to override or modify the inputs to the child resources of a component resource. One example is to use the option to add other resource options (such as `ignoreChanges` or `protect`). Another example is to modify an input property (such as adding to tags or changing a property that is not directly configurable).

Each transformation is a callback that gets invoked by the Pulumi runtime. It receives the resource type, name, input properties, resource options, and the resource instance object itself. The callback returns a new set of resource input properties and resource options that will be used to construct the resource instead of the original values.

> **Warning:** Note that Transformations will be deprecated in the future in favor of [Transforms](/docs/concepts/options/transforms).

Transforms support modifying child resources of packaged components (such as those in [awsx](/registry/packages/awsx) and [eks](/registry/packages/eks)) whereas Transformations do not.

See [Migrating from Transformations to Transforms](#migrating-from-transformations-to-transforms) below for guidance on how to migrate from Transformations to Transforms.

The following example defines a `MyVpcComponent` component resource that creates an AWS VPC and subnet as child resources, then applies a transformation to ignore tag changes on those resources.

First, define the component resource:

<!-- chooser: language -->

<!-- option: typescript -->
```typescript
class MyVpcComponent extends pulumi.ComponentResource {
    constructor(name: string, opts?: pulumi.ComponentResourceOptions) {
        super("custom:index:MyVpcComponent", name, {}, opts);

        const vpc = new aws.ec2.Vpc(`${name}-vpc`, {
            cidrBlock: "10.0.0.0/16",
        }, { parent: this });

        const subnet = new aws.ec2.Subnet(`${name}-subnet`, {
            vpcId: vpc.id,
            cidrBlock: "10.0.1.0/24",
        }, { parent: this });

        this.registerOutputs({});
    }
}

```

<!-- /option -->

<!-- option: python -->
```python
class MyVpcComponent(pulumi.ComponentResource):
    def __init__(self, name: str, opts: pulumi.ResourceOptions | None = None):
        super().__init__("custom:index:MyVpcComponent", name, {}, opts)

        vpc = aws.ec2.Vpc(f"{name}-vpc",
            cidr_block="10.0.0.0/16",
            opts=pulumi.ResourceOptions(parent=self))

        aws.ec2.Subnet(f"{name}-subnet",
            vpc_id=vpc.id,
            cidr_block="10.0.1.0/24",
            opts=pulumi.ResourceOptions(parent=self))

        self.register_outputs({})

```

<!-- /option -->

<!-- option: go -->
```go
type MyVpcComponent struct {
    pulumi.ResourceState
}

func NewMyVpcComponent(ctx *pulumi.Context, name string, opts ...pulumi.ResourceOption) (*MyVpcComponent, error) {
    component := &MyVpcComponent{}
    err := ctx.RegisterComponentResource("custom:index:MyVpcComponent", name, component, opts...)
    if err != nil {
        return nil, err
    }

    vpc, err := ec2.NewVpc(ctx, name+"-vpc", &ec2.VpcArgs{
        CidrBlock: pulumi.String("10.0.0.0/16"),
    }, pulumi.Parent(component))
    if err != nil {
        return nil, err
    }

    _, err = ec2.NewSubnet(ctx, name+"-subnet", &ec2.SubnetArgs{
        VpcId:     vpc.ID(),
        CidrBlock: pulumi.String("10.0.1.0/24"),
    }, pulumi.Parent(component))
    if err != nil {
        return nil, err
    }

    if err := ctx.RegisterResourceOutputs(component, pulumi.Map{}); err != nil {
        return nil, err
    }

    return component, nil
}

```

<!-- /option -->

<!-- option: csharp -->
```csharp
public class MyVpcComponent : Pulumi.ComponentResource
{
    public MyVpcComponent(string name, ComponentResourceOptions? opts = null)
        : base("custom:index:MyVpcComponent", name, opts)
    {
        var vpc = new Vpc($"{name}-vpc", new VpcArgs
        {
            CidrBlock = "10.0.0.0/16",
        }, new CustomResourceOptions { Parent = this });

        var subnet = new Subnet($"{name}-subnet", new SubnetArgs
        {
            VpcId = vpc.Id,
            CidrBlock = "10.0.1.0/24",
        }, new CustomResourceOptions { Parent = this });

        this.RegisterOutputs();
    }
}

```

<!-- /option -->

<!-- option: java -->
```java
public class MyVpcComponent extends ComponentResource {
    public MyVpcComponent(String name, ComponentResourceOptions opts) {
        super("custom:index:MyVpcComponent", name, opts);

        var vpc = new Vpc(name + "-vpc", VpcArgs.builder()
            .cidrBlock("10.0.0.0/16")
            .build(),
            CustomResourceOptions.builder()
                .parent(this)
                .build());

        var subnet = new Subnet(name + "-subnet", SubnetArgs.builder()
            .vpcId(vpc.id())
            .cidrBlock("10.0.1.0/24")
            .build(),
            CustomResourceOptions.builder()
                .parent(this)
                .build());

        this.registerOutputs();
    }
}

```

<!-- /option -->

<!-- option: yaml -->
```yaml
# Pulumi YAML does not support transformations

```

<!-- /option -->

<!-- /chooser -->

With the component defined, the following transformation finds all VPC and Subnet resources inside the component's child hierarchy and adds an option to ignore any changes for tags properties (perhaps because we manage all VPC and Subnet tags outside of Pulumi):

<!-- chooser: language -->

<!-- option: typescript -->
```typescript
const vpc = new MyVpcComponent("vpc", {}, {
    transformations: [args => {
        if (args.type === "aws:ec2/vpc:Vpc" || args.type === "aws:ec2/subnet:Subnet") {
            return {
                props: args.props,
                opts: pulumi.mergeOptions(args.opts, { ignoreChanges: ["tags"] }),
            };
        }
        return undefined;
    }],
});

```

<!-- /option -->

<!-- option: python -->
```python
def transformation(args: ResourceTransformationArgs):
    if args.type_ == "aws:ec2/vpc:Vpc" or args.type_ == "aws:ec2/subnet:Subnet":
        return ResourceTransformationResult(
            props=args.props,
            opts=ResourceOptions.merge(args.opts, ResourceOptions(
                ignore_changes=["tags"],
            )))

vpc = MyVpcComponent("vpc", opts=ResourceOptions(transformations=[transformation]))

```

<!-- /option -->

<!-- option: go -->
```go
transformation := func(args *pulumi.ResourceTransformationArgs) *pulumi.ResourceTransformationResult {
    if args.Type == "aws:ec2/vpc:Vpc" || args.Type == "aws:ec2/subnet:Subnet" {
        return &pulumi.ResourceTransformationResult{
            Props: args.Props,
            Opts:  append(args.Opts, pulumi.IgnoreChanges([]string{"tags"})),
        }
    }
    return nil
}

vpc, err := NewMyVpcComponent(ctx, "vpc", pulumi.Transformations([]pulumi.ResourceTransformation{transformation}))
if err != nil {
    return err
}

```

<!-- /option -->

<!-- option: csharp -->
```csharp
var vpc = new MyVpcComponent("vpc", new ComponentResourceOptions
{
    ResourceTransformations =
    {
        args =>
        {
            if (args.Resource.GetResourceType() == "aws:ec2/vpc:Vpc" ||
                args.Resource.GetResourceType() == "aws:ec2/subnet:Subnet")
            {
                var options = CustomResourceOptions.Merge(
                    (CustomResourceOptions) args.Options,
                    new CustomResourceOptions { IgnoreChanges = {"tags"} });
                return new ResourceTransformationResult(args.Args, options);
            }

            return null;
        }
    }
});

```

<!-- /option -->

<!-- option: java -->
```java
var vpc = new MyVpcComponent("vpc",
    ComponentResourceOptions.builder()
        .resourceTransformations(resourceTransformation -> {
            var resource = resourceTransformation.getResource();
            var args = resourceTransformation.getArgs();
            var options = resourceTransformation.getOptions();
            if (resource.getResourceType() == "aws:ec2/vpc:Vpc" ||
                resource.getResourceType() == "aws:ec2/subnet:Subnet") {

                var mergedOptions = CustomResourceOptions.merge(
                    (CustomResourceOptions) options,
                    CustomResourceOptions.builder()
                        .ignoreChanges("tags")
                        .build());
                return Optional.of(new ResourceTransformation.Result(args, mergedOptions));
            }
            return Optional.of(new ResourceTransformation.Result(args, options));
        }).build());

```

<!-- /option -->

<!-- option: yaml -->
```yaml
# Pulumi YAML does not support transformations

```

<!-- /option -->

<!-- /chooser -->

## Stack Transformations

Transformations can also be applied in bulk to many or all resources in a stack by using Stack Transformations, which are applied to the root stack resource and as a result inherited by all other resources in the stack.  Note that this applies only to resources that are registered after the stack transformation is registered.  Resources in the stack that have already been registered will not get the Stack Transformation applied to them.

<!-- chooser: language -->

<!-- option: typescript -->
```typescript
pulumi.runtime.registerStackTransformation(args => {
    // ...
});

```

<!-- /option -->

<!-- option: python -->
```python
def my_transformation(args):
    # ...

pulumi.runtime.register_stack_transformation(my_transformation)

```

<!-- /option -->

<!-- option: go -->
```go
ctx.RegisterStackTransformation(
    func(args *pulumi.ResourceTransformationArgs) *pulumi.ResourceTransformationResult {
        // ...
    },
)

```

<!-- /option -->

<!-- option: csharp -->
There are two ways of defining stack transformations in C#, depending on which style you’re using for your Pulumi program.
The original way, inheriting from the `Stack` class:
```csharp
public class MyStack : Stack
{
    public MyStack() : base(new StackOptions { ResourceTransformations = { MyTransformation } })
    {
        ...
    }

    private static ResourceTransformationResult? MyTransformation(ResourceTransformationArgs args)
    {
        var tagProperty = args.Args.GetType().GetProperty("Tags");
        if (tagProperty != null) {
            var currentResourceTags = (InputMap<string>)tagProperty.GetValue(args.Args, null) ?? new InputMap<string>();
            currentResourceTags["env"] = "production";
            tagProperty.SetValue(args.Args, currentResourceTags, null);
            return new ResourceTransformationResult(args.Args, (CustomResourceOptions)args.Options);
        }
        return null;
    }
}

```

or the newer, asynchronous way:
```csharp
using System.Collections.Generic;
using Pulumi;

return await Deployment.RunAsync(async () =>
{
    // register resources here

    return new Dictionary<string, object?>
    {
        ["outputKey"] = "outputValue"
    };
}, new StackOptions
{
    ResourceTransformations =
    {
        async (args, _) =>
        {
            var tagProperty = args.Args.GetType().GetProperty("Tags");
            if (tagProperty != null) {
                var currentResourceTags = (InputMap<string>)tagProperty.GetValue(args.Args, null) ?? new InputMap<string>();
                currentResourceTags["env"] = "production";
                tagProperty.SetValue(args.Args, currentResourceTags, null);
                return new ResourceTransformationResult(args.Args, (CustomResourceOptions)args.Options);
            }
            return null;
        }
    }
});

```

<!-- /option -->

<!-- option: java -->
```java
var stackOptions = StackOptions.builder()
    .resourceTransformations(args -> {
        // ...
    })
    .build();

Pulumi.withOptions(stackOptions).run(ctx -> {
    // ...
});

```

<!-- /option -->

<!-- option: yaml -->
```yaml
# Pulumi YAML does not support transformations

```

<!-- /option -->

<!-- /chooser -->

## Migrating from Transformations to Transforms

Transformations will be deprecated in the future in favor of the more capable [Transforms](/docs/concepts/options/transforms) APIs. While the Transforms APIs are similar to Transformations, there are some differences in both API signatures and runtime behavior to be aware of. When moving from Transformations to Transforms you will need to update your transform code to handle the differences.

Summary of key differences:

- [**No resource object**](#no-resource-object): There is no `Resource` object passed to transform functions. Most of the information you could have retrieved from that object is presented on the transform arguments directly, such as the type of the resource.

- [**No typed args classes**](#no-typed-args-classes): In the old transformation system the transform function is called with the same values that are passed to the resource constructor. This means that in languages like Go, C#, and Python, you could typecast the arguments to the typed args struct/class. The new transform system works over the wire protocol, allowing it to run for resources created in other processes, but it means the properties object you get is closer to the raw protocol than the typed arguments you might expect. Objects are represented as dictionaries/maps with camelCase keys (e.g. in Python, access properties with camelCase keys like `environmentVariables` instead of snake_case keys like `environment_variables`). Property names in resource options are also camelCase.

- [**Natively Async**](#natively-async): The new transform API has been designed from the start with async support in mind. In all applicable languages the transform functions support returning a Promise/Task so you can use standard `await` operators for async calls in the transform. In Node.js and Python, returning a Promise/Awaitable is optional.

### No Resource Object

There is no `Resource` object passed to transform functions. Most of the information you could have retrieved from that object is presented on the transform arguments directly.

This mostly impacts C# transforms. With transformations, you would have to call `args.Resource.GetResourceType()` to get the type of the resource:

```csharp
args =>
{
    if (args.Resource.GetResourceType() == "random:index/randomString:RandomString")
    {
        var resultOpts = CustomResourceOptions.Merge((CustomResourceOptions)args.Options,
            new CustomResourceOptions {AdditionalSecretOutputs = {"length"}});
        return new ResourceTransformationResult(resultArgs, resultOpts);
    }
    return null;
}
```

With transforms, this is now available as `args.Type`:

```csharp
async (args, _) =>
{
    if (args.Type == "random:index/randomString:RandomString")
    {
        var resultOpts = CustomResourceOptions.Merge((CustomResourceOptions)args.Options,
            new CustomResourceOptions {AdditionalSecretOutputs = {"length"}});
        return new ResourceTransformResult(resultArgs, resultOpts);
    }
    return null;
}
```

### No Typed Args Classes

In the old transform system the transform function was called with the same values passed to the resource constructor. This meant that in languages like Go, C#, and Python you could typecast to the typed arguments struct/class.

The new transform system works over the wire protocol, allowing it to run for resources created in other
processes, but it means the properties object you get is closer to the raw protocol than the typed arguments
you might expect. Objects are represented as dictionaries/maps with camelCase keys (e.g. in Python, access properties with camelCase keys like `environmentVariables` instead of snake_case keys like `environment_variables`). Property names in resource options are also camelCase.

<!-- chooser: language -->

<!-- option: python -->
Here’s a Python example of the old system:
```python
def transformation(args: pulumi.ResourceTransformationArgs) -> pulumi.ResourceTransformationResult | None:
    if args.type_ == "random:index/randomString:RandomString":
        props = { **args.props }
        length = pulumi.Output.from_input(props["length"])
        props["length"] = length.apply(lambda v: v * 2)
        props["special"] = True
        props["override_special"] = "/@£$"
        return pulumi.ResourceTransformationResult(props=props, opts=args.opts)

```

Here’s the example with the new system:
```python
def transform(args: pulumi.ResourceTransformArgs) -> pulumi.ResourceTransformResult | None:
    if args.type_ == "random:index/randomString:RandomString":
        props = { **args.props }
        length = pulumi.Output.from_input(props["length"])
        props["length"] = length.apply(lambda v: v * 2)
        props["special"] = True
        props["overrideSpecial"] = "/@£$"
        return pulumi.ResourceTransformResult(props=props, opts=args.opts)

```

Note in the new system, we specify the property name as `overrideSpecial` instead of `override_special`.

<!-- /option -->

<!-- option: go -->
In this Go example the old system would have given us a typed `RandomStringArgs` structure with the `Length` field
being a `pulumi.IntInput`:
```go
func(_ context.Context, args *pulumi.ResourceTransformationArgs) *pulumi.ResourceTransformationResult {
    if args.Type == "random:index/randomString:RandomString" {
        var props *random.RandomStringArgs
        if args.Props == nil {
            props = &random.RandomStringArgs{}
        } else {
            props = args.Props.(*random.RandomStringArgs)
        }
        props.Length = props.Length.ToIntOutput().ApplyT(func(v int) int { return v * 2 }).(pulumi.IntOutput)
        props.Special = pulumi.Bool(true)
        props.OverrideSpecial = pulumi.String("/@£$")

        return &pulumi.ResourceTransformationResult{
            Props: props,
            Opts:  args.Opts,
        }
    }
    return nil
}

```

The new system is just a map where we have to know the key is `"length"` and the value is a `Float64Input`:
```go
func(_ context.Context, args *pulumi.ResourceTransformArgs) *pulumi.ResourceTransformResult {
    if args.Type == "random:index/randomString:RandomString" {
        props := args.Props
        if props == nil {
            props = pulumi.Map{}
        }
        length := args.Props["length"].(pulumi.Float64Input).ToFloat64Output()
        props["length"] = length.ApplyT(func(v float64) float64 { return v * 2 })
        props["special"] = pulumi.Bool(true)
        props["overrideSpecial"] = pulumi.String("/@£$")

        return &pulumi.ResourceTransformResult{
            Props: props,
            Opts:  args.Opts,
        }
    }
    return nil
},

```

You might ask why `Float64Input` instead of at least `IntInput`. This is because the wire protocol doesn’t
actually have support for integers (being JSON based), so on receiving the untyped properties the transform
callback can only go on their JSON values and all numbers are 64-bit floats.

<!-- /option -->

<!-- option: csharp -->
In this C# example, the old system would have given us a typed `RandomStringArgs` class with the `Length` field being an <code>Input<int></code>:
```csharp
args =>
{
    if (args.Resource.GetResourceType() == "random:index/randomString:RandomString")
    {
        var props = (RandomStringArgs)args.Args;
        props.Length = props.Length.Apply(v => v * 2);
        props.Special = true;
        props.OverrideSpecial = "/@£$";
        return new ResourceTransformationResult(props, args.Options);
    }
    return null;
}

```

The new system is just a dictionary where we have to know the key is `"length"` and the value is a <code>Input<double></code>:
```csharp
(args, _) =>
{
    if (args.Type == "random:index/randomString:RandomString")
    {
        var props = args.Args;
        var length = (double)props["length"]! * 2;
        props = props.SetItem("length", length);
        props = props.SetItem("special", true);
        props = props.SetItem("overrideSpecial", "/@£$");
        return new ResourceTransformResult(props, args.Options);
    }
    return null;
}

```

<!-- /option -->

<!-- /chooser -->

### Natively Async

The new transform API has been designed from the start with async support in mind.

In all applicable languages the transform functions support returning a Promise/Task so you can use standard `await` operators for async calls in the transform.

For Go, we pass a `context.Context` in as the first argument for the transform function. This is to indicate its async nature, but also allows you access to the async context for tracing/logging/cancellation.

For .NET, we pass a `System.Threading.CancellationToken` as the last argument of the transform function. This allows you to handle cancellation if needed.

For Node.js and Python, the transform function can optionally be `async` and return a `Promise`/`Awaitable`.

