Developers / Architects

How to Implement GraphQL in Salesforce Lightning Web Components

By Ravi Teja

Updated September 25, 2025

The Lightning Web Components (LWC) framework adheres to modern Web Components standards, empowering developers to build fast, reusable components using standard HTML, JavaScript, and CSS on the Salesforce Platform. 

While Lightning Data Service (LDS) enables data access through base components like lightning-record-form and traditional wire adapters, the GraphQL wire adapter introduces a flexible and efficient additional tool.

GraphQL Wire Adapter Overview

The GraphQL wire adapter enables LWCs to perform declarative data fetching using the Salesforce GraphQL API, a relative newcomer to Salesforce APIs. GraphQL is a modern query language supporting filtering, pagination, and aggregation.

The GraphQL wire adapter simplifies data fetching in Lightning Web Components by leveraging Salesforce’s GraphQL API for data access. The GraphQL API is fundamentally a wrapper for SOQL. At runtime, GraphQL queries are transformed into SOQL queries, meaning the API inherits SOQL’s capabilities and limitations. This design ensures compatibility with Salesforce’s existing query infrastructure while adhering to platform limits defined by SOQL.

Unlike other wire adapters, it eliminates the need for multiple adapters for different data shapes, as a single GraphQL query can return structured, nested, and filtered results tailored to the component’s needs. This makes the GraphQL wire adapter a powerful tool for building high-performance, data-driven LWCs and mobile-first experiences.

When to Use the GraphQL Wire Adapter

The GraphQL wire adapter is ideal for scenarios requiring efficient, declarative access to complex Salesforce data models:

  • Consolidated Data Fetching: Query multiple objects and aggregate related data in a single request, minimizing round trips and reducing client-side orchestration.
  • Precision Queries: Retrieve only the specific objects and fields needed, eliminating unnecessary data retrieval and making your Lightning Web Components (LWCs) leaner and more effective.
  • Efficient Pagination: Built-in cursor and offset-based pagination enables scalable handling of large datasets, improving UX responsiveness.

These capabilities make the GraphQL wire adapter another great tool for building Lightning applications.

GraphQL Concepts and Syntax

GraphQL is a powerful query language that enables Salesforce developers to retrieve only the necessary data through a single endpoint, minimizing the need for multiple Apex methods or layered wire adapters in LWC. Unlike traditional REST or SOQL, GraphQL organizes data as a graph – the concept of a record (like Account or Contact) is represented by nodes in this construct. Relationships between records are captured with edges. Cursors are used to facilitate pagination over large datasets. Its JSON-like syntax allows for deeply nested queries, offering greater precision and eliminating over-fetching common in static queries.

In Salesforce, GraphQL is built on the Experience (UI) API and accessed via the lightning/uiGraphQLApi wire adapter, supporting advanced features like filtering, sorting, pagination, and mutations out of the box. This approach enhances performance and streamlines front-end development by letting components specify exactly what data they require.

Basic LWC With GraphQL Wire Adapter

Below is an example that leverages GraphQL to display a list of contacts.

graphQLContacts.html

<template>
   <template lwc:if={results}>
       <template for:each={results} for:item="contact">
           <div key={contact.Id}>
               <lightning-card icon-name="standard:contact" class="slds-var-m-around_xx-small">
                   <h1 slot="title">{contact.Name.value}</h1>
                   <p>Email: {contact.Email.value}</p>
                   <p>Phone: {contact.Phone.value}</p>
               </lightning-card>
           </div>
       </template>
   </template>
</template>

The GraphQLContacts component is a Lightning Web Component (LWC) that fetches and displays a list of contacts using the GraphQL API. It uses the @wire decorator to make a GraphQL query to retrieve the first 10 contacts with their Id, Name, Email, and Phone fields. The results are stored in the results property, and any errors are stored in the error property.

graphQLContacts.js

import { LightningElement, wire } from 'lwc';
import { gql, graphql } from "lightning/uiGraphQLApi";


export default class GraphQLContacts extends LightningElement {
 results;
   error;


   @wire(graphql, {
     query: gql`
     query ContactList {
       uiapi {
         query {
           Contact(first: 10) {
             edges {
               node {
                 Id
                 Name { value }
                 Email { value }
                 Phone { value }
               }
             }
           }
         }
       }
     }
     `,
   })
   graphqlQueryResult({ data, errors }) {
     if (data) {
       this.results = data.uiapi.query.Contact.edges.map((edge) => edge.node);
     }
     this.errors = errors;
   }
 }

This code defines a GraphQL query to fetch data for Contact objects. It retrieves the Id, Name, Email, and Phone fields for each contact. The query result is processed in the graphqlQueryResult function, where the data is mapped to extract the node objects from the edges array, storing them in this.results. Errors, if any, are stored in this.errors.

The map function in JavaScript is an array method that creates a new array by applying a provided callback function to each element of the original array. In this case:

data.uiapi.query.Contact.edges.map((edge) => edge.node);

  • data.uiapi.query.Contact.edges is the original array. 
  • For each edge in the array, the callback function (edge) => edge.node extracts the node property. 
  • The result is a new array containing all the node objects from the edges array. 

This is used to transform the edges array into an array of node objects.

this.results helps visualize the structure of this.results in a readable JSON format.

[
 {
   "Id": "003Qy00000FLYR3IAP",
   "Name": {
     "value": "Rose Gonzalez"
   },
   "Email": {
     "value": "rose@edge.com"
   },
   "Phone": {
     "value": "(512) 757-6000"
   }
 },
 {
   "Id": "003Qy00000FLYR4IAP",
   "Name": {
     "value": "Sean Forbes"
   },
   "Email": {
     "value": "sean@edge.com"
   },
   "Phone": {
     "value": "(512) 757-6000"
   }
 },
 {
   "Id": "003Qy00000FLYR5IAP",
   "Name": {
     "value": "Jack Rogers"
   },
   "Email": {
     "value": "jrogers@burlington.com"
   },
   "Phone": {
     "value": "(336) 222-7000"
   }
 },
 ...
]

graphQLContacts.js.meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
   <apiVersion>61.0</apiVersion>
   <isExposed>true</isExposed>
   <targets>
       <target>lightning__RecordPage</target>
       <target>lightning__AppPage</target>
       <target>lightning__HomePage</target>
   </targets>
</LightningComponentBundle>

The file graphQLContacts.js-meta.xml is a metadata configuration file used in Lightning Web Components. It defines properties such as the component’s visibility, API version, and whether it can be used in specific contexts like Lightning App Builder or Experience Builder. It does not contain executable code but provides metadata about the associated JavaScript file.

Paginate Your Results

GraphQL queries support retrieving up to 2,000 nodes per request. By default, the @wire adapter fetches the initial 10 nodes. To retrieve additional pages, leverage the pageInfo and endCursor fields to perform cursor-based pagination. Both the GraphQL API and the LWC GraphQL wire adapter adhere to the GraphQL Cursor Connections Specification, enabling efficient traversal of large data sets via forward pagination.

Below is an example that leverages the GraphQL wire adapter to display accounts with pagination.

graphQLAccountsPagination.html

<template>
   <lightning-card title="GraphqlPagination" icon-name="custom:custom39">
       <div class="slds-var-m-around_medium">
           <template lwc:if={accounts.data}>
               <template for:each={accounts.data.uiapi.query.Account.edges} for:item="account">
                   <p key={account.node.Id}>{account.node.Name.value}</p>
               </template>
               <div class="slds-p-horizontal_small slds-m-top_medium">
                   <lightning-button label="First" onclick={handleReset} disabled={isFirstPage}>
                   </lightning-button>
                   <lightning-button label="Next" onclick={handleNext} class="slds-m-left_small" disabled={isLastPage}>
                   </lightning-button>
               </div>
           </template>
       </div>
   </lightning-card>
</template>

To implement pagination in graphQLAccountsPagination, the GraphQL query likely uses cursor-based pagination. This involves retrieving a pageInfo object and endCursor from the query response. The endCursor is used as the starting point for the next query to fetch subsequent pages of data. The process involves: 

  • Including pageInfo and edges in the query to get pagination details. 
  • Storing the endCursor from the current query response. 
  • Using the endCursor as a parameter in the next query to fetch the next set of results. Repeating the process until there are no more pages (indicated by hasNextPage being false).

graphQLAccountsPagination.js

import { LightningElement, wire } from "lwc";
import { gql, graphql } from "lightning/uiGraphQLApi";


const pageSize = 3;


export default class GraphQLAccountsPagination extends LightningElement {
 after;
 pageNumber = 1;


 @wire(graphql, {
   query: gql`
     query paginatedAccounts($after: String, $pageSize: Int!) {
       uiapi {
         query {
           Account(first: $pageSize, after: $after, orderBy: { Name: { order: ASC } }) {
             edges {
               node {
                 Id
                 Name {
                   value
                 }
               }
             }
             pageInfo {
               endCursor
               hasNextPage
               hasPreviousPage
             }
           }
         }
       }
     }
   `,
   variables: "$variables",
 })
 accounts;


 get variables() {
   return {
     after: this.after || null,
     pageSize,
   };
 }


 get isFirstPage() {
   return !this.accounts.data?.uiapi.query.Account.pageInfo.hasPreviousPage;
 }


 get isLastPage() {
   return !this.accounts.data?.uiapi.query.Account.pageInfo.hasNextPage;
 }


 handleNext() {
   if (this.accounts.data?.uiapi.query.Account.pageInfo.hasNextPage) {
     this.after = this.accounts.data.uiapi.query.Account.pageInfo.endCursor;
     this.pageNumber++;
   }
 }


 handleReset() {
   this.after = null;
   this.pageNumber = 1;
 }
}

In graphQLAccountsPagination.js, the new GraphQL features include cursor-based pagination with fields like pageInfo and edges. Here’s how it works and interacts with iteration functions: 

GraphQL Features: 

  • edges: Contains the data for each account, with each edge holding a node (the actual account data) and a cursor (a unique identifier for pagination). 
  • pageInfo: Provides metadata about the current page, including hasNextPage, hasPreviousPage, startCursor, and endCursor. 

Interaction With Iteration Functions: 

  • The edges array is iterated using functions like map or forEach to extract and process the node data (e.g., account details). 
  • The endCursor from pageInfo is stored and used in subsequent queries to fetch the next page of results. 
  • If hasNextPage is true, the iteration continues by making another GraphQL query with the endCursor as the starting point.

graphQLAccountsPagination.js.meta.xml

&lt;?xml version="1.0"?>
&lt;LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
   &lt;apiVersion>62.0&lt;/apiVersion>
   &lt;isExposed>true&lt;/isExposed>
   &lt;targets>
       &lt;target>lightning__RecordPage&lt;/target>
       &lt;target>lightning__AppPage&lt;/target>
       &lt;target>lightning__HomePage&lt;/target>
   &lt;/targets>
&lt;/LightningComponentBundle>

The graphQLAccountsPagination.js-meta.xml file is a metadata configuration file used in Salesforce for the associated JavaScript file (graphQLAccountsPagination.js). It defines properties such as: 

  • API Version: Specifies the Salesforce API version the component is compatible with. 
  • Visibility: Determines whether the component is exposed to specific contexts (e.g., Lightning App Builder, Experience Builder). 
  • Targets: Lists where the component can be used (e.g., Lightning pages, record pages, or utility bars). This file ensures the component is properly registered and usable within the Salesforce environment.

GraphQL API Query Limitations in Salesforce

  • API Availability: The GraphQL API is available from version 56.0 onward. Event and Task objects are supported in beta from version 59.0.
  • General Limits: Each GraphQL query can include up to 10 subqueries. Each subquery counts as a separate request for rate limiting. A subquery can return up to 2,000 records, but only the first 10 are returned by default. Use pagination to access more records.
  • Supported Objects: You can only query objects supported by the User Interface API. Some fields, such as NewValue and OldValue in CaseHistory, are not filterable. If a field doesn’t support filtering, you will receive an error.
  • Child-to-Parent Relationships: A query can include up to 55 child-to-parent relationships. You can go up to five levels deep, such as Contact → Account → Owner → FirstName. In API version 57.0 and earlier, only two levels are allowed.
  • Parent-to-Child Relationships: You can include up to 20 parent-to-child relationships in a query, but only one level deep. You cannot access nested children, such as Account → Contacts → Cases.
  • Semi-Join and Anti-Join Queries: You can use up to two nin or ninq operators in a query. These joins must use a single ID field and cannot include relationship paths. The NE operator is not supported with these queries as it alters the join type.
  • Subquery Restrictions: Subqueries must reference a different object type than the main query. You cannot use relationship dot notation in subqueries (e.g., Account.Id is invalid; use AccountId). Subqueries cannot be combined with or or orderBy.
  • Multiple Joins: You can include up to two semi-join or anti-join subqueries in a single query. However, they cannot be nested or used within another subquery’s where clause.
  • Note Object Limitations: The Note object cannot be used in subqueries. You can return the content of Note.Body but cannot filter on it. Fields like blobs or long text are not filterable.
  • External Objects: Subqueries on external objects are limited to 1,000 records. A query can have up to four joins involving external objects. Each join may slow down the response due to separate external calls. orderBy is not supported for relationship queries on external data.

Summary

GraphQL is a flexible API query language that lets you fetch exactly the data you need. In LWC, you can use the @salesforce/graphQL wire adapter to run efficient, single-request queries. It supports filtering, nested fields, and pagination, often removing the need for custom Apex controllers to retrieve data using the Salesforce GraphQL API.

Make sure to leave your feedback in the comments below!

Resources

The Author

Ravi Teja

Ravi is a Salesforce Consultant at a global consulting firm and is a 12x Salesforce Certified Application Architect.

Leave a Reply