Embedding Blocks

A guide for embedding applications

Overview

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.

Discovering blocks

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.

Building the block's properties

Applications need to provide blocks with:

  • the entity data that the block will allow users to visualize or edit,
  • links between entities, and
  • functions and ids to use when updating entities.

These are described in detail in the specification, and we draw out some key points below.

Providing data

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:

idname
456Acme

...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:

idnamecompanyId
41Bob456

...might become the following entity:

{
  "entityId": "41",
  "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.

Handling requests

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.

Rendering blocks

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.

Security

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.