Loading...
Loading...
MUST activate when the user wants to package, distribute, or install/upgrade/uninstall/promote a UI Bundle as a Salesforce second-generation (2GP) package (project may contain uiBundles/ or sfdx-project.json for packaging tasks; install/upgrade tasks may lack local bundle files). Handles making a bundle packageable, choosing managed/unlocked flavor, creating package/version with sf package, installing/upgrading in another org, and debugging failures. TRIGGER on packaging, 2GP, managed package, unlocked package, AppExchange, package version, sf package, sf package install, sf package upgrade, install a package, upgrade a package, cross-org distribution. DO NOT TRIGGER for plain source deploy to one org (use experience-ui-bundle-deploy) or scaffolding a new bundle (use experience-ui-bundle-project-generate). Apply piecemeal — do ONLY the part asked.
npx skill4agent add forcedotcom/sf-skills experience-ui-bundle-2gp-deploy<packageDir>/uiBundles/<name>/<packageDir>sfdx-project.jsonforce-app/main/defaultsf package install| The user wants to… | Go to |
|---|---|
| Decide managed vs. unlocked | Choose a flavor |
| Make the bundle packageable / wire a CustomApplication | Part 1 |
| Create the package or a new version | Part 2 |
| Install / upgrade / uninstall / promote | Part 3 |
| Debug a failure | Part 4 + Troubleshooting |
sf package …sf project deploy …sf project generatesf template generate ui-bundleMyReactAppforce-appforce-app/main/default<packageDir><packageDir>[0]packageDir="$(scripts/find-bundle-package-dir.sh <bundleName>)" # walks packageDirectories; picks the entry whose tree contains uiBundles/<bundleName>/sf org listdevhubsubscribersf org list # connected orgs + default Dev Hub
sf org list --json | jq -r '.result.nonScratchOrgs[]?.alias'devhubsubscriber0Ho…04t…05i…08c…0Hf…06y…9YE…9YF…02u…0Zu…0ap…sf package createNOT_FOUNDPackage2sObject type 'Package2' is not supported# clean "0 records" = 2GP ON; "sObject type 'Package2' is not supported" = toggle OFF
sf data query --target-org devhub --use-tooling-api --query "SELECT Id FROM Package2 LIMIT 1"
sf org display --target-org devhub --json | jq '.result.isDevHub'sf package| Managed | Unlocked — namespaced | Unlocked — org-dependent | |
|---|---|---|---|
| Namespace | required | required | none (empty |
| Source visibility | hidden (IP-protected) | visible / editable | visible / editable |
| Installs as | | | bare |
| Coexists with a local same-name bundle | yes (ns-filtered) | yes (ns-filtered) | no — collides |
| Upgrade behavior | clean replace (locked) | replace, overwrites subscriber edits | replace, overwrites subscriber edits |
| Rollback risk on failed upgrade | yes | yes | none |
| Typical use | ISV / AppExchange distribution | org-agnostic sharing, source open | package depends on metadata already in the target org |
namespacesfdx-project.jsonsf package version createns__Name*.salesforce.appsalesforce.comsalesforce.com*.salesforce.app<ns>.salesforce.appgetSourceZip()dist/ns__NameNamesf package install04t…dist/dist/ui-bundle.jsonsfdx-project.jsonnamespace<ns>""# namespaced (managed / unlocked-namespaced): <ns> MUST be registered & linked to this Dev Hub
node -e "const fs=require('fs'),f='sfdx-project.json',j=JSON.parse(fs.readFileSync(f)); j.sourceApiVersion='68.0'; j.namespace='<ns>'; fs.writeFileSync(f,JSON.stringify(j,null,2))"
# org-dependent unlocked: no namespace
node -e "const fs=require('fs'),f='sfdx-project.json',j=JSON.parse(fs.readFileSync(f)); j.sourceApiVersion='68.0'; j.namespace=''; fs.writeFileSync(f,JSON.stringify(j,null,2))"
cat sfdx-project.json # confirm namespace + sourceApiVersion before packagingnamespacenamespace""dist/cd force-app/main/default/uiBundles/MyReactApp # the real bundle dir
npm install --no-audit --no-fund
npm run build
cd -dist/<SKILL_DIR>/assets/CustomApplication.app-meta.xml<SKILL_DIR>MyReactApp<packageDir>/applications/<uiBundle>ns__Namec__Name<uiType>Lightning</uiType><navType>Standard</navType><formFactors>Large</formFactors>mkdir -p force-app/main/default/applications
# then write the substituted template to:
# force-app/main/default/applications/<BundleName>.app-meta.xmlpackage createsf project deploy start --source-dir force-app --target-org devhub --api-version 68.0 --wait 30<SKILL_DIR>/assets/PermissionSet.permissionset-meta.xmlMyReactApp<application>mkdir -p force-app/main/default/permissionsets
# write the substituted template to:
# force-app/main/default/permissionsets/<BundleName>_Access.permissionset-meta.xml
sf project deploy start --source-dir force-app/main/default/permissionsets/MyReactApp_Access.permissionset-meta.xml --target-org devhub --api-version 68.0 --wait 30
sf org assign permset --name MyReactApp_Access --target-org devhubsf package create0Ho…04t…# managed
sf package create --name MyReactApp --package-type Managed --path force-app --target-dev-hub devhub
# unlocked, namespaced (namespace comes from sfdx-project.json)
sf package create --name MyReactApp --package-type Unlocked --path force-app --target-dev-hub devhub
# unlocked, org-dependent (no namespace)
sf package create --name MyReactApp --package-type Unlocked --org-dependent --path force-app --target-dev-hub devhubsf package version create --package MyReactApp --installation-key-bypass --wait 20 --target-dev-hub devhub
# a specific/patch version instead:
sf package version create --package MyReactApp --version-number 1.0.1 --wait 20 --target-dev-hub devhub--version-number 1.0.0.NEXT1.0.1--installation-key-bypass--installation-key <key>--wait08c…REQ=$(sf package version create --package MyReactApp --installation-key-bypass \
--skip-validation --target-dev-hub devhub --json | jq -r '.result.Id')
echo "request: $REQ"
while :; do
J=$(sf package version create report -i "$REQ" --target-dev-hub devhub --json)
ST=$(echo "$J" | jq -r '.result[0].Status'); echo "status: $ST"
case "$ST" in
Success) echo "$J" | jq -r '.result[0].SubscriberPackageVersionId'; break;;
Error) echo "$J" | jq -r '.result[0].Error[]? // "build failed"'; break;;
esac
sleep 30
done--skip-validationsf package version create report -i 08c… --target-dev-hub devhub# fresh install
sf package install --package 04t… --target-org subscriber --wait 10
# add --installation-key <key> if the version was built with one
# add --publish-wait 10 to wait for the version to finish publishing
# upgrade (newer version over the old)
sf package install --package 04t…v2 --target-org subscriber --upgrade-type Mixed --wait 10
# --upgrade-type: Mixed (default) | DeprecateOnly | Delete (destructive — care)
# uninstall
sf package uninstall --package 04t… --target-org subscriber --wait 20
# promote a managed version to released/immutable — runs on the DEV HUB, irreversible
sf package version promote --package 04t… --target-dev-hub devhub--skip-validationsf package install --waitsf package install --package 04t… --target-org subscriber --wait 20 --no-prompt
sf package installed list --target-org subscriber --json \
| jq -r '.result[]? | select(.SubscriberPackageVersionId=="04t…") | .SubscriberPackageVersionId'sf package installed list# Dev Hub state
sf org display --target-org devhub --json | jq '{isDevHub:.result.isDevHub, user:.result.username, instance:.result.instanceUrl, api:.result.apiVersion}'
# Is 2GP on? (the #1 root cause)
sf data query --target-org devhub --use-tooling-api --query "SELECT Id, Name, NamespacePrefix, ContainerOptions FROM Package2"
# Packages & versions on the Dev Hub
sf package list --target-dev-hub devhub
sf package version list --packages MyReactApp --target-dev-hub devhub --verbose
# Version-create failures — status + Error[]
sf package version create list --target-dev-hub devhub
sf package version create report -i 08c… --target-dev-hub devhub
sf data query --target-org devhub --use-tooling-api \
--query "SELECT Id, Status, Package2Id, Error FROM Package2VersionCreateRequest ORDER BY CreatedDate DESC LIMIT 5"
# A version's details
sf package version report --package 04t… --target-dev-hub devhub
# What's installed in the subscriber
sf package installed list --target-org subscriber --json \
| jq -r '.result[]? | "\(.SubscriberPackageName) \(.SubscriberPackageVersionNumber) \(.SubscriberPackageVersionId)"'
# Install/uninstall stuck IN_PROGRESS
sf package install report --request-id 0Hf… --target-org subscriber
sf package uninstall report --request-id 06y… --target-org subscriber
# Deploy failures (before you can even package)
sf project deploy start --source-dir force-app --target-org devhub --dry-run --wait 30
sf project deploy report --target-org devhub
# Bundle renders blank — confirm built assets shipped
ls -la force-app/main/default/uiBundles/MyReactApp/dist| Symptom | Cause / fix |
|---|---|
| 2GP toggle OFF — Setup → Dev Hub → enable "Unlocked & Second-Gen Managed Packages" (manual, no CLI fix). |
| Same — 2GP not provisioned. Enable toggle, re-auth. |
| Cached CLI login — re-auth. Trust the |
| Build queued. Use the async submit + |
| Still IN_PROGRESS server-side. Confirm with |
| "Cannot upgrade beta package" | Beta can't upgrade beta. Promote v0.1 (managed) or uninstall it first, then install v0.2. |
| Namespace error on managed/namespaced build | Namespace not registered/linked to this Dev Hub (App Launcher → Namespace Registries), or switch to org-dependent unlocked (no namespace). |
| App installs but renders blank | Bundle not built before deploy/package — |
| Installed into the wrong org | Wrong alias confirmed in Step 0. Re-check |
| Org-dependent bundle collides with a local one | Both use a bare (null-prefix) name. Use a namespaced flavor, or rename. |
package createpackage version createinstallpromotesf package