The Details panel is now fully customizable. You can rearrange properties via a simple system, or you can fully customize them using Slate UI Programming. You can also add other UI to the details using Slate syntax.
Setup instructions
-
Create a class for customizing properties in. This must inherit from ILayoutDetails.
- You implement one function: void LayoutDetails( IDetailLayoutBuilder& ).
- The purpose of this class is to encapsulate customization for a classes properties. One instance of the class will be created for each Details panel that requires it.
-
Set up a delegate that will be called when the Details panel recognizes properties for a specific class.
- The sole purpose of this delegate is to create an instance of your customization class for a specific UObject that has properties. Remember that there often are multiple details views up at any point and each instance of the details view gets its own customization class instance. This allows you to store per detail instance data on your layout class.
- Example (more examples are located in DetailCustomizations.cpp):
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor"); PropertyModule.RegisterCustomPropertyLayout( ABrush::StaticClass(), FOnGetDetailLayoutInstance::CreateRaw( &FBrushDetails::MakeInstance ) ); ... static TSharedRef<ILayoutDetails> FBrushDetails::MakeInstance() { return MakeShareable( new FBrushDetails ); }
-
Implement your customization in the LayoutDetails function of the class you made in step 1.
- If this is an engine class, you should add your customization class (if it does not already exist) to the
DetailCustomizations
module. This module can be recompiled and reloaded without restarting the editor, making it useful for fast tweaking of properties. - Bind your delegate in
FDetailCustomizationsModule.StartupModule
and unbind it inFDetailCustomizationsModule.ShutdownModule
. - Game specific classes should use a game specific module.
- See examples in this document and the
DetailCustomizations
module (such as PrimitiveComponentDetails.cpp and StaticMeshComponentDetails.cpp).
- If this is an engine class, you should add your customization class (if it does not already exist) to the
Customizing
You handle all customization inside the LayoutDetails function of your customization class. This function accepts an IDetailLayoutBuilder, which is your interface to properties and how you pass back customization widgets.
The primary function of IDetailLayoutBuilder is to create categories where properties and other details reside. There are some other minor functions on this class which are self explanatory (most of them you will not need). The documentation for those can be found in DetailLayoutBuilder.h.
The first step in customizing is to edit a category:
virtual void LayoutDetails( IDetailLayoutBuilder& DetailBuilder ) override
{
// Edit the lighting category
IDetailCategory& LightingCategory = DetailBuilder.EditCategory("Lighting", TEXT("OptionalLocalizedDisplayName") );
}
The EditCategory function takes an FName for the category where properties will reside and an optional localized display name. If the display name is specified, it will override any existing display name. The category name does not have to be the same category name specified in the UPROPERTY
macro, although it will reuse the UPROPERTY
category if the names are the same. The macro category name is used as the default category if the property is not customized and in the tree view.
EditCategory returns an IDetailCategoryBuilder& which is what you use to add properties to a category. There are a two ways to do this:
- Using simple multibox style layout, which is quick for rearranging properties.
- Using Slate syntax which offers the most robust customization options.
Multibox Style Layout
An easy example using the LightingCategory created above:
// Add a property to the category. The first param is the name of the property and the second is an optional display name override.
LightingCategory.AddProperty("bCastStaticShadow", TEXT("Static") );
LightingCategory.AddProperty("bCastDynamicShadow", TEXT("Dynamic") );
LightingCategory.AddProperty("bCastVolumetricTranslucentShadow", TEXT("Volumetric") );
This is the most basic example. It adds 3 properties stacked vertically and overrides their display names. (The text in the examples is not localized to save space, but should always be localized in general practice.)
Note that customized properties and categories always appear above non-customized ones. You can use this simple syntax to reorganize important properties which may otherwise be buried.
The result:

A slightly more advanced example (located in PrimitiveComponentDetails.cpp
):
// Create a non-collapsible group with the display name "Shadows" which is only visible if the CastShadow property is enabled. All properties below this call will appear in the same group until EndGroup or another BeginGroup is called
LightingCategory.BeginGroup( TEXT("Shadows"), GroupImageName, "CastShadow" );
// Begin a new line. All properties below this call will be added to the same line until EndLine() or another BeginLine() is called
LightingCategory.BeginLine();
// Add properties using their default look
LightingCategory.AddProperty("bCastStaticShadow", TEXT("Static") );
LightingCategory.AddProperty("bCastDynamicShadow", TEXT("Dynamic") );
LightingCategory.AddProperty("bCastVolumetricTranslucentShadow", TEXT("Volumetric") );
LightingCategory.BeginLine();
LightingCategory.AddProperty("bCastInsetShadow", TEXT("Inset") );
LightingCategory.AddProperty("bCastHiddenShadow", TEXT("Hidden") );
LightingCategory.AddProperty("bCastShadowAsTwoSided", TEXT("Two Sided") );
LightingCategory.EndGroup();
BeginGroup
is used to create a new group of properties. It takes a name to display for the group, an optional image name (Slate brush name) to display next to the name, and an optional edit condition property which if false will hide the entire group from view so its properties cannot be changed. These edit conditions are the same as theUPROPERTY
macro style edit conditions except they operate on a group of properties instead of just one. More things like this could be added in the future!AddProperty
adds a property using its default look. It usually only needs one parameter which is the property name. More complicated properties such as properties inside structs need additional information. See the Advanced Tips section or the documentation inDetailCategoryBuilder.h
if you need this.BeginLine
creates a new line of properties. By default, all properties added viaAddProperty
are created on a new line.BeginLine
ensures all properties added until the nextBeginLine
orEndLine
are on the same line.
The result:

Notes About Multibox Style Layout
- It is not very powerful, but more features will be added as needed. It is currently designed to be for quick reorganization.
- The slate layout will require more advanced access to properties, specifically property handles, especially if you need to customize their look.
Property Handles
The two main functions of property handles are to read and write the value of a property and to identify the property to Slate customization widgets. How properties are accessed by the details view/property tree is somewhat complicated, so property handles hide all that away and handles undo/redo, pre/post edit change, package dirtying, world switching etc., for you.
To get a property handle, you must ask the IDetailCategory
where you want to customize it for one. You do this by calling IDetailCategory::GetProperty
. Usually you simply pass in the name of the property as follows:
IDetailCategoryBuilder& LightingCategory = DetailBuilder.EditCategory( "Lighting" );
// Get a handle to the "bOverrideLightmapRes" property
TSharedPtr<IPropertyHandle> OverrideLightmapRes = LightingCategory.GetProperty( "bOverrideLightmapRes" );
Now you have a handle to the bool property bOverrideLightmapRes
.
From here, you can read and write the value of that property and/or pass it to a slate widget for customization.
Useful property handle functions (For the complete documented list see PropertyHandle.h):
Function | Description |
---|---|
IPropertyHandle::SetValue(const ValueType& InValue) and IPropertyHandle::GetValue(ValueType& OutValue) |
Writes and reads property values. These are overloaded for many built in types (including vectors and rotators). For complicated types like user structs, will need to get a child handle. See the advanced section at the end of this document. |
ResetToDefault() |
Resets a property to its default. |
IsValidHandle() |
Returns whether or not you have a valid property handle. |
AsArray() |
Array property values are special. See the advanced section at the end of this document. |
Other notes:
- The handle returned from
GetProperty
may be invalid if the property could not be found or is not going to appear in the details view. CheckIsValidHandle()
to be sure. Calling functions on invalid handles will not crash. - You should not store property handles outside of your layout class unless they are weak pointers. Internally the data they access is a weak pointer so it will not crash if you try to set or get a value on an invalid property but you have a reference to a useless object if you store it and it is not cleaned up.
- If you try to read / write access a value type for an unsupported property (e.g., a
float
for aString
property ), it will fail but no data will be corrupted.
Handling failure cases when accessing values.
Remember that detail views can view multiple objects at once and it is not uncommon for users to select hundreds of Actors at once. In cases like these, you will undoubtedly have multiple values for one property. GetValue and SetValue return an FPropertyAccess::Result
to help you determine whether or not accessing a value was successful. FPropertyAccess::MultipleValues
will be a common return value.
/**
* Potential results from accessing the values of properties
*/
namespace FPropertyAccess
{
enum Result
{
/** Multiple values were found so the value could not be read */
MultipleValues,
/** Failed to set or get the value (Property is no longer available, is not a compatible type, or is edit const are the likely cases) */
Fail,
/** Successfully set or got the value */
Success,
};
}
If you are customizing a low level typed property like an int
or float
, you must handle the multiple values case somehow.
INT MyInteger;
// Get the value of the property
FPropertyAccess::Result MyResult = MyIntHandle->GetValue(MyInteger);
If MyResult is FPropertyAccess::MultipleValues
, MyInteger
will not be set. Sending that to a widget that displays it will show garbage value and initializing it before hand is not much better because it is still not the correct value. How this is handled is up to the customizer. For numeric types, using SNumericEntryBox
is recommended which allows you to optionally return no value in its value attribute. It will then display a label you provide instead. See SNumericEntryBox.h
.
Slate Layout
Slate layout allows you to completely customize the look and arrangement of properties. You pass your layout back to a category via IDetailCategoryBuilder::AddWidget
which takes an arbitrary widget from Slate. To assist you in this, there are some customization widgets available to you:
SProperty
This is the customization widget. You use this widget to customize a property and/or embed the property in other slate declarative syntax. You create an SProperty
using SNew
but you also provide a handle to the property so it knows what to build. The handle is a non-optional parameter to SNew
: SNew( SProperty, HandleToTheProperty )
Example:
// Edit the lighting category
IDetailCategoryBuilder& LightingCategory = DetailBuilder.EditCategory( "Lighting" );
// Get a handle to the bOverrideLightmapRes property
TSharedPtr<IPropertyHandle> OverrideLightmapRes = LightingCategory.GetProperty( "bOverrideLightmapRes" );
LightingCategory.AddWidget()
[
SNew( SHorizontalBox )
+ SHorizontalBox::Slot()
[
// Make a new SProperty
SNew( SProperty, EnableOverrideLightmapRes )
]
+ SHorizontalBox::Slot()
.Padding( 4.0f, 0.0f )
.MaxWidth( 50 )
[
SNew( SProperty, LightingCategory.GetProperty("OverriddenLightMapRes") )
.NamePlacement( EPropertyNamePlacement::Hidden ) // Hide the name
]
];
The result:

SProperty
by default will generate a widget for the property. There are some basic customization attributes on SProperty
for customizing the default look (such as the name). If you want to customize a property, you use the CustomWidget
slot. Once you use the CustomWidget
slot, SProperty
no longer knows anything about how to set and get the value since you have made a custom widget. You need to use your property handle to get and set the value.
Example:
// Customizes the OverridenLightMapRes property to display some text and a spinbox
TSharedPtr<IPropertyHandle> LightMapResValue = LightingCategory.GetProperty("OverriddenLightMapRes")
SNew( SProperty, LightMapResValue )
.CustomWidget()
[
SNew( SHorizontalBox )
+ SHorizontalBox::Slot()
.VAlign( VAlign_Center )
.Padding( 2.0f )
[
SNew( STextBlock )
.Text( TEXT("Lightmap Res") )
]
+ SHorizontalBox::Slot()
[
SNew( SSpinBox )
.MinSliderValue( 0 )
.MaxSliderValue( 1024 )
.OnValueCommitted( &SetValueOnProperty )
.Value( &GetValueFromProperty
]
]
...
FLOAT GetValueFromProperty()
{
// Using the property handle created above, get its value and send it to the spinbox
INT Value; // note lightmap res is an integer so it must be accessed as such.
LightMapResValue.GetValue( Value );
// Note HANDLE FAILURE CASES
return Value;
}
void SetValueOnProperty( FLOAT NewValue )
{
// Using the property handle, set its value
LightMapResValue.SetValue( NewValue )
}
SProperty Notes
SProperty
always displays reset to default even if you make a custom widget. There is an argument on theSProperty
which toggles this behavior. For example, if you have a row of properties, you may want to tell it not to display a reset to default for each one but make a big menu at the end. See SResetToDefaultMenu below.- If you pass an invalid handle to
SProperty
, it will simply not show up.
In addition to SProperty
there are some other customization widgets that can be used.
SAssetProperty
SAssetProperty
is an SProperty
that displays a thumbnail of the asset as well as an entry box for changing the asset. You can change the size of the thumbnail as well. You can use this on UObject
properties that have renderable thumbnails. If you use this on other types, it will not display anything.

SFilterableDetail
SFilterableDetail
is a widget that does not draw anything but filters everything in its content slot when a user types in the search box of the details view. This widget is useful for non-property based details. SProperty
is already filtered so you do not need to set up an SFilterableDetail
for those unless you want to group their filtering.
// Create a widget which will filter out everything in the content slot when "Create Blocking Volume" is not matched with a user's search term
// Note: The second parameter is the localized search term that matches the filter and the third parameter is the category where this filter should reside
SNew( SFilterableDetail, NSLOCTEXT("StaticMeshDetails", "BlockingVolumeMenu", "Create Blocking Volume"), &StaticMeshCategory )
.Content()
[
// Create blocking volume menu
SNew( SComboButton )
.ButtonContent()
[
SNew( STextBlock )
.Text( NSLOCTEXT("StaticMeshDetails", "BlockingVolumeMenu", "Create Blocking Volume") )
.Font( IDetailLayoutBuilder::GetDetailFont() )
]
.MenuContent()
[
BlockingVolumeBuilder.MakeWidget()
]
]
SResetToDefaultMenu
SResetToDefaultMenu
is a menu which displays the yellow reset to default arrow. By default, SProperty
adds a reset to default menu, but sometimes it makes sense to group more than one property into the same menu. (e.g Vector
properties). You can add SProperty
widgets to an SResetToDefaultMenu
to handle this for you. Simply call AddProperty
on SResetToDefaultMenu
and then place the menu in any declarative syntax!
SArrayProperty
This widget allows you to customize an array of properties. You create one just like SProperty
and also hook up a delegate which is called each time a widget for an array element is needed.
Example:
void FMeshComponentDetails::LayoutDetails( IDetailLayoutBuilder& DetailLayout )
{
IDetailCategoryBuilder& DetailCategory = DetailLayout.EditCategory("Rendering");
TSharedRef<IPropertyHandle> MaterialProperty = DetailCategory.GetProperty( "Materials" );
DetailCategory.AddWidget()
[
SNew( SArrayProperty, MaterialProperty )
// This delegate is called for each array element to generate a widget for it
.OnGenerateArrayElementWidget( this, &FMeshComponentDetails::OnGenerateElementForMaterials )
];
}
...
/**
* Generates a widget for a materials element
*
* @param ElementProperty A handle to the array element we need to generate
* @param ElementIndex The index of the element we are generating
*/
TSharedRef<SWidget> FMeshComponentDetails::OnGenerateElementForMaterials( TSharedRef<IPropertyHandle> ElementProperty, INT ElementIndex )
{
return
SNew( SAssetProperty, ElementProperty )
.ThumbnailSize( FIntPoint(32,32) );
}
The result:

Customization Dos and Dont's
- Do check error cases when customizing properties and reading/writing values. Remember that details views can often be viewing multiple objects at once where each object has different values. Customized properties should be able to handle the common case of multiple values.
- Do store any data about the selection on your customization class. Some non-property details will need the selected Actors for a customization. You can get the selected Actors from
IDetailLayoutBuilder
. You can store this selection set or anything selection sensitive on your customization class. It is guaranteed to be around while the selection remains the same in its details view. - Do not use
FActorIterator
,FSelectedActorIterator
, orGEditor->GetSelectedActorIterator
. Remember that the Details panel can be locked and these things operate on global selection sets or lists of Actors which is not the same as the selected Actors in a Details panel if it is locked! Using these will access different data. You can get the list of selected Actors valid for you fromIDetailLayoutBuilder
. - Do not hold a strong reference to your layout class or property handles (you should not need to anyway). Remember that details views (especially level editor ones) can change at any time based on user selection so any references you have to layout classes can easily become invalid. The shared pointers to layout classes are checked for uniqueness when customizing details to prevent this from happening.
Advanced Tips
Accessing complicated properties.
A complicated property is defined as anything that cannot be resolved with just a property name. Usually this involves properties inside structs.
There are two ways to access complicated properties:
- Functions that return a property handle or add a property to a category take optional parameters for resolving properties.
Example:
TSharedPtr<IPropertyHandle> IDetailCategoryBuilder::GetProperty( FName PropertyPath, UClass* ClassOutermost , FName InstanceName)
Parameter | Description |
---|---|
Path | The path to the property. Can be just a name of the property or a path in the format outer.outer.value[optional_index_for_static_arrays] . |
ClassOutermost | Optional outer class if accessing a property outside of the current class being customized. |
InstanceName | Optional instance name if multiple UProperty's of the same type exist (Such as two identical structs, the instance name is one of the struct variable names). |
Examples:
struct MyStruct
{
INT StaticArray[3];
FLOAT FloatVar;
}
class MyActor
{
MyStruct Struct1;
MyStruct Struct2;
FLOAT MyFloat
}
- To access
StaticArray
at index2
fromStruct2
inMyActor
, your path would be"MyStruct.StaticArray[2]"
and your instance name is"Struct2"
. - To access the same
StaticArray
outside ofMyActor
customization functions you would do the same as above butClassOutermost
would beMyActor::StaticClass()
. -
To access
MyFloat
inMyActor
you can just pass in"MyFloat"
because the name of the property is unambiguous. - If you have a property handle, you can get a child property handle by name from it:
TSharedPtr<IPropertyHandle> IPropertyHandle::GetChildHandle( FName ChildName )
Parameter | Description |
---|---|
ChildName | The property name of the child. This will recurse until found. Paths are not supported and children of arrays cannot be accessed in this way. |
Accessing Arrays
You can access arrays via IPropertyHandle::AsArray
. If the property handle is an array, this will return an IPropertyHandleArray
which has functions for adding, removing, inserting, duplicating, and getting the number of elements of an array.
Hiding properties
You can hide properties altogether by calling IDetailLayoutBuilder::HideProperty
. It takes either a name/path or a property handle.