@notionhq/custom-blocks 0.1.11 → 0.1.13

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.
package/README.md CHANGED
@@ -104,7 +104,6 @@ API surface, one page per category. Import framework-neutral APIs from `@notionh
104
104
  - [`docs/users.md`](./docs/users.md) — `users.list / get`, the `NotionUser` shape, paging.
105
105
  - [`docs/errors.md`](./docs/errors.md) — request results, error format, error codes, retries, and initialization failures.
106
106
  - [`docs/deployment.md`](./docs/deployment.md) — worker-backed deploys, localhost self-hosted fallback, where the manifest comes from.
107
- - [`docs/vite-plugin.md`](./docs/vite-plugin.md) — the `notionCustomBlock()` Vite plugin.
108
107
 
109
108
  ## Forbidden APIs
110
109
 
@@ -10,12 +10,11 @@ export type ManifestLoadResult = {
10
10
  error: CustomBlockConnectError;
11
11
  };
12
12
  /**
13
- * Attempts to load the custom block's optional self-hosted manifest, co-located with the bundle at
14
- * `/custom_blocks.json`. This is to support local development where the block is served from a
15
- * local dev server.
13
+ * On the literal `localhost` hostname, attempts to load the custom block's optional self-hosted
14
+ * manifest exposed by the worker dev shell at `/manifest`.
16
15
  *
17
16
  * It is up to the host to decide whether to use this manifest or provide its own persisted manifest.
18
17
  */
19
- export declare function attemptToLoadSelfHostedManifest(): Promise<ManifestLoadResult>;
18
+ export declare function attemptToLoadSelfHostedManifest(hostname?: string | undefined): Promise<ManifestLoadResult>;
20
19
  export {};
21
20
  //# sourceMappingURL=loadManifest.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"loadManifest.d.ts","sourceRoot":"","sources":["../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/bridge/loadManifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAA;AACtF,OAAO,EACN,KAAK,mBAAmB,EAExB,MAAM,8CAA8C,CAAA;AAKrD,KAAK,2BAA2B,GAC7B,sBAAsB,GACtB,kBAAkB,GAClB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEhB,KAAK,uBAAuB,GAAG,oBAAoB,CAAC,2BAA2B,CAAC,CAAA;AAEhF,MAAM,MAAM,kBAAkB,GAC3B;IACA,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAA;IACpC,KAAK,CAAC,EAAE,SAAS,CAAA;CAChB,GACD;IACA,QAAQ,EAAE,IAAI,CAAA;IACd,KAAK,EAAE,uBAAuB,CAAA;CAC7B,CAAA;AAEJ;;;;;;GAMG;AACH,wBAAsB,+BAA+B,IAAI,OAAO,CAAC,kBAAkB,CAAC,CA0DnF"}
1
+ {"version":3,"file":"loadManifest.d.ts","sourceRoot":"","sources":["../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/bridge/loadManifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAA;AACtF,OAAO,EACN,KAAK,mBAAmB,EAExB,MAAM,8CAA8C,CAAA;AAKrD,KAAK,2BAA2B,GAC7B,sBAAsB,GACtB,kBAAkB,GAClB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEhB,KAAK,uBAAuB,GAAG,oBAAoB,CAAC,2BAA2B,CAAC,CAAA;AAEhF,MAAM,MAAM,kBAAkB,GAC3B;IACA,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAA;IACpC,KAAK,CAAC,EAAE,SAAS,CAAA;CAChB,GACD;IACA,QAAQ,EAAE,IAAI,CAAA;IACd,KAAK,EAAE,uBAAuB,CAAA;CAC7B,CAAA;AAEJ;;;;;GAKG;AACH,wBAAsB,+BAA+B,CACpD,QAAQ,GAAE,MAAM,GAAG,SAEQ,GACzB,OAAO,CAAC,kBAAkB,CAAC,CAgE7B"}
@@ -1,14 +1,20 @@
1
1
  import { manifestSchema, } from "../protocol/manifest.js";
2
2
  import * as v from "valibot";
3
- const MANIFEST_URL = "custom_blocks.json";
3
+ const MANIFEST_URL = "/manifest";
4
4
  /**
5
- * Attempts to load the custom block's optional self-hosted manifest, co-located with the bundle at
6
- * `/custom_blocks.json`. This is to support local development where the block is served from a
7
- * local dev server.
5
+ * On the literal `localhost` hostname, attempts to load the custom block's optional self-hosted
6
+ * manifest exposed by the worker dev shell at `/manifest`.
8
7
  *
9
8
  * It is up to the host to decide whether to use this manifest or provide its own persisted manifest.
10
9
  */
11
- export async function attemptToLoadSelfHostedManifest() {
10
+ export async function attemptToLoadSelfHostedManifest(hostname = typeof window === "undefined"
11
+ ? undefined
12
+ : window.location.hostname) {
13
+ // Runtime manifests are a localhost-only development fallback. Deployed
14
+ // blocks receive their authoritative manifest from the host during init.
15
+ if (hostname !== "localhost") {
16
+ return { manifest: null };
17
+ }
12
18
  let response;
13
19
  try {
14
20
  response = await fetch(MANIFEST_URL, { credentials: "omit" });
package/dist/init.js CHANGED
@@ -39,7 +39,7 @@ export function initCustomBlock(opts = {}) {
39
39
  }
40
40
  /**
41
41
  * Performs the host <-> sandbox SDK handshake:
42
- * 1. Attempts to load an optional self-hosted `custom_blocks.json`
42
+ * 1. On localhost, attempts to load the worker manifest from `/manifest`
43
43
  * 2. Sends `connect` with the manifest
44
44
  * 3. Awaits the host's `init` message
45
45
  * 4. Applies the `init` payload and acknowledges with `initResult` (fire-and-forget)
package/dist/version.js CHANGED
@@ -4,4 +4,4 @@
4
4
  *
5
5
  * WARNING: Generated during SDK publish. Do not edit in the published package.
6
6
  */
7
- export const CUSTOM_BLOCKS_SDK_VERSION = "0.1.11"
7
+ export const CUSTOM_BLOCKS_SDK_VERSION = "0.1.13"
@@ -1,39 +1,17 @@
1
1
  # Deployment and manifests
2
2
 
3
- A custom block is a static web bundle — an `index.html` plus optional assets — that Notion loads into a sandboxed `<iframe>`, so the bundle needs to be hosted somewhere Notion can reach. Notion does that hosting: the block ships as part of a worker. Declare the block source and data source schema with `worker.customBlock(...)` in the worker's `src/index.ts`, deploy with `ntn workers deploy`, and Notion serves the built bundle. Serving the bundle from your own dev server is provided as a local-development fallback, not an officially supported deployment target.
3
+ A custom block is a static web bundle — an `index.html` plus optional assets — that Notion loads into a sandboxed `<iframe>`. Custom blocks ship as part of a worker: declare the block source and data source schema with `worker.customBlock(...)` in the worker's `src/index.ts`, deploy with `ntn workers deploy`, and Notion serves the built bundle.
4
4
 
5
- The difference between the two paths is where the manifest lives:
5
+ A custom block manifest describes the data sources a block requires, including their semantic keys, display metadata, and property schemas. The block's `worker.customBlock(...)` declaration is the manifest's single source of truth:
6
6
 
7
- - **Worker-backed** — deploying persists the manifest on the block's definition record in Notion. The host reads it from there. The bundle carries no manifest.
8
- - **Self-hosted (localhost)** — there is no persisted record, so the block serves its manifest dynamically at `/custom_blocks.json` and passes the manifest to the host in the `connect` handshake.
7
+ - **Deployed** — deploying persists the manifest on the block's definition record in Notion. The host reads it from there and sends it to the SDK during initialization.
8
+ - **Local development** — the dev shell extracts the same worker declaration at runtime and serves the selected block's manifest dynamically at `/manifest`.
9
9
 
10
- For a self-hosted block, the sandbox can propose the initial manifest in `connect`. The host decides whether to use it and sends the authoritative manifest back in `init`. The SDK always uses the host-provided manifest from `init` even if it differs from the one sent during `connect`.
10
+ ## Local development
11
11
 
12
- ## Self-hosted blocks (localhost fallback)
12
+ Run `ntn customblocks dev` from a worker. It builds the worker, extracts its custom block declarations, starts one localhost Vite server per block, and renders them in the mock host at http://localhost:9873.
13
13
 
14
- For everyday local development you usually don't need to wire this up yourself: `ntn customblocks dev` runs the custom blocks dev shell, which serves the worker's blocks this way and renders them in a mock Notion host at http://localhost:9873.
15
-
16
- During development, a Notion host can point a custom block at a local dev server instead of a deployed worker. The block describes itself with an optional `custom_blocks.json` at the bundle root:
17
-
18
- ```json
19
- {
20
- "version": 1,
21
- "dataSources": {
22
- "tasks": {
23
- "name": "Tasks",
24
- "description": "The collection of tasks to render",
25
- "properties": {
26
- "title": { "name": "Title", "type": "title" },
27
- "dueDate": { "name": "Due date", "type": "date" }
28
- }
29
- }
30
- }
31
- }
32
- ```
33
-
34
- At startup the SDK fetches this file from the bundle's origin and forwards it in `connect`. If the file is missing, the SDK omits `manifest` from `connect`. If it is present but unreadable or invalid, initialization fails.
35
-
36
- The [Vite plugin](./vite-plugin.md) keeps this file working in dev and emits it into `dist/` on build.
14
+ At startup, the SDK fetches `/manifest` only when the bundle's hostname is exactly `localhost`, then forwards the response in `connect`. On every other hostname, the SDK skips this fetch and relies on the host-provided manifest from `init`.
37
15
 
38
16
  ## Types
39
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notionhq/custom-blocks",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -5,7 +5,7 @@ import {
5
5
  } from "@notionhq/custom-blocks-protocol/manifest.js"
6
6
  import * as v from "valibot"
7
7
 
8
- const MANIFEST_URL = "custom_blocks.json"
8
+ const MANIFEST_URL = "/manifest"
9
9
 
10
10
  type CustomBlockConnectErrorCode =
11
11
  | "manifest_unavailable"
@@ -25,13 +25,22 @@ export type ManifestLoadResult =
25
25
  }
26
26
 
27
27
  /**
28
- * Attempts to load the custom block's optional self-hosted manifest, co-located with the bundle at
29
- * `/custom_blocks.json`. This is to support local development where the block is served from a
30
- * local dev server.
28
+ * On the literal `localhost` hostname, attempts to load the custom block's optional self-hosted
29
+ * manifest exposed by the worker dev shell at `/manifest`.
31
30
  *
32
31
  * It is up to the host to decide whether to use this manifest or provide its own persisted manifest.
33
32
  */
34
- export async function attemptToLoadSelfHostedManifest(): Promise<ManifestLoadResult> {
33
+ export async function attemptToLoadSelfHostedManifest(
34
+ hostname: string | undefined = typeof window === "undefined"
35
+ ? undefined
36
+ : window.location.hostname,
37
+ ): Promise<ManifestLoadResult> {
38
+ // Runtime manifests are a localhost-only development fallback. Deployed
39
+ // blocks receive their authoritative manifest from the host during init.
40
+ if (hostname !== "localhost") {
41
+ return { manifest: null }
42
+ }
43
+
35
44
  let response: Response
36
45
  try {
37
46
  response = await fetch(MANIFEST_URL, { credentials: "omit" })
package/src/init.ts CHANGED
@@ -83,7 +83,7 @@ export function initCustomBlock(
83
83
 
84
84
  /**
85
85
  * Performs the host <-> sandbox SDK handshake:
86
- * 1. Attempts to load an optional self-hosted `custom_blocks.json`
86
+ * 1. On localhost, attempts to load the worker manifest from `/manifest`
87
87
  * 2. Sends `connect` with the manifest
88
88
  * 3. Awaits the host's `init` message
89
89
  * 4. Applies the `init` payload and acknowledges with `initResult` (fire-and-forget)
@@ -1,46 +1,8 @@
1
- /**
2
- * Type declarations for the Vite plugin. The runtime lives in `vite.js` —
3
- * see that file for behavior, options, and the reasoning for splitting `.js`
4
- * + `.d.ts` instead of using a single `.ts` source.
5
- */
6
-
7
- type UserConfig = { base?: string }
8
-
9
- type ResolvedConfig = { root: string; command: "serve" | "build" }
10
-
11
- type ServerLike = {
12
- middlewares: { use: (handler: MiddlewareHandler) => unknown }
13
- }
14
-
15
- type MiddlewareHandler = (
16
- req: { url?: string },
17
- res: {
18
- statusCode?: number
19
- setHeader: (name: string, value: string) => void
20
- end: (body?: string) => void
21
- },
22
- next: () => void,
23
- ) => void
24
-
25
- type AssetEmitterContext = {
26
- emitFile: (file: {
27
- type: "asset"
28
- fileName: string
29
- source: string
30
- }) => string
31
- }
32
-
33
1
  export type NotionCustomBlockPlugin = {
34
2
  name: string
35
- config: (userConfig: UserConfig) => { base: string }
36
- configResolved: (config: ResolvedConfig) => void
37
- configureServer: (server: ServerLike) => void
38
- configurePreviewServer: (server: ServerLike) => void
39
- buildStart: (this: AssetEmitterContext) => void
40
3
  }
41
4
 
42
5
  /**
43
- * Vite plugin that serves an optional self-hosted `custom_blocks.json` to the custom block
44
- * bundle during local development and emits it as a separate asset on build.
6
+ * @deprecated This compatibility stub does nothing. Remove it from Vite config when possible.
45
7
  */
46
8
  export function notionCustomBlock(): NotionCustomBlockPlugin
@@ -1,116 +1,8 @@
1
+ // Compatibility export for existing blocks.
2
+ // TODO(custom-blocks): Remove the Vite server entirely when bumping bridge protocol version.
1
3
  /**
2
- * Vite plugin half of `@notionhq/custom-blocks`. Exposes an optional
3
- * self-hosted `custom_blocks.json` manifest to the custom-block bundle so the
4
- * SDK's runtime fetch can resolve.
5
- *
6
- * Authored as plain JS (with `index.d.ts` co-located for types) — the rest of
7
- * the SDK is `.ts` consumed directly via the workspace symlink, but Vite's
8
- * config loader resolves this subpath through Node's ESM resolver, which
9
- * can't load `.ts` source files. Lives in its own `vite-plugin/` directory
10
- * so it can grow its own tsconfig later.
11
- *
12
- * Usage:
13
- *
14
- * import { notionCustomBlock } from "@notionhq/custom-blocks/vite"
15
- *
16
- * export default defineConfig({
17
- * plugins: [react(), notionCustomBlock()],
18
- * })
19
- *
20
- * Requires `"type": "module"` in the consuming project's `package.json` so
21
- * Vite's config loader uses Node's ESM resolver (not CommonJS `require`,
22
- * which fails for ESM-only deps).
23
- *
24
- * - **Dev (`vite`)** — middleware serves `/custom_blocks.json` from the project root
25
- * when the optional file exists.
26
- * - **Build (`vite build`)** — emits an existing `custom_blocks.json` as an asset
27
- * alongside the built `index.html` so `vite preview` and any local file server
28
- * can serve it.
29
- *
30
- * Validation is intentionally not performed here — the SDK validates on
31
- * receipt, and the CLI validates separately at deploy time.
4
+ * @deprecated This compatibility stub does nothing. Remove it from Vite config when possible.
32
5
  */
33
- import { existsSync, readFileSync } from "node:fs"
34
- import { resolve } from "node:path"
35
-
36
- const MANIFEST_FILENAME = "custom_blocks.json"
37
- const MANIFEST_URL_PATH = `/${MANIFEST_FILENAME}`
38
- const REQUIRED_BASE = "./"
39
-
40
6
  export function notionCustomBlock() {
41
- let projectRoot = process.cwd()
42
- let command = "serve"
43
-
44
- function manifestPath() {
45
- return resolve(projectRoot, MANIFEST_FILENAME)
46
- }
47
-
48
- function readManifest() {
49
- const file = manifestPath()
50
- if (!existsSync(file)) {
51
- return undefined
52
- }
53
- return readFileSync(file, "utf8")
54
- }
55
-
56
- function middleware(req, res, next) {
57
- const path = req.url?.split("?", 1)[0]
58
- if (path !== MANIFEST_URL_PATH) {
59
- next()
60
- return
61
- }
62
- const contents = readManifest()
63
- if (contents === undefined) {
64
- res.statusCode = 404
65
- res.end()
66
- return
67
- }
68
- res.setHeader("Content-Type", "application/json")
69
- res.end(contents)
70
- }
71
-
72
- return {
73
- name: "custom-blocks:notion-manifest",
74
- config(userConfig) {
75
- // Custom blocks are served from a content-addressed path that
76
- // has trailing slashes, eg https://dev.notion.so/custom-block-bundle/357b35e6-e67f-81fd-b425-00e7b3e2afd1/
77
- // so all asset URLs in the built bundle must be relative.
78
- // Force `base: "./"` and reject any other explicit value
79
- //
80
- // Note: once we are set up with a wildcard subdomain host, this will no longer be necessary
81
- // and should be removed, since /assets will work fine.
82
- if (userConfig.base !== undefined && userConfig.base !== REQUIRED_BASE) {
83
- throw new Error(
84
- `@notionhq/custom-blocks/vite: \`base\` must be "${REQUIRED_BASE}" (got ${JSON.stringify(userConfig.base)}). Custom blocks need relative asset paths so the bundle works under any host-served prefix.`,
85
- )
86
- }
87
- return { base: REQUIRED_BASE }
88
- },
89
- configResolved(config) {
90
- projectRoot = config.root
91
- command = config.command
92
- },
93
- configureServer(server) {
94
- server.middlewares.use(middleware)
95
- },
96
- configurePreviewServer(server) {
97
- server.middlewares.use(middleware)
98
- },
99
- buildStart() {
100
- // `emitFile` is only valid during `vite build`. In serve mode the
101
- // middleware above handles `/custom_blocks.json`, so we skip emission.
102
- if (command !== "build") {
103
- return
104
- }
105
- const contents = readManifest()
106
- if (contents === undefined) {
107
- return
108
- }
109
- this.emitFile({
110
- type: "asset",
111
- fileName: MANIFEST_FILENAME,
112
- source: contents,
113
- })
114
- },
115
- }
7
+ return { name: "custom-blocks:legacy-vite-plugin" }
116
8
  }
@@ -1,22 +0,0 @@
1
- # Vite plugin
2
-
3
- The `notionCustomBlock()` plugin from `@notionhq/custom-blocks/vite` wires a block's local dev server and build output to the SDK handshake.
4
-
5
- ```ts
6
- import { defineConfig } from "vite";
7
- import react from "@vitejs/plugin-react";
8
- import { notionCustomBlock } from "@notionhq/custom-blocks/vite";
9
-
10
- export default defineConfig({
11
- plugins: [react(), notionCustomBlock()],
12
- });
13
- ```
14
-
15
- It does two things:
16
-
17
- - **Forces `base: "./"`** (and rejects any other explicit value). Custom blocks are served from a content-addressed path, so asset URLs in the built bundle must be relative.
18
- - **Passes through `custom_blocks.json`** when the optional file exists in the project root: served by middleware in dev so HMR and the SDK handshake see the same file, and emitted into `dist/` as a separate asset on `vite build`.
19
-
20
- The `custom_blocks.json` manifest file only applies when a block is served from `localhost` during development instead of deployed as a worker. Worker-backed blocks declare their manifest with `worker.customBlock(...)` and should not maintain a `custom_blocks.json`, but they still use the plugin for the `base` enforcement. See [deployment.md](./deployment.md) for more information.
21
-
22
- The plugin does not validate the manifest. The SDK validates it on receipt.