MIGRATION · April 14, 2026 · 10 min read
Cross-tenant SharePoint permissions without user-mapping hell
Everything else in a SharePoint site migration is solvable. Content types, list views, site pages, version history — there is a right answer for each, and with the right tool you get there. Permissions in a cross-tenant move are different. They are the one part where a naive tool will silently produce a site that looks fine and is actually half-broken, because the failure mode is “nobody can get in to check”.
This post is the honest playbook: why cross-tenant permissions are hard, how MigrationFox’s CSV mapping handles UPN translation, and what to do for the users who do not have a clean mapping. At the end we walk through a full example so you can see what the job actually produces.
Why cross-tenant permissions are hard
A permission in SharePoint is a triple: (principal, scope, role). For example: alice@acme.com has Full Control on /sites/marketing. For the destination tenant to reproduce that triple after migration, it needs all three parts to still be valid on the other side of the move.
The scope is usually fine — the site or library or item exists on the destination because we just migrated it. The role is fine — Full Control is Full Control. What breaks is the principal. In a same-tenant migration the principal is still the same directory object and Graph resolves it natively. In a cross-tenant migration the source principal is alice@acme.com, living in Acme’s Entra ID, and the destination site lives in New Company’s Entra ID where that principal simply does not exist.
There are four categories of principal you have to translate, and they each have different failure modes:
- Individual users. UPN almost always changes (
alice@acme.com→alice.smith@newcompany.com). Must be explicitly mapped. - Security groups. Destination tenant has its own groups. Membership is not transferred — it is rebuilt from the destination’s directory, which means the target group has to exist and have the right members before permissions replay runs.
- Microsoft 365 groups / Teams. Same as security groups, with the added complication that the group’s SharePoint site is itself something you are migrating, so the ordering matters.
- External guests / sharing links.
anyone-with-the-linkand guest invitees are policy-dependent on the destination tenant. If the destination does not allow Anyone-links, any source Anyone-link disappears. This is usually desirable but it has to be a conscious decision.
Any one of these categories, handled naively, can silently drop a quarter of your site’s effective permissions. The users who had access through the missing principals will call the helpdesk on Monday morning saying “I cannot open the shared drive”.
The worst permissions-migration outcome is not failure. It is silent success. A migration that reports “100% complete” with 30% of the source permissions quietly discarded is worse than a migration that explicitly fails, because the problem surfaces only when users try to work.
The CSV mapping model
MigrationFox’s cross-tenant permissions flow is anchored on two CSV files that you maintain by hand, because no tool can guess the right mappings for you. They are small, they are obvious, and they are the place where the human judgement lives.
user-map.csv
One row per source user, with the destination UPN they map to:
source_upn,destination_upn
alice@acme.com,alice.smith@newcompany.com
bob@acme.com,bob.jones@newcompany.com
carol@acme.com,carol.davis@newcompany.com
dan@acme.com,dan.miller@newcompany.com
A user in the source with no row in the mapping is “unmapped” and lands on the exceptions list. We do not silently guess based on fuzzy name matching, because the cost of getting that wrong is worse than the cost of surfacing it.
group-map.csv
Groups get the same treatment:
source_group,destination_group
marketing-team@acme.com,marketing@newcompany.com
finance-leads@acme.com,finance-leadership@newcompany.com
engineering@acme.com,engineering@newcompany.com
For groups, the destination membership is populated by whoever maintains your Entra ID — usually HR or IT via AD Connect, Okta, or an HRIS integration. The migration does not write group memberships. It only points the site’s permissions at the correct destination group; whether Alice is a member of marketing@newcompany.com is a directory question, not a migration question.
How the replay phase runs
Permissions are the last phase of a SharePoint site migration, after every other piece of content and schema is in place. The sequence for each site is:
- Enumerate source permissions. Walk the site’s role assignments, then every scope with unique permissions (libraries, folders, items) and capture the (principal, scope, role) triples.
- Resolve against the mapping. For each source principal, look it up in
user-map.csvorgroup-map.csv. Mapped principals go to the replay list; unmapped ones go to the exceptions list. - Pre-flight the exceptions list. Before any writes happen, the UI shows you every unmapped principal with the scopes they had access to and the role they had. You decide, per principal: drop it, map it inline, or invite as guest.
- Replay mapped permissions. Each triple is written to the destination against the mapped principal. 429s back off, transient errors retry, terminal errors land in the exceptions report.
- Emit the audit report. A CSV/JSON record of every permission written, every permission skipped, and the reason for each skip.
The pre-flight in step 3 is the part that keeps silent failures from happening. You cannot get to step 4 without making an explicit choice about every unmapped principal. The tool does not quietly decide on your behalf.
What to do for unmapped users
Three reasonable responses to an unmapped source principal, each appropriate in different situations.
Option A: Drop the permission
If the unmapped user is a leaver — they no longer work at the company and have no destination account — dropping the permission is correct. Their access was going to be revoked anyway. The exceptions report still records that they had access on the source, which you may want for compliance/audit reasons, but the destination permission simply is not written.
This is the default for anyone without a destination UPN. We do not invent an account, we do not leave a ghost assignment pointing at a deleted UPN. We record the fact and move on.
Option B: Map inline
If the unmapped user should have been in the CSV but got missed — a contractor whose UPN changed form, a married name that flipped between directories, a recent hire not yet in the mapping — the pre-flight UI lets you add the mapping without re-running the migration. Type the destination UPN, press apply, that principal moves from exceptions to replay.
This happens often enough that we designed the workflow around it. Expect to adjust the mapping 5–10% during pre-flight on a typical site; it is not a failure of preparation, it is just that some users have unusual cases.
Option C: Invite as guest
If the unmapped user is a real external collaborator — a partner at another firm, an agency, a customer — you do not want to drop their access, and they do not have an internal UPN. The right move is to invite them as a B2B guest on the destination tenant.
In the pre-flight, flagging a user as “invite as guest” causes the replay phase to:
- Send a B2B guest invitation to the user’s existing email address
- Create a guest user object in the destination Entra ID when the invite is accepted (or preemptively, depending on your tenant’s invitation policy)
- Apply the source-side permission to that guest object on the destination site
The guest workflow is bounded by your destination tenant’s external collaboration settings. If the destination tenant is locked down to no guest invitations, the “invite as guest” option is simply disabled in the UI. If guest invitations are allowed only to certain domains, the UI warns you when the guest’s email is outside the allow list.
Groups: the ordering problem
There is one ordering trap specific to group-based permissions worth calling out. If the source has a permission like marketing-team@acme.com has Edit on /sites/marketing and you have mapped that group to marketing@newcompany.com, the replay is:
POST /sites/dest-site/items/{id}/invite
{
"recipients": [{ "email": "marketing@newcompany.com" }],
"roles": ["write"]
}
That API call assumes marketing@newcompany.com exists and has a directory object the SharePoint site can resolve. It does not verify that Alice is a member of that group. Membership is a directory-side concern and if the destination group is empty when the migration runs, the replay succeeds but nobody has effective access.
The right sequence is:
- Provision destination groups first (either manually or via AD Connect / HR sync)
- Populate group memberships so the right people are in the right groups
- Run the migration, which maps permissions to the populated groups
The migration tool cannot do step 1 and 2 for you — they are directory-maintenance tasks, not migration tasks. What we can do is flag when a mapped destination group has zero members at replay time, because that is almost always a sign that steps 1/2 were not completed correctly.
A worked example
Acme is acquired by New Company. The SharePoint site /sites/acme-marketing needs to move to newcompany.sharepoint.com/sites/marketing. The site has these permission assignments:
| Principal | Scope | Role |
|---|---|---|
| alice@acme.com | Site | Owner |
| bob@acme.com | Site | Member |
| marketing-team@acme.com | Site | Member |
| partner@external.com | /Shared Documents | Contributor |
| ex-employee@acme.com | Site | Member |
| anyone-with-the-link | /Shared Documents/Q1-Report.pdf | View |
Mappings:
# user-map.csv
source_upn,destination_upn
alice@acme.com,alice.smith@newcompany.com
bob@acme.com,bob.jones@newcompany.com
# group-map.csv
source_group,destination_group
marketing-team@acme.com,marketing@newcompany.com
Pre-flight output:
- alice → alice.smith: will replay
- bob → bob.jones: will replay
- marketing-team → marketing: will replay (group has 14 members on destination — looks healthy)
- partner@external.com: exception — no user-map row. Options: drop, invite as guest, map inline.
- ex-employee@acme.com: exception — no user-map row. Options: drop (default; user is gone).
- anyone-with-the-link: exception — destination tenant policy is “Existing guests only”, so Anyone-links are not reproducible. Choice: drop the link, or escalate to tenant admin to relax policy.
After human decisions (invite partner as guest, drop ex-employee, drop Anyone-link with an explicit note), the replay phase runs and emits a CSV of every permission written. The Monday-morning experience is that marketing works immediately, the partner gets an invitation email, and the Anyone-link is gone (as intended). No mystery access gaps, no silent data loss.
What we do not do
A few explicit non-goals, because we get asked:
- We do not fuzzy-match names. “alice@acme.com looks like alison@newcompany.com” is the beginning of a breach story. If you want fuzzy matching as a suggestion in the pre-flight UI, fine — that is on the roadmap. But no implicit writes without an explicit human confirmation.
- We do not write directory objects. Creating users, creating groups, populating group membership — all directory-side tasks. The migration maps to objects that already exist.
- We do not rewrite SPMT-style permission packages. The mapping operates on Graph/REST permission APIs, which is how you apply permissions after creation. If you have an SPMT workflow that applies permissions inside the migration package itself, that is a different path.
Related reading
- SharePoint Site Migration platform page
- SharePoint migration best practices: 2026 edition
- Migrating SharePoint Site Pages: why canvas layout matters
- Bulk mail migration with user mapping — same CSV pattern for mailboxes
Get started
A free workspace at app.migrationfox.com/register lets you upload a user map and a group map and run the pre-flight for free. You will see the exceptions list before you spend a byte.