using-ui-bundle-salesforce-data
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSalesforce Data Access
Salesforce数据访问
Data SDK Requirement
Data SDK要求
All Salesforce data access MUST use the Data SDK (). The SDK handles authentication, CSRF, and base URL resolution.@salesforce/sdk-data
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?.<ResponseTypeQuery>(query, variables);
// REST for Connect REST, Apex REST, UI API (when GraphQL insufficient)
const res = await sdk.fetch?.("/services/apexrest/my-resource");Always use optional chaining (, ) — these methods may be undefined in some surfaces.
sdk.graphql?.()sdk.fetch?.()所有Salesforce数据访问必须使用Data SDK()。该SDK负责处理身份验证、CSRF和基础URL解析。@salesforce/sdk-data
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?.<ResponseTypeQuery>(query, variables);
// REST for Connect REST, Apex REST, UI API (when GraphQL insufficient)
const res = await sdk.fetch?.("/services/apexrest/my-resource");始终使用可选链(、)——这些方法在某些场景下可能未定义。
sdk.graphql?.()sdk.fetch?.()Preconditions — verify before starting
前置条件——开始前验证
| # | Requirement | How to verify | If missing |
|---|---|---|---|
| 1 | | Check | Cannot proceed — tell user to install it |
| 2 | | Check if file exists | Run |
| 3 | Custom objects/fields deployed | Run | 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 / for data and mark query locations with 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.
null// TODO: add query after schema verification| 序号 | 要求 | 验证方式 | 缺失时处理 |
|---|---|---|---|
| 1 | 已安装 | 检查UI bundle目录下的 | 无法继续——告知用户安装该依赖 |
| 2 | 项目根目录存在 | 检查文件是否存在 | 在UI bundle目录下运行 |
| 3 | 已部署自定义对象/字段 | 运行 | 要求用户部署元数据并分配权限集 |
如果前置条件未满足,你可以搭建组件、路由、布局和UI逻辑,但需使用空数组/填充数据,并在查询位置标记,同时将解决要求并编写GraphQL的步骤纳入计划。在架构流程完成前,请勿编写GraphQL查询字符串。
null// TODO: add query after schema verificationSupported APIs
支持的API
Only the following APIs are permitted. Any endpoint not listed here must not be used.
| API | Method | Endpoints / Use Case |
|---|---|---|
| GraphQL | | All record queries and mutations via |
| UI API REST | | |
| Apex REST | | |
| Connect REST | | |
| Einstein LLM | | |
Not supported:
- Enterprise REST query endpoint (with SOQL) — blocked at the proxy level. Use GraphQL for record reads; use Apex REST if server-side SOQL aggregates are required.
/services/data/v*/query - Aura-enabled Apex () — an LWC/Aura pattern with no invocation path from React UI bundles.
@AuraEnabled - Chatter API () — use
/chatter/users/mein a GraphQL query instead.uiapi { currentUser { ... } } - Any other Salesforce REST endpoint not listed in the supported table above.
仅允许使用以下API。未在此列出的任何端点均不得使用。
| API | 方法 | 端点/使用场景 |
|---|---|---|
| GraphQL | | 通过 |
| UI API REST | | |
| Apex REST | | |
| Connect REST | | |
| Einstein LLM | | |
不支持的API:
- Enterprise REST查询端点(搭配SOQL)——已被代理层拦截。记录读取使用GraphQL;若需要服务器端SOQL聚合操作,使用Apex REST。
/services/data/v*/query - Aura-enabled Apex()——这是LWC/Aura模式,无法从React UI bundles中调用。
@AuraEnabled - Chatter API()——改用GraphQL查询中的
/chatter/users/me。uiapi { currentUser { ... } } - 上述支持列表中未提及的任何其他Salesforce REST端点。
Decision: GraphQL vs REST
决策:GraphQL vs REST
| Need | Method | Example |
|---|---|---|
| Query/mutate records | | Account, Contact, custom objects |
| Current user info | | |
| UI API record metadata | | |
| Connect REST | | |
| Apex REST | | |
| Einstein LLM | | |
GraphQL is preferred for record operations. Use REST only when GraphQL doesn't cover the use case.
| 需求 | 方法 | 示例 |
|---|---|---|
| 查询/变更记录 | | Account、Contact、自定义对象 |
| 当前用户信息 | | |
| UI API记录元数据 | | |
| Connect REST | | |
| Apex REST | | |
| Einstein LLM | | |
记录操作优先使用GraphQL。仅当GraphQL无法覆盖需求时,才使用REST。
GraphQL Non-Negotiable Rules
GraphQL不可协商规则
These rules exist because Salesforce GraphQL has platform-specific behaviors that differ from standard GraphQL. Violations cause silent runtime failures.
-
HTTP 200 does not mean success — Salesforce returns HTTP 200 even when operations fail. Always parse thearray in the response body.
errors -
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). Objects added to UI API in v60+ may use a__esuffix (e.g.,_Recordinstead ofFeedItem_Record).FeedItem -
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
@optionaldirective (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 (@optional) and nullish coalescing (?.).?? -
Correct mutation syntax — Mutations wrap under, not bare
uiapi(input: { allOrNone: true/false }). Always setuiapi { ... }explicitly. Output fields cannot include child relationships or navigated reference fields.allOrNone -
Explicit pagination — Always includein every query. If omitted, the server silently defaults to 10 records. Include
first:for any query that may need pagination. Forward-only (pageInfo { hasNextPage endCursor }/first) —after/lastare unsupported.before -
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.
-
Only requested fields — Only generate fields the user explicitly asked for. Do NOT add extra fields.
-
Compound fields — When filtering or ordering, use constituent fields (e.g.,,
BillingCity), not the compound wrapper (BillingCountry). The compound wrapper is only for selection.BillingAddress
这些规则的存在是因为Salesforce GraphQL具有与标准GraphQL不同的平台特定行为。违反规则会导致静默运行时失败。
-
HTTP 200不代表成功——即使操作失败,Salesforce仍会返回HTTP 200。必须始终解析响应体中的数组。
errors -
架构是唯一数据源——在查询中使用的每个实体名称、字段名称和类型,必须先通过架构搜索脚本确认。切勿猜测——Salesforce字段名称区分大小写,关系可能是多态的,自定义对象使用后缀(、
__c)。在v60+版本中加入UI API的对象可能使用__e后缀(例如_Record而非FeedItem_Record)。FeedItem -
所有记录字段添加(读取查询)——如果用户缺少对任意一个字段的访问权限,Salesforce字段级安全性(FLS)会导致查询完全失败。
@optional指令(v65+)告知服务器省略不可访问的字段,而非让查询失败。将其应用于每个标量字段、父关系和子关系。消费代码必须使用可选链(@optional)和空值合并运算符(?.)。?? -
正确的变更语法——变更需包裹在中,而非直接使用
uiapi(input: { allOrNone: true/false })。必须显式设置uiapi { ... }。输出字段不能包含子关系或导航引用字段。allOrNone -
显式分页——每个查询必须包含。如果省略,服务器会静默默认返回10条记录。对于可能需要分页的任何查询,需包含
first:。仅支持向前分页(pageInfo { hasNextPage endCursor }/first)——不支持after/last。before -
SOQL衍生的执行限制——每个请求最多10个子查询,最多5级子到父的遍历,最多1级父到子(不支持孙辈),每个子查询最多2000条记录。如果查询会超出这些限制,拆分为多个请求。
-
仅请求所需字段——仅生成用户明确要求的字段。切勿添加额外字段。
-
复合字段——过滤或排序时,使用组成字段(例如、
BillingCity),而非复合包装器(BillingCountry)。复合包装器仅用于字段选择。BillingAddress
GraphQL Workflow
GraphQL工作流程
| Step | Action | Key output |
|---|---|---|
| 1 | Acquire schema | |
| 2 | Look up entities | Field names, types, relationships confirmed |
| 3 | Generate query | |
| 4 | Generate types | |
| 5 | Validate | Lint + codegen pass |
| 步骤 | 操作 | 关键输出 |
|---|---|---|
| 1 | 获取架构 | 存在 |
| 2 | 查询实体 | 确认字段名称、类型、关系 |
| 3 | 生成查询 | |
| 4 | 生成类型 | |
| 5 | 验证 | 通过Lint和codegen检查 |
Step 1: Acquire Schema
步骤1:获取架构
The file (265K+ lines) is the source of truth. Never open or parse it directly — no cat, less, head, tail, editors, or programmatic parsers.
schema.graphqlVerify preconditions 1–3 (see Preconditions), then proceed to Step 2.
schema.graphql验证前置条件1-3(参见前置条件),然后进入步骤2。
Step 2: Look Up Entity Schema
步骤2:查询实体架构
Map user intent to PascalCase names ("accounts" → ), then run the search script from the folder (project root):
Accountsfdx-projectbash
bash scripts/graphql-search.sh Account将用户需求映射为大驼峰名称("accounts" → ),然后从文件夹(项目根目录)运行搜索脚本:
Accountsfdx-projectbash
bash scripts/graphql-search.sh AccountMultiple entities:
多个实体:
bash scripts/graphql-search.sh Account Contact Opportunity
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 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)
**Maximum 2 script runs.** If the entity still can't be found, ask the user — the object may not be deployed.bash scripts/graphql-search.sh Account Contact Opportunity
脚本为每个实体输出七个部分:
1. **类型定义**——所有可查询字段和关系
2. **过滤选项**——`where:`条件可用的字段
3. **排序选项**——`orderBy:`可用的字段
4. **创建变更包装器**——`<Entity>CreateInput`
5. **创建变更字段**——`<Entity>CreateRepresentation`(创建变更接受的字段)
6. **更新变更包装器**——`<Entity>UpdateInput`
7. **更新变更字段**——`<Entity>UpdateRepresentation`(更新变更接受的字段)
**最多运行2次脚本**。如果仍无法找到实体,请询问用户——该对象可能未部署。Entity Identification
实体识别
If a candidate does not match:
- Try suffix for custom objects,
__cfor platform events__e - Try suffix — objects added in v60+ may use
_Record<EntityName>_Record - If still unresolved, ask the user — do not guess
如果候选实体不匹配:
- 尝试为自定义对象添加后缀,为平台事件添加
__c后缀__e - 尝试后缀——v60+版本中添加的对象可能使用
_Record<EntityName>_Record - 如果仍未解决,询问用户——切勿猜测
Iterative Introspection (max 3 cycles)
迭代自省(最多3轮)
- Introspect — Run the script for each unresolved entity
- Fields — Extract requested field names and types from the type definition
- References — Identify reference fields. If polymorphic (multiple types), use inline fragments. Add newly discovered entity types to the working list.
- Child relationships — Identify Connection types. Add child entity types to the working list.
- 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.
- 自省——为每个未解决的实体运行脚本
- 字段——从类型定义中提取请求的字段名称和类型
- 引用——识别引用字段。如果是多态(多种类型),使用内联片段。将新发现的实体类型添加到工作列表中。
- 子关系——识别Connection类型。将子实体类型添加到工作列表中。
- 重复如果仍有未解决的实体(最多3轮)
强制停止:如果实体无数据返回,停止操作——它可能未部署。如果3轮后仍有未知实体,询问用户。切勿生成包含未确认实体或字段的查询。
Step 3: Generate Query
步骤3:生成查询
Every field name must be verified from the script output in Step 2.
每个字段名称必须通过步骤2中的脚本输出验证。
Read Query Template
读取查询模板
graphql
query QueryName($after: String) {
uiapi {
query {
EntityName(
first: 10
after: $after
where: { ... }
orderBy: { ... }
) {
edges {
node {
Id
FieldName @optional { value }
# Parent relationship (non-polymorphic)
Owner @optional { Name { value } }
# 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";graphql
query QueryName($after: String) {
uiapi {
query {
EntityName(
first: 10
after: $after
where: { ... }
orderBy: { ... }
) {
edges {
node {
Id
FieldName @optional { value }
# Parent relationship (non-polymorphic)
Owner @optional { Name { value } }
# 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 }
}消费代码必须防范缺失字段:
typescript
const name = node.Name?.value ?? "";
const relatedName = node.Owner?.Name?.value ?? "N/A";Filtering
过滤
graphql
undefinedgraphql
undefinedImplicit AND
隐式AND
Account(where: { Industry: { eq: "Technology" }, AnnualRevenue: { gt: 1000000 } })
Account(where: { Industry: { eq: "Technology" }, AnnualRevenue: { gt: 1000000 } })
Explicit OR
显式OR
Account(where: { OR: [{ Industry: { eq: "Technology" } }, { Industry: { eq: "Finance" } }] })
Account(where: { OR: [{ Industry: { eq: "Technology" } }, { Industry: { eq: "Finance" } }] })
NOT
NOT
Account(where: { NOT: { Industry: { eq: "Technology" } } })
Account(where: { NOT: { Industry: { eq: "Technology" } } })
Date literal
日期字面量
Opportunity(where: { CloseDate: { eq: { value: "2024-12-31" } } })
Opportunity(where: { CloseDate: { eq: { value: "2024-12-31" } } })
Relative date
相对日期
Opportunity(where: { CloseDate: { gte: { literal: TODAY } } })
Opportunity(where: { CloseDate: { gte: { literal: TODAY } } })
Relationship filter (nested objects, NOT dot notation)
关系过滤(嵌套对象,不使用点符号)
Contact(where: { Account: { Name: { like: "Acme%" } } })
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.Account(where: { Owner: { User: { Username: { like: "admin%" } } } })
字符串相等(`eq`)不区分大小写。接受15位和18位记录ID。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 as tie-breaker for deterministic ordering.
Idgraphql
Account(
first: 10,
orderBy: { Name: { order: ASC }, CreatedDate: { order: DESC } }
) { ... }不支持排序的字段:多选选择列表、富文本、长文本区域、加密字段。添加作为排序的决胜字段以确保确定性。
IdUpperBound Pagination (v59+)
上限分页(v59+)
For >200 records per page or >4,000 total records, use . must be 200–2000 when set.
upperBoundfirstgraphql
Account(first: 2000, after: $cursor, upperBound: 10000) {
edges { node { Id Name @optional { value } } }
pageInfo { hasNextPage endCursor }
}当每页记录数>200或总记录数>4000时,使用。设置时必须为200–2000。
upperBoundfirstgraphql
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 (semi-join) or (anti-join) on the parent's . If the only condition is child existence, use .
inqninqIdId: { 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 } } }
}
}
}
}Replace with for anti-join. Restrictions: no in subquery, no in subquery, no nesting joins within each other.
inqninqORorderBy使用父实体的上的(半连接)或(反连接),根据子实体的条件过滤父实体。如果唯一条件是子实体存在,使用。
IdinqninqId: { 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 } } }
}
}
}
}将替换为以实现反连接。限制:子查询中不能使用,子查询中不能使用,不能嵌套连接。
inqninqORorderByCurrent User
当前用户
Use (no arguments) instead of the standard query pattern:
uiapi.currentUsergraphql
query CurrentUser {
uiapi { currentUser { Id Name { value } } }
}使用(无参数)代替标准查询模式:
uiapi.currentUsergraphql
query CurrentUser {
uiapi { currentUser { Id Name { value } } }
}Field Value Wrappers
字段值包装器
Schema fields use typed wrappers — access via :
.value| Wrapper Type | Underlying | Wrapper Type | Underlying |
|---|---|---|---|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| |
All wrappers also expose (server-rendered via /) — use for UI display instead of formatting client-side.
displayValue: StringtoLabel()format()架构字段使用类型化包装器——通过访问:
.value| 包装器类型 | 底层类型 | 包装器类型 | 底层类型 |
|---|---|---|---|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| |
所有包装器还暴露(通过服务器端/渲染)——用于UI展示,而非客户端格式化。
displayValue: StringtoLabel()format()Mutation Template
变更模板
Mutations are GA in API v66+. Three operations: Create, Update, Delete.
graphql
undefined变更在API v66+中正式可用。三种操作:创建、更新、删除。
graphql
undefinedCreate
Create
mutation CreateAccount($input: AccountCreateInput!) {
uiapi(input: { allOrNone: true }) {
AccountCreate(input: $input) {
Record { Id Name { value } }
}
}
}
mutation CreateAccount($input: AccountCreateInput!) {
uiapi(input: { allOrNone: true }) {
AccountCreate(input: $input) {
Record { Id Name { value } }
}
}
}
Update — must include Id
Update — must include Id
mutation UpdateAccount {
uiapi(input: { allOrNone: true }) {
AccountUpdate(input: { Id: "001xx000003GYkZAAW", Account: { Name: "New Name" } }) {
Record { Id Name { value } }
}
}
}
**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 UpdateAccount {
uiapi(input: { allOrNone: true }) {
AccountUpdate(input: { Id: "001xx000003GYkZAAW", Account: { Name: "New Name" } }) {
Record { Id Name { value } }
}
}
}
**输入约束:**
- **创建**:必填字段(除非`defaultedOnCreate`),仅使用`createable`字段,不包含子关系。通过`ApiName`设置引用字段(例如`AccountId`)。
- **更新**:必须包含`Id`,仅使用`updateable`字段,不包含子关系。
- **删除**:仅需`Id`。
- **`IdOrRef`类型**:更新和删除输入中的`Id`字段使用`IdOrRef`类型,可接受文字记录ID(例如`"001xx..."`)或变更链式引用(`"@{Alias}"`)。创建输入中的引用字段(例如`AccountId`)也接受`@{Alias}`用于链式操作。
- **原始值**:不要包含逗号、货币符号或区域格式(例如`80000`而非`"$80,000"`)。
**输出约束:**
- 创建/更新:排除子关系,排除导航引用字段(仅允许`ApiName`成员)。输出字段始终命名为`Record`。
- 删除:仅输出`Id`。
**`allOrNone`语义:**
- `true`(默认)——所有操作要么全部成功,要么全部回滚。
- `false`——独立操作可单独成功,但依赖操作(使用`@{alias}`)仍会一起回滚。Mutation Chaining
变更链式操作
Chain related mutations using references to from earlier mutations. Required for parent-child creation (nested child creates are not supported).
@{alias}Idgraphql
mutation CreateAccountAndContact {
uiapi(input: { allOrNone: true }) {
AccountCreate(input: { Account: { Name: "Acme" } }) {
Record { Id }
}
ContactCreate(input: { Contact: { LastName: "Smith", AccountId: "@{AccountCreate}" } }) {
Record { Id }
}
}
}Rules: must come before in the query. is always the from mutation . Only or can be chained from (not ).
AB@{A}IdACreateDeleteUpdate使用引用早期变更中的,实现相关变更的链式操作。父子创建需要此操作(不支持嵌套子创建)。
@{alias}Idgraphql
mutation CreateAccountAndContact {
uiapi(input: { allOrNone: true }) {
AccountCreate(input: { Account: { Name: "Acme" } }) {
Record { Id }
}
ContactCreate(input: { Contact: { LastName: "Smith", AccountId: "@{AccountCreate}" } }) {
Record { Id }
}
}
}规则:必须在查询中位于之前。始终是变更的。仅能从创建或删除操作进行链式操作(不支持更新)。
AB@{A}AIdDelete Mutation
删除变更
Delete uses generic (not entity-specific). Output is only — no field.
RecordDeleteInputIdRecordgraphql
mutation DeleteAccount($id: ID!) {
uiapi(input: { allOrNone: true }) {
AccountDelete(input: { Id: $id }) {
Id
}
}
}删除使用通用的(非实体特定)。输出仅为——无字段。
RecordDeleteInputIdRecordgraphql
mutation DeleteAccount($id: ID!) {
uiapi(input: { allOrNone: true }) {
AccountDelete(input: { Id: $id }) {
Id
}
}
}Object Metadata & Picklist Values
对象元数据与选择列表值
Use to fetch field metadata or picklist values. Pass either or — never both.
uiapi { objectInfos(...) }apiNamesobjectInfoInputstypescript
// Object metadata
const GET_OBJECT_INFO = gql`
query GetObjectInfo($apiNames: [String!]!) {
uiapi {
objectInfos(apiNames: $apiNames) {
ApiName
label
labelPlural
fields { ApiName label dataType updateable createable }
}
}
}
`;
// Picklist values (use objectInfoInputs + inline fragment)
const GET_PICKLIST_VALUES = gql`
query GetPicklistValues($objectInfoInputs: [ObjectInfoInput!]!) {
uiapi {
objectInfos(objectInfoInputs: $objectInfoInputs) {
ApiName
fields {
ApiName
... on PicklistField {
picklistValuesByRecordTypeIDs {
recordTypeID
picklistValues { label value }
}
}
}
}
}
}
`;使用获取字段元数据或选择列表值。必须传递**或其中之一**——切勿同时传递。
uiapi { objectInfos(...) }apiNamesobjectInfoInputstypescript
// Object metadata
const GET_OBJECT_INFO = gql`
query GetObjectInfo($apiNames: [String!]!) {
uiapi {
objectInfos(apiNames: $apiNames) {
ApiName
label
labelPlural
fields { ApiName label dataType updateable createable }
}
}
}
`;
// Picklist values (use objectInfoInputs + inline fragment)
const GET_PICKLIST_VALUES = gql`
query GetPicklistValues($objectInfoInputs: [ObjectInfoInput!]!) {
uiapi {
objectInfos(objectInfoInputs: $objectInfoInputs) {
ApiName
fields {
ApiName
... on PicklistField {
picklistValuesByRecordTypeIDs {
recordTypeID
picklistValues { label value }
}
}
}
}
}
}
`;Step 4: Generate Types (codegen)
步骤4:生成类型(codegen)
After writing the query (whether in a file or inline with ), generate TypeScript types:
.graphqlgqlbash
undefined编写查询后(无论是在文件中还是通过内联),生成TypeScript类型:
.graphqlgqlbash
undefinedRun from UI bundle dir
从UI bundle目录运行
npm run graphql:codegen
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 to extract the node type from a Connection for cleaner typing:
NodeOfConnection<T>typescript
import { type NodeOfConnection } from "@salesforce/sdk-data";
type AccountNode = NodeOfConnection<GetAccountsQuery["uiapi"]["query"]["Account"]>;npm run graphql:codegen
输出:`src/api/graphql-operations-types.ts`
生成的类型命名约定:
- `<OperationName>Query` / `<OperationName>Mutation`——响应类型
- `<OperationName>QueryVariables` / `<OperationName>MutationVariables`——变量类型
**调用`sdk.graphql`时,始终导入并使用生成的类型**:
```typescript
import type { GetAccountsQuery, GetAccountsQueryVariables } from "../graphql-operations-types";
const response = await sdk.graphql?.<GetAccountsQuery, GetAccountsQueryVariables>(GET_ACCOUNTS, variables);使用从Connection中提取节点类型,实现更简洁的类型定义:
NodeOfConnection<T>typescript
import { type NodeOfConnection } from "@salesforce/sdk-data";
type AccountNode = NodeOfConnection<GetAccountsQuery["uiapi"]["query"]["Account"]>;Step 5: Validate & Test
步骤5:验证与测试
- Lint: from UI bundle dir
npx eslint <file> - codegen: from UI bundle dir
npm run graphql:codegen
- Lint检查:从UI bundle目录运行
npx eslint <file> - codegen检查:从UI bundle目录运行
npm run graphql:codegen
Common Error patterns
常见错误模式
| Error Contains | Resolution |
|---|---|
| Field name wrong — re-run |
| Type name wrong — verify PascalCase entity name via script |
| Argument wrong — check Filter/OrderBy sections in script output |
| Fix syntax per error message |
| Correct argument type from schema |
| Entity deleted — ask for valid Id |
| Check object availability and API version |
| Remove field from mutation output |
| Use API version 64+ for update mutation |
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.
| 错误包含内容 | 解决方法 |
|---|---|
| 字段名称错误——重新运行 |
| 类型名称错误——通过脚本验证大驼峰实体名称 |
| 参数错误——检查脚本输出中的Filter/OrderBy部分 |
| 根据错误消息修复语法 |
| 根据架构修正参数类型 |
| 实体已删除——请求有效的Id |
| 检查对象可用性和API版本 |
| 从变更输出中移除该字段 |
| 更新变更的 |
部分成功场景如果变更同时返回数据和错误(部分成功):报告不可访问的字段,说明它们不能出现在变更输出中,请求用户同意后移除这些字段。等待用户同意后再修改。
UI Bundle Integration (React)
UI Bundle集成(React)
Two integration patterns:
两种集成模式:
Pattern 1 — External .graphql
file (complex queries)
.graphql模式1——外部.graphql
文件(复杂查询)
.graphqlOne operation per file. Each file contains exactly one or (plus its fragments). Do not combine multiple operations in a single file.
.graphqlquerymutationtypescript
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 files, run to generate types into .
.graphqlnpm run graphql:codegensrc/api/graphql-operations-types.ts每个文件包含一个操作。每个文件恰好包含一个或(及其片段)。切勿在单个文件中组合多个操作。
.graphqlquerymutationtypescript
import { createDataSDK, type NodeOfConnection } from "@salesforce/sdk-data";
import MY_QUERY from "./query/myQuery.graphql?raw"; // ?raw后缀是必需的
import type { GetMyDataQuery, GetMyDataQueryVariables } from "../graphql-operations-types";
const sdk = await createDataSDK();
const response = await sdk.graphql?.<GetMyDataQuery, GetMyDataQueryVariables>(MY_QUERY, variables);创建/修改文件后,运行将类型生成到中。
.graphqlnpm run graphql:codegensrc/api/graphql-operations-types.tsPattern 2 — Inline gql
tag (simple queries)
gql模式2——内联gql
标签(简单查询)
gqlMust use — plain template strings bypass ESLint schema validation.
gqltypescript
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 } } }
}
}
}
}
`;
const sdk = await createDataSDK();
const response = await sdk.graphql?.<GetAccountsQuery>(GET_ACCOUNTS);必须使用——普通模板字符串会绕过ESLint架构验证。
gqltypescript
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 } } }
}
}
}
}
`;
const sdk = await createDataSDK();
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) ?? [];typescript
// 严格模式(默认)——任何错误均视为失败
if (response?.errors?.length) {
throw new Error(response.errors.map(e => e.message).join("; "));
}
// 容错模式——记录错误,使用可用数据
if (response?.errors?.length) {
console.warn("GraphQL部分错误:", response.errors);
}
// 判别模式——仅当无数据返回时视为失败
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) ?? [];REST API Patterns
REST API模式
Use when GraphQL is insufficient. See the Supported APIs table for the full allowlist.
sdk.fetchtypescript
declare const __SF_API_VERSION__: string;
const API_VERSION = typeof __SF_API_VERSION__ !== "undefined" ? __SF_API_VERSION__ : "65.0";
// Connect — file upload config
const res = await sdk.fetch?.(`/services/data/v${API_VERSION}/connect/file/upload/config`);
// Apex REST (no version in path)
const res = await sdk.fetch?.("/services/apexrest/auth/login", {
method: "POST",
body: JSON.stringify({ email, password }),
headers: { "Content-Type": "application/json" },
});
// UI API — record with metadata (prefer GraphQL for simple reads)
const res = await sdk.fetch?.(`/services/data/v${API_VERSION}/ui-api/records/${recordId}`);
// Einstein LLM
const res = await sdk.fetch?.(`/services/data/v${API_VERSION}/einstein/llm/prompt/generations`, {
method: "POST",
body: JSON.stringify({ promptTextorId: prompt }),
});Current user: Do not use Chatter (). Use GraphQL instead:
/chatter/users/metypescript
const GET_CURRENT_USER = gql`
query CurrentUser {
uiapi { currentUser { Id Name { value } } }
}
`;
const response = await sdk.graphql?.(GET_CURRENT_USER);当GraphQL无法满足需求时,使用。完整允许列表参见支持的API表格。
sdk.fetchtypescript
declare const __SF_API_VERSION__: string;
const API_VERSION = typeof __SF_API_VERSION__ !== "undefined" ? __SF_API_VERSION__ : "65.0";
// Connect — file upload config
const res = await sdk.fetch?.(`/services/data/v${API_VERSION}/connect/file/upload/config`);
// Apex REST (no version in path)
const res = await sdk.fetch?.("/services/apexrest/auth/login", {
method: "POST",
body: JSON.stringify({ email, password }),
headers: { "Content-Type": "application/json" },
});
// UI API — record with metadata (prefer GraphQL for simple reads)
const res = await sdk.fetch?.(`/services/data/v${API_VERSION}/ui-api/records/${recordId}`);
// Einstein LLM
const res = await sdk.fetch?.(`/services/data/v${API_VERSION}/einstein/llm/prompt/generations`, {
method: "POST",
body: JSON.stringify({ promptTextorId: prompt }),
});当前用户:不要使用Chatter()。改用GraphQL:
/chatter/users/metypescript
const GET_CURRENT_USER = gql`
query CurrentUser {
uiapi { currentUser { Id Name { value } } }
}
`;
const response = await sdk.graphql?.(GET_CURRENT_USER);Directory Structure
目录结构
<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/| Command | Run From | Why |
|---|---|---|
| UI bundle dir | Script in UI bundle's package.json |
| UI bundle dir | Generate GraphQL types |
| UI bundle dir | Reads eslint.config.js |
| project root | Schema lookup |
<project-root>/ ← SFDX项目根目录
├── schema.graphql ← grep目标(存放在此)
├── sfdx-project.json
├── scripts/graphql-search.sh ← 架构查询脚本
└── force-app/main/default/uiBundles/<app-name>/ ← UI bundle目录
├── package.json ← npm脚本
└── src/| 命令 | 运行目录 | 原因 |
|---|---|---|
| UI bundle目录 | 脚本位于UI bundle的package.json中 |
| UI bundle目录 | 生成GraphQL类型 |
| UI bundle目录 | 读取eslint.config.js |
| 项目根目录 | 架构查询 |
Quick Reference
快速参考
Schema Lookup (from project root)
架构查询(从项目根目录)
Run the search script to get all relevant schema info in one step:
bash
bash scripts/graphql-search.sh <EntityName>| Script Output Section | Used For |
|---|---|
| Type definition | Field names, parent/child relationships |
| Filter options | |
| Sort options | |
| CreateRepresentation | Create mutation field list |
| UpdateRepresentation | Update mutation field list |
运行搜索脚本,一步获取所有相关架构信息:
bash
bash scripts/graphql-search.sh <EntityName>| 脚本输出部分 | 用途 |
|---|---|
| Type definition | 字段名称、父/子关系 |
| Filter options | |
| Sort options | |
| CreateRepresentation | 创建变更字段列表 |
| UpdateRepresentation | 更新变更字段列表 |
Error Categories
错误分类
| Error Contains | Resolution |
|---|---|
| Field name is wrong — run |
| Type name is wrong — run |
| Argument name is wrong — run |
| Fix syntax per error message |
| Field name is wrong — run |
| Correct argument type from schema |
| Entity deleted — ask for valid Id |
| 错误包含内容 | 解决方法 |
|---|---|
| 字段名称错误——运行 |
| 类型名称错误——运行 |
| 参数名称错误——运行 |
| 根据错误消息修复语法 |
| 字段名称错误——运行 |
| 根据架构修正参数类型 |
| 实体已删除——请求有效的Id |
Checklist
检查清单
- All field names verified via search script (Step 2)
- applied to all record fields (reads)
@optional - Mutations use wrapper
uiapi(input: { allOrNone: ... }) - specified in every query
first: - Optional chaining in consuming code
- array checked in response handling
errors - Lint passes:
npx eslint <file>
- 所有字段名称已通过搜索脚本验证(步骤2)
- 所有记录字段已添加(读取操作)
@optional - 变更使用包装器
uiapi(input: { allOrNone: ... }) - 每个查询均指定了
first: - 消费代码使用了可选链
- 响应处理中检查了数组
errors - Lint检查通过:
npx eslint <file>