A guide for embedding applications
This guide covers a few key points to consider when writing or updating an application to use blocks, and is a companion to the embedding application section of the block protocol specification.
You can also check out the source code of example embedding applications, such as HASH.
You can browse prototype blocks in the Block Hub right now. You can view their schema (the structure of data they accept), and see how they look given different data.
You can also generate an API key to search our API for available blocks:
https://blockprotocol.org/api/blocks?q=text_query
To authenticate with the API, pass your API key in an x-api-key
header.
It is also possible to search blocks by data structure: allowing you to provide a data object, and be told which blocks can display or edit it.
For example, if you provide { "name": "Bob", "email": "bob@test.com" }
in the json
query parameter, the API will suggest the
person block. It's preferable to URL encode the json object.
https://blockprotocol.org/api/blocks?json={"name":"Bob","email":"bob@test.com"}
URL encoded:
https://blockprotocol.org/api/blocks?json=%7B%20%22name%22%3A%20%22Bob%22,%20%22email%22%3A%20%22bob%40test.com%22%20%7D
Once your app implements the block protocol, your users can search and use blocks to work with a wide variety of data structures, without further effort on your part.
Both the q
and json
query parameters work together. You may provide either, both, or none.
Applications need to provide blocks with:
These are described in detail in the specification, and we draw out some key points below.
The block protocol deals with things called entities and separate things called links, which link entities together.
This data model is intended to be generic and extensible: to allow new entity types to be defined on the fly and linked to any other arbitrary entity, and to enable translation back and forth between a range of other data models.
You may need to normalize data to the expected format. Some points to consider:
1. Each entity should have an entityId
and an entityTypeId
, both of which must be strings.
These will be provided by blocks when requesting updates to an entity (see handling block actions).
For example, a row in a companies
table like the following:
id | name |
---|---|
456 | Acme |
...might become:
{
"entityId": "456",
"entityTypeId": "Company",
"name": "ACME"
}
2. Links between entities are represented as separate records, rather than as properties on an entity.
If you want blocks to allow users to visualize or edit links between entities, you should provide them to blocks.
This might mean translating links encoded on your records directly into separate objects.
If you don't have separate links in your own data model, you would ideally give the links you create a unique, stable id so that blocks can track them across time.
For example, a row in your users
table:
id | name | companyId |
---|---|---|
42 | Bob | 456 |
...might become the following entity:
{
"entityId": "42",
"entityTypeId": "User",
"name": "Bob"
}
...and the following link (assuming you didn't already have a linkId
and generate one):
{
"linkId": "user-42-company-456",
"sourceEntityId": "42",
"destinationEntityId": "456",
"path": "company"
}
Links should be provided to blocks under the linkGroups
field, as described in the spec.
Blocks request the creation or updating of entities and links, via the functions you provide.
Blocks will send requests to update entities with the entityId
and entityTypeId
you provide with them,
and a payload which represents the properties to be assigned to that entity.
As blocks should be portable, they are not tied to a particular embedding context, and you will need to provide functions which are a level of abstraction on top of your API or datastore.
For example, you may need to translate a call to createEntity
with an entityTypeId
of "Company"
,
to a POST
request to your /companies
endpoint.
Depending on your data model, you may also need to translate requests related to links.
For example, you may need to translate a call to createLink
with the following payload:
{
"sourceEntityId": "42",
"sourceEntityTypeId": "User",
"destinationEntityId": "789",
"destinationEntityTypeId": "Company"
}
...into a call to set companyId: "789"
on a user with id 42
, whether that's via a PUT
request to /users
,
a dedicated endpoint for assigning users to companies, or any other number of possible implementations.
Once you have discovered a block you want to use, and have the data in the form it needs, you need to put the two together on a webpage.
How exactly this is done will depend on your own application, and how the block is written.
A block will use the externals
field in its block-metadata.json
to indicate the key dependencies it
expects to be provided with (to keep block bundle size down), and this can also be used to infer how
it expects to be rendered.
For example, a block with react
in its externals, and a JavaScript entry point, is likely a React component,
and the source can be used to create a function which is then rendered like any other React component,
with the required functions and properties passed in as props
. Any dependencies it expects could be provided
by giving a requires
function when creating the function.
A block with an HTML entry point could be added to the page like any other HTML element. Any dependencies it expects could be attached to the scope of the document (we assume blocks are sandboxed).
We will be releasing source code in January 2022 which demonstrates how all this can be done.
If you want to know when the example application code is available, please register your interest.
You should take suitable precautions to ensure that block code is unable to compromise your systems or take unexpected action on behalf of your users.
We will be providing source code for an example application which demonstrates one potential approach to sandboxing block execution.