To convert JSON to an HTML table in Power Automate, paste the JSON into a strict-input converter, copy the resulting semantic table fragment, and inject it into a Compose, HTML email, or approval action with no schema authoring, no manual escaping, and no script-injection risk. Power Automate ships with native Create HTML table and Create CSV table actions, but they expect a tabular array shape, flatten only the columns they discover, and do not always behave predictably when the JSON contains nested objects, missing fields, or HTML-sensitive strings such as <, >, &, and quotes. A browser-based converter that produces escaped, semantic markup locally gives you a copy-ready fragment you can hand to the flow's inputs without uploading data anywhere. The JSON to HTML Table Converter follows that pattern: it parses strict JSON, unions the columns from every record, escapes every header and cell for HTML text context, and renders the preview with React text nodes instead of injected HTML so the value you see in the preview is exactly what your flow will receive when it consumes the fragment.

Why Power Automate's Native Table Actions Sometimes Fall Short
Power Automate's Create HTML table action sits inside the Data operations group and turns an in-memory array into a table-style HTML string. It works well for flat arrays where every row exposes the same properties, and it writes the markup as a single string that you can pipe into an Office 365 Outlook, Send approval mail, or Create file action. The trouble starts when the JSON comes from an HTTP trigger, a SharePoint list with lookup columns, a Dataverse query, or a custom connector that returns nested objects. The native action tends to render nested arrays and objects as empty cells or as the literal word "object", it loses the distinction between a missing field and an explicit JSON null, and it does not guarantee that characters such as < and & are escaped in every cell.
The legacy Convert JSON to HTML table action was deprecated precisely because of these edge cases. Microsoft now points users at the newer Create HTML table built on top of Power Fx, which still inherits the same shape assumptions. A standalone converter that documents its escaping rules, rejects malformed input up front, and produces a plain semantic table gives developers a way to keep control of the markup that ends up inside an email body, a Teams adaptive card, or an approval form summary.
JSON Input Rules the Converter Enforces
Power Automate flows accept anything an HTTP action returns, but the converter itself is strict on purpose. It parses JSON using the same rules as RFC 8259 and rejects input that a JavaScript developer might otherwise get away with: line and block comments, trailing commas, undefined, NaN, Infinity, BigInt suffixes, and JavaScript object literals with quoted keys or single-quoted strings all fail before any table is produced.
Two shape rules matter most when you are feeding it Power Automate output. A single object becomes one row, and an array must contain at least one object, with every array member required to be an object itself. Primitive roots such as numbers, strings, or arrays of primitives are rejected because they do not provide stable named columns. If your flow returns a scalar payload, wrap it in a property first or fan it out into an object before you send it to the converter.
Object key order is the other rule that catches people off guard. Columns appear in first-observed key order: keys from the first record come first, and newly encountered keys from later records are appended. Standard JSON parsing also resolves duplicate member names before the table is generated, so duplicate keys cannot be used as data and should be fixed in the upstream API rather than in the converter.
JSON Input Shapes and How the Converter Responds
| Input shape | Accepted? | Table produced |
|---|---|---|
| One object | Yes | One data row with the object's own keys |
| Array of objects | Yes | One data row per object, columns unioned in first-seen order |
| Array of primitives | No | Rejected, no stable named columns to form |
| Single primitive (string, number, Boolean, null) | No | Rejected, must be wrapped in a property first |
| Object with nested arrays | Yes | Nested arrays serialised as compact JSON in their cell |
| JavaScript literal with comments or trailing commas | No | Rejected before any column is built |
| Object with duplicate keys | Partial | Parser keeps the surviving value, column built from that one |
Convert JSON to an HTML Table Fragment
- Paste one strict JSON object, or a non-empty array of objects, into the converter's input area. Use the value exactly as it appears in your Power Automate flow's Compose output or HTTP trigger body, with no comments and no trailing commas.
- Run the conversion. The tool parses the JSON, unions the columns from every record, and renders a preview pane alongside the output. Read the preview to confirm the column order, the count of rows, and the values shown for any nested data.
- Inspect the column model. New columns from later records should appear at the right of the header row. Missing cells should show as empty space. Explicit JSON null values should read the word "null". Nested arrays and objects should appear as compact JSON inside a single cell.
- Check the escaping on any HTML-sensitive value. A payload containing <img onerror=...>, "double quotes", or 'apostrophes' should show the literal characters in the preview, not as parsed tags. Fix the JSON upstream rather than editing the markup if anything looks wrong.
- Copy the HTML fragment from the output panel. The result is one <table> element with a <thead> containing a single header row and a <tbody> containing every data row. There are no inline styles, CSS classes, captions, IDs, or accessibility attributes attached.
- Add a caption, column widths, borders, or alternating row colours in your destination document, then test the snippet in the real Power Automate action where it will live.
Drop the HTML Into a Power Automate Flow
Inside the Power Automate designer, the safest place to inject hand-built HTML is a Compose action that feeds downstream email, approval, Teams, or Create file actions. Paste the copied fragment into a Compose card named, for example, HtmlTableFragment, so the flow reference becomes outputs('HtmlTableFragment'). That single-string reference keeps the rest of the flow readable and lets you swap the source JSON without touching downstream cards.
For an approval or Office 365 Outlook email, add a Send approval mail or Send an email (Office 365 Outlook) action and place the Compose output inside the Details or Body field. Wrap it in a simple HTML skeleton if your tenant requires one, with the Compose reference inserted where the table should render:
- Approval body: paste the fragment inside the Details field of a Send approval mail action and verify the approver sees real column headers.
- Outlook body: place it inside an <html><body> wrapper in the Body field of a Send an email action so the table renders as actual markup rather than escaped text.
- Teams adaptive card: reference the Compose output from an Adaptive Card JSON expression, or paste it directly into the Message field of a Post message action and confirm Teams treats it as text or HTML based on the action's mode.
- SharePoint column formatter: feed the fragment into a Send an HTTP request action targeting the column-formatter endpoint, where the escaped text characters match what SharePoint expects in JSON payloads.
After the first run, verify the email or approval body in a real mailbox rather than only in the run-history preview. Power Automate sometimes re-escapes already-escaped content, so double-check that < and & in your data did not turn into < and & during the second pass. For visual polish on the receiving end, the Power Automate HTML table formatting guide covers caption placement, border styling, and column widths that hold up across Outlook, Teams, and SharePoint.
How Escaping Protects the Downstream Action
Because the fragment ends up inside HTML emails, adaptive cards, and SharePoint column formatters, every header and cell is encoded for the HTML text context before it is placed between <th> or <td> tags. Ampersands become &, less-than signs become <, greater-than signs become >, double quotes become ", and apostrophes become ', as defined by the WHATWG HTML syntax specification. A value such as <script>alert(1)</script> therefore lands in your mailbox as visible text, never as a tag, and the same behaviour protects attribute contexts from breakouts such as " onmouseover="alert(1).
The preview pane enforces that promise a second time. It is built from React text nodes using the original cell values, so the converter never calls innerHTML on the generated fragment. If you see the literal characters <script> in the preview, your downstream consumer will see the same characters when it renders the markup in text context. If your destination injects the fragment via innerHTML, the escaping still applies, and any HTML you intentionally placed there will activate, which is the correct behaviour for a deliberate injection and a safe behaviour for an accidental one.
For an attacker who controls part of a JSON field, this escaping layer alone is not enough; pair it with the destination's own Content Security Policy, keep untrusted content inside text contexts rather than attribute contexts, and avoid pasting the fragment into a document that allows inline script. The OWASP XSS Prevention Cheat Sheet describes the surrounding controls that turn escaped text into a fully defended sink.
Documented Limits Inside the Converter
The converter is bounded, and every limit is enforced by failing the conversion rather than by silently truncating. Knowing the ceiling of each layer up front keeps your Power Automate run from blowing up on the largest JSON array the upstream API can return. The table below lists the documented limits straight from the tool.
| Resource | Limit | Behaviour when exceeded |
|---|---|---|
| Input characters | 500,000 | Conversion fails with a clear error |
| Data rows | 10,000 | Conversion fails before partial output |
| Columns | 200 | Conversion fails with a column-count message |
| Output characters | 5,000,000 | Conversion fails rather than truncate the HTML |
Power Automate imposes its own per-action and per-flow ceilings for inputs and outputs, and those numbers vary by tenant and licence. For a safe handoff, sample the largest array your upstream connector returns, run it through the converter, and compare both the resulting fragment size and the row count against your tenant's action limits before you publish the flow.
Related reading: Avoid Mistakes When Converting JSON to Rust Structs.