Blocks are discrete components that allow users to view, explore, or edit data.
The Block Protocol defines a standard for communication between blocks and the applications that embed them.
The protocol is split into a core specification setting out how applications and blocks communicate, and module specifications defining what applications and blocks communicate.
This guide helps get you set up and introduces some of the key features of the graph module specification, which deals with creating, reading and updating data records (or “entities”).
In practice, most block developers will not need to know the lower-level details of the specifications, as the libraries we provide implement them.
We provide three templates which allow you to define the entry point for your block in different ways:
custom-element
: create a block defined as a custom element (also known as Web Components).html
: create a block defined as an HTML file, with JavaScript added via <script>
tags.react
: create a block defined as a React component.To create a new block, run npx create-block-app@canary block-name --template template@canary
, replacing block-name
with the name to give your block, and template
with one of the template names listed above (keep the @canary on the end!)
If you want to write blocks using other technologies or frameworks, you have several options:
custom-element
template and use different approaches when constructing the element-i.e. use a custom element as a wrapper for anything else you like.html
template and import and use your additional libraries inside the <script>
tag.You can write your block in regular JavaScript using the methods described above - just rename your files from *.tsx/*.ts
to *.jsx/*.js
, remove the types, and get coding.
npx create-block-app@canary your-block-name --template react@canary
(or --template custom-element@canary
).cd [your-block-name]
.yarn install && yarn dev
or npm install && npm run dev
.The create-block-app
package and provides everything you need to develop a block.
src/app.tsx
or src/app.ts
contains your block’s code.
peerDependencies
in package.json
.yarn dev
or npm run dev
will run your block in development mode, serving it locally with hot reloading at http://localhost:63212.
src/dev.tsx
to render your block within a mock embedding application called MockBlockDock
.debug
from MockBlockDock
to turn this off, or toggle it via the provided switch in the UI.yarn build
or npm run build
will:
peerDependencies
).block-metadata.json
file which:
source
file.package.json
, such as the description.blockprotocol
object in package.json
, e.g.
blockType
: the type of block this is.displayName
: a friendly display nameexamples
: an array of example data structures your block would accept and useimage
: a preview image showing your block in action (in place of public/block-preview.png
)icon
: an icon to be associated with your block (in place of public/omega.svg
)name
: a slugified name for your block (which may differ to the package name
in package.json); it can be defined as blockname
or @namespace/blockname
, where namespace
must be your username on blockprotocol.org if you intend to publish it thereexternals
, which are generated from peerDependencies
in package.json.A key part of the Block Protocol is the use of types to describe the data your block will work with.
Your block should be associated with an “entity type” which will be used by embedding applications to understand what sorts of entities can be sent to it (e.g. what properties do they have?).
When an embedding application loads your block, it should send an entity which complies with the structure of the block's declared entity type. We call such an entity the 'block entity'.
See working with types for more information on the type system, or jump straight to your dashboard to create a type.
Once you have created the type representing the data your block needs, copy its URL, and update the schema
property
in the blockprotocol
object in package.json
. In TypeScript block templates, you can then run yarn schema
to
regenerate the types for your block.
When a block is loaded into an embedding application:
The embedding application parses its block-metadata.json
file and:
blockType
.The block then receives any data which the embedder can provide straight away, for example as part of the graph module:
custom-element
and react
-type blocks will be sent initial data as properties/props.html
-type blocks will be sent messages containing the initial data.At any time after this initialization, the block may send further messages via a Module
for specific purposes,
such as reading and writing data within the embedding application.
The starter blocks created by create-block-app
implement a simple example of this:
Thing
entity type referred to in blockprotocol.schema
in package.json
, for which types are found in src/types.gen.ts
,
defines the properties expected for the block entity.MockBlockDock
in dev.tsx
, including the properties
it expectsblockEntitySubgraph
(described below):react
and custom-element
blocks extract the block entity from the blockEntitySubgraph
html
block registers a callback for the blockEntitySubgraph
message.Hello, World!
message.The Graph Module describes how entities can be created, queried, updated, and linked together, including the block entity. It enables your block to create, read, update, and delete data in the embedding application.
The Graph Module is available via the graphModule
property in each starter template. It has a number of methods corresponding to the messages defined in the specification.
Using these methods in combination, you can create complex graphs from within a block without having to know anything about the implementation details of the application embedding it.
Each message payload is the same: an object containing data
and errors
keys.
graph
properties objectThe graph
properties object is sent in properties
/props
for custom-element
and react
-type blocks, and as a message
for html
-type blocks.
It contains data sent from the embedding application to the block related to the graph module. Importantly:
readonly
: a boolean indicating whether the block is in a read-only context. This typically means that the embedding application
will reject any requests to update data, and the block should alter its UI and behaviour accordingly.
blockEntitySubgraph
: this contains the 'block entity' and any entities immediately linked to it. It is a graph of entities
rooted at the block entity.
We provide helper tools for extracting the key information from blockEntitySubgraph
:
react
template, a useEntitySubgraph
hook can return the rootEntity
(the block entity) and any linkedEntities
custom-element
template, this.getBlockEntity()
and this.getLinkedEntities()
are availablehtml
template (and everywhere), you can use functions available in @blockprotocol/graph/stdlib
, for example:
getRoots
to get the roots from a subgraph (for a blockEntitySubgraph
, there should only be one)getOutgoingLinkAndTargetEntities
to get the entities linked from a given entity (the 'target' entities), and the links themselves
(N.B this is equivalent to linkedEntities
in the other templates, which are for outgoing links from the block entity onl)getIncomingLinkAndTargetEntities
to get the entities linking to a given entity, and the linksMany of the messages sent from the application to the block as part of the graph module return a Subgraph
.
You shouldn't have to worry about the internal workings of a Subgraph
, but it is worth knowing that a given subgraph
represents the result of a query starting with a given entity (or entities) and following links from it to other entities.
The links are also entities (they may have properties and relationships of their own), known as 'link entities'.
The four components of a Subgraph
are:
roots
: the entities which were the starting point of the query (e.g. only the 'block entity' in the case of blockEntitySubgraph
)depths
: which edges
were followed from the roots
when resolving the query, and how faredges
: connections between things in the graph. For example, a 'link entity' connects it and two other entities via hasRightEntity
and hasLeftEntity
edges (conceptually, the link entity is in the middle with an entity on its left and on its right)vertices
: each thing (vertex
, or node
) which was encountered when starting from the roots and following the specified edges to the specified
depths`.A Subgraph
may also be rooted at types, and it may also contain types in its vertices, as well as have edges
which are type-related connections between vertices – but when writing a block you will mostly be working with entities.
Requesting types can be important for validating input when the type of data is not known ahead of time (e.g. for a generic table block).
If you know that your block accepts or generates a specific data structure, you already know its type – but by defining its type,
you are helping applications and end-users of your block choose what data can be sent to it.
Again, you probably don't need to worry about this when getting started – but if you start to work with complex data networks
made up of many entities with different relationships, the Subgraph
and knowing how to query it becomes a powerful tool.
A common use for the Graph Module is to update the block entity – to update the properties that are sent to the block.
Each block template includes a demonstration of calling graphModule.updateEntity
to update the block entity.
To do this, you need to call updateEntity
using the entityId
of the blockEntity
:
// Update the block entity, and receive the updated entity in return
const { data, errors } = await graphModule.updateEntity({
data: {
entityId: blockEntity.metadata.recordId.entityId,
entityTypeId: blockEntity.metadata.entityTypeId,
properties: {
"https://blockprotocol.org/types/property-type/name/": "Bob",
},
},
});
How you get a reference to blockEntity
depends on the type of block, as described above and demonstrated in each template.
As soon as the updateEntity
call is processed, for react
and custom-element
blocks your block will be re-rendered with
the updated properties. You could therefore omit the { data, errors }
from the above snippet and rely on the updated properties
when the block is re-rendered.
If you’re using the custom-element
template, you have a helper method to achieve the above:
this.updateSelfProperties({
"https://blockprotocol.org/types/property-type/name/": "Bob",
});
Because properties are identified by URIs, you may wish to alias them in your code if used in multiple places. For example:
const nameKey = "https://blockprotocol.org/types/property-type/name/";
if (blockEntity.properties[nameKey] !== "Bob") {
await graphModule.updateEntity({
data: {
entityId: blockEntity.metadata.recordId.entityId,
entityTypeId: blockEntity.metadata.entityTypeId,
properties: { [nameKey]: "Bob" },
},
});
}
You can read more about how types are described on the working with types page.
You can create new entities using the createEntity
method.
New entities should be assigned starting properties
and an entityTypeId
(a URI pointing to their entity type).
Because links between entities are just a special kind of entity, you call createEntity
to create them,
specifying additional linkData
to indicate which entities are on the 'left' and 'right' of the link.
For now you can think of the 'left' entity as the source of the link, and the 'right' entity as the target or destination.
For example, to link an entity to the block entity:
// link the 'blockEntity' to some 'otherEntity' you have a reference to (e.g. if you have newly created it)
graphModule.createEntity({
data: {
entityTypeId: "https://blockprotocol.org/types/entity-type/friend/v/1",
linkData: {
leftEntity: blockEntity.metadata.recordId.entityId,
destinationEntityId: otherEntity.metadata.recordId.entityId,
},
properties: {}, // this can contain metadata about the link, if you wish
},
});
You can define the type of relationships between your block entity and other entities when defining its type,
and the entityTypeId
of the relevant relationship will then be available in the generated types (after running yarn schema
)/
Any entities linked directly from the block will appear in the blockEntitySubgraph
property.
You can also link other entities together, but whether or not they appear in blockEntitySubgraph
will depend on
whether they are connected to the block entity at all, and far away they are (what depths
are required to reach them).
There are messages for exploring the data available in the embedding application:
aggregateEntities
allows you to request a list of available entities.You can browse the available entities rder to display them, or create links between them.
If you want to retrieve more detailed information on a specific entity, you can call getEntity
.
If you are using TypeScript, the types for methods available on graphModule
(as defined in the @blockprotocol/graph
package) should help you understand what methods are available and how they operate.
Once you’ve finished writing your block, run yarn build
or npm run build
.
This will produce a compiled version of your code in the dist
folder, along with a metadata file describing your block (block-metadata.json
).
It is worth updating the blockprotocol
object in package.json
to include your own icon
, image
, and examples
for your block. These will automatically be included in the block-metadata.json
produced after running yarn build
or npm run build
.
You now have a block package that you can provide to apps to use, by publishing it on the Hub.
Once you've built a block, you can add it to the Hub, so that your block will have an instant online demo playground, and will be searchable via our block API.
To publish a block on the Hub take the following steps.
npm run build
or yarn build
to create a production build of your block (it will appear in the dist
folder)npx blockprotocol@canary publish
to generate a .blockprotocolrc
file when promptednpx blockprotocol@canary publish
againYou can update your published block at any time by running
npm run build && npx blockprotocol@canary publish
or yarn build && npx blockprotocol@canary publish
public/block-preview.png
description
in package.jsonpublic
folder and update blockprotocol.icon
in package.json
image
to the public
folder and update blockprotocol.image
in package.json
README.md
– it will appear below your block on its hub page if you change it from the defaultblockprotocol.examples
in package.json
Previous