pulumi/pulumi · error

props must be a struct or map or a pointer to a struct or ma

Error message

props must be a struct or map or a pointer to a struct or map

What it means

When props is supplied to a resource read/lookup, the Go SDK reflects on its type: after dereferencing a pointer, it must be a struct or a map with string keys. Any other kind (slice, int, string, map with non-string keys, pointer-to-pointer, etc.) is rejected because the marshaller cannot convert it into the gRPC argument structure.

Source

Thrown at sdk/go/pulumi/context.go:1388

	t, name string, id IDInput, props Input, resource CustomResource, packageRef string, opts ...ResourceOption,
) error {
	if t == "" {
		return errors.New("resource type argument cannot be empty")
	} else if name == "" {
		return errors.New("resource name argument (for URN creation) cannot be empty")
	} else if id == nil {
		return errors.New("resource ID is required for lookup and cannot be empty")
	}

	if props != nil {
		propsType := reflect.TypeOf(props)
		if propsType.Kind() == reflect.Pointer {
			propsType = propsType.Elem()
		}
		//nolint:staticcheck // Not applying de-morgens law right now
		if !(propsType.Kind() == reflect.Struct ||
			(propsType.Kind() == reflect.Map && propsType.Key().Kind() == reflect.String)) {
			return errors.New("props must be a struct or map or a pointer to a struct or map")
		}
	}

	options := merge(opts...)
	parent := options.Parent
	if options.Parent == nil {
		options.Parent = ctx.state.stack
	}

	// Before anything else, if there are transformations registered, give them a chance to run to modify the
	// user-provided properties and options assigned to this resource.
	var transformations []ResourceTransformation
	var err error
	props, options, transformations, err = applyTransformations(t, name, props, resource, opts, options)
	if err != nil {
		return err
	}

View on GitHub (pinned to 793f7b2e16)

Solutions

  1. Pass a plain struct value/pointer or a map with string keys (e.g. map[string]interface{}).
  2. Dereference extra pointer levels before passing (props must be T, *T, **T will fail).
  3. Pass nil if the lookup needs no properties — the check only runs when props != nil.

Example fix

// before
props := []string{"a", "b"}
ctx.GetResource(tok, name, id, props)
// after
props := map[string]interface{}{"keys": []string{"a", "b"}}
ctx.GetResource(tok, name, id, props)
Defensive patterns

Strategy: type-guard

Validate before calling

func validProps(p interface{}) bool {
    if p == nil { return true }
    t := reflect.TypeOf(p)
    for t.Kind() == reflect.Pointer { t = t.Elem() }
    return t.Kind() == reflect.Struct ||
        (t.Kind() == reflect.Map && t.Key().Kind() == reflect.String)
}

Type guard

func validProps(p interface{}) bool {
    t := reflect.TypeOf(p)
    for t != nil && t.Kind() == reflect.Pointer { t = t.Elem() }
    if t == nil { return true }
    return t.Kind() == reflect.Struct ||
        (t.Kind() == reflect.Map && t.Key().Kind() == reflect.String)
}

Try / catch

if err != nil {
    if strings.Contains(err.Error(), "props must be a struct or map") {
        return fmt.Errorf("bad props for %s lookup: %w", name, err)
    }
    return err
}

Prevention

When it happens

Trigger: Passing props like &someSlice, a map[int]string, a struct wrapped in a pointer inside another pointer, or a non-struct/map value to ctx.GetResource's props parameter.

Common situations: Passing a []T of results by mistake; using map[string]any via a typed alias with non-string key; double-pointer from an ORM/JSON decode helper.

Understand the failure class

Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.

Related errors


AI-assisted analysis of pulumi/pulumi@793f7b2e16 (2026-08-31). Data as JSON: /api/errors/19aff4ea59ee661f. Report an issue: GitHub.