> ## Documentation Index
> Fetch the complete documentation index at: https://docs.formal.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Pulumi

> Manage Formal infrastructure as code with the Pulumi provider

export const G = ({term, anchor, children}) => {
  const href = anchor ? `/docs/glossary/index#${anchor}` : `/docs/glossary/index`;
  return <a href={href} className="glossary-link" style={{
    textDecoration: "underline",
    textDecorationLine: "underline",
    textDecorationColor: "#6b7280",
    textDecorationThickness: "1px",
    textUnderlineOffset: "2px",
    color: "inherit",
    transition: "text-decoration-color 0.2s ease",
    borderBottom: "none"
  }} onMouseEnter={e => e.target.style.textDecorationColor = "#fff"} onMouseLeave={e => e.target.style.textDecorationColor = "#6b7280"}>
  {children || term}
</a>;
};

> **Outcome:** After this guide, you can manage Formal resources using Pulumi infrastructure-as-code.\
> **Prerequisites:** Pulumi CLI, Formal API key, programming language environment (TypeScript, Python, Go, or C#).

## Overview

The [Formal Pulumi Provider](https://www.pulumi.com/registry/packages/formal/) enables you to manage <G anchor="connector">Connectors</G>, <G anchor="resource">Resources</G>, <G anchor="policy">Policies</G>, Users, and all other Formal objects as infrastructure-as-code using your preferred programming language.

## Installation

The Formal provider is available for multiple programming languages:

<Tabs>
  <Tab title="TypeScript/JavaScript">
    ```bash theme={null}
    npm install @formalco/pulumi
    ```
  </Tab>

  <Tab title="Python">
    ```bash theme={null}
    pip install pulumi-formal
    ```
  </Tab>

  <Tab title="Go">
    ```bash theme={null}
    go get github.com/formalco/pulumi-formal/sdk/go/formal
    ```
  </Tab>

  <Tab title="C#">
    ```bash theme={null}
    dotnet add package Formal.Pulumi
    ```
  </Tab>
</Tabs>

## Authentication

Create an API key in the [Formal console](https://app.formal.ai/api-keys):

1. Navigate to [API Keys](https://app.formal.ai/api-keys)
2. Click **Create API Key**
3. Name the key (e.g., "pulumi-production")
4. Copy the key immediately

Set the key using Pulumi's configuration system:

```bash theme={null}
pulumi config set formal:apiKey <YOUR_API_KEY> --secret
```

**Verify:**

```bash theme={null}
pulumi config get formal:apiKey
# Expected: <YOUR_API_KEY>
```

Alternatively, set as an environment variable:

```bash theme={null}
export FORMAL_API_KEY="<YOUR_API_KEY>"
```

## Examples

### Full Production Stack

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import * as pulumi from "@pulumi/pulumi";
    import * as formal from "@formalco/pulumi";

    // Create a Space
    const productionSpace = new formal.Space("production", {
        name: "production"
    });

    // Create a Resource
    const postgresResource = new formal.Resource("postgres", {
        name: "production-postgres",
        technology: "postgres",
        hostname: "prod-db.us-east-1.rds.amazonaws.com",
        port: 5432,
        spaceId: productionSpace.id,
        terminationProtection: true
    });

    // Create a Connector
    const productionConnector = new formal.Connector("production", {
        name: "production-connector",
        spaceId: productionSpace.id,
        listener: [{
            port: 5432,
            rule: [{
                type: "resource",
                resourceId: postgresResource.id
            }]
        }]
    });

    // Create Users
    const aliceUser = new formal.User("alice", {
        name: "Alice Smith",
        email: "alice@example.com",
        type: "human"
    });

    const lookerApp = new formal.User("looker_app", {
        name: "Looker Application",
        type: "machine"
    });

    // Create Groups
    const engineeringGroup = new formal.Group("engineering", {
        name: "Engineering Team"
    });

    const aliceMembership = new formal.GroupMembership("alice_eng", {
        groupId: engineeringGroup.id,
        userId: aliceUser.id
    });

    // Create a Policy
    const maskPiiPolicy = new formal.Policy("mask_pii", {
        name: "mask-pii-data",
        description: "Mask PII fields for non-privileged users",
        status: "active",
        owners: ["admin@example.com"],
        code: `package formal.v2

    import future.keywords.if
    import future.keywords.in

    response := {
    "action": "mask",
    "type": "nullify",
    "columns": pii_columns
    } if {
    not "pii_access" in input.user.groups

    pii_columns := [col |
    col := input.row[_]
    col["data_label"] in ["email", "ssn", "phone"]
    ]

    count(pii_columns) > 0
    }`
    });

    // AWS Integration
    const awsIntegration = new formal.IntegrationCloud("aws", {
        name: "aws-production",
        cloudRegion: "us-east-1",
        aws: {
            templateVersion: "1.2.0",
            enableRdsAutodiscovery: true,
            allowS3Access: true,
            s3BucketArn: "<S3_BUCKET_ARN>/*"
        }
    });

    // Log Integration
    const datadogIntegration = new formal.IntegrationLog("datadog", {
        name: "datadog-logs",
        datadog: {
            accountId: "<DATADOG_APPLICATION_KEY>",
            apiKey: "<DATADOG_API_KEY>",
            site: "datadoghq.com"
        }
    });

    // Export outputs
    export const connectorToken = productionConnector.apiKey;
    export const connectorListeners = productionConnector.listener.map(l => l.port);
    export const resourceIds = {
        postgres: postgresResource.id
    };
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import pulumi
    import pulumi_formal as formal

    # Create a Space
    production_space = formal.Space("production",
        name="production"
    )

    # Create a Resource
    postgres_resource = formal.Resource("postgres",
        name="production-postgres",
        technology="postgres",
        hostname="prod-db.us-east-1.rds.amazonaws.com",
        port=5432,
        space_id=production_space.id,
        termination_protection=True
    )

    # Create a Connector
    production_connector = formal.Connector("production",
        name="production-connector",
        space_id=production_space.id,
        listener=[formal.ConnectorListenerArgs(
            port=5432,
            rule=[formal.ConnectorListenerRuleArgs(
                type="resource",
                resource_id=postgres_resource.id
            )]
        )]
    )

    # Create Users
    alice_user = formal.User("alice",
        name="Alice Smith",
        email="alice@example.com",
        type="human"
    )

    looker_app = formal.User("looker_app",
        name="Looker Application",
        type="machine"
    )

    # Create Groups
    engineering_group = formal.Group("engineering",
        name="Engineering Team"
    )

    alice_membership = formal.GroupMembership("alice_eng",
        group_id=engineering_group.id,
        user_id=alice_user.id
    )

    # Create a Policy
    mask_pii_policy = formal.Policy("mask_pii",
        name="mask-pii-data",
        description="Mask PII fields for non-privileged users",
        status="active",
        owners=["admin@example.com"],
        code="""package formal.v2

    import future.keywords.if
    import future.keywords.in

    response := {
    "action": "mask",
    "type": "nullify",
    "columns": pii_columns
    } if {
    not "pii_access" in input.user.groups

    pii_columns := [col |
    col := input.row[_]
    col["data_label"] in ["email", "ssn", "phone"]
    ]

    count(pii_columns) > 0
    }"""
    )

    # AWS Integration
    aws_integration = formal.IntegrationCloud("aws",
        name="aws-production",
        cloud_region="us-east-1",
        aws=formal.IntegrationCloudAwsArgs(
            template_version="1.2.0",
            enable_rds_autodiscovery=True,
            allow_s3_access=True,
            s3_bucket_arn="<S3_BUCKET_ARN>/*"
        )
    )

    # Log Integration
    datadog_integration = formal.IntegrationLog("datadog",
        name="datadog-logs",
        datadog=formal.IntegrationLogDatadogArgs(
            account_id="<DATADOG_APPLICATION_KEY>",
            api_key="<DATADOG_API_KEY>",
            site="datadoghq.com"
        )
    )

    # Export outputs
    pulumi.export("connector_token", production_connector.api_key)
    pulumi.export("connector_listeners", [l.port for l in production_connector.listener])
    pulumi.export("resource_ids", {
        "postgres": postgres_resource.id
    })
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    package main

    import (
        "github.com/pulumi/pulumi/sdk/v3/go/pulumi"
        formal "github.com/formalco/pulumi-formal/sdk/go/formal"
    )

    func main() {
        pulumi.Run(func(ctx *pulumi.Context) error {
            // Create a Space
            productionSpace, err := formal.NewSpace(ctx, "production", &formal.SpaceArgs{
                Name: pulumi.String("production"),
            })
            if err != nil {
                return err
            }

            // Create a Resource
            postgresResource, err := formal.NewResource(ctx, "postgres", &formal.ResourceArgs{
                Name:                 pulumi.String("production-postgres"),
                Technology:           pulumi.String("postgres"),
                Hostname:             pulumi.String("prod-db.us-east-1.rds.amazonaws.com"),
                Port:                 pulumi.Int(5432),
                SpaceId:              productionSpace.ID(),
                TerminationProtection: pulumi.Bool(true),
            })
            if err != nil {
                return err
            }

            // Create a Connector
            productionConnector, err := formal.NewConnector(ctx, "production", &formal.ConnectorArgs{
                Name:   pulumi.String("production-connector"),
                SpaceId: productionSpace.ID(),
                Listener: formal.ConnectorListenerArray{
                    &formal.ConnectorListenerArgs{
                        Port: pulumi.Int(5432),
                        Rule: formal.ConnectorListenerRuleArray{
                            &formal.ConnectorListenerRuleArgs{
                                Type:       pulumi.String("resource"),
                                ResourceId: postgresResource.ID(),
                            },
                        },
                    },
                },
            })
            if err != nil {
                return err
            }

            // Create Users
            aliceUser, err := formal.NewUser(ctx, "alice", &formal.UserArgs{
                Name:  pulumi.String("Alice Smith"),
                Email: pulumi.String("alice@example.com"),
                Type:  pulumi.String("human"),
            })
            if err != nil {
                return err
            }

            lookerApp, err := formal.NewUser(ctx, "looker_app", &formal.UserArgs{
                Name: pulumi.String("Looker Application"),
                Type: pulumi.String("machine"),
            })
            if err != nil {
                return err
            }

            // Create Groups
            engineeringGroup, err := formal.NewGroup(ctx, "engineering", &formal.GroupArgs{
                Name: pulumi.String("Engineering Team"),
            })
            if err != nil {
                return err
            }

            _, err = formal.NewGroupMembership(ctx, "alice_eng", &formal.GroupMembershipArgs{
                GroupId: engineeringGroup.ID(),
                UserId:  aliceUser.ID(),
            })
            if err != nil {
                return err
            }

            // Create a Policy
            maskPiiPolicy, err := formal.NewPolicy(ctx, "mask_pii", &formal.PolicyArgs{
                Name:        pulumi.String("mask-pii-data"),
                Description: pulumi.String("Mask PII fields for non-privileged users"),
                Status:      pulumi.String("active"),
                Owners:      pulumi.StringArray{pulumi.String("admin@example.com")},
                Code: pulumi.String(`package formal.v2

    import future.keywords.if
    import future.keywords.in

    response := {
      "action": "mask",
      "type": "nullify",
      "columns": pii_columns
    } if {
      not "pii_access" in input.user.groups

      pii_columns := [col |
        col := input.row[_]
        col["data_label"] in ["email", "ssn", "phone"]
      ]

      count(pii_columns) > 0
    }`),
            })
            if err != nil {
                return err
            }

            // AWS Integration
            awsIntegration, err := formal.NewIntegrationCloud(ctx, "aws", &formal.IntegrationCloudArgs{
                Name:       pulumi.String("aws-production"),
                CloudRegion: pulumi.String("us-east-1"),
                Aws: &formal.IntegrationCloudAwsArgs{
                    TemplateVersion:         pulumi.String("1.2.0"),
                    EnableRdsAutodiscovery:  pulumi.Bool(true),
                    AllowS3Access:          pulumi.Bool(true),
                    S3BucketArn:            pulumi.String("<S3_BUCKET_ARN>/*"),
                },
            })
            if err != nil {
                return err
            }

            // Log Integration
            datadogIntegration, err := formal.NewIntegrationLog(ctx, "datadog", &formal.IntegrationLogArgs{
                Name: pulumi.String("datadog-logs"),
                Datadog: &formal.IntegrationLogDatadogArgs{
                    AccountId: pulumi.String("<DATADOG_APPLICATION_KEY>"),
                    ApiKey:    pulumi.String("<DATADOG_API_KEY>"),
                    Site:      pulumi.String("datadoghq.com"),
                },
            })
            if err != nil {
                return err
            }

            // Export outputs
            ctx.Export("connectorToken", productionConnector.ApiKey())
            ctx.Export("connectorListeners", pulumi.ToStringArray([]pulumi.StringOutput{
                productionConnector.Listener().Index(pulumi.Int(0)).Port(),
            }))
            ctx.Export("resourceIds", pulumi.StringMap{
                "postgres": postgresResource.ID(),
            })

            return nil
        })
    }
    ```
  </Tab>

  <Tab title="C#">
    ```csharp theme={null}
    using System.Collections.Generic;
    using Pulumi;
    using Formal.Pulumi;

    return await Deployment.RunAsync(() =>
    {
        // Create a Space
        var productionSpace = new Space("production", new SpaceArgs
        {
            Name = "production"
        });

        // Create a Resource
        var postgresResource = new Resource("postgres", new ResourceArgs
        {
            Name = "production-postgres",
            Technology = "postgres",
            Hostname = "prod-db.us-east-1.rds.amazonaws.com",
            Port = 5432,
            SpaceId = productionSpace.Id,
            TerminationProtection = true
        });

        // Create a Connector
        var productionConnector = new Connector("production", new ConnectorArgs
        {
            Name = "production-connector",
            SpaceId = productionSpace.Id,
            Listener = new[]
            {
                new ConnectorListenerArgs
                {
                    Port = 5432,
                    Rule = new[]
                    {
                        new ConnectorListenerRuleArgs
                        {
                            Type = "resource",
                            ResourceId = postgresResource.Id
                        }
                    }
                }
            }
        });

        // Create Users
        var aliceUser = new User("alice", new UserArgs
        {
            Name = "Alice Smith",
            Email = "alice@example.com",
            Type = "human"
        });

        var lookerApp = new User("looker_app", new UserArgs
        {
            Name = "Looker Application",
            Type = "machine"
        });

        // Create Groups
        var engineeringGroup = new Group("engineering", new GroupArgs
        {
            Name = "Engineering Team"
        });

        var aliceMembership = new GroupMembership("alice_eng", new GroupMembershipArgs
        {
            GroupId = engineeringGroup.Id,
            UserId = aliceUser.Id
        });

        // Create a Policy
        var maskPiiPolicy = new Policy("mask_pii", new PolicyArgs
        {
            Name = "mask-pii-data",
            Description = "Mask PII fields for non-privileged users",
            Status = "active",
            Owners = new[] { "admin@example.com" },
            Code = @"package formal.v2

    import future.keywords.if
    import future.keywords.in

    response := {
    ""action"": ""mask"",
    ""type"": ""nullify"",
    ""columns"": pii_columns
    } if {
    not ""pii_access"" in input.user.groups

    pii_columns := [col |
    col := input.row[_]
    col[""data_label""] in [""email"", ""ssn"", ""phone""]
    ]

    count(pii_columns) > 0
    }"
        });

        // AWS Integration
        var awsIntegration = new IntegrationCloud("aws", new IntegrationCloudArgs
        {
            Name = "aws-production",
            CloudRegion = "us-east-1",
            Aws = new IntegrationCloudAwsArgs
            {
                TemplateVersion = "1.2.0",
                EnableRdsAutodiscovery = true,
                AllowS3Access = true,
                S3BucketArn = "<S3_BUCKET_ARN>/*"
            }
        });

        // Log Integration
        var datadogIntegration = new IntegrationLog("datadog", new IntegrationLogArgs
        {
            Name = "datadog-logs",
            Datadog = new IntegrationLogDatadogArgs
            {
                AccountId = "<DATADOG_APPLICATION_KEY>",
                ApiKey = "<DATADOG_API_KEY>",
                Site = "datadoghq.com"
            }
        });

        // Export outputs
        return new Dictionary<string, object?>
        {
            ["connectorToken"] = productionConnector.ApiKey,
            ["connectorListeners"] = new[] { productionConnector.Listener[0].Port },
            ["resourceIds"] = new Dictionary<string, object?>
            {
                ["postgres"] = postgresResource.Id
            }
        };
    });
    ```
  </Tab>
</Tabs>

## Resource Documentation

Full documentation for all resources:

**Core Objects:**

* [Connector](https://www.pulumi.com/registry/packages/formal/api-docs/connector/)
* [Resource](https://www.pulumi.com/registry/packages/formal/api-docs/resource/)
* [User](https://www.pulumi.com/registry/packages/formal/api-docs/user/)
* [Group](https://www.pulumi.com/registry/packages/formal/api-docs/group/)
* [Policy](https://www.pulumi.com/registry/packages/formal/api-docs/policy/)
* [Space](https://www.pulumi.com/registry/packages/formal/api-docs/space/)
* [Satellite](https://www.pulumi.com/registry/packages/formal/api-docs/satellite/)

**Integrations:**

* [IntegrationCloud](https://www.pulumi.com/registry/packages/formal/api-docs/integrationcloud/)
* [IntegrationLog](https://www.pulumi.com/registry/packages/formal/api-docs/integrationlog/)
* [IntegrationBi](https://www.pulumi.com/registry/packages/formal/api-docs/integrationbi/)

## Example Repositories

Clone and customize for your needs:

```bash theme={null}
git clone https://github.com/formalco/pulumi-formal.git
```

## Best Practices

<AccordionGroup>
  <Accordion title="Use Version Constraints" icon="lock">
    Pin provider versions to avoid unexpected changes:

    **TypeScript:**

    ```json theme={null}
    {
      "dependencies": {
        "@formalco/pulumi": "^1.0.0"
      }
    }
    ```

    **Python:**

    ```txt theme={null}
    pulumi-formal==1.0.0
    ```
  </Accordion>

  <Accordion title="Store State Remotely" icon="cloud">
    Use Pulumi Cloud or self-hosted backends for team collaboration:

    ```bash theme={null}
    pulumi login https://api.pulumi.com
    pulumi stack init production
    ```
  </Accordion>

  <Accordion title="Use Configuration" icon="input-text">
    Parameterize configurations for reusability:

    ```bash theme={null}
    pulumi config set environment production
    pulumi config set formal:apiKey <KEY> --secret
    ```

    Access in code:

    ```typescript theme={null}
    const config = new pulumi.Config();
    const environment = config.require("environment");
    ```
  </Accordion>

  <Accordion title="Separate Environments" icon="layer-group">
    Use separate stacks for prod/staging/dev:

    ```bash theme={null}
    pulumi stack init production
    pulumi stack init staging
    pulumi stack init development
    ```
  </Accordion>

  <Accordion title="Enable Termination Protection" icon="shield">
    Protect production resources:

    ```typescript theme={null}
    const resource = new formal.Resource("prod_db", {
        name: "production-postgres",
        terminationProtection: true,
        // ...
    });
    ```
  </Accordion>
</AccordionGroup>

## Importing Existing Resources

Bring existing Formal objects under Pulumi management:

```bash theme={null}
# Import a Resource
pulumi import formal:index/resource:Resource postgres <resource-id>

# Import a Connector
pulumi import formal:index/connector:Connector main <connector-id>

# Import a User
pulumi import formal:index/user:User alice <user-id>
```

Get object IDs from the Formal console or API.

## Outputs

Export useful information:

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    export const connectorToken = productionConnector.apiKey;
    export const connectorListeners = productionConnector.listener.map(l => l.port);
    export const resourceIds = {
        postgres: postgresResource.id
    };
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    pulumi.export("connector_token", production_connector.api_key)
    pulumi.export("connector_listeners", [l.port for l in production_connector.listener])
    pulumi.export("resource_ids", {
        "postgres": postgres_resource.id
    })
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    ctx.Export("connectorToken", productionConnector.ApiKey())
    ctx.Export("connectorListeners", pulumi.ToStringArray([]pulumi.StringOutput{
        productionConnector.Listener().Index(pulumi.Int(0)).Port(),
    }))
    ctx.Export("resourceIds", pulumi.StringMap{
        "postgres": postgresResource.ID(),
    })
    ```
  </Tab>

  <Tab title="C#">
    ```csharp theme={null}
    return new Dictionary<string, object?>
    {
        ["connectorToken"] = productionConnector.ApiKey,
        ["connectorListeners"] = new[] { productionConnector.Listener[0].Port },
        ["resourceIds"] = new Dictionary<string, object?>
        {
            ["postgres"] = postgresResource.Id
        }
    };
    ```
  </Tab>
</Tabs>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Authentication failed">
    **Possible causes:**

    * Invalid API key
    * API key not set correctly

    **Solution:**

    1. Verify API key in [Formal console](https://app.formal.ai/api-keys)
    2. Check configuration: `pulumi config get formal:apiKey`
    3. Ensure no whitespace in key
  </Accordion>

  <Accordion title="Provider not found">
    **Possible causes:**

    * Package not installed
    * Wrong package name

    **Solution:**

    1. Install correct package for your language
    2. Verify import statements
    3. Check package.json/requirements.txt
  </Accordion>

  <Accordion title="Resource creation fails">
    **Possible causes:**

    * Invalid resource configuration
    * Missing required fields

    **Solution:**

    1. Check [API documentation](https://www.pulumi.com/registry/packages/formal/)
    2. Verify all required fields are set
    3. Check resource naming conventions
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Provider Docs" icon="book" href="https://www.pulumi.com/registry/packages/formal/">
    Complete Pulumi provider documentation
  </Card>

  <Card title="Examples" icon="code" href="https://github.com/formalco/pulumi-formal">
    Deployment examples on GitHub
  </Card>

  <Card title="Pulumi Cloud" icon="cloud" href="https://app.pulumi.com">
    Manage state and collaborate
  </Card>

  <Card title="Terraform Provider" icon="code" href="/docs/guides/configuration/terraform">
    Alternative IaC solution
  </Card>
</CardGroup>
