* Split generating-webapp-metadata into single-purpose skills Extract the deployment sequence into a new deploying-webapp skill so each webapp skill describes exactly one actionable goal. This improves agent skill selection accuracy by eliminating multi-concern descriptions. - generating-webapp-metadata: now focused on scaffolding, bundle config, CSP - deploying-webapp: new skill for the 7-step deployment sequence * Rename -webapp- skill directories and references to -webapplication- Rename all 7 webapp skill directories to use the full "webapplication" entity name. Update frontmatter name fields, cross-skill references, and descriptive prose. Package names and CLI commands are preserved as-is since those are actual identifiers.
7.1 KiB
Webapp Integration
When to Use
This guide applies when integrating GraphQL queries into a React webapp using createDataSDK + codegen from @salesforce/sdk-data.
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.
NodeOfConnection
import { type NodeOfConnection } from "@salesforce/sdk-data";
type AccountNode = NodeOfConnection<GetHighRevenueAccountsQuery["uiapi"]["query"]["Account"]>;
Use NodeOfConnection to extract the node type from a Connection type for cleaner typing.
Query Patterns
Choose the pattern based on query complexity:
- Pattern 1 — External
.graphqlfile: Recommended for complex queries with variables, fragments, or shared across files. Full codegen support, syntax highlighting, shareable. Requires codegen step after changes. Does NOT support dynamic queries. - Pattern 2 — Inline
gqltag: Recommended for simple queries. Supports dynamic queries (field set varies at runtime). MUST usegqltag — plain template strings bypass@graphql-eslintvalidation.
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";
Example usage:
const sdk = await createDataSDK();
const response = await sdk.graphql?.<GetMyDataQuery, GetMyDataQueryVariables>(
MY_QUERY,
variables
);
if (response?.errors?.length) {
throw new Error(response.errors.map((e) => e.message).join("; "));
}
const nodes = response?.data?.uiapi?.query?.EntityName?.edges?.map((e) => e.node) ?? [];
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.
Error Handling Strategies
Strategy A — Strict (default): Treat any errors as failure.
if (response?.errors?.length) {
throw new Error(response.errors.map((e) => e.message).join("; "));
}
const result = response?.data;
Strategy B — Tolerant: Log errors but use available data.
if (response?.errors?.length) {
console.warn("GraphQL partial errors:", response.errors);
}
const result = response?.data;
Strategy C — Discriminated: Fail only when no data is returned. Useful for mutations where some return fields may be inaccessible.
if (!response?.data && 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 }.
Conditional Field Selection
For dynamic fieldsets with known fields, use @include(if: $condition) and @skip(if: $condition) directives in .graphql files. See GraphQL spec for details.
ESLint Validation
After writing the query into a source file, validate it against the schema:
# Run from webapp dir (force-app/main/default/webapplications/<app-name>/)
npx eslint <path-to-file-containing-query>
How it works: The ESLint config uses @graphql-eslint/eslint-plugin with its processor, which extracts GraphQL operations from gql template literals in .ts/.tsx files and validates the extracted .graphql virtual files against schema.graphql.
Rules enforced: no-anonymous-operations, no-duplicate-fields, known-fragment-names, no-undefined-variables, no-unused-variables
On failure: Fix the reported issues, re-run npx eslint <file> until clean, then proceed to testing.
Prerequisites: The
schema.graphqlfile must exist and project dependencies must be installed (npm install).
Codegen
Generate TypeScript types from .graphql files and inline gql queries:
# Run from webapp dir (force-app/main/default/webapplications/<app-name>/)
npm run graphql:codegen
Output: src/api/graphql-operations-types.ts
Naming conventions:
<OperationName>Query/<OperationName>Mutation— response types<OperationName>QueryVariables/<OperationName>MutationVariables— variable types
Anti-Patterns
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 Checklists
For Pattern 1 (.graphql files):
- All field names verified via schema search script
- 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 schema search script
- 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)
- Response type generic is provided to
sdk.graphql?.<T>() - Optional chaining is used for nested response data