Microsoft Project Online retires September 30, 2026, migrate to a modern platform before it's too late.Start migration
Back to Blog
Migration

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.

Onplana TeamMay 1, 202611 min read

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

  1. Inventory every field via OData (Projects, Tasks, Resources) + every lookup table (week 1)
  2. Audit usage, retire anything below 5% coverage on the source side, not after migration (week 1–2)
  3. Snapshot calculated fields into static text/number fields BEFORE export, formulas don't migrate, values do (week 2)
  4. Map Project / Task / Resource fields to destination types and attachment points (week 3)
  5. Validate field values on the pilot project against the source OData read (week 4)
Where custom-field data lives in Project Online Four places the data hides, each has a separate export path 1. Field DEFINITIONS Name · Type · Attachment Lookup-table reference Required / multi-line / max OData: /CustomFields 2. Field VALUES Per-task / per-project value Linked by TaskUID + FieldUID Inside the .mpp ExtendedAttr OData: /Tasks(*)/CustomFields 3. Lookup TABLES Hierarchical code list Cost centres, depts, phases Parent-child via Code field OData: /LookupEntries 4. Calculated FORMULAS Project-formula syntax Cannot be exported as data Snapshot value → stored field UI only: PWA → Server Settings → Onplana model CustomFieldDefinition PROJECT / TASK / PROPOSAL CustomFieldValue Per-row entity reference Tag tree (hierarchy) For hierarchical lookups Resource → User profile Skill / dept / cost centre Snapshot → static field For migrated formula values All four exits exist via Onplana

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.

Custom-field coverage audit, typical mature PWA tenant (120 fields) % of active projects/tasks with a non-null value in the field Used >50%, keep + map (28 fields, 23%) Used 20–50%, keep but flag for cleanup (22 fields, 18%) Used 5–20%, judgment call (18 fields) Used <5%, retire (52 fields, 43%) Migrating only the top 50 fields cuts the data-migration line item by 40-60%, with no business impact. Run the audit first.

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:

  1. Create a sibling stored field with the same data type. Name it the same as the calculated field plus _Snapshot.
  2. 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 }
    
  3. Migrate the snapshot, not the formula. The new platform now has the value as static data.
  4. 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.estimatedHours instead 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.

Hierarchical lookup tables, two destination shapes Same source data, different access patterns on the destination side Source: PWA lookup table 01 Engineering 01.01 Backend 01.02 Frontend 02 Operations 02.01 SRE 02.02 Support MaskValue encodes parent Option A: flat DROPDOWN "01 Engineering / Backend" "01 Engineering / Frontend" "02 Operations / SRE" "02 Operations / Support" ✓ Cheapest to model ✗ Loses parent for filtering Option B: Tag tree Engineering ▾ ↳ Backend ↳ Frontend Operations ▾ ↳ SRE ✓ Filter by parent ✗ Slightly more setup

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=CustomFields in 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.

Start your free migration →

Related reading


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.

Microsoft Project OnlineCustom FieldsLookup TablesMigrationPWAODataEnterprise Custom FieldsPMOProject Server

Ready to make the switch?

Start your free Onplana account and import your existing projects in minutes.