Layout Assignments

AttributeTypeDescriptionPackage Types Applicable
ignoreLayoutAssignmentsbooleanStrip UI-magnet metadata (action overrides, object-wide and per-recordType compactLayoutAssignment) from this package's *.object-meta.xml files at build time so the artifact can deploy without depending on UI components owned by another package.
  • source
  • unlocked
  • diff
applyLayoutAssignmentsbooleanRe-applies layout assignments declared in mutators/layout-assignments.yml at deploy time, composing partial CustomObject and Profile metadata using the target org's envelope. Defaults to enabled when the YAML is present; set to false to opt out.
  • source
  • unlocked

In a Flxbl project, the package that owns a CustomObject is rarely the same one that owns the FlexiPages, Lightning record pages and CompactLayouts assigned to it. The CustomObject definition references those UI components by name, which means deploying the object package fails unless the UI package is already in the org. The two flags below split that dependency by stripping the references at build time in the object-owning package and re-applying them at deploy time from the UI package.

How it works

┌──────────────────────┐                          ┌─────────────────────┐
│ Object package       │   build time             │ UI package          │
│ ignoreLayoutAssign-  │  ────── strip ─────►     │ mutators/           │
│ ments: true          │                          │ layout-assignments. │
│                      │                          │ yml                 │
│ Account.object-      │                          │                     │
│ meta.xml             │   deploy time            │ applyLayout-        │
│  (no inline refs)    │  ◄──── compose ────      │ Assignments: true   │
└──────────────────────┘                          └─────────────────────┘
  • Build time — when ignoreLayoutAssignments is true, sfp removes inline <actionOverrides> (Flexipage / Visualforce / LightningComponent), top-level <compactLayoutAssignment> and per-recordType <compactLayoutAssignment> from every *.object-meta.xml in the package. The artifact deploys cleanly even if the UI package isn't yet in the org. The strip log lands in .sfpowerscripts/logs/<package> so build stdout stays tidy.
  • Deploy time — the UI package carries mutators/layout-assignments.yml. At install time, sfp reads the YAML, fetches each object's envelope (label, pluralLabel, nameField, sharingModel, deploymentStatus, recordType active/label) from the target org via REST/Tooling, and composes a partial CustomObject (and partial Profile for layout assignments) that re-applies the references. Because the envelope is sourced from the live org, the deploy never overwrites object metadata.
  • Pull time — when sfp pull retrieves into a package marked with ignoreLayoutAssignments: true, the inline references are stripped again so source-tracking churn doesn't cycle on every pull.

YAML format

The YAML lives at mutators/layout-assignments.yml inside the UI package directory. It is schema-validated; typos and unknown keys cause the deploy to fail loudly rather than silently dropping entries.

# Layout-magnet metadata that this package OWNS but lives on objects
# and profiles defined in other packages. Composed at deploy time using
# the target org's envelope plus the assignments below.

objects:
  Account:
    # Object-wide compact layout (CustomObject's top-level
    # <compactLayoutAssignment>)
    compactLayout: Account_Default_Compact

    # Replaces standard actions on the object
    actionOverrides:
      - actionName: View
        type: Flexipage           # Flexipage | Visualforce | LightningComponent
        content: Account_Record_Page
        formFactor: Large         # optional
        skipRecordTypeSelect: false  # optional

    # Per-recordType compact layout assignments
    recordTypes:
      Business:
        compactLayout: Business_Compact
      Personal:
        compactLayout: Person_Account_Compact

profiles:
  Sales_User:
    # Profile <layoutAssignments>. Deployed as a partial Profile that
    # merges additively — other profile sections (objectPermissions,
    # userPermissions, ...) are untouched.
    layoutAssignments:
      - layout: Account-Business_Layout
        recordType: Account.Business
      - layout: Account-Personal_Layout
        recordType: Account.Personal

Setting up an existing project

Use sfp project layout-assignments extract once per UI package to seed the YAML from inline assignments already in your source. The command refuses to overwrite an existing YAML — pass --force to confirm overwrite.

# Migration: pull existing inline assignments into a UI-owned YAML and
# strip them from the object package's source in one shot.
sfp project layout-assignments extract \
  --source-package src/core-crm \
  --target-package src/ui-crm \
  --strip

After the YAML exists, hand-edit it as the source of truth. Subsequent stripping should happen at build time via ignoreLayoutAssignments rather than re-running the extract command.

Sample sfdx-project.json

{
  "packageDirectories": [
    {
      "path": "src/core-crm",
      "package": "core-crm",
      "versionNumber": "1.0.0.NEXT",
      "type": "source",
      "ignoreLayoutAssignments": true
    },
    {
      "path": "src/ui-crm",
      "package": "ui-crm",
      "versionNumber": "1.0.0.NEXT",
      "type": "source",
      "applyLayoutAssignments": true
    }
  ]
}

What is and isn't handled

ReferenceStrip at buildRe-apply at deploy
<actionOverrides> of type Flexipage / Visualforce / LightningComponent
Object-wide <compactLayoutAssignment>
Per-recordType <compactLayoutAssignment>
Profile <layoutAssignments>sfprofiles reconciler prunes them at deploy✓ (composed as partial Profile)
<actionOverrides> of type Default (no content)n/a — built-in Salesforce behaviourn/a
<recordTypes> definition itself (active, label, picklistValues, businessProcess, ...)n/a — owned by the object packagen/a

If the YAML references an object or recordType that isn't present in the target org, the modifier skips it with a warning rather than failing the deploy. Profile names that don't exist in the target org are likewise skipped.

The deploy-time composer uses the target org's envelope verbatim — label, pluralLabel, nameField, deploymentStatus, sharingModel for the CustomObject and active, label for each recordType. This means re-deploying the partial is idempotent and never overrides the live object's metadata.

On this page