<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Krzysztof Mikołajec</title>
    <description>The latest articles on DEV Community by Krzysztof Mikołajec (@meecool).</description>
    <link>https://dev.to/meecool</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2942628%2Ff8e6f874-0f55-4d89-968b-e623a1534183.png</url>
      <title>DEV Community: Krzysztof Mikołajec</title>
      <link>https://dev.to/meecool</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/meecool"/>
    <language>en</language>
    <item>
      <title>Power Apps Custom Page Dialogs in Model-Driven Apps: Passing &amp; Receiving Data</title>
      <dc:creator>Krzysztof Mikołajec</dc:creator>
      <pubDate>Thu, 24 Apr 2025 13:03:43 +0000</pubDate>
      <link>https://dev.to/meecool/power-apps-custom-page-dialogs-in-model-driven-apps-passing-receiving-data-4cpe</link>
      <guid>https://dev.to/meecool/power-apps-custom-page-dialogs-in-model-driven-apps-passing-receiving-data-4cpe</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Extending standard practices with parameterized dialogs and callback patterns.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  👋 Introduction
&lt;/h3&gt;

&lt;p&gt;Model-Driven Apps (MDA) are powerful, but historically lacked flexibility when it came to user-friendly modal dialogs. The introduction of &lt;strong&gt;Custom Pages&lt;/strong&gt; in Power Apps brought new possibilities - especially when used as dialogs.&lt;/p&gt;

&lt;p&gt;You’ve probably seen great content already like &lt;a href="https://www.matthewdevaney.com/power-apps-custom-page-modal-dialog-for-model-driven-apps/" rel="noopener noreferrer"&gt;Matthew Devaney’s tutorial&lt;/a&gt; or &lt;a href="https://learn.microsoft.com/en-us/power-apps/developer/model-driven-apps/clientapi/navigate-to-custom-page-examples" rel="noopener noreferrer"&gt;Microsoft’s official documentation&lt;/a&gt;. These cover the basics of using &lt;code&gt;Xrm.Navigation.navigateTo()&lt;/code&gt; to open custom pages as modal dialogs.&lt;/p&gt;

&lt;p&gt;This post builds on those ideas and focuses on &lt;strong&gt;two core improvements&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📥 &lt;strong&gt;Passing structured parameters&lt;/strong&gt; to Custom Pages&lt;/li&gt;
&lt;li&gt;📤 &lt;strong&gt;Receiving data back (callbacks)&lt;/strong&gt; after the dialog closes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s walk through the challenges, the solutions, and see some code you can reuse.&lt;/p&gt;




&lt;h3&gt;
  
  
  🧩 The Challenge
&lt;/h3&gt;

&lt;p&gt;Custom Pages offer a canvas-like experience inside MDAs, but:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;navigateTo()&lt;/code&gt; only supports a single parameter (&lt;code&gt;recordId&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The dialog can't return values directly via &lt;code&gt;.then()&lt;/code&gt; — &lt;code&gt;Back()&lt;/code&gt; doesn't support return values&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These limitations make it hard to build richer UI interactions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Approval popups with comment capture&lt;/li&gt;
&lt;li&gt;Multi-value form input for background processing&lt;/li&gt;
&lt;li&gt;Conditional logic on dialog result&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🚀 Solution Overview
&lt;/h3&gt;

&lt;p&gt;Here’s how we enhance the dialog experience:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pass structured data&lt;/strong&gt; by serializing it to JSON and putting it in the &lt;code&gt;recordId&lt;/code&gt; field&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Receive data back&lt;/strong&gt; using a callback mechanism powered by a custom Dataverse table and a session ID&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps our dialogs lightweight, extensible, and decoupled.&lt;/p&gt;




&lt;h3&gt;
  
  
  📦 Step 1: Passing Data with JSON
&lt;/h3&gt;

&lt;p&gt;Instead of just passing a &lt;code&gt;recordId&lt;/code&gt;, we serialize a full object into a string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;pageInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;pageType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;custom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="nx"&gt;customPageName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;entityName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;recordId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;recordId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recordId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;additionalParameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;additionalParameters&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

      &lt;span class="nx"&gt;Xrm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Navigation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigateTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;navigationOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetchCallbackData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cbData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cbData&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;callbackFn&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;callbackFn&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
              &lt;span class="na"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;callbackData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cbData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;entityName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;recordId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="nx"&gt;recordId&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;triggerCustomPageOpenEvent error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  🖼️ Step 2: Reading Parameters in Custom Page (Power Fx)
&lt;/h3&gt;

&lt;p&gt;In your custom page, parse the incoming JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Set(varInput, ParseJSON(Param("recordId")));
Set(gblRecordId, Text(varInput.recordId));
Set(gblAdditionalParams, ParseJSON(varInput.additionalParameters));
Set(gblSessionId, Text(varInput.sessionId));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your page can be fully dynamic!&lt;/p&gt;




&lt;h3&gt;
  
  
  🔄 Step 3: Returning Data via Callback
&lt;/h3&gt;

&lt;p&gt;In the Power Fx "Submit" button (or other buttons as we design), we write to a custom table (e.g., &lt;code&gt;custompagescallback&lt;/code&gt; - In the end I am providing the link to the solution you import in your environment with custompagescallback table) using the sessionId:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Patch(
  custompagescallback,
  Defaults(custompagescallback),
  {
    sessionid: gblSessionId,
    callbackjson: JSON({ Status: drpStatus.Selected.Value }),
    statuscode: 1,
    statecode: 0
  }
);
Back();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  💡 Step 4: Handling the Result in JS
&lt;/h3&gt;

&lt;p&gt;Here’s the example of handler you can include in your &lt;code&gt;CustomPageHandler.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// after Web Resource loads...&lt;/span&gt;
&lt;span class="nx"&gt;CustomPageHandler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;myHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// input.formContext, input.callbackData, input.entityName, input.recordId&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Got callback data:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;callbackData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;callbackData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Rejected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;statecode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Inactive&lt;/span&gt;
        &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;statuscode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Approved&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;statecode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Inactive&lt;/span&gt;
        &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;statuscode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;865420001&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can further extend this to support other types of inputs or multi-record updates. Here is where you can manipulate the MDA form after receiving data from the Custom Page. &lt;/p&gt;




&lt;h3&gt;
  
  
  💼 Real-World Use Cases
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Approval Flows&lt;/strong&gt; – Capture decision and notes via dialog&lt;/li&gt;
&lt;li&gt;📁 &lt;strong&gt;SharePoint Integration&lt;/strong&gt; – Select and return a document from a folder&lt;/li&gt;
&lt;li&gt;🧭 &lt;strong&gt;Advanced Lookups&lt;/strong&gt; – Custom selection UI with map filters&lt;/li&gt;
&lt;li&gt;🔄 &lt;strong&gt;Wizard-style Forms&lt;/strong&gt; – Guide users through complex input screens&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🛠️ How to Wire It Up (Minimal Steps to Be Awesome)
&lt;/h3&gt;

&lt;p&gt;Here’s the minimal setup to make this pattern work with any button in your model-driven app:&lt;/p&gt;




&lt;h3&gt;
  
  
  ⚙️ Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before wiring everything up, make sure you have the following in place:&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;✅ 1. Create or Import the &lt;code&gt;cu_custompagescallback&lt;/code&gt; Table&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This custom table is used for storing the return values from your dialog (the callback). It must include at least these two columns:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column Logical Name&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cu_sessionid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Single Line Text&lt;/td&gt;
&lt;td&gt;Used to uniquely identify the dialog session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cu_callbackjson&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Multiline Text&lt;/td&gt;
&lt;td&gt;Stores the data returned from the dialog as JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;👉 You can import the solution from my GitHub repo to automatically deploy this table to your environment.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;✅ 2. Create the Custom Page&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You’ll need a Custom Page in your solution that acts as the dialog. This page should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read data from &lt;code&gt;Param("recordId")&lt;/code&gt; and parse it using &lt;code&gt;ParseJSON()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Store results using a &lt;code&gt;Patch()&lt;/code&gt; to the &lt;code&gt;cu_custompagescallback&lt;/code&gt; table using the sessionId passed in&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;Back()&lt;/code&gt; after submitting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✅ Make sure your custom page is added to your &lt;strong&gt;Model-Driven App&lt;/strong&gt; using the App Designer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can adapt the following Power Fx for the button:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Patch(
  cu_custompagescallback,
  Defaults(cu_custompagescallback),
  {
    cu_sessionid: gblSessionId,
    cu_callbackjson: JSON({ Status: drpStatus.Selected.Value })
  }
);
Back();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;1️⃣ Upload your Web Resource&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Upload &lt;code&gt;CustomPageHandler.js&lt;/code&gt; (you can download it in the Resources section at the very bottom) to your environment as a Web Resource.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;2️⃣ Register Your Callback&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After the Web Resource loads, register your callback (below is just an example - you need to adapt it for your needs!):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// after your Web Resource loads...&lt;/span&gt;
&lt;span class="nx"&gt;CustomPageHandler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;myHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// input.formContext, input.callbackData, input.entityName, input.recordId&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Got callback data:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;callbackData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;callbackData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Rejected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;statecode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Inactive&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Approved&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;statuscode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;865420001&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;3️⃣ Add or Edit Your Button&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use &lt;strong&gt;Power Apps Command Designer&lt;/strong&gt; (modern) or &lt;strong&gt;Ribbon Workbench&lt;/strong&gt; (classic) to add your button and link it to the handler.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Library&lt;/strong&gt;: &lt;code&gt;CustomPageHandler.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Function Name&lt;/strong&gt;: &lt;code&gt;CustomPageHandler.launch&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parameters&lt;/strong&gt; (in exact order):&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;| Type          | Description                                                   |&lt;br&gt;
  |---------------|---------------------------------------------------------------|&lt;br&gt;
  | &lt;code&gt;PrimaryControl&lt;/code&gt; | Execution context                    |&lt;br&gt;
  | &lt;code&gt;String&lt;/code&gt;         | Callback key (e.g., &lt;code&gt;"myHandler"&lt;/code&gt;)                         |&lt;br&gt;
  | &lt;code&gt;String&lt;/code&gt;         | Custom Page Name (e.g., &lt;code&gt;"my_custom_page"&lt;/code&gt;)               |&lt;br&gt;
  | &lt;code&gt;String&lt;/code&gt;         | JSON with navigation options                               |&lt;br&gt;
  | &lt;code&gt;String&lt;/code&gt;         | JSON with additional parameters (or &lt;code&gt;"{}"&lt;/code&gt; if none)       |&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;📦 Sample Navigation Options JSON:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;680&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"px"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"px"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Delete Ticket"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frll9h2combalqzcx4wn7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frll9h2combalqzcx4wn7.png" alt="Example implementation:" width="276" height="715"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;🎉 Done! Now, every button just calls the same &lt;code&gt;CustomPageHandler.launch(...)&lt;/code&gt; with its own parameters, and your business logic stays cleanly organized in the &lt;code&gt;CustomPageHandler.callbacks&lt;/code&gt; map.&lt;/p&gt;

&lt;p&gt;This makes it &lt;em&gt;easy to reuse, extend, and maintain&lt;/em&gt; across multiple dialogs in your apps.&lt;/p&gt;




&lt;h3&gt;
  
  
  🧠 Final Thoughts
&lt;/h3&gt;

&lt;p&gt;Custom Pages bridge a long-standing gap in Model-Driven Apps. With just a few enhancements, they become powerful tools for user interaction. By implementing JSON-based parameters and a session-based callback mechanism, you unlock:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easier integration scenarios&lt;/li&gt;
&lt;li&gt;Better user experiences&lt;/li&gt;
&lt;li&gt;Stronger separation of concerns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me know what you build with this approach!&lt;/p&gt;




&lt;h3&gt;
  
  
  📎 Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.matthewdevaney.com/power-apps-custom-page-modal-dialog-for-model-driven-apps/" rel="noopener noreferrer"&gt;Matthew Devaney – Power Apps Custom Page Modal Dialog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/power-apps/developer/model-driven-apps/clientapi/navigate-to-custom-page-examples" rel="noopener noreferrer"&gt;Microsoft Docs – navigateTo with Custom Page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/KrzysztofMikolajec/custompagescallbacks" rel="noopener noreferrer"&gt;GitHub – CustomPageHandler.js Code and Callbacks table solution file&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>powerplatform</category>
      <category>powerapps</category>
      <category>lowcode</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
