September 28, 2026
GitHub source: https://github.com/lbakyl/box-folder-owner-migration
This is the sequel to my earlier post on programmatically creating home folders for new users on Box. That post solved the problem going forward, for new hires. This one solves it looking backward, for everyone who already has a folder sitting under a shared admin or service account from before the automation existed.
If you've ever inherited a Box tenant, you've probably seen this pattern: someone, at some point, needed to get new employees a working folder quickly. The fastest way to do that was to create it under an admin or service account and share it with the employee as an editor. It works. It also means that admin account (and anyone with sufficient rights on it) has standing access to every one of those folders, indefinitely, whether or not anyone remembers why.
Fixing this by hand in the Box UI, one folder at a time, doesn't scale past a handful of people, and doing it wrong on a folder with real data in it is not a mistake you want to discover after the fact.
The good news: Box's ownership transfer is a metadata operation, not a data operation. Changing who owns a folder doesn't move, copy, or touch a single byte inside it. It just updates a pointer. That means a folder with 150 GB in it transfers exactly as fast as an empty one, and there's no realistic scenario where this corrupts or loses data. The risk in this kind of migration is entirely about access: making sure the right person ends up owning it and nobody unintended keeps standing access, not about the data itself.
Full source, ready to run: github.com/lbakyl/box-folder-owner-migration.
npm install -g azure-functions-core-tools@4.
This runs the tool locally as a real Azure Functions host (the same
runtime a deployed Function App would use), just on your own machine.
Nothing here needs an actual Azure subscription.npm install.local.settings.json.example to local.settings.json as-is. No
values need filling in for this one; it just gives the local Functions
host somewhere to start.This is the part worth getting right, and it's a deliberate design choice, not an accident: the tool authenticates as the account that currently owns the folders, using a proper OAuth sign-in, rather than as some all-powerful service account that can impersonate anyone.
Why does it matter which one? Because only a folder's actual current owner can hand it over to someone else. I confirmed this the hard way against Box's own API. A co-owner, or an admin with broader rights but no direct relationship to that specific folder, gets a flat 403 trying to change ownership. There's no way around that with more permissions; it has to be the real owner.
The tempting shortcut here is to give a service account the "Make API calls using the as-user header" scope, which lets it act as any user in the enterprise on demand. Don't. That's the exact standing power this whole migration exists to remove. Trading one all-access admin account for one all-access automation credential isn't a fix; it's a rebrand of the same problem.
http://localhost:8765/callback.node tools/boxLogin.js
It prints a URL. Open it in a private browser window, sign in as the
folder owner, approve. The login is stored in ~/.box-migration on your
machine (never in the project folder), and refreshes itself automatically
for up to 60 days after its last use. Run node tools/boxLogin.js --logout
when you're done.
Start the local server:
func start
Find a folder ID (from its URL in the Box web UI) and the target user's Box
ID (node tools/lookupUsers.js [email protected]), then dry-run it:
curl -X POST http://localhost:7071/api/migrateHomeFolder \
-H "Content-Type: application/json" \
-d '{"folderId": "123456789", "userId": "987654321", "userName": "Jane Doe"}'
That returns a report (current owner, who'd be removed, storage check,
whether the folder needs to be relocated first) and changes nothing. Once
it looks right, add confirm: true to actually run it, and run the same
dry-run request one more time afterward: a 404 not_found response means
the old owner can no longer see the folder at all, which is the actual
proof it worked, not just a hopeful assumption.
Full commands, including how to keep specific collaborators, are in the repo's README.
This is the one that actually matters most, and it's not obvious from the
API docs. When you ask Box for a folder's collaborator list, it hands back
both collaborations created directly on that folder and access inherited
from a parent folder, in the same list, with no obvious flag telling you
which is which at a glance (the parent's ID is buried in the entry's item
field).
That distinction bit me directly on a real migration: I transferred ownership of a folder that had its own direct collaborator, confirmed the transfer worked, and moved on, except three other people still had full access to it afterward, because their access had never been on that folder at all. It came from a parent folder several levels up (a shared departmental structure), and transferring ownership of the child folder does nothing to that inherited grant. The folder had a new owner and the exact same standing access problem I was trying to fix.
The fix has two parts, and both matter: only ever touch a collaboration after confirming, by ID, straight from Box, that it actually belongs to the folder you're working on (not a parent), and if a folder has any inherited access on it at all, move it out of that parent tree as part of the migration, not only when it happens to block something else. Ownership transfer alone does not sever inherited access; only physically relocating the folder out of the shared structure does.
Box processes some folder operations in the background, and a large or
recently-touched folder can return 409 operation_blocked_temporary on the
next request. The trap here is assuming that error means nothing happened.
It doesn't necessarily. An earlier step in that same request (commonly,
moving the folder out of its parent) can have already gone through, leaving
the folder in a safe but incomplete state: no longer nested in the shared
structure, but not yet owned by the new person either. Re-checking the
folder's actual state after an error, rather than trusting the error
message alone, saved me from either assuming a completed step needed
redoing, or assuming an incomplete one was done.
A user's own Box storage limit counts everything they own, and transferring
them a large folder counts against it immediately. If their limit is too
small, the upgrade fails with 403 storage_limit_exceeded. Worth checking
before the transfer, not discovering after it's halfway done, which the
tool does automatically as part of the dry run.
The pattern here generalizes past Box: any platform where "who owns this" and "who can currently reach it" get conflated over time is a candidate for this same shape of fix: dry run everything, verify by proving the old owner genuinely can't reach it anymore rather than trusting a success message, and be paranoid about anything inherited rather than set explicitly.
Full source: github.com/lbakyl/box-folder-owner-migration
Enjoying this tutorial?
This site is a non-profit project. If it saved you some time, you can support it by getting Jan some coffee.
☕ Buy me a coffee
Comments