WPGraphQL for ACF: Querying Image, Gallery and Repeater Fields (and Fixing null Values)
WPGraphQL for ACF exposes Advanced Custom Fields in your GraphQL schema, which is how most headless WordPress front ends (Next.js, Nuxt, SvelteKit, Astro) read custom fields. Image, gallery and repeater fields are where most people get stuck – especially after the plugin's v2 rewrite changed their shape. This guide shows the current query patterns and how to debug fields that are missing or return null.
wpgraphql-acf, maintained by the WPGraphQL project). Older tutorials written for v0.x (wp-graphql-acf) query image fields directly, without node, and use location-prefixed type names.Table of Contents
- Exposing a field group
- Querying image fields
- Gallery and repeater fields
- Relationship and post object fields
- Why a field is missing or null
- Using the data in Next.js
Exposing a Field Group
- Install and activate WPGraphQL, Advanced Custom Fields (or ACF PRO) and WPGraphQL for ACF.
- Edit the field group and open its GraphQL settings tab.
- Enable Show in GraphQL and set a GraphQL Field Name in camelCase, e.g.
heroFields. Names cannot start with a number. - Check where the group appears in the schema. By default this follows the field group's location rules; you can also choose the GraphQL types manually.
- Each individual field also has its own Show in GraphQL toggle – if it is off, that field is not in the schema.
Open GraphQL → GraphiQL IDE in wp-admin and use the explorer to confirm the field group shows up on the type you expect (Post, Page, a custom post type, etc.).
Querying Image Fields
In v2, image and file fields are connections to a MediaItem, so the data sits under node:
query PostHero($slug: ID!) {
post(id: $slug, idType: SLUG) {
title
heroFields {
heroImage {
node {
sourceUrl(size: LARGE)
altText
mediaDetails {
width
height
}
}
}
}
}
}
The ACF "return format" setting (array, URL or ID) does not change the GraphQL shape – you always get the media node and pick the properties you need. Use srcSet and sizes if your front end renders responsive images.
Gallery and Repeater Fields
Gallery fields (ACF PRO) are connections with nodes. Repeater fields return a list of objects whose properties are the sub-fields:
query ProjectPage($id: ID!) {
page(id: $id, idType: URI) {
projectFields {
gallery {
nodes {
sourceUrl(size: MEDIUM_LARGE)
altText
}
}
milestones { # repeater
label
date
photo { # image sub-field
node {
sourceUrl
}
}
}
}
}
}
Flexible content fields return a list of layout types; query them with inline fragments (... on ProjectFieldsSectionsTextLayout { ... }) – use GraphiQL's autocomplete to get the exact generated type names.
Relationship and Post Object Fields
Relationship, post object and page link fields are also connections in v2:
relatedPosts {
nodes {
__typename
id
uri
... on Post {
title
}
}
}
Why a Field Is Missing or Returns null
| Symptom | Likely cause |
|---|---|
| Cannot query field "heroFields" on type "Post" | Field group not shown in GraphQL, or its location rules / GraphQL types do not include this type |
| A single field is missing from the schema | That field's own "Show in GraphQL" toggle is off |
Image node is null for public requests but works in GraphiQL | The media item is not publicly viewable – for example it is attached to a draft or private post. GraphiQL runs as your logged-in user, your front end does not |
Field value null although set in wp-admin | The value was saved under a different field key (field recreated or imported), or you are querying a revision/preview without authentication |
| Old queries broke after updating | Upgrade from v0.x to v2: add node/nodes and update type names in fragments |
To test like your front end does, run the query with curl against /graphql without cookies. For drafts and previews you need an authenticated request – see preview mode in headless WordPress.
Using the Data in Next.js
// app/posts/[slug]/page.js (Next.js App Router)
const QUERY = `
query PostHero($slug: ID!) {
post(id: $slug, idType: SLUG) {
title
heroFields { heroImage { node { sourceUrl(size: LARGE) altText } } }
}
}`;
export default async function Page({ params }) {
const { slug } = await params;
const res = await fetch(process.env.WORDPRESS_GRAPHQL_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: QUERY, variables: { slug } }),
next: { revalidate: 300 },
});
const { data } = await res.json();
const image = data?.post?.heroFields?.heroImage?.node;
return (
<article>
<h1>{data?.post?.title}</h1>
{image && <img src={image.sourceUrl} alt={image.altText || ''} />}
</article>
);
}
Always guard with optional chaining: any field can be null when an editor leaves it empty. For a fuller walkthrough see integrating ACF with WPGraphQL and building a headless WordPress blog with Next.js. Official docs: WPGraphQL for ACF.