Skip to main content
Pulumi logo Pulumi logo
  1. Docs
  2. Infrastructure as Code
  3. Guides
  4. Basics
  5. Language Essentials
  6. Classes and Components

Classes and Components

    A class groups related data and behavior under one name. Where a function groups a piece of logic, a class groups a piece of state, plus the operations that act on it, so the group can be passed around, referenced, and reused as a single thing. You don’t need inheritance, interfaces, or generics to use a class productively in a Pulumi program; the minimal version, a constructor plus a few fields, covers what you’ll actually reach for.

    Where you have seen this before

    You’ve already grouped resources under a shared identity: a Terraform module with its own inputs and outputs, or, in Pulumi YAML, a set of resources that share a naming prefix and configuration. A class that extends ComponentResource, covered later on this page, is Pulumi’s version of that same grouping, expressed as a reusable unit in your language rather than a separate module directory.

    The syntax

    A class has fields to hold state and a constructor to set them.

    class Endpoint {
      readonly host: string;
      readonly port: number;
    
      constructor(host: string, port: number) {
        this.host = host;
        this.port = port;
      }
    
      url(): string {
        return `https://${this.host}:${this.port}`;
      }
    }
    
    const endpoint = new Endpoint("api.example.com", 443);
    
    class Endpoint:
        def __init__(self, host: str, port: int):
            self.host = host
            self.port = port
    
        def url(self) -> str:
            return f"https://{self.host}:{self.port}"
    
    endpoint = Endpoint("api.example.com", 443)
    
    type Endpoint struct {
    	Host string
    	Port int
    }
    
    func (e Endpoint) URL() string {
    	return fmt.Sprintf("https://%s:%d", e.Host, e.Port)
    }
    
    endpoint := Endpoint{Host: "api.example.com", Port: 443}
    
    class Endpoint
    {
        public string Host { get; }
        public int Port { get; }
    
        public Endpoint(string host, int port)
        {
            Host = host;
            Port = port;
        }
    
        public string Url() => $"https://{Host}:{Port}";
    }
    
    var endpoint = new Endpoint("api.example.com", 443);
    
    class Endpoint {
        final String host;
        final int port;
    
        Endpoint(String host, int port) {
            this.host = host;
            this.port = port;
        }
    
        String url() {
            return "https://" + host + ":" + port;
        }
    }
    
    var endpoint = new Endpoint("api.example.com", 443);
    

    Pulumi YAML has no classes and no way to define a reusable type. It works directly with the resource types and object shapes that a provider or component already defines.

    Terraform HCL has no classes either. A module, shown below, is the closest equivalent, and it’s a directory-level construct rather than a language-level one.

    Go doesn’t have classes; a struct plus functions that take it as a receiver serves the same purpose, as shown above.

    In a Pulumi program

    Pulumi’s equivalent of a Terraform module is a class that extends ComponentResource. It groups a set of resources under one logical name in the resource graph, exposes the outputs that matter, and can be instantiated as many times as you need, each with its own name:

    import * as pulumi from "@pulumi/pulumi";
    import * as aws from "@pulumi/aws";
    
    export class StaticWebsite extends pulumi.ComponentResource {
      public readonly bucket: aws.s3.Bucket;
      public readonly url: pulumi.Output<string>;
    
      constructor(
        name: string,
        args: { indexDocument: string },
        opts?: pulumi.ComponentResourceOptions,
      ) {
        super("example:index:StaticWebsite", name, {}, opts);
    
        this.bucket = new aws.s3.Bucket(
          `${name}-bucket`,
          { website: { indexDocument: args.indexDocument } },
          { parent: this },
        );
    
        this.url = this.bucket.websiteEndpoint;
    
        this.registerOutputs({ url: this.url });
      }
    }
    
    const site = new StaticWebsite("docs", { indexDocument: "index.html" });
    
    import pulumi
    import pulumi_aws as aws
    
    class StaticWebsite(pulumi.ComponentResource):
        def __init__(self, name: str, index_document: str, opts: pulumi.ResourceOptions = None):
            super().__init__("example:index:StaticWebsite", name, {}, opts)
    
            self.bucket = aws.s3.Bucket(
                f"{name}-bucket",
                website=aws.s3.BucketWebsiteArgs(index_document=index_document),
                opts=pulumi.ResourceOptions(parent=self),
            )
    
            self.url = self.bucket.website_endpoint
    
            self.register_outputs({"url": self.url})
    
    site = StaticWebsite("docs", "index.html")
    
    type StaticWebsiteArgs struct {
    	IndexDocument string
    }
    
    type StaticWebsite struct {
    	pulumi.ResourceState
    
    	URL pulumi.StringOutput `pulumi:"url"`
    }
    
    func NewStaticWebsite(ctx *pulumi.Context, name string, args StaticWebsiteArgs, opts ...pulumi.ResourceOption) (*StaticWebsite, error) {
    	component := &StaticWebsite{}
    	if err := ctx.RegisterComponentResource("example:index:StaticWebsite", name, component, opts...); err != nil {
    		return nil, err
    	}
    
    	bucket, err := s3.NewBucket(ctx, name+"-bucket", &s3.BucketArgs{
    		Website: &s3.BucketWebsiteArgs{
    			IndexDocument: pulumi.String(args.IndexDocument),
    		},
    	}, pulumi.Parent(component))
    	if err != nil {
    		return nil, err
    	}
    
    	component.URL = bucket.WebsiteEndpoint
    	if err := ctx.RegisterResourceOutputs(component, pulumi.Map{"url": component.URL}); err != nil {
    		return nil, err
    	}
    	return component, nil
    }
    
    public class StaticWebsiteArgs
    {
        public string IndexDocument { get; set; } = "index.html";
    }
    
    public class StaticWebsite : ComponentResource
    {
        public Output<string> Url { get; private set; }
    
        public StaticWebsite(string name, StaticWebsiteArgs args, ComponentResourceOptions? opts = null)
            : base("example:index:StaticWebsite", name, opts)
        {
            var bucket = new Bucket($"{name}-bucket", new BucketArgs
            {
                Website = new BucketWebsiteArgs { IndexDocument = args.IndexDocument },
            }, new CustomResourceOptions { Parent = this });
    
            Url = bucket.WebsiteEndpoint;
    
            RegisterOutputs(new Dictionary<string, object?> { { "url", Url } });
        }
    }
    
    var site = new StaticWebsite("docs", new StaticWebsiteArgs { IndexDocument = "index.html" });
    
    public class StaticWebsite extends ComponentResource {
        private final Output<String> url;
    
        public StaticWebsite(String name, String indexDocument, ComponentResourceOptions options) {
            super("example:index:StaticWebsite", name, options);
    
            var bucket = new Bucket(name + "-bucket", BucketArgs.builder()
                .website(BucketWebsiteArgs.builder().indexDocument(indexDocument).build())
                .build(), CustomResourceOptions.builder().parent(this).build());
    
            this.url = bucket.websiteEndpoint();
    
            this.registerOutputs(Map.of("url", Output.of(this.url)));
        }
    }
    
    var site = new StaticWebsite("docs", "index.html", CustomResourceOptions.Empty);
    

    The call to the base constructor, super(...) in TypeScript, C#, and Java, super().__init__(...) in Python, or RegisterComponentResource in Go, registers the component with Pulumi under a type token. Setting each child resource’s parent to the component attaches it in the resource graph, and registerOutputs (or RegisterResourceOutputs in Go) declares which of the component’s own properties are worth exposing.

    Reusing a component across languages

    A component authored once, packaged, and published can be consumed from any Pulumi language, including Pulumi YAML. That means a team that prefers to keep writing YAML for most of its stacks can still consume a component someone on the team wrote in Python or Go, without anyone switching languages for day-to-day work. See when to build a component for the decision itself, and building a component for the full walkthrough, including packaging it for other languages to consume.

    Next steps

    Continue to packages and dependencies to see how a component gets published and installed like any other package.

      The infrastructure as code platform for any cloud.