Validating and submitting data to CRM
The CRM Helper Framework is a JavaScript library designed to simplify validation and data submission tasks in Power Pages, particularly for interacting with Dynamics 365. It provides a set of reusable functions to handle form validation, data submission to CRM entities, and user experience enhancements like error handling, button state management, and loading spinners. This framework is pre-installed with the base portal, making it readily available for you to use in your front-end development.
This page covers how to set up validation, use the framework's functions, and provides practical examples for common scenarios.
Quick reference
Quick reference of available functions
ValidateData(inputObjArr) |
Validates user input and displays error messages |
WebApi.ConstructData(inputsObj) |
Constructs a data object for CRM submission |
WebApi.Update(recordId, entityName, dataObj) |
Updates an existing CRM record |
WebApi.Create(entityName, dataObj) |
Creates a new CRM record |
WebApi.Delete(recordId, entityName) |
Deletes a CRM record |
WebApi.Retrieve(entityName, searchQuery) |
Retrieves records from a CRM table |
WebApi.CallCloudFlow(flowApiId, data) |
Calls a cloud flow and returns the response |
WebApi.UploadFile(inputName, entityName, entityId) |
Uploads a file to a file column on a record |
WebApi.ProcessFileAndUploadToNotes(inputName, entityName, entityId) |
Uploads a file as a note attachment |
DisableButton(buttonID) / EnableButton(buttonID) |
Disables or enables a button |
ShowLoadingSpinner(spinnerID) / HideLoadingSpinner(spinnerID) |
Shows or hides a loading spinner |
Creating your validation data object
Before using the framework's validation functions, you must create a validation data object.
This object is an array of input objects, where each object describes a form field to validate.
You can define a single input as [{...}] or multiple inputs as [{...},{...},{...}].
Base properties for all input types
| String |
The schema name of the target CRM field (e.g., dfe_firstname). |
| String |
The type of input displayed on the page. Supported types: text, text-area, email, tel, number, whole-number, decimal-number, money, date, radio, checkbox, select, file
|
| String |
A user-friendly name for the input, used in error messages.
e.g. "your first name" will display as "Enter your first name".
|
| Boolean |
true if the field is required, false otherwise. |
InputObj examples by input type
Expand the sections below to see complete inputObj examples, when to use each configuration, and what the ConstructData function outputs.
Available functions
The CRM Helper Framework provides core functions for validation and data submission, as well as helper functions for enhancing the user experience.
DfEPortal.ValidateData(inputObjArr, errorContainer)
Error messages on the page are handled within this function.
- Purpose
- Validates user input data on the webpage
- Parameters
-
inputObjArr: Array - the validation data object array (see above).
errorContainer: String (optional) - CSS selector for the error summary container. Defaults to #error-summary-container.
- Returns
- A boolean value (true or false) indicating whether data has passed validation.
- Example scenarios
-
- Validating a contact form to ensure all required fields (e.g., first name, email) are filled correctly.
- Checking a date input to confirm it's a valid date before submission.
- Ensuring a search field matches a record in the CRM before proceeding (e.g., finding an application by reference number)
DfEPortal.WebApi.ConstructData(inputsObj)
- Purpose
- Constructs a data object for submission to the CRM via the Power Pages WebApi.
- Parameters
- Array of input objects - the validated input object array.
- Returns
- The constructed data object.
- Example scenarios
-
- Preparing form data (e.g. first name, last name) for submission to a
contact entity.
- Formatting a date input into an ISO string for a CRM date/time field.
- Mapping a radio input to a boolean value for a CRM field.
DfEPortal.WebApi.Update(recordId, entityName, dataObj)
- Purpose
- Updates an existing record in the CRM.
- Parameters
-
recordId: String - The ID of the record to update.
entityName: String - Schema name of the table to update (e.g. contact).
dataObj: Object - Data object constructed by DfEPortal.WebApi.ConstructData.
- Returns
- A promise that resolves on success or rejects with an error.
- Example scenarios
-
- Updating a contact's email address after the user edits their profile.
- Modifying an application record with new details from a form submission.
DfEPortal.WebApi.Create(entityName, dataObj)
- Purpose
- Creates a new record in the CRM.
- Parameters
-
entityName: String - Schema name of the table (e.g. lead).
dataObj: Object - Data object constructed by DfEPortal.WebApi.ConstructData.
- Returns
- A promise that returns the ID of the created record on success or rejects with an error.
- Example scenarios
-
- Creating a new lead record when a user signs up.
- Adding a new contact record from a "Contact Us" form submission.
DfEPortal.WebApi.Delete(recordId, entityName)
- Purpose
- Deletes a record from the CRM.
- Parameters
-
recordId: String - ID of the record to delete.
entityName: String - Schema name of the table.
- Returns
- A promise that resolves on success or rejects with an error.
- Example scenarios
-
- Deleting a draft application record when a user cancels their submission.
- Removing a contact record when a user requests account deletion.
DfEPortal.WebApi.Retrieve(entityName, searchQuery)
- Purpose
- Retrieves records from a CRM table using an OData query.
- Parameters
-
entityName: String - Schema name of the table (e.g. contacts).
searchQuery: String - OData query string (e.g. ?$select=firstname,lastname&$filter=emailaddress1 eq 'test@example.com').
- Returns
- A promise that returns the query results on success or rejects with an error.
- Example scenarios
-
- Fetching a list of applications for the current user.
- Checking if a record exists before creating a duplicate.
- Populating a dropdown with data from a CRM table.
DfEPortal.WebApi.CallCloudFlow(flowApiId, data)
- Purpose
- Calls a Power Automate cloud flow and returns the response. Useful for complex business logic that cannot be performed client-side.
- Parameters
-
flowApiId: String - The API ID of the cloud flow to call.
data: Object - Data to pass to the cloud flow as input parameters.
- Returns
- A promise that returns the cloud flow's response object on success or rejects with an error.
- Example scenarios
-
- Checking if an application already exists before allowing the user to proceed.
- Performing complex validation that requires server-side data.
- Triggering a workflow that sends notifications or creates related records.
DfEPortal.WebApi.UploadFile(inputName, entityName, entityId)
- Purpose
- Uploads a file to a file-type or image-type column directly on the entity record.
- Parameters
-
inputName: String - The ID of the file input element (also used as the file column schema name).
entityName: String - Schema name of the table (e.g. dfe_applications).
entityId: String - The ID of the record to attach the file to.
- Returns
- A promise that resolves on success or rejects with an error.
- When to use
- Use this function when your entity has a file column or image column and you want to store the file directly on the record. This is the modern approach using Dataverse file columns.
- Example scenarios
-
- Uploading a profile photo to an image column on a contact record.
- Attaching a PDF document to a file column on an application record.
DfEPortal.WebApi.ProcessFileAndUploadToNotes(inputName, entityName, entityId)
- Purpose
- Uploads a file as a note (annotation) attachment linked to the entity record.
- Parameters
-
inputName: String - The ID of the file input element.
entityName: String - Schema name of the table (e.g. dfe_applications).
entityId: String - The ID of the record to attach the note to.
- Returns
- A promise that resolves with the file object on success or rejects with an error.
- When to use
- Use this function when you want to store files as note attachments in the annotations table, linked to the parent record. This is the traditional approach and is useful when you need to attach multiple files to a single record or when file columns are not available.
- Example scenarios
-
- Uploading multiple supporting documents to an application.
- Attaching evidence files that need to be viewable in the CRM timeline.
DfEPortal.DisableButton / EnableButton
- Purpose
- Disables or enables a button on the page to prevent multiple submissions.
- Parameters
buttonID: String - The ID of the button (e.g. submit-btn).
- Returns
- None.
- Example scenarios
-
- Disabling a submit button during form processing to prevent double submissions.
- Re-enabling the button if an error occurs, allowing the user to retry.
DfEPortal.ShowLoadingSpinner / HideLoadingSpinner
- Purpose
- Shows or hides a loading spinner to indicate processing.
- Parameters
spinnerID: String - The ID of the spinner element (e.g. loading-spinner).
- Returns
- None.
- Example scenarios
-
- Showing a spinner while submitting data to the CRM.
- Hiding the spinner when submission completes or fails.
Code examples
Below are practical examples demonstrating how to use the CRM Helper Framework in Power Pages. These examples assume the framework is included in your page (pre-installed with the base portal).
Example 1: Updating a Contact record
This example validates a form with first name and last name fields, then updates an existing contact record in the CRM.
{% extends 'DfE/Layouts/2ColumnWideLeft' %}
{% block title %}
{% assign title = page.title | split: ":" %}
<h1 class="govuk-heading-l">
<span class="govuk-caption-l">{{ title[0] }}</span>
{{ title[1] }}
</h1>
{% endblock %}
{% block main %}
{% unless user %}
<script>
window.location.href="{{ sitemarkers['DfE/Error/AccessDenied'].url }}"
</script>
{% endunless %}
{% unless params.contactid %}
<script>
window.location.href="{{ sitemarkers['DfE/Error/RecordNotFound'].url }}"
</script>
{% endunless %}
<div class="govuk-form-group">
<label class="govuk-label" for="firstname">
What is your first name?
</label>
<input class="govuk-input" id="firstname" name="firstname" type="text">
</div>
<div class="govuk-form-group">
<label class="govuk-label" for="lastname">
What is your last name?
</label>
<input class="govuk-input" id="lastname" name="lastname" type="text">
</div>
<button type="submit" id="submit-btn" class="govuk-button" data-module="govuk-button">Continue</button>
<div id="loading-spinner" class="loader govuk-visually-hidden"></div>
<script type="text/javascript">
const inputObj = [
{
identifier: "firstname",
type: "text",
friendlyName: "your first name",
required: true,
resolve: false
},
{
identifier: "lastname",
type: "text",
friendlyName: "your last name",
required: true,
resolve: false
}
];
$("#submit-btn").on("click", async function(event) {
event.preventDefault();
DfEPortal.DisableButton("submit-btn");
DfEPortal.ShowLoadingSpinner("loading-spinner");
try {
await DfEPortal.ValidateData(inputObj);
const dataObj = await DfEPortal.WebApi.ConstructData(inputObj);
const contactId = "{{ params.contactid }}";
const entityName = "contact";
// Update the contact record
await DfEPortal.WebApi.Update(contactId, entityName, dataObj);
// Redirect or notify the user of success
alert("Contact updated successfully!");
// Optionally redirect: window.location.href = "{{ sitemarkers['NEXT_PAGE_SITEMARKER'].url | add_query: 'contactid', params.contactid }}";
} catch (error) {
console.error("Error:", error);
// The framework automatically displays user-friendly error messages
// Re-enable the button and hide the spinner
DfEPortal.EnableButton("submit-btn");
DfEPortal.HideLoadingSpinner("loading-spinner");
}
});
</script>
{% endblock %}
Explanation:
- The
inputObj array defines validation rules for the first name and last name fields.
- The button is disabled, and a spinner is shown during processing.
ValidateData checks that both fields are filled (since required: true).
ConstructData prepares the data for submission to the CRM.
Update submits the data to the contact entity. If successful, a success message is shown; otherwise, the framework displays an error, and the button/spinner are reset.
Example 2: Creating a new Lead with email validation
This example validates an email field and creates a new lead record in the CRM.
{% extends 'DfE/Layouts/2ColumnWideLeft' %}
{% block title %}{% endblock %}
{% block main %}
<div class="govuk-form-group">
<h1 class="govuk-label-wrapper">
<label class="govuk-label govuk-label--l" for="emailaddress">
## 'What is your email address?' set as the title in the content web page
{{ page.title }}
</label>
</h1>
<input class="govuk-input" id="emailaddress" name="emailaddress" type="email">
</div>
<button type="submit" id="submit-btn" class="govuk-button" data-module="govuk-button">Continue</button>
<div id="loading-spinner" class="loader govuk-visually-hidden"></div>
<script type="text/javascript">
const inputObj = [
{
identifier: "emailaddress",
type: "email",
friendlyName: "your email address",
required: true,
resolve: false
}
];
$("#submit-btn").on("click", async function(event) {
event.preventDefault();
DfEPortal.DisableButton("submit-btn");
DfEPortal.ShowLoadingSpinner("loading-spinner");
try {
await DfEPortal.ValidateData(inputObj);
const dataObj = await DfEPortal.WebApi.ConstructData(inputObj);
const entityName = "lead";
const leadId = await DfEPortal.WebApi.Create(entityName, dataObj);
alert("Lead created successfully!");
{{ sitemarkers['NEXT_PAGE_SITEMARKER'].url }}?leadid=${leadId}`;
} catch (error) {
console.error("Error:", error);
// The framework automatically displays user-friendly error messages
// Re-enable the button and hide the spinner
DfEPortal.EnableButton("submit-btn");
DfEPortal.HideLoadingSpinner("loading-spinner");
}
});
</script>
{% endblock %}
Explanation:
- The
inputObj sets the validation rules for the email field
ValidateData checks the email input ensuring it has a value and that the value is in a valid email format.
ConstructData prepares the email for submission to the CRM.
Create submits the data to the lead entity, returning the new record's ID.
- The form is reset on success, and errors are handled with the button and spinner reset.
Example 3: Calling a cloud flow with custom error handling
This example calls a cloud flow to check if an application already exists, and displays a custom error message using the error helper functions if it does.
{% extends 'DfE/Layouts/2ColumnWideLeft' %}
{% block title %}{% endblock %}
{% block main %}
<div id="error-summary-container"></div>
<h1 class="govuk-heading-xl">Before you start</h1>
<p class="govuk-body">We need to check if an application already exists for your organisation.</p>
<button type="submit" id="check-btn" class="govuk-button" data-module="govuk-button">Check and continue</button>
<div id="loading-spinner" class="loader govuk-visually-hidden"></div>
<script type="text/javascript">
const flowApiId = "your-cloud-flow-api-id";
const organisationId = "{{ user.parentcustomerid.Id }}";
const applicationRoundId = "{{ settings['ApplicationRoundId'] }}";
// Attach event handler to the button
$("#check-btn").on("click", async function(event) {
event.preventDefault();
// Disable the button and show the spinner
DfEPortal.DisableButton("check-btn");
DfEPortal.ShowLoadingSpinner("loading-spinner");
try {
// Prepare the data to send to the cloud flow
const queryData = {
"OrganisationId": organisationId,
"ApplicationRoundId": applicationRoundId
};
// Call the cloud flow
const result = await DfEPortal.WebApi.CallCloudFlow(flowApiId, queryData);
// Check if the cloud flow returned an error
if (!result.success) {
throw result.error;
}
// Check the response and show custom error if needed
if (result.applicationexists) {
// Use the error helper functions to display a custom error
DfEPortal.Errors.ShowErrorSummary();
DfEPortal.Errors.AddErrorSummaryDetail("check-btn", "An application for your organisation already exists.");
DfEPortal.Errors.FocusErrorSummary();
// Re-enable button and hide spinner
DfEPortal.EnableButton("check-btn");
DfEPortal.HideLoadingSpinner("loading-spinner");
return;
}
// Success - redirect to next page
window.location.href = "{{ sitemarkers['NEXT_PAGE_SITEMARKER'].url }}";
} catch (error) {
console.error("Error:", error);
// Show a generic error message
DfEPortal.Errors.ShowErrorSummary();
DfEPortal.Errors.AddErrorSummaryDetail("check-btn", "There was a problem checking your application. Please try again.");
DfEPortal.Errors.FocusErrorSummary();
// Re-enable the button and hide the spinner
DfEPortal.EnableButton("check-btn");
DfEPortal.HideLoadingSpinner("loading-spinner");
}
});
</script>
{% endblock %}
Explanation:
- The
queryData object contains the parameters to pass to the cloud flow.
CallCloudFlow sends the request to the specified cloud flow and awaits the response.
- The response object contains properties returned by the cloud flow (e.g.
success, applicationexists).
- If the business logic check fails, custom error messages are displayed using
DfEPortal.Errors.ShowErrorSummary, AddErrorSummaryDetail, and FocusErrorSummary.
- This pattern is useful for server-side validation that cannot be performed in the browser.
Best practices
- Use
ValidateData before calling ConstructData or any Web API functions to ensure data integrity.
- Use try-catch blocks to handle errors, and leverage the framework's error display functions (
DfEPortal.Errors.ShowErrorSummary).
- Use
DisableButton and ShowLoadingSpinner during processing to prevent multiple submissions and provide visual feedback.
- Ensure your
inputObj properties (e.g. required, targetType) match the form's requirements to avoid unexpected behavior.
- Avoid logging sensitive data (e.g. email addresses) to the console in production.