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.
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.
| Area | Salesforce GraphQL API | Why it matters |
|---|---|---|
| Endpoint model | Single 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 contract | The client explicitly selects fields and relationships. | Payload size stays focused on what the screen or integration step really needs. |
| Security model | Queries return only objects and fields the running user can access. | GraphQL convenience does not bypass object-level or field-level security. |
| Platform scope | Query 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 access | Supports 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. TheIdfield is a direct scalar, but many business fields are not. - Connection-based collections: record lists use GraphQL connection patterns such as
edges,node, andpageInfo, which is how pagination and cursors work. - Permission-safe field selection: in API v65.0 and later, Salesforce supports the
@optionaldirective so a query can still succeed when some fields are inaccessible to the current user.
| Pattern | What it means | Example |
|---|---|---|
| Scalar field | The value is returned directly. | Id |
| Value object field | The field carries a structured value, and often a formatted display value. | Name { value }, AnnualRevenue { value displayValue } |
| Child relationship | The field returns a connection, not a plain array. | Contacts(first: 3) { edges { node { ... } } } |
| Pagination state | The 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.
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,
upperBoundsupports 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.
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
uiapientry point supports anallOrNoneoption, which defaults totrue. - Operation references: Salesforce documents the
IdOrRefscalar 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, andpageInfo, 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
- Get Started with GraphQL API
- What is GraphQL API?
- GraphQL Request and Response Structure
- Schema Conventions
- Query Objects
- Query Objects Examples
- Paginate with Record Connections
- Paginate with Upper-Bound Limit
- GraphQL API Query Limitations
- Mutations Overview
- Send Mutation Requests
- Mutation Field References
