afv-library/skills/generating-webapp-graphql-read-query/SKILL.md
k-j-kim 9ba064174a
feat: syncing webapp skills sync to afv @W-21338965@ (#57)
* 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>
2026-03-19 22:46:27 +05:30

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.
**/*.ts
**/*.tsx
**/*.graphql

Salesforce GraphQL Read Query Generation

Triggering conditions

  1. Only if the schema exploration phase completed successfully (invoke exploring-graphql-schema first)
  2. 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-schema skill. Do NOT open, read, stream, or parse ./schema.graphql with 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:

  1. No Proliferation - Only generate for the explicitly requested fields, nothing else. Do NOT add fields the user did not ask for.
  2. Unique Query - Leverage child relationships to query entities in one single query
  3. Navigate Entities - Always use relationshipName to access reference fields and child entities
    1. Exception - if the relationshipName field is null, you can't navigate the related entity, and will have to return the Id itself
  4. Leverage Fragments - Generate one fragment per possible type on polymorphic fields (field with dataType="REFERENCE" and more than one entry in referenceToInfos introspection attribute)
  5. 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
  6. Type Enforcement - Make sure to leverage field type information from introspection and GraphQL schema to generate field access
  7. 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
  8. @optional for FLS - Apply @optional on 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
  9. 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
  10. Semi and anti joins - Use the semi-join or anti-join templates to filter an entity with conditions on child entities
  11. Query Generation - Use the template to generate the query
  12. Output Format - Use the standalone
  13. 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
  14. Test the Query - Use the Generated Read Query Testing workflow to test the generated query
    1. 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 the fieldName from the childRelationships information 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.graphql file must exist (invoke exploring-graphql-schema first) and project dependencies must be installed (npm install).

Generated Read Query Testing

Triggering conditionsALL conditions must be true:

  1. The Read Query Generation Workflow completed with status SUCCESS and you have a generated query
  2. The query is a read query
  3. A non-manual method was used during schema exploration to retrieve introspection data

Workflow

  1. Report Step - State the exact method you will use to test (e.g., sf api request graphql from the project root, Connect API, etc.) — this MUST match the method used during schema exploration
  2. Interactive Step - Ask the user whether they want you to test the query using the proposed method
    1. STOP and WAIT for the user's answer. Do NOT proceed until the user responds. Do NOT assume consent.
  3. Test Query - Only if the user explicitly agrees:
    1. Use sf api request rest to 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 } } } } } }"}'
      
    2. Replace v65.0 with the API version of the target org
    3. Replace the query value with the generated read query string
    4. If the query uses variables, include them in the JSON body as a variables key
    5. Report the result as SUCCESS if the query executed without error, or FAILED if errors were returned
    6. An empty result set with no errors is SUCCESS — the query is valid, the org simply has no matching data
  4. Remediation Step - If status is FAILED, use the FAILED status handling workflows

FAILED Status Handling Workflow

The query is invalid:

  1. Error Analysis - Parse and categorize the specific error messages
  2. Root Cause Identification - Use error message to identify the root cause:
    • Syntax - Error contains invalid syntax
    • Validation - Error contains validation error
    • Type - Error contains VariableTypeMismatch or UnknownType
  3. 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-schema skill 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
  4. Test Again - Resume the query testing workflow with the updated query (increment and track attempt counter)
  5. Escalation Path - If targeted resolution fails after 2 attempts, ask for additional details and restart the entire GraphQL workflow from the exploring-graphql-schema skill
  • Schema exploration: exploring-graphql-schema (must complete first)
  • Mutation generation: generating-graphql-mutation-query