GraphQL Reference
Endpoint: {WORDPRESS_URL}/graphql
Requires WPGraphQL plugin. Explore interactively with GraphiQL at the same URL (when enabled).
All fetchers live in nextjs-starter-kit/src/lib/graphql.ts.
Theme settings
query GetThemeSettings {
themeSettings {
headlessRedirectUrl
enableHeadlessMode
logoUrl
copyrightText
homeLayout
primaryColor
accentColor
seoDescription
defaultShareImage
googleAnalyticsId
maintenanceMode
headerStyle
footerStyle
socialLinks {
facebook
twitter
instagram
linkedin
github
}
license {
valid
tier
customer
expiresAt
}
navigationMenu {
label
url
path
}
portfolioSkills {
category
items
}
portfolioTimeline {
year
role
company
description
}
siteProfile {
tagline
heroTitle
heroHighlight
heroSubtitle
heroCtaLabel
heroCtaUrl
contactEmail
contactPhone
portfolioBio
portfolioAvatar
}
}
}
Cache tag: theme-settings
Blog posts
All posts
query GetPosts {
posts(first: 10, where: { status: PUBLISH }) {
nodes {
id
title
slug
excerpt
content
date
categories { nodes { name slug } }
author { node { name avatar { url } } }
featuredImage { node { sourceUrl } }
}
}
}
Paginated posts (with search)
Used by getBlogPostsPaginated():
query GetPostsPaginated($first: Int!, $offset: Int, $search: String, $categoryName: String) {
posts(
first: $first
offset: $offset
where: {
status: PUBLISH
search: $search
categoryName: $categoryName
}
) {
pageInfo {
offsetPagination {
total
hasMore
}
}
nodes {
id
title
slug
excerpt
date
categories { nodes { name slug } }
featuredImage { node { sourceUrl } }
}
}
}
SEO fields (seoTitle, seoDescription, seoImage)
Available on Post and Page. Auto-integrates with Yoast SEO or
RankMath when either is active (checked via WPSEO_VERSION/
RANK_MATH_VERSION) — their own postmeta is read directly
(_yoast_wpseo_title/_yoast_wpseo_metadesc/_yoast_wpseo_opengraph-image,
or rank_math_title/rank_math_description/rank_math_facebook_image),
so editors keep using whichever SEO plugin they already have and nothing
needs re-entering. Falls back to this theme's own lightweight "Headless
SEO" meta box (shown in wp-admin only when neither plugin is active) if
neither is installed. See inc/cms-features.php.
query GetPostSeo {
postBy(slug: "hello-world") {
seoTitle
seoDescription
seoImage
}
}
The Next.js side already reads these with a fallback chain
(post.seoTitle || post.title, etc.) in src/app/blog/[slug]/page.tsx.
Categories
query GetCategories {
categories(first: 50, where: { hideEmpty: true }) {
nodes {
name
slug
count
}
}
}
Cache tags: posts, post-{slug}
Corporate services
query GetServices {
services(first: 20, where: { orderby: { field: MENU_ORDER, order: ASC } }) {
nodes {
id
title
content
iconName
}
}
}
Cache tag: services
Case studies
query GetCaseStudies {
portfolioItems(first: 20) {
nodes {
id
title
content
itemCategory
featuredImage { node { sourceUrl } }
}
}
}
Cache tag: portfolio-items
Portfolio projects
query GetProjects {
projects(first: 20) {
nodes {
id
title
content
projectCategory
projectTags
githubUrl
liveUrl
featuredImage { node { sourceUrl } }
}
}
}
Cache tag: projects
Testimonials
query GetTestimonials {
testimonials(first: 20, where: { orderby: { field: MENU_ORDER, order: ASC } }) {
nodes {
id
title
content
authorRole
authorCompany
rating
}
}
}
Cache tag: testimonials
Team members
query GetTeamMembers {
teamMembers(first: 20, where: { orderby: { field: MENU_ORDER, order: ASC } }) {
nodes {
id
title
content
memberRole
featuredImage { node { sourceUrl } }
}
}
}
Cache tag: team-members
FAQ items
query GetFaqs {
faqs(first: 50, where: { orderby: { field: MENU_ORDER, order: ASC } }) {
nodes {
id
title
content
}
}
}
Cache tag: faqs
Pricing plans
query GetPricingPlans {
pricingPlans(first: 10, where: { orderby: { field: MENU_ORDER, order: ASC } }) {
nodes {
id
title
content
planPrice
planPeriod
planFeatures
planHighlighted
}
}
}
Cache tag: pricing-plans
Shop products (Headless Products CPT)
query GetHeadlessProducts {
headlessProducts(first: 20, where: { orderby: { field: MENU_ORDER, order: ASC } }) {
nodes {
id
title
content
productPrice
productSalePrice
productSku
featuredImage { node { sourceUrl } }
}
}
}
Cache tag: headless-products
WooCommerce fallback
When no headless products exist, getShopProducts() queries WooCommerce products (requires WPGraphQL for WooCommerce):
query GetWooProducts {
products(first: 20) {
nodes {
id
databaseId
name
slug
description
... on SimpleProduct {
price
regularPrice
salePrice
image { sourceUrl }
}
}
}
}
databaseId (the raw numeric WooCommerce product ID) is fetched
specifically for real checkout via the
WooCommerce Store API — id alone is an opaque WPGraphQL global ID and
can't be used with /wp-json/wc/store/v1/cart/add-item.
Dynamic pages
query GetPageByUri($uri: String!) {
nodeByUri(uri: $uri) {
__typename
... on Page {
title
content
slug
}
... on Post {
title
content
slug
date
}
}
}
Cache tag: page-{uri}
Contact form mutation
mutation SubmitContact($name: String!, $email: String!, $message: String!) {
submitContactForm(input: { name: $name, email: $email, message: $message }) {
success
error
}
}
On success:
- Submission saved to Contact Logs CPT
- Optional webhook POST to Slack/Discord
wp_mail()to admin email
License delivery mutation
Called only by the Stripe webhook route (/api/stripe-webhook) — never
from client code. See Stripe Licensing.
mutation DeliverLicenseKey($email: String!, $licenseKey: String!, $secret: String!) {
deliverLicenseKey(input: { email: $email, licenseKey: $licenseKey, secret: $secret }) {
success
error
}
}
secret must match headlesscore_license_delivery_secret (wp-admin →
Headless Core → Settings → Licensing) — without a matching secret the
mutation refuses to send, since GraphQL mutations have no built-in
per-mutation auth in this schema. On success, emails the key to email via
wp_mail().
AI content tool mutations
Called from the Gutenberg editor sidebar (assets/blocks/ai-tools.js) —
see AI Content Tools. Both require a valid pro license
and current_user_can('edit_posts') for the authenticated caller
(license alone doesn't gate these, unlike other pro features, since they
sit on the public GraphQL endpoint and could otherwise be called
anonymously to burn API credits).
mutation GenerateSeoDescription($postId: Int!) {
generateSeoDescription(input: { postId: $postId }) {
description
error
}
}
mutation GenerateImageAltText($mediaId: Int!) {
generateImageAltText(input: { mediaId: $mediaId }) {
altText
error
}
}
Neither mutation persists its result itself — the editor JS writes
description into the post excerpt and altText into the media item via
WordPress core's own POST /wp/v2/media/{id} REST endpoint.
Cache tags summary
| Tag | Invalidated when |
|---|---|
theme-settings |
Theme options saved |
posts |
Any post published |
post-{slug} |
Specific post |
page-{uri} |
Specific page |
services |
Service CPT updated |
portfolio-items |
Case study updated |
projects |
Project updated |
testimonials |
Testimonial updated |
team-members |
Team member updated |
faqs |
FAQ updated |
pricing-plans |
Pricing plan updated |
headless-products |
Shop product updated |
Extending the schema
- Register CPT or option in
functions.phporinc/extended-cpts.php - Add
register_graphql_field()for custom meta - Add fetcher in
nextjs-starter-kit/src/lib/graphql.ts - Update
mockData.tsfor offline dev - Document in Content Model
Draft / preview queries
When draftMode() is enabled, fetchers:
- Set
cache: "no-store" - Pass
Authorization: Bearer {WORDPRESS_AUTH_TOKEN} - Omit
status: PUBLISHfilter
Generate JWT via WPGraphQL login mutation or JWT Authentication plugin settings.