Skip to main content
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 enables you to manage Connectors, Resources, Policies, 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:
  • TypeScript/JavaScript
  • Python
  • Go
  • C#
npm install @formalco/pulumi

Authentication

Create an API key in the Formal console:
  1. Navigate to 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:
pulumi config set formal:apiKey <YOUR_API_KEY> --secret
Verify:
pulumi config get formal:apiKey
# Expected: <YOUR_API_KEY>
Alternatively, set as an environment variable:
export FORMAL_API_KEY="<YOUR_API_KEY>"

Examples

Full Production Stack

  • TypeScript
  • Python
  • Go
  • C#
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

post_request := {
"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_ACCOUNT_ID>",
        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
};

Resource Documentation

Full documentation for all resources: Core Objects: Integrations:

Example Repositories

Formal provides complete Pulumi examples: Clone and customize for your needs:
git clone https://github.com/formalco/pulumi-formal.git
cd pulumi-formal/examples/deployments/aws/connector

Best Practices

Pin provider versions to avoid unexpected changes:TypeScript:
{
  "dependencies": {
    "@formalco/pulumi": "^1.0.0"
  }
}
Python:
pulumi-formal==1.0.0
Use Pulumi Cloud or self-hosted backends for team collaboration:
pulumi login https://api.pulumi.com
pulumi stack init production
Parameterize configurations for reusability:
pulumi config set environment production
pulumi config set formal:apiKey <KEY> --secret
Access in code:
const config = new pulumi.Config();
const environment = config.require("environment");
Use separate stacks for prod/staging/dev:
pulumi stack init production
pulumi stack init staging
pulumi stack init development
Protect production resources:
const resource = new formal.Resource("prod_db", {
    name: "production-postgres",
    terminationProtection: true,
    // ...
});

Importing Existing Resources

Bring existing Formal objects under Pulumi management:
# 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:
  • TypeScript
  • Python
  • Go
  • C#
export const connectorToken = productionConnector.apiKey;
export const connectorListeners = productionConnector.listener.map(l => l.port);
export const resourceIds = {
    postgres: postgresResource.id
};

Troubleshooting

Possible causes:
  • Invalid API key
  • API key not set correctly
Solution:
  1. Verify API key in Formal console
  2. Check configuration: pulumi config get formal:apiKey
  3. Ensure no whitespace in key
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
Possible causes:
  • Invalid resource configuration
  • Missing required fields
Solution:
  1. Check API documentation
  2. Verify all required fields are set
  3. Check resource naming conventions

Next Steps