@notionhq/custom-blocks-host 0.3.11 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +143 -236
  3. package/THIRD_PARTY_LICENSES.md +9 -0
  4. package/dist/autoBindProperties.d.ts +5 -2
  5. package/dist/autoBindProperties.d.ts.map +1 -1
  6. package/dist/autoBindProperties.js +5 -2
  7. package/dist/browser.js +1 -1
  8. package/dist/compatibility/addMetadataReadSchemasForProtocol3To4.d.ts +1215 -0
  9. package/dist/compatibility/addMetadataReadSchemasForProtocol3To4.d.ts.map +1 -0
  10. package/dist/compatibility/addMetadataReadSchemasForProtocol3To4.js +27 -0
  11. package/dist/compatibility/normalizeHostToSandboxMessage.d.ts +8 -0
  12. package/dist/compatibility/normalizeHostToSandboxMessage.d.ts.map +1 -0
  13. package/dist/compatibility/normalizeHostToSandboxMessage.js +39 -0
  14. package/dist/compatibility/normalizePageResultsForProtocol3To5.d.ts +11 -0
  15. package/dist/compatibility/normalizePageResultsForProtocol3To5.d.ts.map +1 -0
  16. package/dist/compatibility/normalizePageResultsForProtocol3To5.js +18 -0
  17. package/dist/compatibility/normalizePageResultsForProtocol6.d.ts +9 -0
  18. package/dist/compatibility/normalizePageResultsForProtocol6.d.ts.map +1 -0
  19. package/dist/compatibility/normalizePageResultsForProtocol6.js +18 -0
  20. package/dist/compatibility/normalizeSandboxToHostMessage.d.ts +1099 -0
  21. package/dist/compatibility/normalizeSandboxToHostMessage.d.ts.map +1 -0
  22. package/dist/compatibility/normalizeSandboxToHostMessage.js +9 -0
  23. package/dist/compatibility/pageForProtocol3To5.d.ts +159 -0
  24. package/dist/compatibility/pageForProtocol3To5.d.ts.map +1 -0
  25. package/dist/{lifecycle/legacyPage.js → compatibility/pageForProtocol3To5.js} +50 -9
  26. package/dist/compatibility/pageResult.d.ts +11 -0
  27. package/dist/compatibility/pageResult.d.ts.map +1 -0
  28. package/dist/compatibility/pageWithNullTextFallbackProperties.d.ts +9 -0
  29. package/dist/compatibility/pageWithNullTextFallbackProperties.d.ts.map +1 -0
  30. package/dist/compatibility/pageWithNullTextFallbackProperties.js +46 -0
  31. package/dist/createCustomBlockHost.d.ts +18 -19
  32. package/dist/createCustomBlockHost.d.ts.map +1 -1
  33. package/dist/createCustomBlockHost.js +103 -38
  34. package/dist/index.d.ts +2 -19
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +1 -2
  37. package/dist/{localBlockUrl.d.ts → isAllowedLocalPreviewUrl.d.ts} +2 -2
  38. package/dist/isAllowedLocalPreviewUrl.d.ts.map +1 -0
  39. package/dist/{localBlockUrl.js → isAllowedLocalPreviewUrl.js} +3 -7
  40. package/dist/lifecycle/initErrors.d.ts +1 -0
  41. package/dist/lifecycle/initErrors.d.ts.map +1 -1
  42. package/dist/lifecycle/initErrors.js +7 -0
  43. package/dist/lifecycle/initializationController.d.ts +6 -4
  44. package/dist/lifecycle/initializationController.d.ts.map +1 -1
  45. package/dist/lifecycle/initializationController.js +24 -15
  46. package/dist/lifecycle/resolveManifest.d.ts +13 -0
  47. package/dist/lifecycle/resolveManifest.d.ts.map +1 -0
  48. package/dist/lifecycle/resolveManifest.js +21 -0
  49. package/dist/lifecycle/types.d.ts +1637 -17
  50. package/dist/lifecycle/types.d.ts.map +1 -1
  51. package/dist/lifecycle/types.js +18 -0
  52. package/dist/messages/sandboxMessageRouter.d.ts.map +1 -1
  53. package/dist/messages/sandboxMessageRouter.js +2 -2
  54. package/dist/protocol/dataSources/dataSourcePage.d.ts +3 -4
  55. package/dist/protocol/dataSources/dataSourcePage.js +2 -3
  56. package/dist/protocol/index.d.ts +1 -0
  57. package/dist/protocol/index.js +1 -0
  58. package/dist/protocol/isLocalBlockServerUrl.d.ts +2 -0
  59. package/dist/protocol/isLocalBlockServerUrl.js +9 -0
  60. package/dist/protocol/messages/connect.d.ts +2 -2
  61. package/dist/protocol/messages/connect.js +2 -2
  62. package/dist/protocol/messages/createPageResult.d.ts +2 -2
  63. package/dist/protocol/messages/createPageResult.js +3 -3
  64. package/dist/protocol/messages/getPage.d.ts +2 -2
  65. package/dist/protocol/messages/getPage.js +3 -3
  66. package/dist/protocol/messages/hostToSandbox.d.ts +3 -3
  67. package/dist/protocol/messages/hostToSandbox.js +10 -10
  68. package/dist/protocol/messages/init.d.ts +1 -1
  69. package/dist/protocol/messages/queryDataSource.d.ts +24 -0
  70. package/dist/protocol/messages/queryDataSource.js +2 -0
  71. package/dist/protocol/messages/queryDataSourceResult.d.ts +3 -3
  72. package/dist/protocol/messages/queryDataSourceResult.js +6 -3
  73. package/dist/protocol/messages/sandboxToHost.d.ts +8 -0
  74. package/dist/protocol/messages/updatePageResult.d.ts +2 -2
  75. package/dist/protocol/messages/updatePageResult.js +3 -3
  76. package/dist/protocol/pages/page.d.ts +2 -2
  77. package/dist/protocol/pages/page.js +1 -1
  78. package/dist/protocol/protocolVersion.d.ts +2 -2
  79. package/dist/protocol/protocolVersion.js +2 -2
  80. package/dist/protocol.d.ts +9 -0
  81. package/dist/protocol.d.ts.map +1 -0
  82. package/dist/protocol.js +8 -0
  83. package/docs/bridge/changelog.md +67 -0
  84. package/docs/bridge/protocol.md +189 -0
  85. package/docs/bridge/versioning.md +38 -0
  86. package/docs/compatibility.md +68 -0
  87. package/docs/handlers.md +199 -0
  88. package/docs/lifecycle.md +117 -0
  89. package/package.json +10 -2
  90. package/src/autoBindProperties.ts +5 -2
  91. package/src/compatibility/addMetadataReadSchemasForProtocol3To4.ts +36 -0
  92. package/src/compatibility/normalizeHostToSandboxMessage.ts +54 -0
  93. package/src/compatibility/normalizePageResultsForProtocol3To5.ts +28 -0
  94. package/src/compatibility/normalizePageResultsForProtocol6.ts +23 -0
  95. package/src/compatibility/normalizeSandboxToHostMessage.ts +10 -0
  96. package/src/{lifecycle/legacyPage.ts → compatibility/pageForProtocol3To5.ts} +52 -11
  97. package/src/compatibility/pageResult.ts +18 -0
  98. package/src/compatibility/pageWithNullTextFallbackProperties.ts +53 -0
  99. package/src/createCustomBlockHost.ts +152 -62
  100. package/src/index.ts +4 -51
  101. package/src/{localBlockUrl.ts → isAllowedLocalPreviewUrl.ts} +4 -7
  102. package/src/lifecycle/initErrors.ts +9 -0
  103. package/src/lifecycle/initializationController.ts +34 -19
  104. package/src/lifecycle/resolveManifest.ts +31 -0
  105. package/src/lifecycle/types.ts +84 -16
  106. package/src/messages/sandboxMessageRouter.ts +2 -2
  107. package/src/protocol.ts +8 -0
  108. package/dist/lifecycle/legacyPage.d.ts +0 -70
  109. package/dist/lifecycle/legacyPage.d.ts.map +0 -1
  110. package/dist/lifecycle/loadCustomBlockManifest.d.ts +0 -16
  111. package/dist/lifecycle/loadCustomBlockManifest.d.ts.map +0 -1
  112. package/dist/lifecycle/loadCustomBlockManifest.js +0 -65
  113. package/dist/lifecycle/manifestOptions.d.ts +0 -17
  114. package/dist/lifecycle/manifestOptions.d.ts.map +0 -1
  115. package/dist/lifecycle/normalizeHostToSandboxMessage.d.ts +0 -6
  116. package/dist/lifecycle/normalizeHostToSandboxMessage.d.ts.map +0 -1
  117. package/dist/lifecycle/normalizeHostToSandboxMessage.js +0 -14
  118. package/dist/lifecycle/normalizePage.d.ts +0 -7
  119. package/dist/lifecycle/normalizePage.d.ts.map +0 -1
  120. package/dist/lifecycle/normalizePage.js +0 -47
  121. package/dist/localBlockUrl.d.ts.map +0 -1
  122. package/src/lifecycle/loadCustomBlockManifest.ts +0 -89
  123. package/src/lifecycle/manifestOptions.ts +0 -19
  124. package/src/lifecycle/normalizeHostToSandboxMessage.ts +0 -27
  125. package/src/lifecycle/normalizePage.ts +0 -71
  126. /package/dist/{lifecycle/manifestOptions.js → compatibility/pageResult.js} +0 -0
@@ -0,0 +1,67 @@
1
+ # Bridge protocol changelog
2
+
3
+ The bridge protocol defines the messages exchanged by a custom block's SDK and its host. This
4
+ changelog records breaking contract changes and the SDK releases that introduced them. It also
5
+ describes compatibility exceptions within a declared protocol version and their effects on blocks.
6
+ Protocol version rules are in [Bridge protocol versioning](versioning.md). The host's
7
+ [compatibility reference](../compatibility.md) describes current support and response
8
+ conversions.
9
+
10
+ Publication dates refer to npm releases in Pacific time, not deployment in Notion. Unqualified SDK
11
+ versions refer to `@notionhq/custom-blocks`.
12
+
13
+ | Protocol | SDK release | Published | Breaking contract changes |
14
+ | ------------------------------------------------------ | ---------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
15
+ | 1 | `ncblock@0.0.1` | May 11, 2026 | Initial `ready` / `init` handshake and block APIs |
16
+ | 2 | `ncblock@0.0.36` | June 18, 2026 | Structured errors, handshake changes, and raw data-source IDs instead of manifest keys |
17
+ | 3 | `0.1.0` | July 22, 2026 | Accumulated contract changes include `connect → init → initResult`, initialization identity, subscription identity, and required structured connect errors |
18
+ | 4 | `0.2.0` | September 17, 2026 | Required `init.contrastMode` and page/query `is_archived` and `in_trash` fields |
19
+ | 5 [†](#protocol-5-unavailable-containing-page-context) | `0.2.16` | September 30, 2026 | Query filters and sorts must address actual database properties. Filter and sort payloads reject extra fields |
20
+ | 6 [‡](#protocol-6-richer-fallback-properties) | `0.3.0` | October 26, 2026 | Typed, display-name-keyed query properties and unified page responses replace the older query representation |
21
+ | 7 | `0.3.3` | October 30, 2026 | Typed text fallback properties for unique ID, button, formula, location, rollup, and verification |
22
+ | 8 | `0.4.0` | November 2, 2026 | Local manifests move from `/manifest` to HTML metadata sent in `connect`; `manifestSource` selects the initialization manifest |
23
+
24
+ The table lists SDK release boundaries, not complete supported version ranges. Host compatibility
25
+ depends on `connect.bridgeProtocolVersion`, not the SDK package version.
26
+
27
+ ## Protocol 8: local manifest declarations
28
+
29
+ The dev shell removes the `/manifest` route and embeds the local manifest in block HTML. The SDK
30
+ reads that metadata and sends the declaration in `connect.manifest`; initialization no longer
31
+ fetches `/manifest`.
32
+
33
+ The host option `manifestSource` selects the initialization manifest: `"sandbox"` uses the
34
+ declaration from `connect`, and `"host"` uses the persisted manifest returned by
35
+ `getPersistedManifest`. Only the selected source determines initialization. Deployed blocks using
36
+ supported earlier protocols retain their existing response formats.
37
+
38
+ The shared host runtime minimum remains 3. The dev shell uses the Worker manifest in host mode and
39
+ accepts supported earlier protocols. Sandbox-mode initialization uses the protocol 8 declaration
40
+ from `connect`.
41
+
42
+ ## Changes within a declared version
43
+
44
+ ### Protocol 5: unavailable containing-page context
45
+
46
+ On October 2, 2026, SDK 0.2.17 added `{ type: "unavailable" }` to containing-page context while
47
+ retaining protocol 5. SDK 0.2.16 rejects that shape. The host preserves later protocol 5 behavior
48
+ and accepts the limitation for the older SDK, as decided on October 31, 2026. Affected blocks must
49
+ upgrade and rebuild. The host's [compatibility reference](../compatibility.md)
50
+ describes current compatibility behavior.
51
+
52
+ ### Protocol 6: richer fallback properties
53
+
54
+ The protocol version changed in code on October 22, 2026. On October 26, the `pages` and
55
+ `dataSources` APIs adopted a shared page representation: data-source query rows and page operation
56
+ results use the same page shape, with typed properties keyed by display name.
57
+
58
+ Some SDKs added text fallback property objects for unique ID, button, formula, location, rollup, and
59
+ verification while still reporting protocol 6. Earlier protocol 6 SDKs reject those property
60
+ objects. Because both groups declare the same protocol version, updated hosts send `null` for these
61
+ properties to every protocol 6 block.
62
+
63
+ Blocks built with the later protocol 6 SDKs therefore lose those property values when their host
64
+ updates. UI that displays the values can become empty, and code that assumes a property object is
65
+ present can fail. Upgrade the SDK to protocol 7 or later and rebuild the block to receive the text
66
+ fallback property objects. Host adapters continue returning the current page shape; the host runtime
67
+ applies the protocol-specific conversion.
@@ -0,0 +1,189 @@
1
+ # Bridge wire protocol
2
+
3
+ The bridge carries initialization, data requests, results, and live updates between a custom block's
4
+ SDK and its host. This reference defines the handshake, message identity, validation, and payload
5
+ semantics for host integrators and bridge maintainers. Block authors use the SDK's public APIs.
6
+
7
+ Messages are `postMessage` payloads defined by shared Valibot schemas, exposed through
8
+ `@notionhq/custom-blocks-host/protocol`. The embedding application enforces permissions and record
9
+ access. Version-specific formats and conversions are described in
10
+ [Versioning and compatibility](versioning.md).
11
+
12
+ ## Initialization
13
+
14
+ The handshake is:
15
+
16
+ ```text
17
+ sandbox connect → host init → sandbox initResult
18
+ ```
19
+
20
+ All three messages carry the sandbox-generated `initializationId`. A sandbox document sends
21
+ `connect` once. The host sends one `init` for the accepted connection and ignores subsequent
22
+ `connect` messages, including malformed ones. Live updates wait for `initResult.success`.
23
+ `init.error` and `initResult.error` are terminal.
24
+
25
+ The sandbox waits for `init` without a timeout. If host state preparation times out, the host sends
26
+ `init.error` with `initial_state_timeout` to end that wait.
27
+
28
+ `connect` carries connection identity, version metadata, and an optional local manifest declaration.
29
+ The SDK reads that declaration from the `application/json` script with ID
30
+ `notion-custom-block-manifest`, injected into the block HTML by dev tooling. It reads metadata only
31
+ from supported local block server URLs. Missing metadata omits `connect.manifest`; invalid metadata
32
+ sends `connect.error` with `manifest_invalid`.
33
+
34
+ The embedding application selects the manifest source. In host mode, the host uses its persisted
35
+ manifest and ignores the sandbox declaration. In sandbox mode, the host uses `connect.manifest` from
36
+ a permitted local sandbox; a missing declaration produces `manifest_unavailable`. The selected
37
+ manifest is authoritative and is returned in `init` with context and data-source bindings. Errors
38
+ from the selected manifest source pass through unchanged. Host mode can initialize after a sandbox
39
+ `manifest_invalid` error using its own manifest; other connect errors remain fatal. The connect
40
+ parser discards unknown properties.
41
+
42
+ `initResult.success` may include `initialHeight`, the height of the sandbox's rendered content in
43
+ CSS pixels. The host's [lifecycle guide](../lifecycle.md) describes iframe sizing,
44
+ reveal, and retry behavior.
45
+
46
+ `connect.bridgeProtocolVersion` declares the sandbox's wire contract. `sdkVersion` is analytics
47
+ metadata. See [versioning and compatibility](versioning.md) for supported formats and
48
+ [Validation and errors](#validation-and-errors) for malformed handshake behavior.
49
+
50
+ ## Manifests
51
+
52
+ A manifest declares a block's data sources and display metadata. `manifestSchema` defines its fields
53
+ and defaults omitted `dataSources` and each data source's omitted `properties` to `{}`.
54
+
55
+ `isLocalBlockServerUrl(url)` accepts HTTP(S) URLs on `localhost`, `*.localhost`, or `127.0.0.1`
56
+ without credentials. It checks the address only. The host also requires a sandbox-mode block to use
57
+ a different origin from its embedding page.
58
+
59
+ ## Message identity
60
+
61
+ - One-shot request/result pairs use `requestId`.
62
+ - `queryDataSource` is a long-lived subscription; requests and every update use its stable
63
+ `subscriptionId`.
64
+ - The initialization lifecycle uses `initializationId`, not `requestId` or `subscriptionId`.
65
+ - One-way messages, such as `resize` and bridge NACKs, have no correlation ID.
66
+
67
+ Hosts key query state by `subscriptionId`, replace state when a request reuses one, and suppress
68
+ stale asynchronous results.
69
+
70
+ The sandbox can send `unsubscribeDataSourceQuery` with the same `subscriptionId` when it no longer
71
+ needs the subscription. An unknown or repeated unsubscribe is a no-op. Hosts stop future refreshes
72
+ and suppress results from work already in progress.
73
+
74
+ ## Validation and errors
75
+
76
+ Each direction has a validation schema:
77
+
78
+ - Sandbox to host: `sandboxToHostMessageSchema`
79
+ - Host to sandbox: `hostToSandboxMessageSchema`
80
+
81
+ On parse failure, the host replies with `invalidSandboxMessage` and the sandbox replies with
82
+ `invalidHostMessage`, except for the handshake cases below. These replies are negative
83
+ acknowledgments (NACKs). Receivers do not NACK another NACK.
84
+
85
+ Malformed sandbox messages do not change the host's initialization state or timeout, except as
86
+ follows:
87
+
88
+ - The host expects `connect` and receives a malformed `connect` with a readable `initializationId`.
89
+ The host sends `init.error` instead of a NACK, then fails initialization. An invalid or
90
+ unsupported numeric protocol version takes precedence over `invalid_connect_payload`.
91
+ - The host expects `initResult` and receives a malformed `initResult` with the active
92
+ `initializationId`. The host sends a NACK and fails initialization with
93
+ `invalid_init_result_payload`. This error code describes a local host failure. The host does not
94
+ send it in `initResult.error`.
95
+
96
+ A malformed `initResult` with a missing or stale ID does not end initialization or reset its
97
+ timeout. Malformed messages do not restart initialization after it succeeds or fails.
98
+
99
+ The sandbox handles a malformed `init` differently if it can verify the `initializationId`. In that
100
+ case, it sends `initResult.error` with `invalid_init_payload` instead of a NACK. Otherwise, it sends
101
+ `invalidHostMessage`.
102
+
103
+ `CustomBlockErrorInfo` carries `code`, `message`, and `isRetryable`. Error schemas accept any string
104
+ for `code`, including unknown codes. TypeScript aliases list known codes for autocomplete.
105
+
106
+ ## Data sources
107
+
108
+ ### Bindings and IDs
109
+
110
+ Hosts send `NotionDataSourceBindings` as `dataSources: { bindings }`, keyed by the semantic data
111
+ source key declared in the manifest. `collectionSchema` contains only actual database properties.
112
+
113
+ Sandbox-to-host object references are raw IDs. The SDK resolves semantic data-source and property
114
+ keys before sending query or page-operation requests. Get and update requests can carry an optional
115
+ resolved `dataSourceId`.
116
+
117
+ ### Query requests
118
+
119
+ `queryDataSource` may include a resolved `filter` and an ordered `sorts` list. Each sort contains a
120
+ raw `propertyId` and an `ascending` or `descending` direction. Hosts apply sorts in array order. The
121
+ SDK and host validate that the list contains at most ten unique properties. The host rejects
122
+ property types without a supported sort value.
123
+
124
+ Every filter and sort property ID must exist in `collectionSchema.propertiesById`. Queries with
125
+ filters or sorts return `data_source_schema_unavailable` when the schema is unavailable. Filter
126
+ branches, operators, person references, and sort objects reject extra fields.
127
+
128
+ #### Creator and status filters
129
+
130
+ Creator filters require `propertyId` to identify an actual database property of type `created_by`.
131
+ The `created_by` filter branch selects the property type, while `propertyId` addresses the column.
132
+
133
+ The `created_by` filter supports human user targets with `contains` and `does_not_contain`, plus
134
+ `is_empty` and `is_not_empty`. Hosts use row creator metadata, including non-human and missing
135
+ creators in exclusion results.
136
+
137
+ Status filters support option operators and stored group names. Group operators accept one name or
138
+ an array of 1–5 names. Hosts must validate each name against the bound Status property.
139
+
140
+ #### Relation filters
141
+
142
+ Relation filters require an actual `relation` property in the bound schema. `contains` and
143
+ `does_not_contain` accept one page ID. `is_empty` and `is_not_empty` accept only `true` and require
144
+ no page ID. Each relation filter accepts exactly one operator. Adapters must evaluate empty checks
145
+ against relation values, including XLDB row IDs where supported.
146
+
147
+ #### Date filters
148
+
149
+ Date comparisons accept ISO values and relative values such as `today`. The query schema defines the
150
+ accepted values and range operators. Range operators require an empty object.
151
+
152
+ Date comparisons accept `target` inside `date`, beside the operator. The target is `start_date` by
153
+ default. For ranges, `end_date` compares the range end. For individual dates, it compares the date.
154
+ Empty checks accept either target, but they test the whole property.
155
+
156
+ ```ts
157
+ filter: {
158
+ propertyId: "schedule-id",
159
+ date: { target: "end_date", on_or_after: "2026-10-08" },
160
+ }
161
+ ```
162
+
163
+ ## Page context
164
+
165
+ Containing-page context uses `CustomBlockPage`. The `init.page` and `pageChanged.page` fields
166
+ contain `{ type: "page", id, parent }` for an accessible page, or `{ type: "unavailable" }` when the
167
+ containing page itself is inaccessible. The unavailable shape never carries a page ID. This is
168
+ distinct from an accessible page with an inaccessible parent
169
+ (`page.parent: { type: "unavailable" }`).
170
+
171
+ ## Page writes
172
+
173
+ The `createPage` and `updatePage` messages use raw property IDs from Notion as map keys. Each value
174
+ must carry the same raw property ID in its `id` field.
175
+
176
+ ## Page responses
177
+
178
+ Host handlers return unified `NotionPageData` for queries and page operations. Properties use
179
+ display names as keys and typed values that include their raw property IDs. Only actual schema
180
+ properties are returned. Query results contain `items` and `hasMore`. `notionPageDataSchema` defines
181
+ the page fields, including archive state and creation, edit, and visit metadata.
182
+
183
+ Page reads return string values for `unique_id`, `formula`, `rollup`, `button`, `verification`, and
184
+ `location`, preserving each property's type. These properties are read-only. Values use stored text,
185
+ or `""` when absent. Hosts do not evaluate formulas or rollups.
186
+
187
+ The host converts responses to the sandbox's declared protocol. See
188
+ [versioning and compatibility](versioning.md) for protocol version rules. The host's
189
+ [compatibility reference](../compatibility.md) describes older response formats.
@@ -0,0 +1,38 @@
1
+ # Bridge protocol versioning
2
+
3
+ The SDK inside a block and the host in its embedding application release independently. A bridge
4
+ protocol version identifies the wire contract they use, independently of their package versions.
5
+ This reference defines the version identifiers, protocol format names, and rules for contract
6
+ changes. Release boundaries and exceptions are recorded in the [changelog](changelog.md). The host's
7
+ [compatibility reference](../compatibility.md) describes runtime support and response
8
+ conversions.
9
+
10
+ ## Version identifiers
11
+
12
+ - **Bridge protocol:** `connect.bridgeProtocolVersion` declares the sandbox's wire contract.
13
+ - **SDK package:** identifies the SDK's public API and implementation. It can change without a wire
14
+ change.
15
+ - **Host package:** identifies the runtime implementation. Publishing it does not deploy it to
16
+ Notion.
17
+
18
+ `connect.bridgeProtocolVersion` selects the wire contract. `sdkVersion` is analytics metadata.
19
+ `LATEST_BRIDGE_PROTOCOL_VERSION` identifies the latest contract defined by the package.
20
+
21
+ ## Protocol export names
22
+
23
+ The `@notionhq/custom-blocks-host/protocol` entrypoint exports wire definitions. Page formats used
24
+ by protocols 3–5 are named `NotionPageForProtocol3To5` and `NotionDataSourcePageForProtocol3To5`,
25
+ with matching `notionPageForProtocol3To5Schema` and `notionDataSourcePageForProtocol3To5Schema`
26
+ validators. Message types and schemas containing `WithProtocol3To5Compatibility` accept both current
27
+ page formats and protocol 3–5 page formats. `UNIFIED_PAGE_PROTOCOL_VERSION` is 6, the minimum
28
+ protocol version that uses unified pages.
29
+
30
+ ## Version changes
31
+
32
+ A breaking wire change increases the bridge protocol version. This includes changed meanings,
33
+ required fields, and new response variants an older receiver rejects. Optional fields and new
34
+ messages need no bump only when older receivers can safely ignore or NACK them. Parsing success
35
+ alone does not prove compatible SDK behavior.
36
+
37
+ An SDK that requires a newer protocol needs a host that supports that contract. Updating the SDK
38
+ package does not update the host in the embedding application.
@@ -0,0 +1,68 @@
1
+ # Host compatibility
2
+
3
+ A host can update while deployed blocks keep older SDKs. The runtime accepts supported protocol
4
+ versions and converts current handler results to each block's declared contract. This reference
5
+ covers version admission, normalization, and the formats sent to older blocks. Protocol version
6
+ rules and release history are in the [versioning guide](bridge/versioning.md) and
7
+ [changelog](bridge/changelog.md).
8
+
9
+ ## Supported versions
10
+
11
+ The protocol subpath exports `LATEST_BRIDGE_PROTOCOL_VERSION` and
12
+ `MIN_HOST_SUPPORTED_BRIDGE_PROTOCOL_VERSION`. These constants define the package's latest known
13
+ protocol and runtime minimum. Embedders can raise the minimum but cannot enable retired versions.
14
+ The host admits future positive integer versions and validates them against known schemas. Admission
15
+ does not guarantee that future features work.
16
+
17
+ `sdkVersion` is analytics metadata. Compatibility behavior depends on the protocol version.
18
+
19
+ The current SDK declares protocol 8. The shared runtime minimum is 3. The dev shell uses the Worker
20
+ manifest in host mode and accepts the shared runtime's supported protocol versions. Protocols 7 and
21
+ 8 use the same page and query response formats. Protocol 8's local manifest declaration is selected
22
+ only in sandbox mode; host mode uses the persisted manifest. See the
23
+ [protocol 8 release entry](bridge/changelog.md#protocol-8-local-manifest-declarations).
24
+
25
+ The `minBridgeProtocolVersion` option defaults to the runtime minimum. Raising the option rejects
26
+ older SDKs for this embedding application. Lowering it cannot restore retired versions. Raising
27
+ `MIN_HOST_SUPPORTED_BRIDGE_PROTOCOL_VERSION` retires those versions across the runtime.
28
+
29
+ ## Host normalization
30
+
31
+ The host validates sandbox requests before dispatching them to handlers. Handlers use current data
32
+ shapes, and the host converts responses to the sandbox's declared protocol. Setters, query
33
+ refreshes, and handler responses use the same conversion. Unchanged messages and errors pass
34
+ through.
35
+
36
+ ## Current compatibility behavior
37
+
38
+ | Target | Query results | Page operation results | Binding schemas |
39
+ | ------ | --------------------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------ |
40
+ | 3–4 | Flat ID-keyed values, including synthetic metadata entries | ID-keyed properties with unsupported property objects omitted | Synthetic metadata read schemas restored in outgoing copies |
41
+ | 5 | Same query values as protocols 3–4 | Same page format as protocols 3–4 | Actual database schemas. The SDK supplies synthetic read schemas locally |
42
+ | 6 | Unified name-keyed properties. Text fallback property objects become `null` | Same unified page format | Actual database schemas |
43
+ | 7+ | Unified pages with typed text fallback properties | Same unified page format | Actual database schemas |
44
+
45
+ The synthetic metadata IDs are `created_time`, `last_edited_time`, `created_by`, and
46
+ `last_edited_by`. They retain metadata meaning if an actual property uses the same raw ID. This
47
+ matches the protocol 3–4 binding schema precedence. Host validation always uses the unmodified
48
+ actual database schema.
49
+
50
+ Release-specific compatibility limits are documented in the changelog entries for
51
+ [protocol 5 page context](bridge/changelog.md#protocol-5-unavailable-containing-page-context)
52
+ and
53
+ [protocol 6 text fallback properties](bridge/changelog.md#protocol-6-richer-fallback-properties).
54
+
55
+ ### Unavailable containing-page context
56
+
57
+ Protocols 3–4 receive `context_unavailable` during initialization or stop when containing-page
58
+ context becomes unavailable. This concerns `init.page` and `pageChanged.page`, not a page
59
+ operation's `page_unavailable` error.
60
+
61
+ ### Protocol 3–5 metadata query semantics
62
+
63
+ Page creation and edit metadata is readable without corresponding database columns. Current page
64
+ objects carry these fields at the top level. Protocol 3–5 query adapters copy them into
65
+ `propertiesById` for older SDKs.
66
+
67
+ Filters and sorts require actual database properties in the bound schema. The host does not support
68
+ queries against synthetic metadata IDs without corresponding database properties.
@@ -0,0 +1,199 @@
1
+ # Host handlers and data
2
+
3
+ Handlers connect block requests to your application's data access, persistence, and navigation. The
4
+ runtime validates bridge messages and manages subscriptions. Your application enforces viewer
5
+ permissions and implements the requested operations. This guide covers handler contracts, a
6
+ data-source example, refreshes, and external navigation.
7
+
8
+ The [dev shell](https://www.npmjs.com/package/@notionhq/custom-blocks-dev-shell) is a working host
9
+ implementation with local sample data. Use it as a reference for a larger integration. Production
10
+ hosts must provide their own permissions, data access, and persistence.
11
+
12
+ ## Handler contracts
13
+
14
+ `queryDataSource` is required. Other handlers are optional. Omitting a one-shot request handler
15
+ sends no response to that request. A handler returns a result synchronously or as a promise. Errors
16
+ carry `{ code, message, isRetryable }`, with open string codes.
17
+
18
+ Handler requests are validated messages with branded IDs. Initial state, setters, handler results,
19
+ and `*MessageInput` types accept plain string IDs. The runtime validates these inputs and applies
20
+ schema defaults. ID brands do not check record existence or access.
21
+
22
+ Query filters and sorts are validated against the actual bound database schema before
23
+ `queryDataSource` runs. See the [wire reference](bridge/protocol.md#data-sources) for
24
+ query semantics.
25
+
26
+ ## Connect a data source
27
+
28
+ Connect an embedded block to an in-memory Tasks data source with one numeric Estimate property. This
29
+ walkthrough extends the [README's embed example](../README.md#create-a-host): declare the data
30
+ source, bind its keys to IDs, return a query result, and refresh that result after a value changes.
31
+
32
+ Keep the iframe, context, and lifecycle callbacks from the embed example. The block must declare the
33
+ same manifest and query `tasks` through the SDK's public APIs.
34
+
35
+ ### Declare and bind the data source
36
+
37
+ The manifest uses block-defined keys. Bindings connect those keys to actual data-source and property
38
+ IDs. Add these definitions before creating the host:
39
+
40
+ ```ts
41
+ import type {
42
+ CustomBlockHostDataSourcesPayload,
43
+ CustomBlockHostHandlers,
44
+ } from "@notionhq/custom-blocks-host"
45
+ import type { CustomBlockManifest, NotionPageData } from "@notionhq/custom-blocks-host/protocol"
46
+
47
+ const tasksId = "44444444-4444-4444-8444-444444444444"
48
+
49
+ const manifest: CustomBlockManifest = {
50
+ version: 1,
51
+ dataSources: {
52
+ tasks: {
53
+ name: "Tasks",
54
+ properties: {
55
+ estimate: { name: "Estimate", type: "number" },
56
+ },
57
+ },
58
+ },
59
+ }
60
+
61
+ const taskBindings: CustomBlockHostDataSourcesPayload = {
62
+ bindings: {
63
+ tasks: {
64
+ collectionPointer: { table: "collection", id: tasksId },
65
+ collectionSchema: {
66
+ propertiesById: {
67
+ "estimate-id": { name: "Estimate", type: "number" },
68
+ },
69
+ },
70
+ propertyIdsByKey: { estimate: "estimate-id" },
71
+ },
72
+ },
73
+ }
74
+ ```
75
+
76
+ In the embed example, return `{ status: "success", manifest }` from `getPersistedManifest` and
77
+ replace `initialState.dataSources` with `taskBindings`. Here, `tasks` and `estimate` are semantic
78
+ keys; `tasksId` and `estimate-id` identify the bound data source and property.
79
+
80
+ ### Return query results
81
+
82
+ Add this handler and use it as the embed example's `handlers.queryDataSource`. Keep its existing
83
+ `resize` handler.
84
+
85
+ ```ts
86
+ let estimate = 3
87
+
88
+ const handlers: CustomBlockHostHandlers = {
89
+ queryDataSource: message => {
90
+ if (message.dataSourceId !== tasksId) {
91
+ return {
92
+ status: "error",
93
+ error: {
94
+ code: "unmapped_data_source",
95
+ message: "This data source is not available.",
96
+ isRetryable: false,
97
+ },
98
+ }
99
+ }
100
+
101
+ if (message.filter || message.sorts?.length) {
102
+ return {
103
+ status: "error",
104
+ error: {
105
+ code: "invalid_data_source_query",
106
+ message: "This demo supports only unfiltered, unsorted queries.",
107
+ isRetryable: false,
108
+ },
109
+ }
110
+ }
111
+
112
+ const items: NotionPageData[] = [
113
+ {
114
+ object: "page",
115
+ id: "55555555-5555-4555-8555-555555555555",
116
+ parent: { type: "data_source_id", data_source_id: tasksId },
117
+ is_archived: false,
118
+ in_trash: false,
119
+ properties: {
120
+ Estimate: { id: "estimate-id", type: "number", number: estimate },
121
+ },
122
+ },
123
+ ]
124
+
125
+ return {
126
+ status: "success",
127
+ items: items.slice(0, message.limit),
128
+ hasMore: items.length > message.limit,
129
+ }
130
+ },
131
+ }
132
+ ```
133
+
134
+ The SDK resolves `tasks` to `tasksId` before the host receives the request. Result properties use
135
+ display names as keys (`Estimate`) and include their raw property IDs (`estimate-id`). The runtime
136
+ handles subscription IDs, validates results, and converts them for supported older SDKs.
137
+
138
+ This demo rejects filters and sorts rather than silently ignoring them. A production adapter must
139
+ enforce record access, execute the requested filter and sorts, and honor the result limit. See
140
+ [query semantics](bridge/protocol.md#data-sources).
141
+
142
+ ## Refresh active queries
143
+
144
+ After creating the host, update the in-memory value and notify active subscriptions:
145
+
146
+ ```ts
147
+ estimate = 5
148
+ host.refreshQuery({ dataSourceId: tasksId })
149
+ ```
150
+
151
+ If the block has an active query for Tasks, its next result contains an estimate of `5`. A query
152
+ started later also reads the updated value. The application triggers refreshes when its underlying
153
+ data changes; the runtime re-runs matching subscriptions. Call `host.stop()` when removing the
154
+ embed.
155
+
156
+ `refreshQuery()` re-runs active subscriptions for the supplied data-source ID with their existing
157
+ filter, sorts, and limit. Overlapping refreshes queue one follow-up query. The running query
158
+ delivers its result first. Unsubscribe, replacement, and stop discard queued work and suppress late
159
+ results. Non-retryable query errors end subscriptions. Retryable errors retain them.
160
+
161
+ ## Page and user operations
162
+
163
+ `createPage`, `getPage`, and `updatePage` connect page requests to your application's data access
164
+ and persistence. `getUser` and `listUsers` supply user data. Each handler returns a success payload
165
+ or an error. The runtime adds the request's correlation ID and sends the response. The application
166
+ enforces record access before reading or writing data.
167
+
168
+ `openPage` handles navigation to a Notion page. `resize` receives the block's requested height in
169
+ CSS pixels. The application owns navigation and iframe sizing.
170
+
171
+ ## External navigation
172
+
173
+ `openExternalUrl` receives an `AbortSignal` and an absolute HTTP or HTTPS URL without credentials.
174
+ The URL is limited to 2,048 JavaScript code units before and after normalization. The adapter
175
+ displays the exact URL, obtains approval, and navigates only after approval. It may apply further
176
+ restrictions. Dismiss pending confirmation UI when the signal aborts.
177
+
178
+ Each iframe permits one pending navigation. A different request receives `pending_navigation`. A
179
+ duplicate of the active request is ignored. Completed requests are forgotten, so later requests
180
+ require fresh approval. The runtime redacts the URL from `onLog` payloads and suppresses results
181
+ after stop.
182
+
183
+ ## Property binding
184
+
185
+ `getAutoBoundPropertyIdsByKey({ manifestProperties, propertySchemasById })` requires matching
186
+ property types and either an exact raw ID match or a display-name match after trimming and
187
+ lowercasing. IDs are not trimmed or case-folded. Exactly one candidate creates a binding. Zero or
188
+ multiple candidates leave the property unmapped. The result maps manifest property keys to database
189
+ property IDs. An ID match has no priority over a name match: if they identify different compatible
190
+ properties, the manifest property stays unmapped.
191
+
192
+ ## Date conversion and types
193
+
194
+ `toNotionDateValue()` converts `NotionDateInput` into the four `CustomBlockDateValue` variants,
195
+ including reminders. Invalid input throws `InvalidNotionDateError`.
196
+
197
+ The root exports host configuration, handlers, results, and `*MessageInput` types. Wire schemas,
198
+ message and page types, and constants such as `LATEST_BRIDGE_PROTOCOL_VERSION` use the
199
+ [protocol subpath](../README.md#protocol-schemas-and-types).