You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: skills/using-webapp-salesforce-data/SKILL.md
+44-17Lines changed: 44 additions & 17 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -17,7 +17,7 @@ Use this skill when the user wants to:
17
17
18
18
## Data SDK Requirement
19
19
20
-
> **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.
20
+
> **All Salesforce data access MUST use the Data SDK** (`@salesforce/sdk-data`). The SDK handles authentication, CSRF, and base URL resolution.
@@ -67,6 +67,24 @@ const res = await sdk.fetch?.("/services/apexrest/my-resource");
67
67
68
68
---
69
69
70
+
## GraphQL Non-Negotiable Rules
71
+
72
+
These rules exist because Salesforce GraphQL has platform-specific behaviors that differ from standard GraphQL. Violations cause silent runtime failures.
73
+
74
+
1.**Schema is the single source of truth** — Every entity name, field name, and type must be confirmed via the schema search script before use in a query. Never guess — Salesforce field names are case-sensitive, relationships may be polymorphic, and custom objects use suffixes (`__c`, `__e`). See [Schema Introspection](references/schema-introspection.md) for entity identification and iterative lookup procedures.
75
+
76
+
2.**`@optional` on all record fields** (read queries) — Salesforce field-level security (FLS) causes queries to fail entirely if the user lacks access to even one field. The `@optional` directive (v65+) tells the server to omit inaccessible fields instead of failing. Apply it to every scalar field, parent relationship, and child relationship. Consuming code must use optional chaining (`?.`) and nullish coalescing (`??`).
77
+
78
+
3.**Correct mutation syntax** — Mutations wrap under `uiapi(input: { allOrNone: true/false })`, not bare `uiapi { ... }`. Always set `allOrNone` explicitly. Output fields cannot include child relationships or navigated reference fields. See [Mutation Query Generation](references/mutation-query-generation.md).
79
+
80
+
4.**Explicit pagination** — Always include `first:` in every query. If omitted, the server silently defaults to 10 records. Include `pageInfo { hasNextPage endCursor }` for any query that may need pagination.
81
+
82
+
5.**SOQL-derived execution limits** — Max 10 subqueries per request, max 5 levels of child-to-parent traversal, max 1 level of parent-to-child (no grandchildren), max 2,000 records per subquery. If a query would exceed these, split into multiple requests.
83
+
84
+
6.**HTTP 200 does not mean success** — Salesforce returns HTTP 200 even when operations fail. Always parse the `errors` array in the response body.
85
+
86
+
---
87
+
70
88
## GraphQL Workflow
71
89
72
90
### Step 1: Acquire Schema
@@ -75,18 +93,18 @@ The `schema.graphql` file (265K+ lines) is the source of truth. **Never open or
75
93
76
94
1. Check if `schema.graphql` exists at the SFDX project root
77
95
2. If missing, run from the **webapp dir**: `npm run graphql:schema`
78
-
3. Custom objects appear only after metadata is deployed
96
+
3. Custom objects appear only after metadata is deployed — invoke the `deploying-webapp-to-salesforce` skill if deployment is needed
79
97
80
98
### Step 2: Look Up Entity Schema
81
99
82
100
Map user intent to PascalCase names ("accounts" → `Account`), then **run the search script from the project root**:
83
101
84
102
```bash
85
-
#From project root — look up all relevant schema info for one or more entities
@@ -96,11 +114,11 @@ The script outputs five sections per entity:
96
114
4.**Create input** — fields accepted by create mutations
97
115
5.**Update input** — fields accepted by update mutations
98
116
99
-
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.
117
+
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. For entity identification procedures (`_Record` suffix, `__c` conventions) and iterative introspection cycles, see [Schema Introspection](references/schema-introspection.md).
100
118
101
119
### Step 3: Generate Query
102
120
103
-
Use the templates below. Every field name **must** be verified from the script output in Step 2.
121
+
Use the templates below. Every field name **must** be verified from the script output in Step 2. For detailed generation rules, filtering, pagination, ordering, semi-joins, and field value wrappers, see [Read Query Generation](references/read-query-generation.md). For mutation chaining, input/output constraints, and transactional semantics, see [Mutation Query Generation](references/mutation-query-generation.md).
104
122
105
123
#### Read Query Template
106
124
@@ -138,7 +156,7 @@ const name = node.Name?.value ?? "";
Then fix the query using the exact names from the script output.
246
+
Then fix the query using the exact names from the script output. For detailed error categories, status handling, and retry strategy, see [Query Testing](references/query-testing.md).
229
247
230
248
---
231
249
232
250
## Webapp Integration (React)
233
251
252
+
Two integration patterns are available:
253
+
254
+
-**Pattern 1 — External `.graphql` file** (recommended for complex queries): Create a `.graphql` file, run `npm run graphql:codegen`, import with `?raw` suffix
255
+
-**Pattern 2 — Inline `gql` tag** (for simple queries): Use the `gql` template tag from `@salesforce/sdk-data`. **Must use `gql`** — plain template strings bypass ESLint schema validation.
The GraphQL engine supports three mutation operations:
6
+
7
+
-**Create** — Insert a new record
8
+
-**Update** — Modify an existing record (Id-based)
9
+
-**Delete** — Remove an existing record (Id-based)
10
+
11
+
Mutations are GA in API v66+. They live under `mutation { uiapi { ... } }` and only support UI API-available objects.
12
+
13
+
## Generation Rules
14
+
15
+
1.**Input fields validation** — Validate that input fields satisfy the constraints for the operation type
16
+
2.**Output fields validation** — Validate that output fields satisfy the constraints for the operation type
17
+
3.**Type consistency** — Variables used as query arguments and their related fields must share the same GraphQL type. Verify types via the schema search script — do NOT assume types
18
+
4.**Input arguments** — `input` is the default argument name unless otherwise specified
19
+
5.**Output field** — For `Create` and `Update`, the output field is always named `Record` (type: EntityName)
20
+
6.**Field name validation** — Every field name in the generated mutation **MUST** match a field confirmed via the schema search script. Do NOT guess or assume field names exist
21
+
7.**Raw input values** — Numeric values must be raw numbers without commas, currency symbols, or locale formatting (e.g., `80000` not `"80,000"` or `"$80,000"`). Compound fields (like addresses) require constituent fields (e.g., `BillingCity`, `BillingStreet`) — do not attempt to set the compound wrapper itself.
22
+
23
+
## Transactional Semantics: `allOrNone`
24
+
25
+
The `uiapi` mutation input accepts an `allOrNone` argument that controls rollback behavior:
26
+
27
+
-**`allOrNone: true` (default)** — If any operation fails, all operations in the request are rolled back. Use when operations must succeed or fail together.
28
+
-**`allOrNone: false`** — Independent operations can succeed individually. However, dependent operations (those using `@{alias}` references) still roll back together with their dependencies.
29
+
30
+
Always set `allOrNone` explicitly to make transactional intent clear.
31
+
32
+
## Mutation Schema Patterns
33
+
34
+
Replace `EntityName` with the actual entity name (e.g., Account, Case). `Delete` operations use generic `Record` types.
- Use the API version of the target org (v66.0+ for mutation support, v65.0+ for `@optional`)
14
+
- Replace the `query` value with the generated query string
15
+
- If the query uses variables, include them in the JSON body as a `variables` key
16
+
17
+
## Critical: HTTP 200 Does Not Mean Success
18
+
19
+
Salesforce returns HTTP 200 even when the GraphQL operation has errors (e.g., invalid fields, permission failures, invalid IDs). **Always parse the `errors` array in the response body regardless of HTTP status code.** Do not treat HTTP 200 as confirmation that the query succeeded.
20
+
21
+
## Testing Workflow
22
+
23
+
This workflow applies to both read and mutation queries:
24
+
25
+
1.**Report method** — State the exact method: `sf api request rest` POST to `/services/data/vXX.0/graphql` from the project root
26
+
2.**Ask user** — Ask the user whether they want to test the query. For mutations, also ask for input argument values — mutations modify real data, so explicit consent is essential. Wait for the user's answer before proceeding. Do not fabricate test data.
27
+
3.**Execute test** — Only if the user explicitly agrees. Run `sf api request rest` with the query, variables, and correct API version
28
+
4.**Report result** — Classify the result using the status definitions below. Always check the `errors` array in the response, even on HTTP 200.
|**Syntax**|`InvalidSyntax`| Fix syntax errors using the error message details |
49
+
|**Validation**|`ValidationError`| Field name is likely invalid — re-run the schema search script, ask user if still unclear |
50
+
|**Type**|`VariableTypeMismatch` or `UnknownType`| Use error details and schema to correct the argument type; adjust variables |
51
+
|**Execution**|`DataFetchingException`, `invalid cross reference id`| Entity is unknown/deleted — create entity first if possible, or ask for a valid Id |
52
+
|**Navigation**|`is not currently available in mutation results`| Field cannot be in mutation output — apply PARTIAL status handling |
53
+
|**Unsupported**|`OperationNotSupported`| The operation is not supported — check object availability and API version |
54
+
|**API Version**|`Cannot invoke JsonElement.isJsonObject()` (on update mutations) |`Record` selection requires API version 64+ — report and retry with version 64 |
55
+
56
+
### 2. Targeted Resolution
57
+
58
+
Apply the resolution from the table above based on the error category. Update the query accordingly.
59
+
60
+
### 3. Test Again
61
+
62
+
Re-run the testing workflow with the updated query. Increment and track the attempt counter.
63
+
64
+
## PARTIAL Status Handling
65
+
66
+
The query executed but some fields are inaccessible (mutations only):
67
+
68
+
1. Report the fields listed in the `errors` attribute
69
+
2. Explain that these fields cannot be queried as part of a mutation
70
+
3. Explain that the query will report errors if these fields remain
71
+
4. Offer to remove the offending fields
72
+
5.**STOP and WAIT** for the user's answer. Do NOT remove fields without explicit consent.
73
+
6. If the user agrees, restart the mutation generation workflow with the updated field list
74
+
75
+
## Retry and Escalation
76
+
77
+
-**Maximum 2 test attempts** per generated query
78
+
- If targeted resolution fails after 2 attempts, ask the user for additional details and **restart the entire workflow from Step 1 (Acquire Schema)** to re-validate entity and field information
0 commit comments