Skip to main content

Adding Custom Blocks

Block registration

First, register the block.

add_action(
'acf/init',
function () {
acf_register_block_type(
[
'name' => 'block-name',
'title' => 'Block Title',
'category' => 'acf-blocks',
'mode' => 'edit',
'keywords' => 'block,keywords',
'show_in_graphql' => true,
]
);
},
10
);

Fields registration

Then, register ACF fields for the block.

use StoutLogic\AcfBuilder\FieldsBuilder;

add_action(
'acf/init',
function () {
$builder = new FieldsBuilder(
'block-name',
[
'title' => 'Block Title',
]
);
$builder
->addText( 'field' )
->addImage( 'image', [ 'return_format' => 'array' ] )
->addPostObject(
'post',
[
'label' => 'Post',
'post_type' => [ 'post' ],
'return_format' => 'id',
]
)
->setLocation( 'block', '==', 'acf/block-name' );

acf_add_local_field_group($builder->build());
},
10
);

See also:

Graphql request

The block name in the Graphql scheme generated by the pattern: Acf + the block name defined on registration in pascal case + Block The block data will be available in CMS editor and might be requested by graphql query.

fragment AcfBlockNameBlockFragment on AcfBlockNameBlock {
name
attributesJSON
}

{
resolveUrl(url: "/") {
node {
... on Post {
blocksLight {
...AcfBlockNameBlockFragment
}
}
... on Page {
blocksLight {
...AcfBlockNameBlockFragment
}
}
}
}
}

See also:

Custom resolvers

Scalar values fields (string, numeric, etc) will be available in attributes field automatically. But for complex values (Posts, list of Post, Category, Image, etc.) we have to implement a custom resolvers.

See also:

Post

We return the WP_Post object wrapped by the WPGraphQL\Model\Post object.

use WPGraphQL\Model\Post;

add_action( 'graphql_register_types', function () {
register_graphql_field(
'AcfBlockNameBlock',
'post',
[
'type' => 'Post',
'resolve' => function ( $block ) {
$post_id = $block->attributes['data']['post'];
$post = get_post( $post_id );
if ( ! $post ) {
return null;
}
return new Post( $post );
},
]
);
}, 10 );

Image

The image is similar to the Post resolver with 'type' => 'MediaItem'.

use WPGraphQL\Model\Post;

add_action( 'graphql_register_types', function () {
register_graphql_field(
'AcfBlockNameBlock',
'image',
[
'type' => 'MediaItem',
'resolve' => function( $block ) {
$image_id = $block->attributes['data']['image'];
if ( ! $image_id ) {
return null;
}

$post_object = get_post( $image_id );
if ( $post_object instanceof \WP_Post ) {
return new Post( $post_object );
}
return null;
},
]
);
}, 10 );

Category

The image is similar to the Post resolver with 'type' => 'Category', wrapped by WPGraphQL\Model\Term object.

use WPGraphQL\Model\Term;

add_action( 'graphql_register_types', function () {
register_graphql_field(
'AcfBlockNameBlock',
'category',
[
'type' => 'Category',
'resolve' => function( $block ) {
$category_id = $block->attributes['data']['category'];
$category = get_category( $category_id );
if ( $category instanceof \WP_Term ) {
return new Term( $category );
}
return null;
},
]
);
}, 10 );

List of Posts

To resolve a list of posts (with pagination and deduplication), we recommend using the register_gutenberg_block_posts_connection helper.

use function SWPCore\HeadlessFrontend\register_gutenberg_block_posts_connection;

public function posts_query_builder( array $attributes ): array {
$category_id = $attributes['data']['category'];
return [
'posts_per_page' => 4,
'paged' => 1,
'post_type' => 'post',
'cat' => $category_id,
'orderby' => 'date',
'order' => 'DESC',
];
}

add_action( 'graphql_register_types', function () {
register_gutenberg_block_posts_connection( 'AcfBlockNameBlock', posts_query_builder );
}, 10 );

Querying custom fields

fragment AcfBlockNameBlockFragment on AcfBlockNameBlock {
name
attributesJSON
post {
title
}
image {
sourceUrl
}
posts {
nodes {
title
}
}
}