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.
npx create-block-app@latest your-block-name
.cd [your-block-name]
.yarn install && yarn dev
or npm install && npm run dev
.By default, create-block-app
uses React. If you want to write blocks using another technology or framework, we provide template options for you to choose from which allow you to define the entry point for your block in different ways. To change the template, pass a parameter to the above command as follows: npx create-block-app@latest your-block-name --template <TEMPLATE>
.
Currently the three templates are:
react
- the default template which uses React and has a suite of helpful hooks you can use.custom-element
- create a block defined as a custom element (also known as Web Components), which is based on Lit. You can also use different approaches when constructing the element-i.e. use a custom element as a wrapper for anything else you like.html
- plain HTML and JavaScript. Import and use additional libraries inside the <script>
tag.You may also use any other language which can be transpiled to JavaScript. As an example, see this blog post on writing a block using F#. This block uses React – you can use transpiled JavaScript in blocks defined as HTML files or custom elements too.
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.
The create-block-app
package and provides everything you need to develop a block. This section assumes you have run npx create-block-app@latest your-block-name
and are in the resulting folder. For the HTML template, the folder structure is slightly different. The development server is in a dev/
folder and the block entry point is app.html
.
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 (except for the HTML template):
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 nameimage
: 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.example-graph.json
file using the ./src/example-graph.ts
script if it exists, which provides a sample state of a datastore which can be used when demonstrating the use of your graph.yarn build
simply copies your block source, example-graph.json
, and block-metadata.json
file to a dist/
folder.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:
html
template, update the schema
property in the block-metadata.json
react
and custom-element
template, update the blockEntityType
property in the blockprotocol
object in package.json
yarn codegen
. See the relevant section in working with types for more information.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.blockEntityType
in package.json
(or schema
in block-metadata.json
) defines the properties expected for the block entity.react
and custom-element
blocks generate TypeScript types from this entity type, using the configuration set in the codegen
object in package.json
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 element of the graph which was encountered when starting from the roots and following the specified edges
to the specified depths
.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
and entityTypeId
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/@blockprotocol/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/@blockprotocol/types/property-type/name/": "Bob",
});
Because properties are identified by URLs, you may wish to alias them in your code if used in multiple places. For example:
const nameKey =
"https://blockprotocol.org/@blockprotocol/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 URL 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/@blockprotocol/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, in react
and custom-element
blocks, the entityTypeId
of the relevant relationship will then be available in the generated types (after running yarn codegen
)/
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:
If you know of a specific entity that you want to retrieve more detailed information on, you can call getEntity
with its entityId
.
When doing this, you can provide how much of the graph you want to explore around the entity, by providing graphResolveDepths
.
For example, to retrieve the subgraph rooted at the entity, with its links and their destinations:
const getEntityResponse = await graphModule.getEntity({
data: {
entityId: someEntityId,
graphResolveDepths: {
hasLeftEntity: {
incoming: 1,
outgoing: 0,
},
hasRightEntity: {
incoming: 0,
outgoing: 1,
},
},
},
});
if (getEntityResponse.errors) {
// handle errors
}
const { data: entitySubgraph } = getEntityResponse;
Here graphResolveDepths
specifies two edge kinds,
hasLeftEntity
- the edge that appears between a link entity and its left entity (which can be thought of as the 'source' of the link)hasRightEntity
- the edge that appears between a link entity and its right entity (which can be thought of as the 'destination' of the link)The process of exploring a graph is known as 'traversal'. This involves visiting a vertex (an element in the graph – in our case, an entity), and then following edges from it (connections to other vertices) based on the parameters of the query. The parameters specify which kinds of edges to follow, and how many times they should be followed for any given journey through the graph. Each vertex visited might have multiple edges that can be followed from it, which means that each query actually involves many different journeys through the graph, which we refer to as 'branches' of the traversal.
For each edge kind, we specify the incoming
and outgoing
depths to resolve, where:
hasLeftEntity
outgoing
means there is an outgoing edge from the left entity to the link entity, and defines the number of times to explore the left entity of link entities in a given branch of traversal.incoming
means there is an incoming edge from the link entity to the left entity, which can be thought of as an "outgoing link entity" to the entity being traversed. The depth defines the number of times to explore outgoing link entities of entities in a given branch of traversal.hasRightEntity
outgoing
means there is an outgoing edge from the right entity to the link entity, and defines the number of times to explore the right entity of link entities in a given branch of traversal.incoming
means there is an incoming edge from the link entity to the right entity, which can be thought of as an "incoming link entity" to the entity being traversed. The depth defines the number of times to explore incoming link entities of entities in a given branch of traversal.Therefore, the parameters in the example mean:
(hasLeftEntity, incoming, 1)
- explore outgoing link entities to a depth of 1(hasLeftEntity, outgoing, 0)
- do not explore the left entity of link entities(hasRightEntity, incoming, 0)
- do not explore incoming link entities(hasRightEntity, outgoing, 1)
- explore the right entity of link entities to a depth of 1However, it's also possible to query across all entities in the datastore.
The queryEntities
message allows you submit a query with a filter.
You can use this to browse the available entities in order to display them, or create links between them.
To retrieve the subgraph rooted at all entities with a property https://blockprotocol.org/types/@blockprotocol/property-type/name/
with a value of "Alice"
or "Bob"
:
const nameIsFilter = (value) => ({
field: [
"properties",
"https://blockprotocol.org/types/@blockprotocol/property-type/name/",
],
operator: "EQUALS",
value,
});
const queryEntitiesResponse = await graphModule.queryEntities({
data: {
operation: {
multiFilter: {
filters: [nameIsFilter("Alice"), nameIsFilter("Bob")],
operator: "OR",
},
},
},
});
if (queryEntitiesResponse.errors) {
// handle errors
}
const { data: subgraph } = queryEntitiesResponse;
const entities = getRoots(subgraph);
Here we use the getRoots
method from @blockprotocol/graph/stdlib
to retrieve the entities at the roots of the subgraph.
To retrieve the subgraph rooted at all entities with an entity type with base URL https://blockprotocol.org/types/@blockprotocol/entity-type/person/
:
const entityTypeBaseUrlEqualsFilter = (value) => ({
field: ["metadata", "entityTypeId"],
operator: "EQUALS",
value,
});
const queryEntitiesResponse = await graphModule.queryEntities({
data: {
operation: {
multiFilter: {
filters: [
entityTypeBaseUrlEqualsFilter(
"https://blockprotocol.org/types/@blockprotocol/entity-type/person/",
),
],
operator: "AND",
},
},
},
});
if (queryEntitiesResponse.errors) {
// handle errors
}
const { data: subgraph } = queryEntitiesResponse;
const entities = getRoots(subgraph);
Note, that we could have provided a graphResolveDepths
parameter similar to the getEntity
query example above, but have opted to not do so here which will default to a depth of 0 for all edge kinds, so the Subgraph
will only return the roots.
More information on building filters can be found within the documentation of the queryEntities
message in the specification.
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
, and image
for your block.
These will automatically be included in the block-metadata.json
produced after running yarn build
or npm run build
.
In addition to the above, updating the example-graph
(either ./src/example-graph.ts
or ./example-graph.json
depending on approach) is a great way to help users understand how to use your block, and how it functions. Many environments (such as the Þ Hub) will insert this data when users are viewing blocks.
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. This provides you with an instant online public playground in which to demo your block, and allows other users of the Block Protocol to discover it, or embed it through any Þ application.
To publish a block on the Þ Hub take the following steps.
yarn build
or npm run build
to create a production build of your block (it will appear in the dist
folder)npx blockprotocol@latest publish
to generate a .blockprotocolrc
file when promptednpx blockprotocol@latest publish
againYou can update your published block at any time by running
yarn build && npx blockprotocol@latest publish
or npm run build && npx blockprotocol@latest publish
In order to have your block enter the queue to be reviewed for verification status, please ensure the repository
field in your package.json
points to a public repository containing your code, and add a commit
field to the
blockprotocol
object within package.json
that specifies the commit hash the version you are publishing was built from.
Read more about verification in the FAQ.
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 defaultexample-graph
(either ./src/example-graph.ts
or ./example-graph.json
depending on approach) to update the data the block has available to it when a user loads it on the Þ HubPrevious
Next