Skip to content

feat: add ExtensionConfig to sandbox create and refresh @W-23860697@ - #1368

Open
apanwar-sf wants to merge 2 commits into
forcedotcom:mainfrom
apanwar-sf:ap/extension-config
Open

apanwar-sf wants to merge 2 commits into
forcedotcom:mainfrom
apanwar-sf:ap/extension-config

Conversation

@apanwar-sf

@apanwar-sf apanwar-sf commented Nov 10, 2026 •

Copy link
Copy Markdown

@W-23860697@

Summary

  • Add an optional ExtensionConfig field (Record<string, JsonMap> | string) to SandboxRequest and SandboxInfo, so callers can pass per-extension sandbox config when creating, cloning or refreshing a sandbox. The field is available from API version 69.0.
  • Serialize ExtensionConfig to JSON in Org.createSandbox and Org.refreshSandbox (mirroring the existing PostCopyConfig handling) before sending it to the Tooling API. It is sent only when the caller provides it.
  • Add Org.querySandboxExtensionConfig(sandboxName), a read-only helper that returns the parsed config from the latest SandboxProcess. It returns undefined when no config was sent or the org or API version doesn't have the field.
  • Add unit tests in test/unit/org/org.test.ts.

Backward compatibility

  • ExtensionConfig is not added to sandboxInfoFields or sandboxProcessFields, so existing queries are unchanged on orgs and API versions before 69.0.
  • Without ExtensionConfig in the definition file, requests are the same as today on every org and API version.
  • Refresh never sends back config stored by an earlier operation. The server rejects config for an extension that doesn't support the operation (for example, Marketing Cloud supports create only), so the read helper returns only the config, not a SandboxInfo.
  • Existing PostCopyConfig code is unchanged.

Test plan

  • yarn test passes locally (1170 passing).
  • New tests cover createSandbox/refreshSandbox (config as an object, as a string, and absent), check that ExtensionConfig isn't added to the existing queries, and cover querySandboxExtensionConfig (parsed, null, no records, INVALID_FIELD, other errors re-thrown).

create sandbox

Example sandbox definition file:
{
"SandboxName": "MCDev266",
"LicenseType": "Developer",
"ExtensionConfig": { "marketing_cloud": { "marketing_objects": ["mc_object_1", "mc_object_2"] } }
}
Screenshot 2026-11-10 at 6 07 36 PM

refresh sandbox

Exampe of sandbox definiton file:
{
"SandboxName": "anukt1",
"LicenseType": "Developer",
"ExtensionConfig": {
"commerce_cloud": {
"message": "⚠️ This refresh will break the trust relationship with eCommerce instance: www.mycustomcctrusturl.com",
"acknowledged": true
}
}
}
Screenshot 2026-11-10 at 6 46 39 PM

Create sandbox without extension config showing its optional

Screenshot 2026-11-10 at 6 54 02 PM

@apanwar-sf
apanwar-sf requested a review from a team as a code owner November 10, 2026 06:29
@salesforce-cla

Copy link
Copy Markdown

Thanks for the contribution! It looks like @apanwar-sf is an internal user so signing the CLA is not required. However, we need to confirm this.

Comment thread src/org/org.ts Resolved

@wmorrow-sf wmorrow-sf left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sends ExtensionConfig only when the caller provides it, serializing an
object to JSON as done for PostCopyConfig. The field is not added to the
SandboxInfo or SandboxProcess query fields, so requests to orgs and API
versions without it (before 69.0) are unchanged.

Adds querySandboxExtensionConfig, which reads ExtensionConfig from the
latest SandboxProcess and returns it parsed, or undefined when none was
sent or the org or API version doesn't have the field. It throws
NoRecords when no SandboxProcess is found, like querySandboxInfo. It
returns the config rather than a SandboxInfo, so it isn't sent back by
a later refresh.
WillieRuemmele
WillieRuemmele previously approved these changes Nov 12, 2026
Comment thread src/org/org.ts Outdated
const soql = `SELECT ExtensionConfig FROM SandboxProcess WHERE SandboxName='${sandboxName}' AND Status != 'D' ORDER BY CreatedDate DESC LIMIT 1`;
let records: Array<{ ExtensionConfig?: string | null }>;
try {
records = (await this.connection.tooling.query<{ ExtensionConfig?: string | null }>(soql)).records;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could connection.singleRecordQuery(...,{tooling:true}) which could simplify some of the error handling below

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, thanks. I will switch to singleRecordQuery, so the NoRecords check now comes from there. I kept the catch, because the INVALID_FIELD case (orgs before API 69.0) still returns undefined.

Comment thread src/org/org.ts Resolved
…60697@

Uses connection.singleRecordQuery with tooling, so NoRecords comes from
there instead of a manual check. Notes that the API returns
ExtensionConfig as a JSON string, and adds a test with config for more
than one extension.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants