1. Docs
  2. Infrastructure as Code
  3. Guides
  4. Building & Extending
  5. Providers
  6. Provider architecture

Provider architecture

    A Pulumi provider allows you to define new resource types, enabling integration with virtually any service or tool. Providers are how Pulumi communicates with cloud platforms, SaaS APIs, and other external systems to create, read, update, and delete resources.

    Pulumi also supports dynamic providers, which are lightweight providers defined inline within a Pulumi program. This page focuses on standalone providers that are built and distributed as separate packages.

    Pulumi providers are built on a layered architecture. Understanding these layers helps you choose the right approach for your needs—from high-level SDKs that handle complexity for you, to low-level protocol access for maximum control.

    The three layers

    ┌─────────────────────────────────────────┐
    │      Higher-level SDKs (Layer 3)        │  Convenience + opinions
    │         e.g., pulumi-go-provider        │  Schema inference, middleware
    ├─────────────────────────────────────────┤
    │   Generated language bindings (Layer 2) │  Direct implementation
    │      Go, Python, TypeScript, etc.       │  Full control, any language
    ├─────────────────────────────────────────┤
    │     gRPC/Protobuf protocol (Layer 1)    │  The wire format
    │           provider.proto                │  Language-agnostic contract
    └─────────────────────────────────────────┘
    

    Layer 1: The provider protocol

    At the foundation is the provider protocol, defined using gRPC and Protocol Buffers. This specifies exactly how the Pulumi engine communicates with providers.

    The protocol defines approximately 20 RPC methods. The most important are Configure for initializing the provider with credentials and settings, GetSchema for returning the provider’s resource and type definitions, Check for validating and transforming resource inputs, Diff for computing changes between current and desired state, and the CRUD operations Create, Read, Update, and Delete for managing resource lifecycle.

    This layer is language-agnostic. Any language that can implement a gRPC server can implement a Pulumi provider.

    For protocol details, see Provider protocol reference.

    Layer 2: Generated language bindings

    The Protocol Buffers definition generates native classes and interfaces for each language—Go, Python, TypeScript, Java, C#, and any other language with gRPC support. These generated bindings give you direct access to the provider protocol in idiomatic code for your language.

    At this layer, you implement the provider interface directly. You write your schema JSON by hand, giving you complete control over resource definitions, property types, and how data structures map between Pulumi and your provider. This explicit control over the schema is particularly valuable when you need precise type mappings or want to define resources that don’t fit standard patterns.

    Choose this layer when you need a provider in a language without a higher-level SDK, when you want full control over your schema and data structure mappings, when you’re building something unusual that doesn’t fit SDK patterns, or when you prefer explicit protocol handling over framework abstractions.

    For implementation guides, see Direct implementation in Python, Direct implementation in Go, or Direct implementation in TypeScript.

    Layer 3: Higher-level SDKs

    SDKs build on the language bindings to provide a more ergonomic development experience. Currently, the Pulumi Go Provider SDK is available for Go.

    The Go SDK provides schema inference from Go structs and tags, default implementations for most methods, middleware support for cross-cutting concerns, a testing framework for provider validation, and automatic multi-language SDK generation.

    Choose this layer when you’re writing a provider in Go and want the fastest path to a working provider. It’s ideal when you prefer convention over configuration and want automatic schema generation.

    Choosing your layer

    ConsiderationLayer 2 (Direct)Layer 3 (SDK)
    LanguageAny with gRPC supportGo only (currently)
    SchemaHand-written JSONInferred from code
    BoilerplateMoreLess
    ControlFullFramework-guided
    Learning curveProtocol knowledge requiredGo SDK patterns

    Choosing a language

    Many teams prefer to write providers in their standard language, giving them access to familiar tools and libraries. However, Go has a significant advantage: providers written in Go compile to standalone binaries with no runtime dependencies. Users of your provider can consume it from any Pulumi language without installing Go.

    Providers written in Python or TypeScript require their respective runtimes to be installed, which adds friction for users. You can bundle Python providers with PyInstaller or TypeScript providers with pkg to create standalone executables, though this adds build complexity. That said, if your team is more productive in Python or TypeScript, the familiar ecosystem and libraries may outweigh the runtime dependency—especially for internal providers where you control the deployment environment.

    When to use Layer 2 (direct implementation)

    Direct implementation is the right choice when you want complete control over your provider’s schema and how data structures are mapped. It’s also the path for languages without a higher-level SDK, such as Python or TypeScript. Choose this layer when you need precise control over Check/Diff/Update behavior, when your provider doesn’t fit standard CRUD semantics, or when you want to understand exactly how providers work at the protocol level.

    When to use Layer 3 (SDK)

    The SDK approach is ideal for Go providers where the SDK handles most complexity for you. It works well when your resources follow typical CRUD patterns, when you want automatic SDK generation for multiple languages, and when you want built-in testing infrastructure.

    What all providers need

    Regardless of which layer you choose, every provider needs a schema defining resources, their inputs, outputs, and configuration. It also needs CRUD implementations for each resource type, configuration handling for credentials and settings, and proper diffing to detect what changed.

    The schema can be hand-written JSON when using Layer 2, or inferred from code when using the Layer 3 SDK. See Schema reference for the complete schema specification.

    Next steps

    To get started quickly with the Go SDK, see Build a provider. For direct implementation, start with the Protocol reference. For Python providers specifically, see Direct implementation in Python. For schema details, see Schema reference.

      Neo just got smarter about infrastructure policy automation