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))
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 (
-
{posts.map(p =>
- {p.title} )}
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 🙂 |