Project Online Custom Fields Migration: Enterprise Custom Fields, Lookup Tables, and Calculated Fields
How to migrate Project Online enterprise custom fields, lookup tables, and calculated fields to a modern PM platform without losing values. Step-by-step + field mapping reference + free inventory script.
Project Online Custom Fields Migration: Enterprise Custom Fields, Lookup Tables, and Calculated Fields
Custom fields are where Project Online migrations get genuinely hard. The .mpp export covers the headline data, tasks, dates, dependencies, but the metadata that makes your PMO's reports actually useful lives in enterprise custom fields, lookup tables, and calculated fields. Get those wrong and your dashboards arrive at the new platform looking right but reading wrong.
This post walks the full custom-field migration path: what Project Online stores where, how to inventory and audit before you move, the field-by-field mapping to a modern destination, and the four traps that account for ~80% of the post-migration "where did the data go?" tickets.
If you haven't read the headline migration walk-through yet, start with How to Migrate from Microsoft Project Online, it gives you the 12-week project frame this work fits into. For the budget side, the cost of migrating from MS Project Online in 2026 covers the labor that custom fields specifically drive, they're the single biggest variable in the data-migration line item.
📌 TL;DR: 5-step custom-field migration plan
- Inventory every field via OData (Projects, Tasks, Resources) + every lookup table (week 1)
- Audit usage, retire anything below 5% coverage on the source side, not after migration (week 1–2)
- Snapshot calculated fields into static text/number fields BEFORE export, formulas don't migrate, values do (week 2)
- Map Project / Task / Resource fields to destination types and attachment points (week 3)
- Validate field values on the pilot project against the source OData read (week 4)
Step 1, Inventory every custom field via OData
Before you can migrate anything you need a complete list. The PWA UI shows you fields one at a time; the OData feed gives you the whole catalogue in one query. The free Project Online Inventory Checklist frames this whole tenant-inventory step end to end — custom fields are item 12 of 35, but the post-migration pain comes from the items most teams forget to count.
Pull the field definitions
GET https://your-tenant.sharepoint.com/sites/pwa/_api/ProjectData/CustomFields
Returns one row per defined field:
| Column | What it tells you |
|---|---|
Name |
Display name in the UI |
EntityTypeUID |
Project / Task / Resource attachment |
FieldType |
Text / Number / Date / Cost / Duration / Flag / Lookup |
LookupTableUID |
If non-null, the lookup table this field reads from |
IsMultiLineText |
Distinguishes single-line vs multi-line text fields |
IsRequired |
Whether the UI enforces a value |
Formula |
The calculated-field formula, if any |
RolldownToAssignments |
Whether task-level values cascade to assignment-level |
A typical mature Project Online tenant has 60–200 fields here. Save the result as a CSV, this becomes your migration manifest.
Pull the lookup-table values
GET https://your-tenant.sharepoint.com/sites/pwa/_api/ProjectData/LookupTables
GET https://your-tenant.sharepoint.com/sites/pwa/_api/ProjectData/LookupEntries
LookupTables gives you the table headers; LookupEntries gives you the values, one row per entry, with parent-child links via the MaskValue and ParentLookupEntryUID columns.
Hierarchical tables (cost centres, departments, phase codes) reveal themselves here. If MaskValue shows things like 01.02.03, you have hierarchy, flag the table for special handling in step 4.
Pull the per-task values
GET https://your-tenant.sharepoint.com/sites/pwa/_api/ProjectData/Tasks?$expand=CustomFields
Returns each task with its custom-field values inline. For large tenants paginate via $top and $skip, the feed throttles around 5,000 rows per call.
Step 2, Audit usage before you migrate
The single highest-impact thing you can do is delete unused fields on the source side before you start mapping. Most mature tenants find 40–60% of their custom fields are dead weight.
How to compute the coverage percentage
For each field in your inventory:
-- Pseudo-SQL against the OData feed (run as Power Query)
SELECT
f.Name,
COUNT(v.TaskUID) FILTER (WHERE v.Value IS NOT NULL) * 100.0 / COUNT(t.TaskUID)
AS coverage_pct
FROM CustomFields f
JOIN Tasks t ON t.ProjectUID IN (SELECT ProjectUID FROM Projects WHERE Status = 'Published')
LEFT JOIN TaskCustomFieldsValues v ON v.TaskUID = t.TaskUID AND v.FieldUID = f.UID
GROUP BY f.UID, f.Name
ORDER BY coverage_pct DESC
For Project-attached fields swap Tasks for Projects. For Resource-attached, swap to Resources.
Cleanup decision rules
| Coverage | Decision |
|---|---|
| >50% | Keep, these drive your reports and PMs use them daily |
| 20–50% | Keep but mark for post-migration review, likely a "department-specific" field that one team uses |
| 5–20% | Judgment call, talk to the field owner before deciding |
| <5% | Retire on the source side, almost always a one-off field for a project that ended years ago |
The audit takes a day. The labor saved on mapping, validation, and documentation is typically two to three weeks for a mature PMO. There is no project where this audit doesn't pay for itself many times over.
Step 3, Snapshot calculated fields BEFORE you export
Calculated fields are the single biggest "lost in translation" item in any Project Online migration. The formulas use a Microsoft-proprietary expression language; no destination platform parses them, so the field arrives empty in the new tool.
The fix is dead simple
Snapshot the value into a regular static field before you export.
For each calculated field you want to preserve:
- Create a sibling stored field with the same data type. Name it the same as the calculated field plus
_Snapshot. - Populate it once via a Project Server PowerShell script or a manual Excel-OData round-trip:
# Pseudo-PowerShell — paste each calculated value into its sibling Get-SPProjectField -Name "CalcField" | ForEach-Object { Set-SPProjectField -Name "CalcField_Snapshot" -Value $_.ComputedValue } - Migrate the snapshot, not the formula. The new platform now has the value as static data.
- If you still want it live-computed, recreate the formula in the destination tool's expression engine after migration. About 60% of the time you discover you didn't actually need it live, it was a one-off categorisation that the snapshot covers fine.
Why not just rewrite the formulas? You can, eventually. But rewriting on the destination side AFTER migration is ~5x cheaper than trying to do it in parallel with everything else during cutover. Snapshot first, rewrite the ones that matter later. Most don't.
Step 4, Map fields to the destination model
Every PM platform structures custom data slightly differently. Here's how Project Online's three-axis model maps to a typical modern destination.
| Project Online | Onplana equivalent | Notes |
|---|---|---|
| Project enterprise field | CustomFieldDefinition (entityType=PROJECT) |
1:1 mapping |
| Task enterprise field | CustomFieldDefinition (entityType=TASK) |
1:1 mapping |
| Resource enterprise field | User profile field | Modeled per-user, not per-task |
| Lookup table (flat) | DROPDOWN or MULTI_SELECT field | Values become options |
| Lookup table (hierarchical) | Tag tree with parent/child | Native parent-child support |
| Calculated field | Snapshot in static field | Re-create formula post-migration if needed |
| Multi-line text field | TEXT_LONG field type | Preserves line breaks |
| Cost field | NUMBER field with currency formatting | Currency conversion respects org default |
| Duration field | NUMBER (decimal hours) | Rolls up via task estimatedHours |
| Flag (boolean) | CHECKBOX field type | Maps cleanly |
Field-type fidelity
Five Project Online types have direct equivalents in Onplana's six-type custom-field system. The two edges:
- Cost fields that hold currency need an explicit currency code on the destination side. If your source field stored "$12,000" and your org runs multi-currency, lock the currency on the imported field to whatever the source project recorded, don't assume USD.
- Duration fields in Project Online store an integer count of "duration units" with a separate unit (hours, days, weeks). Onplana's NUMBER field is dimensionless; convert to hours at import time and document the conversion. Or model duration via
task.estimatedHoursinstead and skip the custom field entirely.
Hierarchical lookup tables, the special case
If your lookup table has parent-child entries (most cost-centre and department codes do), you have two paths.
Pick Option A (flat dropdown) when the hierarchy is a UI grouping but reports never aggregate by parent. The flattened display name "01 Engineering / Backend" keeps the visual nesting; reports that need the leaf value just read the field as-is.
Pick Option B (Tag tree) when reports DO aggregate by parent, e.g. "show me total budget by Department, with sub-allocation by team." The tag tree keeps the parent-child relationship native, and Onplana's filter UI lets you scope a query by either level.
Step 5, Validate field values on the pilot
Migration is only successful if the values in the new platform match the source. For custom fields specifically the validation has three checks.
Coverage check
Did every source row that had a value end up with a value in the destination?
COUNT(non_null source values) == COUNT(non_null destination values)
If destination is lower, look at field-type mismatches first (most commonly: a Date field that arrived as TEXT because the destination didn't parse the source format). If destination is higher you have ghost values from a previous import, clear the destination first and re-run.
Type-fidelity check
For each field type, sample 10 rows and confirm:
- Number / Cost: precision is preserved (12.50, not 12.5 or 12)
- Date: timezone is preserved (date-only fields don't shift; timestamp fields land in UTC consistently)
- Multi-line text: line breaks survived
- Lookup: the imported value matches an existing option on the destination, not a free-text overwrite
Rollup check
If your source field had RolldownToAssignments = true, confirm the destination platform's equivalent rollup behaviour matches. Some destinations (Onplana included) compute rollups on read rather than store them, the values you see in the destination's UI may not match the source's stored rollup but should match the source's displayed rollup.
The four traps that account for ~80% of "where did the data go?" tickets
After dozens of Project Online migrations, the same four issues come up again and again.
1. Resource custom fields modelled as task fields
Project Online lets you attach custom fields to Resources (e.g. "Skill Set"). On the destination side these usually need to land as user profile fields, not task or project fields. If you import them as task fields, every task ends up needing a "Skill Set" value entered manually, when the actual data wants to live on the assignee, not the work.
Fix: in step 4 of the inventory, sort by EntityTypeUID = Resource and route those rows to your destination's user-profile schema, not its custom-field schema.
2. Multi-value lookup fields collapsed to single-value
Project Online's MULTI_SELECT lookup field stores values as a delimited string ("OptionA;#OptionB;#OptionC"). If your importer reads each value as a single string, you end up with one option called "OptionA;#OptionB;#OptionC" instead of three separate selections.
Fix: pre-process the OData export to split on the ;# delimiter and emit one row per value. Onplana's wizard handles this natively for known multi-select formats; CSV imports require manual splitting.
3. Calculated fields imported as static text, then "live-updated" by users
Once a snapshot is in the destination, users sometimes treat it as a regular field and start editing it. Six months later the values diverge from what the formula would have computed, and your reports start showing different numbers depending on whether they read the snapshot or recompute.
Fix: rename snapshotted fields with a _Snapshot or _AsOf2026-09-30 suffix on the destination side and lock them read-only. Re-create the formula as a computed view filter (or a destination-tool computed field if available) for live use; keep the snapshot as historical reference.
4. Currency-typed fields lose their currency
Project Online's Cost field type stores a number with implicit organisation-default currency. After migration the value is just a number, if your org runs multi-currency, you've lost the currency context. Reports that mix projects across currencies start summing apples and oranges.
Fix: at import time, capture the source organisation's default currency and tag every imported cost field's value with it. Onplana's import wizard does this automatically when the source project has a recorded currency; manual CSV imports need an explicit Currency column.
Migrating to Onplana specifically
If Onplana is the destination, the custom-field path is paved:
- Built-in OData reader, connect your PWA site URL via Settings → Import → Project Online and the wizard reads
/CustomFields,/LookupEntries,/Tasks?$expand=CustomFields, and/Projects?$expand=CustomFieldsin one pass. Field definitions are created in your Onplana org; values are reconnected to imported tasks via TaskUID. - MULTI_SELECT auto-split, multi-value lookup fields are split on the
;#delimiter automatically. - Currency lock, Cost fields inherit the source project's currency; nothing collapses to a generic number.
- Tag-tree hierarchy, hierarchical lookup tables can land as a Tag tree with one click, preserving parent-child relationships for filtering.
- Free tier, you can run the entire custom-field import on the FREE plan and validate the result before upgrading. Schema and value imports are not plan-gated.
Related reading
- How to Migrate from Microsoft Project Online: A Step-by-Step Guide, the full 12-week migration plan this post fits into
- Cost of Migrating from MS Project Online in 2026, custom fields are the biggest variable in the data-migration line item
- Project Online Migration Checklist 2026, printable cutover checklist
- Microsoft Project Online End-of-Life: Timeline & Decisions, September 30, 2026 deadline
- Why Project Online Migrations Fail, and how to avoid each pattern
- MS Project Migration Compatibility Audit 2026, the source-side diagnostic to run before exporting
Inventory the rest of your tenant before you export Custom fields are 1 of 35 items most migrations underestimate. The free Project Online Inventory Checklist walks every category — projects, resources, custom fields, integrations — with notes, owners, and PDF export. → Open the inventory checklist
Need help with a specific custom-field shape that this post didn't cover? Get in touch, we've seen most of them and can usually point you at the right destination model in 10 minutes.
Ready to make the switch?
Start your free Onplana account and import your existing projects in minutes.