Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
forcedotcom avatar

Using Webapp Salesforce Data

  • 2 installs
  • 787 repo stars
  • Updated August 5, 2026
  • forcedotcom/afv-library

Reads, writes, and queries Salesforce records via the Data SDK using REST, GraphQL, or Apex from web app UI components.

About

Provides Salesforce data-access guidance for fetching, mutating, and querying records via the mandatory @salesforce/sdk-data Data SDK, preferring GraphQL. A developer uses it to wire React/Angular/Vue components to Salesforce standard or custom object data.

  • All access via createDataSDK(), never direct fetch or axios
  • GraphQL preferred for queries/mutations, REST via sdk.fetch when needed

Using Webapp Salesforce Data by the numbers

  • 2 all-time installs (skills.sh)
  • Ranked #3,765 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/forcedotcom/afv-library --skill using-webapp-salesforce-data

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs2
repo stars787
Last updatedAugust 5, 2026
Repositoryforcedotcom/afv-library

What it does

Reads, writes, and queries Salesforce records via the Data SDK using REST, GraphQL, or Apex from web app UI components.

Files

SKILL.mdMarkdownGitHub ↗

Salesforce Data Access

When to Use

Use this skill when the user wants to:

  • Fetch or display Salesforce data — Query records (Account, Contact, Opportunity, custom objects) to show in a component
  • Create, update, or delete records — Perform mutations on Salesforce data
  • Add data fetching to a component — Wire up a React component to Salesforce data
  • Call REST APIs — Use Connect REST, Apex REST, or UI API endpoints
  • Explore the org schema — Discover available objects, fields, or relationships

Data SDK Requirement

All Salesforce data access MUST use the Data SDK (@salesforce/sdk-data). The SDK handles authentication, CSRF, and base URL resolution. Never use fetch() or axios directly.
import { createDataSDK, gql } from "@salesforce/sdk-data";

const sdk = await createDataSDK();

// GraphQL for record queries/mutations (PREFERRED)
const response = await sdk.graphql?.<ResponseType>(query, variables);

// REST for Connect REST, Apex REST, UI API (when GraphQL insufficient)
const res = await sdk.fetch?.("/services/apexrest/my-resource");

Always use optional chaining (sdk.graphql?.(), sdk.fetch?.()) — these methods may be undefined in some surfaces.

Supported APIs

Only the following APIs are permitted. Any endpoint not listed here must not be used.

APIMethodEndpoints / Use Case
GraphQLsdk.graphqlAll record queries and mutations via uiapi { } namespace
UI API RESTsdk.fetch/services/data/v{ver}/ui-api/records/{id} — record metadata when GraphQL is insufficient
Apex RESTsdk.fetch/services/apexrest/{resource} — custom server-side logic, aggregates, multi-step transactions
Connect RESTsdk.fetch/services/data/v{ver}/connect/file/upload/config — file upload config
Einstein LLMsdk.fetch/services/data/v{ver}/einstein/llm/prompt/generations — AI text generation

Not supported:

  • Enterprise REST query endpoint (/services/data/v*/query with SOQL) — blocked at the proxy level. Use GraphQL for record reads; use Apex REST if server-side SOQL aggregates are required.
  • Aura-enabled Apex (@AuraEnabled) — an LWC/Aura pattern with no invocation path from React webapps.
  • Chatter API (/chatter/users/me) — use uiapi { currentUser { ... } } in a GraphQL query instead.
  • Any other Salesforce REST endpoint not listed in the supported table above.

Decision: GraphQL vs REST

NeedMethodExample
Query/mutate recordssdk.graphqlAccount, Contact, custom objects
Current user infosdk.graphqluiapi { currentUser { Id Name { value } } }
UI API record metadatasdk.fetch/ui-api/records/{id}
Connect RESTsdk.fetch/connect/file/upload/config
Apex RESTsdk.fetch/services/apexrest/auth/login
Einstein LLMsdk.fetch/einstein/llm/prompt/generations

GraphQL is preferred for record operations. Use REST only when GraphQL doesn't cover the use case.

---

GraphQL Workflow

Step 1: Acquire Schema

The schema.graphql file (265K+ lines) is the source of truth. Never open or parse it directly.

1. Check if schema.graphql exists at the SFDX project root 2. If missing, run from the webapp dir: npm run graphql:schema 3. Custom objects appear only after metadata is deployed

Step 2: Look Up Entity Schema

Map user intent to PascalCase names ("accounts" → Account), then run the search script from the project root:

# From project root — look up all relevant schema info for one or more entities
bash .a4drules/skills/using-salesforce-data/graphql-search.sh Account

# Multiple entities at once
bash .a4drules/skills/using-salesforce-data/graphql-search.sh Account Contact Opportunity

The script outputs five sections per entity: 1. Type definition — all queryable fields and relationships 2. Filter options — available fields for where: conditions 3. Sort options — available fields for orderBy: 4. Create input — fields accepted by create mutations 5. Update input — fields accepted by update mutations

Use this output to determine exact field names before writing any query or mutation. Maximum 2 script runs. If the entity still can't be found, ask the user — the object may not be deployed.

Step 3: Generate Query

Use the templates below. Every field name must be verified from the script output in Step 2.

Read Query Template
query GetAccounts {
  uiapi {
    query {
      Account(where: { Industry: { eq: "Technology" } }, first: 10) {
        edges {
          node {
            Id
            Name @optional { value }
            Industry @optional { value }
            # Parent relationship
            Owner @optional { Name { value } }
            # Child relationship
            Contacts @optional {
              edges { node { Name @optional { value } } }
            }
          }
        }
      }
    }
  }
}

FLS Resilience: Apply @optional to all record fields. The server omits inaccessible fields instead of failing. Consuming code must use optional chaining:

const name = node.Name?.value ?? "";
Mutation Template
mutation CreateAccount($input: AccountCreateInput!) {
  uiapi {
    AccountCreate(input: $input) {
      Record { Id Name { value } }
    }
  }
}

Mutation constraints:

  • Create: Include required fields, only createable fields, no child relationships
  • Update: Include Id, only updateable fields
  • Delete: Include Id only
Object Metadata & Picklist Values

Use uiapi { objectInfos(...) } to fetch field metadata or picklist values. Pass either apiNames or objectInfoInputs — never both in the same query.

Object metadata (field labels, data types, CRUD flags):

const GET_OBJECT_INFO = gql`
  query GetObjectInfo($apiNames: [String!]!) {
    uiapi {
      objectInfos(apiNames: $apiNames) {
        ApiName
        label
        labelPlural
        fields {
          ApiName
          label
          dataType
          updateable
          createable
        }
      }
    }
  }
`;

const sdk = await createDataSDK();
const response = await sdk.graphql?.(GET_OBJECT_INFO, { apiNames: ["Account"] });
const objectInfos = response?.data?.uiapi?.objectInfos ?? [];

Picklist values (use objectInfoInputs + ... on PicklistField inline fragment):

const GET_PICKLIST_VALUES = gql`
  query GetPicklistValues($objectInfoInputs: [ObjectInfoInput!]!) {
    uiapi {
      objectInfos(objectInfoInputs: $objectInfoInputs) {
        ApiName
        fields {
          ApiName
          ... on PicklistField {
            picklistValuesByRecordTypeIDs {
              recordTypeID
              picklistValues {
                label
                value
              }
            }
          }
        }
      }
    }
  }
`;

const response = await sdk.graphql?.(GET_PICKLIST_VALUES, {
  objectInfoInputs: [{ objectApiName: "Account" }],
});
const fields = response?.data?.uiapi?.objectInfos?.[0]?.fields ?? [];

Step 4: Validate & Test

1. Lint: npx eslint <file> from webapp dir 2. Test: Ask user before testing. For mutations, request input values — never fabricate data.

If ESLint reports a GraphQL error (e.g. Cannot query field, Unknown type, Unknown argument), the field or type name is wrong. Re-run the schema search script to find the correct name — do not guess:

# From project root — re-check the entity that caused the error
bash .a4drules/skills/using-salesforce-data/graphql-search.sh <EntityName>

Then fix the query using the exact names from the script output.

---

Webapp Integration (React)

import { createDataSDK, gql } from "@salesforce/sdk-data";

const GET_ACCOUNTS = gql`
  query GetAccounts {
    uiapi {
      query {
        Account(first: 10) {
          edges {
            node {
              Id
              Name @optional { value }
              Industry @optional { value }
            }
          }
        }
      }
    }
  }
`;

const sdk = await createDataSDK();
const response = await sdk.graphql?.(GET_ACCOUNTS);

if (response?.errors?.length) {
  throw new Error(response.errors.map(e => e.message).join("; "));
}

const accounts = response?.data?.uiapi?.query?.Account?.edges?.map(e => e.node) ?? [];

---

REST API Patterns

Use sdk.fetch when GraphQL is insufficient. See the Supported APIs table for the full allowlist.

declare const __SF_API_VERSION__: string;
const API_VERSION = typeof __SF_API_VERSION__ !== "undefined" ? __SF_API_VERSION__ : "65.0";

// Connect — file upload config
const res = await sdk.fetch?.(`/services/data/v${API_VERSION}/connect/file/upload/config`);

// Apex REST (no version in path)
const res = await sdk.fetch?.("/services/apexrest/auth/login", {
  method: "POST",
  body: JSON.stringify({ email, password }),
  headers: { "Content-Type": "application/json" },
});

// UI API — record with metadata (prefer GraphQL for simple reads)
const res = await sdk.fetch?.(`/services/data/v${API_VERSION}/ui-api/records/${recordId}`);

// Einstein LLM
const res = await sdk.fetch?.(`/services/data/v${API_VERSION}/einstein/llm/prompt/generations`, {
  method: "POST",
  body: JSON.stringify({ promptTextorId: prompt }),
});

Current user: Do not use Chatter (/chatter/users/me). Use GraphQL instead:

const GET_CURRENT_USER = gql`
  query CurrentUser {
    uiapi { currentUser { Id Name { value } } }
  }
`;
const response = await sdk.graphql?.(GET_CURRENT_USER);

---

Directory Structure

<project-root>/                              ← SFDX project root
├── schema.graphql                           ← grep target (lives here)
├── sfdx-project.json
└── force-app/main/default/webapplications/<app-name>/  ← webapp dir
    ├── package.json                         ← npm scripts
    └── src/
CommandRun FromWhy
npm run graphql:schemawebapp dirScript in webapp's package.json
npx eslint <file>webapp dirReads eslint.config.js
bash .a4drules/skills/using-salesforce-data/graphql-search.sh <Entity>project rootSchema lookup
sf api request restproject rootNeeds sfdx-project.json

---

Quick Reference

Schema Lookup (from project root)

Run the search script to get all relevant schema info in one step:

bash .a4drules/skills/using-salesforce-data/graphql-search.sh <EntityName>
Script Output SectionUsed For
Type definitionField names, parent/child relationships
Filter optionswhere: conditions
Sort optionsorderBy:
CreateRepresentationCreate mutation field list
UpdateRepresentationUpdate mutation field list

Error Categories

Error ContainsResolution
Cannot query fieldField name is wrong — run graphql-search.sh <Entity> and use the exact name from the Type definition section
Unknown typeType name is wrong — run graphql-search.sh <Entity> to confirm the correct PascalCase entity name
Unknown argumentArgument name is wrong — run graphql-search.sh <Entity> and check Filter or OrderBy sections
invalid syntaxFix syntax per error message
validation errorField name is wrong — run graphql-search.sh <Entity> to verify
VariableTypeMismatchCorrect argument type from schema
invalid cross reference idEntity deleted — ask for valid Id

Checklist

  • [ ] All field names verified via search script (Step 2)
  • [ ] @optional applied to record fields (reads)
  • [ ] Optional chaining in consuming code
  • [ ] Lint passes: npx eslint <file>

Related skills

Backend & APIsintegrationsbackend

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.