Writing extension flows
MoveData ships with built-in flows that handle standard processing. These create Contacts, Opportunities, Campaigns, and their relationships. But every organisation has unique requirements — custom fields, business rules, conditional logic, and data transformations. Extension flows let you meet those requirements.
Extension flows are unmanaged Autolaunched Flows that run alongside the managed package flows at each pipeline stage. They follow the naming pattern [MoveData Extension] {Schema}: {Object} - {Stage}. For example: [MoveData Extension] Donation: Contact - Mapping. Each extension flow receives the same Record variable that the managed flow uses, adds field assignments, and returns it.
Managed flows vs extension flows
Managed flows (prefixed [MoveData]) belong to the package — never edit them. Extension flows (prefixed [MoveData Extension]) are customer-specific. These are the only flows you create or modify.
How extension flows fit into the architecture#
MoveData processes each notification through a pipeline of ordered stages. At each stage, the managed package runs its own flow first (typically at Order 5). It then runs any registered extension flows — typically at Order 6 for mapping, or Order 4 for record match extensions.
Extension flows join this pipeline through pipeline metadata registration. This is a custom metadata record that tells MoveData "run this flow at this stage, in this order." Without that registration, MoveData never calls the flow — even if it exists in the org.
Pipeline background
For background on how pipelines work, see Pipeline overview.
The core pattern: Record pass-through#
Every extension flow follows the same pattern:
- Receive a Record variable (a Salesforce object — Contact, Opportunity, Campaign, etc.) as input.
- Receive extra input variables with notification data (email, phone, amount, platform-specific fields).
- Null-check each input variable before using it.
- Assign values to fields on the Record variable.
- Return the modified Record as output.
No database operations needed
MoveData handles the insert or update for you. Your mapping extension flow never needs to save the record. This means no SOQL queries and no DML operations — only in-memory assignments and decisions.
Finding a relevant notification#
Before writing flow logic, you need to understand what data MoveData passes into your flow. The best way is to find a real notification from the org's integration and inspect its variables.
What is a notification key?#
MoveData tracks every event it processes as a notification. Each notification has a unique notification key — a 32-character alphanumeric string that ends with the letter N. You use this key to look up, inspect, and replay notifications.
Practical workflow#
- Find a recent successful notification for your platform in the Notifications tab.
- Pick one that matches your use case (e.g., a donation notification for donation mapping logic).
- Open the notification and click the Execution tab. Scroll to the relevant phase to see the variables available at that stage. Use the Variable Inspector to search for specific variables and view their values.
- Use these variable names and sample values to guide your flow design.
For a full guide on reading execution logs and inspecting variables, see Viewing execution logs.
Working with flow variables#
The Record variable#
The Record variable is central to every MoveData extension flow. It is a Salesforce object variable that works as both input and output:
- Input: MoveData passes the Record in with any existing field values.
- Output: MoveData reads back your changes and saves them.
The Salesforce object type depends on the stage and extension:
| Stage | NPSP object type | Nonprofit Cloud object type |
|---|---|---|
| Account | Account |
Account |
| Contact | Contact |
Account (Person Account) |
| Campaign | Campaign |
Campaign |
| Recurring | npe03__Recurring_Donation__c |
GiftCommitment |
| Donation | Opportunity |
GiftTransaction |
| Catalogue | Product2 |
Product2 |
| Order | Opportunity |
Opportunity |
| Order Item | OpportunityLineItem |
OpportunityLineItem |
In mapping flows, you read existing field values and assign new ones to the Record. MoveData saves the Record after all mapping flows complete.
In post-upsert flows, the Record is input-only. The Record has already been saved and includes the Salesforce record ID.
Variable naming conventions#
MoveData variable names follow predictable prefixes:
| Prefix | Meaning | Example |
|---|---|---|
| (none) | Core pipeline variable | Record, Key, Email, Phone, Amount, Status |
Custom_ |
Platform-specific field | Custom_DonorUserId, Custom_PaymentDate |
Campaign_ |
Campaign-context variable for downstream stages | Campaign_FundraiserKey, Campaign_TeamKey |
Context_ |
Processing context flag | Context_ContactType |
Questions_ |
Custom form field from Raisely | Questions_OptIn |
Marketing_ |
UTM attribution | Marketing_Source, Marketing_Campaign |
Config_ |
Pipeline configuration | Config_CampaignNameIgnoreCampaignCode |
Donor |
Donor personal details (donation stage) | DonorFirstName, DonorEmail |
MailingAddress_ |
Address components | MailingAddress_City, MailingAddress_Street |
Why you must null-check all input variables#
Every MoveData input variable can be null. The external platform may not provide a phone number. The donation may not have a fee. If you assign a null variable to a Record field, you risk overwriting existing data with blank or causing a runtime error.
Critical: null-check every input variable
Every input variable must be null-checked before assignment. This is the single most important rule in MoveData extension flow development.
The standard pattern is a Decision element before each Assignment:
Decision: "Assess Phone" (Phone IsNull = false?)
- Exists → Set Mobile Phone
- Default → Skip to next section
Pipeline metadata registration#
Why metadata registration matters#
Every extension flow needs a matching record in the movedata__MoveData_Pipeline__mdt custom metadata type. This record tells MoveData: "At this pipeline stage, for this object, run this flow, in this order."
Registration is required
Without the pipeline metadata record, MoveData will never run your flow. The flow will exist in the org, pass all validation, and deploy — but do nothing.
How to create the pipeline metadata record#
Clone an existing managed pipeline record for your object and stage. Then make these changes:
| Field | Value |
|---|---|
| DeveloperName | Replace the suffix with _EXT (e.g., DONATION_CONTACT_MAPPING_EXT) |
| Label | Keep the PIPELINE_ prefix (e.g., PIPELINE_DONATION_CONTACT_MAPPING) |
| Handler | The API name of your extension flow (e.g., MoveData_Donation_Contact_Mapping_Ext) |
| Order | 4 for record match extensions (before managed), 6 for mapping and post-upsert extensions (after managed) |
| Disabled | false |
| Type | Flow |
Commerce pipeline registration#
MoveData has two pipeline schemas: Donation and Commerce. Extension flows work across both — one flow serves both schemas. But each schema needs its own pipeline metadata record.
| Donation record | Commerce record | Handler (same flow) |
|---|---|---|
DONATION_CONTACT_MAPPING_EXT |
COMMERCE_CONTACT_MAPPING_EXT |
MoveData_Donation_Contact_Mapping_Ext |
DONATION_DONATION_MAPPING_EXT |
COMMERCE_ORDER_MAPPING_EXT |
MoveData_Donation_Donation_Mapping_Ext |
DONATION_CAMPAIGN_MAPPING_EXT |
COMMERCE_CAMPAIGN_MAPPING_EXT |
MoveData_Donation_Campaign_Mapping_Ext |
Shared flows
Do not create separate Commerce extension flows. One flow serves both schemas — only the pipeline metadata records differ.
Ordering#
The Order field controls which flows run first within a stage:
| Order | What runs | Why |
|---|---|---|
| 4 | Extension record match flow | Runs before the managed flow to intercept matching |
| 5 | Managed package flow | Built-in processing |
| 6 | Extension mapping / post-upsert flow | Runs after managed flow to override or add to results |
Fieldsets#
What fieldsets control#
Fieldsets control which Salesforce fields MoveData includes in its query when it looks up a record. If a field is missing from both the managed fieldset and the extension fieldset, MoveData does not query it. The field will appear as null on the Record variable, even if the record has data in that field.
This matters most for Decision elements. If your flow makes a decision based on a field that is not in the fieldset, the field value will be null and the decision may fail or produce incorrect results. Field assignments will still work without the fieldset entry, but it is best practice to add all fields your extension flow interacts with.
Fields used in decisions must be in the fieldset
Any field you evaluate in a Decision element must be included in the extension fieldset. Without it, MoveData does not query the field, and your decision logic receives null regardless of the actual value on the record.
How to create an extension fieldset#
Extension fieldsets follow this naming convention:
| Object | FieldSet API name | Label |
|---|---|---|
| Contact | MoveData_Donation_Contact_Fieldset_Ext |
[MoveData Extension] Donation: Contact Fieldset |
| Opportunity | MoveData_Donation_Donation_Fieldset_Ext |
[MoveData Extension] Opportunity Fieldset |
| Campaign | MoveData_Donation_Campaign_Fieldset_Ext |
[MoveData Extension] Donation: Campaign Fieldset |
| Account | MoveData_Donation_Account_Fieldset_Ext |
[MoveData Extension] Donation: Account Fieldset |
Fieldset pipeline registration#
Like extension flows, fieldsets need pipeline metadata records. Each fieldset needs records for both the Donation and Commerce pipelines.
| Field | Value |
|---|---|
| DeveloperName | e.g., DONATION_CONTACT_FIELDSET_EXT (and COMMERCE_CONTACT_FIELDSET_EXT for Commerce) |
| Handler | The API name of your fieldset (e.g., MoveData_Donation_Contact_Fieldset_Ext) |
| Type | Fieldset |
Fieldset Type
The key difference from flow pipeline records: set Type to Fieldset instead of Flow.
Testing by reprocessing notifications#
How to test your extension flow#
Once you have built your extension flow and deployed it to your sandbox, you can test it by reprocessing an existing notification.
- Open the Notifications tab in the MoveData app.
- Find the notification you used to inspect variables earlier.
- Open the notification and click Reprocess. This sends the notification back through the MoveData processing pipeline. Any changes you have made in your Salesforce flows will run against it.
- Once reprocessing completes, open the Execution tab to review the execution log. Check that your extension flow ran and that the field values were set as expected.
- Open the Salesforce record itself (Contact, Opportunity, Campaign, etc.) to confirm the changes appear on the record.
What to check#
- Your extension flow appears in the execution log at the correct phase.
- The Record fields were set to the expected values.
- No errors occurred during processing.
- The Salesforce record reflects the changes you made in your flow.
Best practices#
Always null-check input variables#
Every MoveData input variable is optional. Assigning a null variable to a Record field overwrites existing data with blank. Always wrap assignments in a null-check Decision.
One flow per schema/object/stage#
Extension flows are shared. There is one flow per schema/object/stage combination. Add multiple requirements to the same flow as separate branches.
Keep mapping flows lightweight#
Mapping flows should not contain Get Records, Create/Update/Delete Records, or callouts. Use only Assignments, Decisions, Formulas, and (when needed) Apex action calls. The Record variable is modified in-place and returned — no database operations needed. Keeping mapping flows lightweight helps you stay within Salesforce governor limits.
Verify picklist values before deployment#
Any hardcoded picklist value in the flow (e.g., LeadSource = "JustGiving") must already exist on the field in the target org. Deploying a flow that assigns a missing picklist value causes runtime errors.