* 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 | |||
|---|---|---|---|---|---|
| generating-webapp-graphql-read-query | Generate Salesforce GraphQL read queries. Use when the query to generate is a read query. Schema exploration must complete first — invoke exploring-graphql-schema first. |
|
Salesforce GraphQL Read Query Generation
Triggering conditions
- Only if the schema exploration phase completed successfully (invoke
exploring-graphql-schemafirst) - Only if the query to generate is a read query
Schema Access Policy
⚠️ GREP ONLY — During query generation you may need to verify field names, types, or relationships. All schema lookups MUST use the grep-only commands defined in the
exploring-graphql-schemaskill. Do NOT open, read, stream, or parse./schema.graphqlwith any tool other than grep.
Field-Level Security and @optional
Field-level security (FLS) restricts which fields different users can see. Use the @optional directive on Salesforce record fields when possible. The server omits the field when the user lacks access, allowing the query to succeed instead of failing. Apply @optional to scalar fields, value-type fields (e.g. Name { value }), parent relationships, and child relationships. Available in API v65.0+.
Consuming code must defend against missing fields. When a field is omitted due to FLS, it will be undefined (or absent) in the response. Use optional chaining (?.), nullish coalescing (??), and explicit null/undefined checks when reading query results. Never assume an optional field is present — otherwise the app may crash or behave incorrectly for users without field access.
// ✅ Defend against missing fields
const name = node.Name?.value ?? '';
const relatedName = node.RelationshipName?.Name?.value ?? 'N/A';
// ❌ Unsafe — will throw if field omitted due to FLS
const name = node.Name.value;
Your Role
You are a GraphQL expert. Generate Salesforce-compatible read queries. Schema exploration must complete first. If the schema exploration has not been executed yet, you MUST run the full exploration workflow from the exploring-graphql-schema skill first, then return here for read query generation.
Read Query Generation Workflow
Strictly follow the rules below when generating the GraphQL read query:
- No Proliferation - Only generate for the explicitly requested fields, nothing else. Do NOT add fields the user did not ask for.
- Unique Query - Leverage child relationships to query entities in one single query
- Navigate Entities - Always use
relationshipNameto access reference fields and child entities- Exception - if the
relationshipNamefield is null, you can't navigate the related entity, and will have to return theIditself
- Exception - if the
- Leverage Fragments - Generate one fragment per possible type on polymorphic fields (field with
dataType="REFERENCE"and more than one entry inreferenceToInfosintrospection attribute) - Type Consistency - Make sure variables used as query arguments and their related fields share the same GraphQL type. Verify types against grep output from the schema — do not assume types
- Type Enforcement - Make sure to leverage field type information from introspection and GraphQL schema to generate field access
- Field Name Validation - Every field name in the generated query MUST match a field confirmed via grep lookup in the schema. Do NOT guess or assume field names exist
- @optional for FLS - Apply
@optionalon all Salesforce record fields when possible (see Field-Level Security and @optional). This lets the query succeed when the user lacks field-level access; the server omits inaccessible fields instead of failing - Consuming code defense - When generating or modifying code that consumes read query results, defend against missing fields (see Field-Level Security and @optional). Use optional chaining, nullish coalescing, and null/undefined checks — never assume optional fields are present
- Semi and anti joins - Use the semi-join or anti-join templates to filter an entity with conditions on child entities
- Query Generation - Use the template to generate the query
- Output Format - Use the standalone
- Lint Validation - After writing the query to a file, run
npx eslint <file>from the webapp dir to validate it against the schema. Fix any reported errors before proceeding. See Lint Validation for details - Test the Query - Use the Generated Read Query Testing workflow to test the generated query
- Report First - Always output the generated query in the proper output format BEFORE initiating any test
Read Query Template
query QueryName {
uiapi {
query {
EntityName(
# conditions here
) {
edges {
node {
# Direct fields — use @optional for FLS resilience
FieldName @optional { value }
# Non-polymorphic reference (single type)
RelationshipName @optional {
Id
Name { value }
}
# Polymorphic reference (multiple types)
PolymorphicRelationshipName @optional {
...TypeAInfo
...TypeBInfo
}
# Child relationship (subquery)
RelationshipName @optional (
# conditions here
) {
edges {
node {
# fields
}
}
}
}
}
}
}
}
}
fragment TypeAInfo on TypeA {
Id
SpecificFieldA @optional { value }
}
fragment TypeBInfo on TypeB {
Id
SpecificFieldB @optional { value }
}
Semi-Join and Anti-Join Condition Template
Semi-joins (resp. anti-joins) condition leverage parent-child relationships and allow filtering the parent entity using a condition on child entities.
This is a standard where condition, on the parent entity's Id, expressed using the inq (resp. ninq, i.e. not inq) operator. This operator accepts two attributes:
- The child entity camelcase name to apply the condition on, with a value expressing the condition
- The field name on the child entity containing the parent entity
Id, which is thefieldNamefrom thechildRelationshipsinformation for the child entity - If the only condition is related child entity existence, you can use an
Id: { ne: null }condition
Semi-Join Example - ParentEntity with at least one Matching ChildEntity
query testSemiJoin {
uiapi {
query {
ParentEntity(
where: {
Id: {
inq: {
ChildEntity: {
# standard conditions here
Name: { like: "test%" }
Type: { eq: "some value" }
}
ApiName: "parentIdFieldInChild"
}
}
}
) {
edges {
node {
Id
Name @optional {
value
}
}
}
}
}
}
}
Anti-Join Example - ParentEntity with no Matching ChildEntity
Same example as the Semi-Join Example, but replacing the inq operator by the ninq one.
Read Standalone (Default) Output Format - CLEAN CODE ONLY
const QUERY_NAME = `
query GetData {
# query here
}
`;
const QUERY_VARIABLES = {
// variables here
};
❌ FORBIDDEN — Do NOT include any of the following:
- Explanatory comments about the query (inline or surrounding)
- Field descriptions or annotations
- Additional text about what the query does
- Workflow step descriptions or summaries
- Comments like
// fetches...,// returns...,/* ... */
✅ ONLY output:
- The raw query string constant
- The variables object constant
- Nothing else — no imports, no exports, no wrapper functions
Lint Validation
After writing the generated query into a source file, validate it against the schema using the project's GraphQL ESLint setup:
# 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 (invokeexploring-graphql-schemafirst) and project dependencies must be installed (npm install).
Generated Read Query Testing
Triggering conditions — ALL conditions must be true:
- The Read Query Generation Workflow completed with status
SUCCESSand you have a generated query - The query is a read query
- A non-manual method was used during schema exploration to retrieve introspection data
Workflow
- Report Step - State the exact method you will use to test (e.g.,
sf api request graphqlfrom the project root, Connect API, etc.) — this MUST match the method used during schema exploration - Interactive Step - Ask the user whether they want you to test the query using the proposed method
- STOP and WAIT for the user's answer. Do NOT proceed until the user responds. Do NOT assume consent.
- Test Query - Only if the user explicitly agrees:
- Use
sf api request restto POST the query to the GraphQL endpoint:sf api request rest /services/data/v65.0/graphql \ --method POST \ --body '{"query":"query GetData { uiapi { query { EntityName { edges { node { Id } } } } } }"}' - Replace
v65.0with the API version of the target org - Replace the
queryvalue with the generated read query string - If the query uses variables, include them in the JSON body as a
variableskey - Report the result as
SUCCESSif the query executed without error, orFAILEDif errors were returned - An empty result set with no errors is
SUCCESS— the query is valid, the org simply has no matching data
- Use
- Remediation Step - If status is
FAILED, use theFAILEDstatus handling workflows
FAILED Status Handling Workflow
The query is invalid:
- Error Analysis - Parse and categorize the specific error messages
- Root Cause Identification - Use error message to identify the root cause:
- Syntax - Error contains
invalid syntax - Validation - Error contains
validation error - Type - Error contains
VariableTypeMismatchorUnknownType
- Syntax - Error contains
- Targeted Resolution - Depending on the root cause categorization
- Syntax - Update the query using the error message information to fix the syntax errors
- Validation - The field name is most probably invalid. Re-run the relevant grep command from the
exploring-graphql-schemaskill to verify the correct field name. If still unclear, ask the user for clarification and STOP and WAIT for their answer - Type - Use the error details and re-verify the type via grep lookup in the schema. Correct the argument type and adjust variables accordingly
- Test Again - Resume the query testing workflow with the updated query (increment and track attempt counter)
- Escalation Path - If targeted resolution fails after 2 attempts, ask for additional details and restart the entire GraphQL workflow from the
exploring-graphql-schemaskill
Related Skills
- Schema exploration:
exploring-graphql-schema(must complete first) - Mutation generation:
generating-graphql-mutation-query