Support/Builder Guide/Convert HTML into BuildWithHQ/Puck pages

Builder Guide

Convert HTML into BuildWithHQ/Puck pages

Turn static HTML or a frontend build entry into deterministic, validated Professional Foundation page JSON without importing arbitrary code or authority.

Input
HTML file or dist/index.html
Output
Canonical Puck page JSON
Security
No scripts, secrets, or inferred bindings

Developer Kit 1.14.0 includes tools/html_to_puck.py, an offline converter for bringing the useful structure of an existing static design into the current BuildWithHQ page system. It accepts one HTML file or a directory whose entry is index.html, then emits real page JSON made only from registered Professional Foundation component keys.

Note

This is a design migration helper, not a compiled-application loader. A Vite dist directory supplies its index.html as input; JavaScript chunks, source maps, CSS bundles, credentials and asset files are never embedded in page JSON.

Run the converter

Windows PowerShell

# From the extracted Developer Kit 1.14.0 directory
python .\tools\html_to_puck.py .\dist `
  --output .\converted\home.page.json `
  --report .\converted\home.report.json

# Reject the conversion if any executable/unsupported content was discarded.
python .\tools\html_to_puck.py .\source.html `
  --output .\converted\source.page.json `
  --report .\converted\source.report.json `
  --strict

Linux or macOS

# From the extracted Developer Kit 1.14.0 directory
python3 ./tools/html_to_puck.py ./dist \
  --output ./converted/home.page.json \
  --report ./converted/home.report.json

python3 ./tools/html_to_puck.py ./source.html \
  --output ./converted/source.page.json \
  --report ./converted/source.report.json \
  --strict

Use --entry another.html when a directory has a different entry filename. The converter uses only Python's standard library and makes no network, API or database connection.

What becomes a Puck component

HTML intentBuildWithHQ outputBehavior
First h1core.page-headerPage title with a stable generated block ID.
Other headings and paragraphscore.textEscaped display text; markup does not become executable HTML.
section, article, asidecore.cardFirst heading/paragraph become title/description; remaining content stays nested.
Grid/flex structural containerscore.containerPreserves stack, row or automatic-grid intent and simple gaps.
Listscore.list-rowBounded display rows inside a stack.
HTML tablescore.data-tableAt most 24 columns and 500 static preview rows.

Repeated conversion with the same seed and source order produces the same block IDs. This makes source review and page diffs meaningful. Input is capped at 2 MiB, output at 5,000 blocks, nesting at 32 levels and charts at 60 finite values.

Annotate HTML when you want an exact component

Use data-bwhq-component with a public registry key and allowlisted data-bwhq-* properties. Unknown components, icons, tones, enum values, properties and responsive spans fail closed.

<div class="grid gap-4" data-bwhq-component="core.container">
  <div
    data-bwhq-component="core.stat-tile"
    data-bwhq-label="Open work"
    data-bwhq-value="42"
    data-bwhq-delta="6 today"
    data-bwhq-direction="up"
    data-bwhq-icon-name="work-order"
    data-bwhq-tone="primary"
    data-bwhq-desktop-span="3"
    data-bwhq-tablet-span="6"
    data-bwhq-mobile-span="12">
  </div>
</div>

For chart arrays or other richer preview properties, data-bwhq-props accepts a JSON object and applies the same property allowlist. It cannot introduce binding identity, authorization identity or executable code.

Review the loss report

The report counts stripped scripts/styles/iframes, removed event handlers, display-only links/buttons, omitted media, ignored asset URLs, rejected authority attributes, component totals and any bounded text/table shortening. Credential-shaped bwk_ and bwu_ tokens are redacted from visible text and attributes and counted by location without copying the secret into the report. With --strict, a lossy conversion returns exit code 2 and does not write the page file; the report is still written for review.

Important

Links, buttons and form controls do not become working writes. Add interactions after conversion through registered components and closed action keys mapped to reviewed APIs. Never recreate an onclick handler or infer an API route from HTML.

Set data bindings outside Puck

Puck is optional for binding assignment. Open the generated page JSON in a text editor, source-controlled build step or custom authoring tool. Put bindingKey and optional dataKey directly on the target block, beside _id, _type and props.

{
  "_id": "home-work-table",
  "_type": "core.presentation-grid",
  "bindingKey": "work-orders.presentation",
  "dataKey": "table",
  "props": {}
}

The deterministic _id gives automation a stable target. Several blocks may share one registered binding and select bounded children such as metrics, recent, table or timeline with dataKey. A data key is only a safe path into returned JSON; it is not SQL, a filter expression or authorization.

The binding must already be installed and registered for that SaaS. Adding an unknown key does not create a binding: server validation rejects the page and runtime resolution fails closed. Get the current pageVersion, save the edited document with BuildWithHQPageDraftClient.savePageDraft and expectedPageVersion, then call validatePageDraft. Keep the bwk_draft_ credential on a trusted server.

Note

The converter refuses binding attributes in source HTML on purpose. Assigning bindings after conversion prevents downloaded markup from selecting a data source or carrying tenant, account, user, role or location identity into the page.

Make the converted design live

  1. Open the generated JSON in Puck or an external JSON workflow and review desktop, tablet, mobile, keyboard, empty and long-content behavior.
  2. Replace static preview values with registered binding keys. The server must still derive the SaaS app, AppAccount, user, DataRoles and Locations.
  3. Add forms, checkboxes, uploads and actions using existing first-class module components and reviewed API handlers.
  4. Save the page as an unpublished draft with the server-only BuildWithHQPageDraftClient and a bwk_draft_ credential.
  5. Run server validation. Publish the exact draft history only with a separate bwk_live_ credential carrying pages.publish.

The static page works immediately in Puck. Live data and writes work when explicitly connected to the platform's real secured contracts—there are no generated placeholder routes or fake bindings.

Complete annotated example

HTML input included in the ZIP
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Operations control panel</title>
  <style>body { font-family: system-ui; }</style>
</head>
<body>
  <main class="flex flex-col gap-4">
    <h1>Operations control panel</h1>
    <p>Review current workload, service health, and team activity.</p>

    <div class="grid gap-4" data-bwhq-component="core.container">
      <div data-bwhq-component="core.stat-tile" data-bwhq-label="Open work" data-bwhq-value="42" data-bwhq-delta="6 today" data-bwhq-direction="up" data-bwhq-icon-name="work-order" data-bwhq-tone="primary" data-bwhq-desktop-span="4" data-bwhq-tablet-span="6" data-bwhq-mobile-span="12"></div>
      <div data-bwhq-component="core.stat-tile" data-bwhq-label="Within service level" data-bwhq-value="96%" data-bwhq-delta="2% this week" data-bwhq-direction="up" data-bwhq-icon-name="check-circle" data-bwhq-tone="success" data-bwhq-desktop-span="4" data-bwhq-tablet-span="6" data-bwhq-mobile-span="12"></div>
      <div data-bwhq-component="core.stat-tile" data-bwhq-label="Needs attention" data-bwhq-value="7" data-bwhq-delta="2 overdue" data-bwhq-direction="down" data-bwhq-icon-name="bell" data-bwhq-tone="warning" data-bwhq-desktop-span="4" data-bwhq-tablet-span="12" data-bwhq-mobile-span="12"></div>
    </div>

    <section class="primary">
      <h2>Priority work</h2>
      <p>A bounded preview that can be replaced with a reviewed live binding.</p>
      <table>
        <caption>Today's work queue</caption>
        <thead><tr><th>Reference</th><th>Summary</th><th>Owner</th><th>Status</th></tr></thead>
        <tbody>
          <tr><td>WO-1048</td><td>Inspect cooling system</td><td>Morgan Lee</td><td>In progress</td></tr>
          <tr><td>TK-318</td><td>Confirm access window</td><td>Avery Patel</td><td>Waiting</td></tr>
          <tr><td>AP-092</td><td>Approve completion proof</td><td>Riley Chen</td><td>Review</td></tr>
        </tbody>
      </table>
    </section>

    <section>
      <h2>Next actions</h2>
      <p>Display-only list imported from HTML.</p>
      <ul>
        <li>Review two overdue tickets</li>
        <li>Confirm tomorrow's technician schedule</li>
        <li>Approve completed checklist evidence</li>
      </ul>
    </section>
  </main>
</body>
</html>
Exact generated Puck page included in the ZIP
{
  "blocks": [
    {
      "_id": "html-control-panel-0014-c80bd7a308",
      "_type": "core.container",
      "props": {
        "layout": "stack",
        "gap": "normal"
      },
      "children": [
        {
          "_id": "html-control-panel-0001-5b3859c95a",
          "_type": "core.page-header",
          "props": {
            "title": "Operations control panel"
          }
        },
        {
          "_id": "html-control-panel-0002-aa94387df2",
          "_type": "core.text",
          "props": {
            "text": "Review current workload, service health, and team activity.",
            "variant": "body",
            "align": "left"
          }
        },
        {
          "_id": "html-control-panel-0006-9d2e403adf",
          "_type": "core.container",
          "props": {
            "layout": "grid",
            "gridMode": "auto-fit",
            "gap": "normal"
          },
          "children": [
            {
              "_id": "html-control-panel-0003-c53609701a",
              "_type": "core.stat-tile",
              "props": {
                "tone": "primary",
                "label": "Open work",
                "value": "42",
                "direction": "up",
                "delta": "6 today",
                "iconName": "work-order",
                "responsive": {
                  "desktop": { "span": 4 },
                  "tablet": { "span": 6 },
                  "mobile": { "span": 12 }
                }
              }
            },
            {
              "_id": "html-control-panel-0004-53f4f0b16d",
              "_type": "core.stat-tile",
              "props": {
                "tone": "success",
                "label": "Within service level",
                "value": "96%",
                "direction": "up",
                "delta": "2% this week",
                "iconName": "check-circle",
                "responsive": {
                  "desktop": { "span": 4 },
                  "tablet": { "span": 6 },
                  "mobile": { "span": 12 }
                }
              }
            },
            {
              "_id": "html-control-panel-0005-51eb9b8be4",
              "_type": "core.stat-tile",
              "props": {
                "tone": "warning",
                "label": "Needs attention",
                "value": "7",
                "direction": "down",
                "delta": "2 overdue",
                "iconName": "bell",
                "responsive": {
                  "desktop": { "span": 4 },
                  "tablet": { "span": 12 },
                  "mobile": { "span": 12 }
                }
              }
            }
          ]
        },
        {
          "_id": "html-control-panel-0008-a56413e43e",
          "_type": "core.card",
          "props": {
            "tone": "primary",
            "title": "Priority work",
            "description": "A bounded preview that can be replaced with a reviewed live binding."
          },
          "children": [
            {
              "_id": "html-control-panel-0007-bda86f9048",
              "_type": "core.data-table",
              "props": {
                "caption": "Today's work queue",
                "columns": [
                  { "key": "reference", "label": "Reference", "align": "left" },
                  { "key": "summary", "label": "Summary", "align": "left" },
                  { "key": "owner", "label": "Owner", "align": "left" },
                  { "key": "status", "label": "Status", "align": "left" }
                ],
                "rows": [
                  { "reference": "WO-1048", "summary": "Inspect cooling system", "owner": "Morgan Lee", "status": "In progress" },
                  { "reference": "TK-318", "summary": "Confirm access window", "owner": "Avery Patel", "status": "Waiting" },
                  { "reference": "AP-092", "summary": "Approve completion proof", "owner": "Riley Chen", "status": "Review" }
                ]
              }
            }
          ]
        },
        {
          "_id": "html-control-panel-0013-76a243792d",
          "_type": "core.card",
          "props": {
            "tone": "neutral",
            "title": "Next actions",
            "description": "Display-only list imported from HTML."
          },
          "children": [
            {
              "_id": "html-control-panel-0012-b612166a0a",
              "_type": "core.container",
              "props": {
                "layout": "stack",
                "gap": "compact"
              },
              "children": [
                {
                  "_id": "html-control-panel-0009-c28519306d",
                  "_type": "core.list-row",
                  "props": { "primary": "Review two overdue tickets", "tone": "neutral" }
                },
                {
                  "_id": "html-control-panel-0010-0bc1e38bf4",
                  "_type": "core.list-row",
                  "props": { "primary": "Confirm tomorrow's technician schedule", "tone": "neutral" }
                },
                {
                  "_id": "html-control-panel-0011-a53fc93bed",
                  "_type": "core.list-row",
                  "props": { "primary": "Approve completed checklist evidence", "tone": "neutral" }
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Verify the tool

python .\tools\html_to_puck.py --self-test
python -m pytest templates\professional-foundation\tests\test_html_to_puck.py -q
Tip

Use this converter to accelerate a migration, then let Puck and the component registry own the page. Keep the original HTML as design reference, not as a second production renderer.