{"record":{"id":"94a222f01573bf39","repo":"pulumi/pulumi","slug":"cannot-marshal-input-that-is-not-a-struct-or-map","errorCode":null,"errorMessage":"cannot marshal Input that is not a struct or map, saw type %s","messagePattern":"cannot marshal Input that is not a struct or map, saw type (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"sdk/go/pulumi/rpc.go","lineNumber":210,"sourceCode":"\t\t\terr := marshalProperty(tag, pv.Field(i).Interface(), destField.Type)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, nil, err\n\t\t\t}\n\t\t}\n\tcase reflect.Map:\n\t\tktype := rt.Key()\n\t\tcontract.Assertf(ktype.Kind() == reflect.String,\n\t\t\t\"expected map with string keys, got %v (%v)\", ktype, ktype.Kind())\n\t\tfor _, key := range pv.MapKeys() {\n\t\t\tkeyname := key.Interface().(string)\n\t\t\tval := pv.MapIndex(key).Interface()\n\t\t\terr := marshalProperty(keyname, val, rt.Elem())\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, nil, err\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil, nil, nil, fmt.Errorf(\"cannot marshal Input that is not a struct or map, saw type %s\", pt.String())\n\t}\n\n\turns := slice.Prealloc[URN](len(deps))\n\tfor v := range deps {\n\t\turns = append(urns, v)\n\t}\n\treturn pmap, pdeps, urns, nil\n}\n\n// `gosec` thinks these are credentials, but they are not.\n//\n//nolint:gosec\nconst rpcTokenUnknownValue = \"04da6b54-80e4-46f7-96ec-b56ff0331ba9\"\n\nconst cannotAwaitFmt = \"cannot marshal Output value of type %T; please use Apply to access the Output's value\"\n\n// marshalInput marshals an input value, returning its raw serializable value along with any dependencies.\nfunc marshalInput(v any, destType reflect.Type) (resource.PropertyValue, []Resource, error) {","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/pulumi/pulumi/blob/793f7b2e160db4321fb7fb6b0607461e01cb251e/sdk/go/pulumi/rpc.go#L192-L228","documentation":"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.","triggerScenarios":"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.","commonSituations":"Custom provider authors building Call/Construct plumbing who accidentally pass a scalar Input as the property bag; misuse of internal marshalling APIs in tests.","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"],"exampleFix":"// before\nprops, _, _, err := marshalInputs(pulumi.String(\"x\"), opts)\n// after\nprops, _, _, err := marshalInputs(&MyResourceArgs{Name: pulumi.String(\"x\")}, opts)","handlingStrategy":"type-guard","validationCode":"func isStructOrMapInput(v any) bool {\n    t := reflect.TypeOf(v)\n    if t == nil {\n        return false\n    }\n    for t.Kind() == reflect.Pointer {\n        t = t.Elem()\n    }\n    return t.Kind() == reflect.Struct || t.Kind() == reflect.Map\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["go","pulumi-sdk","marshalling","type-mismatch"],"backgroundTag":"invalid-input-shape","analyzedSha":"793f7b2e160db4321fb7fb6b0607461e01cb251e","analyzedAt":"2026-08-31T09:36:43.099Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}