From b8411f2c88536833ec2e805e4ebd40c2717f1af5 Mon Sep 17 00:00:00 2001 From: Cam Date: Mon, 20 Oct 2025 23:48:50 +0000 Subject: [PATCH 1/4] Document Component Args type requirements and limitations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change addresses issue #15682 by documenting the type requirements and limitations for Component Args classes in multi-language components. Changes: - Added comprehensive new section "Component arguments and type requirements" to the Components concepts page covering: - Serialization requirements and why they're needed - Supported types (primitives, arrays, objects, Input wrappers) - Unsupported types (union types, functions, complex generics) - Language-specific constructor requirements for TypeScript, Python, Go, .NET, and Java based on technical details from @julienp - Best practices for designing component arguments - Enhanced Build a Component guide with cross-references from all language sections (TypeScript, Python, Go, C#, Java) that mention "serializable" to link to the comprehensive documentation - Included concrete TypeScript example showing unsupported types (union types, functions) vs. supported patterns This ensures developers understand the constraints when authoring multi-language components and can avoid common pitfalls with unsupported types. Fixes #15682 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../docs/iac/concepts/components/_index.md | 183 ++++++++++++++++++ .../components/build-a-component.md | 10 +- 2 files changed, 188 insertions(+), 5 deletions(-) diff --git a/content/docs/iac/concepts/components/_index.md b/content/docs/iac/concepts/components/_index.md index 0df4bf72c9b0..c0dd1071d907 100644 --- a/content/docs/iac/concepts/components/_index.md +++ b/content/docs/iac/concepts/components/_index.md @@ -126,6 +126,189 @@ A component resource must register a unique type name with the base constructor. For a complete end-to-end walkthrough of building a component from scratch, including setup, implementation, and publishing, see the [Build a Component](/docs/iac/using-pulumi/build-a-component/) guide. {{< /notes >}} +## 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 they need to be convertible to a format that can be transmitted and reconstructed. This is necessary because: + +1. The Pulumi engine needs to understand and validate the inputs +1. Multi-language components need to translate arguments between languages +1. 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` + - Python: `pulumi.Input[T]` + - Go: `pulumi.StringInput`, `pulumi.IntInput`, etc. + - .NET: `Input` + - Java: `Output` + +### Unsupported types + +The following types are not supported in component arguments: + +- **Union types**: TypeScript union types like `string | number` cannot be serialized +- **Functions/callbacks**: Functions cannot be passed as component arguments +- **Complex generic types**: Deeply nested or complex generic types may not serialize correctly +- **Platform-specific types**: Types that exist only in one language and cannot be translated + +**Example of unsupported TypeScript types:** + +```typescript +// ❌ 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; + count: pulumi.Input; +} +``` + +### Constructor requirements by language + +Each language has specific requirements for component constructors to ensure proper schema generation: + +{{< chooser language "javascript,typescript,python,go,csharp,java" >}} + +{{% choosable language javascript %}} + +JavaScript components should follow the same pattern as TypeScript, with the constructor accepting `name`, `args`, and `opts` parameters. + +```javascript +class MyComponent extends pulumi.ComponentResource { + constructor(name, args, opts) { + super("pkg:index:MyComponent", name, {}, opts); + } +} +``` + +{{% /choosable %}} +{{% choosable language typescript %}} + +**Requirements:** + +- The constructor must have an argument named exactly `args` +- The `args` parameter must have a type declaration (e.g., `args: MyComponentArgs`) + +```typescript +class MyComponent extends pulumi.ComponentResource { + constructor(name: string, args: MyComponentArgs, opts?: pulumi.ComponentResourceOptions) { + super("pkg:index:MyComponent", name, {}, opts); + } +} +``` + +{{% /choosable %}} +{{% choosable language python %}} + +**Requirements:** + +- The `__init__` method must have an argument named exactly `args` +- The `args` parameter must have a type annotation (e.g., `args: MyComponentArgs`) + +```python +class MyComponent(pulumi.ComponentResource): + def __init__(self, name: str, args: MyComponentArgs, opts: Optional[pulumi.ResourceOptions] = None): + super().__init__('pkg:index:MyComponent', name, None, opts) +``` + +{{% /choosable %}} +{{% choosable language go %}} + +**Requirements:** + +- The constructor function should accept a context, name, args struct, and variadic resource options +- The args should be a struct type + +```go +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 +} +``` + +{{% /choosable %}} +{{% choosable language csharp %}} + +**Requirements:** + +- The constructor must have exactly 3 arguments: + 1. A `string` for the name (or any unspecified first argument) + 1. An argument that is assignable from `ResourceArgs` (must extend `ResourceArgs`) + 1. An argument that is assignable from `ComponentResourceOptions` + +```csharp +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? Value { get; set; } +} +``` + +{{% /choosable %}} +{{% choosable language java %}} + +**Requirements:** + +- The constructor must have exactly one argument that extends `ResourceArgs` +- Other arguments (name, options) are not restricted but typically follow the standard pattern + +```java +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 value; + + public Output getValue() { + return this.value; + } +} +``` + +{{% /choosable %}} + +{{< /chooser >}} + +### Best practices + +When designing component arguments: + +1. **Use Input types**: Always wrap your arguments in the language's input type (e.g., `pulumi.Input`) to support both plain values and outputs from other resources +1. **Keep types simple**: Stick to primitive types, arrays, and simple objects +1. **Avoid union types**: If you need multiple possible types, consider using separate properties or creating multiple component variants +1. **Document required vs. optional**: Clearly document which arguments are required and which have defaults +1. **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. diff --git a/content/docs/iac/guides/building-extending/components/build-a-component.md b/content/docs/iac/guides/building-extending/components/build-a-component.md index e9cc4d8fc47a..c0ee2261cc51 100644 --- a/content/docs/iac/guides/building-extending/components/build-a-component.md +++ b/content/docs/iac/guides/building-extending/components/build-a-component.md @@ -661,7 +661,7 @@ export interface StaticPageArgs { } ``` -Note that argument classes must be *serializable* and use `pulumi.Input` types, rather than the language's default types. +Note that argument classes must be *serializable* and use `pulumi.Input` types, rather than the language's default types. This means certain types like union types (e.g., `string | number`) and functions are not supported. For details on type requirements and limitations, see [Component arguments and type requirements](/docs/iac/concepts/components/#component-arguments-and-type-requirements). {{% /choosable %}} @@ -675,7 +675,7 @@ class StaticPageArgs(TypedDict): """The HTML content for index.html.""" ``` -Note that argument classes must be *serializable* and use `pulumi.Input` types, rather than the language's default types. +Note that argument classes must be *serializable* and use `pulumi.Input` types, rather than the language's default types. This means certain types like union types and functions are not supported. For details on type requirements and limitations, see [Component arguments and type requirements](/docs/iac/concepts/components/#component-arguments-and-type-requirements). Python class properties are typically written in lowercase with words separated by underscores, known as [`snake_case`](https://en.wikipedia.org/wiki/Snake_case), however properties in the [Pulumi package schema](https://www.pulumi.com/docs/iac/using-pulumi/extending-pulumi/schema/) are usually written in [`camelCase`](https://en.wikipedia.org/wiki/Camel_case), where capital letters are used to separate words. To follow these conventions, the inferred schema for a component will have translated property names. In our example `index_content` will become `indexContent` in the schema. When using a component, the property names will follow the conventions of that language, for example if we use our component from TypeScript, we would refer to `indexContent`, but if we use it from Python, we would use `index_content`. @@ -690,7 +690,7 @@ type StaticPageArgs struct { } ``` -Note that argument classes must be *serializable* and use `pulumi.Input` types, rather than the language's default types. +Note that argument classes must be *serializable* and use `pulumi.Input` types, rather than the language's default types. This means complex or platform-specific types may not be supported. For details on type requirements and limitations, see [Component arguments and type requirements](/docs/iac/concepts/components/#component-arguments-and-type-requirements). Go struct fields are typically written in title case, with the first letter capitalized and capital letters used to separate words, however properties in the [Pulumi package schema](https://www.pulumi.com/docs/iac/using-pulumi/extending-pulumi/schema/) are usually written in [`camelCase`](https://en.wikipedia.org/wiki/Camel_case), with the first letter in lowercase and capital letters used to separate words. To follow these conventions, the inferred schema for a component will have translated property names. In our example `IndexContent` will become `indexContent` in the schema. When using a component, the property names will follow the conventions of that language, for example if we use our component from TypeScript, we would refer to `indexContent`, but if we use it from Go, we would use `IndexContent`. @@ -707,7 +707,7 @@ public sealed class StaticPageArgs : ResourceArgs { } ``` -Note that argument classes must be *serializable* and use `Pulumi.Input` types, rather than the language's default types. +Note that argument classes must be *serializable* and use `Pulumi.Input` types, rather than the language's default types. This means complex or platform-specific types may not be supported. For details on type requirements and limitations, see [Component arguments and type requirements](/docs/iac/concepts/components/#component-arguments-and-type-requirements). {{% /choosable %}} @@ -733,7 +733,7 @@ class StaticPageArgs extends ResourceArgs { } ``` -Note that argument classes must be *serializable* and use `com.pulumi.core.Output` types, rather than the language's default types. +Note that argument classes must be *serializable* and use `com.pulumi.core.Output` types, rather than the language's default types. This means complex or platform-specific types may not be supported. For details on type requirements and limitations, see [Component arguments and type requirements](/docs/iac/concepts/components/#component-arguments-and-type-requirements). The `@Import` decorator marks this as a *required* input and allows use to give a name for the input that could be different from the implementation here. From 8c62d49e4da75a319652c981d8597ee97c515cfe Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 00:05:27 +0000 Subject: [PATCH 2/4] Apply style guide fixes to component args documentation - Add ending punctuation to list items in supported/unsupported types sections - Replace passive voice with active voice in serialization description - Replace "simple" with "basic" per style guidelines - Add periods to all best practices list items for consistency Co-authored-by: Cam Soper --- .../docs/iac/concepts/components/_index.md | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/content/docs/iac/concepts/components/_index.md b/content/docs/iac/concepts/components/_index.md index c0dd1071d907..f42e619a42ff 100644 --- a/content/docs/iac/concepts/components/_index.md +++ b/content/docs/iac/concepts/components/_index.md @@ -132,7 +132,7 @@ When authoring components that will be consumed across different languages (mult ### Serialization requirements -Component arguments must be serializable, meaning they need to be convertible to a format that can be transmitted and reconstructed. This is necessary because: +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 1. Multi-language components need to translate arguments between languages @@ -142,9 +142,9 @@ Component arguments must be serializable, meaning they need to be convertible to 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 +- **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` - Python: `pulumi.Input[T]` @@ -156,10 +156,10 @@ The following types are supported in component arguments: The following types are not supported in component arguments: -- **Union types**: TypeScript union types like `string | number` cannot be serialized -- **Functions/callbacks**: Functions cannot be passed as component arguments -- **Complex generic types**: Deeply nested or complex generic types may not serialize correctly -- **Platform-specific types**: Types that exist only in one language and cannot be translated +- **Union types**: TypeScript union types like `string | number` cannot be serialized. +- **Functions/callbacks**: Functions cannot be passed as component arguments. +- **Complex generic types**: Deeply nested or complex generic types may not serialize correctly. +- **Platform-specific types**: Types that exist only in one language and cannot be translated. **Example of unsupported TypeScript types:** @@ -303,11 +303,11 @@ class MyComponentArgs extends ResourceArgs { When designing component arguments: -1. **Use Input types**: Always wrap your arguments in the language's input type (e.g., `pulumi.Input`) to support both plain values and outputs from other resources -1. **Keep types simple**: Stick to primitive types, arrays, and simple objects -1. **Avoid union types**: If you need multiple possible types, consider using separate properties or creating multiple component variants -1. **Document required vs. optional**: Clearly document which arguments are required and which have defaults -1. **Follow language conventions**: Use camelCase for schema properties but follow language-specific naming in implementation (snake_case in Python, PascalCase in .NET) +1. **Use Input types**: Always wrap your arguments in the language's input type (e.g., `pulumi.Input`) to support both plain values and outputs from other resources. +1. **Use basic types**: Stick to primitive types, arrays, and basic objects. +1. **Avoid union types**: If you need multiple possible types, consider using separate properties or creating multiple component variants. +1. **Document required vs. optional**: Clearly document which arguments are required and which have defaults. +1. **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 From db180bb327c6de18173c56cbd2a92aff49f7d9ea Mon Sep 17 00:00:00 2001 From: Cam Date: Tue, 21 Oct 2025 15:23:59 +0000 Subject: [PATCH 3/4] Review feedback --- .../docs/iac/concepts/components/_index.md | 38 ++++++++----------- .../components/build-a-component.md | 2 +- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/content/docs/iac/concepts/components/_index.md b/content/docs/iac/concepts/components/_index.md index c0dd1071d907..299f6853203f 100644 --- a/content/docs/iac/concepts/components/_index.md +++ b/content/docs/iac/concepts/components/_index.md @@ -156,10 +156,15 @@ The following types are supported in component arguments: The following types are not supported in component arguments: -- **Union types**: TypeScript union types like `string | number` cannot be serialized -- **Functions/callbacks**: Functions cannot be passed as component arguments -- **Complex generic types**: Deeply nested or complex generic types may not serialize correctly -- **Platform-specific types**: Types that exist only in one language and cannot be translated +- **Union types**: TypeScript union types like `string | number` are not supported due to limitations in schema inference. +- **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:** @@ -181,21 +186,8 @@ export interface MyComponentArgs { Each language has specific requirements for component constructors to ensure proper schema generation: -{{< chooser language "javascript,typescript,python,go,csharp,java" >}} - -{{% choosable language javascript %}} - -JavaScript components should follow the same pattern as TypeScript, with the constructor accepting `name`, `args`, and `opts` parameters. - -```javascript -class MyComponent extends pulumi.ComponentResource { - constructor(name, args, opts) { - super("pkg:index:MyComponent", name, {}, opts); - } -} -``` +{{< chooser language "typescript,python,go,csharp,java" >}} -{{% /choosable %}} {{% choosable language typescript %}} **Requirements:** @@ -303,11 +295,11 @@ class MyComponentArgs extends ResourceArgs { When designing component arguments: -1. **Use Input types**: Always wrap your arguments in the language's input type (e.g., `pulumi.Input`) to support both plain values and outputs from other resources -1. **Keep types simple**: Stick to primitive types, arrays, and simple objects -1. **Avoid union types**: If you need multiple possible types, consider using separate properties or creating multiple component variants -1. **Document required vs. optional**: Clearly document which arguments are required and which have defaults -1. **Follow language conventions**: Use camelCase for schema properties but follow language-specific naming in implementation (snake_case in Python, PascalCase in .NET) +1. **Wrap all scalar members in Input types**: Every scalar argument should be wrapped in the language's input type (e.g., `pulumi.Input`). This allows users to pass both plain values and outputs from other resources, avoiding the need to use `apply` for resource composition. +1. **Use basic types**: Stick to primitive types, arrays, and basic objects. +1. **Avoid union types**: If you need multiple possible types, consider using separate properties or creating multiple component variants. +1. **Document required vs. optional**: Clearly document which arguments are required and which have defaults. +1. **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 diff --git a/content/docs/iac/guides/building-extending/components/build-a-component.md b/content/docs/iac/guides/building-extending/components/build-a-component.md index c0ee2261cc51..3f5b1e37d6c4 100644 --- a/content/docs/iac/guides/building-extending/components/build-a-component.md +++ b/content/docs/iac/guides/building-extending/components/build-a-component.md @@ -661,7 +661,7 @@ export interface StaticPageArgs { } ``` -Note that argument classes must be *serializable* and use `pulumi.Input` types, rather than the language's default types. This means certain types like union types (e.g., `string | number`) and functions are not supported. For details on type requirements and limitations, see [Component arguments and type requirements](/docs/iac/concepts/components/#component-arguments-and-type-requirements). +Note that argument classes must be *serializable* and use `pulumi.Input` types, rather than the language's default types. Certain types like union types (e.g., `string | number`) and functions are not supported due to schema inference limitations. For details on type requirements and limitations, see [Component arguments and type requirements](/docs/iac/concepts/components/#component-arguments-and-type-requirements). {{% /choosable %}} From 33adabe7b938784461114af86023572309347f4d Mon Sep 17 00:00:00 2001 From: Cam Soper Date: Wed, 22 Oct 2025 11:45:25 -0500 Subject: [PATCH 4/4] Update content/docs/iac/concepts/components/_index.md Co-authored-by: Josh Kodroff --- content/docs/iac/concepts/components/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/iac/concepts/components/_index.md b/content/docs/iac/concepts/components/_index.md index e37a38db6301..84b82b3f2081 100644 --- a/content/docs/iac/concepts/components/_index.md +++ b/content/docs/iac/concepts/components/_index.md @@ -297,7 +297,7 @@ 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`). This allows users to pass both plain values and outputs from other resources, avoiding the need to use `apply` for resource composition. 1. **Use basic types**: Stick to primitive types, arrays, and basic objects. -1. **Avoid union types**: If you need multiple possible types, consider using separate properties or creating multiple component variants. +1. **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. 1. **Document required vs. optional**: Clearly document which arguments are required and which have defaults. 1. **Follow language conventions**: Use camelCase for schema properties but follow language-specific naming in implementation (snake_case in Python, PascalCase in .NET).