DEV Community

Cover image for How to Create Your Own Form with Power Automate
david wyatt
david wyatt Subscriber

Posted on

How to Create Your Own Form with Power Automate

I would say 99% of all Power Automate developers have worked with the MS forms connector. Gathering information, transforming and moving to somewhere like SharePoint is a common requirement and something Forms does well.

But its not quite perfect, there are 3 things that are missing:

  • Pre load information
  • Upload files to external responders
  • Theming (limited to forms templates)

But there is a way around these limitations, and that's for us to make our own version of MS Forms.

We are going to create our own HTML form (no JavaScript required but you can add if you want) and then use Power Automate to send/process.
There are the following steps to go through:

  1. Setup Form
  2. Send Form
  3. Generate Form
  4. Process Form

The form we will create will:

  • Request a responder to add their name, delivery date, and upload their order file
  • The form to include a prepopulated order reference
  • The response will include a reference to ensure only one response is processed
  • Simple authentication

And the flows will:

  • Send link in email
  • Create the form webpage
  • Process the form response, including checking if already processed

process


1. Setup Form

To avoid using JavaScript and to keep the form as simple as possible we are going to use the HTML <form> tag. This allows us to send all inputs within the tag:



<form action="{YOUR TRIGGER URL}" method="post" target="_blank">
        <label for="order">Order Ref:</label>
        <input type="text" id="order" name="order" value="#429"><br><br>
        <label for="name">Name:</label>
        <input type="text" id="name" name="name"><br><br>
        <label for="date">Delivery Date:</label>
        <input type="date" id="date" name="date"><br><br>
        <input type="submit" value="Submit">
    </form>


Enter fullscreen mode Exit fullscreen mode

The above sends a response like below:



{
  "$content-type": "application/x-www-form-urlencoded",
  "$content": "Zm5hbWU9RGF2aWQmbG5hbWU9V3lhdHQ=",
  "$formdata": [
    {
      "key": "name",
      "value": "David"
    },
    {
      "key": "date",
      "value": "2024-08-04"
    }
  ]
}


Enter fullscreen mode Exit fullscreen mode

If you are not requiring file upload then this is good for what you need, but if you want to upload a file you have to change the response type from application/x-www-form-urlencoded to multipart/form-data, this adds complexity in the flow processing it.

The final thing to note is you can not set the headers in a <form> tag, (you can with JavaScript), this limits the way we can authenticate it. But there is a workaround, we can add a prepopulated input and hide it. This will then be sent with the form data, allowing us to use it as a trigger condition. Its not idea as it is easy to see, but it will do for basic level of authentication.



<form action="{YOUR TRIGGER URL}" method="post" target="_blank" enctype="multipart/form-data">
        <input name="key" value="helloWorld" style="visibility:hidden" /><br>
        <label for="order">Order Ref:</label>
        <input type="text" id="order" name="order" value="#429"><br><br>
        <label for="name">Name:</label>
        <input type="text" id="name" name="name"><br><br>
        <label for="date">Delivery Date:</label>
        <input type="date" id="date" name="date"><br><br>
        <input name="file" type="file" /><br><br>
        <input type="submit" value="Submit">
    </form>


Enter fullscreen mode Exit fullscreen mode

html order

As you can see this form isn't exactly pretty, and that's where the true flexibly of HTML and CSS comes in. We can design the form however we like, we can add training information, links, literally anything that can be in a website can be in your form.

complete form

There are also loads of free templates available, I got mine from formbold.com, but there are thousands out there.

2. Send Form

To get the form to our responder we are going to send an email. Unfortunately not all browsers support embedding forms directly in emails, so we will send a link to a web page with the form on it.

The flow is relatively simple, it updates the unique reference in the email body that is part of the form url, and then sends the email.

send form email

The unique reference is a parameter we pass, that way we can use the parameter to find the right row to update with the response. URL parameters follow a ?param=value&param2=value', format. As the url for our form already has parameters we append a '&param=value' to our url. I can then do a replace expression (below example the param is ref):



replace(triggerBody()['text'],'{paramRef}',string(outputs('Get_item')?['body/ID']))


Enter fullscreen mode Exit fullscreen mode

where the triggerBody()['text'] = {URLFLOWURL}&ref={paramRef}

3. Generate Form

So we have sent the link to the form, but how is the form actually created and where do we get the url for the form from.

We are going to use one of my favourite triggers, 'When an HTTP request is received' to create our form. This allows Power Automate to act like a basic API, receiving parameters and sending a response. The cool bit is it can return HTML, and if the url is used in a browser it will load the HTML like a webpage.

So the plan is to use a form like we created in stage 1. The flow will be triggered by a GET request, then using the parameter ref it will get the item from our SharePoint list, this is to check that there has not already been a response. If there is it returns 'Failed - Submission already processed', else it sends the forms HTML (which below I get wth a Get File Content Action.
If you want to prepopulate the form you can do another replace on the HTML with a value taken from the Get Item.

Full Flow
full flow

Already Processed Response
already processed response

Form Response Expression



replace(
    replace(
        body('Get_file_content_HTHML')
    ,
        '{ref}'
    ,
        outputs('Get_item')?['body/Title']
    )
,
    '{paremRef}'
,
    outputs('Get_item')?['body/ID']
)


Enter fullscreen mode Exit fullscreen mode

Form HTML



<form action="{YOUR URL}&ref={paramRef}" method="POST" enctype="multipart/form-data">
      <input name="key" value="helloWorld" style="visibility:hidden" />
      <div class="formbold-input-flex">
        <input type="text" name="reference" id="name" value="{ref}" class="formbold-form-input"/>
        <label for="name" class="formbold-form-label"> Order Reference </label>
      </div>
      <div class="formbold-input-flex">
        <div>
          <input type="text" name="name" id="name" value="" class="formbold-form-input"/>
          <label for="name" class="formbold-form-label"> Name </label>
      </div>
      <div>
        <input type="date" name="deliveryDate" value="" class="formbold-form-input"/>
        <label for="delivery" class="formbold-form-label"> Delivery Date </label>
      </div>
    </div>        
      <div>
        <input name="file" type="file" />
      </div>
      <div>
      <button>Send Message</button>
      </div>
    </form>


Enter fullscreen mode Exit fullscreen mode

As example this data would update have the {paremRef} replaced with 1 and {ref} replaced with #429

4. Process Form

Finally we need to process the forms response. We use another flow with 'When an HTTP request is received'. This is where things get a little complicated due to the requirement to upload the file. Instead of a simple JSON object we get:



{
  "$content-type": "multipart/form-data; boundary=----WebKitFormBoundarygVtkvZjZZZjbmNzg",
  "$content": "LS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5Z1Z0a3ZaalpaWmp==",
  "$multipart": [
    {
      "headers": {
        "Content-Disposition": "form-data; name=\"key\"",
        "Content-Length": "10"
      },
      "body": {
        "$content-type": "application/octet-stream",
        "$content": "aGVsbG9Xb3JsZA=="
      }
    },
    {
      "headers": {
        "Content-Disposition": "form-data; name=\"name\"",
        "Content-Length": "11"
      },
      "body": {
        "$content-type": "application/octet-stream",
        "$content": "RGF2aWQgV3lhdHQ="
      }
    },
    {
      "headers": {
        "Content-Disposition": "form-data; name=\"deliveryDate\"",
        "Content-Length": "10"
      },
      "body": {
        "$content-type": "application/octet-stream",
        "$content": "MjAyNC0wOC0yMg=="
      }
    },
    {
      "headers": {
        "Content-Disposition": "form-data; name=\"upload\"; filename=\"orderForm.csv\"",
        "Content-Type": "text/csv",
        "Content-Length": "5224"
      },
      "body": {
        "$content-type": "text/csv",
        "$content": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAIAAAAiOjnJA"
      }
    }
  ]
}


Enter fullscreen mode Exit fullscreen mode

As you can see its encoded, so we need to base64ToString to convert all values when we output them.

It also means we have to loop over the $multipart array, with the index being the way we identify each value (or you could use a switch to check the header/Content-Disposition field, as this contains the question).

So in my example, the array index's are

0 - authentication key
1 - order reference (the prepopulated one)
2 - name
3 - deliveryDate
4 - file

The flow will:

Check response has not been submitted already
get item

Update SharePoint Entry with Form Data
update item

Attach file
attach uploaded file

Attach file is already encoded correctly so you don't need to base64 if.



triggerBody()?['$multipart'][4]?['body']


Enter fullscreen mode Exit fullscreen mode

Full Flow
full flow

As you can see there is no schema for the post body and this is because of a strange bug. For some reason the trigger starts to fail with the only fix to remove the schema, so I use a schema to create the flow (so that all of the parameters are easy to select), then remove when about to publish.

Authentication
The authentication is a trigger condition, that way we ensure no one spams the api, as the flow will not run without the right key value



@equals(base64ToString(triggerBody()?['$multipart'][0]?['body']?['$content']),'helloWorld')

Enter fullscreen mode Exit fullscreen mode




so in this case my password is helloWorld

And that's it, we no have our own form that can be dynamically prepopulated, upload files from external responders, and zero limits on design.

demo gif

Solution and html templates can be found here. If you want to know more about the 'When an HTTP request is received' trigger check this out, and if you want to see how far you can push it check out my previous blog Creating Wordle in Power Automate

Top comments (2)

Collapse
 
jaloplo profile image
Jaime López

Thinking outside the standards allows to create wonderful things. This article is cream, meaning it is a fantastic solution providing a cool way for getting user responses out of SharePoint and Forms.

Thanks for this hidden gem @wyattdave!!!!

Collapse
 
balagmadhu profile image
Bala Madhusoodhanan

love it.. create your own email option. But i guess this would be more efficient