lwc-graphql-wire

Use when an LWC needs to read related records across multiple sObjects in one request, paginate a related list with cursors, or replace several overlapping `@wire(getRecord)` calls with a single shared-cache query. Triggers: 'read account and related contacts in one request', 'paginate a graphql wire', 'graphql wire not refreshing after apex mutation', 'too many @wire calls for related records', 'what fields does ui api graphql support'. NOT for single-record CRUD or write operations — the GraphQL wire adapter is read-only; use UI API (`updateRecord`, `createRecord`, `deleteRecord`) or imperative Apex for writes, and use `getRecord` when a single record with a known id is all you need.

Best use case

lwc-graphql-wire is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Use when an LWC needs to read related records across multiple sObjects in one request, paginate a related list with cursors, or replace several overlapping `@wire(getRecord)` calls with a single shared-cache query. Triggers: 'read account and related contacts in one request', 'paginate a graphql wire', 'graphql wire not refreshing after apex mutation', 'too many @wire calls for related records', 'what fields does ui api graphql support'. NOT for single-record CRUD or write operations — the GraphQL wire adapter is read-only; use UI API (`updateRecord`, `createRecord`, `deleteRecord`) or imperative Apex for writes, and use `getRecord` when a single record with a known id is all you need.

Teams using lwc-graphql-wire should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/lwc-graphql-wire/SKILL.md --create-dirs "https://raw.githubusercontent.com/PranavNagrecha/AwesomeSalesforceSkills/main/skills/lwc/lwc-graphql-wire/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/lwc-graphql-wire/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How lwc-graphql-wire Compares

Feature / Agentlwc-graphql-wireStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Use when an LWC needs to read related records across multiple sObjects in one request, paginate a related list with cursors, or replace several overlapping `@wire(getRecord)` calls with a single shared-cache query. Triggers: 'read account and related contacts in one request', 'paginate a graphql wire', 'graphql wire not refreshing after apex mutation', 'too many @wire calls for related records', 'what fields does ui api graphql support'. NOT for single-record CRUD or write operations — the GraphQL wire adapter is read-only; use UI API (`updateRecord`, `createRecord`, `deleteRecord`) or imperative Apex for writes, and use `getRecord` when a single record with a known id is all you need.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# LWC GraphQL Wire

Use this skill when an LWC needs to read across more than one sObject or more than one related list in a single request, or when a component currently runs three or four overlapping `@wire(getRecord)` calls that could collapse into one cache-shared query. The GraphQL wire adapter (`lightning/uiGraphQLApi`) is a read-only data path that shares the Lightning Data Service cache, enforces field-level security, and returns a structured response shaped around the `uiapi` root, `edges`, and `node`.

---

## Before Starting

Gather this context before authoring the component:

- Which sObjects and relationships does the UI render, and which fields are actually displayed? GraphQL is a shape-matching tool — over-selection here negates the payload win.
- Is the component read-only, or does it also create/update/delete? GraphQL wire is read-only. Writes still go through UI API or imperative Apex.
- Is pagination needed, and if so, is a cursor-based model acceptable? The connection pattern exposes `pageInfo.endCursor` and `hasNextPage`; classic offset pagination is not the first-class model.
- What invalidates the view? A PE, a CDC event, an imperative Apex mutation — each implies a different refresh strategy built around `refreshGraphQL(this.wiredResult)`.

---

## Core Concepts

Three ideas carry most GraphQL-wire work: the adapter response shape, reactive variables, and the connection-based pagination model.

### Adapter Shape And The `uiapi` Root

Importing `{ gql, graphql }` from `lightning/uiGraphQLApi` and wiring `@wire(graphql, { query, variables, operationName })` gives you a provisioned result whose `data` is rooted at `uiapi`. Field scalar values are returned as `{ value, displayValue }` objects, not bare primitives, because Salesforce exposes both raw storage values and locale-formatted display values. Templates must render `{record.Name.value}` or `{record.Name.displayValue}`, not `{record.Name}`. The adapter shares the LDS cache: if another component has already fetched the same record and fields, this wire is a cache hit.

### Reactive Variables And The `gql` Template Literal

`gql` is a tagged template literal that parses the query at module load. String interpolation into the literal does not make the query reactive — it bakes a frozen value into the query text. Runtime variables must be declared in the `query` block (for example `query ($id: ID) { ... }`) and passed through the wire config as a plain object referenced with a leading `$` (for example `variables: '$vars'`). Rebuilding the variables object on every render creates a new identity and defeats cache dedup; stabilize it in a getter or derive it from tracked fields so identity only changes when a value actually changes.

### Connection Pagination Via `edges`, `node`, `pageInfo`

List queries return a connection: `edges { node { ... } cursor } pageInfo { endCursor hasNextPage startCursor hasPreviousPage }`. To implement "Load more", pass `first: N` and `after: $cursor` into the query and append new `node` values into a local array keyed by `Id.value`. The wire fires for each cursor change; accumulate rather than replace to preserve already-rendered rows. Offset pagination is not the adapter's native model — emulating it forces the adapter to discard cache benefits.

---

## Common Patterns

### Single-Query Replacement For Overlapping `getRecord` Calls

**When to use:** The component currently runs a parent `getRecord` plus one or more related-list or parent-of-parent wires, and the combined payload is still small enough to fit a single query.

**How it works:** Write one `gql` query that selects only the fields the UI renders, wire it once, and destructure in getters. The LDS cache still de-dupes parent-record access for other components on the page.

**Why not the alternative:** Multiple independent wires each have their own provisioning lifecycle, refresh hooks, and identity. They rerender independently, multiplying the surface area of cache-miss bugs.

### Cursor-Paginated "Load More"

**When to use:** The UI shows a related list that can grow beyond a reasonable first-paint size (typically >20 rows).

**How it works:** Track `cursor` as a reactive variable, bind it into `after: $cursor` in the query, and in the wire handler append `data.uiapi.query.<Entity>.edges` into a tracked array. Update `cursor` from `pageInfo.endCursor` only when the user clicks "Load more".

**Why not the alternative:** Replacing the accumulator on each wire fire causes list flicker and scroll-position loss. Treating `hasNextPage` as implicit from `edges.length` is unreliable because server-side filters may return a short page that is still not the last one.

---

## Decision Guidance

| Situation | Recommended Approach | Reason |
|---|---|---|
| Single record, id already known, UI renders a handful of fields | `@wire(getRecord)` from `lightning/uiRecordApi` | Simpler config, full LDS cache hit, no query parsing cost |
| Parent record plus one or more related lists in the same UI | GraphQL wire with a single `gql` query | One round trip, shared cache, consistent refresh handle |
| Component needs to write (create/update/delete) | UI API (`createRecord`, `updateRecord`, `deleteRecord`) or imperative Apex | GraphQL wire is read-only by design |
| List that grows and needs "Load more" or infinite scroll | GraphQL wire with cursor pagination via `pageInfo` | Native shape of the connection pattern |
| Complex server-side aggregation or cross-org joins | Imperative Apex returning a DTO | GraphQL wire does not cover arbitrary aggregation or callouts |
| Single sObject list with standard filters rendered in a simple card | `@wire(getListRecords)` or `lightning-datatable` with LDS | Less boilerplate; GraphQL is overkill |

---

## Recommended Workflow

1. Confirm scope — is this a read-only screen or does it also write? Writes disqualify the adapter.
2. Enumerate the minimal field list the UI actually renders and the relationships that join them.
3. Draft the `gql` query with a `query ($vars: ...)` signature and validate it against the official UI API GraphQL examples.
4. Stabilize the variables object in a getter so its identity only changes when a value changes; reference it as `$vars`.
5. Store `this.wiredResult` in the wire handler and plan a `refreshGraphQL(this.wiredResult)` call after every imperative mutation.
6. Add cursor-paginated accumulation only if the list can grow; key accumulated rows by `Id.value`.
7. Run `scripts/check_lwc_graphql_wire.py --manifest-dir force-app/main/default/lwc` and resolve every finding.

---

## Review Checklist

- [ ] The component reads only. All writes go through UI API or imperative Apex.
- [ ] Every scalar access in the template uses `.value` or `.displayValue`; no bare `{record.Field}` reads.
- [ ] `gql` literal contains no `${...}` JS interpolation — all runtime values flow through declared query variables.
- [ ] Variables object identity is stable across renders; it is not rebuilt in the template or in `renderedCallback`.
- [ ] Pagination uses `pageInfo.endCursor` and `hasNextPage`; "Load more" appends instead of replaces.
- [ ] `refreshGraphQL(this.wiredResult)` is called after imperative mutations that change the queried data.
- [ ] The query selects only the fields the UI renders; no speculative "include everything we might need" selection.

---

## Salesforce-Specific Gotchas

1. **`gql` is a tagged template literal, not a string builder** — `${jsValue}` inside the literal is not reactive and will not re-fetch when the value changes; declare a variable and pass it through the wire config.
2. **Scalars are wrapped objects** — `Name` comes back as `{ value, displayValue }`. Forgetting `.value` in the template silently renders `[object Object]`.
3. **`refreshGraphQL` is not `refreshApex`** — it takes the wired result object, not the data, and comes from `lightning/uiGraphQLApi`, not `@salesforce/apex`.
4. **Pagination is cursor-based** — the connection shape exposes `pageInfo.endCursor`/`hasNextPage`. Emulating offset pagination defeats caching and drifts on inserts.
5. **FLS is enforced silently** — inaccessible fields return `null` without an error; tests on a privileged admin user can mask data loss that production users will hit.
6. **Unstable variables thrash the cache** — rebuilding `{ ids: [...], limit: 25 }` on every getter call creates a new identity each render; memoize it.
7. **The adapter is read-only** — there is no `mutation` support on this wire. Generating a `mutation { ... }` block will fail at runtime.

---

## Output Artifacts

| Artifact | Description |
|---|---|
| `gql` query template | A minimal query sized to the rendered UI, with declared variables and an `operationName` for telemetry |
| Paginator pattern | A `loadMore()` handler that appends new `edges` and advances the cursor from `pageInfo.endCursor` |
| Refresh hook plan | A documented list of imperative mutations paired with `refreshGraphQL(this.wiredResult)` calls |
| Checker report | Line-numbered findings for JS interpolation inside `gql`, wrong refresh helper, missing `pageInfo`, and mutation attempts |

---

## Related Skills

- `lwc/wire-service-patterns` — use when the decision is how to provision data generally; this skill is the GraphQL-specific deep dive.
- `lwc/lwc-imperative-apex` — use when writes or complex server-side logic disqualify the GraphQL wire.
- `lwc/lwc-wire-refresh-patterns` — use when the core problem is invalidating cached data after a mutation across wire types.

Related Skills

wire-service-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing or reviewing Lightning Web Components that use `@wire`, Lightning Data Service, UI API, or the GraphQL wire adapter, especially for reactive parameters, cache behavior, and refresh strategy. Triggers: 'wire service', 'refreshApex', 'reactive parameter', 'getRecord', 'wire vs imperative Apex'. NOT for component communication or generic lifecycle issues when data provisioning is not the main concern.

lwc-wire-refresh-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

refreshApex, getRecordNotifyChange, and RefreshView API for LWC data refresh: when wired data is stale, forcing re-fetch after imperative DML, cross-component refresh, 2024 RefreshView replacement of getRecordNotifyChange. NOT for wire basics (use lwc-wire-service). NOT for Lightning Data Service writes (use lwc-lds-writes).

graphql-api-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing or reviewing Salesforce GraphQL API usage, especially endpoint selection, field shaping, connection-based pagination, LWC wire adapters, and GraphQL vs REST tradeoffs. Triggers: 'GraphQL API', 'lightning/graphql', 'uiGraphQLApi', 'GraphQL pagination', 'GraphQL vs REST'. NOT for building a custom GraphQL server or for generic REST integration design with no GraphQL surface.

xss-and-injection-prevention

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when writing or reviewing Visualforce pages, Apex controllers, or LWC components that output user-supplied data, build dynamic queries, or construct HTTP responses. Triggers: 'XSS in Visualforce', 'SOQL injection vulnerability', 'how to encode output in Apex', 'JSENCODE Visualforce', 'open redirect prevention'. NOT for Apex CRUD/FLS enforcement (use soql-security or apex-crud-and-fls), NOT for Shield encryption (use shield-encryption-key-management), NOT for AppExchange security review process (use secure-coding-review-checklist).

visualforce-security-and-modernization

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when hardening or modernizing legacy Visualforce pages — covers the platform CSRF token model and when disabling it is a security regression, view state encryption guarantees and the 170 KB ceiling, FLS/CRUD enforcement gaps on `<apex:outputField>` and on getters that return sObjects, `<apex:includeScript>` interaction with the org Content Security Policy, hosting LWC inside a VF page via `lightning:container` / `lightning-out`, and the retire-vs-harden-vs-leave-alone decision for an inventory of legacy pages. Triggers: 'should I rewrite this Visualforce page in LWC', 'CSRF protection disabled on Visualforce page is that safe', 'community user sees a field they should not on a Visualforce page', 'view state encryption is that enough for sensitive data', 'how do I host an LWC inside a Visualforce page', 'apex:dynamicComponent and apex:actionFunction safe to keep'. NOT for greenfield Visualforce architecture (use apex/visualforce-fundamentals — controller types, view state pattern selection, PDF rendering); NOT for Visualforce email template authoring (use apex/visualforce-email-templates if/when that skill is authored); NOT for general Apex security review across triggers and async (use apex/soql-security and security/secure-coding-review-checklist).

transaction-security-policies

8
from PranavNagrecha/AwesomeSalesforceSkills

Transaction Security policy creation and configuration: condition builder, enhanced policies, enforcement actions (block, MFA, notification, end session), real-time monitoring mode, and policy troubleshooting. NOT for Event Monitoring log analysis or Shield Event Monitoring setup (use event-monitoring). NOT for Apex testing or debug-log analysis.

sso-saml-troubleshooting

8
from PranavNagrecha/AwesomeSalesforceSkills

Diagnosing broken SAML SSO into Salesforce — IdP-initiated vs SP-initiated flows, signing-certificate validity / expiry, NameID format mismatches, RelayState handling, audience / entityId / issuer mismatches, clock skew, the SAML Assertion Validator in Setup, the Login History debug log, and the My Domain prerequisite for SSO. Covers the standard diagnostic loop: read the SAML response, identify which check failed, fix at the IdP or SP. NOT for OAuth / OpenID Connect SSO (see security/oauth-openid-troubleshooting), NOT for setting up SSO from scratch (see security/sso-saml-setup).

shield-kms-byok-setup

8
from PranavNagrecha/AwesomeSalesforceSkills

Configure Shield Platform Encryption with customer-supplied (BYOK) or customer-held (Cache-Only Key Service) tenant secrets, rotate them, and recover. NOT for Classic Encryption or field masking.

shield-event-log-retention-strategy

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing Salesforce Shield Event Monitoring retention, SIEM routing, and storage-tier strategy — which event types to keep, for how long, where, and how to answer audit queries across hot/warm/cold tiers. Triggers: 'shield event log retention', 'route event monitoring to splunk', 'how long to keep login history', 'siem salesforce integration', 'event monitoring storage tier'. NOT for enabling Shield (see salesforce-shield-deployment).

session-management-and-timeout

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when configuring session timeout values, concurrent session limits, session IP locking, or logout behavior in Salesforce. Covers org-wide session settings, profile-level overrides, Connected App session policies, and Metadata API SecuritySettings deployment. NOT for OAuth token refresh flows, login IP ranges, or MFA/identity-provider configuration.

session-high-assurance-policies

8
from PranavNagrecha/AwesomeSalesforceSkills

Enforce step-up authentication for sensitive pages/objects using High Assurance session level and login flow policies. NOT for initial MFA enrollment UX.

service-account-credential-rotation

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing credential rotation for integration users, connected apps, named credentials, and OAuth client secrets in Salesforce. Covers rotation cadence, zero-downtime handover, secret storage, and detection of stale credentials. Triggers: 'rotate integration user password', 'connected app secret rotation', 'named credential rotation', 'stale service account', 'zero downtime secret rotation'. NOT for end-user password policies.