Which UCLASS specifiers gate Blueprint visibility, why a Blueprint is a class and a Data Asset is an instance, and the ownership specifiers — EditInlineNew, Instanced, DefaultToInstanced — that decide whether a UObject property is shared or owned.
Four specifiers control how a C++ class is exposed to Blueprints and the editor.
| Specifier | Controls | Notes |
|---|---|---|
Blueprintable / NotBlueprintable | Whether a Blueprint class can derive from this C++ class | Inherited by subclasses, which is why it's rarely written explicitly on Actor classes — AActor and UActorComponent already declare it. Use NotBlueprintable to cut off inheritance for a C++-only branch (managers, subsystems, pure data plumbing). |
BlueprintType / NotBlueprintType | Whether the class can be used as a value — variable type, function parameter, array element type, cast target | Independent of Blueprintable. Best declared explicitly on each class meant to be usable, rather than relying on inheritance. NotBlueprintType hides a subclass from type pickers. |
Abstract | Makes the class non-instantiable | Won't appear as a spawnable in Place Actors or class pickers; SpawnActor on it fails. Use for a base class that defines an interface but has no meaningful default behavior. |
Const | Marks every property and function on the class as const for the reflection/scripting layer | Legacy. Enforces nothing in C++ and is unrelated to BlueprintPure. Rarely seen outside a few engine internals; safe to ignore in modern code. |
The four Blueprintable / BlueprintType combinations:
| Combination | Meaning |
|---|---|
Blueprintable, BlueprintType | Subclass it and pass it around — the typical gameplay base class |
Blueprintable only | Designers extend it, but it isn't a variable type |
BlueprintType only | Designers reference instances but can't author subclasses — config/data objects |
| Neither | Invisible to Blueprint entirely |
UCLASS(Abstract, Blueprintable, BlueprintType)
class UAbilityBase : public UObject
{
GENERATED_BODY()
};
Everything in this chapter follows from one fact.
A class — a template for making things. Nothing exists until something spawns or instantiates it.
An instance — an actual object, saved to disk. It doesn't make more things; it is the thing.
This single difference drives referencing, inheritance, mutation safety, and loading behavior, covered section by section below.
A Blueprint normally has two parts: a graph (logic) and default values (Details panel). A data-only Blueprint is a Blueprint where the graph was never touched — just a child class with changed defaults. Unreal detects this automatically and shows a stripped-down editor window (Details panel only, with an "Open Full Blueprint Editor" link). It is not a distinct asset type or a checkbox — it's a normal Blueprint that happens to be empty of logic, and the check is re-evaluated every time the editor opens it.
What flips it out of data-only status:
Special case: an Actor Blueprint with only the automatic DefaultSceneRoot still counts as data-only — the engine ignores that node since it wasn't added by the user. The check is stateless and reversible in both directions — delete the component/node/variable and it becomes data-only again.
Every class has a hidden singleton called the Class Default Object (CDO), auto-created at load (Chapter 6). Editing the Details panel of a data-only Blueprint edits its CDO. Reading a data-only Blueprint's values without spawning anything goes through the CDO:
const UMyWeapon* Defaults = GetDefault<UMyWeapon>(WeaponClass);
float Damage = Defaults->Damage;
A Data Asset has no such indirection — the object already exists:
float Damage = WeaponData->Damage;
This also shapes how the reference is stored:
TSubclassOf<AMyWeapon> WeaponClass; // reference to a TYPE (Blueprint)
TObjectPtr<UWeaponData> WeaponData; // reference to an OBJECT (Data Asset)
Blueprints inherit. Data Assets do not. Base weapon Blueprint: Damage = 10, Range = 1000. Children Pistol/Rifle/Shotgun. Rifle overrides Damage = 25, leaves Range alone. Change Range on the base to 2000 → all three children that never overrode it pick up the new value automatically. The editor shows a revert arrow next to any property that diverges from its parent.
Data Assets are flat and standalone. Fifty weapon Data Assets means fifty separate edits to change one shared value (or a script, or multi-select property matrix editing) — there is no parent to change. Composition can simulate inheritance: give the Data Asset a pointer to a shared "archetype" asset and fall back to it in code when a field is unset. This is hand-built and needs fallback logic wherever the value is read.
A Data Asset is one shared object in memory. If ten enemies reference DA_GoblinStats and one writes to it, all ten see the change — and in the editor, that write dirties the asset on disk, risking gameplay state leaking into source data. Treat Data Assets as read-only at runtime; copy values out into the actor/component for per-instance state.
Spawning from a Blueprint class is safe by construction — each spawned actor is its own object with its own copy of the values.
Data-only Blueprints can silently grow logic — add one node and it's no longer data-only, with no warning or gate. Convenient for quick designer overrides, risky for "just data" assumptions elsewhere in the codebase. This is a team-discipline problem, not something the engine prevents.
Data Assets structurally cannot hold logic through the normal workflow (barring an unusual Blueprint subclass of a DataAsset class). This makes the data/logic split enforced by the tooling rather than by convention.
Data Assets are the lighter option. A Blueprint drags in a UBlueprintGeneratedClass, its CDO, its component hierarchy (if an Actor), and every hard reference in its defaults — so a data-only weapon Blueprint with an assigned mesh/sound pulls those into memory on class load (Chapter 7 covers why hard references pull their target in).
UPrimaryDataAsset is the intended hook into the Asset Manager (Chapter 9): override GetPrimaryAssetId, register the type in project settings, get asset registry scanning, asset bundles, async loading, and chunking for free. Blueprint classes can be registered as primary assets too, but carry the extra class/CDO overhead. Both support TSoftObjectPtr for deferred loading of heavy content — this part is a wash.
Two questions, in order:
| Use case | Choice |
|---|---|
| Spawnable actor with behavior variants | Blueprint |
| Item definitions, stat blocks, config tables | Data Asset |
| DataTable-like data but wanting real asset refs + type safety | Data Asset |
| Designer-tunable variant of an existing actor | Data-only Blueprint |
Declaring a variable on a Blueprint always makes it a real Blueprint — this cannot be avoided directly. The fix is to reorganize the hierarchy so only leaf classes need to stay data-only.
Only the variants at the bottom (the ones duplicated dozens of times for designers) need to be data-only. Classes in the middle that define structure are supposed to be real classes.
AWeapon (C++) <- shared fields
|- ARifle (C++) <- rifle-only fields declared HERE
|- BP_AK47 <- data only
|- BP_M4 <- data only
|- BP_ScoutRifle <- data only
UCLASS(Blueprintable)
class ARifle : public AWeapon
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, Category = "Rifle")
int32 MagazineSize = 30;
UPROPERTY(EditDefaultsOnly, Category = "Rifle")
float ReloadTime = 2.0f;
};
Every data-only child inherits these fields with full revert-arrow support, and never declares anything itself. If C++ isn't available: same shape, one level lower. Make BP_Rifle_Base a regular Blueprint that declares the rifle variables; its children stay data-only. Pay the cost once instead of per-leaf.
Put a pointer on the base class and let the pointed-at object carry type-specific data, so leaf Blueprints never need their own variables at all.
UCLASS(Blueprintable)
class AWeapon : public AActor
{
UPROPERTY(EditDefaultsOnly, Category = "Config")
TObjectPtr<UWeaponData> WeaponData;
};
URifleData : UWeaponData carries the rifle fields. Every weapon Blueprint stays data-only permanently — new weapon categories mean new Data Asset classes, not new Blueprint layers.
ReloadTime on ARifle, every non-overriding rifle updates). Composition is flat — no such propagation.Putting every field (rifle, shotgun, launcher) on a single base class works for small projects but grows badly — a shotgun's Details panel ends up showing HomingTurnRate doing nothing, and nobody can tell which fields are live. If unavoidable, gate visibility cosmetically:
UPROPERTY(EditDefaultsOnly, meta = (EditCondition = "bIsProjectile"))
float ProjectileSpeed = 3000.f;
These solve one problem: UObject properties are references by default; sometimes ownership is wanted instead.
UPROPERTY(EditDefaultsOnly)
TObjectPtr<UWeaponConfig> Config;
This gives an asset picker — a reference to an existing asset on disk. Two weapons pointing at the same asset share it.
A permission flag: "this class may be created inline in a Details panel," instead of only picked from the Content Browser.
UCLASS(EditInlineNew, Blueprintable, Abstract)
class UWeaponConfig : public UObject
{
GENERATED_BODY()
};
Without it, the editor never offers to create one inline. Default for UObject is effectively NotEditInlineNew.
Says "this pointer owns its object."
UPROPERTY(EditDefaultsOnly, Instanced)
TObjectPtr<UWeaponConfig> Config;
Two effects: the Details panel becomes a class picker + inline expandable editor instead of an asset picker, and the object is deep-copied on instantiation — each child Blueprint and each spawned actor gets its own copy rather than sharing the parent's object. (Implies ExportSubObject, which is what makes the sub-object save inside the owning asset.)
Convenience: every property pointing at this class is automatically treated as Instanced, even without the property-level specifier.
UCLASS(EditInlineNew, DefaultToInstanced, Blueprintable, Abstract)
class UWeaponConfig : public UObject { ... };
This is what UActorComponent itself declares — the reason every component pointer on an actor automatically owns its component without ever writing Instanced explicitly.
DefaultToInstanced applies everywhere the class is referenced. If a shared reference to the same config object is ever wanted elsewhere, it makes that awkward. Prefer per-property Instanced when intent should stay visible at the point of use.
Both EditInlineNew (class) and Instanced (property) are needed together — one without the other gives either a class with nothing offering to create it, or a property still wanting an asset reference.
UCLASS(EditInlineNew, Blueprintable, Abstract)
class UWeaponConfig : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly)
float Damage = 10.f;
};
UCLASS()
class URifleConfig : public UWeaponConfig
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly)
int32 MagazineSize = 30;
};
UPROPERTY(EditDefaultsOnly, Instanced, Category = "Config")
TObjectPtr<UWeaponConfig> Config;
| Situation | Choice |
|---|---|
| Shared across many owners, authored once, browsable in Content Browser | Data Asset, no specifiers |
| Owned by exactly one thing, never reused, no asset-file clutter wanted | EditInlineNew + Instanced |
| Base class where the answer is always owned (e.g. components) | DefaultToInstanced |
Note: Instanced works on arrays too (TArray<TObjectPtr<UWeaponConfig>>) — a common pattern for a list of modifiers/effects where each entry is a different subclass.
Answer each out loud before re-reading the chapter.
SpreadPattern field, but a Pistol doesn't. Where does the field go, and why?EditInlineNew but not DefaultToInstanced, and a property of that type is declared without Instanced. What does the Details panel show, and why?