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 service specifications defining what applications and blocks communicate.
This guide helps get you set up and introduces some of the key features of the graph service 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@latest block-name --template template
, replacing block-name
with the name to give your block, and template
with one of the template names listed above.
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.
Bear in mind that a block schema will not be automatically generated as part of the build process – you can write your own block-schema.json
file at the root of your block’s folder, giving the type of props your App component accepts in JSON Schema format.
npx create-block-app@latest your-block-name --template template
.cd [your-block-name]
.yarn install && yarn dev
or npm install && npm run dev
.The create-block-app
package 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.yarn build
or npm run build
will:
peerDependencies
).BlockEntityProperties
type representing the data interface with the block. If your block folder contains block-schema.json
, this custom schema will be used instead.block-metadata.json
file which:
schema
and source
files.package.json
, such as the block name and description.blockprotocol
object in package.json
, e.g.
blockType
: the type of block this is.displayName
: a friendly display name.examples
: an array of example data structures your block would accept and use.image
: a preview image showing your block in action.icon
: an icon to be associated with your block.externals
, which are generated from peerDependencies
in package.json.When a block is loaded into an embedding application:
block-metadata.json
file and:blockType
.custom-element
and react
-type blocks will be sent this initial data as properties.html
-type blocks will be sent messages containing the initial data.Service
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 lifecycle:
BlockEntityProperties
type in src/app.tsx
defines the properties expected for the blockEntity
(part of the Graph Service).blockEntity
are passed to MockBlockDock
in dev.tsx
, including the properties
it expects (in this case, a name
of type string
).blockEntity
:react
and custom-element
blocks receive the blockEntity
as part of the graph
object in their properties.html
block registers a callback for the blockEntity
message.blockEntity
and uses the name
property to render its Hello, World!
message.The Graph Service 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 Service is available via the graphService
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.
Depending on the template, you may need to check that the graphService
is available before using it (we will be removing the need for this shortly). These checks are omitted below.
blockEntity
A common use for the Graph Service is to update the blockEntity
to update the properties that are sent to the block (including when it is loaded again in the future).
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 graphService.updateEntity({
data: {
entityId: blockEntity.entityId,
properties: { name: "Bob" },
},
});
You can get the blockEntity
from properties for custom-element
and react
-type blocks, and from a message callback for html
-type blocks. There are examples in the templates.
As soon as the updateEntity
call is processed, your block will be re-rendered with the updated properties, or in the case of html
blocks, the blockEntity
message re-sent with the updated data. You could therefore omit the { data, errors }
from the above snippet and rely on the updated properties or message.
If you’re using the custom-element
template, you have a helper method to achieve the above:
this.updateSelf({ name: "Bob" });
There are messages for exploring the data available in the embedding application:
aggregateEntities
allows you to request a list of available entities.aggregateEntityTypes
allows you to request a list of available entity *types-*entities typically belong to a type, which describes their expected structure.You can browse the available entities and types in order to display them, or create links between them.
blockEntity
To link other entities to the block, call createLink
with the relevant source and destination:
graphService?.createLink({
data: {
sourceEntityId: blockEntity.entityId,
destinationEntityId: "person-1",
path: "friend",
},
});
Any entities linked to the block will appear in the linkedEntities
property, with the links themselves appearing in linkGroups
. You can also link other arbitrary entities together.
If you are using TypeScript, the types for methods available on graphService
(as defined in the @blockprotocol/graph
package) should help you understand what methods are available and how they operate.
You can also review the messages defined in the graph specification here. If you do so, bear in mind the following:
source: "block"
are available as methods to your block.sentOnInitialization
with source: "embeder"
will be passed to your block as soon as it’s loaded. Remember that custom-element
and react
-type blocks receive these messages as properties under a graph
object, whereas html
blocks can register callbacks for each message.respondedToBy
, it expects a response and the graphService
method will return a Promise which will resolve with the { data, errors }
object. If a message does not expect a response, the method will return void.HASH is an open-source, data-centric, all-in-one workspace built atop the Block Protocol. While it is not yet ready for production use, you can use it to test your block in a real embedding application with example data.
To load your block into HASH:
yarn dev
or npm run dev
. This will serve your block locally at a localhost URL in development mode and will reload if you make changes to your source code. Alternatively, you can run yarn build && yarn serve
to test a production build of your block which will not automatically rebuild if you make changes to the source code.If you’re running your block in dev mode (yarn dev
or npm run dev
) and you make changes to the source code, your block will rebuild and HASH should automatically refresh the page. If you’re running your block in production mode, you will need to run yarn build
from your block folder and reload your block manually in HASH by repeating steps 5-6 again.
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 metadata files describing your block (block-metadata.json
), and the data it accepts (block-schema.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:
To publish a block on the Hub, the initial process is as follows:
blockprotocol
repository@
symbol (e.g. @myusername
)The JSON metadata file should look like this:
{
// REQUIRED - path to the repository with your block's source code
repository: "https://github.com/hashintel/hash.git",
// REQUIRED - commit hash (pins block to a specific version)
commit: "adb915bf8fc4f84e33a2cd21f217225e50c3d7fa",
// IF NEEDED - see below
distDir: "dist",
folder: "blocks/block-embed",
workspace: "@hashintel/block-embed",
}
workspace
folder
pathdistDir
where the build artifacts will appearNote
We assume that running yarn install && yarn build
will build your block (after switching to the relevant folder/workspace, if provided).
The specified distDir
will be relative to the workspace directory or folder, if provided.
Previous
Next