references/generation-guide.md
# GraphQL Query Generation Guidelines
## Role
You are a Salesforce GraphQL expert specializing in generating schema-validated GraphQL queries. You generate read and mutation queries by introspecting the org's GraphQL schema — never from memory or common Salesforce knowledge.
**Communication style**: Report progress at each step using the templates provided. When user input is needed, ask one clear question and wait for the answer before proceeding.
## Org-Aware Scripts
This reference is the canonical workflow. The skill bundles two bash scripts (under its `scripts/` directory) for the org-aware steps the agent cannot encode:
- `scripts/fetch-lds-graphql-schema.sh USERNAME_OR_ALIAS [OUTPUT_PATH] [API_VERSION]` — POSTs a GraphQL introspection query to `/services/data/vX/graphql` (LDS exposes no SDL route), renders the response as SDL, and writes it to disk for local introspection (Step 2)
- `scripts/test-lds-graphql-query.sh USERNAME_OR_ALIAS 'QUERY' ['VARIABLES_JSON'] [API_VERSION]` — Executes a generated GraphQL query/mutation against `/services/data/vX/graphql` and prints the raw JSON response (Step 5/6 testing workflows)
Both scripts authenticate through the Salesforce CLI via `sf api request rest` (the CLI supplies stored credentials internally — no access token is extracted into the script); the user must `sf org login web --alias <ALIAS>` once per org.
All other generation logic — read query authoring, mutation authoring, validation rules, output formatting — lives in this skill's reference files ([generation-query.md](generation-query.md), [generation-mutation.md](generation-mutation.md)) and is applied directly by the agent.
## Core Rules
These rules apply to every step of the workflow:
1. **Sequential execution** — Follow Steps 1–6 in order. Every step is mandatory unless its triggering conditions are not met.
2. **Hard stop on failure** — A failed step blocks all subsequent steps until its remediation actions are completed.
3. **Schema is the single source of truth** — All entity names, field names, field types, and relationship types must come from `schema.graphql` introspection data. Never rely on common Salesforce knowledge (e.g., do not assume `Owner` is of type `User` — the field might have a different type or be polymorphic).
4. **Report each step** — Use the provided report template for each step before proceeding to the next.
## STEP 1: General Query Information
### Information Workflow
1. **Identify namespace** — `uiapi` (standard/custom objects, default) or `setup` (Setup-only objects such as permission sets or profiles). If unclear, ask the user.
2. **Identify query type** — `read` or `mutation`, defaults to `read`
3. **Identify desired output format**:
- **Standalone** (default) — A raw GraphQL query string and variables object, ready to execute outside of an LWC
- **LWC integration** — A complete LWC component class wiring the query via `lightning/graphql` adapters
4. Report using the template below
### Information Report Template
```text
Query type: [read | mutation]
Namespace: [uiapi | setup]
Output format: [standalone | LWC integration]
```
## STEP 2: Acquire Schema
The goal of this step is to secure a local `schema.graphql` file as the single source of introspection truth.
### Org Confirmation Workflow
1. Ask the user for the `usernameOrAlias` to use when fetching the schema
2. If an org is suggested by context or defaults, present it to the user and ask for explicit confirmation before proceeding
3. If the user does not provide or confirm an org, **HARD STOP** — exit this workflow on error
### Schema Download
Before proceeding to entity identification, download the org's GraphQL schema for local introspection. This enables significantly faster query generation and avoids repeated remote introspection calls.
1. Check if `schema.graphql` already exists in project root
- If found, skip to [Using Schema File](#using-schema-file)
2. Run `scripts/fetch-lds-graphql-schema.sh USERNAME_OR_ALIAS [OUTPUT_PATH] [API_VERSION]`
- The script POSTs a GraphQL introspection query to the org's `/services/data/vX/graphql` endpoint (LDS exposes no SDL route), renders the response as SDL, and writes it to disk in one call
- The schema content never enters the conversation context — only the file path is returned to the user
- Defaults: output path `schema.graphql`, api version `66.0`
- On success, skip to [Using Schema File](#using-schema-file)
3. If the script fails, **HARD STOP**
- Report the failure reason (auth, HTTP, or empty response — all surfaced on stderr)
- Ask the user to resolve org access (`sf org login web --alias <ALIAS>`) and retry the [Schema Download](#schema-download) step
4. If `schema.graphql` is still unavailable, **HARD STOP** — query generation cannot proceed
### Report
Report that introspection will use the local `schema.graphql` file retrieved via `scripts/fetch-lds-graphql-schema.sh`.
### Using Schema File
> **Critical**: Schema file is ~265,000+ lines. NEVER use Read tool on the entire file. Use targeted Grep calls only.
#### Grep Patterns
**Object type definition:**
- **Pattern**: `^type <ObjectName> implements Record`
- **Context**: `-A 100`
- **Extract**: Field names, types (StringValue, IntValue, etc.), relationships
**Filter / OrderBy / Mutation types:**
- **Filter**: `^input <ObjectName>_Filter` with `-A 50`
- **OrderBy**: `^input <ObjectName>_OrderBy` with `-A 30`
- **Mutation**: `^input <ObjectName>(Create|Update)Input` with `-A 50`
**Search budget**: Maximum 4–5 Grep calls per entity. Plan searches before executing them.
## STEP 3: Entity Identification
Extract and list the **entities** involved in the query to generate.
### Workflow
1. Entity names must use PascalCase convention
2. If entity names are not provided and cannot be deduced from context, use the [Map Entity Names](#map-entity-names) sub-workflow
3. If entity names remain unresolved after mapping, ask the user for their names and wait for the answer
4. Do **not** resolve exact field names yet — that happens in [Step 4](#step-4-iterative-entity-introspection)
5. Report using the [Identification Report Template](#identification-report-template)
6. Evaluate [hard stop rules](#identification-hard-stop-rules)
### Map Entity Names
Extract entities from `schema.graphql`:
1. Use the Grep patterns from [Using Schema File](#using-schema-file), especially `^type <ObjectName> implements Record`
2. Build the candidate entity list from those type definitions
3. If some entities are still unresolved from user intent, ask the user for clarification and wait for their answer
### Identification Hard Stop Rules
If the unknown entity list is not empty:
- Set global step status to `FAILED`
- Stop generation
- Execute [remediation actions](#identification-remediation-actions)
### Identification Remediation Actions
**Condition**: Only if global step status is `FAILED`
Ask the user for clarification on unknown entities, then restart [Step 3](#step-3-entity-identification).
### Identification Report Template
```text
Identified entities:
- EntityName (Field1, Field2, ...)
Unknown entities:
- entity textual name
Step 3 status: SUCCESS | FAILED
```
## STEP 4: Iterative Entity Introspection
Extract and list entity **fields** involved in the query to generate.
### Introspection Workflow
**Iteration limit**: Maximum 3 introspection cycles (primary entity → references → child relationships). If unresolved entities remain after 3 cycles, **HARD STOP** and ask the user for clarification.
Using the list of entities from [Step 3](#step-3-entity-identification), follow these steps in order:
1. **Cleanup** — Remove from the list all entities for which introspection data was already retrieved
2. **Introspection** — Retrieve introspection data for all remaining entities:
- Use Grep patterns from [Using Schema File](#using-schema-file)
- If no introspection data is returned for an entity, **HARD STOP** — skip to step 8
3. **Fields identification** — Extract requested field types from introspection data
4. **Reference fields** — Identify reference fields from the schema (e.g., `Owner: User`):
- Two fields with the same name on different entities may have different types — always check each entity independently
- **Polymorphic fields** — If a reference field resolves to multiple entity types in the schema, mark it as polymorphic. In the generated query, use inline fragments (`... on TypeA`, `... on TypeB`) to access type-specific fields
- Add any newly discovered entity types to the unknown list
5. **Child relationships** — Identify child relationships (Connection types, e.g., `Contacts: ContactConnection`):
- Add any newly discovered child entity types to the unknown list
6. **Next cycle** — If the unknown list is not empty and the iteration limit has not been reached, resume from step 1
7. **Field type information** — All type info must come from `schema.graphql` introspection data
8. **Report** — Use the [Introspection Report Template](#introspection-report-template)
9. **Evaluate** — Apply [hard stop rules](#introspection-hard-stop-rules)
### Introspection Hard Stop Rules
If the global status is `FAILED`, **STOP** generation and execute [introspection remediation actions](#introspection-remediation-actions).
### Introspection Remediation Actions
**Condition**: Only if global step status is `FAILED`
Ask the user for clarification on unknown fields, then resume the [Introspection Workflow](#introspection-workflow).
### Introspection Report Template
An entity is `[PASS]` only if it has no unknown fields, otherwise it is `[FAIL]`.
If any entity is `[FAIL]`, the global status is `FAILED`.
```text
Introspection results:
[PASS|FAIL] EntityName
- Standard fields: FieldName (type), ...
- Reference fields: FieldName → TargetEntityType, ...
- Polymorphic fields: FieldName → [TypeA, TypeB], ...
- Child relationships: RelationshipName → ChildEntityType, ...
- Unknown fields: FieldName, ...
[PASS|FAIL] EntityName2
- ...
Introspection cycles used: N/3
Step 4 status: SUCCESS | FAILED
```
## STEP 5: Read Query Generation
**Triggering conditions:**
1. [Step 4](#step-4-iterative-entity-introspection) global status is `SUCCESS`
2. Query type is `read`
### Workflow
1. Apply the read-query authoring rules in [generation-query.md](generation-query.md), feeding in the introspection data, entity list, field types, output format (standalone or LWC), and `usernameOrAlias` from previous steps
2. If a rule cannot be satisfied (e.g. a field type cannot be resolved), report the gap and ask the user how to proceed
3. Present the generated query to the user using the output format selected in Step 1
4. Execute the testing workflow at the end of [generation-query.md](generation-query.md) using `scripts/test-lds-graphql-query.sh`
## STEP 6: Mutation Query Generation
**Triggering conditions:**
1. [Step 4](#step-4-iterative-entity-introspection) global status is `SUCCESS`
2. Query type is `mutation`
### Workflow
1. Apply the mutation-query authoring rules in [generation-mutation.md](generation-mutation.md), feeding in the introspection data, entity list, field types, output format (standalone or LWC), and `usernameOrAlias` from previous steps
2. If a rule cannot be satisfied (e.g. a required `Create` field is missing or non-creatable), report the gap and ask the user how to proceed
3. Present the generated query to the user using the output format selected in Step 1
4. Execute the testing workflow at the end of [generation-mutation.md](generation-mutation.md) using `scripts/test-lds-graphql-query.sh`
## Error Reporting Rules
When reporting errors from schema acquisition, introspection, or query generation:
1. **Categorize, don't echo** — Report error category and short impact, not raw command/tool output
2. **Actionable remediation** — Provide a next action as a safe retry path (confirm org alias, retry tool call, re-check schema file path)
references/generation-mutation.md
# GraphQL Mutation Query Generation
**Triggering conditions**
1. Only after Steps 1–4 of [generation-guide.md](generation-guide.md) have completed successfully (introspection produced a `SUCCESS` status with no unknown entities or fields)
2. Only if the query to generate is a mutation query
## Your Role
You are a GraphQL expert and your role is to help generate Salesforce-compatible GraphQL mutation queries once the exploration phase has completed.
If the introspection workflow in [generation-guide.md](generation-guide.md) has not been executed yet, you **MUST** run it first, and then return to mutation query generation.
You will leverage the context provided by the requesting user as well as the validated introspection results from [generation-guide.md](generation-guide.md) to generate the queries, and then test them against a live org using the bundled `scripts/test-lds-graphql-query.sh` script.
## Mutation Queries General Information
**IMPORTANT**:
1. **Mutation Types**: The GraphQL engine supports `Create`, `Update` and `Delete` operations
2. **Id Based Mutations**: `Update` and `Delete` operations operate on Id-based entity identification
3. **Mutation Schema**: Defined in the [mutation query schema](#mutation-query-schema) section
## Mutation Query Generation Workflow
Strictly follow the rules below when generating the GraphQL mutation query:
1. **Input Fields Validation** - Validate that the set of fields validate [input field constraints](#mutation-queries-input-field-constraints)
2. **Output Fields Validation** - Validate that the set of fields used in the select part of the query validate the [output fields constraints](#mutation-queries-output-field-constraints)
3. **Type Consistency** - Make sure variables used as query arguments and their related fields share the same GraphQL type
4. **Report Phase** - Use the [Mutation Query Report Template](#mutation-query-report-template) below to report on the previous validation phases
5. **Input Arguments** - `input` is the default name for the argument, unless otherwise specified
6. **Output Field** - For `Create` and `Update` operations, the output field is always named `Record`, and is of type EntityName
7. **Mutations Chaining** - It is possible to chain mutations in a single query using the [Mutations Chaining](#mutations-chaining) instructions
8. **Query Generation** - Use the [mutation query](#mutation-query-templates) template and adjust it based on the selected operation
9. **Output Format** - Use the [standalone](#mutation-standalone-default-output-format---clean-code-only) or [LWC](#mutation-lwc-integration-only-when-requested-output-format) output format templates based on desired output
10. **Report** - Always report, using the proper output format, before testing
11. **Test the Query** - Use the [Generated Mutation Query Testing](#generated-mutation-query-testing) workflow to test the generated query
## Mutation Query Schema
**Important**: In the schema fragments below, replace **EntityName** occurrences by the real entity name (i.e. Account, Case...).
**Important**: `Delete` operations all share the same generic `Record` entity name for both input and payload, only exposing the standard `Id` field.
```graphql
input EntityNameCreateRepresentation {
# Subset of EntityName fields here
}
input EntityNameCreateInput { EntityName: EntityNameCreateRepresentation! }
type EntityNameCreatePayload { Record: EntityName! }
input EntityNameUpdateRepresentation {
# Subset of EntityName fields here
}
input EntityNameUpdateInput { Id: IdOrRef! EntityName: EntityNameUpdateRepresentation! }
type EntityNameUpdatePayload { Record: EntityName! }
input RecordDeleteInput { Id: IdOrRef! }
type RecordDeletePayload { Id: ID }
type UIAPIMutations {
EntityNameCreate(input: EntityNameCreateInput!): EntityNameCreatePayload
EntityNameDelete(input: RecordDeleteInput!): RecordDeletePayload
EntityNameUpdate(input: EntityNameUpdateInput!): EntityNameUpdatePayload
}
```
## Mutation Queries Input Field Constraints
1. **`Create` Mutation Queries**:
1. **MUST** include all required fields, unless they have `defaultedOnCreate` set to `true` and are not explicitly requested
2. **MUST** only include `createable` fields
3. Child relationships can't be set and **MUST** be excluded
4. Fields with type `REFERENCE` can only be assigned IDs through their `ApiName` name
2. **`Update` Mutation Queries**:
1. **MUST** include the id of the entity to update
2. **MUST** only include `updateable` fields
3. Child relationships can't be set and **MUST** be excluded
4. Fields with type `REFERENCE` can only be assigned IDs through their `ApiName` name
3. **`Delete` Mutation Queries**:
1. **MUST** include the id of the entity to delete
## Mutation Queries Output Field Constraints
1. **`Create` and `Update` Mutation Queries**:
1. **MUST** exclude all child relationships
2. **MUST** exclude all `REFERENCE` fields, unless accessed through their `ApiName` member (no navigation to referenced entity)
3. Inaccessible fields will be reported as part of the `errors` attribute in the returned payload
4. Child relationships **CAN'T** be queried as part of a mutation
5. Fields with type `REFERENCE` can only be queried through their `ApiName` (no referenced entities navigation, no sub fields)
2. **`Delete` Mutation Queries**:
1. **MUST** only include the `Id` field
## Mutations Chaining
It is possible to chain related mutations in a single query, using references to `Id` resulting from a previous mutation execution:
1. **Ordering** - Mutation `B` can reference mutation `A` only if mutation `A` comes first in the query
2. **Notation** - Use `SomeId: "@{A}"` in mutation `B` to set the `SomeId` field value to the `Id` produced by mutation `A` execution
3. **IDs only** - This only works with IDs, and `@{A}` will always be interpreted as the `Id` produced by mutation `A`
4. **Restrictions** - `A` needs to be either a `Create` or `Delete` mutation, this will fail for `Update`
## Mutation Query Report Template
Input arguments:
- Required fields: FieldName1 (Type1), FieldName2 (Type2)...
- Other fields: FieldName3 (Type3)...
- Output fields: FieldNameA (TypeA), FieldNameB (TypeB)...
## Mutation Query Templates
```graphql
mutation mutateEntityName(
# arguments
) {
uiapi {
EntityNameOperation(input: {
# the following is for `Create` and `Update` operations only
EntityName: {
# Input fields
}
# the following is for `Update` and `Delete` operations only
Id: ... # id here
}) {
# the following is for `Create` and `Update` operations only
Record {
# Output fields
}
# the following is for `Delete` operations only
Id: ... # id here
}
}
}
```
## Mutation Standalone (Default) Output Format - CLEAN CODE ONLY
If generating for LWC, the variable should provide the entire `input` argument:
```javascript
const QUERY_NAME = `
mutation mutateEntity($input: EntityNameOperationInput!) {
uiapi {
EntityNameOperation(input: $input) {
# select output fields here depending on operation type
}
}
}
`;
const QUERY_VARIABLES = {
input: {
// The following is for `Create` and `Update` operations only
EntityName: {
// variables here
},
// The following is for `Update` and `Delete` operations only
Id: ... // id here
}
};
```
**DO NOT INCLUDE:**
- Explanatory comments about the query
- Field descriptions
- Additional text about what the query does
- Workflow step descriptions
**ONLY INCLUDE:**
- Raw query string
- Variables object
- Nothing else
## Mutation LWC Integration (Only When Requested) Output Format
When integrating within an LWC:
1. You must use the `executeMutation` imperative adapter
2. The first parameter must be the result of calling the `gql` tag function on the query
3. Both the `executeMutation` imperative adapter and the `gql` tag function must be imported from the `lightning/graphql` package
```javascript
import { LightningElement, wire } from 'lwc';
import { gql, executeMutation } from 'lightning/graphql';
export default class MyComponent extends LightningElement {
get mutationQuery() {
return gql`
mutation operationEntityName {
// mutation query here
}
`;
}
async mutateEntity() {
// executeMutation takes the mutation document as its FIRST argument and
// the options ({ variables }) as its SECOND. Passing a single
// { query, variables } object leaves the document undefined.
const result = await executeMutation(this.mutationQuery, {
variables: {
// variables here
},
});
// do something with the result
}
}
```
**DO:**
- use the `executeMutation` imperative adapter to execute the query and retrieve the result
- use the `gql` tag function to wrap the GraphQL query before passing it to the imperative adapter
**DO NOT:**
- don't attempt to use the `executeMutation` adapter in a wired way
## Generated Mutation Query Testing
**Triggering conditions** - **ALL CONDITIONS MUST VALIDATE\***
1. Only if the [Mutation Query Generation Workflow](#mutation-query-generation-workflow) step global status is `SUCCESS` and you have a generated query
2. Only if the query to generate is a mutation query
You will use the `scripts/test-lds-graphql-query.sh` script to test the query, passing the confirmed alias, the generated query, and related variables (if any).
**Workflow**
1. **Report Step** - Explain that you are able to test the query using the same method used during introspection
1. You **MUST** report the method you will use, based on the org alias confirmed during Step 2 of [generation-guide.md](generation-guide.md)
2. **Interactive Step** - Ask the user whether they want you to test the query using the proposed method
1. **WAIT** for the user's answer.
3. **Input Arguments** - You **MUST** ask the user for the input arguments to use
1. **WAIT** for the user's answer.
4. **Test Query** - If the user are OK with you testing the query:
1. Run `scripts/test-lds-graphql-query.sh USERNAME_OR_ALIAS 'QUERY' '<VARIABLES_JSON>'` to test the query
5. **Result Analysis** - Retrieve the `data` and `errors` attributes from the returned payload, and report the result of the test as one of the following options:
1. `PARTIAL` if `data` is not an empty object, but `errors` is not an empty list - Explanation: some of the queried fields are not accessible on mutations
2. `FAILED` if `data` is an empty object - Explanation: the query is not valid
3. `SUCCESS` if `errors` is an empty list
6. **Remediation Step** - If status is not `SUCCESS`, use the [`FAILED`](#failed-status-handling-workflow) or [`PARTIAL`](#partial-status-handling-workflow) 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:
- **Execution** - Error contains `invalid cross reference id` or `entity is deleted`
- **Syntax** - Error contains `invalid syntax`
- **Validation** - Error contains `validation error`
- **Type** - Error contains `VariableTypeMismatch` or `UnknownType`
- **Navigation** - Error contains `is not currently available in mutation results`
- **API Version** - Query deals with updates, you're testing with Connect API and error contains `Cannot invoke JsonElement.isJsonObject()`
3. **Targeted Resolution** - Depending on the root cause categorization
- **Execution** - You're trying to update or delete an unknown/no longer available entity: either create an entity first, if you have generated the related query, or ask for a valid entity id to use
- **Syntax** - Update the query using the error message information to fix the syntax errors
- **Validation** - This field's name is most probably invalid, ask user for clarification and **WAIT** for the user's answer
- **Type** - Use the error details and GraphQL schema to correct argument's type, and adjust variables accordingly
- **Navigation** - Use the [`PARTIAL` status handling workflow](#partial-status-handling-workflow) below
- **API Version** - `Record` selection is only available with API version 64 and higher, **report** the issue, and try again with API version 64
4. **Test Again** - Resume the [query testing workflow](#generated-mutation-query-testing) 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, going again through the introspection phase
### `PARTIAL` Status Handling Workflow
The query can be improved:
1. Report the fields mentioned in the `errors` list
2. Explain that these fields can't be queried as part of a mutation query
3. Explain that the query might be considered as failing, as it will report errors
4. Offer to remove the offending fields
5. **WAIT** for the user's answer
6. If they are OK with removing the fields restart the [generation workflow](#mutation-query-generation-workflow) with the new field list
references/generation-query.md
# GraphQL Read Query Generation
**Triggering conditions**
1. Only after Steps 1–4 of [generation-guide.md](generation-guide.md) have completed successfully (introspection produced a `SUCCESS` status with no unknown entities or fields)
2. Only if the query to generate is a read query
## Your Role
You are a GraphQL expert and your role is to help generate Salesforce-compatible GraphQL read queries once the exploration phase has completed.
If the introspection workflow in [generation-guide.md](generation-guide.md) has not been executed yet, you **MUST** run it first, and then return to read query generation.
You will leverage the context provided by the requesting user as well as the validated introspection results from [generation-guide.md](generation-guide.md) to generate the queries, and then test them against a live org using the bundled `scripts/test-lds-graphql-query.sh` script.
## 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
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
6. **Type Enforcement** - Make sure to leverage field type information from introspection and GraphQL schema to generate field access
7. **Semi and anti joins** - Use the semi-join or anti-join templates to filter an entity with conditions on child entities
8. **Query Generation** - Use the [template](#read-query-template) to generate the query
1. **Exception** - if you're generating a query to retrieve **current user**, **connected user** or **authenticated user** information, you must use the `uiapi.currentUser` field, which takes **no arguments**, and returns a `User` field
9. **Output Format** - Use the [standalone](#read-standalone-default-output-format---clean-code-only) or [LWC](#read-lwc-integration-only-when-requested-output-format) output format templates based on desired output
10. **Test the Query** - Use the [Generated Read Query Testing](#generated-read-query-testing) workflow to test the generated query
1. **Report First** - Always report first, using the proper output format, before testing
## Read Query Template
```graphql
query QueryName {
uiapi {
query {
EntityName(
# conditions here
) {
edges {
node {
# Direct fields
FieldName { value }
# Non-polymorphic reference (single type)
RelationshipName {
Id
Name { value }
}
# Polymorphic reference (multiple types)
PolymorphicRelationshipName {
...TypeAInfo
...TypeBInfo
}
# Child relationship (subquery)
RelationshipName(
# conditions here
) {
edges {
node {
# fields
}
}
}
}
}
}
}
}
}
fragment TypeAInfo on TypeA {
Id
SpecificFieldA { value }
}
fragment TypeBInfo on TypeB {
Id
SpecificFieldB { 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
```graphql
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 {
value
}
}
}
}
}
}
}
```
### Anti-Join Example - ParentEntity with no Matching ChildEntity
Same example as the [Semi-Join Example](#semi-join-example---parententity-with-at-least-one-matching-childentity), but replacing the `inq` operator by the `ninq` one.
## Read Standalone (Default) Output Format - CLEAN CODE ONLY
```javascript
const QUERY_NAME = `
query GetData {
// query here
}
`;
const QUERY_VARIABLES = {
// variables here
};
```
**DO NOT INCLUDE:**
- Explanatory comments about the query
- Field descriptions
- Additional text about what the query does
- Workflow step descriptions
**ONLY INCLUDE:**
- Raw query string
- Variables object
- Nothing else
## Read LWC Integration (Only When Requested) Output Format
When integrating within an LWC:
1. You must use the `graphql` wire adapter
2. The first parameter must be the result of calling the `gql` tag function on the query
3. Both the `graphql` wire adapter and the `gql` tag function must be imported from the `lightning/graphql` package
```javascript
import { LightningElement, wire } from 'lwc';
import { gql, graphql } from 'lightning/graphql';
export default class MyComponent extends LightningElement {
get queryDef() {
return gql`
query getEntityName {
// query here
}
`;
}
@wire(graphql, {
query: '$queryDef',
variables: '$queryVariables',
})
wiredData({ data, error }) {
// handle result
}
}
```
**DO:**
- use the `graphql` wire adapter to execute the query and wire the handling function
- use the `gql` tag function to wrap the GraphQL query before passing it to the wire adapter
**DO NOT:**
- don't attempt to use the `graphql` adapter in an imperative way
## Generated Read Query Testing
**Triggering conditions** - **ALL CONDITIONS MUST VALIDATE\***
1. Only if the [Read Query Generation Workflow](#read-query-generation-workflow) step global status is `SUCCESS` and you have a generated query
2. Only if the query to generate is a read query
You will use the `scripts/test-lds-graphql-query.sh` script to test the query, passing the confirmed alias, the generated query, and related variables (if any).
**Workflow**
1. **Report Step** - Explain that you are able to test the query using the same method used during introspection
1. You **MUST** report the method you will use, based on the org alias confirmed during Step 2 of [generation-guide.md](generation-guide.md)
2. **Interactive Step** - Ask the user whether they want you to test the query using the proposed method
1. **WAIT** for the user's answer.
3. **Test Query** - If the user are OK with you testing the query:
1. Run `scripts/test-lds-graphql-query.sh USERNAME_OR_ALIAS 'QUERY' '<VARIABLES_JSON>'` to test the query
2. Report the result of the test as `SUCCESS` if the query executed without error, or `FAILED` if you got errors
3. If the query executed without any errors, but you received no data, then the query is valid, and the result of the test is `SUCCESS`
4. **Remediation Step** - If status is `FAILED`, use the [`FAILED` status handling workflows](#failed-status-handling-workflow)
### `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** - This field's name is most probably invalid, ask user for clarification and **WAIT** for the user's answer
- **Type** - Use the error details and GraphQL schema to correct argument's type, and adjust variables accordingly
4. **Test Again** - Resume the [query testing workflow](#generated-read-query-testing) 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, going again through the introspection phase
scripts/fetch-lds-graphql-schema.sh
#!/usr/bin/env bash
#
# LDS GraphQL Schema Fetcher
#
# Fetches the Salesforce LDS GraphQL schema for a connected org by POSTing a
# full introspection query to /services/data/vX/graphql, then renders the
# response as SDL on disk so the skill can introspect it with targeted grep
# calls. (LDS does not expose a /graphql/sdl endpoint — introspection is the
# only path.)
#
# The schema content never enters the chat context — only the file path is
# returned to the user.
#
# Usage:
# fetch-lds-graphql-schema.sh USERNAME_OR_ALIAS [OUTPUT_PATH] [API_VERSION]
#
# Arguments:
# $1 usernameOrAlias SF CLI alias or username for the target org (required)
# $2 outputPath File to write SDL to (default: schema.graphql)
# $3 apiVersion API version (default: 62.0, matching the legacy
# fetch_lds_graphql_schema MCP tool's DEFAULT_API_VERSION)
#
# Exit codes:
# 0 Schema written successfully
# 1 Argument error
# 2 Salesforce CLI missing
# 3 Auth or HTTP failure
# 4 Schema response was empty, contained errors, or had no types
set -euo pipefail
if [ $# -lt 1 ]; then
echo "Usage: $0 USERNAME_OR_ALIAS [OUTPUT_PATH] [API_VERSION]" >&2
exit 1
fi
ALIAS="$1"
OUTPUT_PATH="${2:-schema.graphql}"
API_VERSION="${3:-62.0}"
# Skip the fetch when a non-empty schema file is already on disk. Re-fetching
# is a 30-90s introspection round-trip, so a deterministic short-circuit keeps
# repeated invocations idempotent. Set LDS_FETCH_FORCE=1 to override.
if [ -s "$OUTPUT_PATH" ] && [ "${LDS_FETCH_FORCE:-0}" != "1" ]; then
echo "Schema already present at ${OUTPUT_PATH} — skipping introspection." >&2
echo "Set LDS_FETCH_FORCE=1 to re-fetch." >&2
exit 0
fi
# Output-path containment: refuse paths that escape the current working
# directory (e.g. ../../etc/passwd, /tmp/foo, symlinks pointing outside).
# Uses python3's os.path.realpath to resolve `..`, `.`, and symlinks even
# when intermediate directories don't exist yet — the previous bash-`pwd`
# approach was bypassable in two ways:
# 1. when the parent didn't exist, the fallback `$PWD/$(dirname ...)`
# kept `..` segments uncollapsed (e.g. `nonexistent/../../etc` would
# string-prefix-match `$PWD` even though it escapes);
# 2. bash's `cd` returns the *logical* path, so a symlink under `$PWD`
# pointing outside would prefix-match `$PWD` while the real target
# sat elsewhere on disk.
PWD_REAL=$(python3 -c 'import os; print(os.path.realpath("."))')
OUTPUT_REAL=$(python3 -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$OUTPUT_PATH")
case "$OUTPUT_REAL/" in
"$PWD_REAL"/*) ;;
*)
echo "ERROR: refusing to write to '$OUTPUT_PATH' — resolved path '$OUTPUT_REAL' escapes \$PWD ($PWD_REAL)." >&2
echo " Pass an output path that resolves under the current working directory." >&2
exit 1
;;
esac
if ! command -v sf &>/dev/null; then
echo "ERROR: Salesforce CLI (sf) is not installed." >&2
echo "Install: https://developer.salesforce.com/tools/salesforcecli" >&2
exit 2
fi
GRAPHQL_PATH="/services/data/v${API_VERSION}/graphql"
echo "Fetching LDS GraphQL schema for org '${ALIAS}' (api v${API_VERSION})..." >&2
echo "Introspection can take 30-90s for large orgs." >&2
INTROSPECTION_QUERY='{"query":"query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { kind name description fields(includeDeprecated: true) { name description args { name description type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } defaultValue } type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } isDeprecated deprecationReason } inputFields { name description type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } defaultValue } interfaces { kind name ofType { kind name } } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { kind name } } } }","variables":{},"operationName":"IntrospectionQuery"}'
INTROSPECTION_FILE=$(mktemp)
trap 'rm -f "$INTROSPECTION_FILE"' EXIT
# Use `sf api request rest`, which authenticates internally from the CLI's
# stored credentials — the access token never enters this script's process
# arguments, environment, or shell history (Prizm S1). Body is passed via a
# temp file (`--body @file`) so the query text also stays out of the argv.
BODY_FILE=$(mktemp)
trap 'rm -f "$INTROSPECTION_FILE" "$BODY_FILE"' EXIT
printf '%s' "$INTROSPECTION_QUERY" >"$BODY_FILE"
if ! sf api request rest "$GRAPHQL_PATH" \
--target-org "$ALIAS" \
--method POST \
--header "Content-Type: application/json" \
--header "X-Chatter-Entity-Encoding: false" \
--body "@${BODY_FILE}" \
>"$INTROSPECTION_FILE" 2>"${INTROSPECTION_FILE}.err"; then
# `sf api request rest` exits non-zero on auth failure or HTTP >= 400.
# Don't echo the response/error body — Salesforce error payloads can carry
# request headers or signed front-door URLs we don't want in shared logs.
echo "ERROR: Introspection request failed for org '${ALIAS}'." >&2
echo " If authentication expired, re-authenticate: sf org login web --alias $ALIAS" >&2
echo " Diagnostic output left at: ${INTROSPECTION_FILE}.err" >&2
echo " Inspect with: head -c 500 \"${INTROSPECTION_FILE}.err\" (and remove when done)" >&2
trap 'rm -f "$BODY_FILE"' EXIT # leave response + err in place for the user
exit 3
fi
rm -f "${INTROSPECTION_FILE}.err"
if [ ! -s "$INTROSPECTION_FILE" ]; then
echo "ERROR: Empty introspection response." >&2
exit 4
fi
OUTPUT_DIR="$(dirname "$OUTPUT_PATH")"
[ -n "$OUTPUT_DIR" ] && mkdir -p "$OUTPUT_DIR"
python3 - "$INTROSPECTION_FILE" "$OUTPUT_PATH" <<'PYEOF'
import json, sys
intro_path, out_path = sys.argv[1], sys.argv[2]
with open(intro_path) as f:
payload = json.load(f)
errors = payload.get("errors") or []
schema = (payload.get("data") or {}).get("__schema") or {}
types = schema.get("types") or []
if errors:
print(f"ERROR: introspection returned {len(errors)} errors. First: {errors[0]}", file=sys.stderr)
sys.exit(4)
if not types:
print("ERROR: introspection returned zero types.", file=sys.stderr)
sys.exit(4)
def render_type_ref(t):
"""Recursively render a type reference like NonNull(List(NonNull(Account)))."""
if t is None:
return "Unknown"
kind = t.get("kind")
if kind == "NON_NULL":
return render_type_ref(t.get("ofType")) + "!"
if kind == "LIST":
return "[" + render_type_ref(t.get("ofType")) + "]"
return t.get("name") or "Unknown"
def render_args(args):
if not args:
return ""
rendered = []
for a in args:
s = f"{a['name']}: {render_type_ref(a.get('type'))}"
if a.get("defaultValue") is not None:
s += f" = {a['defaultValue']}"
rendered.append(s)
return "(" + ", ".join(rendered) + ")"
def render_fields(fields):
out = []
for f in fields or []:
line = f" {f['name']}{render_args(f.get('args'))}: {render_type_ref(f.get('type'))}"
if f.get("isDeprecated"):
reason = (f.get("deprecationReason") or "").replace('"', '\\"')
line += f' @deprecated(reason: "{reason}")'
out.append(line)
return "\n".join(out)
def render_input_fields(fields):
out = []
for f in fields or []:
line = f" {f['name']}: {render_type_ref(f.get('type'))}"
if f.get("defaultValue") is not None:
line += f" = {f['defaultValue']}"
out.append(line)
return "\n".join(out)
# Render in stable order; skip GraphQL builtin __ types (introspection meta-types).
chunks = []
for t in sorted(types, key=lambda x: (x.get("name") or "")):
name = t.get("name") or ""
kind = t.get("kind")
if name.startswith("__"):
continue
if kind == "OBJECT":
interfaces = t.get("interfaces") or []
impl = ""
if interfaces:
impl = " implements " + " & ".join(i["name"] for i in interfaces if i.get("name"))
body = render_fields(t.get("fields"))
chunks.append(f"type {name}{impl} {{\n{body}\n}}")
elif kind == "INTERFACE":
body = render_fields(t.get("fields"))
chunks.append(f"interface {name} {{\n{body}\n}}")
elif kind == "INPUT_OBJECT":
body = render_input_fields(t.get("inputFields"))
chunks.append(f"input {name} {{\n{body}\n}}")
elif kind == "ENUM":
values = "\n".join(f" {v['name']}" for v in (t.get("enumValues") or []))
chunks.append(f"enum {name} {{\n{values}\n}}")
elif kind == "UNION":
members = " | ".join(p["name"] for p in (t.get("possibleTypes") or []) if p.get("name"))
chunks.append(f"union {name} = {members}")
elif kind == "SCALAR":
# Skip the standard scalars; emit only org-defined ones.
if name in ("String", "Int", "Float", "Boolean", "ID"):
continue
chunks.append(f"scalar {name}")
with open(out_path, "w") as f:
f.write("\n\n".join(chunks))
f.write("\n")
# Sanity: count Record-implementing types so the caller can sanity-check the output.
record_count = sum(1 for t in types if any((i.get("name") == "Record") for i in (t.get("interfaces") or [])))
print(f"types_total={len(types)} types_implementing_Record={record_count}", file=sys.stderr)
PYEOF
LINE_COUNT=$(wc -l <"$OUTPUT_PATH" | tr -d ' ')
echo "Schema written to ${OUTPUT_PATH} (${LINE_COUNT} lines)." >&2
scripts/test-lds-graphql-query.sh
#!/usr/bin/env bash
#
# LDS GraphQL Query Tester
#
# Executes a GraphQL query or mutation against the LDS endpoint of a connected
# Salesforce org and prints the raw JSON response. Use this to validate a
# generated query end-to-end before handing it to the developer.
#
# Usage:
# test-lds-graphql-query.sh USERNAME_OR_ALIAS 'GRAPHQL_QUERY' ['VARIABLES_JSON'] [API_VERSION] [OPERATION_NAME]
#
# Arguments:
# $1 usernameOrAlias SF CLI alias or username for the target org (required)
# $2 query Complete GraphQL query or mutation string (required)
# $3 variables Variables JSON object (default: {})
# $4 apiVersion API version (default: 66.0, matching the legacy
# test_lds_graphql_query MCP tool's DEFAULT_API_VERSION)
# $5 operationName Operation to run when the query string defines
# multiple named operations (default: omitted)
#
# Exit codes:
# 0 Query executed and returned HTTP 200 (the response itself may still
# contain a GraphQL `errors` field; the caller categorizes those)
# 1 Argument error
# 2 Salesforce CLI missing
# 3 Auth or HTTP failure
set -euo pipefail
if [ $# -lt 2 ]; then
echo "Usage: $0 USERNAME_OR_ALIAS 'GRAPHQL_QUERY' ['VARIABLES_JSON'] [API_VERSION] [OPERATION_NAME]" >&2
exit 1
fi
ALIAS="$1"
QUERY="$2"
VARIABLES="${3-}"
[ -z "$VARIABLES" ] && VARIABLES='{}'
API_VERSION="${4:-66.0}"
OPERATION_NAME="${5-}"
# Mutation guard: this script's name says "tester" — by default we refuse to
# execute write operations (`mutation`) since they have side effects on the
# connected org. Set LDS_ALLOW_MUTATIONS=1 to opt in when intentionally
# testing a mutation.
#
# Detection runs in python3 so we can:
# 1. Strip line comments (`# ...`) before scanning, so a `# mutation note`
# comment doesn't trip the guard.
# 2. Anchor on operation-start (`^mutation` or `}\smutation`), not bash
# word-class boundaries — bash's `[[:space:]]|\{|$` boundary class
# missed `mutation(...)` (variables), `mutation@auth` (directives),
# and `mutation#comment`, all of which are valid GraphQL.
# 3. Avoid the `query { mutation: get }` field-alias false positive:
# `mutation` only counts when followed by name / vars / directive /
# `{`, not by `:` (alias) or `=` (default value).
if [ "${LDS_ALLOW_MUTATIONS:-0}" != "1" ]; then
if ! printf '%s' "$QUERY" | python3 -c '
import re, sys
src = sys.stdin.read()
# Strip GraphQL line comments (`# ...` to end of line) before scanning.
src = re.sub(r"#[^\n]*", "", src)
# Trim leading whitespace so the `^` anchor below sees the first real
# token. Heredocs, file-read queries, and multiline shell strings often
# carry leading newlines/spaces that would otherwise hide a top-level
# `mutation` from the `^mutation` branch.
src = src.lstrip()
# Match `mutation` only at operation-start: either at the start of the
# stripped source, or after a `}` that closes a previous operation. The
# trailing class accepts every valid follow-up for a mutation operation:
# identifier (name), `(` (variables), `@` (directive), `{` (anonymous
# body), whitespace, or end-of-input. This deliberately excludes `:`
# (field alias, e.g. `query { mutation: get }`) and `=` (default value).
if re.search(r"(?:^|\}\s*)mutation(?:\s|$|[A-Za-z_(@{])", src):
sys.exit(2)
sys.exit(0)
'; then
echo "ERROR: refusing to execute a mutation — this script is a read-only tester by default." >&2
echo " To intentionally run a mutation, re-invoke with LDS_ALLOW_MUTATIONS=1:" >&2
echo " LDS_ALLOW_MUTATIONS=1 $0 ..." >&2
exit 1
fi
fi
if ! command -v sf &>/dev/null; then
echo "ERROR: Salesforce CLI (sf) is not installed." >&2
exit 2
fi
GRAPHQL_PATH="/services/data/v${API_VERSION}/graphql"
REQUEST_BODY=$(python3 -c '
import sys, json
body = {"query": sys.argv[1], "variables": json.loads(sys.argv[2].strip())}
if sys.argv[3]:
body["operationName"] = sys.argv[3]
print(json.dumps(body))
' "$QUERY" "$VARIABLES" "$OPERATION_NAME")
RESPONSE_FILE=$(mktemp)
BODY_FILE=$(mktemp)
trap 'rm -f "$RESPONSE_FILE" "$BODY_FILE"' EXIT
printf '%s' "$REQUEST_BODY" >"$BODY_FILE"
# Use `sf api request rest`, which authenticates internally from the CLI's
# stored credentials — the access token never enters this script's process
# arguments, environment, or shell history (Prizm S1). Body is passed via a
# temp file (`--body @file`) so the query text also stays out of the argv.
if ! sf api request rest "$GRAPHQL_PATH" \
--target-org "$ALIAS" \
--method POST \
--header "Content-Type: application/json" \
--header "X-Chatter-Entity-Encoding: false" \
--body "@${BODY_FILE}" \
>"$RESPONSE_FILE" 2>"${RESPONSE_FILE}.err"; then
# `sf api request rest` exits non-zero on auth failure or HTTP >= 400.
# Don't echo the response/error body — Salesforce error payloads can carry
# request headers or signed URLs we don't want surfacing in shared logs.
echo "ERROR: GraphQL request failed for org '${ALIAS}'." >&2
echo " If authentication expired, re-authenticate: sf org login web --alias $ALIAS" >&2
echo " Diagnostic output left at: ${RESPONSE_FILE}.err" >&2
echo " Inspect with: head -c 500 \"${RESPONSE_FILE}.err\" (and remove when done)" >&2
trap 'rm -f "$BODY_FILE"' EXIT # leave response + err in place for the user
exit 3
fi
rm -f "${RESPONSE_FILE}.err"
cat "$RESPONSE_FILE"
SKILL.md
---
name: experience-lds-graphql-generate
description: "Use ALWAYS when a prompt mentions GraphQL, lightning/uiGraphQLApi, @wire(graphql, ...), or gql template tags in an LWC context — even if the surface ask is \"build an LWC\". Owns the ENTIRE flow: introspect the org's LDS GraphQL schema, identify entities/fields, construct the schema-validated gql query or mutation, wire it into the LWC via lightning/uiGraphQLApi, verify against a connected org. REQUIRED whenever a prompt asks to render, list, or edit Salesforce records (Account, Contact, Case, custom objects) via GraphQL — the .html and .js scaffolding IS in scope. DO NOT delegate to experience-lwc-generate: that skill has NO GraphQL schema introspection, NO create_lds_graphql_read_query binding, and defaults to getRecord/getRelatedListRecords wire adapters which will not satisfy a GraphQL prompt. DO NOT TRIGGER only if the prompt forbids GraphQL, chooses UIAPI/Apex (use experience-lds-best-practices-apply), or asks for data requirements (use experience-lds-data-requirements-generate)."
metadata:
relatedSkills:
- "experience-lds-best-practices-apply"
- "experience-lds-data-requirements-generate"
- "experience-lwc-generate"
version: "1.0"
domains: ["Experience", "Platform"]
cliTools:
- tool: ["python3"]
semver: ">=3.8"
- tool: ["sf"]
semver: ">=2.0.0"
---
<!-- adk-managed-skill -->
# Building LDS GraphQL
Generate schema-validated Salesforce LDS GraphQL queries (read or mutation), either as standalone queries or wired into a Lightning Web Component via the `lightning/graphql` adapters. The skill encodes the schema-as-source-of-truth workflow end-to-end. Two bundled bash scripts handle the org-aware steps — `scripts/fetch-lds-graphql-schema.sh` for schema introspection and `scripts/test-lds-graphql-query.sh` for live-org validation.
## When to Use
- User wants to create, modify, or integrate a Salesforce GraphQL query (standard objects, custom objects, or setup objects).
- Building or updating an LWC that consumes/mutates LDS data through GraphQL.
- Introspecting an org's schema before writing a query.
- Validating a generated query end-to-end against a connected org.
Do NOT use this skill for:
- REST-based UI API (use LDS wire adapters; see `experience-lds-best-practices-apply`).
- Apex callouts or custom GraphQL endpoints — this skill is LDS-scoped.
- Data-requirements discovery — that's `experience-lds-data-requirements-generate`.
## Prerequisites
- A connected Salesforce org (username or alias). User must confirm it before schema fetch — never assume.
- Salesforce CLI / `sf` available in the shell for the schema fetch.
- Decision on:
- **Namespace** — `uiapi` (default: standard + custom objects) or `setup` (setup objects like permission sets, profiles).
- **Query type** — `read` (default) or `mutation`.
- **Output format** — `standalone` (raw GraphQL + variables) or `LWC integration` (full component wiring).
## Core Rules
These apply to every step — they are the rules this skill enforces:
1. **Sequential execution** — Steps 1→6 run in order. Every step is mandatory unless its triggering conditions are not met.
2. **Hard stop on failure** — A failed step blocks subsequent steps until remediation is complete.
3. **Schema is the single source of truth** — Every entity name, field name, field type, and relationship must come from `schema.graphql` introspection. Never use common Salesforce knowledge (e.g., do not assume `Owner` is a `User` — it may be polymorphic).
4. **Report each step** — Use the provided templates before advancing.
5. **Error reporting** — Categorize errors; never echo raw tool output into the chat.
## Workflow
The full normative workflow lives in [references/generation-guide.md](references/generation-guide.md). Read it before starting. Read-query specifics are in [references/generation-query.md](references/generation-query.md); mutation specifics are in [references/generation-mutation.md](references/generation-mutation.md).
### Step 1 — General query information
Collect and echo back:
```text
Query type: [read | mutation]
Namespace: [uiapi | setup]
Output format: [standalone | LWC integration]
```
If any is unclear, ask once and wait.
### Step 2 — Acquire the schema
1. Ask for the `usernameOrAlias`. If a default is inferred from context, present it and wait for explicit confirmation.
2. Run `scripts/fetch-lds-graphql-schema.sh USERNAME_OR_ALIAS [OUTPUT_PATH] [API_VERSION]` with the confirmed alias. The script writes the SDL to `schema.graphql` (or the path you pass) so the schema never enters the chat context. If a non-empty `schema.graphql` already exists at `OUTPUT_PATH`, the script exits early (set `LDS_FETCH_FORCE=1` to re-fetch).
3. On failure: **hard stop**, report category, ask user to resolve org access, then retry.
#### Using the schema file
The schema is 265,000+ lines. **NEVER** read the whole file — use targeted `grep` calls only.
- Object type: `^type <ObjectName> implements Record` with `-A 100`.
- Filter: `^input <ObjectName>_Filter` with `-A 50`.
- OrderBy: `^input <ObjectName>_OrderBy` with `-A 30`.
- Mutation input: `^input <ObjectName>(Create|Update)Input` with `-A 50`.
**Search budget**: max 4–5 grep calls per entity. Plan before executing.
### Step 3 — Entity identification
1. Entity names are PascalCase.
2. If names aren't given, extract candidates from `^type <Name> implements Record` matches.
3. If any entity is still unresolved, ask the user and wait.
4. Report:
```text
Identified entities:
- EntityName (Field1, Field2, ...)
Unknown entities:
- <textual name>
Step 3 status: SUCCESS | FAILED
```
5. If `Unknown entities` is non-empty → status `FAILED` → ask for clarification → restart Step 3.
### Step 4 — Iterative entity introspection
Iteration limit: **3 cycles** (primary entity → references → child relationships). Hard-stop after 3.
Per cycle:
1. Remove already-introspected entities from the list.
2. Grep for the remaining entities' fields using the schema patterns above.
3. Extract standard field types.
4. Identify **reference fields** (`Owner: User`). Fields with the same name on different entities may have different types — check each entity independently. If a field resolves to multiple entity types, mark it **polymorphic** and plan to use inline fragments (`... on TypeA`, `... on TypeB`).
5. Identify **child relationships** (Connection types, e.g., `Contacts: ContactConnection`). Add new entities to the unknown list.
6. If unknown list not empty and iterations < 3, loop.
7. Report:
```text
[PASS|FAIL] EntityName
- Standard fields: FieldName (type), ...
- Reference fields: FieldName → TargetType, ...
- Polymorphic fields: FieldName → [TypeA, TypeB], ...
- Child relationships: RelationshipName → ChildType, ...
- Unknown fields: FieldName, ...
Introspection cycles used: N/3
Step 4 status: SUCCESS | FAILED
```
8. If any entity is `[FAIL]` → global `FAILED` → remediation → resume from cycle start.
### Step 5 — Read query generation (only if query type is `read`)
Author the read query per [references/generation-query.md](references/generation-query.md), feeding in the introspection data, entity list, field types, output format, and `usernameOrAlias`.
Apply the rules in [references/generation-query.md](references/generation-query.md) — covers:
- Query root / namespace selection (`uiapi.query` vs `setup.query`).
- Field selection discipline (ask only for fields actually needed — every field is a billable scan).
- Filter operators (`eq`, `ne`, `in`, `nin`, `gt`, `gte`, `lt`, `lte`, `like`, `contains`).
- OrderBy (per-field direction).
- Pagination (`first`, `after`, `last`, `before`; `edges.node`, `pageInfo`).
- Polymorphic inline fragments.
- Aliasing and variable placeholders.
- Standalone vs LWC output (wire adapter from `lightning/graphql`, `gql` tagged template, `refreshGraphQL` for imperative refresh).
If the tool returns an error, categorize it and ask the user how to proceed.
### Step 6 — Mutation query generation (only if query type is `mutation`)
Author the mutation per [references/generation-mutation.md](references/generation-mutation.md) — covers:
- `create`, `update`, `delete` operation shape.
- Input types (`<Entity>CreateInput`, `<Entity>UpdateInput`) discovered via the `input` grep pattern.
- Required vs optional fields (from schema's `!` annotation).
- Reference-field updates using `Id` only.
- Return selection — what to read back after the mutation to drive cache consistency.
- Error handling (`record.errors[]`).
- LWC integration: imperative mutation via `graphqlMutate` from `lightning/graphql`.
### Step 7 — Test the query
Run `scripts/test-lds-graphql-query.sh USERNAME_OR_ALIAS 'QUERY' '<VARIABLES_JSON>'` against the confirmed `usernameOrAlias` and present the response shape/sample to the user. Errors are categorized, not echoed verbatim.
## Cross-References
- Bundled scripts:
- `scripts/fetch-lds-graphql-schema.sh` — schema acquisition via a GraphQL introspection query against the org's `/services/data/vX/graphql` endpoint (LDS exposes no `/graphql/sdl` route); call once per org/session before query authoring.
- `scripts/test-lds-graphql-query.sh` — org-backed validation of the generated query against `/services/data/vX/graphql`.
- Related skills:
- `experience-lds-best-practices-apply` — general LDS principles, cache semantics, and wire-vs-imperative choice.
- `experience-lds-data-requirements-generate` — pre-work that decides *what* to query before this skill decides *how*.
- `experience-lwc-generate` — host the generated wire adapter cleanly.
## Examples
**Standalone read — minimal**
```graphql
query Accounts($limit: Int = 10) {
uiapi {
query {
Account(first: $limit) {
edges {
node {
Id
Name { value }
}
}
}
}
}
}
```
**LWC integration — read with wire**
```javascript
import { LightningElement, wire } from 'lwc';
import { gql, graphql } from 'lightning/graphql';
export default class AccountList extends LightningElement {
@wire(graphql, {
query: gql`
query Accounts($limit: Int = 10) {
uiapi {
query {
Account(first: $limit) {
edges { node { Id Name { value } } }
}
}
}
}
`,
variables: '$variables'
})
accounts;
variables = { limit: 10 };
get records() {
return this.accounts?.data?.uiapi?.query?.Account?.edges ?? [];
}
}
```
## Verification
- `Step 3 status: SUCCESS` before Step 4; `Step 4 status: SUCCESS` before Steps 5/6.
- Every field in the generated query appears in the introspection report (no hallucinated fields).
- Polymorphic fields use inline fragments; non-polymorphic fields do not.
- For mutations, every required input field (`!` in schema) is present.
- `scripts/test-lds-graphql-query.sh` returns without errors; or, on error, a categorized remediation is presented.
- If output format is `LWC integration`, the component imports from `lightning/graphql`, uses `gql` tagged template, and exposes data via a getter (not directly in HTML).