Fundraising & Donations extension (pre-1.81)
Version 1.81 of the Fundraising & Donations Extension changed where platform keys are stored. Platform keys are unique identifiers that link Salesforce records to their corresponding records on the source fundraising platform.
If you are upgrading from a version below 1.81, you must run a migration script to move existing platform key data into the new structure. Failing to migrate may result in duplicate records, as MoveData can no longer match existing contacts and accounts.
Does this article apply to you?#
Check your version first — most people who land here do not need this article.
- Go to Setup > Installed Packages.
- Find MoveData NPSP Extensions.
- Read the Version Number.
Version numbers are not decimals
The number after the first dot counts up as a whole number. So the order is 1.80, then 1.81, then 1.100, then 1.157, then 1.189 — each one newer than the last.
1.100 and anything above it is newer than 1.81, not older.
| Your extension version | What to do |
|---|---|
| 1.80 or lower | Follow this article. |
| 1.81 or higher — including any 1.1xx version | Stop here. There is nothing to migrate. Your platform keys moved to the new structure when you upgraded. |
Fields and objects labelled [Deprecated] are expected
Seeing [Deprecated] against a MoveData field or object is normal and is not a fault. Since extension v1.157 those fields and objects are unmanaged by MoveData and you may delete them at your own discretion. See the release notes for v1.157.
Back up your data before upgrading
Before you begin, export your Salesforce data. Go to Setup > Data Export > Export Now to create a full backup. Perform the upgrade and migration in a sandbox environment first.
Before you begin#
- Confirm your extension version is 1.80 or lower, as above.
- Ensure you have System Administrator access to Salesforce.
- Note your MoveData App version too (Setup > Installed Packages > MoveData). The script writes into objects that ship with the app, not with the extension, so the app is upgraded first.
Migration steps#
Do these in order. The order matters: the objects the script writes into belong to the MoveData App, and upgrading the extension before the app can leave your data stranded in objects that nothing reads.
1. Upgrade the MoveData App#
Install the most recent version of the MoveData App.
- If your app is earlier than v1.167, follow MoveData App (pre-1.167) and return here at its Step 3.
- Otherwise see Upgrading MoveData.
2. Confirm the MoveData batch job has finished#
Upgrading the app starts an Apex batch job called MoveDataBatchWorker, which moves platform key data into the app's own objects.
Let it finish before you run the script.
- Go to Setup > Apex Jobs.
- Look for jobs named MoveDataBatchWorker.
- The migration is complete when every
MoveDataBatchWorkerjob shows Completed and no new job has started for at least five minutes.
3. Open the Developer Console#
In Salesforce, click the gear icon and select Developer Console. Go to Debug > Open Execute Anonymous Window.

4. Run the migration script#
Paste the following Apex script into the Execute Anonymous window:
// MoveData: migrate pre-1.81 platform keys into the MoveData App's platform key objects.
// Safe to run on any org - it checks what exists before it changes anything.
String legacyObject = 'md_npsp_pack__Platform_Key__c';
String contactObject = 'movedata__Contact_Platform_Key__c';
String accountObject = 'movedata__Account_Platform_Key__c';
Map<String, Schema.SObjectType> orgObjects = Schema.getGlobalDescribe();
Boolean hasLegacy = orgObjects.containsKey(legacyObject);
Boolean hasTargets = orgObjects.containsKey(contactObject) && orgObjects.containsKey(accountObject);
List<SObject> legacyRows = new List<SObject>();
Set<String> platformKeys = new Set<String>();
Map<String, Id> contactKeyOwner = new Map<String, Id>();
Map<String, Id> accountKeyOwner = new Map<String, Id>();
List<SObject> toInsert = new List<SObject>();
List<SObject> toDelete = new List<SObject>();
Integer alreadyMigrated = 0;
Integer keyHeldElsewhere = 0;
Integer noPlatformKey = 0;
Integer noParent = 0;
if (hasLegacy == false) {
System.debug('List Size: 0');
System.debug('Nothing to migrate: this org has no ' + legacyObject + ' object.');
} else if (hasTargets == false) {
System.debug('Stopped: this org has no ' + contactObject + ' object.');
System.debug('Upgrade the MoveData App first, confirm MoveDataBatchWorker has finished, then run this script again.');
} else {
legacyRows = Database.query(
'SELECT Id, md_npsp_pack__Platform__c, md_npsp_pack__Platform_Key__c,'
+ ' md_npsp_pack__Contact__c, md_npsp_pack__Account__c'
+ ' FROM ' + legacyObject + ' LIMIT 2000');
System.debug('List Size: ' + legacyRows.size());
for (SObject row : legacyRows) {
String key = (String) row.get('md_npsp_pack__Platform_Key__c');
if (String.isNotBlank(key)) platformKeys.add(key);
}
}
if (platformKeys.isEmpty() == false) {
for (SObject row : Database.query('SELECT movedata__Platform_Key__c, movedata__Contact__c FROM '
+ contactObject + ' WHERE movedata__Platform_Key__c IN :platformKeys')) {
contactKeyOwner.put((String) row.get('movedata__Platform_Key__c'), (Id) row.get('movedata__Contact__c'));
}
for (SObject row : Database.query('SELECT movedata__Platform_Key__c, movedata__Account__c FROM '
+ accountObject + ' WHERE movedata__Platform_Key__c IN :platformKeys')) {
accountKeyOwner.put((String) row.get('movedata__Platform_Key__c'), (Id) row.get('movedata__Account__c'));
}
}
for (SObject row : legacyRows) {
Id legacyId = (Id) row.get('Id');
String key = (String) row.get('md_npsp_pack__Platform_Key__c');
String platform = (String) row.get('md_npsp_pack__Platform__c');
Id contactId = (Id) row.get('md_npsp_pack__Contact__c');
Id accountId = (Id) row.get('md_npsp_pack__Account__c');
if (String.isBlank(key)) {
noPlatformKey++;
System.debug('Left in place, no platform key: ' + legacyId);
continue;
}
if (contactId == null && accountId == null) {
noParent++;
System.debug('Left in place, no contact or account: ' + legacyId);
continue;
}
Boolean safeToRemove = true;
if (contactId != null) {
if (contactKeyOwner.containsKey(key) == false) {
SObject target = orgObjects.get(contactObject).newSObject();
target.put('movedata__Platform__c', platform);
target.put('movedata__Platform_Key__c', key);
target.put('movedata__Contact__c', contactId);
toInsert.add(target);
contactKeyOwner.put(key, contactId);
} else if (contactKeyOwner.get(key) == contactId) {
alreadyMigrated++;
} else {
keyHeldElsewhere++;
safeToRemove = false;
System.debug('Left in place, contact platform key ' + key + ' already belongs to '
+ contactKeyOwner.get(key) + ': ' + legacyId);
}
}
if (accountId != null) {
if (accountKeyOwner.containsKey(key) == false) {
SObject target = orgObjects.get(accountObject).newSObject();
target.put('movedata__Platform__c', platform);
target.put('movedata__Platform_Key__c', key);
target.put('movedata__Account__c', accountId);
toInsert.add(target);
accountKeyOwner.put(key, accountId);
} else if (accountKeyOwner.get(key) == accountId) {
alreadyMigrated++;
} else {
keyHeldElsewhere++;
safeToRemove = false;
System.debug('Left in place, account platform key ' + key + ' already belongs to '
+ accountKeyOwner.get(key) + ': ' + legacyId);
}
}
if (safeToRemove) toDelete.add(row);
}
System.debug('To migrate: ' + toInsert.size());
System.debug('Already migrated: ' + alreadyMigrated);
System.debug('Left in place, key held by another record: ' + keyHeldElsewhere);
System.debug('Left in place, no platform key: ' + noPlatformKey);
System.debug('Left in place, no contact or account: ' + noParent);
if (toInsert.isEmpty() == false) insert toInsert;
if (toDelete.isEmpty() == false) delete toDelete;
System.debug('Migrated: ' + toInsert.size() + ' | Legacy records removed: ' + toDelete.size());
Click Execute.
The script checks what your org has before it changes anything, so it is safe to run more than once, and safe to run on an org that turns out to need nothing.
5. Check the results#
Click the Debug Only toggle in the log output and read the last few lines.
| Line | What it means |
|---|---|
List Size |
How many old platform key records the script read. It reads 2,000 at a time. |
Migrated |
How many records were written into the app's platform key objects. |
Legacy records removed |
How many old records were deleted, having been migrated or found already migrated. |
Already migrated |
Records the MoveData App had already moved for you. Nothing to do. |
Left in place, … |
Records the script did not touch, each one also named on its own line above. See Troubleshooting. |

6. Rerun until nothing is migrated#
The script processes 2,000 records at a time.
Run it again while Migrated is above zero.
You are finished when Migrated: 0.
At that point the only old records left are the ones the script reported as Left in place — List Size will keep reporting those every run, so do not wait for List Size: 0 if the script is telling you records were left behind.
7. Upgrade the extension#
Now install the most recent version of the Fundraising & Donations Extension. See Upgrading MoveData extensions for instructions.
Troubleshooting the migration#
"Stopped: this org has no movedata__Contact_Platform_Key__c object."#
Your MoveData App is older than v1.167, so the objects the script writes into do not exist yet. Nothing was changed.
Upgrade the app first — see MoveData App (pre-1.167) — wait for MoveDataBatchWorker to finish, then run the script again.
Note that this message appears instead of the List Size line.
"Left in place, contact platform key … already belongs to …"#
The platform key on an old record is already held by a different contact or account in the app's platform key objects. Platform keys are unique, so the script cannot write a second copy, and it will not guess which record is right. It leaves the old record alone and reports both record IDs.
To resolve each one:
- Note the two IDs on the message: the record that holds the key, and the old platform key record.
- Open the record that holds the key by pasting its ID into the URL:
https://yourdomain.lightning.force.com/lightning/r/movedata__Contact_Platform_Key__c/RECORD_ID/viewFor an account platform key, usemovedata__Account_Platform_Key__cin the URL instead. - Check which contact or account the key should belong to. If you are not sure, contact MoveData support — this usually means two records represent the same supporter.
- If the record that holds the key is correct, delete the old platform key record. Nothing needs to be migrated from it.
- If it is wrong, correct the contact or account on it, then run the script again.
To list every platform key record for one key value, open the Query Editor tab in the Developer Console:
SELECT Id, movedata__Platform_Key__c, movedata__Contact__c
FROM movedata__Contact_Platform_Key__c
WHERE movedata__Platform_Key__c = 'YOUR_KEY_VALUE'
Check both contact and account platform keys
A key may be held on either movedata__Contact_Platform_Key__c or movedata__Account_Platform_Key__c.
Run the query against both objects if you cannot find it on the first.
"Nothing to migrate" but you can still see platform key data#
If the script reports Nothing to migrate while Setup still shows an [Deprecated] Platform Key object holding records, the extension has already been upgraded past v1.157 without the migration being run.
The records are still in your org and still hold your data, but Salesforce no longer lets Apex reach that object, so neither this script nor the MoveData App can migrate them. The same applies to deprecated fields left holding values on Account, Campaign, Contact or Opportunity.
Do not delete anything. Contact MoveData support and say that your platform keys were left behind by the upgrade — the data can still be read and moved through the Salesforce API.
DmlException — duplicate value found#
You should not see this with the script above: it checks for existing platform keys before it writes. If it does happen, no records from that execution were migrated — the whole execution rolls back, including the deletions, so your org is never left half migrated. Rerun the script, and if the error repeats, contact MoveData support with the full error line.
After migration#
Once the script reports Migrated: 0 and you have upgraded the extension:
- Resume normal MoveData notification processing.
- Monitor the first few notifications to confirm they process with Success status.
- Verify that contacts and accounts match correctly — no new duplicates should appear.