APIs - Data Access

Salesforce GraphQL API Overview

A docs-first guide to Salesforce GraphQL API for architects and developers who want fewer round trips, exact field selection, better relationship traversal, and a realistic view of where GraphQL fits better than REST-style access.

12 min readPublished April 13, 2026By Shivam Gupta
Shivam Gupta
Shivam GuptaSalesforce Architect and founder at pulsagi.com
Graph query and API design

This article is checked against current Salesforce GraphQL API documentation for request shape, schema conventions, query limits, pagination, and mutation support.

Introduction

Salesforce GraphQL API gives you a single endpoint for querying Salesforce data in the UI API family. The official docs consistently emphasize three benefits: field selection, resource aggregation, and schema introspection. That sounds abstract until you look at the practical outcome: one screen can often fetch exactly the fields and related records it needs in one request instead of orchestrating several REST calls.

The important architectural caveat is that this is not a universal replacement for every Salesforce API. GraphQL in Salesforce is intentionally scoped. It is strongest when you want UI-facing, permission-aware data retrieval across supported objects and relationships, not when you need every platform capability or custom server-side orchestration.

Short version: start with GraphQL when a client needs data from multiple related Salesforce resources and you want one stable request shape with exact field control. Reach for Apex, REST, or purpose-built APIs when the use case is broader than UI API-backed data access.

What the API is, and what it is not

Salesforce documents GraphQL API as a standards-based endpoint that follows the June 2018 GraphQL spec and is available in Enterprise, Performance, Unlimited, and Developer Editions. The Salesforce-specific twist is that the schema is versioned through the normal REST API version path, which keeps the GraphQL contract aligned with platform release behavior.

AreaSalesforce GraphQL APIWhy it matters
Endpoint modelSingle versioned endpoint at /services/data/vXX.X/graphql.Clients can fetch multiple supported resources from one endpoint instead of stitching together several REST URLs.
Data contractThe client explicitly selects fields and relationships.Payload size stays focused on what the screen or integration step really needs.
Security modelQueries return only objects and fields the running user can access.GraphQL convenience does not bypass object-level or field-level security.
Platform scopeQuery and mutation coverage is limited to UI API-supported objects and GraphQL-supported operations.It is powerful, but it is not a replacement for every REST resource, Tooling API call, or Apex service.
Relationship accessSupports parent and child traversal within documented limits.This is one of the biggest practical wins over many REST call chains.

That last row is usually the real selling point. If a page needs Accounts, a few child Contacts, and some filterable related collections, GraphQL is often a cleaner fit than multiple REST calls or a custom Apex passthrough. If you are building Lightning Web Components specifically, pair this API view with the site's companion article on GraphQL wire adapter for LWC.

Endpoint and schema shape

POST https://{MyDomain}.my.salesforce.com/services/data/v66.0/graphql

Salesforce documents the request body as a JSON envelope that can include query, operationName, variables, and extensions. The response can contain data, errors, and extensions.

Three schema details matter in practice:

  • Field wrappers: many UI API-backed fields are returned as objects such as Name { value displayValue } rather than flat scalars. The Id field is a direct scalar, but many business fields are not.
  • Connection-based collections: record lists use GraphQL connection patterns such as edges, node, and pageInfo, which is how pagination and cursors work.
  • Permission-safe field selection: in API v65.0 and later, Salesforce supports the @optional directive so a query can still succeed when some fields are inaccessible to the current user.
PatternWhat it meansExample
Scalar fieldThe value is returned directly.Id
Value object fieldThe field carries a structured value, and often a formatted display value.Name { value }, AnnualRevenue { value displayValue }
Child relationshipThe field returns a connection, not a plain array.Contacts(first: 3) { edges { node { ... } } }
Pagination stateThe server returns cursors for fetching the next slice.pageInfo { hasNextPage endCursor }

One more detail from the schema docs is easy to miss and useful when you inspect generated types. For supported sObjects added in API v60.0 and later, the node type name in the schema uses a _Record suffix to avoid naming collisions. That is why newer types can look slightly different from the sObject API name even though the query field itself still uses the object name.

Useful nuance from the docs: API version changes are not cosmetic here. GraphQL capabilities such as five-level child-to-parent traversal, aggregate features, optional fields, upper-bound pagination behavior, and mutation support all depend on the version you target in the endpoint path.

Code examples

These examples are intentionally written in the request shapes Salesforce documents today. They are not toy syntax sketches. They show the patterns you actually need to understand: variables, field wrappers, relationship traversal, and cursor pagination.

Example 1: Query accounts and related contacts with variables

This is the kind of request that makes GraphQL worth using: one operation, exact fields, and one level of child relationship traversal.

query AccountsWithContacts($name: String!, $cursor: String) {
  uiapi {
    query {
      Account(first: 5, after: $cursor, where: { Name: { like: $name } }) {
        edges {
          cursor
          node {
            Id
            Name { value }
            Industry { value }
            Contacts(first: 3) {
              edges {
                node {
                  Id
                  FirstName { value }
                  LastName { value }
                  Email { value }
                }
              }
            }
          }
        }
        pageInfo {
          hasNextPage
          endCursor
        }
        totalCount
      }
    }
  }
}
{
  "name": "Acme%",
  "cursor": null
}

Notice three Salesforce-specific details. Name and Industry are value objects, not plain strings. Contacts is a connection, not a simple array. And the list state is expressed through pageInfo and cursors, not offset-based paging.

Example 2: Send the query through the GraphQL endpoint

The request body keeps the GraphQL document stable while moving runtime values into variables. That is cleaner for clients, easier to reuse, and friendlier to tooling.

curl https://your-domain.my.salesforce.com/services/data/v66.0/graphql \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "operationName": "AccountsWithContacts",
    "query": "query AccountsWithContacts($name: String!, $cursor: String) { uiapi { query { Account(first: 5, after: $cursor, where: { Name: { like: $name } }) { edges { cursor node { Id Name { value } Industry { value } Contacts(first: 3) { edges { node { Id FirstName { value } LastName { value } Email { value } } } } } } pageInfo { hasNextPage endCursor } totalCount } } } }",
    "variables": {
      "name": "Acme%",
      "cursor": null
    }
  }'

Example 3: Query multiple objects in one call

Salesforce's examples show that you can retrieve multiple supported objects in one GraphQL request, as long as the operations are independent. What you cannot do is make one part of the same request depend on a value returned by an earlier query field.

query HomePageData {
  uiapi {
    query {
      Account(first: 3, where: { Industry: { eq: "Manufacturing" } }) {
        edges {
          node {
            Id
            Name { value }
          }
        }
      }
      Case(first: 5, where: { Status: { eq: "New" } }) {
        edges {
          node {
            Id
            CaseNumber { value }
            Subject { value }
          }
        }
      }
    }
  }
}

This is exactly the sort of homepage or dashboard query shape where GraphQL can remove a lot of client orchestration noise.

Pagination and limits

Salesforce's GraphQL docs are clear that the API uses forward cursor pagination. By default, the first 10 records are returned. If you need more, you use first and then continue with after plus the previous endCursor.

  • Default page size: the first 10 records are returned unless you set first.
  • Per-subquery cap: each subquery can return up to 2,000 records within the same GraphQL query.
  • Subquery count: each GraphQL query can contain up to 10 subqueries, and each subquery counts as one request for rate limiting.
  • Upper-bound pagination: in API v59.0 and later, upperBound supports larger paged retrieval patterns with valid values from 200 to 2000; in API v60.0 and later, you can switch to upper-bound pagination after an initial record-connection query.
  • Relationship limits: parent-to-child traversal is one level deep, while child-to-parent traversal goes up to five levels in API v58.0 and later.
Architectural implication: GraphQL reduces round trips, but it does not remove the need to think about query cost. A single huge document with many subqueries, child collections, and counts can still be the wrong shape for a page.

Another easy-to-miss detail is that newer schema versions expose extra paging helpers such as pageResultCount in API v60.0 and later. Object support also remains bounded. Query coverage follows UI API-supported objects, with documented exceptions such as Task and Event support arriving as Beta for GraphQL query use in API v59.0 and later. If a data model or metadata area is outside the supported GraphQL scope, you still need another API.

Mutations and current platform scope

Older summaries of Salesforce GraphQL API often describe it as read-only. That is no longer current. Salesforce documentation now states that Mutation support was a Beta service in API v59.0 through v65.0 and is generally available in API v66.0 and later.

  • Create, update, delete: mutation coverage is now part of the official platform story for UI API-supported objects.
  • Transaction behavior: the mutation uiapi entry point supports an allOrNone option, which defaults to true.
  • Operation references: Salesforce documents the IdOrRef scalar so one mutation operation can reference an earlier operation in the same request.
  • Still scoped: mutation support does not turn GraphQL into a universal replacement for Apex services, Composite API, or every Salesforce write path.

That scope matters. If you need a secure, permission-aware create or update flow directly against supported record types, GraphQL is more capable than many older blog posts suggest. If you need business logic in system context, cross-object orchestration, or behavior outside UI API support, Apex still owns that space.

When GraphQL fits best

Strong fit

  • One screen needs several related record collections and you want one request instead of a REST fan-out.
  • You want exact field selection to reduce payload size and overfetching.
  • You want permission-aware reads without writing an Apex aggregation layer.
  • You want the client to own the shape of the response while staying within supported UI API data access.

Use something else when

  • You need platform capabilities that are not exposed through the GraphQL schema.
  • You need complex server-side logic, system-context execution, or reusable domain services in Apex.
  • A simpler adapter such as LDS or a small REST resource already matches the use case cleanly.
  • You need a workflow where later steps depend on values produced by earlier queries in the same request.

The cleanest mental model is this: Salesforce GraphQL API is best when the client genuinely benefits from assembling one precise data graph. It is less compelling when the client just needs one record or when the business logic belongs on the server anyway.

Common mistakes

  • Treating the schema like plain JSON: many Salesforce fields are value objects such as Name { value }, so a flat-field assumption breaks clients quickly.
  • Assuming all Salesforce objects are queryable: GraphQL coverage follows documented supported objects, not the entire platform surface.
  • Forgetting the default page size: if you do not think about first, after, and pageInfo, you will silently under-fetch data.
  • Putting dependent workflows into one query: multiple object queries are supported, but one query block cannot depend on values returned by another query block in that same execution.
  • Using GraphQL only because it is fashionable: a simple record view, LDS adapter, or small Apex endpoint can still be the better architecture.
  • Missing version-specific behavior: optional fields, upper-bound pagination improvements, and mutation support all depend on the API version in the endpoint path.

References