Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/automattic/vip-block-data-api

WordPress plugin that provides an API to retrieve Gutenberg content as structured JSON.
https://github.com/automattic/vip-block-data-api

wordpress wordpress-plugin wpvip-plugin

Last synced: 6 days ago
JSON representation

WordPress plugin that provides an API to retrieve Gutenberg content as structured JSON.

Awesome Lists containing this project

README

        

# VIP Block Data API



VIP Block Data API attribute sourcing animation

The Block Data API is an API for retrieving block editor posts structured as JSON data, with integrations for both the official WordPress REST API and WPGraphQL. While primarily designed for use in decoupled WordPress, the Block Data API can be used anywhere you want to represent block markup as structured data.

This plugin is currently developed for use on WordPress sites hosted on the VIP Platform.

## Table of contents

- [Installation](#installation)
- [Install on WordPress VIP](#install-on-wordpress-vip)
- [Install via ZIP file](#install-via-zip-file)
- [Plugin activation](#plugin-activation)
- [APIs](#apis)
- [REST](#rest)
- [Usage](#usage)
- [Versioning](#versioning)
- [Examples](#examples)
- [Example: Basic text blocks: `core/heading` and `core/paragraph`](#example-basic-text-blocks-coreheading-and-coreparagraph)
- [Example: Text attributes in `core/pullquote`](#example-text-attributes-in-corepullquote)
- [Example: Nested blocks in `core/media-text`](#example-nested-blocks-in-coremedia-text)
- [GraphQL](#graphql)
- [Setup](#setup)
- [Usage](#usage-1)
- [Block Attributes](#block-attributes)
- [Complex attributes](#complex-attributes)
- [Example: Simple nested blocks: `core/list` and `core/quote`](#example-simple-nested-blocks-corelist-and-corequote)
- [API Consumption](#api-consumption)
- [Preact](#preact)
- [Block hierarchy reconstruction](#block-hierarchy-reconstruction)
- [Limitations](#limitations)
- [Client-side blocks](#client-side-blocks)
- [Client-side example](#client-side-example)
- [Registering client-side blocks](#registering-client-side-blocks)
- [Rich text support](#rich-text-support)
- [Deprecated blocks](#deprecated-blocks)
- [Rest API Query Parameters](#rest-api-query-parameters)
- [Example Post](#example-post)
- [`include`](#include)
- [`exclude`](#exclude)
- [Filters and actions](#filters-and-actions)
- [GraphQL](#graphql-1)
- [REST](#rest-1)
- [`vip_block_data_api__rest_validate_post_id`](#vip_block_data_api__rest_validate_post_id)
- [`vip_block_data_api__rest_permission_callback`](#vip_block_data_api__rest_permission_callback)
- [`vip_block_data_api__allow_block`](#vip_block_data_api__allow_block)
- [`vip_block_data_api__sourced_block_result`](#vip_block_data_api__sourced_block_result)
- [`vip_block_data_api__before_parse_post_content`](#vip_block_data_api__before_parse_post_content)
- [`vip_block_data_api__after_parse_blocks`](#vip_block_data_api__after_parse_blocks)
- [Analytics](#analytics)
- [Caching on WPVIP](#caching-on-wpvip)
- [Errors and Warnings](#errors-and-warnings)
- [Error: `vip-block-data-api-no-blocks`](#error-vip-block-data-api-no-blocks)
- [Error: `vip-block-data-api-parser-error`](#error-vip-block-data-api-parser-error)
- [Warning: Unregistered block type](#warning-unregistered-block-type)
- [Development](#development)
- [Tests](#tests)

## Installation

### Install on WordPress VIP

The Block Data API plugin is authored and maintained by [WordPress VIP][wpvip], and made available to all WordPress sites by [VIP MU plugins][vip-go-mu-plugins]. Customers who host on WordPress VIP or use [`vip dev-env`](https://docs.wpvip.com/how-tos/local-development/use-the-vip-local-development-environment/) to develop locally have access to the Block Data API automatically. We recommend this activation method for WordPress VIP customers.

Enable the plugin by adding the method shown below to your application's [`client-mu-plugins/plugin-loader.php`][vip-go-skeleton-plugin-loader-example]:

```php
// client-mu-plugins/plugin-loader.php

\Automattic\VIP\Integrations\activate( 'block-data-api' );
```

Create this path in your WordPress VIP site if it does not yet exist.

This will automatically install and activate the latest mu-plugins release of the Block Data API. Remove this line to deactivate the plugin. For more WordPress VIP-specific information about using this plugin, see documentation for the [Block Data API plugin on WordPress VIP][wpvip-mu-plugins-block-data-api].

We plan to utilize API versioning to make automatic updates safe for consumer code. See [Versioning](#versioning) for more information.

To use the Block Data API after activation, skip to [Usage](#usage).

### Install via ZIP file

The latest version of the plugin can be downloaded from the [repository's Releases page][repo-releases]. Unzip the downloaded plugin and add it to the `plugins/` directory of your site's GitHub repository.

#### Plugin activation

Usually VIP recommends [activating plugins with code][wpvip-plugin-activate]. In this case, we are recommending activating the plugin in the WordPress Admin dashboard. This will allow the plugin to be more easily enabled and disabled during testing.

To activate the installed plugin:

1. Navigate to the WordPress Admin dashboard as a logged-in user.
2. Select **Plugins** from the lefthand navigation menu.
3. Locate the "VIP Block Data API" plugin in the list and select the "Activate" link located below it.

![Plugin activation][media-plugin-activate]

## APIs

The VIP Block Data API plugin provides two types of APIs to use - REST and GraphQL. The Block Data API [uses server-side registered blocks][wordpress-block-metadata-php-registration] to determine block attributes. Refer to the **[Client-side blocks](#client-side-blocks)** section for more information about client-side block support limitations.

### REST

There is no extra setup necessary for the REST API. It is ready to use out of the box.

#### Usage

The REST URL is located at:

```js
/wp-json/vip-block-data-api/v1/posts//blocks

// e.g. https://my-site.com/wp-json/vip-block-data-api/v1/posts/139/blocks
```

This public endpoint will return editor block metadata as structured JSON for any published post, page, or published `WP_Post` object.

Review these [**Filters**](#filters) to learn more about limiting access to the REST endpoint:

- [`vip_block_data_api__rest_validate_post_id`](#vip_block_data_api__rest_validate_post_id)
- [`vip_block_data_api__rest_permission_callback`](#vip_block_data_api__rest_permission_callback)

#### Versioning

The current REST endpoint uses a `v1` prefix:

```
/wp-json/vip-block-data-api/v1/...
```

We plan to utilize API versioning to avoid unexpected changes to the plugin. In the event that we make breaking changes to API output, we will add a new endpoint (e.g. `/wp-json/vip-block-data-api/v2/`) with access to new data. Previous versions will remain accessible for backward compatibility.

#### Examples

Examples of WordPress block markup and the associated data structure returned by the Block Data API.

##### Example: Basic text blocks: `core/heading` and `core/paragraph`

![Heading and paragraph block in editor][media-example-heading-paragraph]

Block Markup
Block Data API

```html

Block Data API

Blocks as JSON.

```

```json
[{
"name": "core/heading",
"attributes": {
"level": 3,
"content": "Block Data API"
}
},
{
"name": "core/paragraph",
"attributes": {
"content": "Blocks as JSON."
}
}]
```

---

##### Example: Text attributes in `core/pullquote`

![Pullquote block in editor][media-example-pullquote]

Block Markup
Block Data API

```html


From markup -> props


~ WPVIP

```

```json
[{
"name": "core/pullquote",
"attributes": {
"value": "From markup -> props",
"citation": "~ WPVIP"
}
}]
```

---

##### Example: Nested blocks in `core/media-text`

![Media-text block containing heading in editor][media-example-media-text]

Block Markup
Block Data API

```html








REST API




```

```json
[{
"name": "core/media-text",
"attributes": {
"mediaId": 256,
"mediaType": "image",
"mediaPosition": "left",
"mediaUrl": "http://my.site/api.jpg",
"mediaWidth": 50
},
"innerBlocks": [
{
"name": "core/heading",
"attributes": {
"content": "REST API",
"level": 2
}
}
]
}]
```

### GraphQL

The GraphQL API requires some setup before it can be it can be used.

#### Setup

The Block Data API integrates with **WPGraphQL** to provide a GraphQL API. It is necessary to have [WPGraphQL installed and activated][wpgraphql-install].

Once WPGraphQL has been installed and setup, a new field called `blocksDataV2` will be available for post types that provide content, like posts, pages, etc.

For information on the legacy `blocksData` (v1) field, see [the README from plugin version `1.2.4`][repo-readme-1.2.4].

#### Usage

The `blocksDataV2` field provides block data for post types that support it. Here is an example query:

```graphQL
query NewQuery {
post(id: 1, idType: DATABASE_ID) {
blocksDataV2 {
blocks {
name
id
parentId
attributes {
name
value
}
}
}
}
}
```

The `id` and `parentId` fields are dynamically generated unique IDs that help to identify parent-child relationships between blocks. The resulting set of blocks is a flattened list that can be untangled using the combination of `id` and `parentId` fields. This allows a flat query to return a complex nested block structure. For more information on recreating `innerBlocks` from IDs, see the example code in [Block Hierarchy Reconstruction](#block-hierarchy-reconstruction).

#### Block Attributes

The attributes of a block in GraphQL are available in a list of `name` / `value` string pairs, e.g.

```js
"attributes": [
{
"name": "content",
"value": "This is item 1 in the list",
},
{
"name": "fontSize",
"value": "small"
}
]
```

This is used instead of a key-value structure. This is a trade-off that makes it easy to retrieve block attributes without specifying the the block type ahead of time, but attribute type information is lost.

#### Complex attributes

Some block attributes contain arrays or complex nested values. Demonstrated below, [the `core/table` block uses an array of objects][gutenberg-code-table-body] to represent head, body, and footer cell contents. The GraphQL Block Data API implementation represents these attributes as JSON-encoded strings along with the `isValueJsonEncoded` boolean field. When `isValueJsonEncoded` is `true`, an attribute's value must be JSON decoded to get the original complex value.

For example, using this table:

![Example core/table block with a two header cells and two body cells][media-example-table]

We can query for attributes along with the `isValueJsonEncoded` field in a GraphQL query:

```graphql
query PostQuery {
post(id: 1, idType: DATABASE_ID) {
blocksDataV2 {
blocks {
name
id
parentId
attributes {
name
value
isValueJsonEncoded
}
}
}
}
}
```

The result will contain JSON-encoded attributes designated by the `isValueJsonEncoded` field:

```json
{
"data": {
"post": {
"blocksDataV2": {
"blocks": [
{
"name": "core/table",
"attributes": [
{
"name": "hasFixedLayout",
"value": "false",
"isValueJsonEncoded": true
},
{
"name": "head",
"value": "[{\"cells\":[{\"content\":\"Header A\",\"tag\":\"th\"},{\"content\":\"Header B\",\"tag\":\"th\"}]}]",
"isValueJsonEncoded": true
},
{
"name": "body",
"value": "[{\"cells\":[{\"content\":\"Value 1\",\"tag\":\"td\"},{\"content\":\"Value 2\",\"tag\":\"td\"}]}]",
"isValueJsonEncoded": true
},
{
"name": "foot",
"value": "[]",
"isValueJsonEncoded": true
}
]
}
]
}
}
}
}
```

---

#### Example: Simple nested blocks: `core/list` and `core/quote`

![List and Quote block in editor][media-example-list-quote]

*Block Markup*

```html


  • This is item 1 in the list


  • This is item 2 in the list




This is a paragraph within a quote



```

Query
Block Data API

```graphQL
query NewQuery {
post(id: "1", idType: DATABASE_ID) {
blocksDataV2 {
blocks {
name
id
parentId
attributes {
name
value
}
}
}
}
}
```

```json
{
"data": {
"post": {
"blocksDataV2": {
"blocks": [
{
"name": "core/list",
"id": "1",
"parentId": null,
"attributes": [
{ "name": "ordered", "value": "false" },
{ "name": "values", "value": "" }
]
},
{
"name": "core/list-item",
"id": "2",
"parentId": "1",
"attributes": [
{ "name": "content", "value": "This is item 1 in the list" }
]
},
{
"name": "core/list-item",
"id": "3",
"parentId": "1",
"attributes": [
{ "name": "content", "value": "This is item 2 in the list" }
]
},
{
"name": "core/quote",
"id": "4",
"parentId": null,
"attributes": [
{ "name": "value", "value": "" }
]
},
{
"name": "core/paragraph",
"id": "QmxvY2tEYXRhVjI6NDY6NQ==",
"parentId": "4",
"attributes": [
{ "name": "content", "value": "This is a paragraph within a quote" },
{ "name": "dropCap", "value": "false" }
]
}
]
}
}
}
}
```

Note that `id` values returned from GraphQL will be alpha-numeric strings, e.g. `"id": "SUQ6MQ=="` and not integers.

## API Consumption

### Preact

An example [Preact app][preact] app that queries for block data and maps it into customized components.

The example post being queried contains a `core/media-text` element with an image on the left and `core/heading` and `core/paragraph` blocks on the right side:

![Screenshot of example media-text post content][media-preact-media-text]

The following code uses the REST API to retrieve post and block metadata and map each block onto a custom component.

```html



VIP Block Data API Preact example

import { h, render } from 'https://esm.sh/preact';

renderPost('https://gutenberg-content-api-test.go-vip.net/wp-json', 55);

async function renderPost(restUrl, postId) {
const postResponse = await fetch(`${restUrl}/wp/v2/posts/${postId}`);
const postTitle = (await postResponse.json())?.title?.rendered;

const blocksResponse = await fetch(`${restUrl}/vip-block-data-api/v1/posts/${postId}/blocks`);
const blocks = (await blocksResponse.json())?.blocks;

const App = Post(postTitle, blocks);
render(App, document.body);
}

function mapBlockToComponent(block) {
if (block.name === 'core/heading') {
return Heading(block);
} else if (block.name === 'core/paragraph') {
return Paragraph(block);
} else if (block.name === 'core/media-text') {
return MediaText(block);
} else {
return null;
}
}

/* Components */

function Post(title, blocks) {
return h('div', { className: 'post' },
h('h1', null, title),
blocks.map(mapBlockToComponent),
);
}

function Heading(props) {
// Use dangerouslySetInnerHTML for rich text formatting
return h('h2', { dangerouslySetInnerHTML: { __html: props.attributes.content } });
}

function Paragraph(props) {
// Use dangerouslySetInnerHTML for rich text formatting
return h('p', { dangerouslySetInnerHTML: { __html: props.attributes.content } });
}

function MediaText(props) {
return h('div', { className: 'media-text' },
h('div', { className: 'media' },
h('img', { src: props.attributes.mediaUrl })
),
h('div', { className: 'text' },
props.innerBlocks ? props.innerBlocks.map(mapBlockToComponent) : null,
),
)
}