Power Automate: Copy SharePoint Multi-Person Fields Using Claims

Copying data between SharePoint lists usually behaves as expected. Text, numbers, and choice fields rarely cause problems.

Person fields are one of the exceptions.

This came up while building a Power Automate flow that created items in a destination list whenever a new item was added to a source list. Most fields copied without issue. The multi-person field did not.

The flow ran without errors, but the SharePoint multi-person column remained empty in the destination list.

The solution is to extract the Claims value from each source user and pass an array of Claims objects to the destination multi-person field.

Scenario Overview

The source list had a multi-person column. The destination list had a matching field. The requirement was to copy users from one list to another.

The obvious approaches were tried first: e-mail address, display name, user ID, and direct field mapping. Individual properties from the person object were passed in every combination available. None of them worked. The flow succeeded every time, but the user column was always blank.

To make things harder, the source list included guest accounts that had no presence on the destination site. Those users never copied over regardless of the approach used, and it was unclear whether that was a separate problem or the same one.

It turned out the problem was how the flow represented the SharePoint person identity when writing the destination field.

Why SharePoint Uses Claims

A Person or Group field (also called a People Picker field) in SharePoint is not a string value. It is a structured identity object. Power Automate exposes it like this:

{
  "Claims": "i:0#.f|membership|john.smith@example.com",
  "DisplayName": "John Smith",
  "Email": "john.smith@example.com",
  "Department": "Operations",
  "JobTitle": "Project Manager"
}

E-mail and display name are attributes of the person object. In this Power Automate scenario, SharePoint uses the Claims value when writing the person field.

Claims exists because e-mail and display name can both change over time, and users can exist in multiple contexts, including guest, synced, and external accounts. Claims provides the identity information SharePoint uses to resolve a person or group. In this Power Automate scenario, the Claims value is used to represent each source user when populating the destination Person or Group field.

Passing an e-mail address instead of a Claims string means SharePoint does not attempt to resolve it to a user. The person column stays empty and no error is thrown. Display name behaves the same way. It is not unique and SharePoint does not use it as a lookup key. This is why the flow appears to succeed while the field does not populate.

NOTE: The guest account issue turned out to be a separate identity-resolution problem rather than a Claims formatting problem. Guest users can have a different Claims format, but a correctly formatted Claims value still requires the user to be resolvable in the destination site. That is covered in the Troubleshooting section below.

What Claims Looks Like

Claims follow this format:

i:0#.f|membership|john.smith@example.com

For standard Microsoft 365 users, the prefix is always i:0#.f|membership|. Guest users follow a different pattern.

Single vs. Multi-Person Fields

Both field types use the same underlying Claims structure. The difference is only in what SharePoint expects at write time:

A multi-person field accepts an array of Claims objects regardless of whether it holds one person or several, so the same array-building logic in this flow handles both cases without modification. This does not extend to the field’s underlying type: a single-person field still requires a single object rather than an array, so converting a column from single-person to multi-person means updating the flow’s output to build an array instead of unwrapping it to a single object.

Single-person field:

{
  "Claims": "i:0#.f|membership|john.smith@example.com"
}

Multi-person field:

[
  {
    "Claims": "i:0#.f|membership|john.smith@example.com"
  },
  {
    "Claims": "i:0#.f|membership|jane.doe@example.com"
  }
]

Building the Flow

Each person object in the source field already contains the Claims value. The key transformation is to convert each source person object into a simplified object containing only its Claims value. The resulting array of Claims objects can then be assigned to the destination multi-person field.

Step 1: Initialize an Array Variable

Before the loop, add an Initialize variable action to the flow. Set the type to Array and the initial value to []. This variable will hold the Claims objects before they are written to the destination list.

Step 2: Loop Through the Source Field

Add an Apply to each action using the multi-person column as input.

Step 3: Append Claims to the Array

Inside the loop, use Append to array variable with this value:

{
  "Claims": "@{items('Apply_to_each')?['Claims']}"
}

Step 4: Write to SharePoint

Pass the array into the destination multi-person field in Create item or Update item. SharePoint resolves the Claims values and populates the field.

The resulting array passed to SharePoint looks like this:

[
  {
    "Claims": "i:0#.f|membership|john.smith@example.com"
  },
  {
    "Claims": "i:0#.f|membership|jane.doe@example.com"
  }
]

NOTE: If Claims is null or missing on the source item, the append step will produce incomplete objects. See the Troubleshooting section for how to handle that.

Troubleshooting

The following issues came up while testing and troubleshooting person field updates.

The Flow Succeeds but the Person Column Is Still Blank

This is the most common symptom. It means SharePoint accepted the request but could not resolve the supplied identity. Check that Claims, not e-mail or display name, is being passed. Inspect the raw output in the flow run history to confirm the Claims value is present on the source item.

Claims Is Null or Missing in the Source Field

This can happen if the source item was created programmatically, imported, or migrated in a way that did not properly resolve users. Open the item in SharePoint, re-save it manually, and check whether Claims populates on the next flow run.

If the source data is coming from outside SharePoint, the Claims string can be constructed by prepending i:0#.f|membership| to the user’s e-mail address. This only works for users already known to the tenant. Testing confirmed that passing a manually constructed Claims value directly to Create item or Update item adds the user to the destination site’s user information list automatically, even if they were not previously listed there, so a separate ensureuser call is not required for a known tenant user. ensureuser is still useful when the Claims format cannot be constructed this way, such as for guest accounts.

Resolving Claims Dynamically With EnsureUser

Manually prepending i:0#.f|membership| works for known tenant users with a standard account type, but it will not produce a correct Claims value for guest users, whose format differs. SharePoint exposes a REST endpoint (ensureuser) that resolves a login name to the correct Claims value regardless of account type, which is useful when the Claims format cannot be constructed this way in advance. This call also adds the resolved user to the user information list of the site it is called against, if they are not already present there, though it does not grant them access to that site. Testing confirmed that a manually constructed Claims value, written directly via Create item or Update item, adds the user to the site’s user information list on its own. The main advantage of ensureuser for a known tenant user is resolving the Claims format correctly rather than being required to add them to the site user information list.

Add a Send an HTTP request to SharePoint action with the following configuration:

Power Automate’s Send an HTTP request to SharePoint action handles authentication and the X-RequestDigest header automatically. Testing against a live tenant also confirmed that a Content-Type header of application/json; odata=verbose alone is sufficient. No additional accept header is required, even though Microsoft’s raw REST examples typically show accept instead of Content-Type.

The e-mail address or UPN can be passed dynamically from the source field. The response body includes a LoginName property containing the resolved Claims value:

{
  "d": {
    "LoginName": "i:0#.f|membership|john.smith@example.com"
  }
}

Parse this with a Parse JSON action, then use body('Parse_JSON')?['d']?['LoginName'] in place of the manually constructed string when building the Claims array.

NOTE: ensureuser resolves any user known to the tenant, regardless of whether they already have access to the destination site. It adds the resolved user to that site’s user information list if not already present, scoped to that one site rather than tenant-wide, and it does not grant access or permissions. A user unknown to the tenant entirely returns a 400 error with a message similar to The specified user john.smith@example.com could not be found.

As noted above, a correctly formatted Claims value written directly via Create item or Update item produces this same user information list addition without calling ensureuser first.

Guest Users Use a Different Claims Format

Guest Claims values can have a different format. Do not construct them manually from the pattern below. Inspect the actual Claims value returned by SharePoint or resolve the user with ensureuser.

i:0#.f|membership|john.smith_example.org#ext#@yourtenant.onmicrosoft.com

In this example, example.org represents the guest’s home organization and yourtenant.onmicrosoft.com is the tenant. The @ in the guest’s original e-mail address is replaced with an underscore, and #ext# marks the account as an external user.

Guest Users Unknown to the Tenant Cannot Be Resolved

An external account that has never been invited into the tenant, such as a guest with no prior relationship to the organization, cannot be resolved by SharePoint or by ensureuser. There is no record of the account anywhere in the tenant to resolve the Claims string against.

Unlike the silent failures described elsewhere in this article, this case does not fail quietly. Calling ensureuser against an unresolvable guest returns the 400 error described above, which fails the HTTP action and the flow along with it.

The guest must first exist in the destination tenant’s directory before SharePoint can resolve the account. Once the guest exists in the tenant, ensureuser can resolve the user against the destination site, though this specific path was not directly tested.

A practical fallback is storing guest display names or e-mail addresses in a separate text column so the information is not lost.

The Field Saves One User but Not Multiple

This usually indicates an object is being passed instead of an array. Multi-person fields require an array even when there is only one user.

Normalize all output to an array before writing to SharePoint.

Cross-Site or Cross-Tenant Copying Fails

When copying users across tenants, the source tenant’s SharePoint identity cannot simply be reused in the destination tenant. Resolve the user in the destination tenant and use the destination site’s identity when populating the person field.

This scoping also applies within a single tenant. A user already added to one site’s user information list is not automatically added to another’s. As with the single-site case, writing a correctly formatted Claims value directly via Create item or Update item adds the user to the destination site’s list automatically for known tenant users. ensureuser remains useful here when the Claims format is uncertain or when resolving a guest across sites, since it resolves the correct value per site rather than tenant-wide.

Summary

For this Power Automate approach, the destination Person or Group field needs a SharePoint-resolvable identity rather than the complete person object returned by the source field. The Claims value provides that identity information. The solution is to extract Claims from the source field, build the array, and pass it back to SharePoint.

For cases where Claims resolution is not possible, such as guest users copying to internal-only sites or cross-tenant scenarios, the limitation is identity access rather than the flow itself.