using-ui-bundle-salesforce-data

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Salesforce Data Access

Salesforce数据访问

Data SDK Requirement

Data SDK要求

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?.<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 (
sdk.graphql?.()
,
sdk.fetch?.()
) — these methods may be undefined in some surfaces.
所有Salesforce数据访问必须使用Data SDK
@salesforce/sdk-data
)。该SDK负责处理身份验证、CSRF和基础URL解析。
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

前置条件——开始前验证

#RequirementHow to verifyIf 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 existsRun
npm run graphql:schema
from UI bundle dir
3Custom objects/fields deployedRun
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.
序号要求验证方式缺失时处理
1已安装
@salesforce/sdk-data
检查UI bundle目录下的
package.json
无法继续——告知用户安装该依赖
2项目根目录存在
schema.graphql
检查文件是否存在在UI bundle目录下运行
npm run graphql:schema
3已部署自定义对象/字段运行
graphql-search.sh <Entity>
——无输出表示未部署
要求用户部署元数据并分配权限集
如果前置条件未满足,你可以搭建组件、路由、布局和UI逻辑,但需使用空数组/
null
填充数据,并在查询位置标记
// TODO: add query after schema verification
,同时将解决要求并编写GraphQL的步骤纳入计划。在架构流程完成前,请勿编写GraphQL查询字符串。

Supported APIs

支持的API

Only the following APIs are permitted. Any endpoint not listed here must not be used.
APIMethodEndpoints / Use Case
GraphQL
sdk.graphql
All record queries and mutations via
uiapi { }
namespace
UI API REST
sdk.fetch
/services/data/v{ver}/ui-api/records/{id}
— record metadata when GraphQL is insufficient
Apex REST
sdk.fetch
/services/apexrest/{resource}
— custom server-side logic, aggregates, multi-step transactions
Connect REST
sdk.fetch
/services/data/v{ver}/connect/file/upload/config
— file upload config
Einstein LLM
sdk.fetch
/services/data/v{ver}/einstein/llm/prompt/generations
— AI text generation
Not supported:
  • Enterprise REST query endpoint (
    /services/data/v*/query
    with SOQL) — blocked at the proxy level. Use GraphQL for record reads; use Apex REST if server-side SOQL aggregates are required.
  • Aura-enabled Apex (
    @AuraEnabled
    ) — an LWC/Aura pattern with no invocation path from React UI bundles.
  • Chatter API (
    /chatter/users/me
    ) — use
    uiapi { currentUser { ... } }
    in a GraphQL query instead.
  • Any other Salesforce REST endpoint not listed in the supported table above.
仅允许使用以下API。未在此列出的任何端点均不得使用。
API方法端点/使用场景
GraphQL
sdk.graphql
通过
uiapi { }
命名空间进行所有记录查询和变更
UI API REST
sdk.fetch
/services/data/v{ver}/ui-api/records/{id}
——当GraphQL无法满足需求时,用于获取记录元数据
Apex REST
sdk.fetch
/services/apexrest/{resource}
——自定义服务器端逻辑、聚合操作、多步骤事务
Connect REST
sdk.fetch
/services/data/v{ver}/connect/file/upload/config
——文件上传配置
Einstein LLM
sdk.fetch
/services/data/v{ver}/einstein/llm/prompt/generations
——AI文本生成
不支持的API:
  • Enterprise REST查询端点
    /services/data/v*/query
    搭配SOQL)——已被代理层拦截。记录读取使用GraphQL;若需要服务器端SOQL聚合操作,使用Apex REST。
  • Aura-enabled Apex
    @AuraEnabled
    )——这是LWC/Aura模式,无法从React UI bundles中调用。
  • Chatter API
    /chatter/users/me
    )——改用GraphQL查询中的
    uiapi { currentUser { ... } }
  • 上述支持列表中未提及的任何其他Salesforce REST端点

Decision: GraphQL vs REST

决策:GraphQL vs REST

NeedMethodExample
Query/mutate records
sdk.graphql
Account, Contact, custom objects
Current user info
sdk.graphql
uiapi { currentUser { Id Name { value } } }
UI API record metadata
sdk.fetch
/ui-api/records/{id}
Connect REST
sdk.fetch
/connect/file/upload/config
Apex REST
sdk.fetch
/services/apexrest/auth/login
Einstein LLM
sdk.fetch
/einstein/llm/prompt/generations
GraphQL is preferred for record operations. Use REST only when GraphQL doesn't cover the use case.

需求方法示例
查询/变更记录
sdk.graphql
Account、Contact、自定义对象
当前用户信息
sdk.graphql
uiapi { currentUser { Id Name { value } } }
UI API记录元数据
sdk.fetch
/ui-api/records/{id}
Connect REST
sdk.fetch
/connect/file/upload/config
Apex REST
sdk.fetch
/services/apexrest/auth/login
Einstein LLM
sdk.fetch
/einstein/llm/prompt/generations
记录操作优先使用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.
  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. 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. @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. 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. 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. 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.

这些规则的存在是因为Salesforce GraphQL具有与标准GraphQL不同的平台特定行为。违反规则会导致静默运行时失败。
  1. HTTP 200不代表成功——即使操作失败,Salesforce仍会返回HTTP 200。必须始终解析响应体中的
    errors
    数组
  2. 架构是唯一数据源——在查询中使用的每个实体名称、字段名称和类型,必须先通过架构搜索脚本确认。切勿猜测——Salesforce字段名称区分大小写,关系可能是多态的,自定义对象使用后缀(
    __c
    __e
    )。在v60+版本中加入UI API的对象可能使用
    _Record
    后缀(例如
    FeedItem_Record
    而非
    FeedItem
    )。
  3. 所有记录字段添加
    @optional
    (读取查询)——如果用户缺少对任意一个字段的访问权限,Salesforce字段级安全性(FLS)会导致查询完全失败。
    @optional
    指令(v65+)告知服务器省略不可访问的字段,而非让查询失败。将其应用于每个标量字段、父关系和子关系。消费代码必须使用可选链(
    ?.
    )和空值合并运算符(
    ??
    )。
  4. 正确的变更语法——变更需包裹在
    uiapi(input: { allOrNone: true/false })
    中,而非直接使用
    uiapi { ... }
    。必须显式设置
    allOrNone
    。输出字段不能包含子关系或导航引用字段。
  5. 显式分页——每个查询必须包含
    first:
    。如果省略,服务器会静默默认返回10条记录。对于可能需要分页的任何查询,需包含
    pageInfo { hasNextPage endCursor }
    。仅支持向前分页(
    first
    /
    after
    )——不支持
    last
    /
    before
  6. SOQL衍生的执行限制——每个请求最多10个子查询,最多5级子到父的遍历,最多1级父到子(不支持孙辈),每个子查询最多2000条记录。如果查询会超出这些限制,拆分为多个请求。
  7. 仅请求所需字段——仅生成用户明确要求的字段。切勿添加额外字段。
  8. 复合字段——过滤或排序时,使用组成字段(例如
    BillingCity
    BillingCountry
    ),而非复合包装器(
    BillingAddress
    )。复合包装器仅用于字段选择。

GraphQL Workflow

GraphQL工作流程

StepActionKey output
1Acquire schema
schema.graphql
exists
2Look up entitiesField names, types, relationships confirmed
3Generate query
.graphql
file or inline
gql
tag
4Generate types
graphql-operations-types.ts
5ValidateLint + codegen pass
步骤操作关键输出
1获取架构存在
schema.graphql
2查询实体确认字段名称、类型、关系
3生成查询
.graphql
文件或内联
gql
标签
4生成类型
graphql-operations-types.ts
5验证通过Lint和codegen检查

Step 1: Acquire Schema

步骤1:获取架构

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.
Verify preconditions 1–3 (see Preconditions), then proceed to Step 2.
schema.graphql
文件(265K+行)是数据源。切勿直接打开或解析它——不要使用cat、less、head、tail、编辑器或程序化解析器。
验证前置条件1-3(参见前置条件),然后进入步骤2。

Step 2: Look Up Entity Schema

步骤2:查询实体架构

Map user intent to PascalCase names ("accounts" →
Account
), then run the search script from the
sfdx-project
folder (project root)
:
bash
bash scripts/graphql-search.sh Account
将用户需求映射为大驼峰名称("accounts" →
Account
),然后
sfdx-project
文件夹(项目根目录)运行搜索脚本
bash
bash scripts/graphql-search.sh Account

Multiple 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
    __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
如果候选实体不匹配:
  • 尝试为自定义对象添加
    __c
    后缀,为平台事件添加
    __e
    后缀
  • 尝试
    _Record
    后缀——v60+版本中添加的对象可能使用
    <EntityName>_Record
  • 如果仍未解决,询问用户——切勿猜测

Iterative Introspection (max 3 cycles)

迭代自省(最多3轮)

  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.
  1. 自省——为每个未解决的实体运行脚本
  2. 字段——从类型定义中提取请求的字段名称和类型
  3. 引用——识别引用字段。如果是多态(多种类型),使用内联片段。将新发现的实体类型添加到工作列表中。
  4. 子关系——识别Connection类型。将子实体类型添加到工作列表中。
  5. 重复如果仍有未解决的实体(最多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
undefined
graphql
undefined

Implicit 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
Id
as tie-breaker for deterministic ordering.
graphql
Account(
  first: 10,
  orderBy: { Name: { order: ASC }, CreatedDate: { order: DESC } }
) { ... }
不支持排序的字段:多选选择列表、富文本、长文本区域、加密字段。添加
Id
作为排序的决胜字段以确保确定性。

UpperBound Pagination (v59+)

上限分页(v59+)

For >200 records per page or >4,000 total records, use
upperBound
.
first
must be 200–2000 when set.
graphql
Account(first: 2000, after: $cursor, upperBound: 10000) {
  edges { node { Id Name @optional { value } } }
  pageInfo { hasNextPage endCursor }
}
当每页记录数>200或总记录数>4000时,使用
upperBound
。设置时
first
必须为200–2000。
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 } } }
      }
    }
  }
}
Replace
inq
with
ninq
for anti-join. Restrictions: no
OR
in subquery, no
orderBy
in subquery, no nesting joins within each other.
使用父实体的
Id
上的
inq
(半连接)或
ninq
(反连接),根据子实体的条件过滤父实体。如果唯一条件是子实体存在,使用
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 } } }
      }
    }
  }
}
inq
替换为
ninq
以实现反连接。限制:子查询中不能使用
OR
,子查询中不能使用
orderBy
,不能嵌套连接。

Current User

当前用户

Use
uiapi.currentUser
(no arguments) instead of the standard query pattern:
graphql
query CurrentUser {
  uiapi { currentUser { Id Name { value } } }
}
使用
uiapi.currentUser
(无参数)代替标准查询模式:
graphql
query CurrentUser {
  uiapi { currentUser { Id Name { value } } }
}

Field Value Wrappers

字段值包装器

Schema fields use typed wrappers — access via
.value
:
Wrapper TypeUnderlyingWrapper TypeUnderlying
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.
架构字段使用类型化包装器——通过
.value
访问:
包装器类型底层类型包装器类型底层类型
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
所有包装器还暴露
displayValue: String
(通过服务器端
toLabel()
/
format()
渲染)——用于UI展示,而非客户端格式化。

Mutation Template

变更模板

Mutations are GA in API v66+. Three operations: Create, Update, Delete.
graphql
undefined
变更在API v66+中正式可用。三种操作:创建更新删除
graphql
undefined

Create

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
@{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
).
使用
@{alias}
引用早期变更中的
Id
,实现相关变更的链式操作。父子创建需要此操作(不支持嵌套子创建)。
graphql
mutation CreateAccountAndContact {
  uiapi(input: { allOrNone: true }) {
    AccountCreate(input: { Account: { Name: "Acme" } }) {
      Record { Id }
    }
    ContactCreate(input: { Contact: { LastName: "Smith", AccountId: "@{AccountCreate}" } }) {
      Record { Id }
    }
  }
}
规则:
A
必须在查询中位于
B
之前。
@{A}
始终是变更
A
Id
。仅能从创建删除操作进行链式操作(不支持更新)。

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
    }
  }
}
删除使用通用的
RecordDeleteInput
(非实体特定)。输出仅为
Id
——无
Record
字段。
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.
typescript
// 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(...) }
获取字段元数据或选择列表值。必须传递**
apiNames
objectInfoInputs
其中之一**——切勿同时传递。
typescript
// 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
.graphql
file or inline with
gql
), generate TypeScript types:
bash
undefined
编写查询后(无论是在
.graphql
文件中还是通过
gql
内联),生成TypeScript类型:
bash
undefined

Run 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
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"]>;
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);
使用
NodeOfConnection<T>
从Connection中提取节点类型,实现更简洁的类型定义:
typescript
import { type NodeOfConnection } from "@salesforce/sdk-data";

type AccountNode = NodeOfConnection<GetAccountsQuery["uiapi"]["query"]["Account"]>;

Step 5: Validate & Test

步骤5:验证与测试

  1. Lint:
    npx eslint <file>
    from UI bundle dir
  2. codegen:
    npm run graphql:codegen
    from UI bundle dir
  1. Lint检查:从UI bundle目录运行
    npx eslint <file>
  2. codegen检查:从UI bundle目录运行
    npm run graphql:codegen

Common Error patterns

常见错误模式

Error ContainsResolution
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.

错误包含内容解决方法
Cannot query field
/
ValidationError
字段名称错误——重新运行
graphql-search.sh <Entity>
Unknown type
类型名称错误——通过脚本验证大驼峰实体名称
Unknown argument
参数错误——检查脚本输出中的Filter/OrderBy部分
invalid syntax
/
InvalidSyntax
根据错误消息修复语法
VariableTypeMismatch
/
UnknownType
根据架构修正参数类型
invalid cross reference id
实体已删除——请求有效的Id
OperationNotSupported
检查对象可用性和API版本
is not currently available in mutation results
从变更输出中移除该字段
Cannot invoke JsonElement.isJsonObject()
更新变更的
Record
选择使用API版本64+
部分成功场景如果变更同时返回数据和错误(部分成功):报告不可访问的字段,说明它们不能出现在变更输出中,请求用户同意后移除这些字段。等待用户同意后再修改

UI Bundle Integration (React)

UI Bundle集成(React)

Two integration patterns:
两种集成模式:

Pattern 1 — External
.graphql
file (complex queries)

模式1——外部
.graphql
文件(复杂查询)

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
.
每个
.graphql
文件包含一个操作
。每个文件恰好包含一个
query
mutation
(及其片段)。切勿在单个文件中组合多个操作。
typescript
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);
创建/修改
.graphql
文件后,运行
npm run graphql:codegen
将类型生成到
src/api/graphql-operations-types.ts
中。

Pattern 2 — Inline
gql
tag (simple queries)

模式2——内联
gql
标签(简单查询)

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 } } }
        }
      }
    }
  }
`;

const sdk = await createDataSDK();
const response = await sdk.graphql?.<GetAccountsQuery>(GET_ACCOUNTS);
必须使用
gql
——普通模板字符串会绕过ESLint架构验证。
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 } } }
        }
      }
    }
  }
`;

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
sdk.fetch
when GraphQL is insufficient. See the Supported APIs table for the full allowlist.
typescript
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 (
/chatter/users/me
). Use GraphQL instead:
typescript
const GET_CURRENT_USER = gql`
  query CurrentUser {
    uiapi { currentUser { Id Name { value } } }
  }
`;
const response = await sdk.graphql?.(GET_CURRENT_USER);

当GraphQL无法满足需求时,使用
sdk.fetch
。完整允许列表参见支持的API表格。
typescript
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(
/chatter/users/me
)。改用GraphQL:
typescript
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/
CommandRun FromWhy
npm run graphql:schema
UI bundle dirScript in UI bundle's package.json
npm run graphql:codegen
UI bundle dirGenerate GraphQL types
npx eslint <file>
UI bundle dirReads eslint.config.js
bash scripts/graphql-search.sh <Entity>
project rootSchema 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/
命令运行目录原因
npm run graphql:schema
UI bundle目录脚本位于UI bundle的package.json中
npm run graphql:codegen
UI bundle目录生成GraphQL类型
npx eslint <file>
UI bundle目录读取eslint.config.js
bash scripts/graphql-search.sh <Entity>
项目根目录架构查询

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 SectionUsed For
Type definitionField names, parent/child relationships
Filter options
where:
conditions
Sort options
orderBy:
CreateRepresentationCreate mutation field list
UpdateRepresentationUpdate mutation field list
运行搜索脚本,一步获取所有相关架构信息:
bash
bash scripts/graphql-search.sh <EntityName>
脚本输出部分用途
Type definition字段名称、父/子关系
Filter options
where:
条件
Sort options
orderBy:
CreateRepresentation创建变更字段列表
UpdateRepresentation更新变更字段列表

Error Categories

错误分类

Error ContainsResolution
Cannot query field
Field name is wrong — run
graphql-search.sh <Entity>
and use the exact name from the Type definition section
Unknown type
Type name is wrong — run
graphql-search.sh <Entity>
to confirm the correct PascalCase entity name
Unknown argument
Argument name is wrong — run
graphql-search.sh <Entity>
and check Filter or OrderBy sections
invalid syntax
Fix syntax per error message
validation error
Field name is wrong — run
graphql-search.sh <Entity>
to verify
VariableTypeMismatch
Correct argument type from schema
invalid cross reference id
Entity deleted — ask for valid Id
错误包含内容解决方法
Cannot query field
字段名称错误——运行
graphql-search.sh <Entity>
,使用Type definition部分中的准确名称
Unknown type
类型名称错误——运行
graphql-search.sh <Entity>
确认正确的大驼峰实体名称
Unknown argument
参数名称错误——运行
graphql-search.sh <Entity>
,检查Filter或OrderBy部分
invalid syntax
根据错误消息修复语法
validation error
字段名称错误——运行
graphql-search.sh <Entity>
验证
VariableTypeMismatch
根据架构修正参数类型
invalid cross reference id
实体已删除——请求有效的Id

Checklist

检查清单

  • All field names verified via search script (Step 2)
  • @optional
    applied to all record fields (reads)
  • Mutations use
    uiapi(input: { allOrNone: ... })
    wrapper
  • first:
    specified in every query
  • Optional chaining in consuming code
  • errors
    array checked in response handling
  • Lint passes:
    npx eslint <file>
  • 所有字段名称已通过搜索脚本验证(步骤2)
  • 所有记录字段已添加
    @optional
    (读取操作)
  • 变更使用
    uiapi(input: { allOrNone: ... })
    包装器
  • 每个查询均指定了
    first:
  • 消费代码使用了可选链
  • 响应处理中检查了
    errors
    数组
  • Lint检查通过:
    npx eslint <file>