Schema configuration when request value is masked in response

Hi there.

I am new to Terraform Provider development and have no experience with SDKv2. My API has a model that accepts phone numbers, but returns a masked value in the response:

+11234567890 → +112xxxxxx90

After the Create method is called, Terraform reports an error to me:

was cty.StringVal("+11234567890"), but now cty.StringVal("+112xxxxxx90").

I have not figured out how to control the value of the attribute stored in the cty instance, perhaps I am doing something wrong :frowning:
Does anyone have any ideas on the right approach?

Schema goes like this:

"phone_number": schema.StringAttribute{
	Required:    true,
	PlanModifiers: []planmodifier.String{

Create goes like this:

func (r *contactsResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
	var plan ContactsModel
	// ... snip ...
	plan.ID = types.StringValue(contact.ID)
	maskedValue := numMasker(contact.PhoneNumber)
	plan = ContactsModel{
		ID:          types.StringValue(contact.ID),
		PhoneNumber: types.StringValue(maskedValue),

	diags = resp.State.Set(ctx, plan)
	if resp.Diagnostics.HasError() {

Thank you, sir!

I think I’d probably approach this by storing the full unmasked value in the Terraform state and plan upon Create or Update, and in the resource’s Read method checking whether the masked value read back from the API is consistent with the unmasked value in the state. If it is consistent, keep the existing state value. If it is not consistent, update the state with the read (masked) value, which will force Terraform to try to re-update it to whatever is specified in the configuration in a future Update.

Hi there @k21205 :wave: , welcome to Terraform provider development!

Just to make a small clarification to make sure we’re on the same page. You mentioned SDKv2, which is the legacy SDK for building terraform providers, but the code snippets you provided are all using Plugin Framework, which is the most recent SDK and recommended way to build terraform providers. Side note: That distinction is important if you’re looking at documentation or other developer examples for provider development as the SDKs are very different.

As @maxb mentioned, you can store the full unmasked value (marking the schema as Sensitive if you want to supress CLI output) in state during Create and Update.

As for handling of masked values being returned from the API during Read:

  • Another approach for checking equality between a string value like +11234567890 and +112xxxxxx90 actually falls into a recent feature we released with custom types called Semantic Equality.
    • This can handle the logic of determining whether the masked value read back from the API is consistent with the unmasked value in the state. You can then signal if the existing state value (also referred to as Prior State) should be kept or not. You can then reuse that custom type for all other masked values if the logic is the same.

You can also implement this logic in the Read method, it all depends on how you want to structure your code. Hopefully this helps!

Thanks for the quick response and I apologize for the delay in replying due to work! Your suggested approach makes a lot of sense. I will adopt this approach and proceed with the implementation, cheers!


And thank you for the very detailed explanation, this was the very perspective I was missing. I was wondering if I should use custom types. I was wondering where to implement the validation of masked and unmasked values.

Your comment is exactly a pointer to me, I think I can implement the approach suggested by maxb using Semantic Equality

Thanks to both of you. Developing Terraform Provider is a lot of fun!