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.

Block API

You can also generate an API key to search our API for available blocks.

The API endpoint is https://blockprotocol.org/api/blocks.

You must authenticate with the API by passing your API key in an x-api-key header.

For example, to retrieve a list of all blocks:

fetch("https://blockprotocol.org/api/blocks", {
  headers: {
    "content-type": "application/json",
    "x-api-key": "YOUR_API_KEY",
  },
})
  .then((resp) => resp.json())
  .then(console.log);

The following query parameters are available. All are optional, and may be combined to narrow results by a combination of filters:

  • author: search for blocks with a matching author.
  • license: search for blocks with a matching license.
  • name: search for blocks with a matching name.
  • q: search for blocks with a matching name or author.
  • json: filters blocks by compatability with a given data object.

The json query parameter searches blocks by data structure: allowing you to provide a data object (as a string, preferably URL encoded), and retrieve a list of blocks that 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.

Full example:

const dataObject = { name: "Bob", email: "bob@test.com" };

const stringifiedObject = JSON.stringify(dataObject);

const encodedString = encodeURIComponent(stringifiedObject);

fetch(`https://blockprotocol.org/api/blocks?json=${encodedString}`, {
  headers: {
    "content-type": "application/json",
    "x-api-key": "YOUR_API_KEY",
  },
})
  .then((resp) => resp.json())
  .then(console.log);

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.

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:

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

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.

We're hiring full-stack TypeScript/React and PHP plugin developers to help grow theBlock ProtocolWordPress ecosystem.Learn more