* feat: removing old webapp skills * feat: adding sync of skills from webapps to afv * feat: adding the first iteration of skills * feat: pin template deps to latest npm versions and flatten skill folders - Add pin-template-deps.js to resolve "*" deps to exact npm versions - Integrate pinning into sync-template-skills npm script - Remove check-template-skills-versions.js (no longer needed) - Simplify workflow to single sync step - Flatten skill output: one folder per skill with cleaned names Made-with: Cursor * fix: resolve skill validation errors - Move .template-versions.json from skills/ to root - Shorten skill names to meet 64-char limit: - salesforce-webapp-feature-micro-frontend-generating-micro-frontend-lwc → salesforce-webapp-micro-frontend-lwc - salesforce-webapp-feature-react-agentforce-conversation-client-integrating-agentforce-conversation-client → salesforce-webapp-agentforce-conversation-client - salesforce-webapp-feature-react-file-upload-implementing-file-upload → salesforce-webapp-react-file-upload - Expand descriptions to meet 20-word minimum with trigger context * Add webapp skills from template, sync script updates - Rename skill folders from salesforce-webapp-* to *-webapp-* convention - Update sync-template-skills.js: set SKILL.md front matter name to dest folder - Remove sync-template-skills workflow and pin-template-deps script - Add .synced-template-skills.json manifest, deploying-webapp-to-salesforce skill - Replace salesforce-webapp-designing-webapp-ui-ux with designing-webapp-ui-ux Made-with: Cursor * Align SKILL.md front matter name with folder for all webapp skills Made-with: Cursor * Fix skill validation: description length and trigger context for configuring-webapp-metadata, creating-webapp Made-with: Cursor * Rename sync script to sync-webapp-skills, drop manifest file - Rename sync-template-skills.js to sync-webapp-skills.js - Update package.json script to sync-webapp-skills - Remove .synced-template-skills.json creation and add to .gitignore Made-with: Cursor * Revert sync-react-b2e-sample and sync-react-b2x-sample to upstream version Made-with: Cursor * Sync script: pin b2e and b2x to latest, sync skills from template - Pin both template packages to latest in sync-webapp-skills.js - Update package.json / package-lock.json (b2x 1.109.0) - Sync skills: managing-webapp-agentforce-conversation-client, bar-line-chart, remove building-webapp-analytics-charts and integrating-webapp-agentforce-conversation-client - Minor skill content updates Made-with: Cursor * Remove interactive map, weather widget, and Unsplash skills (no longer in template) Made-with: Cursor --------- Co-authored-by: Hemant Singh Bisht <hsinghbisht@salesforce.com>
12 KiB
| name | description | paths | |||
|---|---|---|---|---|---|
| using-webapp-graphql | Salesforce GraphQL data access. Use when the user asks to fetch, query, or mutate Salesforce data, or add a GraphQL operation for an object like Account, Contact, or Opportunity. |
|
Salesforce GraphQL
Guidance for querying and mutating Salesforce data via the Salesforce GraphQL API. Use createDataSDK() + sdk.graphql?.() and codegen tooling.
When to Use
- User asks to "fetch data from Salesforce"
- User asks to "query" or "mutate" Salesforce records
- User wants to add a new GraphQL operation (query or mutation)
- User asks to add data access for a Salesforce object (Account, Contact, Opportunity, etc.)
Schema Access Policy (GREP ONLY)
GREP ONLY — The
schema.graphqlfile is very large (~265,000+ lines). All schema lookups MUST use the grep-only commands defined in theexploring-graphql-schemaskill. Do NOT open, read, stream, or parse./schema.graphqlwith any tool other than grep.
Directory Context
The generated app has a two-level directory structure. Commands must run from the correct directory.
<project-root>/ ← SFDX project root
├── schema.graphql ← grep target
├── sfdx-project.json
└── force-app/main/default/webapplications/<app-name>/ ← webapp dir
├── package.json (npm scripts: graphql:schema, graphql:codegen, lint)
├── eslint.config.js (schema ref: ../../../../../schema.graphql)
├── codegen.yml (schema ref: ../../../../../schema.graphql)
└── src/ (source code, .graphql query files)
| Command | Run from | Why |
|---|---|---|
npm run graphql:schema |
webapp dir | Script is in webapp's package.json |
npm run graphql:codegen |
webapp dir | Reads codegen.yml in webapp dir |
npx eslint <file> |
webapp dir | Reads eslint.config.js in webapp dir |
grep ... schema.graphql |
project root | schema.graphql lives at project root |
sf api request graphql |
project root | Needs sfdx-project.json |
Wrong directory = silent failures.
npm run graphql:schemafrom the project root will fail with "missing script."grep ./schema.graphqlfrom the webapp dir will fail with "no such file."
Prerequisites
The base React app (base-react-app) ships with all GraphQL dependencies and tooling pre-configured:
@salesforce/sdk-data— runtime SDK forcreateDataSDKandgql@graphql-codegen/cli+ plugins — type generation from.graphqlfiles and inlinegqlqueries@graphql-eslint/eslint-plugin— validates.graphqlfiles andgqltemplate literals againstschema.graphql(used as a query validation gate — see Step 6)graphql— shared by codegen, ESLint, and schema introspection
Before using this skill, ensure:
- The
@salesforce/sdk-datapackage is available (providescreateDataSDK,gql,NodeOfConnection) - Deployment order: Metadata must be deployed before schema fetch; schema must be refetched after any metadata deployment. Invoke the
deploying-to-salesforceskill when deploying or syncing with the org. - A
schema.graphqlfile exists at the project root. If missing, generate it:# Run from webapp dir (force-app/main/default/webapplications/<app-name>/) npm run graphql:schema
npm Scripts
npm run graphql:schema— (run from webapp dir) Downloads the full GraphQL schema from a connected Salesforce org via introspection. Outputsschema.graphqlto the project root.npm run graphql:codegen— (run from webapp dir) Generates TypeScript types from.graphqlfiles and inlinegqlqueries. Outputs tosrc/api/graphql-operations-types.ts.
Workflow
Step 1: Download Schema
Ensure schema.graphql exists at the project root. If missing, run npm run graphql:schema from the webapp dir.
Step 2: Explore the Schema (grep-only)
Before writing any query, verify the target object and its fields exist in the schema.
Invoke the exploring-graphql-schema skill for the full exploration workflow and mandatory grep-only access policy.
GREP ONLY — All schema lookups MUST use the grep commands defined in the
exploring-graphql-schemaskill. Do NOT open, read, stream, or parse./schema.graphqlwith any tool other than grep.
Key actions (all via grep):
type <ObjectName> implements Record— find available fieldsinput <ObjectName>_Filter— find filter optionsinput <ObjectName>_OrderBy— find sorting optionsinput <ObjectName>CreateInput/<ObjectName>UpdateInput— find mutation input types
Step 3: Choose the Query Pattern
Pattern 1 — External .graphql file (recommended for complex queries):
- Queries with variables, fragments, or shared across files
- Full codegen support, syntax highlighting, shareable
- Requires codegen step after changes
- See example:
api/utils/accounts.ts+api/utils/query/highRevenueAccountsQuery.graphql
Pattern 2 — Inline gql tag (recommended for simple queries):
- Simple queries without variables; colocated with usage code
- Supports dynamic queries (field set varies at runtime)
- MUST use
gqltag — plain template strings bypass@graphql-eslintvalidation - See example:
api/utils/user.ts
Step 4: Write the Query
For Pattern 1:
- Create a
.graphqlfile undersrc/api/utils/query/ - Follow UIAPI structure:
query { uiapi { query { ObjectName(...) { edges { node { ... } } } } } } - For mutations, invoke the
generating-graphql-mutation-queryskill - For read queries, invoke the
generating-graphql-read-queryskill
For Pattern 2:
- Define query inline using the
gqltemplate tag - Ensure the query name matches what codegen expects
Step 5: Test Queries Against Live Org
Use the testing workflows in the generating-graphql-read-query and generating-graphql-mutation-query skills to validate queries against the connected org before integrating into the app.
Step 6: Generate Types
# Run from webapp dir (force-app/main/default/webapplications/<app-name>/)
npm run graphql:codegen
This updates src/api/graphql-operations-types.ts with <OperationName>Query/<OperationName>Mutation and <OperationName>QueryVariables/<OperationName>MutationVariables.
Step 7: Lint Validate
Run ESLint on the file containing the query to validate it against the schema before any live testing:
# Run from webapp dir
npx eslint <path-to-file>
The @graphql-eslint/eslint-plugin processor extracts GraphQL from gql template literals and validates them against schema.graphql. Fix all ESLint errors before proceeding.
Step 8: Implement and Verify
Implement the data access function using the pattern below. Use the Quality Checklist before completing.
Core Types & Function Signatures
createDataSDK and graphql
import { createDataSDK } from "@salesforce/sdk-data";
const sdk = await createDataSDK();
const response = await sdk.graphql?.<ResponseType, VariablesType>(query, variables);
createDataSDK() returns a DataSDK instance. The graphql method uses optional chaining (?.) because not all surfaces support GraphQL.
gql Template Tag
import { gql } from "@salesforce/sdk-data";
const MY_QUERY = gql`
query MyQuery {
uiapi { ... }
}
`;
The gql tag enables ESLint validation against the schema. Plain template strings bypass validation.
Error Handling
Default: treat any errors as failure (Strategy A). For partial data tolerance, log errors but use data. For mutations where some return fields are inaccessible, use Strategy C (fail only when no data).
// Default: strict
if (response?.errors?.length) {
throw new Error(response.errors.map((e) => e.message).join("; "));
}
const result = response?.data;
Responses follow uiapi.query.ObjectName.edges[].node; fields use { value }.
NodeOfConnection
import { type NodeOfConnection } from "@salesforce/sdk-data";
type AccountNode = NodeOfConnection<GetHighRevenueAccountsQuery["uiapi"]["query"]["Account"]>;
Pattern 1: External .graphql File
Create a .graphql file, run npm run graphql:codegen, import with ?raw suffix, and use generated types.
Required imports:
import { createDataSDK, type NodeOfConnection } from "@salesforce/sdk-data";
import MY_QUERY from "./query/myQuery.graphql?raw"; // ← ?raw suffix required
import type { GetMyDataQuery, GetMyDataQueryVariables } from "../graphql-operations-types";
When to use: Complex queries with variables, fragments, or shared across files. Does NOT support dynamic queries (field set varies at runtime).
Pattern 2: Inline gql Tag
Required imports:
import { createDataSDK, gql } from "@salesforce/sdk-data";
import { type CurrentUserQuery } from "../graphql-operations-types";
const MY_QUERY = gql`
query CurrentUser {
uiapi { ... }
}
`;
MUST use
gqltag — plain template strings bypass the@graphql-eslintprocessor entirely, meaning no lint validation against the schema.
When to use: Simple, colocated queries. Supports dynamic queries (field set varies at runtime).
Conditional Field Selection
For dynamic fieldsets with known fields, use @include(if: $condition) and @skip(if: $condition) in .graphql files. See GraphQL spec for details.
Anti-Patterns (Not Recommended)
Direct API Calls
// NOT RECOMMENDED: Direct axios/fetch calls for GraphQL
// PREFERRED: Use the Data SDK
const sdk = await createDataSDK();
const response = await sdk.graphql?.<ResponseType>(query, variables);
Missing Type Definitions
// NOT RECOMMENDED: Untyped GraphQL calls
// PREFERRED: Provide response type
const response = await sdk.graphql?.<GetMyDataQuery>(query);
Plain String Queries (Without gql Tag)
// NOT RECOMMENDED: Plain strings bypass ESLint validation
const query = `query { ... }`;
// PREFERRED: Use gql tag for inline queries
const QUERY = gql`query { ... }`;
Quality Checklist
If you have not completed the workflow above, stop and complete it first. Invoke the skill workflow before using this checklist.
Before completing GraphQL data access code:
For Pattern 1 (.graphql files):
- All field names verified via grep against
schema.graphql(invokeexploring-graphql-schema) - Create
.graphqlfile for the query/mutation - Run
npm run graphql:codegento generate types - Import query with
?rawsuffix - Import generated types from
graphql-operations-types.ts - Use
sdk.graphql?.<ResponseType>()with proper generic - Handle
response.errorsand destructureresponse.data - Use
NodeOfConnectionfor cleaner node types when needed - Run
npx eslint <file>from webapp dir — fix all GraphQL errors
For Pattern 2 (inline with gql):
- All field names verified via grep against
schema.graphql - Define query using
gqltemplate tag (NOT a plain string) - Ensure query name matches generated types in
graphql-operations-types.ts - Import generated types for the query
- Use
sdk.graphql?.<ResponseType>()with proper generic - Handle
response.errorsand destructureresponse.data - Run
npx eslint <file>from webapp dir — fix all GraphQL errors
General:
- Lint validation passes (
npx eslint <file>reports no GraphQL errors) - Query field names match the schema exactly (case-sensitive, confirmed via grep)
- Response type generic is provided to
sdk.graphql?.<T>() - Optional chaining is used for nested response data
Reference
- Schema exploration: invoke the
exploring-graphql-schemaskill - Read query generation: invoke the
generating-graphql-read-queryskill - Mutation query generation: invoke the
generating-graphql-mutation-queryskill - Shared GraphQL schema types:
shared-schema.graphqls(in this skill directory) - Schema download:
npm run graphql:schema(run from webapp dir) - Type generation:
npm run graphql:codegen(run from webapp dir)