Where this stands in 2026
Still appliesStill a valid pattern. Power Pages still has no built-in “save as draft” button on basic forms, classic real-time workflows are still supported (Power Automate flows still can’t run synchronously), and the client-side validation hooks used here are still documented.
Official docs for today’s approach
- Add custom JavaScript to a formPage_Validators, validation groups and entityFormClientValidate.
- Replace classic workflows with flowsWhy a real-time workflow is still the right tool here.
- Multistep formsSaves data step by step, which may remove the need for a draft button.
Written in 2021 for PowerApps Portals. See what things are called now.
Microsoft has since renamed the product and most of its parts. The text keeps the original terms.
| In this note | Today |
|---|---|
| PowerApps Portals | Power Pages |
| Entity | Table |
| Entity Form | Basic form |
| Web Form | Advanced (multistep) form |
| Entity List | List |
Overview
Building on the Locking Forms after Submission post, let’s explore how we can use the same idea to implement a “Save as Draft” feature to complement the “Submission” functionality. This can come in handy when the form is long or tedious, but you still want to know when the user has completely finished entering all of the data. Web Forms have a place here as well, especially when data entry needs to follow a step-by-step flow. However, my personal preference is to use Entity Forms due to various issues I’ve experienced with Web Forms over the years – but this post isn’t intended to make an argument for one over the other.
To do this, I’ve set up my pages and forms slightly differently.
The Setup (Building on the Previous Example)
This section is for anyone who has followed along with the aforementioned example, based on the Proposal entity I created.
The first thing we want to do is remove the Entity Form Metadata that saves the Status Reason on submission. From now on, we will need to drive that change using a different field and a different methodology.
I’ve added a few other changes, but rather than break them all out, here is an overview:
- I’ve created a Web Page with the Partial URL of “new” as a child of the Proposals page
- It uses the same Entity Form and Page Template as before, with the idea being that it would be our “create” page
- I’ve updated the Entity List on our Proposals Web Page to point to this new Web Page
- When someone creates a new record from the list, they’ll be taken here instead
- This page shares the same Entity Form as before, so it will still redirect to our Submission page on completion
- I’ve updated the “Proposal – Create” Entity Form to point to a new tab, which accepts our bare minimum requirement – in this case, the Name of the Proposal
- I’ve added some new fields and relationships to better prove out the process
- “Created By (Contact)”, Lookup to Contact – set by the Insert Entity Form
- “Modified By (Contact)”, Lookup to Contact – set by the Edit Entity Form, via Entity Form Metadata
- “Key Contacts”, N:N relationship between Proposal and Contact – this helps us drive permissions and have a relationship to the Proposal to showcase
- “Submitted On”, DateTime – this is how we track submissions
The Approach
Ultimately, we will do the following:
- Clone the “Update” button
- Turn the cloned button into a “Save” button
- Attach an event to the Submit button in order to fill our hidden date field when pressed
- Use a real-time process (workflow or plugin) to update the Status Reason field when the date field is changed
- If there’s a date, change to Submitted
- If there’s no date (such as a reversal in Dataverse to allow further updates Portal-side), change to Draft
- Using a real-time process will force the Portal to be aware of the change
I typically hide fields by adding them to a Section (with the section label hidden), naming the Section something like “hiddenProposalData”, and using the CSS I’ve shared on the Code Snippets page.
The Real-Time Process
For this example, I’m going to use a Real-Time Workflow. To implement this, I start by switching to the classic view of my solution.
- From here, I’m going to create a Real-Time Workflow for the Proposal table
- The triggers are on Create & on Update of my “Submitted On” field (field not shown selected in image)
- Despite the image, the scope should be Organization
- Add the logic to check whether Submitted On has data – if it does, set the Status Reason to “Submitted”; otherwise, set it to “Draft”
Recreated illustration: Create a Proposal Process: the classic Create Process dialog for a Workflow on the Proposal entity, with “Run this workflow in the background” unchecked so it runs real-time.
- Callout 1: Category: Workflow, on the Proposal entity.
- Callout 2: “Run this workflow in the background” is unchecked, which makes it a real-time workflow.
Recreated illustration. The original screenshot was lost with the old site; this drawing follows the article text.
Recreated illustration: Set the Proposal Triggers: workflow options with scope Organization, starting when the record is created and when the Submitted On field changes.
- Callout 1: Scope: Organization. (The original screenshot showed a narrower scope; the text corrected it to Organization.)
- Callout 2: Start when the record is created, and when “Submitted On” changes.
Recreated illustration. The original screenshot was lost with the old site; this drawing follows the article text.
Recreated illustration: Create the Proposal Steps: if Submitted On contains data, set Status Reason to Submitted; otherwise set it to Draft.
- Callout 1: The check: does Submitted On contain data?
- Callout 2: Yes: Status Reason becomes Submitted.
- Callout 3: No (for example, cleared in Dataverse to reopen it): back to Draft.
Recreated illustration. The original screenshot was lost with the old site; this drawing follows the article text.
The JavaScript
Next, I add the following JavaScript to the Proposal (standard) Entity Form. If it’s added to the Web Page instead, it will still be fine – jQuery won’t throw any errors in the chain for Insert (which uses #InsertButton) or Read-Only (no button).
This will clone the Update button, change the cloned button’s class, and add the appropriate onclick code to either add (Submit) or remove (Save) the hidden date that triggers the process.
Both are important in case a user attempts to Submit, hits a validation error, and then tries to Save – if we only applied the code to the Submit button, the date would still be filled and would trigger our process.
There are more sophisticated ways to solve this, but for the sake of the example we will keep it simple.
$(document).ready(function () {
var $updateBtn = $("#UpdateButton");
var $saveBtn = $updateBtn.clone();
$saveBtn.attr("id", "SaveButton")
.attr("onclick", "$('#new_submitdatefield').next('div.datetimepicker').datetimepicker().data('DateTimePicker').date(null);" + $updateBtn.attr("onclick"))
.removeClass("btn-primary")
.addClass("btn-default")
.val("Save")
.insertBefore($updateBtn);
$updateBtn.attr("onclick", "$('#new_submitdatefield').next('div.datetimepicker').datetimepicker().data('DateTimePicker').date(new moment());" + $updateBtn.attr("onclick"));
});End Result
Below you will find examples of this process in place. For some of the more complicated problems this can present, I will try to keep the Considerations section updated as I come across more.
Recreated illustration: Create Proposal: the new “Proposals – Create” insert form asks only for the proposal name, then redirects to the full edit form.
- Callout 1: The /proposals/new/ page asks for the bare minimum: the Name.
- Callout 2: After creation the user lands on the full edit form with both Save and Submit.
Recreated illustration. The original screenshot was lost with the old site; this drawing follows the article text.
Recreated illustration: Save the Proposal: the cloned Save button clears the hidden Submitted On date and saves, so the proposal stays a Draft.
- Callout 1: The cloned Save button (btn-default) clears the hidden date before posting.
- Callout 2: After the reload the record is still a Draft and still editable.
Recreated illustration. The original screenshot was lost with the old site; this drawing follows the article text.
Recreated illustration: Submit the Proposal: Submit stamps the hidden Submitted On date, the real-time workflow sets Submitted, and the page reloads read-only.
- Callout 1: Submit fills the hidden Submitted On field with the current time.
- Callout 2: The real-time workflow sets Status Reason to Submitted before the page reloads, so the read-only form is shown.
Recreated illustration. The original screenshot was lost with the old site; this drawing follows the article text.
Considerations
Being able to save the form without submitting it is helpful – unless it’s too inconvenient to satisfy all of the required fields and other conditions put in place. Having had requirements along the lines of “there needs to be at least N rows in each and every subgrid” as submission criteria, I know that such requirements can get extensive. Here are some options to help alleviate this:
- Add/Remove Dynamic Validations
- I’ve provided some decently flexible code samples on the Code Snippets page that can very easily add and remove validations on fields and sections
- Remove the Required logic on click of the Save button, then allow the function to proceed
- Dynamic Validations & Validation Groups
- Look to add these validations to specific validation groups, then check those validation groups as part of your
entityFormClientValidatefunction- E.g., only check this group if the triggering event is the Submit button, not the Save button
- Look to add these validations to specific validation groups, then check those validation groups as part of your
- Dynamic Validations & Custom Validation Functions
- Consider adding a custom validation function that checks for a specific class or attribute value on the element – such as
data-validate, which you set/unset on click of Save without removing the validation itself
- Consider adding a custom validation function that checks for a specific class or attribute value on the element – such as
Restored in 2026 from the Internet Archive at its original address. Figures are recreated illustrations of lost screenshots; the text was lightly copy-edited.