platform-salesforce-connect-adapter-generate

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Salesforce Connect — Custom Apex Adapter

Salesforce Connect — 自定义Apex适配器

Route the user through building a complete custom Salesforce Connect Apex adapter: two Apex classes, metadata deployment, and registration. Do not guess at the user's API shape — ask before generating.
引导用户构建完整的Salesforce Connect自定义Apex适配器:包含两个Apex类、元数据部署和注册步骤。请勿猜测用户的API结构——生成前先询问相关信息。

Scope

范围

In scope: Generating
DataSource.Connection
and
DataSource.Provider
Apex classes, Named Credential metadata, deployment via sf CLI, and step-by-step Setup registration guidance for any REST API.
Out of scope: Configuring standard adapters (OData, Snowflake, GraphQL — those have their own flows). Generating the External Data Source metadata file (not deployable via sf CLI — must be registered manually in Setup). Writing Apex that calls external APIs outside the Salesforce Connect framework (use platform-apex-generate or integration-connectivity-generate).
包含范围: 生成
DataSource.Connection
DataSource.Provider
Apex类、Named Credential元数据、通过sf CLI进行部署,以及针对任意REST API的分步Setup注册指导。
排除范围: 配置标准适配器(OData、Snowflake、GraphQL——这些有各自的流程);生成外部数据源元数据文件(无法通过sf CLI部署——必须在Setup中手动注册);编写Salesforce Connect框架外调用外部API的Apex代码(请使用platform-apex-generateintegration-connectivity-generate)。

Before starting

开始前准备

Confirm two things. If either is missing, ask before proceeding.
1. Is this actually a Salesforce Connect use case?
The user wants to…Right tool
Query external data live without copying it — read-only or read-write, appears as Salesforce recordsThis skill
Copy or sync data into Salesforce on a scheduleplatform-data-manage
Connect to OData, GraphQL, DynamoDB, Athena, or Cross-OrgStandard adapter setup — no Apex needed, different flow
Connect to Snowflake via Salesforce's native Snowflake adapter (direct Snowflake protocol)Built-in Snowflake adapter — no Apex needed
Access Snowflake (or any database) data via a REST or HTTP APIThis skill — Snowflake REST endpoint → custom Apex adapter
Call an external API from Apex or Flow logicplatform-apex-generate or integration-connectivity-generate
Expose Salesforce data to an external systemintegration-connectivity-generate
Receive real-time pushed data or subscribe to external eventsNot Salesforce Connect — Connect is pull-only. Use Platform Events or Change Data Capture instead
Sync or copy data for analytics or bulk processingData Cloud or ETL — Connect is zero-copy virtualization only
If the user has a standard adapter available, tell them — writing a custom adapter when a standard one fits is unnecessary work.
2. Do they have a Salesforce Connect license?
A custom adapter requires a Salesforce Connect license (one license per External Data Source). Without it, the External Data Source menu won't show the Apex option. If the user doesn't have one, tell them before going further.

确认以下两点。若缺少任意一项,请先询问用户再继续。
1. 这是否确实是Salesforce Connect的适用场景?
用户需求合适工具
无需复制数据即可实时查询外部数据——只读或读写,以Salesforce记录形式展示本技能
按计划将数据复制或同步到Salesforceplatform-data-manage
连接到OData、GraphQL、DynamoDB、Athena或跨组织标准适配器设置——无需Apex,流程不同
通过Salesforce原生Snowflake适配器连接到Snowflake(直接使用Snowflake协议)内置Snowflake适配器——无需Apex
通过REST或HTTP API访问Snowflake(或任意数据库)数据本技能 ——Snowflake REST端点 → 自定义Apex适配器
从Apex或Flow逻辑中调用外部APIplatform-apex-generateintegration-connectivity-generate
向外部系统暴露Salesforce数据integration-connectivity-generate
接收实时推送数据或订阅外部事件不属于Salesforce Connect——Connect仅支持拉取。请改用Platform Events或Change Data Capture
同步或复制数据用于分析或批量处理Data Cloud或ETL——Connect仅支持零拷贝虚拟化
如果用户可使用标准适配器,请告知他们——当标准适配器适用时编写自定义适配器属于不必要的工作。
2. 用户是否拥有Salesforce Connect许可证?
自定义适配器需要Salesforce Connect许可证(每个外部数据源需一个许可证)。若无许可证,外部数据源菜单将不会显示Apex选项。若用户没有许可证,请在继续前告知他们。

Default org context (when running locally)

默认组织上下文(本地运行时)

If the user does not specify an org alias, use
demo-org
. If the user does not specify an SFDX project path, use
~/salesforce-connect-apex-skill/sfconnect-demo/
. These are the defaults for local testing — always override if the user specifies their own.

若用户未指定组织别名,使用
demo-org
。 若用户未指定SFDX项目路径,使用
~/salesforce-connect-apex-skill/sfconnect-demo/
。 这些是本地测试的默认值——若用户指定了自己的配置,请始终覆盖默认值。

Collect inputs before generating

生成前收集输入信息

Never generate code without these. If any are missing, ask:
InputWhy it matters
API name and what it doesNames the classes and sets context for field mapping
Base URLBecomes the Named Credential endpoint
Auth typeDetermines Named Credential setup and
getAuthenticationMode()
Key GET endpoint(s) + sample responseDefines the External Object schema — field names, types, nesting
Write support needed?Determines whether to implement
upsertRows()
and
deleteRows()
Org type (DE, scratch, sandbox)Sets
apiVersion
in
.cls-meta.xml
If the user provides an OpenAPI spec, extract:
  • All GET endpoints returning arrays → each becomes a
    DataSource.Table
  • Response object properties →
    DataSource.Column
    entries
  • id
    /
    uuid
    / primary key → map to
    ExternalId
  • Property types → use the field type mapping table below

在缺少以下信息时,切勿生成代码。若有缺失,请询问用户:
输入信息重要性
API名称及其功能用于命名类并为字段映射设置上下文
基础URL将成为Named Credential的端点
认证类型决定Named Credential的设置和
getAuthenticationMode()
方法实现
关键GET端点+示例响应定义外部对象架构——字段名称、类型、嵌套结构
是否需要写入支持?决定是否实现
upsertRows()
deleteRows()
方法
组织类型(DE、scratch、sandbox)设置
.cls-meta.xml
中的
apiVersion
若用户提供OpenAPI规范,请提取以下信息:
  • 所有返回数组的GET端点 → 每个端点对应一个
    DataSource.Table
  • 响应对象属性 →
    DataSource.Column
    条目
  • id
    /
    uuid
    /主键 → 映射到
    ExternalId
  • 属性类型 → 使用下方的字段类型映射表

What to build

构建内容

Every custom Salesforce Connect adapter is exactly two Apex classes.
每个Salesforce Connect自定义适配器都包含恰好两个Apex类。

DataSource.Connection

DataSource.Connection

Handles communication with the external API. Exact signatures:
apex
override global DataSource.TableResult query(DataSource.QueryContext context)
override global List<DataSource.TableResult> search(DataSource.SearchContext context)

// Write support — only if API supports it:
global override List<DataSource.UpsertResult> upsertRows(DataSource.UpsertContext context)
global override List<DataSource.DeleteResult> deleteRows(DataSource.DeleteContext context)
Key distinction:
query()
always operates on one table (
QueryContext
has a single
TableSelection
).
search()
can operate on multiple tables simultaneously (
SearchContext
has multiple
TableSelection
instances) — handle each in a loop and return a result per table.
处理与外部API的通信。精确方法签名:
apex
override global DataSource.TableResult query(DataSource.QueryContext context)
override global List<DataSource.TableResult> search(DataSource.SearchContext context)

// 写入支持——仅当API支持时实现:
global override List<DataSource.UpsertResult> upsertRows(DataSource.UpsertContext context)
global override List<DataSource.DeleteResult> deleteRows(DataSource.DeleteContext context)
关键区别:
query()
始终针对单个表(
QueryContext
包含单个
TableSelection
)。
search()
可同时针对多个表(
SearchContext
包含多个
TableSelection
实例)——需循环处理每个表并返回对应结果。

DataSource.Provider

DataSource.Provider

Declares the adapter's capabilities and schema to Salesforce. Required methods:
  • getAuthenticationMode()
    — return
    ANONYMOUS
    for public APIs;
    NAMED_PRINCIPAL
    or
    PER_USER
    for authenticated APIs
  • getCapabilities()
    — declare
    QUERY
    ,
    SEARCH
    ; add
    ROW_CREATE
    ,
    ROW_UPDATE
    ,
    ROW_DELETE
    only if implementing writes
  • getConnection(ConnectionParams params)
    — return
    new YourConnection(params)
  • sync()
    — called when the user clicks "Validate and Sync" in Setup; returns
    List<DataSource.Table>
    defining the External Object schema and columns
The Provider class appears in Salesforce Setup as
Custom-[ClassName]
under the External Data Source type dropdown.
Critical: whenever you edit the Connection class, you must resave the Provider class too — even with no changes. Otherwise the adapter disappears from the Type picklist and existing External Object tabs break.

向Salesforce声明适配器的功能和架构。必填方法:
  • getAuthenticationMode()
    —— 公共API返回
    ANONYMOUS
    ;需认证的API返回
    NAMED_PRINCIPAL
    PER_USER
  • getCapabilities()
    —— 声明
    QUERY
    SEARCH
    ;仅当实现写入功能时添加
    ROW_CREATE
    ROW_UPDATE
    ROW_DELETE
  • getConnection(ConnectionParams params)
    —— 返回
    new YourConnection(params)
  • sync()
    —— 用户在Setup中点击“Validate and Sync”时调用;返回
    List<DataSource.Table>
    定义外部对象的架构和列
Provider类在Salesforce Setup的外部数据源类型下拉菜单中显示为**
Custom-[ClassName]
**。
关键注意事项: 无论是否修改Provider类,每当编辑Connection类时,必须重新保存Provider类。否则适配器会从类型选择列表中消失,且现有外部对象标签会失效。

Field type mapping

字段类型映射

External API typeUse this
string / text
DataSource.DataType.TEXT_TYPE
number / integer / decimal
DataSource.DataType.NUMBER_TYPE
boolean
DataSource.DataType.BOOLEAN_TYPE
date (ISO 8601)
DataSource.DataType.DATE_TYPE
datetime / timestamp
DataSource.DataType.DATETIME_TYPE
URL
DataSource.DataType.URL_TYPE
email
DataSource.DataType.EMAIL_TYPE
phone
DataSource.DataType.PHONE_TYPE
enum / picklist
TEXT_TYPE
— map enum values as strings
nested object / JSON blobFlatten to scalar fields, or
TEXT_TYPE
and parse in
query()
arrayDerive a count field (
NUMBER_TYPE
), or flatten first-element fields
string > 255 charsLong text area — do not truncate; Salesforce maps it automatically
Every
DataSource.Table
must include these columns:
apex
DataSource.Column.text('ExternalId', 255)  // unique key from external system — REQUIRED
DataSource.Column.text('Name', 255)        // display label shown in Salesforce UI — REQUIRED
DataSource.Column.url('DisplayUrl')        // link to the record in the external system — recommended
Missing
ExternalId
is the most common reason an adapter deploys but records don't appear.
DisplayUrl
enables the clickable link icon in list views — populate it with the external record's URL.

外部API类型使用对应Salesforce类型
string / text
DataSource.DataType.TEXT_TYPE
number / integer / decimal
DataSource.DataType.NUMBER_TYPE
boolean
DataSource.DataType.BOOLEAN_TYPE
date (ISO 8601)
DataSource.DataType.DATE_TYPE
datetime / timestamp
DataSource.DataType.DATETIME_TYPE
URL
DataSource.DataType.URL_TYPE
email
DataSource.DataType.EMAIL_TYPE
phone
DataSource.DataType.PHONE_TYPE
enum / picklist
TEXT_TYPE
—— 将枚举值映射为字符串
nested object / JSON blob展平为标量字段,或使用
TEXT_TYPE
并在
query()
中解析
array派生计数字段(
NUMBER_TYPE
),或展平第一个元素的字段
string > 255字符长文本区域——请勿截断;Salesforce会自动映射
每个
DataSource.Table
必须包含以下列:
apex
DataSource.Column.text('ExternalId', 255)  // 外部系统的唯一键——必填
DataSource.Column.text('Name', 255)        // Salesforce UI中显示的标签——必填
DataSource.Column.url('DisplayUrl')        // 指向外部系统记录的链接——推荐
缺少
ExternalId
是适配器部署成功但记录不显示的最常见原因。
DisplayUrl
可在列表视图中启用可点击链接图标——需填充外部记录的URL。

Scenarios

场景示例

Four scenarios: (1) public read-only, (2) authenticated API key/OAuth, (3) read-write with upsert/delete, (4) paginated API. Full code patterns for each are in
references/scenarios.md
.
Key differences by scenario:
ScenarioAuth modeExtra capabilitiesNamed Credential
Public read-only
ANONYMOUS
QUERY
,
SEARCH
Deploy as metadata
Authenticated
NAMED_PRINCIPAL
or
PER_USER
QUERY
,
SEARCH
Create manually in Setup — never deploy credentials as metadata
Read-write
ANONYMOUS
or auth
Add
ROW_CREATE
,
ROW_UPDATE
,
ROW_DELETE
Per above
PaginatedAnyAnyUse
context.tableSelection.numberOfRows
(default 500 if null); no automatic
queryMore

四种场景:(1) 公共只读API,(2) 已认证API(API密钥/OAuth),(3) 支持读写(含upsert/delete),(4) 分页API。每个场景的完整代码模式见
references/scenarios.md
各场景关键差异:
场景认证模式额外功能Named Credential
公共只读
ANONYMOUS
QUERY
,
SEARCH
作为元数据部署
已认证API
NAMED_PRINCIPAL
PER_USER
QUERY
,
SEARCH
在Setup中手动创建——切勿将凭据作为元数据部署
读写API
ANONYMOUS
或已认证
添加
ROW_CREATE
,
ROW_UPDATE
,
ROW_DELETE
同上
分页API任意任意使用
context.tableSelection.numberOfRows
(若为null则默认500);无自动
queryMore

How to verify the adapter is working

验证适配器是否正常工作

Option 1 — SOQL via sf CLI (fastest, no UI needed)

选项1 — 通过sf CLI执行SOQL(最快,无需UI)

bash
sf data query \
  --query "SELECT ExternalId, Name__c FROM [YourObject]__x LIMIT 5" \
  --target-org [org-alias]
If rows come back, the adapter is working. If 0 rows, check the troubleshooting steps below.
bash
sf data query \
  --query "SELECT ExternalId, Name__c FROM [YourObject]__x LIMIT 5" \
  --target-org [org-alias]
若返回数据行,则适配器正常工作。若返回0行,请查看下方故障排除步骤。

Option 2 — Visualforce page (best for demos, works on any org)

选项2 — Visualforce页面(最适合演示,适用于所有组织)

Deploy a Visualforce page + Apex controller that queries
[Object]__x
and renders rows in an
apex:pageBlockTable
. Open at
[orgUrl]/apex/[PageName]
. More reliable than list views for testing — bypasses tab requirements, deployment status, and filter issues. See
references/scenarios.md
for the standard template.
部署一个Visualforce页面+Apex控制器,查询
[Object]__x
并在
apex:pageBlockTable
中渲染数据行。在
[orgUrl]/apex/[PageName]
打开页面。比列表视图测试更可靠——绕过标签要求、部署状态和过滤问题。标准模板见
references/scenarios.md

Option 3 — List view (standard UI, but requires extra steps)

选项3 — 列表视图(标准UI,但需额外步骤)

For the list view to show records, all four of these must be true:
  1. External Object Deployment Status = Deployed (Object Manager → Edit)
  2. A tab exists for the External Object (Setup → Tabs → Custom Object Tabs → New)
  3. List view filter is set to All (not "Recently Viewed")
  4. Remote Site Setting exists for the external API URL
要让列表视图显示记录,必须满足以下四个条件:
  1. 外部对象部署状态 = 已部署(Object Manager → 编辑)
  2. 为外部对象创建了标签(Setup → 标签 → 自定义对象标签 → 新建)
  3. 列表视图过滤器设置为 全部(而非“最近查看”)
  4. 为外部API URL配置了远程站点设置

Troubleshooting: 0 records with no error

故障排除:返回0记录且无错误

SymptomCauseFix
SOQL returns 0Remote Site Setting missingSetup → Remote Site Settings → New
SOQL returns 0Named Credential not foundSetup → Named Credentials → confirm
callout:
name matches exactly
SOQL returns 0Org proxy blocks external URLsUse loopback pattern or switch to an external DE org
List view shows 0Deployment Status = In DevelopmentObject Manager → [Object]__x → Edit → Deployed
List view shows 0Filter is "Recently Viewed"Change filter to "All"
App Launcher shows nothingNo tab createdSetup → Tabs → Custom Object Tabs → New

症状原因修复方法
SOQL返回0缺少远程站点设置Setup → 远程站点设置 → 新建
SOQL返回0未找到Named CredentialSetup → Named Credentials → 确认
callout:
名称完全匹配
SOQL返回0组织代理阻止外部URL使用环回模式或切换到外部DE组织
列表视图显示0部署状态 = 开发中Object Manager → [Object]__x → 编辑 → 已部署
列表视图显示0过滤器为“最近查看”将过滤器更改为“全部”
应用启动器无显示未创建标签Setup → 标签 → 自定义对象标签 → 新建

What the agent deploys vs. what the developer does manually

代理部署内容 vs 开发者手动操作内容

Agent deploys (one
sf project deploy start
command)

代理部署(通过
sf project deploy start
命令)

FileNotes
classes/[API]DataSourceConnection.cls
+ meta
Always
classes/[API]DataSourceProvider.cls
+ meta
Always
namedCredentials/[API].namedCredential-meta.xml
Public APIs only — skip for any API requiring credentials
Do NOT generate
externalDataSources/[API].externalDataSource-meta.xml
.
The External Data Source must be registered manually in Setup after deploying the Apex classes. The
sf project deploy start
command cannot deploy custom Apex External Data Sources — the sf CLI will error with "Could not infer a metadata type." Tell the user to go to Setup → External Data Sources → New after deployment.
Correct NamedCredential XML format (no
<name>
element — the API name comes from the filename):
xml
<?xml version="1.0" encoding="UTF-8"?>
<NamedCredential xmlns="http://soap.sforce.com/2006/04/metadata">
    <label>My API</label>
    <endpoint>https://api.example.com</endpoint>
    <allowMergeFieldsInBody>false</allowMergeFieldsInBody>
    <allowMergeFieldsInHeader>false</allowMergeFieldsInHeader>
    <generateAuthorizationHeader>false</generateAuthorizationHeader>
    <principalType>Anonymous</principalType>
    <protocol>NoAuthentication</protocol>
</NamedCredential>
Deploy command:
bash
sf project deploy start \
  --source-dir force-app/main/default/ \
  --target-org [org-alias]
文件说明
classes/[API]DataSourceConnection.cls
+ 元数据
始终需要
classes/[API]DataSourceProvider.cls
+ 元数据
始终需要
namedCredentials/[API].namedCredential-meta.xml
仅公共API —— 任何需要凭据的API均跳过
请勿生成
externalDataSources/[API].externalDataSource-meta.xml
外部数据源必须在部署Apex类后在Setup中手动注册。
sf project deploy start
命令无法部署自定义Apex外部数据源——sf CLI会报错“Could not infer a metadata type.”。请告知用户部署完成后前往Setup → 外部数据源 → 新建。
正确的NamedCredential XML格式(无
<name>
元素——API名称来自文件名):
xml
<?xml version="1.0" encoding="UTF-8"?>
<NamedCredential xmlns="http://soap.sforce.com/2006/04/metadata">
    <label>My API</label>
    <endpoint>https://api.example.com</endpoint>
    <allowMergeFieldsInBody>false</allowMergeFieldsInBody>
    <allowMergeFieldsInHeader>false</allowMergeFieldsInHeader>
    <generateAuthorizationHeader>false</generateAuthorizationHeader>
    <principalType>Anonymous</principalType>
    <protocol>NoAuthentication</protocol>
</NamedCredential>
部署命令:
bash
sf project deploy start \
  --source-dir force-app/main/default/ \
  --target-org [org-alias]

Developer does manually (always — no metadata equivalent exists)

开发者手动操作(始终需要——无等效元数据)

Validate and Sync triggers a live call to the external API to discover its schema and create External Object definitions. This cannot be scripted.
text
Setup → External Data Sources → [your data source] → Validate and Sync
Check the box next to each External Object → Sync
App Launcher → search [External Object name] → confirm records appear
Always end with: "Deployment complete. One step left: open the External Data Source in Salesforce Setup and click Validate and Sync."

Validate and Sync会触发对外部API的实时调用,以发现其架构并创建外部对象定义。此步骤无法脚本化。
text
Setup → 外部数据源 → [你的数据源] → Validate and Sync
勾选每个外部对象旁的复选框 → Sync
应用启动器 → 搜索[外部对象名称] → 确认记录显示
始终以这句话结束:“部署完成。最后一步:在Salesforce Setup中打开外部数据源并点击Validate and Sync。”

Error handling rules

错误处理规则

Never let an exception propagate uncaught out of
query()
— Salesforce shows a generic error to the user with no context.
SituationHandle it by
HTTP 401
throw new DataSource.OAuthTokenExpiredException()
HTTP 429 / rate limitReturn empty rows; log via
System.debug()
HTTP 500 or network errorReturn empty rows; do not rethrow
Null field in response
record.get('field') != null ? (String) record.get('field') : ''
Nested JSON that won't flattenReturn raw JSON string as a TEXT_TYPE field rather than failing the whole query

切勿让异常未经捕获就从
query()
方法中抛出——Salesforce会向用户显示无上下文的通用错误。
场景处理方式
HTTP 401
throw new DataSource.OAuthTokenExpiredException()
HTTP 429 / 速率限制返回空数据行;通过
System.debug()
记录日志
HTTP 500或网络错误返回空数据行;请勿重新抛出异常
响应中存在空字段
record.get('field') != null ? (String) record.get('field') : ''
无法展平的嵌套JSON返回原始JSON字符串作为TEXT_TYPE字段,而非导致整个查询失败

Governor limits

governor限制

  • Max 100 HTTP callouts per transaction — each
    query()
    call is one transaction
  • Heap limit 6 MB (synchronous) — don't deserialize massive payloads; use server-side pagination
  • CPU limit 10 seconds — avoid nested loops over large response arrays

  • 每个事务最多100次HTTP调用——每次
    query()
    调用为一个事务
  • 堆内存限制6 MB(同步)——请勿反序列化超大负载;使用服务器端分页
  • CPU限制10秒——避免对大型响应数组使用嵌套循环

Setup prerequisites — in this exact order

设置前提条件——按以下精确顺序

1. Deploy Apex classes first

1. 先部署Apex类

The
Salesforce Connect: Custom (Developed with Apex)
type only appears in the External Data Source type dropdown after at least one
DataSource.Provider
subclass is deployed in the org.
This is not a permissions issue. It is a dependency — Salesforce discovers available Provider classes at runtime. No deployed Provider = no option in the dropdown.
Correct sequence — always:
  1. Deploy Apex classes first (
    sf project deploy start
    )
  2. Then go to Setup → External Data Sources → New → the Custom Apex type will appear
If the user says "I don't see the Custom Apex option in the dropdown", the answer is: deploy the classes first, then come back to Setup.
仅当组织中至少部署了一个
DataSource.Provider
子类后,
Salesforce Connect: Custom (Developed with Apex)
类型才会显示在外部数据源类型下拉菜单中。
这并非权限问题,而是依赖关系——Salesforce在运行时发现可用的Provider类。未部署Provider类 → 下拉菜单中无此选项。
正确顺序——始终遵循:
  1. 先部署Apex类(
    sf project deploy start
  2. 然后前往Setup → 外部数据源 → 新建 → 自定义Apex类型将显示
若用户表示“我在下拉菜单中看不到自定义Apex选项”,解决方案是:先部署类,再返回Setup操作。

2. Add Remote Site Setting before testing

2. 测试前添加远程站点设置

The external API's base URL must be whitelisted or callouts fail silently —
query()
returns empty rows with no exception, no error message. Deploy as metadata or add at Setup → Remote Site Settings → New.
必须将外部API的基础URL加入白名单,否则调用会静默失败——
query()
返回空数据行,无异常、无错误消息。可作为元数据部署,或在Setup → 远程站点设置 → 新建中添加。

3. After Validate and Sync — set Deployment Status to Deployed

3. Validate and Sync后——将部署状态设置为已部署

After Validate and Sync, Salesforce creates the External Object with status "In Development". Records return 0 results in list views and SOQL until this is changed. No error is shown — another silent failure.
text
Setup → Object Manager → [YourObject]__x → Edit
  Deployment Status → Deployed → Save
Always tell the user this step after Validate and Sync completes.
Validate and Sync完成后,Salesforce创建的外部对象状态为**“开发中”**。在将状态更改前,列表视图和SOQL返回的记录数均为0。无错误提示——又是一个静默失败。
text
Setup → Object Manager → [YourObject]__x → 编辑
  部署状态 → 已部署 → 保存
Validate and Sync完成后,请务必告知用户此步骤。

4. Create a tab to surface the External Object in App Launcher

4. 创建标签以在应用启动器中显示外部对象

External Objects are invisible in the UI without a tab. Create one at:
text
Setup → Tabs → Custom Object Tabs → New → select [YourObject]__x → Save
若无标签,外部对象在UI中不可见。在以下路径创建标签:
text
Setup → 标签 → 自定义对象标签 → 新建 → 选择[YourObject]__x → 保存

Also required

其他必填项

  • Salesforce Connect license — without it, all Connect adapter types including OData are hidden. If the user sees OData and Cross-Org in the dropdown, the license is already active.

  • Salesforce Connect许可证 —— 若无许可证,所有Connect适配器类型(包括OData)都会隐藏。若用户在下拉菜单中看到OData和跨组织选项,则许可证已激活。

Anti-patterns

反模式

Don'tWhyDo
Generate code without knowing the API shapeWrong field types, wrong ExternalId mapping, broken adapterAsk for API name, base URL, auth type, sample response first
Deploy ExternalDataSource as metadataNot a deployable metadata type — sf CLI will errorRegister the External Data Source manually in Setup after deploying the Apex classes
Use
upsertRow()
or
deleteRow()
(singular)
These methods don't exist — compile errorUse
upsertRows()
and
deleteRows()
(plural)
Put
sync()
only on Provider
On some API versions
sync()
must be on Connection — compiler will tell you
Implement
sync()
on Connection; remove from Provider if it errors
Hardcode the API URL in ApexCredentials exposed in source, callout blockedAlways use
callout:NamedCredentialName
Return a single
DataSource.TableResult
from
search()
search()
returns
List<DataSource.TableResult>
— compile error
Loop over
context.tableSelections
and return one result per table
Skip COUNT handling in
query()
List views fire COUNT queries; without detection they return wrong resultsCheck
columnsSelected[0].aggregation == DataSource.QueryAggregation.COUNT
Leave External Object in "In Development"Records return 0 with no error — invisible to the userAfter Validate and Sync: Object Manager → [Object]__x → Edit → Deployed
Skip the Remote Site SettingCallout fires silently, returns empty rows, no exceptionDeploy
remoteSiteSettings/[API].remoteSite-meta.xml
or add manually in Setup

请勿操作原因正确操作
在未知API结构的情况下生成代码字段类型错误、ExternalId映射错误、适配器失效先询问API名称、URL、认证类型、示例响应
将ExternalDataSource作为元数据部署并非可部署的元数据类型——sf CLI会报错部署Apex类后在Setup中手动注册外部数据源
使用
upsertRow()
deleteRow()
(单数形式)
这些方法不存在——编译错误使用
upsertRows()
deleteRows()
(复数形式)
仅在Provider类中实现
sync()
在某些API版本中
sync()
必须在Connection类中实现——编译器会提示错误
在Connection类中实现
sync()
;若报错则从Provider类中移除
在Apex中硬编码API URL凭据暴露在源码中、调用被阻止始终使用
callout:NamedCredentialName
search()
返回单个
DataSource.TableResult
search()
返回
List<DataSource.TableResult>
——编译错误
循环遍历
context.tableSelections
并为每个表返回一个结果
query()
中跳过COUNT处理
列表视图会触发COUNT查询;若未处理则返回错误结果检查
columnsSelected[0].aggregation == DataSource.QueryAggregation.COUNT
让外部对象处于“开发中”状态记录返回0且无错误——对用户不可见Validate and Sync后:Object Manager → [Object]__x → 编辑 → 已部署
跳过远程站点设置调用静默失败,返回空数据行,无异常部署
remoteSiteSettings/[API].remoteSite-meta.xml
或在Setup中手动添加

Before shipping

交付前确认

Confirm: inputs collected (API name, URL, auth, sample response, write support) → code has
sync()
+
query()
+
search()
on Connection,
getCapabilities()
+
getConnection()
on Provider,
ExternalId
and
nameColumn
on every table, COUNT handling in
query()
, mock data for tests,
callout:
prefix on all endpoints → both
.cls-meta.xml
files generated → deploy command given → post-deploy steps communicated (Named Credential setup, Remote Site Setting, External Data Source registered, Validate and Sync, Deployment Status → Deployed, tab created).

确认:已收集输入信息(API名称、URL、认证方式、示例响应、写入支持)→ 代码在Connection类中包含
sync()
+
query()
+
search()
,在Provider类中包含
getCapabilities()
+
getConnection()
,每个表都有
ExternalId
nameColumn
query()
中包含COUNT处理,测试用模拟数据,所有端点使用
callout:
前缀 → 生成了两个
.cls-meta.xml
文件 → 提供了部署命令 → 传达了部署后步骤(Named Credential设置、远程站点设置、外部数据源注册、Validate and Sync、部署状态→已部署、创建标签)。

Reference examples

参考示例

See
references/official-examples.md
for annotated study of the official Salesforce Connect adapter examples — GitHub Issues (full DML, picklist, cross-table relationships), Google Drive (OAuth + test mock), Google Books (pagination), StackOverflow (multiple tables), and Loopback (filter translation). Read before generating for a new user.

查看
references/official-examples.md
获取带注释的官方Salesforce Connect适配器示例——GitHub Issues(完整DML、选择列表、跨表关系)、Google Drive(OAuth+测试模拟)、Google Books(分页)、StackOverflow(多表)和Loopback(过滤器转换)。为新用户生成代码前请阅读这些示例。

Project setup — if the developer has no SFDX project yet

项目设置——若开发者尚无SFDX项目

Before deploying, they need a project structure. If one doesn't exist, generate it first:
bash
sf project generate --name my-adapter --output-dir .
cd my-adapter
This creates
force-app/main/default/classes/
and the required
sfdx-project.json
. All generated files go into this structure. The developer does not need VS Code, Agentforce Vibes, or any Salesforce IDE — just
sf
CLI installed and an org authenticated via
sf org login web --alias my-org
.
This skill works with any coding agent that can read a context file — Claude Code, Cursor, Windsurf, GitHub Copilot, or any agent with the SKILL.md loaded. No Salesforce MCP server, no Agentforce Vibes, no internal Salesforce tooling required.

部署前需要项目结构。若不存在,请先生成:
bash
sf project generate --name my-adapter --output-dir .
cd my-adapter
此命令会创建
force-app/main/default/classes/
和所需的
sfdx-project.json
。所有生成的文件都放入此结构。开发者无需VS Code、Agentforce Vibes或任何Salesforce IDE——只需安装
sf
CLI并通过
sf org login web --alias my-org
认证组织。
本技能可与任何能读取上下文文件的编码代理配合使用——Claude Code、Cursor、Windsurf、GitHub Copilot,或任何加载了SKILL.md的代理。无需Salesforce MCP服务器、Agentforce Vibes或内部Salesforce工具。

Output format

输出格式

Generate files in this order, then give the deploy command:
  1. force-app/main/default/classes/[API]DataSourceConnection.cls
  2. force-app/main/default/classes/[API]DataSourceProvider.cls
  3. force-app/main/default/classes/[API]DataSourceConnection.cls-meta.xml
  4. force-app/main/default/classes/[API]DataSourceProvider.cls-meta.xml
  5. force-app/main/default/namedCredentials/[API].namedCredential-meta.xml
    — public APIs only
One-line comment at top of each Apex class:
// Salesforce Connect custom adapter for [API name]
No other inline comments unless a logic choice is non-obvious.
按以下顺序生成文件,然后提供部署命令:
  1. force-app/main/default/classes/[API]DataSourceConnection.cls
  2. force-app/main/default/classes/[API]DataSourceProvider.cls
  3. force-app/main/default/classes/[API]DataSourceConnection.cls-meta.xml
  4. force-app/main/default/classes/[API]DataSourceProvider.cls-meta.xml
  5. force-app/main/default/namedCredentials/[API].namedCredential-meta.xml
    —— 仅公共API
每个Apex类顶部添加一行注释:
// Salesforce Connect custom adapter for [API name]
除非逻辑选择不明显,否则请勿添加其他内联注释。