Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Pulumi By James Joyner IV · · 9 min read

Pulumi Error: 'unmarshalling properties: expected a value of type but got' — Cause, Fix, and Troubleshooting Guide

Quick answer

Fix Pulumi's 'unmarshalling properties: unmarshalling value: expected a value of type but got' error caused by a type mismatch in resource inputs or state.

  • #pulumi
  • #iac
  • #troubleshooting
  • #errors
Free toolkit

Fixing errors like this? Get 500 free DevOps AI prompts

500 copy-paste AI prompts for the stack you actually run — one PDF, free.

Overview

error: unmarshalling properties means Pulumi passed a resource’s inputs or outputs across the wire between the engine, your Python program, and the provider — and the value it received did not match the type the provider’s schema expects. The provider (for example pulumi-aws) is strongly typed; when you hand it a string where it wants a number, a list where it wants a single object, or None where a value is required, decoding fails.

The literal message names the expected and actual types, which is your primary clue:

error: unmarshalling properties: unmarshalling value: expected a value of type string but got number

This is almost always a bug in how a resource argument is built in your Python code, though it can also come from stale or hand-edited state, or from feeding one resource’s output into an incompatible input.

Symptoms

  • pulumi preview or pulumi up fails during resource registration with unmarshalling properties.
  • The message spells out a type pair, e.g. expected a value of type array but got object or expected a value of type bool but got string.
  • The error points at a specific resource type/URN (an aws:s3/bucket:Bucket, aws:ec2/instance:Instance, etc.).
  • It started after you changed an argument, upgraded a provider SDK, or imported/refreshed state.
  • The same program worked before a pulumi-aws major-version bump.

Common Root Causes

1. Passing the wrong Python type for an argument

The most common case: an argument wants a number, a bool, or a nested object, but your code supplies a string (or vice versa). Values that come from os.environ, pulumi.Config().get(), or JSON are strings by default.

import os
import pulumi_aws as aws

# WRONG: port comes back as a string "8080"
port = os.environ["APP_PORT"]

# The provider expects an int for from_port/to_port
aws.ec2.SecurityGroupRule(
    "app",
    type="ingress",
    from_port=port,      # <-- string, provider wants a number
    to_port=port,
    protocol="tcp",
    cidr_blocks=["0.0.0.0/0"],
    security_group_id=sg.id,
)

2. A list vs. single value (or object vs. array) mismatch

Some arguments take a single object, others take a list of them. Wrapping (or not wrapping) in a list produces expected a value of type array but got object.

# WRONG: tags must be a dict/map, not a list
aws.s3.BucketV2("data", tags=[{"Name": "data"}])   # object expected, array given

# RIGHT
aws.s3.BucketV2("data", tags={"Name": "data"})

3. A provider SDK upgrade changed a property’s shape

Major-version provider upgrades (for example pulumi-aws v5 to v6) rename or retype properties. State written by the old SDK, or code written for it, can now decode into an incompatible type.

expected a value of type object but got array

4. Corrupted or hand-edited state

If someone edited the stack’s state (via pulumi state surgery, a bad import, or manual JSON edits) so a stored value no longer matches the schema, reading that state back fails to unmarshal.

5. Feeding an Output of the wrong type into an input

Chaining resource.some_output into an argument that expects a different type (e.g. an ARN where an ID is expected, or a whole object where a string field is expected) surfaces here once the value resolves.

How to Diagnose

Read the type pair in the message first — expected X but got Y tells you exactly what to correct. Then get detail:

# Verbose engine logs show the resource + payload being decoded
pulumi up --logtostderr -v=9 2>diag.log

Identify which resource is failing and inspect the exact inputs Pulumi will send:

pulumi preview --diff

Check the provider version in play, since upgrades are a frequent trigger:

pulumi plugin ls
grep -i pulumi-aws requirements.txt

Confirm the expected type for the argument by checking the property in the Pulumi Registry docs for that resource, then compare against what your Python builds:

# Print the runtime type of the value you're passing
python3 -c "import os; print(type(os.environ.get('APP_PORT')))"

Inspect stored state if you suspect corruption:

pulumi stack export > stack.json
# search stack.json for the failing resource URN and check the property value

Fixes

Coerce the value to the type the provider expects: Cast strings to int/float/bool before passing them in.

import os
import pulumi_aws as aws

port = int(os.environ["APP_PORT"])   # cast to int

aws.ec2.SecurityGroupRule(
    "app",
    type="ingress",
    from_port=port,
    to_port=port,
    protocol="tcp",
    cidr_blocks=["0.0.0.0/0"],
    security_group_id=sg.id,
)

Match list vs. object shape to the schema: Use the Pulumi Registry page for the resource to confirm whether an argument is a map, a single object, or a list, then wrap accordingly.

# tags is a map
aws.s3.BucketV2("data", tags={"Name": "data", "Env": "prod"})

# an argument documented as a list of objects
aws.ec2.Instance(
    "web",
    ebs_block_devices=[{"device_name": "/dev/sdb", "volume_size": 50}],
    # ... other args
)

Handle Config typing explicitly: Config returns strings unless you use the typed getters, which convert and validate for you.

import pulumi

config = pulumi.Config()
port = config.require_int("port")     # returns an int
enabled = config.require_bool("enabled")
sizes = config.require_object("sizes")  # returns a parsed structured value

After a provider upgrade, refresh and re-run: Align state with the new schema, and update code to the new property names/shapes from the changelog.

pulumi refresh --yes
pulumi preview

Repair bad state as a last resort: Export, correct the offending value to the right type, and re-import.

pulumi stack export > stack.json
# edit stack.json so the value matches the expected type
pulumi stack import --file stack.json

What to Watch Out For

  • Values from environment variables, CLI flags, and JSON files are strings — cast them before handing them to a provider.
  • Prefer Config.require_int / require_bool / require_object over get() + manual parsing.
  • Read the provider’s major-version upgrade guide before bumping pulumi-aws; property retypes are common.
  • Never hand-edit exported state unless you must; a wrong type there just moves the failure to read time.
  • When chaining outputs into inputs, confirm the output is the field you think it is (.id vs .arn vs the whole object).
Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.