---
title: "Binding Contracts"
section: "Builder Guide"
canonical_url: "https://support.buildwithhq.com/builder-guide/binding-contracts.html"
reviewed_at: "2026-09-14"
authority: Official
---

# Binding Contracts

Use the binding catalog and live checklist/contacts page contract to connect React template blocks to bounded, authorized server data and actions.

## In brief

- **Catalog bindings:** 4
- **Live example:** Checklist + Contacts
- **Browser SQL access:** Never
- **Authorization:** Server enforced

A binding contract connects a React template block to an installed, bounded server operation. The page stores a public `bindingKey` and an optional `dataKey`; it stores no SQL, database route, tenant identity, credential or authorization rule.

[Binding catalog](#binding-catalog) [Live checklist contract](#live-checklist-contracts) [Runtime flow](#runtime) [Actions and refresh](#actions) [Download Developer Kit 1.20.28](../downloads/BuildWithHQ-API43-Developer-Kit-1.20.28.zip)

Note

The binding catalog is an authoring allowlist. It tells Puck which installed keys and child data paths a builder may select. It does not grant access to records or actions.

## Binding catalog

The current local Puck catalog is `developer-kit/local-puck/binding-catalog.json`. Each entry gives the stable binding name and the bounded child documents that blocks may select with `dataKey`.

| Binding key | Purpose | Available data keys |
| --- | --- | --- |
| `work-orders.presentation` | Work Orders presentation | `metrics`, `byStatus`, `recent`, `trend`, `table`, `timeline` |
| `checklists-signoffs.presentation` | Checklists & Signoffs presentation | `tasks` |
| `contacts.presentation` | Contacts presentation | `table` |
| `company-news.presentation` | Company News presentation | `table`, `feed`, `metrics` |

**Copy the complete binding catalog**

```
{
  "schemaVersion": 1,
  "bindings": [
    {
      "bindingKey": "work-orders.presentation",
      "title": "Work Orders presentation",
      "dataKeys": ["metrics", "byStatus", "recent", "trend", "table", "timeline"]
    },
    {
      "bindingKey": "checklists-signoffs.presentation",
      "title": "Checklists & Signoffs presentation",
      "dataKeys": ["tasks"]
    },
    {
      "bindingKey": "contacts.presentation",
      "title": "Contacts presentation",
      "dataKeys": ["table"]
    },
    {
      "bindingKey": "company-news.presentation",
      "title": "Company News presentation",
      "dataKeys": ["table", "feed", "metrics"]
    }
  ]
}
```

Several blocks can reuse one binding. For example, Work Orders can render metrics, a status distribution, recent records, a trend, a grid and a timeline from one authorized presentation document. Reuse avoids issuing a broad record query for every visual block.

## Live checklist and Contacts page contract

The shipped `pages/live-checklist-contacts.json` example composes two independent live contracts. The checklist task list uses `checklists-signoffs.presentation` with `dataKey: tasks`. The Contacts grid uses `contacts.presentation` with `dataKey: table`.

**Copy the complete live checklist and Contacts page JSON**

```
{
  "blocks": [
    {
      "_id": "live-actions-header",
      "_type": "core.page-header",
      "props": {
        "title": "Live checklist and contact actions",
        "lead": "These components receive only the records and actions authorized for the signed-in user. Checklist toggles use optimistic concurrency; contact actions refetch their scoped presentation after each write."
      }
    },
    {
      "_id": "live-actions-layout",
      "_type": "core.container",
      "props": {
        "layout": "grid",
        "gridMode": "twelve-column",
        "gap": "normal"
      },
      "children": [
        {
          "_id": "live-actions-checklist-card",
          "_type": "core.card",
          "props": {
            "title": "Checklist work",
            "description": "Items outside the user's DataRole and Location scope are never sent to the browser.",
            "responsive": {
              "desktop": { "span": 5 },
              "tablet": { "span": 12 },
              "mobile": { "span": 12 }
            }
          },
          "children": [
            {
              "_id": "live-actions-checklist",
              "_type": "core.task-list",
              "bindingKey": "checklists-signoffs.presentation",
              "dataKey": "tasks",
              "props": {}
            }
          ]
        },
        {
          "_id": "live-actions-contacts-card",
          "_type": "core.card",
          "props": {
            "title": "Recent contacts",
            "description": "The grid is bounded to 25 visible contacts and each row declares only its currently valid favorite action.",
            "responsive": {
              "desktop": { "span": 7 },
              "tablet": { "span": 12 },
              "mobile": { "span": 12 }
            }
          },
          "children": [
            {
              "_id": "live-actions-contacts",
              "_type": "core.presentation-grid",
              "bindingKey": "contacts.presentation",
              "dataKey": "table",
              "props": {}
            }
          ]
        }
      ]
    }
  ]
}
```

The layout and presentation remain editable. Changing card widths, titles, responsive spans or component styling does not change the server contract. Changing a `bindingKey` or `dataKey` must still reference an installed catalog entry and pass server validation.

## How the runtime resolves a binding

1. The page renderer collects unique binding keys from visible, registered blocks.
2. The native runtime requests `GET /api/runtime/data/<bindingKey>` under the current same-origin user session. Headless applications can resolve 1–24 keys with `POST /v1/apps/{saasAppId}/runtime/bindings` and a delegated-user credential.
3. The server resolves the tenant from the real host and requires the authenticated SaaS app, AppAccount and user context.
4. The registered binding chooses a reviewed stored procedure. That procedure applies tenant, DataRole, Location, record visibility, soft-delete and action-entitlement rules before returning JSON.
5. The React block receives a typed loading/data/error state and selects only its configured `dataKey`.

A native binding response has this envelope:

```
{
  "contractVersion": 1,
  "bindingKey": "checklists-signoffs.presentation",
  "data": {
    "tasks": {
      "presentationVersion": 1,
      "kind": "task-list",
      "items": []
    }
  }
}
```

Important

A page definition is presentation, not data authority. Never replace a binding with browser SQL, a connector secret, an arbitrary stored-procedure name or a client-supplied SaaS/AppAccount/User identifier.

## Live actions, revisions and exact refresh

Bindings are read contracts. A checklist toggle or Contacts favorite uses the separate Lego action broker at `POST /api/runtime/lego/actions`. The rendered row receives only server-issued record, field and action identifiers that are currently available to that user.

```
{
  "contractVersion": 1,
  "pageKey": "LiveChecklistContacts",
  "blockId": "live-actions-checklist",
  "actionId": "server-issued-action-guid",
  "recordId": "server-issued-record-guid",
  "expectedRevision": 7,
  "values": {
    "checked": true
  }
}
```

1. The browser sends the saved page key, block ID, server-issued action ID, record ID, expected revision when required, and validated input values.
2. The broker reloads the saved page and catalog, confirms that the block declares the action, then calls the typed module service.
3. The endpoint and stored procedure independently re-authorize the operation. A displayed action ID is user-interface intent, not proof of permission.
4. A successful result names the exact binding that became stale. React reloads only that binding with `?refresh=true`.
5. A stale checklist revision returns HTTP 409. The optimistic state is removed, the current binding is fetched again, and the user can retry against the current revision.

## Use these contracts in a template

1. Open the page in hosted Puck or the local Puck editor.
2. Select a data-aware block such as `core.task-list` or `core.presentation-grid`.
3. Choose its `bindingKey` from the binding catalog.
4. Choose a `dataKey` declared by that catalog entry.
5. Save and validate the draft, then publish it through the normal version-fenced workflow.
6. Exercise loading, empty, populated, forbidden, stale-write and successful-mutation states with a real route-resolved disposable tenant.

An unknown binding name does not create an API or query. Adding a binding is a separate reviewed application contract with a stable key, approved stored procedure, mandatory server context, bounded output schema, cache policy and authorization tests.

Continue with [the local Puck binding editor](local-puck-binding-editor.html), [interactive API presentations](interactive-api-presentations.html), and [the headless application kit](headless-application-kit.html).

---

Capability review: 2026-09-14. [View the canonical guide](https://support.buildwithhq.com/builder-guide/binding-contracts.html).
