---
title: Write your own
url: /docs/insights/policy/policy-packs/authoring/
---
If Pulumi's pre-built policy packs don't meet your requirements, you can write custom policy packs. Custom policies let you enforce any compliance, security, or operational rule.

Policies can be written in TypeScript/JavaScript (Node.js), Python, or OPA (Rego) and can be applied to Pulumi stacks written in any language. Learn more about [language support for policies](/docs/insights/policy/#languages).

### Creating a Policy Pack with Neo

This guide walks you through creating a policy pack manually, but [Neo](/product/neo/) can help streamline the process.  

Neo can generate policy pack content tailored to your preferred programming language and cloud providers, allowing you to quickly build policies that meet your specific requirements while reducing errors. When paired with the [GitHub App](/docs/iac/guides/continuous-delivery/github-app/), Neo can even open pull requests directly in your repository.  

Here are some example prompts to inspire your workflow:

> "Create a boilerplate TypeScript policy pack at `<GitHub Repository>`"  
> "Create a policy to enforce encryption of S3 buckets"  
> "Create a policy that requires environment tagging on all Google Cloud resources"

## Prerequisites

Before authoring your first policy pack, ensure you have:

- [Pulumi CLI installed](/docs/install/).
- For TypeScript/JavaScript policies: [Node.js installed](https://nodejs.org/en/download/).
- For Python policies: [Python installed](https://python.org/downloads/).
- For OPA policies: Pulumi CLI v3.227.0+ automatically installs the OPA analyzer plugin on first use. No manual installation is needed.
- (Optional) Access to Pulumi Cloud if you want to publish and centrally manage policy packs. Not required for local policy pack usage with open source Pulumi.
- An understanding of [Policy as Code core concepts](/docs/insights/policy/).

## Creating a policy pack

Create your first policy pack:

<!-- chooser: language -->

<!-- option: typescript -->

Create a directory for your policy pack and navigate to it.
```sh
$ mkdir policypack && cd policypack

```

Create a new TypeScript project:
```sh
$ pulumi policy new aws-typescript

```

Replace the generated policy in `index.ts` with this example, which demonstrates a clearer pattern for organizational policy enforcement:
Each policy must have:

A unique name, description, and validation function
A validation function (this example uses `validateResourceOfType` to run only for AWS S3 bucket resources)
An enforcement level set at the policy pack level (applies to all policies) or per policy (overrides the pack level)

For more information on all available fields, see [policy metadata](/docs/insights/policy/policy-as-code/policy-metadata/).

```typescript
import * as aws from "@pulumi/aws";
import { PolicyPack, validateResourceOfType } from "@pulumi/policy";

// Create a new policy pack.
new PolicyPack("policy-pack-typescript", {
    // Specify the policies in the policy pack.
    policies: [{
        // The name for the policy must be unique within the pack.
        name: "s3-bucket-prefix",

        // The description should document what the policy does and why it exists.
        description: "Ensures S3 buckets use the required naming prefix.",

        // The enforcement level can be "advisory", "mandatory", or "disabled". An "advisory" enforcement level
        // simply prints a warning for users, while a "mandatory" policy will block an update from proceeding, and
        // "disabled" disables the policy from running.
        enforcementLevel: "mandatory",

        // The validateResourceOfType function allows you to filter resources. In this case, the rule only
        // applies to S3 buckets and reports a violation if the bucket prefix doesn't match the required prefix.
        validateResource: validateResourceOfType(aws.s3.Bucket, (bucket, args, reportViolation) => {
            const requiredPrefix = "mycompany-";
            const bucketPrefix = bucket.bucketPrefix || "";
            if (!bucketPrefix.startsWith(requiredPrefix)) {
                reportViolation(
                    `S3 bucket must use '${requiredPrefix}' prefix. Current prefix: '${bucketPrefix}'`);
            }
        }),
    }],
});

```

<!-- /option -->

<!-- option: python -->

Create a directory for your policy pack and navigate to it.
```sh
$ mkdir policypack && cd policypack

```

Create a new Python project:
```sh
$ pulumi policy new aws-python

```

**Virtual environment configuration**: Python policy packs use a virtual environment specified in `PulumiPolicy.yaml`. The default name is `venv`. If you use a different name (like `.venv`), update `PulumiPolicy.yaml`. See the [project file reference](/docs/insights/policy/policy-packs/project-file/) for all available settings.
```yaml
runtime:
name: python
options:
    virtualenv: .venv

```

**Using .gitignore to manage policy pack size**: Create a `.gitignore` file alongside `PulumiPolicy.yaml` to exclude unnecessary files from the published policy pack archive (`.tgz`). Add patterns to ignore Python bytecode files, virtual environments, and other development artifacts:
```
*.pyc
__pycache__/
venv/
.venv/

```

This keeps your published policy pack size small and ensures only the necessary policy code is distributed.

Replace the generated policy in `__main__.py` with this example, which demonstrates a clearer pattern for organizational policy enforcement:
Each policy must have:

A unique name, description, and validation function
An enforcement level set at the policy pack level (applies to all policies) or per policy (overrides the pack level)

```python
from pulumi_policy import (
    EnforcementLevel,
    PolicyPack,
    ReportViolation,
    ResourceValidationArgs,
    ResourceValidationPolicy,
)

# The validation function is called before each resource is created or updated.
# In this case, the rule only applies to S3 buckets and reports a violation if the
# bucket prefix doesn't match the required prefix.
REQUIRED_S3_PREFIX = "mycompany-"

def s3_bucket_prefix_validator(args: ResourceValidationArgs, report_violation: ReportViolation):
    if args.resource_type == "aws:s3/bucket:Bucket" and "bucketPrefix" in args.props:
        actual_prefix = args.props["bucketPrefix"]
        if not actual_prefix.startswith(REQUIRED_S3_PREFIX):
            report_violation(
                f"S3 bucket must use '{REQUIRED_S3_PREFIX}' prefix. Current prefix: '{actual_prefix}'")

s3_bucket_prefix = ResourceValidationPolicy(
    # The name for the policy must be unique within the pack.
    name="s3-bucket-prefix",

    # The description should document what the policy does and why it exists.
    description="Ensures S3 buckets use the required naming prefix.",

    # The enforcement level can be ADVISORY, MANDATORY, or DISABLED. An ADVISORY enforcement level
    # simply prints a warning for users, while a MANDATORY policy will block an update from proceeding, and
    # DISABLED disables the policy from running.
    enforcement_level=EnforcementLevel.MANDATORY,

    # The validation function, defined above.
    validate=s3_bucket_prefix_validator,
)

# Create a new policy pack.
PolicyPack(
    name="policy-pack-python",
    # Specify the policies in the policy pack.
    policies=[s3_bucket_prefix,
    ],
)

```

<!-- /option -->

<!-- option: opa -->

Create a directory for your policy pack and navigate to it.
```sh
$ mkdir policypack && cd policypack

```

Create a new OPA project:
```sh
$ pulumi policy new aws-opa

```

This creates a `PulumiPolicy.yaml` (with `runtime: opa`) and a starter `policy.rego` file. Templates are available for AWS (`aws-opa`), Azure (`azure-opa`), GCP (`gcp-opa`), and Kubernetes (`kubernetes-opa`).

Replace the generated policy in `policy.rego` with this example, which demonstrates metadata annotations and multiple rules:
Each resource is passed as `input` with metadata fields like `__name` (logical name), `__urn`, and `type`, plus all resource properties at the top level.
Use [OPA metadata annotations](https://www.openpolicyagent.org/docs/latest/policy-reference/#annotations) (`# METADATA` comment blocks) to provide a `title`, `description`, and remediation guidance (`custom.message`) for each rule. The analyzer extracts these annotations and reports them to Pulumi:
```rego
package aws

# METADATA
# title: No Public S3 Buckets
# description: S3 buckets must not use public-read ACLs.
# custom:
#   message: Set the ACL to 'private' or remove it entirely.
deny_public_buckets[msg] {
    input.type == "aws:s3/bucket:Bucket"
    input.acl == "public-read"
    msg := sprintf("S3 bucket '%s' must not have public-read ACL", [input.__name])
}

# METADATA
# title: Require S3 Encryption
# description: All S3 buckets must have server-side encryption configured.
# custom:
#   message: Add a serverSideEncryptionConfiguration block to the bucket.
deny_encryption[msg] {
    input.type == "aws:s3/bucket:Bucket"
    not input.serverSideEncryptionConfiguration
    msg := sprintf("S3 bucket '%s' must have encryption enabled", [input.__name])
}

```

Each rule must use a recognized name prefix that determines its enforcement level: `deny` (or `violation`) for mandatory rules that block deployments, and `warn` for advisory rules. Rules can include a descriptive suffix (e.g., `deny_public_buckets`). An empty result set means the resource is compliant.

<!-- /option -->

<!-- /chooser -->

You can find more example policy packs in the [Pulumi examples repository](https://github.com/pulumi/examples/tree/master/policy-packs).

## Testing your policies

Write unit tests to verify your policies work correctly before publishing.

<!-- chooser: language -->

<!-- option: typescript -->
Here’s a simple test example using Mocha and assert:
```typescript

```typescript
describe("s3-bucket-prefix-policy", () => {
it("should pass when bucket has correct prefix", () => {
const args = getEmptyArgs();
args.type = "aws.s3.Bucket";
args.props.bucketPrefix = "mycompany-data";
assert.doesNotThrow(() => {
runResourcePolicy(s3BucketPrefixPolicy, args);
});
});

```

```

For a complete example including test helpers and setup, see the [unit test policy example on GitHub](https://github.com/pulumi/docs/tree/master/static/programs/unit-test-policy-typescript).

```

<!-- /option -->

<!-- option: python -->
Here’s a simple test example using pytest:
```python

```python
def test_bucket_with_correct_prefix():
    """Test that policy passes when bucket has correct prefix."""
    args = ResourceValidationArgs(
        resource_type="aws:s3/bucket:Bucket",
        props={"bucketPrefix": "mycompany-data"},
        urn="urn:pulumi:dev::test::aws:s3/bucket:Bucket::my-bucket",
        name="my-bucket",
        opts={},
        provider="",
    )

    # Should not raise any violations
    violations = []
    def report_violation(message: str):
        violations.append(message)

    s3_bucket_prefix_validator(args, report_violation)
    assert len(violations) == 0

```

```

For a complete example including additional test cases, see the [unit test policy example on GitHub](https://github.com/pulumi/docs/tree/master/static/programs/unit-test-policy-python).

```

<!-- /option -->

<!-- option: opa -->
OPA policies can be tested using the standard `opa test` command from the [OPA CLI](https://www.openpolicyagent.org/docs/latest/#running-opa). Create a test file (e.g., `s3_security_test.rego`) alongside your policy:
```rego
package aws

test_deny_public_buckets {
    count(deny_public_buckets) > 0 with input as {
        "type": "aws:s3/bucket:Bucket",
        "__name": "my-bucket",
        "acl": "public-read"
    }
}

test_allow_private_bucket {
    count(deny_public_buckets) == 0 with input as {
        "type": "aws:s3/bucket:Bucket",
        "__name": "my-bucket",
        "acl": "private"
    }
}

test_deny_missing_encryption {
    count(deny_encryption) > 0 with input as {
        "type": "aws:s3/bucket:Bucket",
        "__name": "my-bucket"
    }
}

```

Run the tests:
```sh
$ opa test .

```

<!-- /option -->

<!-- /chooser -->

## Resource validation vs stack validation

Pulumi policies validate at two scopes:

### Resource validation policies

Resource validation policies run during `pulumi preview` or `pulumi up`, examining each resource before creation or update. These policies execute **before** the desired state is sent to the engine, which means they can block non-compliant resources during both preview and update operations.

Use resource validation policies when you need to:

- Enforce rules on specific resource types (e.g., "S3 buckets must have encryption enabled")
- Validate resource properties before deployment
- Block individual non-compliant resources

### Stack validation policies

Stack validation policies run after resource registration completes. These policies execute **after** resources have been created or updated, and only run during `pulumi up` (not during `pulumi preview`). They examine relationships between resources and enforce stack-wide rules.

Use stack validation policies when you need to:

- Validate relationships between resources (e.g., "databases must be in private subnets")
- Enforce stack-wide rules (e.g., "stack must not exceed 50 resources")
- Examine the complete resource graph

Most policies are resource validation policies. Stack validation policies are useful for more complex scenarios that require understanding the full context of your infrastructure.

The following example limits the number of S3 buckets in a stack:

<!-- chooser: language -->

<!-- option: typescript -->
In TypeScript, use the `validateStack` callback to access all resources in the stack:
```typescript
import { PolicyPack } from "@pulumi/policy";

new PolicyPack("stack-policies", {
    policies: [{
        name: "maximum-s3-bucket-count",
        description: "Limits the number of S3 buckets per stack.",
        enforcementLevel: "mandatory",
        validateStack: (args, reportViolation) => {
            const buckets = args.resources.filter(
                r => r.type === "aws:s3/bucket:Bucket"
            );
            if (buckets.length > 3) {
                reportViolation(
                    `Stack has ${buckets.length} S3 buckets, maximum allowed is 3.`);
            }
        },
    }],
});

```

<!-- /option -->

<!-- option: python -->
In Python, use `StackValidationPolicy` to access all resources in the stack:
```python
from pulumi_policy import EnforcementLevel, PolicyPack, StackValidationPolicy

def max_bucket_count(args, report_violation):
    buckets = [r for r in args.resources if r.resource_type == "aws:s3/bucket:Bucket"]
    if len(buckets) > 3:
        report_violation(
            f"Stack has {len(buckets)} S3 buckets, maximum allowed is 3.")

PolicyPack(
    "stack-policies",
    policies=[StackValidationPolicy(
            name="maximum-s3-bucket-count",
            description="Limits the number of S3 buckets per stack.",
            enforcement_level=EnforcementLevel.MANDATORY,
            validate=max_bucket_count,
        ),
    ],
)

```

<!-- /option -->

<!-- option: opa -->
In OPA, the rule name prefix determines the validation scope. Use `stack_deny` or `stack_warn` prefixes for stack-level rules. These rules receive all resources in the stack via `input.resources`:
```rego
package aws

# METADATA
# title: Maximum S3 Bucket Count
# description: Limits the number of S3 buckets per stack.
stack_deny_too_many_buckets[msg] {
    buckets := [r | r := input.resources[_]; r.type == "aws:s3/bucket:Bucket"]
    n := count(buckets)
    n > 3
    msg := sprintf("Stack has %d S3 buckets, maximum allowed is 3", [n])
}

```

<!-- /option -->

<!-- /chooser -->

### Using stack tags in policies

Stack validation policies can access tags assigned to the stack via `args.stackTags` (TypeScript) or `args.stack_tags` (Python). This lets you enforce tagging standards, like requiring every stack to declare an environment or owning team, as a governance gate.

The following example requires `env` and `team` tags on every stack. Because `args.stackTags` only contains tags that existed before the current update, the policy also checks for [`StackTag`](/registry/packages/pulumiservice/api-docs/stacktag/) resources in the stack so it passes on the first deployment when tags are created declaratively.

<!-- chooser: language -->

<!-- option: typescript -->
```typescript
import { PolicyPack } from "@pulumi/policy";

const requiredTags = ["env", "team"];

new PolicyPack("stack-tag-policies", {
    policies: [{
        name: "require-stack-tags",
        description: "Requires 'env' and 'team' tags on every stack.",
        enforcementLevel: "mandatory",
        validateStack: (args, reportViolation) => {
            // Collect tag names set via StackTag resources in this deployment.
            const resourceTagNames = args.resources
                .filter(r => r.type === "pulumiservice:index:StackTag")
                .map(r => r.props.name as string);

            for (const tag of requiredTags) {
                const inStackTags = args.stackTags.has(tag);
                const inResources = resourceTagNames.includes(tag);
                if (!inStackTags && !inResources) {
                    reportViolation(`Missing required stack tag: '${tag}'.`);
                }
            }
        },
    }],
});

```

<!-- /option -->

<!-- option: python -->
```python
from pulumi_policy import EnforcementLevel, PolicyPack, StackValidationPolicy

REQUIRED_TAGS = ["env", "team"]

def require_stack_tags(args, report_violation):
    # Collect tag names set via StackTag resources in this deployment.
    resource_tag_names = [r.props.get("name")
        for r in args.resources
        if r.resource_type == "pulumiservice:index:StackTag"
    ]

    for tag in REQUIRED_TAGS:
        in_stack_tags = tag in args.stack_tags
        in_resources = tag in resource_tag_names
        if not in_stack_tags and not in_resources:
            report_violation(f"Missing required stack tag: '{tag}'.")

PolicyPack(
    "stack-tag-policies",
    policies=[StackValidationPolicy(
            name="require-stack-tags",
            description="Requires 'env' and 'team' tags on every stack.",
            enforcement_level=EnforcementLevel.MANDATORY,
            validate=require_stack_tags,
        ),
    ],
)

```

<!-- /option -->

<!-- /chooser -->

> **Note:** Stack tags are available on both `StackValidationArgs` and `ResourceValidationArgs`, so resource-level policies can also make decisions based on stack metadata.

You can assign tags to a stack using the CLI ([`pulumi stack tag set`](/docs/iac/cli/commands/pulumi_stack_tag_set/)), the [`pulumi:tags` config](/docs/iac/concepts/config/#pulumitags) in your `Pulumi.yaml` or `Pulumi.<stack>.yaml` file, the [`StackTag`](/registry/packages/pulumiservice/api-docs/stacktag/) resource from the [Pulumi Cloud provider](/registry/packages/pulumiservice/), the Pulumi Cloud console, or the [Stack Tags REST API](/docs/reference/cloud-rest-api/stack-tags/). To learn how to apply policy packs to groups of stacks, see [policy groups](/docs/insights/policy/policy-groups/).

> **Note:** Stack tags are not currently accessible in OPA policies. OPA stack-level policies can validate the full set of resources using `input.resources` (see [Creating a policy pack](#opa) for rule prefix conventions), but they cannot read stack tag metadata. Use TypeScript or Python policies if you need to enforce stack tag requirements.

## Writing policies for dynamic providers

[Dynamic providers](/docs/iac/concepts/providers/dynamic-providers/) allow you to create custom resource types directly in your Pulumi programs. When writing policies for dynamic providers, you need to account for a key constraint: **all dynamic resources share the same resource type** (`pulumi-nodejs:dynamic:Resource` for TypeScript/JavaScript or `pulumi-python:dynamic:Resource` for Python).

Since you cannot rely on the resource type alone to identify which dynamic provider a resource uses, you must inspect the resource's properties to differentiate between different dynamic provider implementations.

### Example: Validating a specific dynamic provider

This example shows how to write a policy that validates resources from a specific dynamic provider by checking for a unique property:

<!-- chooser: language -->

<!-- option: typescript -->
```typescript
import * as pulumi from "@pulumi/pulumi";
import { PolicyPack, ResourceValidationPolicy } from "@pulumi/policy";

new PolicyPack("dynamic-provider-policies", {
    policies: [{
        name: "environment-name-validation",
        description: "Validates that environment dynamic resources use the correct name.",
        enforcementLevel: "mandatory",
        validateResource: (args, reportViolation) => {
            // All dynamic resources in TypeScript/JavaScript have the type "pulumi-nodejs:dynamic:Resource".
            // To identify a specific dynamic provider, check for unique properties.
            if (args.type === "pulumi-nodejs:dynamic:Resource" && args.props.environmentName !== undefined) {
                const envName = args.props.environmentName;
                if (envName !== "myTestEnv") {
                    reportViolation(
                        `Environment name must be 'myTestEnv'. Current value: '${envName}'`);
                }
            }
        },
    }],
});

```

<!-- /option -->

<!-- option: python -->
```python
from pulumi_policy import (
    EnforcementLevel,
    PolicyPack,
    ReportViolation,
    ResourceValidationArgs,
    ResourceValidationPolicy,
)

def env_dynprov_check(args: ResourceValidationArgs, report_violation: ReportViolation):
    # All dynamic resources in Python have the type "pulumi-python:dynamic:Resource"
    # To identify a specific dynamic provider, check for unique properties
    # In this case, we look for resources with an "environment_name" property
    if args.resource_type == "pulumi-python:dynamic:Resource" and "environment_name" in args.props:
        environment_name = args.props["environment_name"]
        if environment_name != "myTestEnv":
            report_violation(
                f"Environment name must be 'myTestEnv'. Current value: '{environment_name}'")

dyn_prov_policy = ResourceValidationPolicy(
    name="environment-name-validation",
    description="Validates that environment dynamic resources use the correct name.",
    enforcement_level=EnforcementLevel.MANDATORY,
    validate=env_dynprov_check,
)

PolicyPack(
    name="dynamic-provider-policies",
    policies=[dyn_prov_policy,
    ],
)

```

<!-- /option -->

<!-- option: opa -->
OPA policies can validate dynamic resources using the same `input.type` field. Dynamic resources use `pulumi-nodejs:dynamic:Resource` (TypeScript/JavaScript) or `pulumi-python:dynamic:Resource` (Python) as their resource type:
```rego
package dynamic

# METADATA
# title: Environment Name Validation
# description: Dynamic environment resources must use the correct name.
# custom:
#   message: Set the environmentName property to 'myTestEnv'.
# This rule only fires for dynamic resources that have an environmentName
# property. Other dynamic resources are silently skipped.
deny_environment_name[msg] {
    input.type == "pulumi-nodejs:dynamic:Resource"
    input.environmentName
    input.environmentName != "myTestEnv"
    msg := sprintf("Environment name must be 'myTestEnv'. Current value: '%s'",
                   [input.environmentName])
}

```

<!-- /option -->

<!-- /chooser -->

### Best practices for dynamic provider policies

When writing policies for dynamic providers:

1. **Identify unique properties**: Determine which properties uniquely identify the dynamic provider you want to validate. In the example above, the `environment_name` (or `environmentName`) property indicates this is an environment resource.

1. **Be specific with property checks**: Since all dynamic resources share the same type, check for specific property names or combinations that distinguish your dynamic provider from others.

1. **Handle missing properties gracefully**: Use property existence checks (like `"environment_name" in args.props`) before accessing property values to avoid errors when the policy runs against other dynamic providers.

1. **Document your assumptions**: Clearly document which properties your policy uses to identify dynamic providers so that changes to the dynamic provider implementation don't inadvertently break policy enforcement.

## Running policies locally

Test your policy pack locally before publishing.

<!-- chooser: language -->

<!-- option: typescript -->

Use the `--policy-pack` flag to specify your policy pack directory:
If you need a test program, create one with `pulumi new aws-typescript`. This scaffolds a Pulumi program that provisions an S3 bucket you can test the policy against.
```sh
$ mkdir test-program && cd test-program
$ pulumi new aws-typescript

```

For AWS examples, ensure you have [AWS credentials configured](/registry/packages/aws/installation-configuration/) and set your region with <code>pulumi config set aws:region <region></code>.

In the Pulumi program’s directory, run:
```sh
$ pulumi preview --policy-pack 

```

If the stack is compliant, the output shows which policy packs ran.
<code class="language-output" data-lang="output">Previewing update (dev):
Type                 Name          Plan
+   pulumi:pulumi:Stack  test-dev      create
+   └─ aws:s3:Bucket     my-bucket     create

Resources:
+ 2 to create

Policy Packs run:
Name                                                 Version
aws-typescript (/Users/user/path/to/policy-pack)     (local)

```

Edit the stack code to specify a non-matching prefix:
```typescript
const bucket = new aws.s3.Bucket("my-bucket", {
bucketPrefix: "wrongprefix-",
});

```

Run `pulumi preview` again. This time, the policy violation blocks the preview:
<code class="language-output" data-lang="output">Previewing update (dev):
        Type                 Name          Plan       Info
    +   pulumi:pulumi:Stack  test-dev      create     1 error
    +   └─ aws:s3:Bucket     my-bucket     create

Diagnostics:
    pulumi:pulumi:Stack (test-dev):
    error: preview failed

Policy Violations:
    [mandatory]  aws-typescript v0.0.1  s3-bucket-prefix (my-bucket: aws:s3/bucket:Bucket)
    Ensures S3 buckets use the required naming prefix.
    S3 bucket must use 'mycompany-' prefix. Current prefix: 'wrongprefix-'

```

<!-- /option -->

<!-- option: python -->

Use the `--policy-pack` flag to specify your policy pack directory:
If you need a test program, create one with `pulumi new aws-python`. This scaffolds a Pulumi program that provisions an S3 bucket you can test the policy against.
```sh
$ mkdir test-program && cd test-program
$ pulumi new aws-python

```

For AWS examples, ensure you have [AWS credentials configured](/registry/packages/aws/installation-configuration/) and set your region with <code>pulumi config set aws:region <region></code>.

In the Pulumi program’s directory, run:
```sh
$ pulumi preview --policy-pack 

```

If the stack is compliant, the output shows which policy packs ran.
<code class="language-output" data-lang="output">Previewing update (dev):
Type                 Name          Plan
+   pulumi:pulumi:Stack  test-dev      create
+   └─ aws:s3:Bucket     my-bucket     create

Resources:
+ 2 to create

Policy Packs run:
Name                                             Version
aws-python (/Users/user/path/to/policy-pack)     (local)

```

Edit the stack code to specify a non-matching prefix:
```python
bucket = s3.Bucket('my-bucket', bucket_prefix="wrongprefix-")

```

Run `pulumi preview` again. This time, the policy violation blocks the preview:
```
 Previewing update (dev):
Type                 Name          Plan       Info
  +   pulumi:pulumi:Stack  test-dev      create     1 error
  +   └─ aws:s3:Bucket     my-bucket     create

 Diagnostics:
   pulumi:pulumi:Stack (test-dev):
error: preview failed

 Policy Violations:
[mandatory]  aws-python v0.0.1  s3-bucket-prefix (my-bucket: aws:s3/bucket:Bucket)
Ensures S3 buckets use the required naming prefix.
S3 bucket must use 'mycompany-' prefix. Current prefix: 'wrongprefix-'

```

<!-- /option -->

<!-- option: opa -->

Use the `--policy-pack` flag to specify your policy pack directory:
If you need a test program, create one with `pulumi new aws-typescript` or `pulumi new aws-python`. This scaffolds a Pulumi program that provisions an S3 bucket you can test the policy against.
```sh
$ mkdir test-program && cd test-program
$ pulumi new aws-typescript

```

    For AWS examples, ensure you have [AWS credentials configured](/registry/packages/aws/installation-configuration/) and set your region with <code>pulumi config set aws:region <region></code>.

In the Pulumi program’s directory, run:
```sh
$ pulumi preview --policy-pack 

```

If the stack is compliant, the output shows which policy packs ran.
<code class="language-output" data-lang="output">Previewing update (dev):
        Type                 Name          Plan
    +   pulumi:pulumi:Stack  test-dev      create
    +   └─ aws:s3:Bucket     my-bucket     create

Resources:
    + 2 to create

Policy Packs run:
    Name                                                    Version
    aws-opa-policies (/Users/user/path/to/policy-pack)      (local)

```

Edit the stack code to set the bucket ACL to `public-read`:
```typescript
const bucket = new aws.s3.Bucket("my-bucket", {
    acl: "public-read",
});

```

Run `pulumi preview` again. This time, the policy violation blocks the preview:
<code class="language-output" data-lang="output">Previewing update (dev):
Type                 Name          Plan       Info
+   pulumi:pulumi:Stack  test-dev      create     1 error
+   └─ aws:s3:Bucket     my-bucket     create

Diagnostics:
pulumi:pulumi:Stack (test-dev):
error: preview failed

Policy Violations:
[mandatory]  No Public S3 Buckets  deny_public_buckets (my-bucket: aws:s3/bucket:Bucket)
S3 buckets must not use public-read ACLs.
S3 bucket 'my-bucket' must not have public-read ACL

```

<!-- /option -->

<!-- /chooser -->

## Configuring policy packs

Configuration makes policy packs flexible and reusable. Adjust enforcement levels, allowed values, and other settings without modifying code.

### Enforcement levels

All policies support configurable enforcement levels. Set enforcement for all policies in a pack or override individual policies:

```json
{
"all": {
"enforcementLevel": "advisory"
},
"critical-security-policy": {
"enforcementLevel": "mandatory"
}
}
```

As shorthand, specify enforcement levels directly:

```json
{
"all": "advisory",
"critical-security-policy": "mandatory"
}
```

**Enforcement levels:**

- **advisory** - Issues warnings but allows deployments to proceed
- **mandatory** - Blocks deployments when violations are detected
- **disabled** - Skips policy evaluation entirely

### Custom configuration

Policy authors define configuration schemas using JSON Schema, enabling administrators to customize policy behavior without code changes.

**Example: Optional configuration with defaults**

<!-- chooser: language -->

<!-- option: typescript -->
```typescript
const examplePolicy: ResourceValidationPolicy = {
name: "example-policy-with-schema",
description: "Example policy with configurable message.",
configSchema: {
properties: {
message: {
type: "string",
minLength: 5,
maxLength: 50,
}
},
},
validateResource: validateResourceOfType(aws.s3.Bucket, (_, args, reportViolation) => {
const config = args.getConfig<{ message?: string }>();
const message = config.message || "Using default message";
reportViolation("Configured message: " + message);
}),
}

```

<!-- /option -->

<!-- option: python -->
```python
def example_policy_validator(args: ResourceValidationArgs, report_violation: ReportViolation):
config = args.get_config()
message = config.get("message", "Using default message")
report_violation(f"Configured message: {message}")

example_policy = ResourceValidationPolicy(
name="example-policy-with-schema",
description="Example policy with configurable message.",
config_schema=PolicyConfigSchema(
properties={
"message": {
"type": "string",
"minLength": 5,
"maxLength": 50,
},
}
),
validate=example_policy_validator,
)

```

<!-- /option -->

<!-- option: opa -->
OPA policies access configuration values through <code>data.config.<rule_name>.<key></code>. Access the configuration in your Rego rule like any other data reference:
```rego
package aws

# METADATA
# title: Restrict EC2 Instance Size
# description: EC2 instances must not exceed the configured maximum size.
# custom:
#   message: Use an instance type at or below the configured maxInstanceSize.
deny_large_instances[msg] {
input.type == "aws:ec2/instance:Instance"
max_size := data.config.deny_large_instances.maxInstanceSize
sizes := {"t3.micro": 1, "t3.small": 2, "t3.medium": 3, "t3.large": 4,
"t3.xlarge": 5, "m5.xlarge": 6, "m5.2xlarge": 7, "m5.4xlarge": 8}
sizes[input.instanceType] > sizes[max_size]
msg := sprintf("Instance '%s' type '%s' exceeds maximum allowed size '%s'",
[input.__name, input.instanceType, max_size])
}

```

Pass configuration values using the standard Pulumi policy configuration. The values inside the `"properties"` object are injected as <code>data.config.<rule_name></code>. The `"properties"` wrapper key is required in the configuration JSON:
```json
{
"deny_large_instances": {
"properties": {
"maxInstanceSize": "m5.xlarge"
}
}
}

```

<!-- /option -->

<!-- /chooser -->

**Example: Required configuration**

To require configuration values, add them to the `required` list:

<!-- chooser: language -->

<!-- option: typescript -->
```typescript
configSchema: {
properties: {
message: {
type: "string",
minLength: 5,
maxLength: 50,
}
},
required: ["message"],
}

```

<!-- /option -->

<!-- option: python -->
```python
config_schema=PolicyConfigSchema(
properties={
"message": {
"type": "string",
"minLength": 5,
"maxLength": 50,
},
},
required=["message"]
)

```

<!-- /option -->

<!-- option: opa -->
For OPA policies, declare a configuration schema in a `config-schema.json` file alongside your Rego files. Pulumi validates configuration against this schema before evaluation:
```json
{
"deny_large_instances": {
"properties": {
"maxInstanceSize": {
"type": "string",
"default": "m5.xlarge"
}
},
"required": ["maxInstanceSize"]
}
}

```

If a rule declares a config schema but no configuration is provided, the analyzer emits a warning because rules that reference `data.config` will silently not fire without configuration.

<!-- /option -->

<!-- /chooser -->

### Using configuration files

#### Local execution

Pass configuration via JSON file:

**config.json:**

```json
{
"all": "mandatory",
"example-policy-with-schema": {
"message": "Resources must follow naming standards"
}
}
```

**Run with configuration:**

```bash
pulumi preview --policy-pack  --policy-pack-config config.json
```

#### Pulumi Cloud configuration

After publishing, administrators configure policy packs through the Pulumi Cloud console or CLI.

**Using the console:**

1. Navigate to your Policy Group
2. Click **Add Policy Pack**
3. Select the policy pack
4. Fill in the configuration form (automatically validated against the schema)
5. Save the configuration

**Using the CLI:**

Validate configuration before enabling:

```bash
pulumi policy validate-config <org>/ <version> --config config.json
```

Enable with configuration:

```bash
pulumi policy enable <org>/ <version> --config config.json
```

Or for a specific policy group:

```bash
pulumi policy enable <org>/ <version> --config config.json --policy-group <group-name>
```

### Using ESC environments

Policy packs can also receive configuration and secrets from [Pulumi ESC](/docs/esc/) environments. When you attach an ESC environment to a policy pack in a policy group, values defined under the [`policyConfig`](/docs/esc/environments/syntax/reserved-properties/policy-config/) reserved property are available to your policies at runtime. You can also use [`environmentVariables`](/docs/esc/environments/syntax/reserved-properties/environment-variables/) to inject environment variables into the policy runtime.

## Publishing to your organization

After local validation, publish your policy pack to Pulumi Cloud. Policy enforcement runs automatically during `preview` and `update` for any stack using Pulumi Cloud.

Pulumi Cloud versions policy packs, enabling updates, rollbacks, and gradual rollouts.

1. From the policy pack directory, publish:

    ```sh
$ pulumi policy publish <org-name>
```

    Pulumi Cloud assigns a monotonic version number:

    ```
Obtaining policy metadata from policy plugin
Compressing policy pack
Uploading policy pack to Pulumi Cloud
Publishing my-policy-pack to myorg
Published as version 1.0.0
```

### Managing policy pack versions

Policy pack versions are managed differently by language:

- **TypeScript/JavaScript**: Set the `version` field in `package.json`. You can also set `version` in `PulumiPolicy.yaml` to override it.
- **Python**: Set the `version` field in `PulumiPolicy.yaml`
- **OPA**: Set the `version` field in `PulumiPolicy.yaml`

For a complete list of `PulumiPolicy.yaml` fields, see the [project file reference](/docs/insights/policy/policy-packs/project-file/).

Each version can only be published once.

**Publishing a new version:**

1. Update the version number:
   - TypeScript: Edit `package.json`: `"version": "0.0.2"`
   - Python: Edit `PulumiPolicy.yaml`: `version: 0.0.2`
   - OPA: Edit `PulumiPolicy.yaml`: `version: 0.0.2`

1. Publish:

   ```bash
   pulumi policy publish <org-name>
   ```

**If you try to republish an existing version**, you'll see:

```
error: [400] Bad Request: Policy Pack "aws-typescript" (Version 0.0.1) has already been published.
Please specify a new version tag.
```

We recommend [semantic versioning](https://semver.org/):

- **Major** (1.0.0 → 2.0.0): Breaking changes to policy behavior
- **Minor** (1.0.0 → 1.1.0): New policies added
- **Patch** (1.0.0 → 1.0.1): Bug fixes

After publishing, your policy pack appears in Pulumi Cloud's policy pack list. Apply it to stacks or cloud accounts using policy groups. See [Get Started with Pulumi Policies](/docs/insights/policy/get-started/) for details.

## Considerations for authoring policies

Best practices for authoring policy packs:

### Naming policies

Each policy within a policy pack must have a unique name. The name must be between 1 and 100 characters and may contain letters, numbers, dashes (`-`), underscores (`_`), or periods (`.`).

### Policy assertions

Write policy assertions as complete sentences in imperative tone, specifying which resource violated the policy.

| ✅ Good | ❌ Poor |
| ------- | ------- |
| "The RDS cluster must specify a node type." | "Specify a node type." |
| "The RDS cluster must have audit logging enabled." | "Enable audit logging." |
| "S3 bucket must use 'mycompany-' prefix." | "Use correct prefix." |

This format helps users understand which resource failed and why.

## Examples and resources

- [Policy examples repository](https://github.com/pulumi/examples/tree/master/policy-packs) - Example policy packs demonstrating various implementation patterns
- [Policy as Code overview](/docs/insights/policy/)
- [Policy Metadata fields](/docs/insights/policy/metadata/)

## Next steps

- [Apply policies to stacks and accounts using policy groups](/docs/insights/policy/get-started/)
- [View and manage policy findings](/docs/insights/policy/policy-findings/)
- [Learn about policy groups and enforcement modes](/docs/insights/policy/policy-groups/#types-of-policy-groups)
- [Learn about policy pack configuration](/docs/insights/policy/policy-packs/)
```
