Skip to content

Fetching Custom Configuration Metadata from Salesforce with sf CLI

How to retrieve CustomObject, CustomField, Layout, FlexiPage, RecordType, and CompactLayout metadata from Salesforce using sf CLI: source format, MDAPI format, and package.xml.

This article is part of the Salesforce Admin Git & sf CLI series, a complete guide to version control and metadata management for Salesforce admins. Articles are standalone; no need to read in order.

TL;DR

  • Retrieve all custom objects: sf project retrieve start --metadata CustomObject --target-org myorg: pulls every CustomObject in the org into force-app/main/default/objects/
  • Retrieve one specific component: sf project retrieve start --metadata "CustomObject:Account" --target-org myorg: the colon separates the type name from the component name
  • List before you retrieve: sf org list metadata --metadata-type CustomObject --target-org myorg: shows every component's exact API name, which you need for the colon syntax
  • Source format vs MDAPI format: source format (the default) writes one file per field, one file per record type. Much cleaner for git diffs. MDAPI format dumps one big XML file per object.

What You'll Learn

  • How to retrieve CustomObject, CustomField, Layout, FlexiPage, RecordType, and CompactLayout using sf CLI
  • How to list what components are in your org before retrieving
  • The difference between source format and MDAPI format
  • How to retrieve packaged vs unpackaged metadata
  • How to use package.xml to retrieve multiple types at once
  • What the retrieved files look like on disk

The Problem

Your Salesforce org has dozens of custom objects, fields, and page layouts. You want to save them to version control. Where do you start?

You know you need to use sf CLI. You know there's a retrieve command somewhere. But what you're missing is the exact syntax, the right metadata type names, and an understanding of what actually lands on your hard drive after you run the command.

This article walks through all of it. By the end you'll be able to pull CustomObject, CustomField, Layout, FlexiPage, RecordType, and CompactLayout metadata out of your org and into files you can commit to git.

Common questions this article answers:

  • How do I retrieve a CustomObject with sf CLI?
  • How do I get just one field rather than the whole object?
  • What does force-app/main/default/objects/ look like after retrieval?
  • How do I retrieve Layout or FlexiPage metadata?
  • What is the difference between source format and MDAPI format?

Quick Answer

Three commands cover most situations:

# 1. List what's in your org (get the exact API names)
sf org list metadata --metadata-type CustomObject --target-org myorg

# 2. Retrieve all custom objects
sf project retrieve start --metadata CustomObject --target-org myorg

# 3. Retrieve one specific object by name
sf project retrieve start --metadata "CustomObject:MyObject__c" --target-org myorg

The --target-org flag takes the alias you set when you connected your org (Article 3 of this series covers that). If you only have one org connected you may be able to omit it, but it is safer to always include it.

These commands work identically on Windows and macOS. sf CLI handles the path differences for you.

Section 1: List Available Components Before Retrieving

Before you retrieve anything, run a list command. This gives you two things: confirmation that the metadata type exists in your org, and the exact API names you need for the colon syntax in retrieve commands.

# List all custom objects in the org
sf org list metadata --metadata-type CustomObject --target-org myorg

# List all custom fields (across all objects)
sf org list metadata --metadata-type CustomField --target-org myorg

# List all layouts
sf org list metadata --metadata-type Layout --target-org myorg

# List FlexiPages (Lightning App Builder pages)
sf org list metadata --metadata-type FlexiPage --target-org myorg

# See every metadata type available in the org
sf org list metadata-types --target-org myorg

The output looks something like this for CustomObject:

=== CustomObjects
 FULL NAME              NAMESPACE PREFIX  TYPE
 ─────────────────────  ────────────────  ────────────
 Account                                  CustomObject
 Contact                                  CustomObject
 MyObject__c                              CustomObject
 AnotherObject__c                         CustomObject

The FULL NAME column is what matters. That value (MyObject__c, Account, etc.) is exactly what you pass after the colon in a retrieve command.

A few things worth knowing about the naming patterns:

  • CustomObject: The full name is the object API name: MyObject__c for custom objects, Account for standard objects that have been customised.
  • CustomField: The full name is ObjectName__c.FieldName__c, the object and field joined with a dot. For example: MyObject__c.Status__c.
  • Layout: The full name is ObjectName-Layout Name, object and layout name joined with a hyphen. For example: Account-Account Layout.
  • FlexiPage: The full name is just the page API name: MyHomePage, My_Record_Page, etc.

Knowing these patterns means you can often guess the right full name without running the list command first. When in doubt, list first.

Section 2: Retrieve in Source Format (Default, Recommended)

Source format is the default output format for sf project retrieve start. Files land in force-app/main/default/ in a structured folder tree with one file per sub-component. This is the format you want for version control: git diffs are clean and readable.

# Retrieve all custom objects (every CustomObject in the org)
sf project retrieve start --metadata CustomObject --target-org myorg

# Retrieve a specific object
sf project retrieve start --metadata "CustomObject:Account" --target-org myorg

# Retrieve a specific custom field
sf project retrieve start --metadata "CustomField:MyObject__c.MyField__c" --target-org myorg

# Retrieve all Layout metadata
sf project retrieve start --metadata Layout --target-org myorg

# Retrieve a specific layout
sf project retrieve start --metadata "Layout:Account-Account Layout" --target-org myorg

# Retrieve all FlexiPages (Lightning App Builder pages)
sf project retrieve start --metadata FlexiPage --target-org myorg

# Retrieve a specific FlexiPage
sf project retrieve start --metadata "FlexiPage:MyPage" --target-org myorg

# Retrieve all RecordType metadata
sf project retrieve start --metadata RecordType --target-org myorg

# Retrieve all CompactLayout metadata
sf project retrieve start --metadata CompactLayout --target-org myorg

# Retrieve multiple types in one command
sf project retrieve start --metadata CustomObject CustomField Layout FlexiPage --target-org myorg

The pattern is consistent throughout:

  • --metadata TypeName retrieves all components of that type.
  • --metadata "TypeName:ComponentName" retrieves one specific component. The colon : separates the type name from the component's full name.
  • Multiple types are space-separated: --metadata CustomObject CustomField Layout.

When a retrieve completes, sf CLI prints a summary of what was written to disk. If you see zero files written and no error, it usually means the org has no components of that type. Run the list command to confirm.

Section 3: Retrieve in MDAPI Format

MDAPI format produces the older Metadata API structure: a flat folder of files plus a package.xml manifest. You rarely need this for day-to-day version control work, but it is useful if a downstream tool (a legacy CI pipeline, a third-party deployment tool) expects the traditional format.

# Retrieve CustomObject in MDAPI format
sf project retrieve start --metadata CustomObject --target-org myorg --target-metadata-dir mdapi-output --unzip

# Retrieve Layout in MDAPI format
sf project retrieve start --metadata Layout --target-org myorg --target-metadata-dir mdapi-output --unzip

The --target-metadata-dir flag tells sf CLI to produce a ZIP file containing the MDAPI format structure in the specified folder. The --unzip flag auto-extracts the ZIP so you get the raw files immediately. The folder is created if it does not exist.

Note: --output-dir is a different flag. It changes where source format files land. It does NOT produce MDAPI format.

Source format vs MDAPI format: the practical difference:

Source Format MDAPI Format
Output location force-app/main/default/ Specified by --target-metadata-dir
Object fields One .field-meta.xml file per field All fields inside one MyObject__c.object file
Record types One .recordType-meta.xml per record type All record types inside the .object file
Git diffs Clean: one change = one changed file Noisy: any change touches the whole .object file
Tool compatibility Salesforce DX tools, VS Code extension Legacy CI tools, older Ant-based deployments

For version control purposes, source format wins every time. MDAPI format was the standard before Salesforce DX. It is still valid and still deployed, but source format is what you want in git.

Section 4: Retrieve Using package.xml (Manifest-Based)

If you need to retrieve multiple types repeatedly (for example, every time you sync a sandbox) the cleanest approach is a package.xml manifest file that lists everything you want. Then you run one command and it retrieves the lot.

# Retrieve everything specified in package.xml
sf project retrieve start --manifest package.xml --target-org myorg

Here is a complete package.xml for custom configuration metadata:

<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members>*</members>
        <name>CustomObject</name>
    </types>
    <types>
        <members>*</members>
        <name>CustomField</name>
    </types>
    <types>
        <members>*</members>
        <name>Layout</name>
    </types>
    <types>
        <members>*</members>
        <name>FlexiPage</name>
    </types>
    <types>
        <members>*</members>
        <name>RecordType</name>
    </types>
    <types>
        <members>*</members>
        <name>CompactLayout</name>
    </types>
    <version>62.0</version>
</Package>

Save this as package.xml in the root of your project (next to sfdx-project.json), then run the retrieve command above.

A few things to know about package.xml:

  • <members>*</members> means "all components of this type." The wildcard * retrieves everything.
  • To retrieve just one component, replace * with the full name: <members>Account</members> retrieves only the Account object.
  • The <version> tag should match your org's API version. Check sfdx-project.json for the sourceApiVersion value: that is the version sf CLI is using.
  • You can mix wildcards and specific names in the same <types> block by adding multiple <members> tags.

package.xml is particularly useful when you hand off the project to a teammate or set up a CI job. Anyone who runs sf project retrieve start --manifest package.xml --target-org myorg gets exactly the same set of metadata, every time.

Section 5: Retrieve Packaged Metadata

If your org has installed packages (managed or unmanaged), those components also live in the org and can be retrieved.

# Retrieve using a package.xml that references packaged components
sf project retrieve start --manifest package.xml --target-org myorg

To retrieve components from a specific package, reference them by their full name including the namespace prefix in your package.xml:

<types>
    <members>mypkg__MyObject__c</members>
    <name>CustomObject</name>
</types>

Important caveat about managed packages: Managed package metadata (components with a namespace prefix like mypkg__MyObject__c) is read-only. You can retrieve it to inspect it, read the field definitions, understand how it is structured. But you cannot modify it and deploy it back. Salesforce will reject any attempt to overwrite managed package metadata.

The practical rule: retrieve managed package metadata only when you need to understand it. Never commit it to your version-controlled source directory as if it were your own configuration. If it accidentally ends up in your repo, remove it. It will cause confusion and deployment failures.

Section 6: Preview Before Retrieving

Before running a large retrieve, you can see exactly what would happen without actually writing any files:

# See what WOULD be retrieved without actually retrieving it
sf project retrieve preview --target-org myorg

The preview command shows which files would be added, updated, or deleted locally compared to what is in the org. This is useful when you are about to run a broad retrieve (like all CustomObjects) and want to make sure you are not about to overwrite local changes you have not committed yet.

Think of it as a dry run: same logic, no file writes.

Section 7: What the Files Look Like on Disk

After retrieving in source format, your project folder looks like this:

force-app/
└── main/
    └── default/
        ├── objects/
        │   ├── Account/
        │   │   ├── Account.object-meta.xml          ← object definition
        │   │   ├── fields/
        │   │   │   ├── MyField__c.field-meta.xml    ← one file per field
        │   │   │   └── AnotherField__c.field-meta.xml
        │   │   ├── recordTypes/
        │   │   │   └── MyRecordType.recordType-meta.xml
        │   │   ├── compactLayouts/
        │   │   │   └── MyCompactLayout.compactLayout-meta.xml
        │   │   └── listViews/
        │   │       └── All.listView-meta.xml
        ├── layouts/
        │   └── Account-Account Layout.layout-meta.xml
        └── flexipages/
            └── MyPage.flexipage-meta.xml

Each sub-component gets its own file. That is the whole point of source format: when someone changes a field, only that field's file changes in git. The diff shows exactly what the label was before and what it is now, which validation rules changed, whether the field became required. None of that is visible in MDAPI format where one field change means a diff across a 1,000-line XML blob.

A few details about the structure:

  • Account.object-meta.xml contains the object-level settings: API name, label, sharing model, search layouts. It does not contain the fields.
  • Each field lives in fields/FieldName__c.field-meta.xml: the field's type, label, length, picklist values, validation, everything.
  • Record types, compact layouts, and list views each get their own subfolder following the same pattern.
  • Layouts live in layouts/ at the top level, not inside the object folder. They are a separate metadata type.
  • FlexiPages live in flexipages/: one file per App Builder page.

Troubleshooting

"No such metadata type: CustomObject" The type name is case-sensitive in the Metadata API. CustomObject is correct; customobject or Custom Object will fail. Run sf org list metadata-types --target-org myorg to see the exact names your org supports.

"Component not found" The component name after the colon must match exactly what sf org list metadata returned, including capitalisation, namespace prefix, and any dots or hyphens. Run the list command first and copy the full name directly from the output.

Retrieve succeeds but produces no files This happens when the org has no components of that type, or your project's ignore rules are filtering the output. Check with sf org list metadata --metadata-type <Type> --target-org myorg first to confirm components exist.

--target-metadata-dir creates an empty folder Same cause: no components matched. Confirm with the list command before retrieving.

Managed package fields retrieved but a read-only warning appears This is expected behaviour. Salesforce flags managed metadata as read-only during retrieve. Retrieve it to inspect it if you need to, but do not commit it as your own source. It cannot be deployed.

Frequently Asked Questions

How do I retrieve just one custom field, not the whole object?

Use --metadata "CustomField:ObjectName__c.FieldName__c". The API name of a custom field always includes the object name, dot-separated. Get the exact name from sf org list metadata --metadata-type CustomField --target-org myorg and copy it directly from the output.

What Salesforce API version does sf CLI use?

sf CLI uses the API version set in your sfdx-project.json file. Look for the sourceApiVersion key. The package.xml example above uses 62.0; update it to match your org's version. If you are unsure what version your org is on, check Setup → Company Information → Salesforce.com Organization ID page or run sf org display --target-org myorg and check the instance URL.

Should I commit everything that gets retrieved?

No. Retrieve is a read operation: it pulls everything Salesforce decides to return for the types you specified. Only commit files that represent your team's customisations. If you retrieved all CustomObjects to see what is there, stage only the objects your team owns. Leave standard objects and managed package objects out of your committed source unless you have a specific reason to track them.

What is the difference between CustomObject and CustomField in the Metadata API?

They are separate metadata types. Retrieving CustomObject gives you the object shell (API name, label, sharing model, search layouts) but not the fields. Retrieving CustomField gives you the fields. To get a complete picture of an object, include both types in your retrieve command or package.xml.

What about standard fields: can I retrieve those?

Standard fields (like Account.Name, Account.Phone) are not retrievable as metadata. They are part of Salesforce core and are not exposed through the Metadata API as independent components. Only custom fields with the __c suffix are retrievable.

Key Takeaways

  • List first: Run sf org list metadata --metadata-type <Type> --target-org myorg before retrieving to get exact component API names. You need these for the colon syntax.
  • Source format is the default: sf project retrieve start writes granular files to force-app/main/default/, better for git diffs than MDAPI format.
  • MDAPI format when needed: Add --target-metadata-dir folder --unzip to get the flat package.xml + single-file-per-object structure for legacy tooling.
  • Type:Name syntax: --metadata "CustomObject:Account" retrieves one component; --metadata CustomObject retrieves all components of that type.
  • package.xml for repeatable retrieves: --manifest package.xml retrieves everything listed in the manifest. The right approach for CI pipelines and regular syncs.
  • Managed package metadata is read-only: Retrieve it to inspect, never commit it as your own source.

What's Next?

This is Article 4 of the Salesforce Admin Git & sf CLI series.

Recommended Reading:

Action Items:

  1. Run sf org list metadata --metadata-type CustomObject --target-org myorg to see what objects are in your org
  2. Retrieve one object: sf project retrieve start --metadata "CustomObject:YourObject__c" --target-org myorg
  3. Open the retrieved files in VS Code and inspect the XML

Resources & References


About This Guide: Part of the Salesforce Admin Git & sf CLI series. Each article stands alone.

Tags: #salesforce #sfcli #metadata #versioncontrol #salesforceadmin