Power Pages archive

PowerApps Portal Security Part 3: Locking Forms after Submission

Using one web page, three entity forms and a bit of Liquid to lock a record once it has been submitted.

Updated 6 min readPortal security · Part 3 of 3Still applies

Where this stands in 2026

Still applies

Still works as written. Entity Forms are now called basic forms, but the Insert, Edit and Read Only modes, basic form metadata and the entityform Liquid tag all behave the same way. Power Pages still has no built-in setting to lock a record after it is submitted.

Official docs for today’s approach

Written in 2020 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 noteToday
PowerApps PortalsPower Pages
EntityTable
Entity PermissionTable permission
Entity FormBasic form
Web FormAdvanced (multistep) form
Entity ListList
Option SetChoice

Overview

Often, I am asked questions such as “How can I have one list of records that navigate to two different web pages?”, “How can I give users a receipt of their submission?”, or “How can I prevent a user from submitting the information more than once?” I find that all of these questions are best solved with the same trick I’ve been using since 2017: proper record naming conventions and a little bit of Liquid code. With this, we can develop a single web page and template to handle multiple scenarios.

If we were to build two different Web Pages – that is, two different URLs – then what is to stop someone from maliciously changing from one to the other? As an example, if you have https://contoso.powerappsportals.com/lead/edit/?id=123456 before submission and https://contoso.powerappsportals.com/lead/read/?id=123456 after, with only the fact that the record is in two different views to separate the functionality, then changing the URL of a “read” record to the “edit” version breaks your security.

There are many additional use cases for this method, including showing different forms based on the target record or the user’s settings, hiding Draft records from Portal users if they somehow get the ID, having field-based security that extends beyond what Entity Permissions can do, and more.

The end result is a Read-Only page after submission, which allows for a convenient printer-friendly receipt. You can also slightly modify the Read-Only form to show more or less information, as needed. Additionally, you can have processes that check the pre-update to see if the record has previously been submitted – for those times when a user abuses the Back button – in order to prevent multiple submissions.

An Example Implementation

Let’s say that you have a Portal to accept Proposals from partners. For the sake of simplicity, you want the users to create the Proposals as a Draft and then submit them to become Read-Only.

First, set up your entity and a new system form, as well as Entity Permissions and Web Roles. You can also optionally load up any default data you want to test against. Next, we need to set up the portal components.

Proposal Entity

Create the entity of your choice and add a field that you want to use to indicate a submission. This can be a DateTime, an Option Set, a Two Option or the like. For the example we are building today, I have simply added the following two values to the Status Reason field: Draft (1), Submitted (108620000). Draft is the Default Value.

Add these fields to your system form. I typically create a new form, named something like “Portal Form”, to keep Portal functionality isolated.

Templates

Let’s save the actual Template implementation for last. Start by creating a blank Web Template titled “Proposal”, and a Page Template titled “Proposal Details” that points to this Web Template.

Figure 1Page Template

Recreated illustration: Page Template: the Proposal Details page template record, of type Web Template, pointing at the Proposal web template.

  1. Callout 1: Type is Web Template, so the page is rendered by our Liquid.
  2. Callout 2: It points at the blank “Proposal” Web Template we fill in last.

Recreated illustration. The original screenshot was lost with the old site; this drawing follows the article text.

Web Pages

We will want to use two web pages for this: one for the list, and one for the record. I typically make the record a Child Page of the list, but there is no requirement to follow this practice. To start, leave the Entity Form, Web Form and Entity List fields blank. For the Proposals Web Page, set the Page Template to Full Page. For the Proposal Web Page, set it to our newly created Proposal Details Page Template.

Figure 2Proposal Web Pages

Recreated illustration: Proposal Web Pages: the Proposals list page using the Full Page template, and its child Proposal page using the Proposal Details template.

  1. Callout 1: Proposals, the list page, uses the Full Page template.
  2. Callout 2: Proposal, the record page, is a child of Proposals and uses Proposal Details.

Recreated illustration. The original screenshot was lost with the old site; this drawing follows the article text.

Entity Forms

While this approach will also work with Web Forms, Entity Forms are my preferred solution the large majority of the time. We want to create three nearly identical Entity Forms for the sake of this example.

1. Proposal

This will be our Edit form, using the Query String “id” as our key.

Figure 3Edit form setup

Recreated illustration: Edit Form Setup: the Proposal entity form in Edit mode, with the record source taken from the “id” query string parameter.

  1. Callout 1: Mode: Edit.
  2. Callout 2: The record comes from the query string, using “id” as the parameter name.
  3. Callout 3: Entity Permissions stay enabled, so the form honours the user’s Web Roles.

Recreated illustration. The original screenshot was lost with the old site; this drawing follows the article text.

Next, we want to set up the On Success Settings. For these settings, we want to redirect to the Proposal Web Page while appending the record ID as “id” and a custom query string of “success=true” so that we can differentiate a submission from a standard page load.

Figure 4On Success settings

Recreated illustration: On Success Settings: redirect back to the Proposal web page, appending the record ID as “id” and a custom query string.

  1. Callout 1: On Success: Redirect, back to the same Proposal web page.
  2. Callout 2: Append the record ID as “id”, so the template can load the record.
  3. Callout 3: A custom query string marks the request as a submission rather than a normal page load.

Recreated illustration. The original screenshot was lost with the old site; this drawing follows the article text.

Lastly, we want to set up our Entity Form Metadata. This is how we will indicate a Portal submission, as well as control the field to use as a switch in our form types. For our sample, we are setting a custom Date field (“Date of Submission”) to Today’s Date and changing the Status Reason to the value for “Submitted.”

Figure 5Entity Form Metadata

Recreated illustration: Entity Form Metadata: two attribute rows that, on save, set Date of Submission to today and Status Reason to Submitted.

  1. Callout 1: Date of Submission is set to today’s date when the form is saved.
  2. Callout 2: Status Reason is set to Submitted (108620000), which the Liquid template checks.

Recreated illustration. The original screenshot was lost with the old site; this drawing follows the article text.

2. Proposal – Read

This will be the Entity Form we show when the record is locked. Use the same initial settings as the first Entity Form, but with the Mode set to ReadOnly. We do not need any On Success Settings or Entity Form Metadata for this form in this example.

3. Proposal – Create

This is completely optional, depending on your actual criteria, but we will create it for the sake of the example. This form will be used when either the URL does not contain an ID, or the ID does not point to a record the user has access to.

Like the “Proposal – Read” Entity Form, we want to use the same base settings but change the Mode to Insert. We still do not need Entity Form Metadata, but we should create the same On Success Settings as the original Entity Form. Optionally, you can change the label of the Submit button to “Save”.

Entity List

Now we need to create an Entity List to show the existing records and to provide a location for a Create button for new ones. I created a basic System View that includes the Name, Date of Submission and Status Reason fields.

For the configuration, select the View and set both the Web Page for Details and Web Page for Create to the Proposal page. For the detail setting, use “id” as the ID Query String Parameter Name.

Web Pages (Part 2)

Now we need to go back and set up the form and list on our Web Pages. Set the Proposal Web Page to point at the Proposal Entity Form, and set the Proposals Web Page to point at the Proposals Entity List.

You may have noticed that there is now a cyclic relationship between the Entity Form and Web Page. When you navigate to the Proposal Web Page, it will show the Entity Form, and submitting this form will redirect you to the same Web Page. We do this instead of showing a success message on the Entity Form because data is not retrieved with the success message, and we need that data to identify that a change has occurred.

Web Template

Finally, we need to set up the Web Template. This template should be a good starting point for future implementations. Developers can easily remove unneeded functionality, such as the Create or messaging logic, and add anything that may be necessary to complete their requirements. For example, the logic can be generalized so that the entity name and/or field to check for previous submission are parameters used in an include tag.

Liquid/HTML
{%- extends 'Layout 1 Column' -%}

{%- block main -%}
  {%- include 'Page Copy' -%}
  {%- if page.adx_entityform -%}
    {%- assign formName = page.adx_entityform.name -%}
    {%- if params.id -%}
      {%- assign proposal = entities.jb_proposal[params.id] -%}
      {%- if proposal -%}
        {%- if proposal.statuscode.value == 108620000 -%}
          {%- assign formName = formName | append: ' - Read' -%}
          {%- if params.submit == 'true' -%}
            {%- assign messageClass = "success alert-success" -%}
            {%- assign messageSnippet = "Custom/RecordSubmitted" -%}
            {%- assign messageDefault = "Your Proposal has been submitted!" -%}
          {%- endif -%}
        {%- elsif params.submit == 'true' -%}
          {%- assign messageClass = "success alert-success" -%}
          {%- assign messageSnippet = "Custom/RecordSaved" -%}
          {%- assign messageDefault = "Your Proposal has been saved! You can submit at any time." -%}
        {%- endif -%}
      {%- else -%}
        {%- assign formName = formName | append: ' - Create' -%}
        {%- assign messageClass = "error alert-danger" -%}
        {%- assign messageSnippet = "Custom/MissingRecord" -%}
        {%- assign messageDefault = "The proposal you are looking for couldn't be found. You can create a new one here." -%}
      {%- endif -%}
    {%- else -%}
      {%- assign formName = formName | append: ' - Create' -%}
    {%- endif -%}
    {%- if messageClass -%}
      <div id="CustomMessagePanel" class="message alert {{ messageClass | default: 'error alert-danger' }}">
        <span id="CustomMessageLabel">{{ snippets[messageSnippet] | default: messageDefault | default: "An unexpected error has been encountered." }}</span>
      </div>
    {%- endif -%}
    {%- entityform name: formName -%}
  {%- endif -%}
{%- endblock -%}

End Result

1. Create a New Proposal

Figure 6Save a proposal

Recreated illustration: Save a Proposal: the Create form for a new proposal, then the redirect to the same page showing the editable form and a saved message.

  1. Callout 1: No id in the URL, so the template renders “Proposal – Create”.
  2. Callout 2: After saving, the redirect adds the record id; the record is still a Draft, so the edit form and the “saved” message appear.

Recreated illustration. The original screenshot was lost with the old site; this drawing follows the article text.

2. Submit a Proposal

Figure 7Submit a proposal

Recreated illustration: Submit a Proposal: submitting the draft sets the status to Submitted, and the page reloads as a read-only receipt.

  1. Callout 1: Submitting runs the Entity Form Metadata: today’s date and Status Reason “Submitted”.
  2. Callout 2: The same URL now renders “Proposal – Read”: a read-only, printable receipt.

Recreated illustration. The original screenshot was lost with the old site; this drawing follows the article text.

3. Edit Draft Proposals

Figure 8Edit a draft proposal

Recreated illustration: Edit a Draft Proposal: choosing a Draft row from the Proposals list opens the editable form.

  1. Callout 1: A Draft row in the Proposals Entity List.
  2. Callout 2: Its status is not Submitted, so the editable “Proposal” form is shown.

Recreated illustration. The original screenshot was lost with the old site; this drawing follows the article text.

4. View a Submitted Proposal

Figure 9View a submitted proposal

Recreated illustration: View Submitted Proposal: choosing a Submitted row from the list opens the read-only version of the same page.

  1. Callout 1: A Submitted row in the same list, linking to the same Proposal page.
  2. Callout 2: Status Reason is Submitted, so the template swaps in “Proposal – Read”. Changing the URL cannot reach an editable form.

Recreated illustration. The original screenshot was lost with the old site; this drawing follows the article text.

Restored in 2026 from the Internet Archive at its original address. Figures are recreated illustrations of lost screenshots; the text was lightly copy-edited.