Experience UI Bundle Salesforce Data Access
MUST activate whenever a uiBundles/*/src/ project reads, writes, or displays Salesforce data — INCLUDING building a page, list, table, card grid, dashboard, or form that shows, filters, counts, or edits records of any object (e.g. Property__c, Account, Case), even when the prompt names only the UI or the object and never says query, GraphQL, or SDK. Records behind such a component come from Salesforce, so use this ALONGSIDE experience-ui-bundle-frontend-generate: that skill styles the component, this one wires its data. Also triggers on @salesforce/platform-sdk imports, sdk.graphql.query / mutate / sdk.fetch calls, *.graphql files, or stale data needing force-refresh. New read/write work uses the current @salesforce/platform-sdk API; migrate only EXISTING old @salesforce/sdk-data callable code. Not for pure styling/layout with no records, app shell, file upload, or auth/search scaffolding. DO NOT TRIGGER for OAuth, object/field schema changes, Bulk/Tooling/Metadata API, or declarative automation.
- Skill ID
- forcedotcom/sf-skills/experience-ui-bundle-salesforce-data-access
- Publisher
- forcedotcom
- Repository
- sf-skills
- Installs
- 0
- Files
- 8
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
forcedotcom/sf-skills/experience-ui-bundle-salesforce-data-accessInstalls these files- SKILL.md
- references/caching.md
- references/graphiti-cli.md
- references/graphql-hand-authoring.md
- references/migration.md
- references/rest-and-integration.md
- references/sdk-api.md
- scripts/graphql-search.sh
What this skill tells the agent
Salesforce Data Access (UI bundles)
All Salesforce data access in a UI bundle goes through the `@salesforce/platform-sdk` data SDK. The SDK handles auth, CSRF, and base-URL resolution, and — on the WebApp surface — caches every GraphQL query by default.
This file is the workflow + guardrail spine. Depth lives in linked docs:
- [references/graphiti-cli.md](references/graphiti-cli.md) — the `graphiti` CLI (
sf-gql-*commands) that compiles a small JSON spec into a schema-correct, guardrail-applied query + variables + types. The preferred way to author the GraphQL in steps below; falls back to the schema-grep script when unavailable. - [references/sdk-api.md](references/sdk-api.md) —
query/mutatecall surface + generated-type placement; the behavior nuance (surfaces, error stances,QueryResult) grounds on tier-2b. - [references/caching.md](references/caching.md) — the on-by-default cache + two refresh modes; behavior grounds on tier-2b
docs/data/when installed, with the full version-stamped fallback here. - [references/graphql-hand-authoring.md](references/graphql-hand-authoring.md) — schema lookup, read / mutation templates, every platform guardrail (
@optional, pagination, limits, semi-join, wrappers, error table…). - [references/rest-and-integration.md](references/rest-and-integration.md) —
sdk.fetch, the supported-API allowlist, and the reactive/lifecycle integration patterns. - [references/migration.md](references/migration.md) — old
@salesforce/sdk-datacallable code → new namespace. The only place the dead API appears as usable code.
The one-paragraph mental model
const sdk = await createDataSDK(). Then sdk.graphql is a namespace, not a function: `sdk.graphql!.query({...})` for reads, `sdk.graphql!.mutate({...})` for writes. On WebApp, every `query()` is cached by default (300s). HTTP 200 never means success — always check result.errors. Verify every entity and field against the schema before you query it: one unverified field fails the whole query at runtime, and schema.graphql is too large to eyeball — look it up.
import { createDataSDK, gql } from "@salesforce/platform-sdk"; // gql tags the query string so codegen + eslint validate it
const sdk = await createDataSDK();
const result = await sdk.graphql!.query({ query: GET_ACCOUNTS, variables });
if (result.errors?.length) throw new Error(result.errors.map((e) => e.message).join("; "));
const rows = result.data?.uiapi?.query?.Account?.edges?.map((e) => e.node) ?? []; // unwrap edges/node; read field values via .valueTyped call params (query<GetAccountsQuery, GetAccountsQueryVariables>), the CacheControl type, and NodeOfConnection<T> (extracts a node type from a Connection for clean typing) all live in references/sdk-api.md.
This changed (breaking — PR #502). The previous callablesdk.graphql(...)form and the previous package name are dead — the code above is the only correct form. If you encounter the old API in existing code (or a staledist/artifact), don't copy it; convert it per Working on existing code. `sdk.graphql!` is WebApp-only. The non-null assertion above is correct only if the bundle runs solely on WebApp. On other surfaces it can crash — decide before you write it. See [Surfaces — `!` vs guard](#surfaces--sdkgraphql-vs-guard) below.
Ground the SDK contract on the installed types (tier-2a)
@salesforce/platform-sdk force-publishes on a shared version line and moves fast. This SKILL's prose is a point-in-time snapshot of the call contract; the installed declarations are authoritative for the version you actually have. Before writing any query/mutate, read the installed types and let them win:
node_modules/@salesforce/platform-sdk/dist/core/data.d.ts—query/mutatesignatures,QueryResult(hassubscribe/refresh) vsMutationResult(has neither, by design), theCacheControlunion, the default TTL.node_modules/@salesforce/platform-sdk/dist/data/index.d.ts—createDataSDK,gql,NodeOfConnection.
Precedence — installed `.d.ts` beats this SKILL's prose. If a signature, type, or default here disagrees with the installed declaration, follow the declaration and note the drift; do not "correct" the types to match the prose.
Grounding ladder (one model, two axes):
| Tier | Grounds | Answers | Via |
|---|---|---|---|
| tier-1 | GraphQL schema | what data exists | graphiti / graphql-search.sh (Precondition #2) |
| tier-2a | SDK contract | how you call it | the installed .d.ts above |
| tier-2b | SDK behavior | how it behaves | the installed docs/data/ folder (below) |
| spine | this SKILL.md | workflow + guardrails that orchestrate all three; the fallback when a tier can't ground |
Fallback when the `.d.ts` is absent — the package is installed but ships no declarations (a stale or types-stripped build artifact). Then use this SKILL's prose as best-effort. This fallback does not cover a missing package: if @salesforce/platform-sdk isn't installed, stop and install it (Precondition #1) — do not author calls from prose against a dependency you don't have.
Ground the SDK behavior on the installed docs (tier-2b)
The same package ships an authored behavior guide beside its types: node_modules/@salesforce/platform-sdk/docs/data/ (numbered files, read them in order). Tier-2a's .d.ts fixes the call contract; this folder is authoritative for the behavior the contract doesn't spell out — the caching model, the surface !-vs-guard decision, error-handling stances, the migration mindset. Read it before choosing a caching policy, a surface assertion, or an error stance, and let it win — same precedence as tier-2a (the installed source beats this prose; when present it's the fuller, version-current copy).
Fallback when the folder is absent (older SDK, or a types-only build): this SKILL keeps a thin per-behavior fallback — below and in each section — sized only to keep you moving; act on it. As with tier-2a, a missing package is different: if @salesforce/platform-sdk isn't installed, stop and install it (Precondition #1).
Surfaces — sdk.graphql! vs guard
sdk.graphql / sdk.fetch are genuinely optional (typed graphql?: …), and whether you may assert them with ! is a runtime-crash decision — make it before writing any query/mutate. Fallback rule: WebApp-only bundle → `sdk.graphql!` is safe; any bundle that might run off-WebApp (Mosaic / OpenAI / MCPApps) → guard first (`if (!sdk.graphql) return …`), then call. If you cannot prove WebApp-only, guard — a bare ! that later ships elsewhere throws Cannot read properties of undefined and TypeScript won't catch it (same for sdk.fetch!).
The surface matrix, the portable guard snippet, and the full reasoning ground on tier-2b docs/data/ (fallback above); the guard snippet is also in references/sdk-api.md.
Step 0 — Route the task
| The task is… | Go to |
|---|---|
| Read records | [Read workflow](#read-workflow) below |
| Create / update / delete records | [Write workflow](#write-workflow) below |
| Object/field metadata, picklist values, related-list metadata, aggregations | [Beyond record CRUD](#beyond-record-crud) below |
| Data is stale / "add a refresh button" / "cache it longer" | [Freshness & caching](#freshness--caching) below |
| Something GraphQL can't express (Apex REST, file upload, Einstein) | references/rest-and-integration.md |
Migrating old sdk.graphql?.(query, vars) code | [Working on existing code](#working-on-existing-code-migration) below |
More from forcedotcom
Use this skill in a project
Open any RiverX project, search the Skills panel for forcedotcom/sf-skills/experience-ui-bundle-salesforce-data-access, and install it in one click.
