1. Docs
  2. Infrastructure as Code
  3. Concepts
  4. Components

Component resources

    Pulumi Components enable you to create, share, and consume reusable infrastructure building blocks across your organization and the broader community. Components instantiate a set of related resources, acting as an abstraction to encapsulate the resources’ implementation details.

    Here are a few examples of component resources:

    • A Vpc that automatically comes with built-in best practices.
    • An AcmeCorpVirtualMachine that adheres to your company’s requirements, such as tagging.
    • A KubernetesCluster that can create EKS, AKS, and GKE clusters, depending on the target.

    The implicit pulumi:pulumi:Stack resource is itself a component resource that contains all top-level resources in a program.

    Authoring a New Component Resource

    To author a new component, either in a program or for a reusable library, create a subclass of ComponentResource. Inside of its constructor, chain to the base constructor, passing its type string, name, arguments, and options. Also inside of its constructor, allocate any child resources, passing the parent option as appropriate to ensure component resource children are parented correctly.

    Here’s a simple component example:

    class MyComponent extends pulumi.ComponentResource {
        constructor(name: string, myComponentArgs: MyComponentArgs, opts: pulumi.ComponentResourceOptions) {
            super("pkg:index:MyComponent", name, {}, opts);
        }
    }
    
    class MyComponent(pulumi.ComponentResource):
        def __init__(self, name, my_component_args, opts = None):
            super().__init__('pkg:index:MyComponent', name, None, opts)
    
    type MyComponent struct {
        pulumi.ResourceState
    }
    
    func NewMyComponent(ctx *pulumi.Context, name string, myComponentArgs MyComponentArgs, opts ...pulumi.ResourceOption) (*MyComponent, error) {
        myComponent := &MyComponent{}
        err := ctx.RegisterComponentResource("pkg:index:MyComponent", name, myComponent, opts...)
        if err != nil {
            return nil, err
        }
        return myComponent, nil
    }
    
    using System.Collections.Generic;
    using Pulumi;
    
    class MyComponent : ComponentResource
    {
        public MyComponent(string name, MyComponentArgs myComponentArgs, ComponentResourceOptions opts)
            : base("pkg:index:MyComponent", name, opts)
        {
        }
    }
    
    import com.pulumi.resources.ComponentResource;
    import com.pulumi.resources.ComponentResourceOptions;
    
    class MyComponent extends ComponentResource {
        public MyComponent(String name, MyComponentArgs myComponentArgs, ComponentResourceOptions opts) {
            super("pkg:index:MyComponent", name, null, opts);
        }
    }
    

    Upon creating a new instance of MyComponent, the call to the base constructor (using super/base) registers the component resource instance with the Pulumi engine. This records the resource’s state and tracks it across program deployments so that you see diffs during updates just like with a regular resource (even though component resources have no provider logic associated with them). Since all resources must have a name, a component resource constructor should accept a name and pass it to super.

    If you wish to have full control over one of the custom resource’s lifecycle in your component resource—including running specific code when a resource has been updated or deleted—you should look into dynamic providers. These let you create full-blown resource abstractions in your language of choice.

    A component resource must register a unique type name with the base constructor. In the example, the registration is pkg:index:MyComponent. To reduce the potential of other type name conflicts, this name contains the package and module name, in addition to the type: <package>:<module>:<type>. These names are namespaced alongside non-component resources, such as aws:lambda:Function.

    For a complete end-to-end walkthrough of building a component from scratch, including setup, implementation, and publishing, see the Build a Component guide.
    Not all resource options apply to component resources. For a complete list of which options work with components and how to apply options to child resources, see Resource options and component resources.

    Component arguments and type requirements

    When authoring components that will be consumed across different languages (multi-language components), the arguments class has specific requirements and limitations due to the need for serialization. These constraints ensure that component arguments can be transmitted to the Pulumi engine and reconstructed across language boundaries.

    Serialization requirements

    Component arguments must be serializable, meaning you must convert them to a format that the engine can transmit and reconstruct. This is necessary because:

    1. The Pulumi engine needs to understand and validate the inputs
    2. Multi-language components need to translate arguments between languages
    3. The state needs to be stored and retrieved across deployments

    Supported types

    The following types are supported in component arguments:

    • Primitive types: string, number/int, boolean.

    • Arrays/lists: Arrays of any supported type.

    • Objects/maps: Objects with properties of supported types.

    • Input wrappers: Language-specific input types that wrap values:

      • TypeScript/JavaScript: pulumi.Input<T>
      • Python: pulumi.Input[T]
      • Go: pulumi.StringInput, pulumi.IntInput, etc.
      • .NET: Input<T>
      • Java: Output<T>
    • Enums: Enum types are supported in TypeScript and Python:

      TypeScript: Both the enum keyword and the “as const object pattern” are supported:

      // Using the enum keyword
      enum MyEnum { Value1 = "Value1", Value2 = "Value2" }
      
      // or alternatively using a const object
      const MyEnum = { Value1: "Value1", Value2: "Value2" } as const;
      type MyEnum = typeof MyEnum[keyof typeof MyEnum];
      

      Python: The standard library enum module is supported:

      from enum import Enum
      
      class MyEnum(Enum):
          VALUE1 = "Value1"
          VALUE2 = "Value2"
      
    • Utility types (TypeScript): The Required<T> and Partial<T> utility types are supported.

    Unsupported types

    The following types are not supported in component arguments:

    • Union types: TypeScript and Python union types like string | number are not supported due to limitations in schema inference. One exception: unions of undefined (TypeScript) or None (Python) with exactly one other type are supported to represent optional values (e.g., string | undefined or str | None).
    • Functions/callbacks: Functions cannot be used in component arguments as they cannot be represented in the schema.
    • Platform-specific types: Types that exist only in one language and cannot be translated.

    Design recommendations

    For better usability and maintainability:

    • Avoid deeply nested types: While complex generic types can be serialized, deeply nested structures make components harder to use and understand. Keep argument structures simple and flat when possible.

    Example of unsupported TypeScript types:

    // ❌ This will NOT work - union types are not supported
    export interface MyComponentArgs {
        value: string | number;  // Union type - unsupported
        callback: () => void;    // Function - unsupported
    }
    
    // ✅ This WILL work - use primitives or Input types
    export interface MyComponentArgs {
        value: pulumi.Input<string>;
        count: pulumi.Input<number>;
    }
    

    Constructor requirements by language

    Each language has specific requirements for component constructors to ensure proper schema generation:

    Requirements:

    • The constructor must have an argument named exactly args
    • The args parameter must have a type declaration (e.g., args: MyComponentArgs)
    class MyComponent extends pulumi.ComponentResource {
        constructor(name: string, args: MyComponentArgs, opts?: pulumi.ComponentResourceOptions) {
            super("pkg:index:MyComponent", name, {}, opts);
        }
    }
    

    Requirements:

    • The __init__ method must have an argument named exactly args
    • The args parameter must have a type annotation (e.g., args: MyComponentArgs)
    class MyComponent(pulumi.ComponentResource):
        def __init__(self, name: str, args: MyComponentArgs, opts: Optional[pulumi.ResourceOptions] = None):
            super().__init__('pkg:index:MyComponent', name, None, opts)
    

    Requirements:

    • The constructor function should accept a context, name, args struct, and variadic resource options
    • The args should be a struct type
    func NewMyComponent(ctx *pulumi.Context, name string, args *MyComponentArgs, opts ...pulumi.ResourceOption) (*MyComponent, error) {
        myComponent := &MyComponent{}
        err := ctx.RegisterComponentResource("pkg:index:MyComponent", name, myComponent, opts...)
        if err != nil {
            return nil, err
        }
        return myComponent, nil
    }
    

    Requirements:

    • The constructor must have exactly 3 arguments:
      1. A string for the name (or any unspecified first argument)
      2. An argument that is assignable from ResourceArgs (must extend ResourceArgs)
      3. An argument that is assignable from ComponentResourceOptions
    public class MyComponent : ComponentResource
    {
        public MyComponent(string name, MyComponentArgs args, ComponentResourceOptions? opts = null)
            : base("pkg:index:MyComponent", name, opts)
        {
        }
    }
    
    public sealed class MyComponentArgs : ResourceArgs
    {
        [Input("value")]
        public Input<string>? Value { get; set; }
    }
    

    Requirements:

    • The constructor must have exactly one argument that extends ResourceArgs
    • Other arguments (name, options) are not restricted but typically follow the standard pattern
    public class MyComponent extends ComponentResource {
        public MyComponent(String name, MyComponentArgs args, ComponentResourceOptions opts) {
            super("pkg:index:MyComponent", name, null, opts);
        }
    }
    
    class MyComponentArgs extends ResourceArgs {
        @Import(name = "value")
        private Output<String> value;
    
        public Output<String> getValue() {
            return this.value;
        }
    }
    

    Best practices

    When designing component arguments:

    1. Wrap all scalar members in Input types: Every scalar argument should be wrapped in the language’s input type (e.g., pulumi.Input<string>). This allows users to pass both plain values and outputs from other resources, avoiding the need to use apply for resource composition.
    2. Use basic types: Stick to primitive types, arrays, and basic objects.
    3. Avoid union types: Instead of a single value with multiple types, consider multiple, mutually exclusive argument members and validate that only one of them has a value in your component constructor.
    4. Document required vs. optional: Clearly document which arguments are required and which have defaults.
    5. Follow language conventions: Use camelCase for schema properties but follow language-specific naming in implementation (snake_case in Python, PascalCase in .NET).

    Creating Child Resources

    Component resources often contain child resources. The names of child resources are often derived from the component resources’s name to ensure uniqueness. For example, you might use the component resource’s name as a prefix. Also, when constructing a resource, children must be registered as such. To do this, pass the component resource itself as the parent option.

    This example demonstrates both the naming convention and how to designate the component resource as the parent:

    class MyComponent extends pulumi.ComponentResource {
        bucket: aws.s3.Bucket;
    
        constructor(name: string, myComponentArgs: MyComponentArgs, opts: pulumi.ComponentResourceOptions) {
            super("pkg:index:MyComponent", name, {}, opts);
    
            // Create Child Resource
            this.bucket = new aws.s3.Bucket(`${name}-bucket`,
                {}, { parent: this });
        }
    }
    
    class MyComponent(pulumi.ComponentResource):
        def __init__(self, name, my_component_args, opts = None):
            super().__init__('pkg:index:MyComponent', name, None, opts)
    
            # Create Child Resource
            self.bucket = s3.Bucket(f"{name}-bucket",
                opts=pulumi.ResourceOptions(parent=self))
    
    type MyComponent struct {
        pulumi.ResourceState
        Bucket *s3.Bucket
    }
    
    func NewMyComponent(ctx *pulumi.Context, name string, myComponentArgs MyComponentArgs, opts ...pulumi.ResourceOption) (*MyComponent, error) {
        myComponent := &MyComponent{}
        err := ctx.RegisterComponentResource("pkg:index:MyComponent", name, myComponent, opts...)
        if err != nil {
            return nil, err
        }
    
        // Create Child Resource
        bucket, err := s3.NewBucket(ctx, fmt.Sprintf("%s-bucket", name),
            &s3.BucketArgs{}, pulumi.Parent(myComponent))
        if err != nil {
            return nil, err
        }
        myComponent.Bucket = bucket
    
        return myComponent, nil
    }
    
    using System.Collections.Generic;
    using Pulumi;
    using Pulumi.Aws.S3;
    
    class MyComponent : ComponentResource
    {
        public Bucket Bucket { get; private set; }
    
        public MyComponent(string name, MyComponentArgs myComponentArgs, ComponentResourceOptions opts)
            : base("pkg:index:MyComponent", name, opts)
        {
            // Create Child Resource
            this.Bucket = new Bucket($"{name}-bucket",
                new BucketArgs(), new CustomResourceOptions { Parent = this });
        }
    }
    
    import com.pulumi.resources.ComponentResource;
    import com.pulumi.resources.ComponentResourceOptions;
    import com.pulumi.aws.s3.Bucket;
    import com.pulumi.aws.s3.BucketArgs;
    import com.pulumi.resources.CustomResourceOptions;
    
    class MyComponent extends ComponentResource {
        private final Bucket bucket;
    
        public MyComponent(String name, MyComponentArgs myComponentArgs, ComponentResourceOptions opts) {
            super("pkg:index:MyComponent", name, null, opts);
    
            // Create Child Resource
            this.bucket = new Bucket(String.format("%s-bucket", name),
                BucketArgs.builder().build(),
                CustomResourceOptions.builder()
                    .parent(this)
                    .build());
        }
    
        public Bucket bucket() {
            return this.bucket;
        }
    }
    

    Registering Component Outputs

    Component resources can define their own output properties. Outputs in a component must be registered with the Pulumi IaC engine by calling registerOutputs. The Pulumi engine uses this information to display the logical outputs of the component resource and any changes to those outputs will be shown during an update.

    For example, this code registers an S3 bucket’s computed domain name, which won’t be known until the bucket is created:

    class MyComponent extends pulumi.ComponentResource {
        bucket: aws.s3.Bucket;
    
        constructor(name: string, myComponentArgs: MyComponentArgs, opts: pulumi.ComponentResourceOptions) {
            super("pkg:index:MyComponent", name, {}, opts);
    
            this.bucket = new aws.s3.Bucket(`${name}-bucket`,
                {}, { parent: this });
    
            // Registering Component Outputs
            this.registerOutputs({
                bucketDnsName: this.bucket.bucketDomainName
            });
        }
    }
    
    class MyComponent(pulumi.ComponentResource):
        bucket_dns_name: pulumi.Output[str]
        """The DNS name of the bucket"""
    
        def __init__(self, name, my_component_args, opts = None):
            super().__init__('pkg:index:MyComponent', name, None, opts)
    
            self.bucket = s3.Bucket(f"{name}-bucket",
                opts=pulumi.ResourceOptions(parent=self))
    
            # Registering Component Outputs
            self.register_outputs({
                "bucket_dns_name": self.bucket.bucket_domain_name
            })
    
    type MyComponent struct {
        pulumi.ResourceState
        Bucket *s3.Bucket
    }
    
    func NewMyComponent(ctx *pulumi.Context, name string, myComponentArgs MyComponentArgs, opts ...pulumi.ResourceOption) (*MyComponent, error) {
        myComponent := &MyComponent{}
        err := ctx.RegisterComponentResource("pkg:index:MyComponent", name, myComponent, opts...)
        if err != nil {
            return nil, err
        }
    
        bucket, err := s3.NewBucket(ctx, fmt.Sprintf("%s-bucket", name),
            &s3.BucketArgs{}, pulumi.Parent(myComponent))
        if err != nil {
            return nil, err
        }
        myComponent.Bucket = bucket
    
        //Registering Component Outputs
        ctx.RegisterResourceOutputs(myComponent, pulumi.Map{
            "bucketDnsName": bucket.BucketDomainName,
        })
    
        return myComponent, nil
    }
    
    using System.Collections.Generic;
    using Pulumi;
    using Pulumi.Aws.S3;
    
    class MyComponent : ComponentResource
    {
        public Bucket Bucket { get; private set; }
    
        public MyComponent(string name, MyComponentArgs myComponentArgs, ComponentResourceOptions opts)
            : base("pkg:index:MyComponent", name, opts)
        {
    
            this.Bucket = new Bucket($"{name}-bucket",
                new BucketArgs(), new CustomResourceOptions { Parent = this });
    
            // Registering Component Outputs
            this.RegisterOutputs(new Dictionary<string, object?>
            {
                { "bucketDnsName", Bucket.BucketDomainName }
            });
        }
    }
    
    import com.pulumi.resources.ComponentResource;
    import com.pulumi.resources.ComponentResourceOptions;
    import com.pulumi.aws.s3.Bucket;
    import com.pulumi.aws.s3.BucketArgs;
    import com.pulumi.resources.CustomResourceOptions;
    
    class MyComponent extends ComponentResource {
        private final Bucket bucket;
    
        public MyComponent(String name, MyComponentArgs myComponentArgs, ComponentResourceOptions opts) {
            super("pkg:index:MyComponent", name, null, opts);
    
            this.bucket = new Bucket(String.format("%s-bucket", name),
                BucketArgs.builder().build(),
                CustomResourceOptions.builder()
                    .parent(this)
                    .build());
    
            // Registering Component Outputs
            this.registerOutputs(Map.of(
                "bucketDnsName", bucket.bucketDomainName()
            ));
        }
    
        public Bucket bucket() {
            return this.bucket;
        }
    }
    

    The call to registerOutputs typically happens at the very end of the component resource’s constructor.

    What RegisterOutputs Does

    The registerOutputs call serves two critical functions:

    1. Marks the component as fully constructed: It signals to the Pulumi engine that the component resource has finished registering all its child resources and should be considered complete.
    2. Saves outputs to state: It registers the component’s outputs with the Pulumi engine so they are properly saved to the state file and can be referenced by other resources or exported from the stack.

    Failing to call registerOutputs could cause serious issues with your component resource:

    • The component will appear as “creating…” indefinitely in the Pulumi Console
    • Outputs will not be saved to the state file, potentially causing data loss
    • The component lifecycle will not complete properly, which may affect dependency tracking and updates

    Inheriting Resource Providers

    One option all resources have is the ability to pass an explicit resource provider to supply explicit configuration settings. For instance, you may want to ensure that all AWS resources are created in a different region than the globally configured region. In the case of component resources, the challenge is that these providers must flow from parent to children.

    To allow this, component resources accept a providers option that custom resources don’t have. This value contains a map from the provider name to the explicit provider instance to use for the component resource. The map is used by a component resource to fetch the proper provider object to use for any child resources. This example overrides the globally configured AWS region and sets it to us-east-1. Note that myk8s is the name of the Kubernetes provider.

    let component = new MyComponent("...", {
        providers: {
            aws: useast1,
            kubernetes: myk8s,
        },
    });
    
    component = MyComponent('...', ResourceOptions(providers={
        'aws': useast1,
        'kubernetes': myk8s,
    }))
    
    component, err := NewMyResource(ctx, "...", nil, pulumi.ProviderMap(
        map[string]pulumi.ProviderResource{
            "aws":        awsUsEast1,
            "kubernetes": myk8s,
        },
    ))
    
    var component = new MyResource("...", new ComponentResourceOptions {
        Providers = {
            { "aws", awsUsEast1 },
            { "kubernetes", myk8s }
        }
    });
    
    var component = new MyResource("...",
        ComponentResourceOptions.builder()
            .providers(awsUsEast1, myk8s)
            .build());
    

    If a component resource is itself a child of another component resource, its set of providers is inherited from its parent by default.

    Cross-language components

    By default, components are authored and consumed in the same programming language by extending the ComponentResource class. However, components can also be made available to programs written in other languages. When configured for cross-language support, Pulumi can introspect your component class and automatically generate SDKs for consumption in any Pulumi language.

    For detailed information on setting up and using cross-language components, including how to configure PulumiPlugin.yaml, define entry points, publish, and consume components, see Cross-language Components.

    For a comparison of all component packaging approaches (single-language, cross-language, and provider-based), see Packaging Components.