How to consume WPGraphQL GraphQL from JS on the frontend in WordPress

Contents

Overview

This tutorial explains, in exhaustive detail, how to consume WPGraphQL (the GraphQL endpoint for WordPress) from JavaScript running on the frontend. It covers discovery and debugging, building queries and mutations, using the browser fetch API, modern GraphQL clients (graphql-request, Apollo Client, urql), authentication approaches, file uploads, CORS and security considerations, SSR/CSR patterns (Next.js/vanilla), caching and performance strategies, error handling, and common pitfalls. Code examples for each technique are included and intentionally explicit so you can copy paste or adapt them immediately.

Prerequisites

  • WordPress site with WPGraphQL plugin installed and activated. By default the endpoint is /graphql.
  • Familiarity with JavaScript and basic GraphQL concepts (queries, mutations, variables, fragments).
  • Optional plugins for extended features: WPGraphQL JWT or another JWT provider for token auth, WPGraphQL ACF to expose Advanced Custom Fields, WPGraphQL Uploads or similar for file uploads.

Discovering the Schema and Testing Locally

Before writing client code, inspect the schema and test queries. WPGraphQL ships with an Explorer or you can install GraphiQL, GraphQL Playground, or use the GraphiQL Explorer plugin for WPGraphQL. The endpoint is typically:

https://example.com/graphql (replace with your domain)

Use the explorer to run queries and examine available types, fields, and input types—this dramatically reduces guesswork when writing client code.

Example: Simple Introspection Query (via web explorer)

Run in your GraphiQL/Explorer to see types and fields:

{
  __schema {
    types {
      name
      kind
      fields {
        name
      }
    }
  }
}

Core Concepts: Endpoint, Operations, Variables, Fragments

  • Endpoint: Usually /graphql on your WordPress site.
  • Operations: query, mutation (WPGraphQL supports both mutations often require authentication).
  • Variables: preferred to parameterize queries to avoid string concatenation and enable caching.
  • Fragments: reuse field selections across queries and mutations.

Basic: Using fetch from the Browser (Client-Side)

This is the simplest approach. Use POST to send a JSON body with query and variables. For same-origin authenticated requests, include credentials.

Unauthenticated query (public posts)

const endpoint = https://example.com/graphql

const query = 
  query GetRecentPosts(count: Int!) {
    posts(first: count) {
      nodes {
        id
        databaseId
        title
        excerpt
        date
        uri
      }
    }
  }


async function fetchPosts(count = 5) {
  const res = await fetch(endpoint, {
    method: POST,
    headers: {
      Content-Type: application/json,
    },
    body: JSON.stringify({
      query,
      variables: { count },
    }),
  })
  const json = await res.json()
  if (json.errors) {
    throw new Error(JSON.stringify(json.errors))
  }
  return json.data.posts.nodes
}

// usage
fetchPosts(10).then(posts => console.log(posts)).catch(err => console.error(err))

Authenticated requests using cookies (same-origin)

If your frontend is served from the same domain (or you set proper CORS and allow credentials), use cookies by adding credentials: include. WordPress session cookies will authenticate the user for mutations or protected fields.

// Example: create a post via a mutation requires authentication (logged-in user with correct capabilities)
const mutation = 
  mutation CreatePost(title: String!, content: String!) {
    createPost(input: {title: title, content: content, status: PUBLISH}) {
      post {
        id
        databaseId
        title
        uri
      }
      clientMutationId
    }
  }


async function createPost(title, content) {
  const res = await fetch(/graphql, {
    method: POST,
    credentials: include, // send cookies
    headers: { Content-Type: application/json },
    body: JSON.stringify({ query: mutation, variables: { title, content } }),
  })
  const json = await res.json()
  if (json.errors) throw new Error(JSON.stringify(json.errors))
  return json.data.createPost.post
}

Authentication Options for Frontend GraphQL

  • Cookie-based authentication: Best for same-origin or when embedding frontend in the same WordPress domain. Use credentials: include.
  • JWT: Use a JWT plugin (e.g., WPGraphQL JWT Authentication or a REST JWT provider) to obtain a token and add Authorization: Bearer lttokengt header to GraphQL requests. Works cross-origin if CORS is enabled.
  • Application Passwords: Built into WP for REST, not GraphQL by default. You can write glue code or plugin to accept basic auth tokens and translate, but not standard out-of-the-box for WPGraphQL.
  • Custom endpoints/nonce: For a headless frontend running on same domain, you can pass WP nonces to the client and validate them on the server side, or use the REST authentication flow to exchange for a GraphQL-permitted token.

Example: Get JWT via REST then use with GraphQL

# Request a JWT token (example uses a common JWT plugins REST route)
curl -X POST https://example.com/wp-json/jwt-auth/v1/token 
  -H Content-Type: application/json 
  -d {username:youruser,password:yourpass}
# Response:
# { token: eyJ0eXAiOiJKV1QiLCJh..., user_email: ..., user_nicename: ... }
// Then call GraphQL with the token:
const token = eyJ0eXAiOiJKV1QiLCJh... // obtained from auth server
const res = await fetch(https://example.com/graphql, {
  method: POST,
  headers: {
    Content-Type: application/json,
    Authorization: Bearer    token,
  },
  body: JSON.stringify({ query: { viewer { id name } } }),
})
const json = await res.json()

Using graphql-request (minimal GraphQL client)

graphql-request is a tiny client that simplifies making GraphQL requests and supports types when used with TypeScript.

npm install graphql-request
import { GraphQLClient, gql } from graphql-request

const client = new GraphQLClient(https://example.com/graphql, {
  headers: { Content-Type: application/json },
})

const query = gql
  query GetPost(id: ID!) {
    post(id: id, idType: DATABASE_ID) {
      id
      databaseId
      title
      content
    }
  }


async function getPost(id) {
  return client.request(query, { id })
}

Using Apollo Client (full-featured)

Apollo Client provides caching, optimistic updates, developer tools and robust features for larger applications. Below is a recommended setup for client-side and a brief example for server-side rendering (Next.js).

Install

npm install @apollo/client graphql

Client setup (vanilla SPA)

import { ApolloClient, InMemoryCache, HttpLink } from @apollo/client

const client = new ApolloClient({
  link: new HttpLink({
    uri: https://example.com/graphql,
    // For cookie-based auth on same-origin, add credentials: include here
    fetchOptions: { credentials: include },
  }),
  cache: new InMemoryCache({
    typePolicies: {
      Query: {
        fields: {
          posts: {
            // example keyArgs to enable pagination strategies
            keyArgs: false,
            merge(existing = {}, incoming) {
              return {
                ...incoming,
                nodes: [...(existing.nodes  []), ...(incoming.nodes  [])],
              }
            },
          },
        },
      },
    },
  }),
})

export default client

Executing a query with Apollo

import { gql } from @apollo/client

const GET_POSTS = gql
  query GetPosts(first: Int!) {
    posts(first: first) {
      nodes { id databaseId title excerpt date uri }
    }
  }


// Using client directly
client.query({ query: GET_POSTS, variables: { first: 10 } })
  .then(result => console.log(result.data))
  .catch(err => console.error(err))

Server-side rendering with Next.js (getServerSideProps)

// pages/index.js (Next.js)
import { ApolloClient, InMemoryCache, HttpLink, gql } from @apollo/client
import fetch from node-fetch

export async function getServerSideProps(context) {
  const client = new ApolloClient({
    ssrMode: true,
    link: new HttpLink({
      uri: https://example.com/graphql,
      fetch,
      headers: {
        // forward cookies for SSR if needed: context.req.headers.cookie
        cookie: context.req.headers.cookie  ,
      },
    }),
    cache: new InMemoryCache(),
  })

  const { data } = await client.query({
    query: gqlquery { posts(first: 5) { nodes { id title } } },
  })

  return { props: { initialApolloState: client.extract(), posts: data.posts.nodes } }
}

Mutations and Optimistic UI

Client libraries support optimistic responses so the UI updates instantly while the mutation completes on the server.

Example mutation (Apollo optimistic UI)

import { gql, useMutation } from @apollo/client

const CREATE_POST = gql
  mutation CreatePost(title: String!, content: String!) {
    createPost(input: { title: title, content: content, status: PUBLISH }) {
      post { id databaseId title content }
    }
  }


// In a React component
function NewPostForm() {
  const [createPost] = useMutation(CREATE_POST, {
    optimisticResponse: ({ title, content }) => ({
      createPost: {
        __typename: CreatePostPayload,
        post: {
          __typename: Post,
          id: temp-id,
          databaseId: -1,
          title,
          content,
        },
      },
    }),
    update(cache, { data: { createPost } }) {
      // update cache to include new post in lists
    },
  })

  // call createPost({ variables: { title, content } })
}

File uploads (multipart)

GraphQL file uploads use the multipart request specification. WPGraphQL needs a plugin such as WPGraphQL Uploads or a compatible resolver that handles multipart form data.

// Example using fetch   FormData for file upload (mutations must be configured to accept uploads server-side)
const mutation = 
  mutation UploadMedia(file: Upload!, title: String) {
    createMediaItem(input: { file: file, title: title }) {
      mediaItem { id mediaItemUrl mimeType }
    }
  }


// Build multipart form body per GraphQL multipart request spec
const form = new FormData()
form.append(operations, JSON.stringify({
  query: mutation,
  variables: { file: null, title: My Upload },
}))
form.append(map, JSON.stringify({ 0: [variables.file] }))
form.append(0, fileInput.files[0], fileInput.files[0].name)

const res = await fetch(/graphql, {
  method: POST,
  body: form,
  credentials: include,
})
const json = await res.json()

CORS and Server Configuration

If your JavaScript frontend is hosted on a different origin than WordPress, configure CORS. Open CORS to all origins () is convenient for development but not recommended for production instead restrict origins to known domains and enable credentials only when required.

Example: enabling CORS in WordPress (restrict origins)

// Add to a site-specific plugin or themes functions.php
add_action(init, function () {
  allowed_origins = [
    https://your-frontend.example.com,
    // add other allowed origins
  ]
  origin = isset(_SERVER[HTTP_ORIGIN]) ? _SERVER[HTTP_ORIGIN] : 
  if (in_array(origin, allowed_origins, true)) {
    header(Access-Control-Allow-Origin:  . origin)
    header(Access-Control-Allow-Credentials: true)
    header(Access-Control-Allow-Methods: POST, GET, OPTIONS)
    header(Access-Control-Allow-Headers: Content-Type, Authorization)
  }
  // respond to preflight
  if (_SERVER[REQUEST_METHOD] === OPTIONS) {
    exit(0)
  }
})

Error Handling and Best Practices

  • Always check the response JSON for an errors field and handle it explicitly GraphQL often returns a 200 HTTP status even on GraphQL errors.
  • Use variables instead of string interpolation to avoid injection risks and to make caching work better.
  • Limit returned fields to what you need to reduce payload size. Use fragments to share common selections.
  • Sanitize and validate data on server-side for mutations. Client-side validation is convenience only.
  • Be careful exposing sensitive fields publicly in the schema use WP capability checks in resolvers or configure type visibility with WPGraphQL settings or extensions.

Performance: Caching and Pagination

  • Server-side caching: consider WP GraphQL Cache or HTTP cache headers and full-page caching for read-heavy endpoints. WPGraphQL Smart Cache solutions can help.
  • Client-side caching: use Apollo InMemoryCache or urql cache to reduce repeated network requests for the same data.
  • Pagination: prefer cursor or offset-based pagination using first/after or page parameters supported by WPGraphQL for large lists.
  • Persisted queries: reduce payload by storing queries on server and sending only query hashes from the client (requires server support).

Common Patterns and Examples

Fetch in useEffect (React – CSR)

import React, { useEffect, useState } from react

function LatestPosts() {
  const [posts, setPosts] = useState([])
  useEffect(() => {
    const q = query { posts(first: 5) { nodes { id title excerpt uri } } }
    fetch(/graphql, {
      method: POST,
      headers: {Content-Type: application/json},
      body: JSON.stringify({query: q}),
    })
      .then(res => res.json())
      .then(json => {
        if (json.errors) throw json.errors
        setPosts(json.data.posts.nodes)
      })
      .catch(err => console.error(GraphQL error, err))
  }, [])
  return (
    
  )
}

Using fragements for reusability

fragment PostSummary on Post {
  id
  databaseId
  title
  excerpt
  date
}

query GetPosts(first: Int!) {
  posts(first: first) {
    nodes {
      ...PostSummary
    }
  }
}

Security Considerations

  • Do not embed plain admin credentials in client-side code.
  • Restrict CORS to trusted origins only.
  • Use HTTPS always to protect credentials and tokens in transit.
  • Limit what fields are exposed publicly use WP capability checks for sensitive data.
  • Rate-limit endpoints if you expose them to the public or put GraphQL behind a firewall/CDN when necessary.

Advanced Topics

  • Schema customization: WPGraphQL exposes many filters and actions to modify the schema use them to tailor the schema to your frontend needs.
  • Persisted queries: store heavy queries on the server and reference them by hash to improve payload size and client security.
  • Subscriptions: WPGraphQL does not provide subscriptions out-of-the-box you can add a custom websocket layer or use a separate real-time service for live updates.
  • Authorization rules: implement field-level authorization in resolvers or gate access via REST/JWT middleware.

Troubleshooting Checklist

  • Is the WPGraphQL plugin enabled? Confirm the /graphql endpoint responds.
  • Are you seeing CORS preflight failures? Adjust server headers and allow Authorization or cookies as needed.
  • Are mutations failing with permission errors? Verify user capabilities, cookie forwarding for SSR, or JWT token validity.
  • Are images or uploads not working? Install and configure a GraphQL uploads plugin and follow multipart spec on the client.
  • Are GraphQL errors returning inside 200 responses? Inspect json.errors and use the detailed messages to debug schema or input issues.

Complete End-to-End Example: Fetching Posts Then Creating One with JWT

Assumptions: you have a JWT provider that exposes /wp-json/jwt-auth/v1/token and WPGraphQL accepts Authorization Bearer tokens.

# 1) Obtain JWT token
curl -X POST https://example.com/wp-json/jwt-auth/v1/token 
  -H Content-Type: application/json 
  -d {username:editor,password:secret}
# Response includes token
// 2) Use token to query and create a post
const token = eyJ... // from step 1
const endpoint = https://example.com/graphql

// Query latest posts
async function getLatest() {
  const res = await fetch(endpoint, {
    method: POST,
    headers: { Content-Type: application/json, Authorization: Bearer    token },
    body: JSON.stringify({ query: query { posts(first:5) { nodes { title uri } } } }),
  })
  const json = await res.json()
  if (json.errors) throw json.errors
  return json.data.posts.nodes
}

// Create a new post (mutation)
const CREATE_POST = 
  mutation CreatePost(title: String!, content: String!) {
    createPost(input: { title: title, content: content, status: PUBLISH }) {
      post { id databaseId title uri }
    }
  }


async function createPost(title, content) {
  const res = await fetch(endpoint, {
    method: POST,
    headers: { Content-Type: application/json, Authorization: Bearer    token },
    body: JSON.stringify({ query: CREATE_POST, variables: { title, content } }),
  })
  const json = await res.json()
  if (json.errors) throw json.errors
  return json.data.createPost.post
}

// Example use:
getLatest().then(posts => console.log(latest, posts))
createPost(Headless Post, Content from frontend).then(post => console.log(created, post))

Recommended Libraries Plugins

  • Client-side: @apollo/client, graphql-request, urql
  • WordPress: WPGraphQL, WPGraphQL JWT Authentication (or other auth bridge), WPGraphQL ACF (if using ACF), WPGraphQL Uploads (for file handling)
  • Development: GraphiQL, GraphQL Playground, Insomnia, Postman

Summary

Consuming WPGraphQL from JavaScript can be as simple as a fetch POST to /graphql for public data, or as full-featured as an Apollo-powered headless app with SSR, caching, and optimistic updates. Key considerations are authentication (cookies vs JWT), CORS, limiting exposed schema surface, and protecting sensitive operations. Use GraphiQL or the WPGraphQL Explorer to build and validate queries, then pick a client that fits your apps complexity: simple fetch or graphql-request for small widgets Apollo or urql for larger apps requiring cache control and developer tooling.

Useful Links



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Leave a Reply

Your email address will not be published. Required fields are marked *