This document (version 0.2) specifies how to define and use "blocks" – discrete components which can be used across multiple applications.
It defines how "embedding applications" should render and communicate with blocks.
It defines how "modules" should be specified. Modules provide functionality by describing the messages blocks and applications may exchange.
You will see the following sub-headers within each section:
Block: a component defined according to this specification, designed to be used as part of an application.
Block instance: a specific example of a block rendered on a page by an embedding application.
Block package: a collection of files making a block available for use by embedding applications, including its source code and accompanying metadata.
Embedding application: any application that can take a block and insert it in a web page according to this specification.
Message: the unit of communication between blocks and embedding applications, transported according to this specification.
Module: a description of the purpose and content of messages sent between blocks and applications, and any additional required metadata, defined according to this specification.
At a minimum, blocks consist of:
We refer to the collection of files defining a block as a block package.
A block package might be made available via a URL, package manager (e.g. npm), or block catalog (e.g. the Þ Hub).
A block package MUST contain:
block-metadata.json
, so that it can be reliably identified as the entry point for the block's other filesblockType
: the type of block this is, so that embedding applications know how to render it – see Types of block).name:
a name for the block, in slug format. This will be expected to be unique in the package manager or catalog where the block package is hostedsource
: the path or URL to the entry point source file (e.g. index.html
, index.js
)version
: the version of the block in the package in question, which SHOULD use semantic versioningprotocol
: the version of this core specification the block complies withexternals
: any libraries the block expects the embedding application to supply it with, i.e. libraries which the block depends on but does not include in its package and expects to be available when it calls import "library"
. This MAY be omitted if the block does not have any such dependencies. If defined, this MUST be an array of objects where the key is the name of the library used when importing it and the value is the expected version (or version range). For example, "externals": [{ "react": "^18.0.0" }]
author
: a display name for the author of the blockdescription
: a brief description of the blockdisplayName
: a display name used for the blockicon
: a path to the icon for the block, to be displayed when the user is selecting from available blocks (and elsewhere as appropriate, e.g. in a website listing the block). This should be 1:1 width:height ratio, with no padding around the iconimage
: a path to the preview image of the block to help users visualize it before using it. This would ideally have a 3:2 width:height ratio and be a minimum of 900x600pxlicense
: the name of the license the block is made available under (e.g. MIT), or the URL of a licenserepository
: specifies the place where your block's code lives. This is helpful for people who want to explore the source, or contribute to your block's developmentexternals
: must not be specified when blockType.entryPoint
is specified as "html"
. Block Protocol does not currently support embedding applications injecting dependencies for HTML blocks. This is a technical limitation of ES Modules.As blocks can be defined in different ways, embedding applications need to be able to render blocks defined in different ways.
HTML blocks also require some special provisions in order to enable interaction with the embedding application.
The blockType
of the block in block-metadata.json
MUST define the type of entry point of the block, which MUST be an object, which:
entryPoint
key defining what exactly the block’s source is or exports, which MUST be one of:
"custom-element"
: a user-defined CustomElement, often referred to by the broader technology Web Component. This includes components written using libraries and exported as a custom element. Blocks of this kind MUST export the class as the default or single named export from their source
file, and MUST specify tagName
(see below)."html"
: an HTML file, whether a simple <p>
or a complicated block that loads various scripts. Blocks of this kind MUST have an .html
file as their source
."react"
: a React component. Blocks of this kind MUST export the component function as the default or single named export from their source
file.entryPoint
is "custom-element"
, MUST also specify a tagName
key, to specify the element tag name. This is important where the block’s code relies on the custom element being registered by the application using a specific tag.See loading blocks for more handling of different kinds of blocks.
Embedding applications are responsible for rendering blocks as part of their application, with different strategies depending on the type of block.
An embedding application MUST render blocks by:
Blocks MUST be loaded with a strategy appropriate to their declared entryPoint
, e.g.
custom-element
class MUST be used to define the element in the elementRegistry
, using the tag
name provided.react
component MUST be rendered EITHER by calling it inside another React component, or given its own React tree with ReactDOM.render
html
file MUST be rendered by attaching it to document, without breaking any <script>
tags within it (e.g. by using document.createRange().createContextualFragment(html)
rather than setting innerHtml
, within which scripts will not work). HTML files require additional provisions as described immediately below.The embedding application MUST pass the data of "initResponse"
to blocks as properties, where blocks support being passed or assigned properties, so that the data within it is made available synchronously and immediately to blocks.
Blocks of kind "html"
require special handling in order that they can identify elements within them – since they may share their scope with other blocks, including identical blocks, they cannot rely on finding elements by id or other selectors.
The @blockprotocol/core
package exports for embedding applications' convenience a renderHtmlBlock
function which handles both assigning an implementation of the blockprotocol
API described below to the global scope, and rendering the block within a provided node.
If an embedding application wishes to handle rendering separately, the package also exports (which renderHtmlBlock
makes use of):
assignBlockProtocolGlobals
function, which simply assigns an implementation of the blockprotocol
API described below to the global scope. The package also exports a corresponding teardownBlockProtocol
function to reset this.markBlockScripts
function. Embedding applications MUST call this when rendering a block if they wish to use the implementation of the blockprotocol
API provided by assignBlockProtocolGlobals
, as this ensures those APIs know which block is calling them.Embedding applications which load ANY block of kind: "html"
MUST provide a blockprotocol
object on the global scope (i.e. globalThis.blockprotocol
/ window.blockprotocol
), which:
getBlockContainer
key where the value is a function, which the HTML block can call to obtain a reference to the container element for that specific block:
markScript
).getBlockUrl
key where the value is a function, which the HTML block can call to obtain the URL at which the HTML source of the block is hosted:
markScript
key where the value is a function, which is used to attach ids to dynamically script tags so that they can be identified when calling getBlockContainer
Blocks of kind: "html"
MUST call getBlockContainer
from within any script they load, with different strategies depending on whether the script is:
<script>
tag) or remote (a file loaded via the src
property of a <script>
tag)type="module"
or notgetBlockContainer -> HTMLElement |
Inline | Remote |
---|---|---|
Module | js getBlockContainer() |
js getBlockContainer( import.meta.url ) |
Script | js getBlockContainer( document.currentScript ) |
js getBlockContainer( document.currentScript ) |
Blocks of kind: "html"
MAY call getBlockUrl
from within any script they load, with different strategies depending on whether the script is:
<script>
tag) or remote (a file loaded via the src
property of a <script>
tag)type="module"
or notgetBlockUrl -> HTMLElement |
Inline | Remote |
---|---|---|
Module | js getBlockUrl() |
js getBlockUrl( import.meta.url ) |
Script | js getBlockUrl( document.currentScript ) |
js getBlockUrl( document.currentScript ) |
If blocks generate any scripts dynamically at runtime, they MUST call markScript
in order to have the script marked by the embedding application. As with static scripts, they MUST call getBlockContainer
within any dynamically generated script in order to obtain a reference to the element containing the block.
markScript |
Inline | Remote |
---|---|---|
Module | js markScript(script) |
js markScript( script, import.meta.url ) |
Script | js markScript( script, document.currentScript ) |
js markScript( script, document.currentScript ) |
Embedding applications MUST update any relative static imports (or script tag src attributes) contained within a block's HTML source to ensure the URLs are relative to the block's source, and not the page the HTML is hosted on. The @blockprotocol/core
package exports markBlockScripts
which can be used to do this for scripts (not e.g. an img
with a relative src
).
An example HTML block is available via npx create-block-app@latest your-block-name --template html
Communication between blocks and embedding application happens via messages transported as DOM events.
As blocks may not sandboxed, messages to or from them must be scoped to their location in the document, so that their origin can be identified.
Blocks:
CustomEvent
named "blockprotocolmessage"
, where the event matches the structure described in message content"blockprotocolmessage"
sent from the embedder on the SAME ELEMENT from which they dispatch eventsEmbedding applications:
"blockprotocolmessage"
sent from the block on an element higher in the DOM tree than the block.CustomEvent
on the SAME element the block dispatches messages from – identified via listening for the initial "init"
message and subsequently dispatching on the target of that initial event.The basic structure of Block Protocol messages is defined by this specification, and involves data
and possibly any errors
encountered in producing the message.
The content of data
and the permissible codes under errors
are left to module specifications to define.
Messages MUST be transported via a CustomEvent
which
type: "blockprotocolmessage"
detail
object which:
requestId
, which MUST be a uuid
requestId
of the original messagerequestId
module
this message relates to (or "core"
for messages defined in this document)name
of the message, as defined in the relevant specificationsource
, which MUST be one of "block"
or "embedder"
data
and errors
data
may be any type of JSON value (string, object, null, etc)errors
MUST be an array of objects, each of which MUST have a code
and message
– both strings – and MAY have extensions
, an arbitrary JSON object defined by the module specification. Valid error codes are defined per message, in an errorCodes
property in the message definitionMessages MUST therefore be an object which conforms to the following schema:
{
"type": "object",
"properties": {
"type": {
"const": "blockprotocolmessage"
},
"detail": {
"type": "object",
"properties": {
"requestId": {
"type": "string",
"format": "uuid"
},
"name": {
"type": "string"
},
"module": {
"type": "string"
},
"source": {
"type": "string",
"enum": ["block", "embedder"]
},
"data": {},
"errors": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": { "type": "string" },
"message": { "type": "string" },
"extensions": { "type": "object" }
},
"required": ["code", "message"]
}
}
},
"required": ["requestId", "name", "module", "source"]
}
},
"required": ["detail", "type"]
}
The data
and errors
of any messages exchanged between applications and blocks, and not described in Core messages, MUST be described in separate module specifications.
The core specification defines messages which are related to the core functionality of the Block Protocol. There are currently two such messages:
"init"
: a message a block sends on load and whenever it changes the element it is dispatching from (e.g. destroys the previous one)"initResponse"
: a message the embedder sends in responseThese messages are how blocks may exchange messages which are sentOnInitialization
, which might be values available immediately on load, or configuration information, or anything else the module defines as useful for blocks and embedders to exchange on initialization.
init
Immediately on loading, blocks MUST send a message of the following shape:
{
"type": "blockprotocolmessage",
"detail": {
"requestId": "<block-generated-uuid>",
"module": "core",
"name": "init",
"data": {}
}
}
Blocks MUST listen for subsequent message events dispatched by the embedder on the element they dispatched this message from.
If the block removes this element from the DOM, the block MUST send another init
message event from a new element,
and listen to subsequent messages from the embedder on that element.
Embedding applications MUST listen for the init
message event being dispatched from the block, by attaching an event listener to an element wrapping or at the root of the block.
Embedding applications MUST dispatch ALL events intended for the block on the init
message event’s target
element, including the "initResponse"
message described immediately below. It MUST update the element being listened on if a new init
message is received.
initResponse
When an "init"
message is received, embedding applications MUST send a message of the following shape:
{
"type": "blockprotocolmessage",
"detail": {
"requestId": "<requestId-from-initRequest>",
"module": "core",
"name": "initResponse",
"data": {}
}
}
Initialization data
The data
for both "init"
and "initResponse"
MUST be an object, where
name
of messages
for that module which are marked as sentOnInitialization
, with the data as described in the relevant module specificationFor example:
{
"type": "blockprotocolmessage",
"detail": {
"requestId": "abcd-1234-efg0-5678",
"module": "core",
"name": "initResponse",
"data": {
"graph": {
"blockEntity": {
"//": "the block entity"
}
}
}
}
}
A module aims to provide a logical grouping of functionality and/or solve a specific problem related to block-application interaction.
A module definition must set out the messages that a block and application will exchange as part of the module.
Messages should be specified in a JSON file. The JSON file must conform to the constraints defined by the Module Specification JSON Schema. To aid understanding, the constraints defined by the JSON schema are laid out in this section (definition), and a section related to messages.
Modules may also include an additional markdown file which provides further explanation and narrative, and imposes any additional requirements not captured via messages. This may, for example, specify additional metadata related to the module that can be expressed as part of a block package, whether in block-metadata.json
or in an additional file or files.
Each module:
The JSON schema requires that a module specification is a JSON object which
name
description
messages
version
coreVersion
If there are any discrepancies between the requirements listed above and the requirements in the JSON schema, the JSON schema takes precedence.
Messages are the means by which blocks and applications communicate.
The messages a module defines should be relevant to the delivery of the module.
Messages are listed under in the messages
key of the JSON file which defines the module.
A module specification SHOULD include any messages to be sent as part of the module under a messages
key on the JSON file defining the module.
The value of messages
should be an array of JSON objects.
An entry in messages
MUST specify:
messageName
for the requestdescription
source
: one of block
or embedder
data
An entry in messages
MAY specify:
respondedToBy
: a string
, which names another entry in messages
. The presence of this indicates this message is part of a request/response pairsentOnInitialization
: whether this message will be sent immediately when the block is initialized, in response to the "initRequest"
message (and provided as properties synchronously to blocks which support it). Note that such messages may also be sent after intialization, e.g. if their data changeserrorCodes
: valid codes for any
errors encountered in producing the message data. this may appear alongside or
instead of data
in the message contents.{
"$id": "https://blockprotocol.org/types/core/0.3/schema/module-meta",
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "A short description of the functionality the module provides and/or the problems it aims to solve. Markdown is permitted."
},
"name": {
"type": "string",
"description": "A unique name for the module, consisting of lowercase letters, numbers, and hyphens only.",
"pattern": "^[-a-z0-9]$"
},
"messages": {
"type": "array",
"description": "The messages that may be exchanged by blocks and embedding applications as part of the module",
"items": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"messageName": {
"type": "string"
},
"source": {
"type": "string",
"enum": ["block", "embedder"]
},
"data": {
"type": "object"
},
"errorCodes": {
"type": "array",
"items": {
"type": "string"
},
"sentOnInitialization": {
"type": "boolean"
},
"respondedToBy": {
"type": "string"
}
},
"required": ["description", "messageName", "source", "data"]
}
},
"version": {
"type": "string",
"description": "The semantic version of this module specification"
},
"coreVersion": {
"type": "string",
"description": "The version (or version range) of the core specification this is compatible with"
}
},
"required": ["description", "messages", "name", "version"]
}
}
Previous
Next
Anyone with an existing application who wants to embed semantically-rich, reusable blocks in their product can use the protocol. Improve your app’s utility and tap into a world of structured data with no extra effort, for free.
Any developer can build and publish blocks to the Hub for others to use. Contribute to an open-source community building a more interoperable future web.