SfSkills Domains Docs GitHub

Home / devops / cicd-for-experience-cloud

cicd-for-experience-cloud

CI/CD pipeline patterns for Experience Cloud sites — the layer between sfdx-project metadata deploys and a working production site.

Open the skill folder on GitHub


CI/CD for Experience Cloud

The pipeline layer that turns a versioned ExperienceBundle into a working production site. Generic SFDX CI/CD gets you 80 % of the way there; the last 20 % is Experience-Cloud-specific — metadata ordering rules, theme promotion, guest-user permission-set automation, and the custom-domain binding that lives outside Metadata API entirely.

What this skill is NOT. The shape of ExperienceBundle / DigitalExperienceBundle metadata, the CMS Managed Content exclusion, and the per-bundle deploy mechanics live in devops/experience-cloud-deployment-dev. Generic pipeline design (stage sequence, promotion strategy, quality gates) lives in architect/ci-cd-pipeline-architecture. This skill is the Experience-Cloud-specific pipeline-tooling layer between them.


Before Starting


Core Concepts

The four metadata-ordering rules

Experience Cloud deploys are non-trivially ordered. The pipeline must sequence:

  1. Foundation metadata first. Custom objects, custom fields,

    permission sets, profiles, profile field-level security. The ExperienceBundle references these; if missing, the deploy fails.

  2. Network metadata. Defines the Site / Community itself

    (URL prefix, login behavior, accessibility). Required before any ExperienceBundle that targets it.

  3. ExperienceBundle / DigitalExperienceBundle. The bundle

    itself — pages, components, properties, navigation menus.

  4. BrandingSet and ExperiencePropertyTypeBundle. Theme

    tokens (colors, fonts, logos), component property types. Often shared across multiple sites — promote after the bundles that reference them, but the BrandingSet metadata itself can deploy in the same package.

CMS Managed Content (articles, images, video assets) is outside this ordering — handled separately because it doesn't ship via SFDX.

Guest-user permission-set automation

Every Experience Cloud site has a guest-user profile auto-provisioned when the site is created. Permissions on the guest user control what anonymous visitors can read. The pipeline must:

Salesforce CLI does not have a single "set up the guest user" command. The automation pattern is a small sf-cli wrapper script (or Apex anonymous block) that assigns the relevant permission sets to the site's guest user, idempotent on re-run.

Custom-domain / CDN binding

Production Experience Cloud sites usually live on a custom domain (portal.example.com, not example.my.site.com). Two pieces:

  1. Salesforce-side: Setup → Domains → register the custom domain

    → bind to the site. Metadata-deployable via CustomDomain / CustomSite types but with caveats — the domain registration step requires DNS verification that's not pipeline-automatable on the Salesforce side.

  2. DNS-side: CNAME the custom domain at the Salesforce-supplied

    target (or a CDN that fronts it). Owned by the DNS team, not Salesforce.

The pipeline coordinates these: deploy the Salesforce-side metadata, emit the DNS targets to a known location, the DNS team applies them. Many pipelines stop at "metadata deployed" and miss that the site isn't reachable until DNS propagates — explicit DNS-confirmation gate prevents that.

Theme reuse via BrandingSet

BrandingSet defines tokens (primary color, font family, logo references) that themes consume. A single BrandingSet referenced from multiple sites is the right pattern for multi-site brand consistency. Pipeline implication: BrandingSet promotion is a separate concern from bundle promotion — same metadata package can carry both, but the theme team's promotion cadence often differs from the site-feature-team's cadence.


Common Patterns

Pattern A — GitHub Actions pipeline for a single LWR site

# .github/workflows/experience-cloud-deploy.yml
name: deploy-experience-cloud-site

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: install sf cli
        run: npm install -g @salesforce/cli

      - name: authenticate
        env:
          SFDX_AUTH_URL: ${{ secrets.SFDX_AUTH_URL }}
        run: |
          echo "$SFDX_AUTH_URL" > ./auth.txt
          sf org login sfdx-url --sfdx-url-file ./auth.txt --alias prod
          rm ./auth.txt

      # 1. Foundation: objects/fields/permission-sets the bundle depends on.
      - name: deploy foundation metadata
        run: sf project deploy start
              --source-dir force-app/main/default/objects
              --source-dir force-app/main/default/permissionsets
              --target-org prod

      # 2. Network metadata (defines the site itself).
      - name: deploy network metadata
        run: sf project deploy start --source-dir force-app/main/default/networks --target-org prod

      # 3. The ExperienceBundle / DigitalExperienceBundle itself.
      - name: deploy experience bundle
        run: sf project deploy start --source-dir force-app/main/default/experiences --target-org prod

      # 4. BrandingSets last — they reference bundle artifacts.
      - name: deploy branding sets
        run: sf project deploy start --source-dir force-app/main/default/brandingSets --target-org prod

      # 5. Guest-user permission reconciliation.
      - name: assign guest-user permsets
        run: bash scripts/assign-guest-permsets.sh prod

      # 6. Smoke test — confirm site loads, anonymous read works.
      - name: smoke test
        run: bash scripts/smoke-test-site.sh prod

      # 7. Emit DNS targets for the DNS team (if domain not yet bound).
      - name: emit DNS targets
        run: bash scripts/emit-dns-targets.sh prod > dns-targets.txt
      - uses: actions/upload-artifact@v4
        with:
          name: dns-targets
          path: dns-targets.txt

The seven-step shape: foundation → Network → bundle → BrandingSet → guest-user → smoke → DNS-emit. Drop or reorder a step and the deploy either fails noisily (good) or appears to succeed but leaves the site broken (bad — see Gotchas).

Pattern B — Multi-site theme promotion

When to use. A BrandingSet shared by 4+ sites (corporate brand), promoted on its own cadence (theme team owns it) separate from the per-site feature releases.

Approach. Two pipelines, one repository:

The pipelines are independent; site bundles reference the BrandingSet by API name. Coordination happens via the API name being stable — renaming a BrandingSet is a coordinated migration across every site that consumes it.

Pattern C — Guest-user permission-set baseline validator

When to use. Production refreshes / sandbox copies that reset guest-user permissions. Without a validator, you discover the regression when a customer reports "site says no access".

#!/bin/bash
# scripts/validate-guest-permsets.sh
set -eu
TARGET=$1
EXPECTED_PERMSETS="Site_Public_Read,Site_Lookup_Access"

# Find the site's guest user.
GUEST_USER=$(sf data query --query \
    "SELECT Username FROM User WHERE Profile.Name LIKE '%Site Profile%' AND IsActive = TRUE LIMIT 1" \
    --target-org "$TARGET" --json | jq -r '.result.records[0].Username')

# Check assigned permission sets.
ASSIGNED=$(sf data query --query \
    "SELECT PermissionSet.Name FROM PermissionSetAssignment WHERE Assignee.Username = '$GUEST_USER'" \
    --target-org "$TARGET" --json | jq -r '.result.records[].PermissionSet.Name' | sort | tr '\n' ',')

# Compare against baseline.
for ps in ${EXPECTED_PERMSETS//,/ }; do
    if ! echo "$ASSIGNED" | grep -q "$ps"; then
        echo "MISSING guest permset: $ps"
        exit 1
    fi
done
echo "OK: guest user has all expected permsets"

Run as a CI gate on every promotion. Cheap; catches the regression before customers do.


Decision Guidance

SituationApproachReason
First Experience Cloud pipeline for a new sitePattern A (single-site 7-step pipeline)Standard shape; covers ordering, guest user, DNS coordination
Multi-site brand consistencyPattern B (theme pipeline + per-site pipelines)BrandingSet promotion has a different cadence than site features
Refreshed sandbox loses guest permissionsPattern C validator as CI gateCatches the regression automatically
LWR site (modern, post-API v58)DigitalExperienceBundle deploysLWR-specific bundle type
Aura-based siteExperienceBundle deploysOlder bundle type
Hybrid Aura + LWR site (uncommon)Both bundle types in the packageEach component lives in its own bundle
Custom domain not yet DNS-boundPipeline emits DNS targets; gate on confirmationDon't promote past "metadata deployed" until the site is reachable
CMS Managed Content needed in targetSeparate CMS-content-export step (NOT SFDX)CMS isn't covered by Metadata API
Generic CI/CD pipeline design without Experience-Cloud specificsUse architect/ci-cd-pipeline-architectureDon't reinvent the pipeline layer
Bundle metadata details / shapeUse devops/experience-cloud-deployment-devThis skill is pipelines, not bundle internals

  1. Identify bundle type (Aura ExperienceBundle vs LWR DigitalExperienceBundle). Pipeline source paths and deploy commands differ.
  2. Inventory dependencies (objects, fields, profiles, permission sets, Network metadata) and order them as foundation → network → bundle → branding.
  3. Build the pipeline following Pattern A's seven-step shape.
  4. Add guest-user reconciliation as an explicit step. Don't assume it's automatic.
  5. Add the guest-user permset validator (Pattern C) as a CI gate.
  6. Coordinate DNS with the DNS team — pipeline emits targets, DNS team applies, gate the next promotion on DNS-confirmation.
  7. Plan CMS-content promotion separately if the site uses CMS Managed Content.

Review Checklist


Salesforce-Specific Gotchas

  1. CMS Managed Content is not covered by SFDX. Pipelines that assume "deploy and the content comes too" ship broken sites. (See references/gotchas.md § 1.)
  2. Guest-user permission sets reset on sandbox refresh. Without a CI step that re-applies them, the first guest after refresh sees access errors. (See references/gotchas.md § 2.)
  3. Network metadata must deploy before the ExperienceBundle that targets it. Reverse order produces a deploy that succeeds-but-doesn't-work. (See references/gotchas.md § 3.)
  4. ExperienceBundle (Aura) and DigitalExperienceBundle (LWR) are different metadata types. Pipeline source paths differ; mixing them in a single deploy is fine but the paths and target sites must match. (See references/gotchas.md § 4.)
  5. Custom-domain registration requires DNS verification that's not pipeline-automatable on the Salesforce side. The pipeline must coordinate with the DNS team. (See references/gotchas.md § 5.)
  6. ExperienceBundleSettings must be enabled in the target org before any ExperienceBundle deploys. First-time setup catches this once. (See references/gotchas.md § 6.)
  7. Renaming a BrandingSet API name breaks every site that references it. Treat as a coordinated migration. (See references/gotchas.md § 7.)

Output Artifacts

ArtifactDescription
Pipeline definition.github/workflows/*.yml, Jenkinsfile, GitLab CI YAML, etc. — the seven-step shape
Guest-user assignment scriptsf-cli wrapper that assigns permission sets to the site guest user
Guest-user validatorCI gate that confirms the guest user has the expected permission sets
DNS-target emitterPost-deploy script that surfaces the Salesforce-supplied CNAME target for the DNS team
Smoke-test scriptConfirms the site URL responds 200 and the homepage renders for an anonymous visitor

Package files