Merge branch 'forcedotcom:main' into main

This commit is contained in:
thrylokya 2026-04-07 16:21:14 +05:30 committed by GitHub
commit b1ebd23ffc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 553 additions and 2610 deletions

View File

@ -1,3 +1,30 @@
## [1.6.8](https://github.com/forcedotcom/afv-library/compare/1.6.7...1.6.8) (2026-04-07)
### Bug Fixes
* trigger ui-bundle skill for React app prompts @W-21916680@ ([#175](https://github.com/forcedotcom/afv-library/issues/175)) ([1806c89](https://github.com/forcedotcom/afv-library/commit/1806c89b28a889de8292be62e751acb75a680d6b))
## [1.6.7](https://github.com/forcedotcom/afv-library/compare/1.6.6...1.6.7) (2026-04-03)
### Bug Fixes
* tighten ui-bundle skill descriptions to reduce false triggers @W-21800526@ ([#162](https://github.com/forcedotcom/afv-library/issues/162)) ([77108ba](https://github.com/forcedotcom/afv-library/commit/77108bae20bc67c92acefa019b6e11a7192d26d0))
## [1.6.6](https://github.com/forcedotcom/afv-library/compare/1.6.5...1.6.6) (2026-04-02)
### Bug Fixes
* condense using-ui-bundle-salesforce-data reference into skill.md @W-21685788 ([#161](https://github.com/forcedotcom/afv-library/issues/161)) ([4ad7963](https://github.com/forcedotcom/afv-library/commit/4ad796346ec366379cc7de840c3c38e3c4521bb2))
## [1.6.5](https://github.com/forcedotcom/afv-library/compare/1.6.4...1.6.5) (2026-04-01)

44
package-lock.json generated
View File

@ -1,16 +1,16 @@
{
"name": "@salesforce/afv-skills",
"version": "1.6.5",
"version": "1.6.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@salesforce/afv-skills",
"version": "1.6.5",
"version": "1.6.7",
"license": "CC-BY-NC-4.0",
"devDependencies": {
"@salesforce/ui-bundle-template-app-react-sample-b2e": "^1.120.2",
"@salesforce/ui-bundle-template-app-react-sample-b2x": "^1.120.2",
"@salesforce/ui-bundle-template-app-react-sample-b2e": "^1.120.3",
"@salesforce/ui-bundle-template-app-react-sample-b2x": "^1.120.3",
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "^1.117.1",
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "^1.117.1",
"@types/js-yaml": "^4.0.9",
@ -690,22 +690,22 @@
}
},
"node_modules/@salesforce/sdk-core": {
"version": "1.120.2",
"resolved": "https://registry.npmjs.org/@salesforce/sdk-core/-/sdk-core-1.120.2.tgz",
"integrity": "sha512-PZzUfYgi1+0EDTxu+DwvChvb88CqedYp7zv6mAQA7VrVgScTU980CEcgqN6ecDDMcf0jN2AzFDCqVkjdQEBSzA==",
"version": "1.120.3",
"resolved": "https://registry.npmjs.org/@salesforce/sdk-core/-/sdk-core-1.120.3.tgz",
"integrity": "sha512-fzzQwWvN8GCK5peEf37sedsb1gBIHwiAOWXKrohPcwRjUd1wfhMoe45LvV8bOMgO4CBjKrNA21sf0D3FOLtzdQ==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},
"node_modules/@salesforce/sdk-data": {
"version": "1.120.2",
"resolved": "https://registry.npmjs.org/@salesforce/sdk-data/-/sdk-data-1.120.2.tgz",
"integrity": "sha512-bOoKJXo5SJo9eXbf9Bvf/JNHkyz8AYQ7XBYbFRB/o35jnOk666toBF62LDVnyo4YrwK4UrQxIOz0f/IJpEZljg==",
"version": "1.120.3",
"resolved": "https://registry.npmjs.org/@salesforce/sdk-data/-/sdk-data-1.120.3.tgz",
"integrity": "sha512-pw7Bx7Zo0K+apB+waxgbGpGigIsuoPmqDxbMIEdTepW/2/oxFx7RH7edG2qjjt96oHQcTCyPbNdzY/6HjC6TLw==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@conduit-client/service-fetch-network": "3.17.0",
"@conduit-client/utils": "3.17.0",
"@salesforce/sdk-core": "^1.120.2"
"@salesforce/sdk-core": "^1.120.3"
}
},
"node_modules/@salesforce/ts-types": {
@ -719,14 +719,14 @@
}
},
"node_modules/@salesforce/ui-bundle": {
"version": "1.120.2",
"resolved": "https://registry.npmjs.org/@salesforce/ui-bundle/-/ui-bundle-1.120.2.tgz",
"integrity": "sha512-LqjsEc1GVDPjkAV/8SGrUZSjELTaR8nCVMfAxhYV+zSSRdRYhatE4HXlFPC5UubwIziliYW4b02gbTC13yrGWg==",
"version": "1.120.3",
"resolved": "https://registry.npmjs.org/@salesforce/ui-bundle/-/ui-bundle-1.120.3.tgz",
"integrity": "sha512-rZAWPKRxTWwFbwudESFqromtF9OFIH7LcqrkJMkq0Wfdt6mRh3t+jVNg8z3Fj4aQf/BqpY4efLofmSnAhjABAQ==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@salesforce/core": "^8.23.4",
"@salesforce/sdk-data": "^1.120.2",
"@salesforce/sdk-data": "^1.120.3",
"micromatch": "^4.0.8",
"path-to-regexp": "^8.3.0"
},
@ -735,20 +735,20 @@
}
},
"node_modules/@salesforce/ui-bundle-template-app-react-sample-b2e": {
"version": "1.120.2",
"resolved": "https://registry.npmjs.org/@salesforce/ui-bundle-template-app-react-sample-b2e/-/ui-bundle-template-app-react-sample-b2e-1.120.2.tgz",
"integrity": "sha512-jdugbOdYbA968HGJcCojWQTNmwKcpLvKjXmGNAgSvhivXUTe2ol80ZmTQvJAR3xjUQbKbMhd/ilSDjsWpHka8Q==",
"version": "1.120.3",
"resolved": "https://registry.npmjs.org/@salesforce/ui-bundle-template-app-react-sample-b2e/-/ui-bundle-template-app-react-sample-b2e-1.120.3.tgz",
"integrity": "sha512-1Fn0gPRe4p91+nUptRgN1AcHCuYLe+ooQBSjJPdq/hVmU6E2oISIpntgDj7Z2FIc6O+F3DZDkGXyhcDs0B8j7w==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@salesforce/ui-bundle": "^1.120.2",
"@salesforce/ui-bundle": "^1.120.3",
"sonner": "^1.7.0"
}
},
"node_modules/@salesforce/ui-bundle-template-app-react-sample-b2x": {
"version": "1.120.2",
"resolved": "https://registry.npmjs.org/@salesforce/ui-bundle-template-app-react-sample-b2x/-/ui-bundle-template-app-react-sample-b2x-1.120.2.tgz",
"integrity": "sha512-0z3Xo3rJ7dGWn8BharC8WnbyYfJ56cQsbUTKnLxf0rDDAXkKyViwTwbtNCAa9dDK1F78+hk83KEBfTUhwCdJ0w==",
"version": "1.120.3",
"resolved": "https://registry.npmjs.org/@salesforce/ui-bundle-template-app-react-sample-b2x/-/ui-bundle-template-app-react-sample-b2x-1.120.3.tgz",
"integrity": "sha512-waF/yPAjnsM5y7RhcypNaj+WPiy1fD5FdYXPSQyZqngfc/SyVEqtuDr8J7l+pLjkyLoAAIBCRAA0I/uEQfxArQ==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},

View File

@ -1,6 +1,6 @@
{
"name": "@salesforce/afv-skills",
"version": "1.6.5",
"version": "1.6.8",
"description": "Salesforce skills for Agentforce Vibes",
"license": "CC-BY-NC-4.0",
"files": [
@ -11,8 +11,8 @@
"registry": "https://registry.npmjs.org"
},
"devDependencies": {
"@salesforce/ui-bundle-template-app-react-sample-b2e": "^1.120.2",
"@salesforce/ui-bundle-template-app-react-sample-b2x": "^1.120.2",
"@salesforce/ui-bundle-template-app-react-sample-b2e": "^1.120.3",
"@salesforce/ui-bundle-template-app-react-sample-b2x": "^1.120.3",
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "^1.117.1",
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "^1.117.1",
"@types/js-yaml": "^4.0.9",

View File

@ -1,9 +1,117 @@
# Native Mobile Rental Tenant App
A sample Custom Agentic Mobile App (CAMA) for property managers and tenants. Use this as a starting point for building native mobile apps with Salesforce Mobile Publisher, MCF (Mobile Component Framework), and SharedUI.
A sample Agentic Mobile App metadata project for Salesforce. Demonstrates a rental tenant mobile experience scaffold that you can deploy and extend for native iOS or Android.
## Overview
## Table of Contents
This sample demonstrates a rental tenant experience—browse properties, submit maintenance requests, pay rent, and communicate with property management—all from a native iOS or Android app.
1. [What's Included](#whats-included)
2. [Prerequisites](#prerequisites)
3. [Quick Start](#quick-start)
4. [Step-by-Step Setup](#step-by-step-setup)
- [1. Authenticate Your Org](#1-authenticate-your-org)
- [2. Deploy Metadata](#2-deploy-metadata)
5. [Testing in the Playground App](#testing-in-the-playground-app)
6. [Resources](#resources)
---
## What's Included
| Path | Description |
| ---- | ----------- |
| `force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/` | Agentic Mobile App Digital Experience metadata for the Rental Tenant app (screen definitions, app metadata, build metadata, and language settings) |
| `config/project-scratch-def.json` | Scratch org definition file for creating a development org |
| `sfdx-project.json` | Salesforce project configuration (default package directory, login URL, API version) |
| `package.json` | Lightweight project scripts for basic build/lint/test placeholders |
---
## Prerequisites
Before you begin, ensure the following are in place.
| Tool | Minimum Version | Install |
| ---- | --------------- | ------- |
| [Salesforce CLI (`sf`)](https://developer.salesforce.com/tools/salesforcecli) | v2+ | `npm install -g @salesforce/cli` |
| [Node.js](https://nodejs.org/) | v22+ | [nodejs.org](https://nodejs.org/) |
| [Git](https://git-scm.com/) | Any recent version | [git-scm.com](https://git-scm.com/) |
Verify your Salesforce CLI version with:
```bash
sf --version
```
---
## Quick Start
Run these commands from this sample directory:
```bash
sf org login web --alias <alias>
sf project deploy start --source-dir force-app --target-org <alias>
```
Replace `<alias>` with your target org alias or username.
---
## Step-by-Step Setup
Use this section if you prefer to run each step manually.
This project is metadata-first and does not require a local UI build step for deployment.
### 1. Authenticate Your Org
Log in to your target org using the Salesforce CLI:
```bash
sf org login web --alias <alias>
```
To verify authentication:
```bash
sf org display --target-org <alias>
```
If you are working with a sandbox, use:
```bash
sf org login web --alias <alias> --instance-url https://test.salesforce.com
```
### 2. Deploy Metadata
Deploy the rental app Digital Experience metadata:
```bash
sf project deploy start \
--source-dir force-app/main/default/digitalExperiences \
--target-org <alias>
```
---
## Testing in the Playground App
After deploying your metadata, you can preview the app on a mobile device using the Salesforce Mobile Playground.
1. In VS Code, open the Command Palette with **Cmd+Shift+P** and run:
```
SFDX: Open in Live Preview
```
2. A QR code will appear — scan it with your device and follow the on-screen instructions to launch the app in the Playground.
---
## Resources
- [Salesforce Mobile Publisher](https://help.salesforce.com/s/articleView?id=xcloud.branded_apps_exp_cloud.htm&type=5)
- [Salesforce CLI Setup Guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_setup.meta/sfdx_setup/sfdx_setup_intro.htm)
- [Salesforce DX Developer Guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_intro.htm)
- [Salesforce CLI Command Reference](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference_top.htm)

View File

@ -1 +1 @@
1.120.2
1.120.3

View File

@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.120.3](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.120.2...v1.120.3) (2026-04-02)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.120.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.120.1...v1.120.2) (2026-04-01)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project

View File

@ -18,8 +18,8 @@
"graphql:schema": "node scripts/get-graphql-schema.mjs"
},
"dependencies": {
"@salesforce/sdk-data": "^1.120.2",
"@salesforce/ui-bundle": "^1.120.2",
"@salesforce/sdk-data": "^1.120.3",
"@salesforce/ui-bundle": "^1.120.3",
"@tailwindcss/vite": "^4.1.17",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@ -46,7 +46,7 @@
"@graphql-eslint/eslint-plugin": "^4.1.0",
"@graphql-tools/utils": "^11.0.0",
"@playwright/test": "^1.49.0",
"@salesforce/vite-plugin-ui-bundle": "^1.120.2",
"@salesforce/vite-plugin-ui-bundle": "^1.120.3",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.5.2",

View File

@ -1,12 +1,12 @@
{
"name": "@salesforce/webapp-template-base-sfdx-project-experimental",
"version": "1.120.2",
"version": "1.120.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@salesforce/webapp-template-base-sfdx-project-experimental",
"version": "1.120.2",
"version": "1.120.3",
"license": "SEE LICENSE IN LICENSE.txt",
"devDependencies": {
"@lwc/eslint-plugin-lwc": "^3.3.0",

View File

@ -1,6 +1,6 @@
{
"name": "@salesforce/ui-bundle-template-base-sfdx-project",
"version": "1.120.2",
"version": "1.120.3",
"description": "Base SFDX project template",
"license": "SEE LICENSE IN LICENSE.txt",
"publishConfig": {

View File

@ -1 +1 @@
1.120.2
1.120.3

View File

@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.120.3](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.120.2...v1.120.3) (2026-04-02)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.120.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.120.1...v1.120.2) (2026-04-01)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project

View File

@ -18,8 +18,8 @@
"graphql:schema": "node scripts/get-graphql-schema.mjs"
},
"dependencies": {
"@salesforce/sdk-data": "^1.120.2",
"@salesforce/ui-bundle": "^1.120.2",
"@salesforce/sdk-data": "^1.120.3",
"@salesforce/ui-bundle": "^1.120.3",
"@tailwindcss/vite": "^4.1.17",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@ -50,7 +50,7 @@
"@graphql-eslint/eslint-plugin": "^4.1.0",
"@graphql-tools/utils": "^11.0.0",
"@playwright/test": "^1.49.0",
"@salesforce/vite-plugin-ui-bundle": "^1.120.2",
"@salesforce/vite-plugin-ui-bundle": "^1.120.3",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.5.2",

View File

@ -1,12 +1,12 @@
{
"name": "@salesforce/webapp-template-base-sfdx-project-experimental",
"version": "1.120.2",
"version": "1.120.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@salesforce/webapp-template-base-sfdx-project-experimental",
"version": "1.120.2",
"version": "1.120.3",
"license": "SEE LICENSE IN LICENSE.txt",
"devDependencies": {
"@lwc/eslint-plugin-lwc": "^3.3.0",

View File

@ -1,6 +1,6 @@
{
"name": "@salesforce/ui-bundle-template-base-sfdx-project",
"version": "1.120.2",
"version": "1.120.3",
"description": "Base SFDX project template",
"license": "SEE LICENSE IN LICENSE.txt",
"publishConfig": {

View File

@ -1,9 +1,9 @@
---
name: building-ui-bundle-app
description: "Build complete Salesforce React UI bundle applications from natural language descriptions. Use this skill when a user requests a full React app, UI bundle app, web app on Salesforce, or describes a scenario requiring scaffolding, features, data access, UI pages, and deployment of a React application hosted on Salesforce. Orchestrates all UI bundle skills in proper dependency order to produce a deployable application. Triggers on: build a React app, create a UI bundle, build me an app, full-stack Salesforce React app, create a web app on Salesforce."
description: "MUST activate when the user wants to build, create, or generate a React application, React app, web application, single-page application (SPA), or frontend application — even if no project files exist yet. MUST also activate when the project contains a uiBundles/*/src/ directory or sfdx-project.json and the prompt says create, build, construct, or generate a new app, site, or page from scratch — even if the prompt also describes visual styling. MUST also activate when the task spans more than one ui-bundle skill. Use this skill when building a complete app end-to-end. This is the orchestrator that coordinates scaffolding, features, data access, frontend UI, integrations, and deployment in the correct dependency order. Without it, phases execute out of order and the app breaks. Do NOT use for Lightning Experience apps with custom objects (use generating-lightning-app). Do NOT use for single-concern edits to an existing page (use building-ui-bundle-frontend)."
metadata:
version: "1.0"
related-skills: generating-ui-bundle-metadata, generating-ui-bundle-features, using-ui-bundle-salesforce-data, building-ui-bundle-frontend, implementing-ui-bundle-agentforce-conversation-client, implementing-ui-bundle-file-upload, deploying-ui-bundle, generating-experience-react-site
related-skills: generating-ui-bundle-metadata, generating-ui-bundle-features, using-ui-bundle-salesforce-data, building-ui-bundle-frontend, implementing-ui-bundle-agentforce-conversation-client, implementing-ui-bundle-file-upload, deploying-ui-bundle, generating-ui-bundle-site
---
# Building a UI Bundle App

View File

@ -1,6 +1,6 @@
---
name: building-ui-bundle-frontend
description: "Build and modify React UI for Salesforce UI bundles — pages, components, layout, navigation, and headers/footers. Use whenever creating or editing TSX/JSX files or making visual/layout changes. Triggers on: add page, add component, header, footer, navigation, layout, styling, Tailwind, shadcn, React component, appLayout."
description: "MUST activate before editing ANY file under uiBundles/*/src/ for visual or UI changes to an EXISTING app — pages, components, sections, layout, styling, colors, fonts, navigation, animations, or any look-and-feel change. Use this skill when modifying pages, components, layout, styling, or navigation in an existing UI bundle app. Activate when the project contains appLayout.tsx, routes.tsx, src/pages/, src/components/, or global.css. This skill contains critical project-specific conventions (appLayout.tsx shell, shadcn/ui components, Tailwind CSS, Salesforce base-path routing, module restrictions) that override general knowledge. Without this skill, generated code will use wrong imports, break routing, or ignore project structure. Do NOT use when creating a new app from scratch (use building-ui-bundle-app instead)."
---
# UI Bundle UI
@ -94,6 +94,8 @@ Choose a clear conceptual direction and execute it with precision. Bold maximali
- **Spatial Composition:** Unexpected layouts — asymmetry, overlap, diagonal flow, grid-breaking elements. Generous negative space OR controlled density.
- **Backgrounds & Depth:** Create atmosphere rather than defaulting to solid colors. Gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, grain overlays.
- **Mobile Responsiveness:** All generated UI MUST be mobile-responsive. Use Tailwind responsive prefixes (`sm:`, `md:`, `lg:`) to adapt layouts across breakpoints. Stack columns on small screens, use flexible grids, and ensure touch targets are at least 44px. Test that navigation, typography, and spacing work on mobile viewports.
Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate animations and effects. Minimalist designs need restraint, precision, and careful spacing/typography. No two designs should look the same — vary themes, fonts, and aesthetics across generations.
---

View File

@ -1,6 +1,6 @@
---
name: deploying-ui-bundle
description: "Deploy a Salesforce UI bundle to an org — the full deployment sequence including org authentication, pre-deploy build, metadata deployment, permission set assignment, data import, GraphQL schema fetch, and codegen. Use whenever the user wants to deploy, push to org, assign permission sets, import data, fetch GraphQL schema, run codegen, or set up an org after development. Triggers on: deploy, push to org, deploy metadata, assign permission set, import data, schema fetch, codegen, org auth, authenticate org, build and deploy, post-deploy, org setup."
description: "MUST activate when the project contains a uiBundles/*/src/ directory or sfdx-project.json and the task involves deploying, pushing to an org, or post-deploy setup. Use this skill when deploying a UI bundle app to a Salesforce org. Covers the full deployment sequence: org authentication, pre-deploy build, metadata deployment, permission set assignment, data import, GraphQL schema fetch, and codegen. Activate when files like *.uibundle-meta.xml or sfdx-project.json exist and the user mentions deploying, pushing, org setup, or post-deploy tasks."
---
# Deploying a UI Bundle

View File

@ -1,243 +0,0 @@
---
name: generating-experience-lwr-site
description: "Creates, modifies, or manages Salesforce Experience Cloud LWR sites via DigitalExperience metadata. Always trigger when users mention Experience sites, LWR sites, DigitalExperience, Experience Cloud, community sites, portals, creating pages, adding routes, views, theme layouts, branding sets, previewing sites, or any DigitalExperience bundle work. Also use when users mention specific content types like sfdc_cms__route, sfdc_cms__themeLayout, etc. or when troubleshooting site deployment. ALWAYS trigger for ANY guest sharing rule (metadata type sharingGuestRules) creation/modification, guest user access, sharing records to guest users, or when user provides a guest user ID (15 or 18 characters starting with 005)"
---
# Experience LWR Site Builder
Build and configure Salesforce Experience Cloud Lightning Web Runtime (LWR) sites via metadata (DigitalExperienceConfig, DigitalExperienceBundle, Network, CustomSite, CMS contents).
## IMPORTANT!!
Right after loading this skill, you MUST copy the selected workflows/steps to your plan as a TODO checklist and work on each of the item carefully to ensure correctness.
You MUST load the relevant reference docs even though they may live outside of user's project folder.
## Table of Contents
- When to Use
- Critical Rules
- Core Site Properties
- Project Structure in DigitalExperienceBundle Format
- Reference Docs
- Common Workflows
## When to Use
When working with Experience LWR sites:
- Creating and scaffolding new LWR site
- Adding pages (routes + views)
- Configuring LWC components, layouts, themes, or branding styles
- Setting up guest user access (public sites)
- Creating or modifying **guest sharing rules** (`sharingGuestRules`) for any Salesforce object (Account, Case, Contact, etc.) — including when the user refers to a "Site Guest User" username or any guest user by ID
- Troubleshoot deployment errors related to Experience LWR Sites
**Supported Template**: Build Your Own (LWR) - `talon-template-byo`
- More templates to support in the future.
## Critical Rules
1. Before using any MCP tool, make sure they're actually available. If a tool is missing for the current task, let the user know and pause the current workflow.
2. **MUST ALWAYS** load the relevant reference docs before doing anything.
3. **MUST ALWAYS** strictly follow workflows in [Common Workflows](#common-workflows) that match user's requirements. The instructions there should override any conflicting global rules and should have the highest priority over your existing knowledge.
4. Flexipage is abstracted away for newer LWR sites with DigitalExperienceBundle, so **NEVER** use any Flexipage-related MCP tool or skills to handle LWR sites' contents.
## Core Site Properties
Before doing anything else, note down the following properties from the local project if available as they will be used for various operations. Check with the user if any of the following is missing:
- **Site name**: Required. (e.g., `'My Community'`).
- **URL path prefix**: Optional. Alphanumeric characters only. Convert from site name if not provided (e.g., `'mycommunity'`) and verify with the user for the converted value.
- **Template type devName**: `talon-template-byo`.
## Project Structure in DigitalExperienceBundle Format
### Site Metadata
- DigitalExperienceConfig
- `digitalExperienceConfigs/{siteName}1.digitalExperienceConfig-meta.xml`
- DigitalExperienceBundle
- `digitalExperiences/site/{siteName}1/{siteName}1.digitalExperience-meta.xml`
- Network
- `networks/{siteName}.network-meta.xml`
- CustomSite
- `sites/{siteName}.site-meta.xml`
### DigitalExperience Contents
- `digitalExperiences/site/{siteName}1/sfdc_cms__*/{contentApiName}/*`
- These are the content components defining routes, views, theme layouts, etc. Each component must have a `_meta.json` and `content.json` file.
#### Content Type Descriptions
| Content Type | Description | When to Use |
|-|-|-|
| `sfdc_cms__site` | Root site configuration containing site-wide settings | Required for every site; one per site |
| `sfdc_cms__appPage` | Application page container that groups routes and views | Required; defines the app shell |
| `sfdc_cms__route` | URL routing definition mapping paths to views | Create one for each page/URL path |
| `sfdc_cms__view` | Page layout and component structure | Create one for each route; defines page content. Also use to edit existing views (e.g., adding/removing components on a specific page) |
| `sfdc_cms__brandingSet` | Brand colors, fonts, and styling tokens | Required; defines site-wide styling. Use to create or edit existing branding sets |
| `sfdc_cms__languageSettings` | Language and localization configuration | Required; defines supported languages |
| `sfdc_cms__mobilePublisherConfig` | Mobile app publishing settings | Required for mobile app deployment |
| `sfdc_cms__theme` | Theme definition referencing layouts and branding | Required; one per site |
| `sfdc_cms__themeLayout` | Page layout templates used by views | Create layouts for different page structures. Also use to edit existing theme layouts (e.g., updating theme layout, add a component that's persistent across pages) |
**Important:** Creating any new pages require BOTH `sfdc_cms__route` AND `sfdc_cms__view`.
#### Object Pages
Object Pages are dedicated pages used to display and manage record-level data for a specific Salesforce entity/object. For example, an custom object "Car" should have "Car_Detail", "Car_List", and "Car_Related_list" views.
## References
Reference docs within the skill directory. Note that these are **local** and not MCP.
Before doing anything, you **MUST ALWAYS** load them first if they match user intent.
- [bootstrap-template-byo-lwr.md](docs/bootstrap-template-byo-lwr.md) - Site creation, template defaults
- [configure-content-route.md](docs/configure-content-route.md) - Route creation (custom/object pages)
- [configure-content-view.md](docs/configure-content-view.md) - View creation/editing (custom/object pages)
- [configure-content-themeLayout.md](docs/configure-content-themeLayout.md) - Theme layout creation + theme sync
- [configure-content-brandingSet.md](docs/configure-content-brandingSet.md) - Branding with color patterns/WCAG
- [handle-component-and-region-ids.md](docs/handle-component-and-region-ids.md) - **UUID generation (CRITICAL)** for component and region ids used in views and themeLayout.
- [handle-ui-components.md](docs/handle-ui-components.md) - Component discovery, schemas, insertion, configuration
- [configure-guest-sharing-rules.md](docs/configure-guest-sharing-rules.md) - **Guest sharing rules** (`sharingGuestRules`) for public sites — use for any request involving "guest sharing rule", "Site Guest User", or sharing object records with unauthenticated visitors
- [update-site-urls.md](docs/update-site-urls.md) - **Updating site URLs** - URL architecture, workflow for updating `urlPathPrefix` in DigitalExperienceConfig, Network, and CustomSite
## Common Workflows
- See [References](#references) for detailed capabilities.
- **Always** follow the steps defined in the workflows sequentially whether the task is small, big, quick, or complex.
### Creating a New Site
**Rules**:
- **NEVER** generate the files manually.
**Steps** (Follow the steps sequentially. Do not skip any step before proceeding):
- [ ] **ALWAYS** read [bootstrap-template-byo-lwr.md](docs/bootstrap-template-byo-lwr.md) within the skill directory. Do not proceed to the next step without loading the file.
- [ ] Follow the bootstrap doc strictly on site creation
### Creating and Editing Standard or Object Pages
**Steps** (Follow the steps sequentially. Do not skip any step before proceeding):
- [ ] MUST read [configure-content-route.md](docs/configure-content-route.md)
- [ ] MUST read [configure-content-view.md](docs/configure-content-view.md)
- [ ] MUST read [handle-component-and-region-ids.md](docs/handle-component-and-region-ids.md)
### Adding UI Components to Pages
**Steps** (Follow the steps sequentially. Do not skip any step before proceeding):
- [ ] MUST read [handle-ui-components.md](docs/handle-ui-components.md) to add LWCs to LWR sites.
- [ ] MUST read [handle-component-and-region-ids.md](docs/handle-component-and-region-ids.md) to handle id generation
- [ ] MUST read [configure-content-themeLayout.md](docs/configure-content-themeLayout.md) if a component has one of the following requirements:
- needs to be "sticky" and persistent across pages
- is used as a theme layout
### Creating Page Layouts / Container Components
**Steps** (Follow the steps sequentially. Do not skip any step before proceeding):
- [ ] MUST read [handle-ui-components.md](docs/handle-ui-components.md)
### Creating Theme Layouts
**Steps** (Follow the steps sequentially. Do not skip any step before proceeding):
- [ ] **CRITICAL**:Before doing anything else, MUST Check with user whether this new theme layout reuses an existing theme layout Lightning web component or requires a new one. If it requires a new one, make sure to read [handle-ui-components.md](docs/handle-ui-components.md) to create the new theme layout component before proceeding. DO NOT skip this step even if doing so would be faster or more efficient.
- [ ] MUST read [configure-content-themeLayout.md](docs/configure-content-themeLayout.md).
- [ ] MUST read [configure-content-view.md](docs/configure-content-view.md) if need to apply theme layout to pages
### Applying/Setting Theme Layouts
**Steps** (Follow the steps sequentially. Do not skip any step before proceeding):
- [ ] MUST read [configure-content-view.md](docs/configure-content-view.md)
### Configuring Branding
**Steps** (Follow the steps sequentially. Do not skip any step before proceeding):
- [ ] MUST read [configure-content-brandingSet.md](docs/configure-content-brandingSet.md) to configure background colors, foreground colors, button colors, and other branding colors that affect all pages.
### CUD Operations on DigitalExperience Contents
- Users can perform create, update, delete operations on DigitalExperience Contents.
**Steps** (Follow the steps sequentially. Do not skip any step before proceeding):
- [ ] Determine what content types the user wants to modify
- [ ] MUST read the reference doc related to the target content types if the doc exists. e.g., if modifying `sfdc_cms__route`, load [configure-content-route.md](docs/configure-content-route.md).
- [ ] MUST read [handle-component-and-region-ids.md](docs/handle-component-and-region-ids.md) if creating or modifying view or theme layout
- [ ] **Always** Call `execute_metadata_action` to get the schema and examples for that content type **after** loading the corresponding reference docs.
- **Call once per content type per user request**: If you're creating/modifying multiple items of the same content type (e.g., creating 3 routes), you only need to call `execute_metadata_action` ONCE for that content type. Reuse the schema and examples for all items of that type within the same user request.
- For each unique content type you need to work with, **always** call `execute_metadata_action` using the following:
```json
{
"metadataType": "ExperienceSiteLwr",
"actionName": "getSiteContentMetadata",
"parameters": {
"contentType": "<content type from table above>",
"shouldIncludeExamples": true
}
}
```
### Configuring Guest User Sharing Rules
- [ ] MUST read [configure-guest-sharing-rules.md](docs/configure-guest-sharing-rules.md) and follow all steps there.
### Retrieving Site Preview and Builder URLs After Deployment
**Use when** user requests to preview a site, access a builder site, or after successfully deploying a site.
Use the `execute_metadata_action` MCP tool to get the preview and builder URLs:
```json
{
"metadataType": "ExperienceSiteLwr",
"actionName": "getSiteUrls",
"parameters": {
"siteDevName": "<site developer name>"
}
}
```
The site developer name can be found in the CustomSite filename (e.g., `sites/MySite.site-meta.xml` → developer name is `MySite`).
If the site is not found, an error message will be returned indicating that the site may not be deployed. Ensure the site has been successfully deployed before calling this action.
### Updating Experience Site URLs
**Use when** user wants to update or change site URLs (urlPathPrefix).
**Steps** (Follow the steps sequentially. Do not skip any step before proceeding):
- [ ] MUST read [update-site-urls.md](docs/update-site-urls.md) to understand the three-component architecture and URL update workflow
- [ ] Follow the step-by-step workflow in the doc to update URLs consistently across all three components (DigitalExperienceConfig, Network, CustomSite)
### Validation & Deployment
Use `sf` CLI to validate and deploy. Access help docs by attaching `--help`, e.g.:
- `sf project deploy --help`
- `sf project deploy validate --help`
Note that metadata types are space-delimited. **Never** wrap them in quotes or use commas. For example, `--metadata "DigitalExperienceBundle DigitalExperience"` is **incorrect** — always use `--metadata DigitalExperienceBundle DigitalExperience`.
**Validate**:
```bash
sf project deploy validate --metadata DigitalExperienceBundle DigitalExperience DigitalExperienceConfig Network CustomSite --target-org ${usernameOrAlias}
```
**Deploy**:
```bash
sf project deploy start --metadata DigitalExperienceBundle DigitalExperience DigitalExperienceConfig Network CustomSite --target-org ${usernameOrAlias}
```

View File

@ -1,279 +0,0 @@
# Build Your Own (LWR) Template
**Use when** creating a new site or retrieving default values for site metadata.
## Table of Contents
- Bootstrap Options
- Default Values Reference
## Bootstrap Options
**IMPORTANT**: First site setup initializes services. Warn user - recommend Option 1 for first site.
Ask user to choose:
1. Create in org, then download metadata
2. Scaffold locally before deploying
### Option 1: Create in Org
**Step 1:** Run the create command:
```bash
sf community create --name "{siteName}" --template-name 'Build Your Own (LWR)' --url-path-prefix "{prefix}" --target-org {usernameOrAlias} --json
```
Site creation is an async job. As soon as the terminal returns output, capture the `jobId` and move on — do not wait for the shell command to fully exit.
**Step 2:** Ask the user: *"Would you like me to wait for the site creation to complete and then retrieve the metadata for you, or would you prefer to retrieve it yourself once it's ready?"*
**Stop here and wait for the user's response before proceeding.**
---
**If the user wants to wait and retrieve:**
Poll the `BackgroundOperation` object using the following command, replacing `{jobId}` with the ID returned from the create command:
```soql
SELECT Status FROM BackgroundOperation WHERE Id = '{jobId}'
```
Use the MCP tool `run_soql_query` to run this query on the given target org. If the MCP tool is not available, run the following command instead, replacing `{jobId}` and `{usernameOrAlias}` with the appropriate values:
```bash
sf data query --query "SELECT Status FROM BackgroundOperation WHERE Id = '{jobId}'" --target-org {usernameOrAlias} --json
```
Repeat until `Status` is `Complete`. If the query does not return `Complete` after several attempts, ask the user to manually check their target org to confirm whether site creation has completed. **Stop here and do not proceed until the user confirms the site is ready.**
Once complete, run each of the following retrieval commands **one at a time**. Do not chain them together (e.g. do not use `&&`). Wait for each command to return output before running the next. Metadata types are space-delimited — **never** wrap them in quotes or use commas:
```bash
sf project retrieve start --metadata DigitalExperienceBundle --target-org {usernameOrAlias} --json
```
```bash
sf project retrieve start --metadata DigitalExperienceConfig --target-org {usernameOrAlias} --json
```
```bash
sf project retrieve start --metadata Network --target-org {usernameOrAlias} --json
```
```bash
sf project retrieve start --metadata CustomSite --target-org {usernameOrAlias} --json
```
---
**If the user wants to retrieve themselves:**
Provide them with the retrieval command to run once the site is ready. Metadata types are space-delimited — **never** wrap them in quotes or use commas:
```bash
sf project retrieve start --metadata DigitalExperienceBundle DigitalExperienceConfig Network CustomSite --target-org {usernameOrAlias} --json
```
---
### Option 2: Scaffold Locally
```bash
sf template generate digital-experience site --name "{siteName}" --template-name 'Build Your Own (LWR)' --url-path-prefix "{prefix}"
```
Generates metadata with defaults: DigitalExperienceConfig, DigitalExperiences, Network, CustomSite, and content (route, view, themeLayout, etc.).
#### Post-Creation Config
After site metadata is generated, use MCP tool `execute_metadata_action` to fetch additional information about the template:
```json
{
"metadataType": "ExperienceSiteLwr",
"actionName": "getSiteTemplateMetadata",
"parameters": { "templateDevName": "talon-template-byo" }
}
```
If result provides `disabledViews`, delete both view and route directories.
**Example**: If `disabledViews` includes "tooManyRequests":
- Delete: `digitalExperiences/site/{siteDevName}/sfdc_cms__view/tooManyRequests/`
- Delete: `digitalExperiences/site/{siteDevName}/sfdc_cms__route/Too_Many_Requests/`
---
## Default Values Reference
**Note**: CLI generates these. Listed for reference only - use CLI first.
### Metadata Defaults
**DigitalExperienceConfig**:
```yaml
label: {siteName}
urlPathPrefix: {siteUrlPathPrefix}
space: site/{siteName}1
```
**DigitalExperienceBundle**:
```yaml
label: {siteName}1
modules: [sfdc_cms__collection, sfdc_cms__mobilePublisherConfig]
```
**Network**:
```yaml
allowInternalUserLogin: false
allowMembersToFlag: false
changePasswordTemplate: unfiled$public/CommunityChangePasswordEmailTemplate
disableReputationRecordConversations: true
emailSenderAddress: {adminEmailAddress}
emailSenderName: {siteName}
embeddedLoginEnabled: false
enableApexCDNCaching: true
enableCustomVFErrorPageOverrides: false
enableDirectMessages: true
enableExpFriendlyUrlsAsDefault: false
enableExperienceBundleBasedSnaOverrideEnabled: true
enableGuestChatter: false
enableGuestFileAccess: false
enableGuestMemberVisibility: false
enableImageOptimizationCDN: true
enableInvitation: false
enableKnowledgeable: false
enableLWRExperienceConnectedApp: true
enableMemberVisibility: false
enableNicknameDisplay: true
enablePrivateMessages: false
enableReputation: false
enableShowAllNetworkSettings: false
enableSiteAsContainer: true
enableTalkingAboutStats: true
enableTopicAssignmentRules: true
enableTopicSuggestions: false
enableUpDownVote: false
forgotPasswordTemplate: unfiled$public/CommunityForgotPasswordEmailTemplate
gatherCustomerSentimentData: false
headlessForgotPasswordTemplate: unfiled$public/CommunityHeadlessForgotPasswordTemplate
headlessRegistrationTemplate: unfiled$public/CommunityHeadlessRegistrationTemplate
networkMemberGroups:
- profile: admin
networkPageOverrides:
- changePasswordPageOverrideSetting: Standard
- forgotPasswordPageOverrideSetting: Designer
- homePageOverrideSetting: Designer
- loginPageOverrideSetting: Designer
- selfRegProfilePageOverrideSetting: Designer
newSenderAddress: admin@company.com
picassoSite: {siteName}1
selfRegistration: false
sendWelcomeEmail: true
site: {siteName}
siteArchiveStatus: NotArchived
status: UnderConstruction
tabs:
- defaultTab: home
- standardTab: Chatter
urlPathPrefix: {siteUrlPathPrefix}vforcesite
welcomeTemplate: unfiled$public/CommunityWelcomeEmailTemplate
```
**CustomSite**:
```yaml
active: true
allowGuestPaymentsApi: false
allowHomePage: false
allowStandardAnswersPages: false
allowStandardIdeasPages: false
allowStandardLookups: false
allowStandardPortalPages: true
allowStandardSearch: false
authorizationRequiredPage: CommunitiesLogin
bandwidthExceededPage: BandwidthExceeded
browserXssProtection: true
cachePublicVisualforcePagesInProxyServers: true
clickjackProtectionLevel: SameOriginOnly
contentSniffingProtection: true
enableAuraRequests: true
fileNotFoundPage: FileNotFound
genericErrorPage: Exception
inMaintenancePage: InMaintenance
indexPage: CommunitiesLanding
masterLabel: {siteName}
redirectToCustomDomain: false
referrerPolicyOriginWhenCrossOrigin: true
selfRegPage: CommunitiesSelfReg
siteType: ChatterNetwork
urlPathPrefix: {siteUrlPathPrefix}vforcesite
```
### Content Defaults
apiNames and other metadata of site contents:
**sfdc_cms__appPage**:
- mainAppPage
**sfdc_cms__brandingSet**:
- Build_Your_Own_LWR
**sfdc_cms__languageSettings**:
- languages
**sfdc_cms__mobilePublisherConfig**:
- mobilePublisherConfig
**sfdc_cms__theme**:
- Build_Your_Own_LWR
**sfdc_cms__route**:
| Route | apiName | routeType | urlPrefix | urlName | viewId | configurationTags |
|-------|---------|-----------|-----------|---------|--------|-------------------|
| Home | Home | home | "" | home | home | |
| Login | Login | login-main | login | login | login | |
| Register | Register | self-register | SelfRegister | register | register | |
| Forgot_Password | Forgot_Password | forgot-password | ForgotPassword | forgot-password | forgotPassword | |
| Check_Password | Check_Password | check-password | CheckPasswordResetEmail | check-password | checkPasswordResetEmail | |
| Error | Error | error | error | error | error | |
| Service_Not_Available | Service_Not_Available | service-not-available | service-not-available | service-not-available | serviceNotAvailable | allow-in-static-site |
| Too_Many_Requests | Too_Many_Requests | too-many-requests | too-many-requests | too-many-requests | tooManyRequests | too-many-requests, allow-in-static-site |
| News_Detail__c | News_Detail__c | managed-content-sfdc_cms__news | news | news-detail | newsDetail | |
**sfdc_cms__view**:
| View | apiName | viewType | urlName | themeLayoutType |
|------|---------|----------|---------|-----------------|
| home | home | home | home | Inner |
| login | login | login-main | login | Inner |
| register | register | self-register | register | Inner |
| forgotPassword | forgotPassword | forgot-password | forgot-password | Inner |
| checkPasswordResetEmail | checkPasswordResetEmail | check-password | check-password | Inner |
| error | error | error | error | Inner |
| serviceNotAvailable | serviceNotAvailable | service-not-available | service-not-available | ServiceNotAvailable |
| tooManyRequests | tooManyRequests | too-many-requests | too-many-requests | ServiceNotAvailable |
| newsDetail | newsDetail | managed-content-sfdc_cms__news | news-detail | Inner |
**sfdc_cms__site**:
- {siteName}1
**sfdc_cms__themeLayout**:
- scopedHeaderAndFooter
- snaThemeLayout

View File

@ -1,141 +0,0 @@
# Content Type: sfdc_cms__brandingSet
**Use when** user explicitly requests creating/updating branding set.
## Table of Contents
- Core Principles
- Generation Guidelines
- Editing Existing Branding Sets
- Branding Property Patterns
## Core Principles
1. **Purpose**: Manage site-wide branding properties (colors, fonts, etc.).
2. **Site Association**: Branding sets are linked to the site configuration.
## Generation Guidelines
### 1. Directory Structure
1. **Location**: `digitalExperiences/site/[SITE_NAME]/sfdc_cms__brandingSet/[BRANDING_SET_NAME]/`
2. **Required Files**:
- `_meta.json` - Metadata file defining the API name and type
- `content.json` - Content file defining the configuration and layout
3. **Naming Convention**: Underscore-separated names (e.g., `Branding_Set`).
### 2. _meta.json Structure
The `_meta.json` file must contain:
```json
{
"apiName": "[BRANDING_SET_NAME]",
"type": "sfdc_cms__brandingSet",
"path": "brandingSets"
}
```
**Rules**:
- `apiName`: Must match the directory name exactly (e.g., `Branding_Set`)
### 3. content.json Structure
The `content.json` file must contain:
```json
{
"type": "sfdc_cms__brandingSet",
"title": "[DISPLAY_TITLE]",
"contentBody": {},
"urlName": "[URL_NAME]"
}
```
**Field Definitions**:
- `type`: **Required**. Represents the content type. The only supported value is `"sfdc_cms__brandingSet"`.
- `title`: **Required**. Human-readable display title (e.g., Branding Set).
- Maximum length is **100 characters**.
- Must be **unique** within the space's brandingSet content items.
- `contentBody`: Include all `required` properties from `schemaDefinition`.
1. **Seed**: Always call `execute_metadata_action` with `shouldIncludeExamples: true`. Copy the *entire* example object from `examplesOfContentType[0]` into `content.json`. **NEVER** start from a minimal stub.
2. **Recalculate (CRITICAL STOP)**: You MUST stop and perform explicit changes for dependent tokens BEFORE generating JSON.
- [] Refer to "Branding Property Patterns" for detailed calculations.
- `brandingSetType`: Represents whether the color palette is for the entire site or a specific section.
- `APP`: The branding set applies to the entire site. There can be only one branding set of this type.
- `SCOPED`: A `SCOPED` branding set can be applied only to a section component for granular overrides.
- `definitionName`: **Required**. Represents the name for the branding set used in the site or templates theme.
- **Build Your Own (LWR)**: uses `talon-template-byo:branding`
- **Microsite**: uses `microsite-template-marketing:branding`
- `values`: **Required**. Represents a map (object) of branding values (colors, fonts, etc.) that can be applied to a site.
- **Format**: An object containing key-value pairs that represent branding-set values.
- **Patterns**: See the "Branding Property Patterns" section for details on value relationships.
- `urlName`: Lowercase with hyphens (e.g., `branding-set`)
### 4. Naming Conventions Summary
| Field | Format | Example |
|-------|--------|--------|
| Directory/apiName | Underscore-separated | `Branding_Set` |
| title | Human-readable | `Branding Set` |
| urlName | Lowercase-hyphens | `build-your-own-lwr` |
### 5. Generation Checklist
- [ ] Directory and `_meta.json` follow naming conventions
- [ ] `content.json` has all required fields
- [ ] `contentBody` follows the schema provided by `execute_metadata_action`
- [ ] **STOP AND VERIFY**: `contentBody.values` honors all **Branding Property Patterns** defined below and explicitly recalculated and updated all dependent tokens based on any token updates requested by the user.
## Editing Existing Branding Sets
Use this section when modifying existing branding sets under the `sfdc_cms__brandingSet` directory.
### Editing Checklist
- [ ] Ensure all modified branding properties honor the **Branding Property Patterns** defined below.
## Branding Property Patterns
When generating or validating `contentBody.values`, follow these established patterns for consistency:
### 1. Color Scaling Patterns (The "Rule of 3")
Salesforce uses a numeric suffix system (`Color`, `Color1`, `Color2`, `Color3`) to create a tonal palette.
- **Darkening Trend**: As the suffix number increases, the color becomes progressively darker.
- Example: `BackgroundColor` (#ffffff) → `_BackgroundColor1` (#ebebeb) → `_BackgroundColor2` (#c2c2c2) → `_BackgroundColor3` (#858585).
- **Contrast/Foreground Colors**: Every base color has a corresponding `ForegroundColor` to ensure accessibility.
- **WCAG Compliance**: Ensure a color contrast ratio of at least **4.5:1** between the background and foreground colors for standard text.
- Dark base colors usually have white (#ffffff) foregrounds.
- Light base colors (like `_NeutralColor`) usually have black (#000000) foregrounds.
### 2. Font Size Hierarchy
- **Base vs. Small**: The `Small` variant is typically **75%** of the base size.
- Example: `BodyFontSize` (1rem) → `BodySmallFontSize` (0.75rem).
- **Heading Scale**: Headings follow a standard typographic scale:
- `HeadingExtraLarge`: 2.5rem
- `HeadingLarge`: 1.75rem (~70% of XL)
- `HeadingMedium`: 1.25rem (~50% of XL)
- `HeadingSmall`: 1.125rem
### 3. Design Token Mapping
Prefer using **DXP Design Tokens** over hardcoded values where possible:
- **Fonts**: Use `var(--dxp-s-html-font-family)` for base, body, and button fonts.
- **Brand Alignment**: Use `var(--dxp-g-brand)` for primary brand colors and links.
### 4. Component Consistency
- **Buttons**: Maintain consistent `BorderRadius` (e.g., 4px) across all button sizes (Small, Medium, Large).
- **Form Elements**: `FormElementLabelFontSize` and `FormElementTextFontSize` should match.
### 5. Spacing and Ratios
- **Device Ratios**: Desktop spacing (padding/spacers) is typically **1.33x** larger than mobile spacing.
- Example: `ColumnSpacerSizeDesktop` (1rem) vs `ColumnSpacerSizeMobile` (0.75rem).

View File

@ -1,232 +0,0 @@
# Content Type: sfdc_cms__route
**Use when** user explicitly requests creating a new page. Not for editing existing routes.
## Table of Contents
- Generation Guidelines
- Core Principles
- Directory Structure (All Routes)
- _meta.json Structure
- Part A: CUSTOM PAGES
- Part B: OBJECT PAGES
## Generation Guidelines
**PAGE TYPES**: These guidelines supports two types of pages:
1. **Custom Pages** - Single route pages for custom content (e.g., About Us). **Note**: Standard pages (e.g., Home, Login) come pre-built with the site and cannot be created.
2. **Object Pages** - Requires 3 routes: Detail, List, and Related List (e.g., Account, custom objects)
## Core Principles
1. **Purpose**: Generate new routes under the `sfdc_cms__route` directory.
2. **View Association**: Each route must reference a corresponding view in the `sfdc_cms__view` directory.
3. **CRITICAL**: The `routeType` in the route's `content.json` MUST exactly match the `viewType` in the corresponding view's `content.json`. This is a required validation rule.
## Directory Structure (All Routes)
1. **Location**: `digitalExperiences/site/[SITE_NAME]/sfdc_cms__route/[ROUTE_NAME]/`
2. **Required Files**:
- `_meta.json` - Metadata file defining the API name and type
- `content.json` - Content file defining the configuration and layout
3. **Naming Convention**: Underscore-separated names with "__c" suffix (About_Us__c, Account_Detail__c)
## _meta.json Structure (All Routes)
The `_meta.json` file must contain:
```json
{
"apiName": "[ROUTE_NAME]",
"type": "sfdc_cms__route",
"path": "routes"
}
```
**Rules**:
- `apiName`: Must match the route directory name exactly
- `type`: Always `"sfdc_cms__route"`
- `path`: Always `"routes"`
## Part A: CUSTOM PAGES
Use this section when creating single-route custom content pages.
### A.1. content.json Structure
The `content.json` file must contain:
```json
{
"type": "sfdc_cms__route",
"title": "[DISPLAY_TITLE]",
"contentBody": {},
"urlName": "[URL_NAME]"
}
```
**Field Definitions**:
- `type`: Always `"sfdc_cms__route"`
- `title`: Human-readable display title (e.g., About Us)
- `contentBody`: Include all `required` properties from `schemaDefinition`. Use `examplesOfContentType` for reference.
- `urlName`: URL identifier (lowercase with hyphens, e.g., `about-us`)
### A.2. Naming Conventions
For a page named "About Us":
| Field | Format | Example |
|-------|--------|--------|
| Directory Name | Underscore-separated + "__c" | `About_Us__c` |
| apiName | Same as directory | `About_Us__c` |
| title | Human-readable | `About Us` |
| contentBody.activeViewId | Underscore-separated (no __c) | `About_Us` |
| contentBody.routeType | "custom-" + lowercase hyphens | `custom-about-us` |
| contentBody.urlPrefix | Lowercase hyphens | `about-us` |
| urlName | Lowercase hyphens | `about-us` |
**CRITICAL**: `routeType` MUST exactly match `viewType` in the corresponding view's `content.json`.
### A.3. View Dependency
- Before creating a route, ensure the corresponding view exists in `sfdc_cms__view/[view_name]/`
- If the view doesn't exist, create it first following the view creation guidelines
### A.4. Generation Checklist
- [ ] Route directory and files created (see Directory Structure)
- [ ] `_meta.json` follows structure (see _meta.json Structure)
- [ ] `content.json` follows structure (see A.1)
- [ ] All naming conventions applied (see A.2)
- [ ] Corresponding view exists (see A.3)
## Part B: OBJECT PAGES
Use this section when creating object pages that require Detail, List, and Related List routes.
### B.1. Overview
Object pages require **three routes** to be created together:
1. **Detail Route** - Displays a single record
2. **List Route** - Displays a list of records
3. **Related List Route** - Displays related records for a parent record
**OBJECT TYPES**: Two types of Salesforce objects use different `routeType` formats:
| Object Type | routeType Format | Example |
|-------------|------------------|----------|
| **Standard** (Account, Contact) | `[type]-[keyPrefix]` | `detail-001`, `list-001`, `relatedlist-001` |
| **Custom** (Test_Object__c) | `[type]-[ObjectApiName]` | `detail-Test_Object__c`, `list-Test_Object__c` |
- **keyPrefix**: 3-character identifier unique to each standard object (Account=001, Contact=003)
- **ObjectApiName**: Custom object API name including the "__c" suffix
Obtain object information from the `objectList` MCP output:
```json
[
["Label", "ApiName", "KeyPrefix", "IsCustom"]
]
```
### B.2. Required Routes
Create three directories under `sfdc_cms__route/`:
- `[OBJECT_NAME]_Detail__c/`
- `[OBJECT_NAME]_List__c/`
- `[OBJECT_NAME]_Related_List__c/`
### B.3. content.json Structure
Each route's `content.json` file must contain:
```json
{
"type": "sfdc_cms__route",
"title": "[OBJECT_NAME] [TYPE]",
"contentBody": {},
"urlName": "[object_name_lowercase]-[type]"
}
```
**Field Definitions**:
- `type`: Always `"sfdc_cms__route"`
- `title`: Human-readable title (Account Detail, Account List)
- `contentBody`: Include all `required` properties from `schemaDefinition`. Use `examplesOfContentType` for reference.
- `contentBody.urlPrefix`: **CRITICAL**: Must be identical across all three object page views (Detail, List, and Related List) for the same object.
- `urlName`: Lowercase with hyphens (account-detail, account-list)
### B.4. Object Page Examples
Use `[ObjectName]` as the object name (Account, Test_Object) and `[IDENTIFIER]` as:
- **Standard objects**: keyPrefix (001 for Account, 003 for Contact)
- **Custom objects**: ObjectApiName (Test_Object__c)
#### content.json Template
```json
{
"type": "sfdc_cms__route",
"title": "[ObjectName] [Detail|List|Related List]",
"contentBody": {
"activeViewId": "[ObjectName]_[Detail|List|Related_List]",
"configurationTags": [],
"pageAccess": "UseParent",
"routeType": "[detail|list|relatedlist]-[IDENTIFIER]",
"urlPrefix": "[object-name-lowercase]"
},
"urlName": "[object-name-lowercase]-[detail|list|related-list]"
}
```
**Rules**:
- Before any actions, *always* call `execute_metadata_action` to get the full schema and examples per the skill document.
#### routeType Examples
| Route Type | Standard (Account) | Custom (Test_Object__c) |
|------------|-------------------|------------------------|
| Detail | `detail-001` | `detail-Test_Object__c` |
| List | `list-001` | `list-Test_Object__c` |
| Related List | `relatedlist-001` | `relatedlist-Test_Object__c` |
### B.5. Naming Conventions
For an object named "Account":
| Field | Detail | List | Related List |
|-------|--------|------|---------------|
| Directory Name | `Account_Detail__c` | `Account_List__c` | `Account_Related_List__c` |
| apiName | `Account_Detail__c` | `Account_List__c` | `Account_Related_List__c` |
| title | `Account Detail` | `Account List` | `Account Related List` |
| activeViewId | `Account_Detail` | `Account_List` | `Account_Related_List` |
| routeType (Standard) | `detail-[keyPrefix]` | `list-[keyPrefix]` | `relatedlist-[keyPrefix]` |
| routeType (Custom) | `detail-[ObjectApiName]` | `list-[ObjectApiName]` | `relatedlist-[ObjectApiName]` |
| urlPrefix | `account` | `account` | `account` |
| urlName | `account-detail` | `account-list` | `account-related-list` |
**CRITICAL**: `routeType` MUST exactly match `viewType` in the corresponding view's `content.json`.
### B.6. View Dependency
- Before creating routes, ensure corresponding views exist in `sfdc_cms__view/`:
- `[ObjectName]_Detail/`, `[ObjectName]_List/`, `[ObjectName]_Related_List/`
- `activeViewId` must match the view directory name exactly
- `routeType` must exactly match `viewType` in the corresponding view
- If views don't exist, create them first following the view creation guidelines
### B.7. Generation Checklist
- [ ] Object type determined (Standard or Custom) and identifier obtained (keyPrefix or ObjectApiName)
- [ ] All three routes created: **Detail**, **List**, and **Related List**, each with `_meta.json` and `content.json`
- [ ] All naming conventions applied (see B.5)
- [ ] Corresponding views exist (see B.6)
- [ ] `routeType` matches `viewType` for all three routes

View File

@ -1,145 +0,0 @@
# Content Type: sfdc_cms__themeLayout
**Use when** user explicitly requests creating a new layout.
## Table of Contents
- Directory Structure
- Purpose A: Generate new theme layouts under the `sfdc_cms__themeLayout` directory.
- _meta.json Structure
- content.json Structure
- Naming Conventions
- Theme Sync After Creation
- Generation Checklist
- Purpose B: Editing existing theme layouts under the `sfdc_cms__themeLayout` directory.
## Directory Structure
1. **Location**: `digitalExperiences/site/[SITE_NAME]/sfdc_cms__themeLayout/[THEME_LAYOUT_NAME]/`
2. **Required Files**:
- `_meta.json` - Metadata file defining the API name and type
- `content.json` - Content file defining the configuration and layout
## Purpose A: Generate New Theme Layouts
**IMPORTANT**: These guidelines should ONLY be applied when the user explicitly requests creating a new layout for their site. Do not apply these guidelines automatically for other tasks or when editing existing layouts.
### Order of operations
If the user decides to create a new LWC, create the LWC first THEN the theme layout metadata.
### _meta.json Structure
The `_meta.json` file must contain:
```json
{
"apiName": "[THEME_LAYOUT_NAME]",
"type": "sfdc_cms__themeLayout",
"path": "themeLayouts"
}
```
**Rules**:
- `apiName`: Must match the themeLayout directory name exactly
- `type`: Always `"sfdc_cms__themeLayout"`
- `path`: Always `"themeLayouts"`
### content.json Structure
The `content.json` file must contain:
```json
{
"type": "sfdc_cms__themeLayout",
"title": "[DISPLAY_TITLE]",
"contentBody": {
"component": {
"attributes": { },
"children": [ "[regions in the layout]" ],
"definition": "[FQN of root layout component]",
"id": "[root component id]",
"type": "component"
}
},
"urlName": "[url name]"
}
```
**Field Definitions**:
- `type`: Always `"sfdc_cms__themeLayout"`
- `title`: Human-readable display title, words separated by spaces (e.g. "Scoped Header and Footer")
- `contentBody`: Include all `required` properties from `schemaDefinition`. Use `examplesOfContentType` for reference.
- Do not add additional fields.
- `urlName`: URL identifier (lowercase, words separated by dashes e.g., "scoped-header-and-footer")
- `contentBody.compnent.definition`: The actual theme layout component that displays/renders the layout and includes theme region components.
**Rules**:
- Before any actions, *always* call `execute_metadata_action` to get the full schema and examples per the skill document.
### Naming Conventions
1. **Directory Name**: Should be in camelCase
2. **apiName**: Must exactly match the directory name
3. **title**: Human-readable title with spaces (e.g., "Service Not Available Theme Layout")
4. **urlName**: Lowercase with hyphens for URL-friendly format (e.g., "new-layout")
### Theme Sync After Creation
After creating a new `sfdc_cms__themeLayout`, you MUST update:
```
digitalExperiences/site/[SITE_NAME]/sfdc_cms__theme/[THEME_API_NAME]/content.json
```
**Lookup**: To find the theme content.json for the current site:
1. Navigate up from the current theme layout directory to the site directory.
2. Look in sfdc_cms__theme/ (sibling directory to sfdc_cms__themeLayout/).
3. Find the theme directory (typically one per site).
4. Read the file: content.json.
**Action (append-only)**:
- ALWAYS append a new entry to `contentBody.layouts`.
- Do NOT replace or remove existing `layouts` entries.
- `layoutId` MUST exactly match the new theme layout `apiName`.
- `layoutType` MUST be chosen based on intended view usage.
- **Default**: Generate a random 30-character alphanumeric string (e.g., `xEGgPxY5j5TForZe3J7SBguOfQicEy`) for the `layoutType`. Ensure this string is unique and does not match any existing `layoutType` in the list.
**Example**:
```json
{
"contentBody": {
"layouts": [
{ "layoutId": "existingLayoutA", "layoutType": "Inner" },
{ "layoutId": "existingLayoutB", "layoutType": "ServiceNotAvailable" },
{ "layoutId": "[NEW_THEME_LAYOUT_API_NAME]", "layoutType": "[30_CHAR_RANDOM_STRING]" }
]
}
}
```
### Generation Checklist
When generating a new theme layout, ensure:
- [ ] `_meta.json` created with correct `apiName`, `type`, and `path`
- [ ] `content.json` created with all required fields
- [ ] `urlName` uses lowercase with hyphens
- [ ] `title` is human-readable
- [ ] `sfdc_cms__theme/[THEME_API_NAME]/content.json` updated by appending a new `contentBody.layouts` mapping
- [ ] **CRITICAL**: Complete all the UUID generation steps. See [handle-component-and-region-ids.md](docs/handle-component-and-region-ids.md)
## Purpose B: Editing Existing Theme Layouts
### Component Modifications
When adding, removing, or configuring components in existing theme layouts, **always** refer to [handle-ui-components.md](docs/handle-ui-components.md) for placement hierarchy, component structure, column layout, and property configuration.
**Note**: Theme layouts often define the overall structure (header/footer) surrounding the main content region. Ensure components are added to the correct region (e.g., `header`, `footer`).

View File

@ -1,233 +0,0 @@
# Content Type: sfdc_cms__view
**Use when** user explicitly requests creating a new page or editing an existing page.
## Table of Contents
- Purpose A: Generate New Views
- Purpose B: Editing Existing Views
## Purpose A: Generate New Views
### Generation Guidelines
**PAGE TYPES**: These guidelines supports two types of pages:
1. **Custom Pages** - Single view pages for custom content (e.g., About Us). **Note**: Standard pages (e.g., Home, Login) come pre-built with the site and cannot be created.
2. **Object Pages** - Requires 3 views: Detail, List, and Related List (e.g., Account, custom objects)
### Core Principles
1. **Route Association**: Views are referenced by routes via the `activeViewId` field.
2. **CRITICAL**: The `viewType` MUST exactly match the `routeType` in the corresponding route.
### Directory Structure (All Views)
1. **Location**: Views must be created under: `digitalExperiences/site/[SITE_NAME]/sfdc_cms__view/[VIEW_NAME]/`
2. **Required Files**:
- `_meta.json` - Metadata file defining the API name and type
- `content.json` - Content file defining the configuration and layout
3. **Naming Convention**: Underscore-separated names, no "__c" suffix (About_Us, Account_Detail)
### _meta.json Structure (All Views)
The `_meta.json` file must contain:
```json
{
"apiName": "[VIEW_NAME]",
"type": "sfdc_cms__view",
"path": "views"
}
```
**Rules**:
- `apiName`: Must match directory name exactly. **No "__c" suffix**.
- `type`: Always `"sfdc_cms__view"`
- `path`: Always `"views"`
### Theme Layout Type (All Views)
The `contentBody.themeLayoutType` field specifies which theme layout to use for the view. There can only be one per view.
- **Default**: `"Inner"` - Use this default if the user does not specify a layout OR if the lookup fails to find a matching layoutType
- **Lookup**: To find valid values:
1. Navigate up from the current view directory to the site directory
2. Look in `sfdc_cms__theme/` (sibling directory to `sfdc_cms__view/`)
3. Find the theme directory (typically one per site)
4. Check `content.json``contentBody.layouts[]` for the layouts array
- **Layout Name/ID Resolution**: If the user provides only a layout name or ID (e.g., "scopedHeaderAndFooter"), you must look up the corresponding `layoutType`:
1. Find the theme's `content.json` as described above
2. Locate the `contentBody.layouts` array containing `layoutId`/`layoutType` pairs
3. Match the user-provided name/ID against `layoutId` values
4. Use the corresponding `layoutType` value for `contentBody.themeLayoutType`
5. **Use ONLY the `layoutType` value** for `contentBody.themeLayoutType` - do NOT use the layoutId or user's provided name
6. **If no match is found, use the default `"Inner"`**
### PART A: CUSTOM PAGES
Use this section when creating single-view custom content pages.
#### A.1. content.json Structure
The `content.json` file must contain:
```json
{
"type": "sfdc_cms__view",
"title": "[DISPLAY_TITLE]",
"contentBody": {},
"urlName": "[URL_NAME]"
}
```
**Field Definitions**:
- `type`: Always `"sfdc_cms__view"`
- `title`: Human-readable display title (e.g., About Us)
- `contentBody`: Include all `required` properties from `schemaDefinition`. Use `examplesOfContentType` for reference.
- `urlName`: Lowercase with hyphens (e.g., `about-us`)
#### A.2. Component Structure
**MUST** use `community_layout:sldsFlexibleLayout` as the root with exactly 2 regions (`content` and `sfdcHiddenRegion`), even if no components exist:
```
community_layout:sldsFlexibleLayout (root)
├── content (region) — main page content
└── sfdcHiddenRegion (region) — hidden region for SEO and metadata
```
**CRITICAL REQUIREMENTS**:
- **Region names are fixed**: The region `name` field MUST be exactly `content` or `sfdcHiddenRegion`. Do NOT invent custom region names.
- **sfdcHiddenRegion MUST contain seoAssistant**: The `sfdcHiddenRegion` region MUST ALWAYS include a `community_builder:seoAssistant` component in its `children` array.
- **Components live in children**: All components are placed inside the `children` array of a region. Use an empty `children: []` array for `content` if no components exist.
Each region requires: `id` (unique UUID), `name`, `title`, `type: "region"`, `children`. Do not add any other fields.
#### A.3. Naming Conventions Summary
| Field | Format | Example |
|-|-|-|
| Directory/apiName | Underscore-separated, no "__c" | `About_Us` |
| title | Human-readable | `About Us` |
| viewType | `custom-` + lowercase-hyphens | `custom-about-us` |
| urlName | Lowercase-hyphens | `about-us` |
#### A.4. Route Dependency
The route's `activeViewId` must match the view's directory name exactly.
#### A.5. Generation Checklist
- [ ] Directory and `_meta.json` follow structure (see Directory Structure, _meta.json Structure)
- [ ] `content.json` has all required fields (A.1)
- [ ] Component structure correct with both regions (A.1)
- [ ] **CRITICAL**: Complete all the UUID generation steps. see [handle-component-and-region-ids.md](docs/handle-component-and-region-ids.md)
- [ ] `viewType` matches route's `routeType` (CRITICAL)
### PART B: OBJECT PAGES
Use this section when creating object pages that require Detail, List, and Related List views.
#### B.1. Overview
Object pages require **three views**: Detail, List, and Related List. All share the same object name.
**Object Types & viewType Format**:
| Object Type | Identifier | viewType Example |
|-|-|-|
| Standard (Account, Contact) | `keyPrefix` (3-char) | `detail-001`, `list-001`, `relatedlist-001` |
| Custom (Test_Object__c) | API name with `__c` | `detail-Test_Object__c`, `list-Test_Object__c` |
Obtain object information from the `objectList` MCP output from `sfdc_cms__route`:
```json
[
["Label", "ApiName", "KeyPrefix", "IsCustom"]
]
```
#### B.2. Required Views
Create three directories under `sfdc_cms__view/`:
- `[OBJECT_NAME]_Detail/`
- `[OBJECT_NAME]_List/`
- `[OBJECT_NAME]_Related_List/`
#### B.3. content.json Structure
```json
{
"type": "sfdc_cms__view",
"title": "[OBJECT_NAME] [TYPE]",
"contentBody": {
"component": {},
"dataProviders": [],
"themeLayoutType": "[THEME_LAYOUT_TYPE]",
"viewType": "[PREFIX]-[IDENTIFIER]"
},
"urlName": "[OBJECT_NAME_LOWERCASE]-[TYPE]"
}
```
**Field Definitions**:
- `type`: Always `"sfdc_cms__view"`
- `title`: Human-readable (e.g., "Account Detail")
- `contentBody`: Include all `required` properties from `schemaDefinition`. Use `examplesOfContentType` for reference.
- `contentBody.viewType`: **CRITICAL**: Must exactly match route's `routeType`
- `urlName`: Lowercase with hyphens (e.g., `account-detail`)
**Rules**:
- Before any actions, *always* call `execute_metadata_action` to get the full schema and examples per the skill document.
#### B.4. Component Structure
Uses same structure as Part A.1 (Component Structure) with these SEO assistant differences:
- **Detail View**: `pageTitle: "{!Record._Object}: {!Record._Title}"`
- **List/Related List Views**: `recordId: "{!recordId}"` (no pageTitle)
Default template includes one section with one empty column. `seedComponents` must be `[]` (not `null`).
#### B.5. Naming Conventions Summary
| Field | Detail | List | Related List |
|-|-|-|-|
| Directory/apiName | `[Object]_Detail` | `[Object]_List` | `[Object]_Related_List` |
| title | `[Object] Detail` | `[Object] List` | `[Object] Related List` |
| viewType (Standard) | `detail-[keyPrefix]` | `list-[keyPrefix]` | `relatedlist-[keyPrefix]` |
| viewType (Custom) | `detail-[ApiName__c]` | `list-[ApiName__c]` | `relatedlist-[ApiName__c]` |
| urlName | `[object]-detail` | `[object]-list` | `[object]-related-list` |
#### B.6. Route Dependency
The route's `activeViewId` must match the view's directory name exactly. The `viewType` must exactly match the route's `routeType`.
#### B.7. Generation Checklist
- [ ] Object type determined; identifier obtained (`keyPrefix` or API name with `__c`)
- [ ] All three views created: **Detail**, **List**, and **Related List**, each with `_meta.json` and `content.json`
- [ ] `viewType` matches route's `routeType` for all three views (CRITICAL)
- [ ] Component structure correct with both regions (see A.1)
- [ ] SEO assistant configured correctly per view type (B.4)
- [ ] **CRITICAL**: Complete both UUID generation steps. see [handle-component-and-region-ids.md](docs/handle-component-and-region-ids.md)
## Purpose B: Editing Existing Views
Use this section when modifying existing views under the `sfdc_cms__view` directory.
### Component Modifications
When adding, removing, or configuring components in existing views, **always** refer to [handle-ui-components.md](docs/handle-ui-components.md) for placement hierarchy, component structure, column layout, and property configuration.
### Theme Layout Type
To change a view's theme layout, update `contentBody.themeLayoutType` in the view's `content.json`. See **Theme Layout Type (All Views)** for default and lookup details

View File

@ -1,49 +0,0 @@
# Guest User Sharing Rules (Public Sites Only)
**Use when** the user asks to create or modify a guest sharing rule, mentions a username containing "Guest User" or "Site Guest User" (e.g. "ZenLease Site Guest User"), or wants to share object records with unauthenticated visitors.
## Steps
1. **Resolve the guest user identity**: If the user provides a username like "ZenLease Site Guest User", use it directly as the `<guestUser>` value (`CommunityNickname`). If a user ID is provided (e.g. `005AAC00003f8EP`), query the org to get the `CommunityNickname` first.
2. **Check for existing file**: Look for `sharingRules/{ObjectName}.sharingRules-meta.xml` locally. If missing, retrieve it from the org before editing.
3. **Generate the rule**: Follow the XML example and critical requirements below. Never use `sharingCriteriaRules` or `<role>`/`<group>` for guest rules.
If `sharingRules` metadata is not available locally in `force-app/main/default/sharingRules`, retrieve it from the org before creating new rules.
## Retrieve Full SharingRules Schema
Use the metadata MCP tool with metadataType "SharingRules" to retrieve schema.
## XML Example
```xml
<?xml version="1.0" encoding="UTF-8"?>
<SharingRules xmlns="http://soap.sforce.com/2006/04/metadata">
<sharingGuestRules>
<fullName>ShareAccountsWithSiteGuest</fullName>
<accessLevel>Read</accessLevel>
<includeHVUOwnedRecords>false</includeHVUOwnedRecords>
<label>Share Accounts With Site Guest</label>
<sharedTo>
<guestUser>[site Guest User's CommunityNickanme]</guestUser>
</sharedTo>
<criteriaItems>
<field>Name</field>
<operation>notEqual</operation>
<value>null</value>
</criteriaItems>
</sharingGuestRules>
</SharingRules>
```
## Critical Requirements
1. **SharedTo Element**: Must use `<guestUser>{site Guest User's CommunityNickanme}</guestUser>` (not URL path prefix).
2. **includeHVUOwnedRecords**: Required field. Set to `false` unless records owned by high-volume site users should be included.
3. **One XML file per object**: Put all rules for a given object in one file. Do not create additional.
## Common Mistakes
- Using `<role>` or `<group>` instead of `<guestUser>` in sharedTo
- Omitting the required `includeHVUOwnedRecords` field
- Using `includeRecordsOwnedByAll` (that's for `sharingCriteriaRules`, not guest rules)

View File

@ -1,27 +0,0 @@
# UUID Generation
**Use when** handling IDs for components and regions of views. All component and region IDs in Experience Site content must be unique UUIDs.
## Requirements
1. **Format**: Lowercase UUID v4 (e.g., `5d56a22f-c1e8-40d3-92ec-6e10e71e36de`)
2. **Uniqueness**: Must be unique across ALL `content.json` files in site (under `digitalExperiences/site/<SITE_NAME>/`)
## For New content.json Files Only
**Multistep Process (REQUIRED)**:
- **CRITICAL**: Each step must be performed separately - do NOT combine steps into a single automated command or script
- **Step 1**: Create files with descriptive placeholders for UUIDs (e.g., `UUID_CONTENT_REGION`, `UUID_HIDDEN_REGION`, `UUID_SEO_COMPONENT`)
- **Step 2**: Count the total number of UUID placeholder occurrences in the generated file, then generate exactly that many UUIDs using:
- `node -e "console.log(Array.from({length: N}, () => require('crypto').randomUUID()).join('\n'))"` where N is the total count of placeholder occurrences. Present this command to the user for execution.
- **Step 3**: Replace each placeholder occurrence sequentially with the generated UUIDs from the list, ensuring each occurrence gets a unique UUID from the list. Perform replacements one at a time or in small batches - do NOT automate this with scripts.
- **Step 4**: Validate that all placeholders have been replaced - read the file and search for any remaining placeholder patterns (e.g., `UUID_`). The file is NOT valid until all placeholders are replaced with actual UUIDs.
- **CRITICAL**: Every single placeholder occurrence must be replaced with a DIFFERENT UUID from the generated list, even if the placeholder name is repeated. For example, if you have 5 total placeholder occurrences, generate 5 UUIDs and replace each occurrence with the next UUID from the list.
- **NEVER** write UUIDs inline during file creation - always use the multistep placeholder approach
## For Editing Existing content.json Files
- **CRITICAL**: Read file first and preserve all existing UUIDs exactly as-is
- NEVER replace existing UUIDs with placeholders
- For newly added components/regions only, follow the multistep placeholder process from step 3

View File

@ -1,215 +0,0 @@
# UI Component Handling
**Use when** adding/configuring components to be used in Experience site.
## Component Insertion
Insert custom Lightning Web Components (LWC) into views.
### What is a Custom Component?
Any LWC in `c` namespace (e.g., `c:heroBanner`). Distinct from OOTB components (e.g., `community_builder:htmlEditor`).
### Prerequisites for Custom LWC
**js-meta.xml Requirements**:
- `<isExposed>true</isExposed>`
- Targets: `lightningCommunity__Page`, `lightningCommunity__Default`
**Property Type Constraints (MANDATORY GATE)**:
1. **Supported**: String, Integer, Boolean, Color, Picklist
2. **Unsupported**: Any other type → **STOP immediately**
- Do NOT delete, comment, or auto-correct
- Advise user to set up Custom Property Editor (CPE) or Custom Property Type
3. **Type Mismatch**: `type="Number"` → change to `type="Integer"` in js-meta.xml
**Do not proceed** until LWC files are compliant or user advised on CPE/CPT.
### Placement Hierarchy
**NEVER** place components directly in top-level regions. Must nest inside `community_layout:section` → column region.
```
community_layout:sldsFlexibleLayout (root)
└── region (content/header/footer)
└── community_layout:section
└── region (column: col1/col2)
└── component(s)
```
### Column Width & Layout
**12-unit grid**: Column widths sum to 12 per section.
**Width Formats**:
- Grid units: 8 + 4
- Percentages: 66% + 33% → 8 + 4; 50% + 50% → 6 + 6
- Ratios: 2:1 → 8 + 4; 1:1 → 6 + 6; equal thirds → 4 + 4 + 4
**Layout Rules**:
- One section = one horizontal row
- Multiple rows = multiple sections (siblings)
- Multiple components in column = vertical stack
**Set width in** `sectionConfig` (JSON string attribute on section component).
### sectionConfig Structure
**Top-level** (when parsed):
- `UUID`: Section ID (matches section component's `id`)
- `columns`: Array of column definitions
**Each column**:
- `UUID`: Column ID (matches column region's `id`)
- `columnKey`: Column identifier (e.g., `col1`, `col2`) - matches column region's `name`
- `columnName`: Display name (e.g., "Column 1")
- `columnWidth`: String from `"1"` to `"12"` (must sum to 12)
- `seedComponents`: Array or `null` (typically `[]` or `null`)
**Example** (serialized as JSON string in `sectionConfig` attribute):
```json
{
"UUID": "295e6a8b-fd94-485b-af9d-7ccf5b3048ee",
"columns": [
{
"UUID": "7e1f7e33-5ba8-4fef-8494-6ea3e90b22a0",
"columnKey": "col1",
"columnName": "Column 1",
"columnWidth": "12",
"seedComponents": null
}
]
}
```
### Named Region Creation
In order to create a component with drag-n-droppable region/slot that can be used in Experience Builder sites and persist across views, there are multiple steps needed.
Page layout components should add the lightningCommunity__Page_Layout target in js-meta.xml.
Theme layout components should add the lightningCommunity__Theme_Layout target in js-meta.xml.
Add lightningCommunity__Page as a target for page layouts and any component with slots that is not explicitly defined as a theme layout.
The js file in LWC need to declare named slots:
```js
/**
* @slot header
* @slot footer
*/
export default class YourComponentName extends LightningElement {}
```
Do not add any other comments in the declaration comment block. The named @slot annotations must be the last comments
in the block before the class declaration.
In html, named slots are needed. <slot name="header"> and <slot name="footer"> in the above example.
For theme layout component, a <slot> with no name is the main content region, a slot with name is a sticky region that doesn't change from page to page that uses the same theme layout component.
No need to declare target config properties for the slots/regions.
See the example below for adding a component with named slots into a view.
### Component Structure
```json
{
"id": "[UNIQUE_UUID]",
"type": "component",
"definition": "[NAMESPACE]:[COMPONENT_NAME]",
"attributes": {
"[ATTRIBUTE_NAME]": "[ATTRIBUTE_VALUE]"
}
}
```
**Field Definitions**:
- `id`: Unique UUID (see `handle-component-and-region-ids.md`)
- `type`: Always `"component"`
- `definition`:
- Custom LWC: `c:[componentName]` (e.g., `c:heroBanner`)
- OOTB: `[namespace]:[componentName]` (e.g., `community_builder:richTextEditor`)
- `attributes`: Component properties
- **Omit if no attributes** (don't include empty object)
- Custom LWC: Only `@api` properties in `targetConfigs` (with `lightningCommunity__Default` target)
- OOTB: Only exposed schema properties
### Complete Examples
**Example 1: Overall structure**
Correct nesting: `content` region → section → column region → components
```json
{
"type": "region",
"name": "content",
"children": [
{
"attributes": {
"sectionConfig": "{\"UUID\":\"295e6a8b-fd94-485b-af9d-7ccf5b3048ee\",\"columns\":[{\"UUID\":\"7e1f7e33-5ba8-4fef-8494-6ea3e90b22a0\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}"
},
"children": [
{
"children": [
{
"definition": "c:testComponent",
"id": "2ae498bd-2871-487d-8fb1-b186376cee3b",
"type": "component"
},
{
"id": "7c7d3b6a-1e2f-4a33-9c1e-8b2a6d5f4e3b",
"type": "component",
"definition": "c:helloWorld",
"attributes": {
"title": "Hello"
}
}
],
"id": "7e1f7e33-5ba8-4fef-8494-6ea3e90b22a0",
"name": "col1",
"title": "Column 1",
"type": "region"
}
],
"definition": "community_layout:section",
"id": "295e6a8b-fd94-485b-af9d-7ccf5b3048ee",
"type": "component"
}
]
}
```
**CRITICAL**: Follow UUID generation process (`handle-component-and-region-ids.md`) when inserting components.
**Example 2: Representing slots**
If a component with slots (i.e. @slot annotation) is inserted, slots must appear as named regions.
In this example the component threeColumn has 3 slots, named left, center, and right.
```json
{
"attributes" : { },
"children" : [ {
"id" : "4c6148c7-c07e-4245-ae50-ac07891046f2",
"name" : "left",
"title" : "left",
"type" : "region"
}, {
"id" : "f362e789-7f09-40b4-a59f-03f76ea73401",
"name" : "center",
"title" : "center",
"type" : "region"
}, {
"id" : "2678ddd4-a1a4-41c4-bf5a-1a3e55891eb2",
"name" : "right",
"title" : "right",
"type" : "region"
} ],
"definition" : "c:threeColumn",
"id" : "b9e517c5-90ac-49e9-91b7-3730512c95a3",
"type" : "component"
}

View File

@ -1,100 +0,0 @@
# Updating Experience Site URLs
Experience sites have a three-component architecture with two distinct URL patterns. Understanding this structure is critical when updating site URLs.
## Architecture Overview
Every Salesforce Experience Site consists of three components:
1. **Network** (metadata: `Network`) - Network configuration
2. **ChatterNetwork Site** (metadata: `CustomSite`) - Authentication endpoints and core site services
3. **ChatterNetworkPicasso Site** (metadata: `DigitalExperienceConfig` + `DigitalExperienceBundle`) - Customer-facing pages and content
## URL Pattern
These three components use **two different URLs**:
- **Primary URL** (ChatterNetworkPicasso): Used for customer-facing pages
- Defined in: `DigitalExperienceConfig``<urlPathPrefix>`
- Example: `mysite`
- **Secondary URL** (Network + CustomSite): Used for authentication endpoints and other services
- Defined in: `Network``<urlPathPrefix>` AND `CustomSite``<urlPathPrefix>`
- Example: `mysitevforcesite`
- **Must be synchronized** - both files must have identical values
By default, Salesforce differentiates these URLs by appending `vforcesite` suffix to the Network/CustomSite URL.
## URL Update Workflow
When updating site URLs, follow this workflow:
### Step 1: Discover All URL References
Search for all occurrences of `urlPathPrefix` across the project metadata files.
**For agents**: Use the `search_files` tool with these parameters:
- path: `force-app/main/default`
- regex: `urlPathPrefix`
- file_pattern: `*.xml`
**For humans**: Use your IDE's search functionality or command line tools:
```bash
# Using grep
grep -r "urlPathPrefix" force-app/main/default --include="*.xml"
# Using VS Code: Ctrl+Shift+F (Windows/Linux) or Cmd+Shift+F (Mac)
# Search for: urlPathPrefix
# Files to include: *.xml
```
### Step 2: Identify URL Groups
Determine which files belong to which URL group:
- **Primary URL Group**: `DigitalExperienceConfig`
- **Secondary URL Group**: `Network` AND `CustomSite`
### Step 3: Update URLs Consistently
Update the `<urlPathPrefix>` value in each file:
- **DigitalExperienceConfig**: Update to new primary URL
- **Network**: Update to new secondary URL (typically primary URL + `vforcesite`)
- **CustomSite**: Update to **same value as Network** (must be synchronized)
### Step 4: Validate Naming Convention
Ensure URL values follow best practices:
- Use lowercase letters only
- Avoid special characters except hyphens where appropriate
- Keep URLs concise and meaningful
### Step 5: Verify Consistency
Before deploying, confirm:
- [ ] Primary URL in `DigitalExperienceConfig` is set correctly
- [ ] Secondary URL in `Network` matches `CustomSite` exactly
- [ ] URLs are properly differentiated (typically via suffix)
- [ ] All URL values follow naming conventions
## Example URL Configuration
```
ChatterNetworkPicasso Site (Primary):
DigitalExperienceConfig: <urlPathPrefix>bestsupport</urlPathPrefix>
Network + ChatterNetwork Site (Secondary):
Network: <urlPathPrefix>bestsupportvforcesite</urlPathPrefix>
CustomSite: <urlPathPrefix>bestsupportvforcesite</urlPathPrefix>
```
## Common Pitfalls to Avoid
**Don't** update only one or two files - all three must be updated
**Don't** use different values in Network and CustomSite
**Don't** use the same URL for both Primary and Secondary groups
**Don't** skip the discovery step with `search_files`
**Do** use `search_files` to find all occurrences first
**Do** maintain URL differentiation between the two groups
**Do** follow lowercase naming conventions

View File

@ -1,6 +1,6 @@
---
name: generating-ui-bundle-features
description: "Search and install pre-built features into Salesforce React UI bundles — authentication, shadcn, search, navigation, GraphQL, Agentforce AI, and more. Use whenever searching for or installing features. Always check for an existing feature before building from scratch. Triggers on: install feature, add authentication, add shadcn, add feature, search features, list features."
description: "MUST activate when the project contains a uiBundles/*/src/ directory and the user wants to add authentication or search to their app. Use this skill when adding authentication or search to a UI bundle app. Only covers two features: authentication (login, logout, protected routes, session management) and search (global search across pages and content). Always use this skill for these two features instead of building from scratch."
---
# UI Bundle Features

View File

@ -1,6 +1,6 @@
---
name: generating-ui-bundle-metadata
description: "Scaffold new Salesforce UI bundles and configure their metadata — sf template generate ui-bundle, UIBundle bundles (meta XML, ui-bundle.json with routing/headers/outputDir), and CSP Trusted Sites for external domains. Use whenever creating a new UI bundle, setting up UI bundle metadata structure, configuring routing or headers, setting outputDir, adding external domains that need CSP registration, or editing bundle configuration. Triggers on: create UI bundle, create ui-bundle, new app, sf template generate ui-bundle, metadata, ui-bundle.json, CSP, trusted site, bundle configuration, meta XML, routing config, external domain, headers config, outputDir."
description: "MUST activate when the project contains a uiBundles/*/src/ directory and scaffolding a new UI bundle or app, or when editing ui-bundle.json, .uibundle-meta.xml, or CSP trusted site files. Use this skill when scaffolding with sf template generate ui-bundle, configuring ui-bundle.json (routing, headers, outputDir), or registering CSP Trusted Sites. Activate when the task involves files matching *.uibundle-meta.xml, ui-bundle.json, or cspTrustedSites/*.cspTrustedSite-meta.xml."
---
# UI Bundle Metadata
@ -9,7 +9,14 @@ description: "Scaffold new Salesforce UI bundles and configure their metadata
Use `sf template generate ui-bundle` to create new apps — not create-react-app, Vite, or other generic scaffolds.
**UI bundle name (`-n`):** Alphanumerical only — no spaces, hyphens, underscores, or special characters. Example: `CoffeeBoutique` (not `Coffee Boutique`).
**Always pass `--template reactbasic`** to scaffold a React-based bundle.
**UI bundle name (`-n`):** Alphanumerical only — no spaces, hyphens, underscores, or special characters.
**Example:**
```bash
sf template generate ui-bundle -n CoffeeBoutique --template reactbasic
```
After generation:
1. Replace all default boilerplate — "React App", "Vite + React", default `<title>`, placeholder text

View File

@ -1,6 +1,6 @@
---
name: generating-ui-bundle-site
description: "Use this skill when users need to create or configure a Salesforce Digital Experience Site specifically for hosting a React UI bundle. Trigger when users mention creating an Experience site for a React app, setting up a React site on Salesforce, configuring Network/CustomSite/DigitalExperience metadata for a UI bundle, or deploying site infrastructure for a React application. Also trigger when users mention site URL path prefixes, app namespaces, appDevName, guest access configuration, DigitalExperienceConfig, DigitalExperienceBundle, or sfdc_cms__site content types in the context of React apps. Always use this skill for any React UI bundle site creation or site infrastructure configuration work, even if the user just says \"create a site for my React app\" or \"set up the site for my UI bundle.\""
description: "MUST activate when the project contains a uiBundles/*/src/ directory and the task involves creating or configuring site infrastructure. Use this skill when creating or configuring a Salesforce Digital Experience Site for hosting a UI bundle. Activate when files matching digitalExperiences/, networks/, customSite/, or DigitalExperienceBundle exist and need modification, or when the user wants to publish, host, or configure guest access for their app."
---
# Digital Experience Site for React UI Bundles

View File

@ -1,6 +1,6 @@
---
name: implementing-ui-bundle-agentforce-conversation-client
description: Use this skill when the user asks to add, embed, integrate, configure, style, or remove an agent, chatbot, chat widget, conversation client, or AI assistant in a React component. Covers styling (colors, fonts, spacing, borders), layout (inline vs floating, width, height, dimensions), and props (agentId, agentLabel, headerEnabled, showHeaderIcon, showAvatar, styleTokens). Also matches references to specific agents (e.g. travel agent, HR agent, support agent, employee agent). Never create a custom agent, chatbot, or chat widget component.
description: "MUST activate when the project contains a uiBundles/*/src/ directory and the task involves adding or modifying a chat widget, chatbot, or conversational AI. Use this skill when the user asks to add, embed, integrate, configure, style, or remove an agent, chatbot, chat widget, conversation client, or AI assistant. Covers styling (colors, fonts, spacing, borders), layout (inline vs floating, width, height, dimensions), and props (agentId, agentLabel, headerEnabled, showHeaderIcon, showAvatar, styleTokens). Activate when files under uiBundles/*/src/ import AgentforceConversationClient or when adding any chat or agent functionality to a page. Never create a custom agent, chatbot, or chat widget component."
metadata:
author: ACC Components
version: 1.0.1

View File

@ -1,6 +1,6 @@
---
name: implementing-ui-bundle-file-upload
description: "Add file upload functionality to React UI bundles with progress tracking and Salesforce ContentVersion integration. Use when the user wants to upload files, attach documents, handle file input, create file dropzones, track upload progress, or link files to Salesforce records. This feature provides programmatic APIs ONLY — no components or hooks are exported. Build your own custom UI using the upload() API. ALWAYS use this feature instead of building file upload from scratch with FormData or XHR."
description: "MUST activate when the project contains a uiBundles/*/src/ directory and the task involves uploading, attaching, or dropping files. Use this skill when adding file upload functionality to a UI bundle app. Provides progress tracking and Salesforce ContentVersion integration. This feature provides programmatic APIs ONLY — build custom UI using the upload() API. ALWAYS use this instead of building file upload from scratch with FormData or XHR."
---
# File Upload API (workflow)

View File

@ -1,31 +1,22 @@
---
name: using-ui-bundle-salesforce-data
description: "Salesforce data access for reading, writing, and querying records via REST, GraphQL, Apex, or Platform SDK. Use when the user wants to fetch, search, filter, sort, display, create, update, delete, or attach files to Salesforce records (standard objects like Accounts, Contacts, Opportunities, Cases, Quotes, or any custom object) in a UI bundle or UI component (React, Angular, Vue, etc.); call Chatter, Connect, or Apex REST APIs; or invoke AuraEnabled Apex methods from an external app. Does not apply to authentication/OAuth setup, schema changes (adding fields, relationships), Bulk/Tooling/Metadata API usage, declarative automation (Flows, Process Builder), general LWC/Apex coding guidance without a specific data operation, or Salesforce admin/configuration tasks."
description: "MUST activate when the project contains a uiBundles/*/src/ directory and the task involves ANY Salesforce record operation — reading, creating, updating, or deleting. Use this skill when building forms that submit to Salesforce, pages that display Salesforce records, or any code that touches Salesforce objects or custom objects. Activate when files under uiBundles/*/src/ import from @salesforce/sdk-data, or when *.graphql files or codegen.yml exist. This skill owns all Salesforce data access patterns in UI bundles. Does not apply to authentication/OAuth setup, schema changes, Bulk/Tooling/Metadata API, or declarative automation."
---
# Salesforce Data Access
## When to Use
Use this skill when the user wants to:
- **Fetch or display Salesforce data** — Query records (Account, Contact, Opportunity, custom objects) to show in a component
- **Create, update, or delete records** — Perform mutations on Salesforce data
- **Add data fetching to a component** — Wire up a React component to Salesforce data
- **Call REST APIs** — Use Connect REST, Apex REST, or UI API endpoints
- **Explore the org schema** — Discover available objects, fields, or relationships
## Data SDK Requirement
> **All Salesforce data access MUST use the Data SDK** (`@salesforce/sdk-data`). The SDK handles authentication, CSRF, and base URL resolution.
```typescript
import { createDataSDK, gql } from "@salesforce/sdk-data";
import type { ResponseTypeQuery } from "../graphql-operations-types";
const sdk = await createDataSDK();
// GraphQL for record queries/mutations (PREFERRED)
const response = await sdk.graphql?.<ResponseType>(query, variables);
const response = await sdk.graphql?.<ResponseTypeQuery>(query, variables);
// REST for Connect REST, Apex REST, UI API (when GraphQL insufficient)
const res = await sdk.fetch?.("/services/apexrest/my-resource");
@ -33,6 +24,16 @@ const res = await sdk.fetch?.("/services/apexrest/my-resource");
**Always use optional chaining** (`sdk.graphql?.()`, `sdk.fetch?.()`) — these methods may be undefined in some surfaces.
## Preconditions — verify before starting
| # | Requirement | How to verify | If missing |
|---|-------------|---------------|------------|
| 1 | `@salesforce/sdk-data` installed | Check `package.json` in the UI bundle dir | Cannot proceed — tell user to install it |
| 2 | `schema.graphql` at project root | Check if file exists | Run `npm run graphql:schema` from UI bundle dir |
| 3 | Custom objects/fields deployed | Run `graphql-search.sh <Entity>` — no output means not deployed | Ask user to deploy metadata and assign permission sets |
**If preconditions are not met**, you may scaffold components, routes, layout, and UI logic, but use empty arrays / `null` for data and mark query locations with `// TODO: add query after schema verification` and include in the plan to go back, resolve requirements and write the GraphQL. Do not write GraphQL query strings until the schema workflow is complete.
## Supported APIs
**Only the following APIs are permitted.** Any endpoint not listed here must not be used.
@ -71,90 +72,241 @@ const res = await sdk.fetch?.("/services/apexrest/my-resource");
These rules exist because Salesforce GraphQL has platform-specific behaviors that differ from standard GraphQL. Violations cause silent runtime failures.
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.
1. **HTTP 200 does not mean success** — Salesforce returns HTTP 200 even when operations fail. **Always parse the `errors` array in the response body.**
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 (`??`).
2. **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`). Objects added to UI API in v60+ may use a `_Record` suffix (e.g., `FeedItem_Record` instead of `FeedItem`).
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).
3. **`@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 (`??`).
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.
4. **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.
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.
5. **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. Forward-only (`first`/`after`) — `last`/`before` are unsupported.
6. **HTTP 200 does not mean success** — Salesforce returns HTTP 200 even when operations fail. Always parse the `errors` array in the response body.
6. **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.
7. **Only requested fields** — Only generate fields the user explicitly asked for. Do NOT add extra fields.
8. **Compound fields** — When filtering or ordering, use constituent fields (e.g., `BillingCity`, `BillingCountry`), not the compound wrapper (`BillingAddress`). The compound wrapper is only for selection.
---
## GraphQL Workflow
| Step | Action | Key output |
|------|--------|------------|
| 1 | Acquire schema | `schema.graphql` exists |
| 2 | Look up entities | Field names, types, relationships confirmed |
| 3 | Generate query | `.graphql` file or inline `gql` tag |
| 4 | Generate types | `graphql-operations-types.ts` |
| 5 | Validate | Lint + codegen pass |
### Step 1: Acquire Schema
The `schema.graphql` file (265K+ lines) is the source of truth. **Never open or parse it directly.**
The `schema.graphql` file (265K+ lines) is the source of truth. **Never open or parse it directly** — no cat, less, head, tail, editors, or programmatic parsers.
1. Check if `schema.graphql` exists at the SFDX project root
2. If missing, run from the **UI bundle dir**: `npm run graphql:schema`
3. Custom objects appear only after metadata is deployed
Verify preconditions 13 (see [Preconditions](#preconditions--verify-before-starting)), then proceed to Step 2.
### Step 2: Look Up Entity Schema
Map user intent to PascalCase names ("accounts" → `Account`), then **run the search script from the project root**:
Map user intent to PascalCase names ("accounts" → `Account`), then **run the search script from the `sfdx-project` folder (project root)**:
```bash
# Look up all relevant schema info for one or more entities
bash scripts/graphql-search.sh Account
# Multiple entities at once
# Multiple entities:
bash scripts/graphql-search.sh Account Contact Opportunity
```
The script outputs five sections per entity:
The script outputs seven sections per entity:
1. **Type definition** — all queryable fields and relationships
2. **Filter options** — available fields for `where:` conditions
3. **Sort options** — available fields for `orderBy:`
4. **Create input** — fields accepted by create mutations
5. **Update input** — fields accepted by update mutations
4. **Create mutation wrapper**`<Entity>CreateInput`
5. **Create mutation fields**`<Entity>CreateRepresentation` (fields accepted by create mutations)
6. **Update mutation wrapper**`<Entity>UpdateInput`
7. **Update mutation fields**`<Entity>UpdateRepresentation` (fields accepted by update mutations)
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).
**Maximum 2 script runs.** If the entity still can't be found, ask the user — the object may not be deployed.
#### Entity Identification
If a candidate does not match:
- Try `__c` suffix for custom objects, `__e` for platform events
- Try `_Record` suffix — objects added in v60+ may use `<EntityName>_Record`
- If still unresolved, **ask the user** — do not guess
#### Iterative Introspection (max 3 cycles)
1. **Introspect** — Run the script for each unresolved entity
2. **Fields** — Extract requested field names and types from the type definition
3. **References** — Identify reference fields. If polymorphic (multiple types), use inline fragments. Add newly discovered entity types to the working list.
4. **Child relationships** — Identify Connection types. Add child entity types to the working list.
5. **Repeat** if unresolved entities remain (max 3 cycles)
**Hard stops:** If no data returned for an entity, stop — it may not be deployed. If unknown entities remain after 3 cycles, ask the user. Do not generate queries with unconfirmed entities or fields.
### Step 3: Generate Query
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).
Every field name **must** be verified from the script output in Step 2.
#### Read Query Template
```graphql
query GetAccounts {
query QueryName($after: String) {
uiapi {
query {
Account(where: { Industry: { eq: "Technology" } }, first: 10) {
EntityName(
first: 10
after: $after
where: { ... }
orderBy: { ... }
) {
edges {
node {
Id
Name @optional { value }
Industry @optional { value }
# Parent relationship
FieldName @optional { value }
# Parent relationship (non-polymorphic)
Owner @optional { Name { value } }
# Child relationship
Contacts @optional {
# Parent relationship (polymorphic — use fragments)
What @optional {
...WhatAccount
...WhatOpportunity
}
# Child relationship — max 1 level, no grandchildren
Contacts @optional(first: 10) {
edges { node { Name @optional { value } } }
}
}
}
pageInfo { hasNextPage endCursor }
}
}
}
}
fragment WhatAccount on Account {
Id
Name @optional { value }
}
fragment WhatOpportunity on Opportunity {
Id
Name @optional { value }
}
```
**Consuming code must defend against missing fields:**
```typescript
const name = node.Name?.value ?? "";
const relatedName = node.Owner?.Name?.value ?? "N/A";
```
#### Filtering
```graphql
# Implicit AND
Account(where: { Industry: { eq: "Technology" }, AnnualRevenue: { gt: 1000000 } })
# Explicit OR
Account(where: { OR: [{ Industry: { eq: "Technology" } }, { Industry: { eq: "Finance" } }] })
# NOT
Account(where: { NOT: { Industry: { eq: "Technology" } } })
# Date literal
Opportunity(where: { CloseDate: { eq: { value: "2024-12-31" } } })
# Relative date
Opportunity(where: { CloseDate: { gte: { literal: TODAY } } })
# Relationship filter (nested objects, NOT dot notation)
Contact(where: { Account: { Name: { like: "Acme%" } } })
# Polymorphic relationship filter
Account(where: { Owner: { User: { Username: { like: "admin%" } } } })
```
String equality (`eq`) is case-insensitive. Both 15-char and 18-char record IDs are accepted.
#### Ordering
```graphql
Account(
first: 10,
orderBy: { Name: { order: ASC }, CreatedDate: { order: DESC } }
) { ... }
```
Unsupported for ordering: multi-select picklist, rich text, long text area, encrypted fields. Add `Id` as tie-breaker for deterministic ordering.
#### UpperBound Pagination (v59+)
For >200 records per page or >4,000 total records, use `upperBound`. `first` must be 2002000 when set.
```graphql
Account(first: 2000, after: $cursor, upperBound: 10000) {
edges { node { Id Name @optional { value } } }
pageInfo { hasNextPage endCursor }
}
```
#### Semi-Join and Anti-Join
Filter a parent entity by conditions on child entities using `inq` (semi-join) or `ninq` (anti-join) on the parent's `Id`. If the only condition is child existence, use `Id: { ne: null }`.
```graphql
query SemiJoinExample {
uiapi {
query {
Account(where: {
Id: {
inq: {
Contact: { LastName: { like: "Smith%" } }
ApiName: "AccountId"
}
}
}, first: 10) {
edges { node { Id Name @optional { value } } }
}
}
}
}
```
**FLS Resilience**: Apply `@optional` to all record fields. The server omits inaccessible fields instead of failing. Consuming code must use optional chaining:
Replace `inq` with `ninq` for anti-join. Restrictions: no `OR` in subquery, no `orderBy` in subquery, no nesting joins within each other.
```typescript
const name = node.Name?.value ?? "";
#### Current User
Use `uiapi.currentUser` (no arguments) instead of the standard query pattern:
```graphql
query CurrentUser {
uiapi { currentUser { Id Name { value } } }
}
```
#### Field Value Wrappers
Schema fields use typed wrappers — access via `.value`:
| Wrapper Type | Underlying | Wrapper Type | Underlying |
|---|---|---|---|
| `StringValue` | `String` | `BooleanValue` | `Boolean` |
| `IntValue` | `Int` | `DoubleValue` | `Double` |
| `CurrencyValue` | `Currency` | `PercentValue` | `Percent` |
| `DateTimeValue` | `DateTime` | `DateValue` | `Date` |
| `PicklistValue` | `Picklist` | `LongValue` | `Long` |
| `IDValue` | `ID` | `TextAreaValue` | `TextArea` |
| `EmailValue` | `Email` | `PhoneNumberValue` | `PhoneNumber` |
| `UrlValue` | `Url` | | |
All wrappers also expose `displayValue: String` (server-rendered via `toLabel()`/`format()`) — use for UI display instead of formatting client-side.
#### Mutation Template
Mutations are GA in API v66+. Three operations: **Create**, **Update**, **Delete**.
```graphql
# Create
mutation CreateAccount($input: AccountCreateInput!) {
uiapi(input: { allOrNone: true }) {
AccountCreate(input: $input) {
@ -162,20 +314,71 @@ mutation CreateAccount($input: AccountCreateInput!) {
}
}
}
# Update — must include Id
mutation UpdateAccount {
uiapi(input: { allOrNone: true }) {
AccountUpdate(input: { Id: "001xx000003GYkZAAW", Account: { Name: "New Name" } }) {
Record { Id Name { value } }
}
}
}
```
**Mutation constraints:**
- Create: Include required fields, only `createable` fields, no child relationships
- Update: Include `Id`, only `updateable` fields
- Delete: Include `Id` only
**Input constraints:**
- **Create**: Required fields (unless `defaultedOnCreate`), only `createable` fields, no child relationships. Reference fields set by `ApiName` (e.g., `AccountId`).
- **Update**: Must include `Id`, only `updateable` fields, no child relationships.
- **Delete**: `Id` only.
- **`IdOrRef` type**: The `Id` field in Update and Delete inputs uses the `IdOrRef` type, which accepts either a literal record ID (e.g., `"001xx..."`) or a mutation chaining reference (`"@{Alias}"`). Reference fields in Create inputs (e.g., `AccountId`) also accept `@{Alias}` for chaining.
- **Raw values**: No commas, currency symbols, or locale formatting (e.g., `80000` not `"$80,000"`).
**Output constraints:**
- Create/Update: Exclude child relationships, exclude navigated reference fields (only `ApiName` member allowed). Output field is always named `Record`.
- Delete: `Id` only.
**`allOrNone` semantics:**
- `true` (default) — All operations succeed or all roll back.
- `false` — Independent operations succeed individually, but dependent operations (using `@{alias}`) still roll back together.
#### Mutation Chaining
Chain related mutations using `@{alias}` references to `Id` from earlier mutations. Required for parent-child creation (nested child creates are not supported).
```graphql
mutation CreateAccountAndContact {
uiapi(input: { allOrNone: true }) {
AccountCreate(input: { Account: { Name: "Acme" } }) {
Record { Id }
}
ContactCreate(input: { Contact: { LastName: "Smith", AccountId: "@{AccountCreate}" } }) {
Record { Id }
}
}
}
```
Rules: `A` must come before `B` in the query. `@{A}` is always the `Id` from mutation `A`. Only `Create` or `Delete` can be chained from (not `Update`).
#### Delete Mutation
Delete uses generic `RecordDeleteInput` (not entity-specific). Output is `Id` only — no `Record` field.
```graphql
mutation DeleteAccount($id: ID!) {
uiapi(input: { allOrNone: true }) {
AccountDelete(input: { Id: $id }) {
Id
}
}
}
```
#### Object Metadata & Picklist Values
Use `uiapi { objectInfos(...) }` to fetch field metadata or picklist values. Pass **either** `apiNames` or `objectInfoInputs` — never both in the same query.
**Object metadata** (field labels, data types, CRUD flags):
Use `uiapi { objectInfos(...) }` to fetch field metadata or picklist values. Pass **either** `apiNames` or `objectInfoInputs` — never both.
```typescript
// Object metadata
const GET_OBJECT_INFO = gql`
query GetObjectInfo($apiNames: [String!]!) {
uiapi {
@ -183,26 +386,13 @@ const GET_OBJECT_INFO = gql`
ApiName
label
labelPlural
fields {
ApiName
label
dataType
updateable
createable
}
fields { ApiName label dataType updateable createable }
}
}
}
`;
const sdk = await createDataSDK();
const response = await sdk.graphql?.(GET_OBJECT_INFO, { apiNames: ["Account"] });
const objectInfos = response?.data?.uiapi?.objectInfos ?? [];
```
**Picklist values** (use `objectInfoInputs` + `... on PicklistField` inline fragment):
```typescript
// Picklist values (use objectInfoInputs + inline fragment)
const GET_PICKLIST_VALUES = gql`
query GetPicklistValues($objectInfoInputs: [ObjectInfoInput!]!) {
uiapi {
@ -213,10 +403,7 @@ const GET_PICKLIST_VALUES = gql`
... on PicklistField {
picklistValuesByRecordTypeIDs {
recordTypeID
picklistValues {
label
value
}
picklistValues { label value }
}
}
}
@ -224,52 +411,95 @@ const GET_PICKLIST_VALUES = gql`
}
}
`;
const response = await sdk.graphql?.(GET_PICKLIST_VALUES, {
objectInfoInputs: [{ objectApiName: "Account" }],
});
const fields = response?.data?.uiapi?.objectInfos?.[0]?.fields ?? [];
```
### Step 4: Validate & Test
### Step 4: Generate Types (codegen)
1. **Lint**: `npx eslint <file>` from UI bundle dir
2. **Test**: Ask user before testing. For mutations, request input values — never fabricate data.
**If ESLint reports a GraphQL error** (e.g. `Cannot query field`, `Unknown type`, `Unknown argument`), the field or type name is wrong. Re-run the schema search script to find the correct name — do not guess:
After writing the query (whether in a `.graphql` file or inline with `gql`), generate TypeScript types:
```bash
# From project root — re-check the entity that caused the error
bash scripts/graphql-search.sh <EntityName>
# Run from UI bundle dir
npm run graphql:codegen
```
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).
Output: `src/api/graphql-operations-types.ts`
Generated type naming conventions:
- `<OperationName>Query` / `<OperationName>Mutation` — response types
- `<OperationName>QueryVariables` / `<OperationName>MutationVariables` — variable types
**Always import and use the generated types** when calling `sdk.graphql`:
```typescript
import type { GetAccountsQuery, GetAccountsQueryVariables } from "../graphql-operations-types";
const response = await sdk.graphql?.<GetAccountsQuery, GetAccountsQueryVariables>(GET_ACCOUNTS, variables);
```
Use `NodeOfConnection<T>` to extract the node type from a Connection for cleaner typing:
```typescript
import { type NodeOfConnection } from "@salesforce/sdk-data";
type AccountNode = NodeOfConnection<GetAccountsQuery["uiapi"]["query"]["Account"]>;
```
### Step 5: Validate & Test
1. **Lint**: `npx eslint <file>` from UI bundle dir
2. **codegen**: `npm run graphql:codegen` from UI bundle dir
#### Common Error patterns
| Error Contains | Resolution |
|----------------|------------|
| `Cannot query field` / `ValidationError` | Field name wrong — re-run `graphql-search.sh <Entity>` |
| `Unknown type` | Type name wrong — verify PascalCase entity name via script |
| `Unknown argument` | Argument wrong — check Filter/OrderBy sections in script output |
| `invalid syntax` / `InvalidSyntax` | Fix syntax per error message |
| `VariableTypeMismatch` / `UnknownType` | Correct argument type from schema |
| `invalid cross reference id` | Entity deleted — ask for valid Id |
| `OperationNotSupported` | Check object availability and API version |
| `is not currently available in mutation results` | Remove field from mutation output |
| `Cannot invoke JsonElement.isJsonObject()` | Use API version 64+ for update mutation `Record` selection |
**On PARTIAL** If a mutation returns both data and errors (partial success): Report inaccessible fields, explain they cannot be in mutation output, offer to remove them. **Wait for user consent** before changing.
---
## UI Bundle Integration (React)
Two integration patterns are available:
Two integration patterns:
- **Pattern 1 — External `.graphql` file** (recommended for complex queries): Create a `.graphql` file, run `npm run graphql:codegen`, import with `?raw` suffix
- **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.
### Pattern 1 — External `.graphql` file (complex queries)
**One operation per `.graphql` file.** Each file contains exactly one `query` or `mutation` (plus its fragments). Do not combine multiple operations in a single file.
```typescript
import { createDataSDK, type NodeOfConnection } from "@salesforce/sdk-data";
import MY_QUERY from "./query/myQuery.graphql?raw"; // ?raw suffix required
import type { GetMyDataQuery, GetMyDataQueryVariables } from "../graphql-operations-types";
const sdk = await createDataSDK();
const response = await sdk.graphql?.<GetMyDataQuery, GetMyDataQueryVariables>(MY_QUERY, variables);
```
After creating/changing `.graphql` files, run `npm run graphql:codegen` to generate types into `src/api/graphql-operations-types.ts`.
### Pattern 2 — Inline `gql` tag (simple queries)
**Must use `gql`** — plain template strings bypass ESLint schema validation.
```typescript
import { createDataSDK, gql } from "@salesforce/sdk-data";
import type { GetAccountsQuery } from "../graphql-operations-types";
const GET_ACCOUNTS = gql`
query GetAccounts {
uiapi {
query {
Account(first: 10) {
edges {
node {
Id
Name @optional {
value
}
}
}
edges { node { Id Name @optional { value } } }
}
}
}
@ -277,15 +507,30 @@ const GET_ACCOUNTS = gql`
`;
const sdk = await createDataSDK();
const response = await sdk.graphql?.(GET_ACCOUNTS);
const response = await sdk.graphql?.<GetAccountsQuery>(GET_ACCOUNTS);
```
### Error Handling
```typescript
// Strict (default) — any errors = failure
if (response?.errors?.length) {
throw new Error(response.errors.map(e => e.message).join("; "));
}
// Tolerant — log errors, use available data
if (response?.errors?.length) {
console.warn("GraphQL partial errors:", response.errors);
}
// Discriminated — fail only when no data returned
if (!response?.data && response?.errors?.length) {
throw new Error(response.errors.map(e => e.message).join("; "));
}
const accounts = response?.data?.uiapi?.query?.Account?.edges?.map(e => e.node) ?? [];
```
For detailed patterns (external .graphql files, codegen, error handling strategies, quality checklists), see [UI Bundle Integration](references/ui-bundle-integration.md).
---
## REST API Patterns
@ -335,6 +580,7 @@ const response = await sdk.graphql?.(GET_CURRENT_USER);
<project-root>/ ← SFDX project root
├── schema.graphql ← grep target (lives here)
├── sfdx-project.json
├── scripts/graphql-search.sh ← schema lookup script
└── force-app/main/default/uiBundles/<app-name>/ ← UI bundle dir
├── package.json ← npm scripts
└── src/
@ -343,9 +589,9 @@ const response = await sdk.graphql?.(GET_CURRENT_USER);
| Command | Run From | Why |
|---------|----------|-----|
| `npm run graphql:schema` | UI bundle dir | Script in UI bundle's package.json |
| `npm run graphql:codegen` | UI bundle dir | Generate GraphQL types |
| `npx eslint <file>` | UI bundle dir | Reads eslint.config.js |
| `bash scripts/graphql-search.sh <Entity>` | project root | Schema lookup |
| `sf api request rest` | project root | Needs sfdx-project.json |
---

View File

@ -1,140 +0,0 @@
# Mutation Query Generation
## Mutation Types
The GraphQL engine supports three mutation operations:
- **Create** — Insert a new record
- **Update** — Modify an existing record (Id-based)
- **Delete** — Remove an existing record (Id-based)
Mutations are GA in API v66+. They live under `mutation { uiapi { ... } }` and only support UI API-available objects.
## Generation Rules
1. **Input fields validation** — Validate that input fields satisfy the constraints for the operation type
2. **Output fields validation** — Validate that output fields satisfy the constraints for the operation type
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
4. **Input arguments**`input` is the default argument name unless otherwise specified
5. **Output field** — For `Create` and `Update`, the output field is always named `Record` (type: EntityName)
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
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.
## Transactional Semantics: `allOrNone`
The `uiapi` mutation input accepts an `allOrNone` argument that controls rollback behavior:
- **`allOrNone: true` (default)** — If any operation fails, all operations in the request are rolled back. Use when operations must succeed or fail together.
- **`allOrNone: false`** — Independent operations can succeed individually. However, dependent operations (those using `@{alias}` references) still roll back together with their dependencies.
Always set `allOrNone` explicitly to make transactional intent clear.
## Mutation Schema Patterns
Replace `EntityName` with the actual entity name (e.g., Account, Case). `Delete` operations use generic `Record` types.
```graphql
input EntityNameCreateRepresentation {
# Subset of EntityName fields
}
input EntityNameCreateInput { EntityName: EntityNameCreateRepresentation! }
type EntityNameCreatePayload { Record: EntityName! }
input EntityNameUpdateRepresentation {
# Subset of EntityName fields
}
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
}
```
## Input Field Constraints
### Create
- **Must** include all required fields (unless `defaultedOnCreate` is `true` and not explicitly requested)
- **Must** only include `createable` fields
- Child relationships cannot be set — exclude them
- Reference fields (`REFERENCE` type) can only be assigned IDs through their `ApiName` name
- **No nested child creates** — Creating a record with child relationships in a single create operation is not supported. To create a parent and child together, use separate operations with `IdOrRef` chaining (see [Mutation Chaining](#mutation-chaining)).
### Update
- **Must** include the `Id` of the entity to update
- **Must** only include `updateable` fields
- Child relationships cannot be set — exclude them
- Reference fields (`REFERENCE` type) can only be assigned IDs through their `ApiName` name
### Delete
- **Must** include the `Id` of the entity to delete
## Output Field Constraints
### Create and Update
- **Must** exclude all child relationships (child relationships cannot be queried in mutations)
- **Must** exclude all `REFERENCE` fields unless accessed through their `ApiName` member (no navigation to referenced entity, no sub fields)
- Inaccessible fields are reported in the `errors` attribute of the returned payload
### Delete
- **Must** only include the `Id` field
## Mutation Chaining
Chain related mutations in a single request using references to `Id` values from previous mutations. This is the required approach for creating parent-child records together, since nested child creates are not supported.
1. **Ordering** — Mutation `B` can reference mutation `A` only if `A` comes first in the query
2. **Notation** — Use `SomeId: "@{A}"` in mutation `B` to set a field to the `Id` produced by mutation `A`
3. **IDs only**`@{A}` is always interpreted as the `Id` from mutation `A`
4. **Restrictions**`A` must be a `Create` or `Delete` mutation (chaining from `Update` will fail)
### Chaining Example
```graphql
mutation CreateAccountAndContact {
uiapi(input: { allOrNone: true }) {
AccountCreate(input: { Account: { Name: "Acme" } }) {
Record { Id }
}
ContactCreate(input: { Contact: { LastName: "Smith", AccountId: "@{AccountCreate}" } }) {
Record { Id }
}
}
}
```
## Mutation Query Template
```graphql
mutation mutateEntityName(
# arguments
) {
uiapi(input: { allOrNone: true }) {
EntityNameOperation(input: {
# For Create and Update only:
EntityName: {
# Input fields — use raw values, no formatting
}
# For Update and Delete only:
Id: ... # id here
}) {
# For Create and Update only:
Record {
# Output fields
}
# For Delete only:
Id
}
}
}
```

View File

@ -1,78 +0,0 @@
# Query Testing
## Testing Method
Use `sf api request rest` to POST the query to the GraphQL endpoint. Run from the **SFDX project root** (where `sfdx-project.json` lives).
```bash
sf api request rest /services/data/v66.0/graphql \
--method POST \
--body '{"query":"query GetData { uiapi { query { EntityName { edges { node { Id } } } } } }"}'
```
- Use the API version of the target org (v66.0+ for mutation support, v65.0+ for `@optional`)
- Replace the `query` value with the generated query string
- If the query uses variables, include them in the JSON body as a `variables` key
## Critical: HTTP 200 Does Not Mean Success
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.
## Testing Workflow
This workflow applies to both read and mutation queries:
1. **Report method** — State the exact method: `sf api request rest` POST to `/services/data/vXX.0/graphql` from the project root
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.
3. **Execute test** — Only if the user explicitly agrees. Run `sf api request rest` with the query, variables, and correct API version
4. **Report result** — Classify the result using the status definitions below. Always check the `errors` array in the response, even on HTTP 200.
## Result Status Definitions
| Status | Condition | Meaning |
| --------- | ----------------------------------------------- | --------------------------------------------- |
| `SUCCESS` | `errors` is absent or empty | Query is valid (even if no data is returned) |
| `FAILED` | `data` is empty or null | Query is invalid |
| `PARTIAL` | `data` is present **and** `errors` is not empty | Some fields are inaccessible (mutations only) |
## FAILED Status Handling
The query is invalid. Follow this sequence:
### 1. Error Analysis
Parse the `errors` array and check `errors[].extensions.ErrorType` for Salesforce-specific error classification. Categorize into:
| Category | ErrorType / Message Contains | Resolution |
| --------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| **Syntax** | `InvalidSyntax` | Fix syntax errors using the error message details |
| **Validation** | `ValidationError` | Field name is likely invalid — re-run the schema search script, ask user if still unclear |
| **Type** | `VariableTypeMismatch` or `UnknownType` | Use error details and schema to correct the argument type; adjust variables |
| **Execution** | `DataFetchingException`, `invalid cross reference id` | Entity is unknown/deleted — create entity first if possible, or ask for a valid Id |
| **Navigation** | `is not currently available in mutation results` | Field cannot be in mutation output — apply PARTIAL status handling |
| **Unsupported** | `OperationNotSupported` | The operation is not supported — check object availability and API version |
| **API Version** | `Cannot invoke JsonElement.isJsonObject()` (on update mutations) | `Record` selection requires API version 64+ — report and retry with version 64 |
### 2. Targeted Resolution
Apply the resolution from the table above based on the error category. Update the query accordingly.
### 3. Test Again
Re-run the testing workflow with the updated query. Increment and track the attempt counter.
## PARTIAL Status Handling
The query executed but some fields are inaccessible (mutations only):
1. Report the fields listed in the `errors` attribute
2. Explain that these fields cannot be queried as part of a mutation
3. Explain that the query will report errors if these fields remain
4. Offer to remove the offending fields
5. **STOP and WAIT** for the user's answer. Do NOT remove fields without explicit consent.
6. If the user agrees, restart the mutation generation workflow with the updated field list
## Retry and Escalation
- **Maximum 2 test attempts** per generated query
- 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

View File

@ -1,307 +0,0 @@
# Read Query Generation
## Generation Rules
1. **No proliferation** — Only generate for 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. Exception: if `relationshipName` is null, return the `Id` itself
4. **Leverage fragments** — Generate one fragment per possible type on polymorphic fields (fields with `dataType="REFERENCE"` and more than one entry in `referenceToInfos`)
5. **Type consistency** — Variables used as query arguments and their related fields must share the same GraphQL type. Verify types against the schema search script output — do not assume types
6. **Type enforcement** — Use field type information from introspection and the GraphQL schema to generate correct field access
7. **Field name validation** — Every field name in the generated query **MUST** match a field confirmed via the schema search script. 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](#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](#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. **Explicit pagination** — Always include `first:` in every query to control page size (see [Pagination](#pagination)). Default is 10 if omitted.
12. **Respect execution limits** — Stay within SOQL-derived limits: max 10 subqueries per request, max 5 child-to-parent relationship levels, max 1 parent-to-child level (no grandchildren), max 55 child-to-parent relationships, max 20 parent-to-child relationships per query
13. **Compound fields** — When filtering, ordering, or aggregating, use constituent fields (e.g., `BillingCity`, `BillingCountry`) not the compound wrapper (`BillingAddress`). The compound wrapper is only for selection.
14. **`_Record` suffix awareness** — Objects added to UI API in v60+ may use a `_Record` suffix for their type name (e.g., `FeedItem_Record` instead of `FeedItem`). Always verify type names via schema lookup — do not assume type name equals sObject API name.
15. **Query generation** — Use the read query template below
## 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. Available in API v65.0+.
Apply `@optional` to:
- Scalar fields and value-type fields (e.g. `Name { value }`)
- Parent relationships
- Child relationships
**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.
```ts
// 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;
```
## Pagination
Salesforce GraphQL uses Relay Cursor Connections with **forward-only pagination**. There is no backward pagination (`last`/`before` are not supported).
### Core Rules
- **Always specify `first:`** — If omitted, the server defaults to 10 records. Be explicit.
- **Forward-only** — Use `first` and `after` only. Do **not** use `last` or `before` — they are unsupported and will fail.
- **Maximum without upperBound** — Standard pagination allows up to 4,000 total records across pages.
- **Use `pageInfo`** — Select `pageInfo { hasNextPage endCursor }` for any query that may need pagination.
### UpperBound Pagination (v59+)
When you need more than 200 records per page or more than 4,000 total records, switch to upperBound mode:
- **`first` must be 2002000** when `upperBound` is set. Values below 200 are invalid.
- **`upperBound`** declares the estimated total record count and enables extended pagination.
```graphql
# Standard pagination
Account(first: 50, after: $cursor) {
edges { node { Id Name @optional { value } } }
pageInfo { hasNextPage endCursor }
}
# UpperBound pagination for large result sets
Account(first: 2000, after: $cursor, upperBound: 10000) {
edges { node { Id Name @optional { value } } }
pageInfo { hasNextPage endCursor }
}
```
## Ordering
Use the `orderBy:` argument with generated `<Object>_OrderBy` input types. Run the schema search script to verify sortable fields.
### Rules
- Use `orderBy:` with the generated OrderBy type: `orderBy: { FieldName: { order: ASC } }`
- **Multi-column sorting** is supported by combining fields in the orderBy input
- **Unsupported field types** for ordering: multi-select picklist, rich text, long text area, encrypted fields. Do not order by these.
- **Locale sensitivity** — Sort order depends on user locale. For deterministic ordering, add `Id` as a tie-breaker field.
- **Compound fields** — Use constituent fields for ordering (e.g., `BillingCity`), not the compound wrapper.
```graphql
Account(
first: 10,
orderBy: { Name: { order: ASC }, CreatedDate: { order: DESC } }
) { ... }
```
## Filtering
### Boolean Filter Composition
Filter types include `AND`, `OR`, and `NOT` fields for combining conditions. Multiple filter fields at the same level combine with implicit AND.
```graphql
# Implicit AND — both conditions must match
Account(where: { Industry: { eq: "Technology" }, AnnualRevenue: { gt: 1000000 } })
# Explicit OR
Account(where: { OR: [
{ Industry: { eq: "Technology" } },
{ Industry: { eq: "Finance" } }
] })
# NOT
Account(where: { NOT: { Industry: { eq: "Technology" } } })
```
### Date and DateTime Filtering
Date and DateTime fields use special input objects (`DateInput`/`DateTimeInput`) that support both literal values and SOQL-style relative date semantics.
```graphql
# Literal date
Opportunity(where: { CloseDate: { eq: { value: "2024-12-31" } } })
# Relative date literal
Opportunity(where: { CloseDate: { gte: { literal: TODAY } } })
```
Verify exact literal enum values (e.g., `TODAY`, `THIS_MONTH`) via the schema search script.
### String Equality Is Case-Insensitive
String comparisons with `eq` are case-insensitive in Salesforce GraphQL. Do not rely on case sensitivity for string equality filters.
### Relationship Filters
Filter through parent relationships using nested filter objects (not dot notation):
```graphql
# Correct — nested filter objects
Contact(where: { Account: { Name: { like: "Acme%" } } })
# Wrong — dot notation is not supported
Contact(where: { "Account.Name": { like: "Acme%" } })
```
### Polymorphic Relationship Filters
Polymorphic relationships use union-aware filter input types named `<Object>_<RelationshipName>_Filters`. Filter by specific concrete types within the union:
```graphql
# Filter by polymorphic Owner (which is a union of User, Group, etc.)
Account(where: { Owner: { User: { Username: { like: "admin%" } } } })
```
Verify exact filter input type names and available concrete types via the schema search script.
### ID Filtering
Salesforce accepts both 15-character and 18-character record IDs for `Id` filtering. Do not reject or "correct" either form.
## Semi-Join and Anti-Join Templates
Semi-joins and anti-joins filter a parent entity using conditions on child entities. They use `inq` (semi-join) and `ninq` (anti-join) operators on the parent entity's `Id`.
The operator accepts:
- The child entity camelCase name with conditions
- The `ApiName` field containing the parent entity `Id` (`fieldName` from `childRelationships`)
If the only condition is child entity existence, use `Id: { ne: null }`.
### Restrictions
Semi-join and anti-join queries have SOQL-derived restrictions:
- **Limited count** — There are limits on the number of `inq`/`ninq` operators per query
- **No `ne` with joins** — Cannot use `ne` operator in combination with join operators
- **No `or` in subquery** — The join subquery conditions cannot use `OR`
- **No `orderBy` in subquery** — Join subqueries do not support ordering
- **Nesting restrictions** — Semi/anti-joins cannot be nested within each other
### Semi-Join Example
Filter `ParentEntity` to include only those with at least one matching `ChildEntity`:
```graphql
query testSemiJoin {
uiapi {
query {
ParentEntity(
where: {
Id: {
inq: {
ChildEntity: {
Name: { like: "test%" }
Type: { eq: "some value" }
}
ApiName: "parentIdFieldInChild"
}
}
}
) {
edges {
node {
Id
Name @optional {
value
}
}
}
}
}
}
}
```
### Anti-Join Example
Same as the semi-join example, but replace `inq` with `ninq` to filter `ParentEntity` with **no** matching `ChildEntity`.
## Current User Exception
To retrieve **current user**, **connected user**, or **authenticated user** information, use `uiapi.currentUser` instead of the standard query pattern. This field takes **no arguments** and returns a `User` type.
## Conditional Field Selection
For dynamic fieldsets with **known** fields, use `@include(if: $condition)` and `@skip(if: $condition)` directives in `.graphql` files. See GraphQL spec for details.
## Read Query Template
```graphql
query QueryName($after: String) {
uiapi {
query {
EntityName(
first: 10 # Always specify — default is 10 if omitted
after: $after # For pagination
where: { ... } # Filter conditions
orderBy: { ... } # Sort order
) {
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) — max 1 level deep, no grandchildren
RelationshipName @optional (
first: 10 # Always specify
) {
edges {
node {
# fields
}
}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
fragment TypeAInfo on TypeA {
Id
SpecificFieldA @optional { value }
}
fragment TypeBInfo on TypeB {
Id
SpecificFieldB @optional { value }
}
```
## Field Value Wrappers
Schema fields use typed wrappers. Access the underlying value via `.value`:
| Wrapper Type | Underlying Type | Access Pattern |
| ----------------- | --------------- | --------------------- |
| `StringValue` | `String` | `field { value }` |
| `IntValue` | `Int` | `field { value }` |
| `CurrencyValue` | `Currency` | `field { value }` |
| `DateTimeValue` | `DateTime` | `field { value }` |
| `PicklistValue` | `Picklist` | `field { value }` |
| `BooleanValue` | `Boolean` | `field { value }` |
| `DoubleValue` | `Double` | `field { value }` |
| `PercentValue` | `Percent` | `field { value }` |
| `IDValue` | `ID` | `field { value }` |
| `EmailValue` | `Email` | `field { value }` |
| `PhoneNumberValue`| `PhoneNumber` | `field { value }` |
| `UrlValue` | `Url` | `field { value }` |
| `DateValue` | `Date` | `field { value }` |
| `LongValue` | `Long` | `field { value }` |
| `TextAreaValue` | `TextArea` | `field { value }` |
All wrappers also expose `displayValue: String` for formatted display. `displayValue` is server-rendered using SOQL `toLabel()` or `format()` depending on field type — use it for UI display instead of formatting values client-side.

View File

@ -1,53 +0,0 @@
# Schema Introspection
## Schema Access Policy
The `schema.graphql` file is **265,000+ lines**. Loading it into context or opening it in an editor will overwhelm the context window or crash tools.
Do not use cat, less, more, head, tail, editors (VS Code, vim, nano), or programmatic parsers (node, python, awk, sed, jq) on `schema.graphql`. Use the schema search script or targeted grep calls only.
## Schema Lookup
Run the search script from the **SFDX project root** to get all relevant schema info in one step:
```bash
bash scripts/graphql-search.sh <EntityName>
# Multiple entities:
bash scripts/graphql-search.sh Account Contact Opportunity
```
**Maximum 2 script runs.** If the entity still can't be found after checking naming variations, ask the user.
## Entity Identification
Map user intent to PascalCase entity names:
1. Convert natural language to PascalCase (e.g., "accounts" → `Account`, "case comments" → `CaseComment`, "custom objects" → `CustomObject__c`)
2. Run the schema search script to validate the entity exists
3. If a candidate does not match, try:
- `__c` suffix for custom objects, `__e` for platform events
- **`_Record` suffix** — Objects added to UI API in API v60+ may use `<EntityName>_Record` as their type name (e.g., `FeedItem_Record` instead of `FeedItem`)
4. If an entity cannot be resolved, **ask the user** for the correct name — do not guess
## Iterative Introspection
Use a maximum of **3 introspection cycles** to resolve all entities and their dependencies:
1. **Introspect** — Run the schema search script for each unresolved entity
2. **Fields** — Extract requested field names and types from the type definition output
3. **References** — Identify reference fields. If a reference resolves to multiple types, mark it as **polymorphic** (use inline fragments in the generated query). Add newly discovered entity types to the working list.
4. **Child relationships** — Identify Connection types (e.g., `Contacts: ContactConnection`). Add child entity types to the working list.
5. **Next cycle** — If unresolved entities remain and the cycle limit hasn't been reached, repeat from step 1
### Hard Stop Rules
- If no introspection data is returned for an entity, **stop** — the entity may not be deployed
- If unknown entities remain after 3 cycles, **stop** — ask the user for clarification
- Do not proceed with query generation until all entities and requested fields are confirmed in the schema
## Deployment Prerequisites
The schema reflects the **current org state**. Custom objects and fields appear only after metadata is deployed.
- **Before** running `npm run graphql:schema`: Deploy all metadata and assign permission sets. Invoke the `deploying-ui-bundle` skill for the full sequence.
- **After** any metadata deployment: Re-run `npm run graphql:schema` and `npm run graphql:codegen` so types and queries stay in sync.

View File

@ -1,221 +0,0 @@
# UI Bundle Integration
## When to Use
This guide applies when integrating GraphQL queries into a React UI bundle using `createDataSDK` + codegen from `@salesforce/sdk-data`.
## Core Types & Function Signatures
### createDataSDK and graphql
```typescript
import { createDataSDK } from "@salesforce/sdk-data";
const sdk = await createDataSDK();
const response = await sdk.graphql?.<ResponseType, VariablesType>(query, variables);
```
`createDataSDK()` returns a `DataSDK` instance. The `graphql` method uses optional chaining (`?.`) because not all surfaces support GraphQL.
### gql Template Tag
```typescript
import { gql } from "@salesforce/sdk-data";
const MY_QUERY = gql`
query MyQuery {
uiapi { ... }
}
`;
```
The `gql` tag enables ESLint validation against the schema. Plain template strings bypass validation.
### NodeOfConnection
```typescript
import { type NodeOfConnection } from "@salesforce/sdk-data";
type AccountNode = NodeOfConnection<GetHighRevenueAccountsQuery["uiapi"]["query"]["Account"]>;
```
Use `NodeOfConnection` to extract the node type from a Connection type for cleaner typing.
## Query Patterns
Choose the pattern based on query complexity:
- **Pattern 1 — External `.graphql` file**: Recommended for complex queries with variables, fragments, or shared across files. Full codegen support, syntax highlighting, shareable. Requires codegen step after changes. Does NOT support dynamic queries.
- **Pattern 2 — Inline `gql` tag**: Recommended for simple queries. Supports dynamic queries (field set varies at runtime). **MUST use `gql` tag** — plain template strings bypass `@graphql-eslint` validation.
## Pattern 1: External .graphql File
Create a `.graphql` file, run `npm run graphql:codegen`, import with `?raw` suffix, and use generated types.
**Required imports:**
```typescript
import { createDataSDK, type NodeOfConnection } from "@salesforce/sdk-data";
import MY_QUERY from "./query/myQuery.graphql?raw"; // ?raw suffix required
import type { GetMyDataQuery, GetMyDataQueryVariables } from "../graphql-operations-types";
```
**Example usage:**
```typescript
const sdk = await createDataSDK();
const response = await sdk.graphql?.<GetMyDataQuery, GetMyDataQueryVariables>(
MY_QUERY,
variables
);
if (response?.errors?.length) {
throw new Error(response.errors.map((e) => e.message).join("; "));
}
const nodes = response?.data?.uiapi?.query?.EntityName?.edges?.map((e) => e.node) ?? [];
```
## Pattern 2: Inline gql Tag
**Required imports:**
```typescript
import { createDataSDK, gql } from "@salesforce/sdk-data";
import { type CurrentUserQuery } from "../graphql-operations-types";
const MY_QUERY = gql`
query CurrentUser {
uiapi { ... }
}
`;
```
> **MUST use `gql` tag** — plain template strings bypass the `@graphql-eslint` processor entirely, meaning no lint validation against the schema.
## Error Handling Strategies
**Strategy A — Strict (default):** Treat any errors as failure.
```typescript
if (response?.errors?.length) {
throw new Error(response.errors.map((e) => e.message).join("; "));
}
const result = response?.data;
```
**Strategy B — Tolerant:** Log errors but use available data.
```typescript
if (response?.errors?.length) {
console.warn("GraphQL partial errors:", response.errors);
}
const result = response?.data;
```
**Strategy C — Discriminated:** Fail only when no data is returned. Useful for mutations where some return fields may be inaccessible.
```typescript
if (!response?.data && response?.errors?.length) {
throw new Error(response.errors.map((e) => e.message).join("; "));
}
const result = response?.data;
```
Responses follow `uiapi.query.ObjectName.edges[].node`; fields use `{ value }`.
## Conditional Field Selection
For dynamic fieldsets with **known** fields, use `@include(if: $condition)` and `@skip(if: $condition)` directives in `.graphql` files. See GraphQL spec for details.
## ESLint Validation
After writing the query into a source file, validate it against the schema:
```bash
# Run from UI bundle dir (force-app/main/default/uiBundles/<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 and project dependencies must be installed (`npm install`).
## Codegen
Generate TypeScript types from `.graphql` files and inline `gql` queries:
```bash
# Run from UI bundle dir (force-app/main/default/uiBundles/<app-name>/)
npm run graphql:codegen
```
Output: `src/api/graphql-operations-types.ts`
Naming conventions:
- `<OperationName>Query` / `<OperationName>Mutation` — response types
- `<OperationName>QueryVariables` / `<OperationName>MutationVariables` — variable types
## Anti-Patterns
### Direct API Calls
```typescript
// NOT RECOMMENDED: Direct axios/fetch calls for GraphQL
// PREFERRED: Use the Data SDK
const sdk = await createDataSDK();
const response = await sdk.graphql?.<ResponseType>(query, variables);
```
### Missing Type Definitions
```typescript
// NOT RECOMMENDED: Untyped GraphQL calls
// PREFERRED: Provide response type
const response = await sdk.graphql?.<GetMyDataQuery>(query);
```
### Plain String Queries (Without gql Tag)
```typescript
// NOT RECOMMENDED: Plain strings bypass ESLint validation
const query = `query { ... }`;
// PREFERRED: Use gql tag for inline queries
const QUERY = gql`query { ... }`;
```
## Quality Checklists
### For Pattern 1 (.graphql files):
1. [ ] All field names verified via schema search script
2. [ ] Create `.graphql` file for the query/mutation
3. [ ] Run `npm run graphql:codegen` to generate types
4. [ ] Import query with `?raw` suffix
5. [ ] Import generated types from `graphql-operations-types.ts`
6. [ ] Use `sdk.graphql?.<ResponseType>()` with proper generic
7. [ ] Handle `response.errors` and destructure `response.data`
8. [ ] Use `NodeOfConnection` for cleaner node types when needed
9. [ ] Run `npx eslint <file>` from UI bundle dir — fix all GraphQL errors
### For Pattern 2 (inline with gql):
1. [ ] All field names verified via schema search script
2. [ ] Define query using `gql` template tag (NOT a plain string)
3. [ ] Ensure query name matches generated types in `graphql-operations-types.ts`
4. [ ] Import generated types for the query
5. [ ] Use `sdk.graphql?.<ResponseType>()` with proper generic
6. [ ] Handle `response.errors` and destructure `response.data`
7. [ ] Run `npx eslint <file>` from UI bundle dir — fix all GraphQL errors
### General:
- [ ] Lint validation passes (`npx eslint <file>` reports no GraphQL errors)
- [ ] Query field names match the schema exactly (case-sensitive)
- [ ] Response type generic is provided to `sdk.graphql?.<T>()`
- [ ] Optional chaining is used for nested response data