The FieldSet metadata type represents an administrator-configurable grouping of fields on a standard or custom object. Introduced in API version 21.0, FieldSet is one of the most versatile architectural patterns for building zero-code-change UI and integration components: developers write an ApexClass or LightningComponentBundle once that reads a FieldSet by name, and administrators can add, remove, or reorder fields in Setup without modifying a single line of Apex or JavaScript.
In Salesforce DX projects, field sets live inside their parent object folder at force-app/main/default/objects/<ObjectName>/fieldSets/<FieldSetName>.fieldSet-meta.xml.
Key Metadata Fields
| Field | Type | Required | Description |
|---|---|---|---|
fullName |
string |
No | Developer API name of the field set |
label |
string |
Yes | Display name in Object Manager -> Field Sets |
description |
string |
Yes | Required. Explains to administrators where this field set is used (shown directly in the Setup drag-and-drop editor) |
displayedFields |
FieldSetItem[] |
No | Ordered list of active fields (field, isFieldManaged, isRequired) |
availableFields |
FieldSetItem[] |
No | Pool of optional fields exposed by managed packages for subscriber admins to drag into displayedFields |
Production-Grade XML Example
Below is a FieldSet on OpportunityLineItem (force-app/main/default/objects/OpportunityLineItem/fieldSets/Fast_Quote_Grid.fieldSet-meta.xml) that includes both direct fields and a cross-object Product2 relationship field:
<?xml version="1.0" encoding="UTF-8"?>
<FieldSet xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Fast_Quote_Grid</fullName>
<availableFields>
<field>Description</field>
<isFieldManaged>false</isFieldManaged>
<isRequired>false</isRequired>
</availableFields>
<description>Controls the columns displayed in the Fast Quote Grid LWC on the Opportunity record page.</description>
<displayedFields>
<field>Product2.Name</field>
<isFieldManaged>true</isFieldManaged>
<isRequired>false</isRequired>
</displayedFields>
<displayedFields>
<field>Quantity</field>
<isFieldManaged>false</isFieldManaged>
<isRequired>true</isRequired>
</displayedFields>
<displayedFields>
<field>UnitPrice</field>
<isFieldManaged>false</isFieldManaged>
<isRequired>true</isRequired>
</displayedFields>
<displayedFields>
<field>Discount</field>
<isFieldManaged>false</isFieldManaged>
<isRequired>false</isRequired>
</displayedFields>
<label>Fast Quote Grid</label>
</FieldSet>Consuming FieldSet Dynamically in Apex for LWC
Here is a reusable Apex pattern that converts any FieldSet into lightning-datatable column metadata and executes a secure WITH USER_MODE dynamic SOQL query:
public with sharing class DynamicFieldSetController {
public class FieldMemberDTO {
@AuraEnabled public String fieldPath;
@AuraEnabled public String label;
@AuraEnabled public String type;
@AuraEnabled public Boolean required;
}
@AuraEnabled(cacheable=true)
public static List<FieldMemberDTO> getFieldSetMetadata(String objectApiName, String fieldSetName) {
Schema.SObjectType targetType = Schema.getGlobalDescribe().get(objectApiName);
Schema.FieldSet fs = targetType.getDescribe().fieldSets.getMap().get(fieldSetName);
List<FieldMemberDTO> result = new List<FieldMemberDTO>();
for (Schema.FieldSetMember member : fs.getFields()) {
FieldMemberDTO dto = new FieldMemberDTO();
dto.fieldPath = member.getFieldPath();
dto.label = member.getLabel();
dto.type = String.valueOf(member.getType());
dto.required = member.getRequired() || member.getDbRequired();
result.add(dto);
}
return result;
}
}Tip
isFieldManaged in Managed Packages: When authoring an ISV managed package, setting <isFieldManaged>true</isFieldManaged> on a <displayedFields> entry locks that field so subscriber administrators cannot remove it from the FieldSet, while still allowing them to append their own custom fields after it.
Frequently asked questions
What is the difference between displayedFields and availableFields in a FieldSet?
Fields listed in displayedFields are active in the FieldSet by default and returned by Schema.FieldSet.getFields() in Apex. Fields in availableFields are packaged by an ISV developer as optional fields that a subscriber admin can drag into the active FieldSet without creating a new package version.
How do I read a FieldSet in Apex to build a dynamic SOQL query or LWC column list?
Use SObjectType.ObjectName.fieldSets.getMap().get('FieldSet_API_Name').getFields() to iterate over Schema.FieldSetMember instances, which provide getFieldPath(), getLabel(), getType(), and getRequired().
Can a FieldSet include parent relationship fields like Account.Name on an Opportunity FieldSet?
Yes. FieldSets support spanning one level up standard or custom lookup/master-detail relationships using dot notation, such as Account.Name or Account.BillingCountry, inside the field tag.
Where are FieldSet metadata files stored in SFDX source format?
In SFDX source format, FieldSet files are decomposed inside the parent object directory at force-app/main/default/objects/ObjectName/fieldSets/FieldSetName.fieldSet-meta.xml.
Does a FieldSet automatically enforce Field-Level Security (FLS) in custom LWC or Apex?
If you pass FieldSet field names into lightning-record-form or lightning-input-field in LWC, Lightning Data Service enforces FLS automatically. If you build a dynamic SOQL query in Apex from a FieldSet, you must enforce FLS using WITH USER_MODE or Security.stripInaccessible.
Comments
Questions, corrections and tips are welcome. Comments are GitHub Discussions, so you sign in with GitHub.