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