pulumi/pulumi · error
cannot marshal Input that is not a struct or map, saw type %
Error message
cannot marshal Input that is not a struct or map, saw type %s
What it means
marshalInputs in rpc.go can only project Input values whose Go type is a struct or a map (it iterates fields or keys to build RPC properties). Any other Input kind (scalars, slices) reaches the default branch and is rejected, because there is no generic way to map it to the gRPC property structure.
Source
Thrown at sdk/go/pulumi/rpc.go:210
err := marshalProperty(tag, pv.Field(i).Interface(), destField.Type)
if err != nil {
return nil, nil, nil, err
}
}
case reflect.Map:
ktype := rt.Key()
contract.Assertf(ktype.Kind() == reflect.String,
"expected map with string keys, got %v (%v)", ktype, ktype.Kind())
for _, key := range pv.MapKeys() {
keyname := key.Interface().(string)
val := pv.MapIndex(key).Interface()
err := marshalProperty(keyname, val, rt.Elem())
if err != nil {
return nil, nil, nil, err
}
}
default:
return nil, nil, nil, fmt.Errorf("cannot marshal Input that is not a struct or map, saw type %s", pt.String())
}
urns := slice.Prealloc[URN](len(deps))
for v := range deps {
urns = append(urns, v)
}
return pmap, pdeps, urns, nil
}
// `gosec` thinks these are credentials, but they are not.
//
//nolint:gosec
const rpcTokenUnknownValue = "04da6b54-80e4-46f7-96ec-b56ff0331ba9"
const cannotAwaitFmt = "cannot marshal Output value of type %T; please use Apply to access the Output's value"
// marshalInput marshals an input value, returning its raw serializable value along with any dependencies.
func marshalInput(v any, destType reflect.Type) (resource.PropertyValue, []Resource, error) {View on GitHub (pinned to 793f7b2e16)
Solutions
- Pass the proper Inputs struct type for the resource (e.g. &MyResourceArgs{...}) rather than a scalar Input
- Wrap keyed values in a Map/struct Input before marshalling
- If implementing provider plumbing, use marshalInputOptions per property instead of passing scalars to marshalInputs
Example fix
// before
props, _, _, err := marshalInputs(pulumi.String("x"), opts)
// after
props, _, _, err := marshalInputs(&MyResourceArgs{Name: pulumi.String("x")}, opts) Defensive patterns
Strategy: type-guard
Validate before calling
func isStructOrMapInput(v any) bool {
t := reflect.TypeOf(v)
if t == nil {
return false
}
for t.Kind() == reflect.Pointer {
t = t.Elem()
}
return t.Kind() == reflect.Struct || t.Kind() == reflect.Map
} Prevention
- Pass resource Args structs (or pulumi.Map) wherever a property bag is required
- Do not pass scalar Inputs (StringInput, IntInput) to marshalInputs-style APIs
- When writing provider plumbing, marshal each property individually with marshalInputOptions
When it happens
Trigger: Passing a value whose dynamic type is not a struct or map Input into marshalInputs, e.g. calling lower-level registration APIs or custom providers that hand raw Input values (like pulumi.StringArray or a StringInput) where a struct/map Inputs value is expected.
Common situations: Custom provider authors building Call/Construct plumbing who accidentally pass a scalar Input as the property bag; misuse of internal marshalling APIs in tests.
Related errors
- ResourceArrayInput resolved to a value of unexpected type %v
- result must be a map, but was a
- result must have exactly one element
- result field type %s does not match expected type %s
- must be a string
AI-assisted analysis of pulumi/pulumi@793f7b2e16 (2026-08-31).
Data as JSON: /api/errors/94a222f01573bf39.
Report an issue: GitHub.