UE Data Assets and Tables
Target engine: UE 5.8. APIs below are verified against the 5.8 headers; older forms are listed under "Deprecated — do not use".
This skill covers designer-authored game data and the loading paths that bring it into memory: UDataAsset/UPrimaryDataAsset, UDataTable/UCurveTable, soft references, FStreamableManager, UAssetManager primary assets and cook rules, IAssetRegistry queries, the DataRegistry plugin and UDeveloperSettings. Build.cs modules: Engine and CoreUObject for data assets, tables and soft pointers; AssetRegistry for registry queries; DeveloperSettings for settings classes; DataRegistry for data registries. Deep dives live in asset loading patterns [blocked] and data-driven design patterns [blocked].
Context
Read .agents/ue-project-context.md if it exists (module names, conventions, enabled plugins, custom UAssetManager subclass, registered primary asset types). Do not stop if it is missing.
Identify the area from the request and the codebase. Ask only when two plausible readings would produce different code.
Choosing a Data Container
UDataAsset supports Blueprint subclasses and inheritance; UDataTable rows do not. UDataTable gives lookup by row name out of the box; data assets need an Asset Manager scan or an Asset Registry query to be discoverable.
Data Assets
UDataAsset is a plain UObject you can instantiate from the Content Browser. UPrimaryDataAsset adds GetPrimaryAssetId() so UAssetManager can scan, load and unload it by id.
GetPrimaryAssetId() rules (Engine/DataAsset.h:36-53):
- The default returns type = name of the first native class going up the hierarchy (or the highest-level Blueprint class), name = the asset FName.
- Blueprint subclasses should be authored as Data Only Blueprints, not Data Asset instances, so parent-class edits propagate.
- Override only when you need a different type/name scheme; inside the class body the declaration is
virtual FPrimaryAssetId GetPrimaryAssetId() const override;. UpdateAssetBundleData()scansmeta=(AssetBundles="Name")on the class and fills theAssetBundleDataUPROPERTY duringPreSave. Bundle names are free-form;meta=(AssetBundles="Client,Server")puts one property in two bundles.- Plain
UDataAssethas no primary asset id and loads only through whoever references it.
Data Tables
Every row struct derives FTableRowBase (Engine/DataTable.h:33) and uses GENERATED_BODY().
FTableRowBase::IsDataValid(FDataValidationContext& Context) const is the third hook and is wrapped in #if WITH_EDITOR (Engine/DataTable.h:60-68); guard any override the same way and include Misc/DataValidation.h.
Lookup
FDataTableRowHandle is the UPROPERTY-friendly reference (table plus row name) designers pick in the editor:
Runtime mutation and import
GetTableAsCSV(), GetTableAsJSON() and GetTableAsString() sit inside #if WITH_EDITOR (Engine/DataTable.h:318-345) — they do not exist in a cooked build. bStripFromClientBuilds on the table asset makes NeedsLoadForClient() return false, so server-only tables never ship to clients (Engine/DataTable.h:117,149).
Blueprint access
UDataTableFunctionLibrary (Kismet/DataTableFunctionLibrary.h) exposes tables to Blueprint: GetDataTableRowFromName, DoesDataTableRowExist, GetDataTableRowNames, GetDataTableColumnNames, GetDataTableColumnAsString, GetDataTableRowStruct, EvaluateCurveTableRow, GetCurveTableRowNames, plus the editor-scripting FillDataTableFromCSVString / FillDataTableFromCSVFile.
Curve Tables and Composite Tables
FCurveTableRowHandle also offers GetRichCurve(), GetSimpleCurve(), IsNull() and Eval(float XValue, float* YValue, const FString& ContextString), which reports whether the lookup succeeded.
UCompositeDataTable merges its ParentTables array into one row map; later tables override earlier ones by row name. Use it for base game plus DLC or per-platform overrides. AppendParentTables(), RemoveParentTables() and GetParentTables() manage the stack; AddRow and RemoveRow are overridden as no-ops (Engine/CompositeDataTable.h:43-47, CompositeDataTable.cpp:191-204), so edit the parent tables instead.
References: Hard vs Soft
A hard reference inside a UPrimaryDataAsset pulls its whole dependency chain into memory as soon as the definition loads — the single biggest cause of runaway memory in data-driven projects. Default to soft references for content, hard references for values and small structs.
Async Loading
FStreamableManager (Engine/StreamableManager.h) is owned by the Asset Manager; reach it with UAssetManager::GetStreamableManager() and never construct your own in game code.
RequestAsyncLoad has two forms. The classic form takes the paths, a delegate, then TAsyncLoadPriority Priority, bool bManageActiveHandle, bool bStartStalled, FString DebugName. The params form takes a single FStreamableAsyncLoadParams&&.
Params form, when you need the cancel/update delegates or JIT trickling:
RequestSyncLoad(TargetsToStream, bManageActiveHandle = false, DebugName = FString()) blocks until done — loading screens and one-time init only.
The handle is the reference. Assets loaded through RequestAsyncLoad stay alive only while a TSharedPtr<FStreamableHandle> to that request survives (or bManageActiveHandle was true). Dropping every pointer before completion does not cancel: the manager holds the handle until the completion delegate has run, then releases it (Engine/StreamableManager.h:316-320), so the assets are collectable at the next GC unless something else hard-references them.
Delegate factories: FStreamableDelegate::CreateUObject(this, &UMyLoader::Fn) (safe, checks the object), CreateWeakLambda(this, Lambda) (safe, drops if the owner died), CreateLambda(Lambda) (unsafe with a captured this).
UE_ENABLE_STREAMABLE_JIT_ASYNC_LOADING (Engine/StreamableManager.h:20) defaults to 0 and gates the just-in-time async loader at compile time. s.StreamableEnableJITAsyncLoading enables it for requests that opt in through bUseJustInTimeAsyncLoader; s.StreamableEnableJITAsyncLoadingGlobally forces it for every request. JIT trickles requests to the async loader instead of queueing them all at once, which keeps prioritisation and cancellation responsive under load. FStreamableHandle::IsUsingJustInTimeAsyncLoader() reports the resolved state.
More patterns — batching, combined handles, progress, cancellation — in asset loading patterns [blocked].
Asset Manager
UAssetManager is the global singleton registered through [/Script/Engine.Engine] AssetManagerClassName. Subclass it to override StartInitialLoading() and PostInitialAssetScan().
Signatures (Engine/AssetManager.h:333,340):
LoadPrimaryAssets (plural), LoadPrimaryAssetsWithType, ChangeBundleStateForPrimaryAssets and ChangeBundleStateForMatchingPrimaryAssets all come in the same pair of shapes. Location is filled in by the compiler — never pass it. Prefer the FAssetManagerLoadParams overload for new code; it carries OnComplete, OnCancel (both FStreamableDelegateWithHandle), OnUpdate and Priority.
Unlike raw streamable requests, primary assets stay loaded until you unload them: you do not have to keep the returned handle alive, only to poll or wait on it.
ScanPathsForPrimaryAssets(FPrimaryAssetType, const TArray<FString>& Paths, UClass* BaseClass, bool bHasBlueprintClasses, bool bIsEditorOnly = false, bool bForceSynchronousScan = true) registers types from code when config-driven scanning is not enough. AddDynamicAsset(const FPrimaryAssetId&, const FSoftObjectPath&, const FAssetBundleData&) registers a runtime-generated primary asset.
Cook Rules
FPrimaryAssetTypeInfo fields (Engine/AssetManagerTypes.h:131): PrimaryAssetType, AssetBaseClass, bHasBlueprintClasses, bIsEditorOnly, Directories, SpecificAssets, Rules. FPrimaryAssetRules (Engine/AssetManagerTypes.h:66): Priority, ChunkId, bApplyRecursively, CookRule.
EPrimaryAssetCookRule (Engine/AssetManagerTypes.h:28):
UAssetManagerSettings (config = Game, defaultconfig) also exposes DirectoriesToExclude, PrimaryAssetRules, CustomPrimaryAssetRules and bOnlyCookProductionAssets — turn the last one on for shipping branches so ProductionNeverCook assets error instead of leaking into the build.
An asset reachable neither from a scanned primary asset nor from a hard reference is not cooked. A soft reference alone does not pull an asset into the cook; give it a primary asset type or a UPrimaryAssetLabel.
Asset Registry Queries
IAssetRegistry reads asset metadata without loading the assets.
IAssetRegistry::Get() returns a pointer that can be null before the module is up; GetChecked() asserts instead, and FAssetRegistryModule::GetRegistry() is the module-level equivalent. AssetRegistrySearchable on a UPROPERTY writes that property into the registry so FARFilter::TagsAndValues and GetAssetsByTagValues can filter on it without loading. Use ScanPathsSynchronous() when assets may not have been discovered yet.
Data Registry
DataRegistry (Beta in 5.8; plugin off by default, module DataRegistry) resolves one FDataRegistryId against a chain of sources — data tables, curve tables or custom ones — so gameplay code asks for an item by id without knowing which table or DLC provides it.
UDataRegistrySource_DataTable points a registry at a TSoftObjectPtr<UDataTable> SourceTable with FDataRegistrySource_DataTableRules (bPrecacheTable, CachedTableKeepSeconds); UDataRegistrySource_CurveTable does the same for curves. Registry assets are configured through UDataRegistrySettings.
Project Settings
UDeveloperSettings (module DeveloperSettings) auto-registers a class in Project Settings and reads from an ini section named after the class.
Declare the class UCLASS(config = Game, defaultconfig, meta = (DisplayName = "My Game")) deriving UDeveloperSettings, mark each field UPROPERTY(config, EditAnywhere, Category = "Economy"), and read it with GetDefault<UMyGameSettings>(). config = Game plus defaultconfig writes to DefaultGame.ini under [/Script/MyGame.MyGameSettings]; override GetContainerName(), GetCategoryName() or GetSectionName() to place the page. Use this for a handful of global tunables and data assets for anything designers iterate on per entry — the full class, with includes and the Build.cs dependency, is in data-driven design patterns [blocked].
Deprecated — do not use
Common Mistakes
Letting the streamable handle die: loaded assets are referenced by the handle, not by the soft pointer. Store TSharedPtr<FStreamableHandle> as a member until a hard reference (component, TObjectPtr, array) owns the asset, then reset it.
Hard-referencing content from a data asset: TObjectPtr<UNiagaraSystem> in a UPrimaryDataAsset loads the effect with the definition. Use TSoftObjectPtr plus an asset bundle.
LoadSynchronous() in Tick or on gameplay-critical paths: it stalls the game thread whenever the asset is not already resident. Start one async load and act in the callback.
Passing FName to GetAssetsByClass: 5.8 takes FTopLevelAssetPath(TEXT("/Script/Module"), TEXT("ClassName")). A short class name will not compile.
Forgetting PrimaryAssetTypesToScan: without a registered type, GetPrimaryAssetIdList returns nothing and LoadPrimaryAsset silently does nothing. Register the type in DefaultGame.ini or call ScanPathsForPrimaryAssets.
Capturing this in a bare CreateLambda: the callback can fire after the object is gone. Use CreateUObject or CreateWeakLambda(this, Lambda).
Calling GetTableAsCSV() at runtime: it is WITH_EDITOR only and will not link in a cooked build. Serialise the rows yourself instead. Likewise, renaming or removing a row-struct property makes existing rows lose that column on the next import — export the table to CSV before changing the struct, then re-import.
Related Skills
ue-cpp-foundations—UPROPERTY/USTRUCTspecifiers,TObjectPtr, UObject lifecycle, the subsystem tableue-async-threading— general async idioms (UE::Tasks,Async,FTSTicker) beyond streamable loadingue-serialization-savegames— persisting soft object paths and primary asset ids across sessionsue-game-features— shipping data assets inside plugins and game feature asset scanningue-gameplay-abilities— ability and effect definitions that consume these data assetsue-world-level-streaming— level streaming, World Partition and Data Layersue-module-build-system— addingAssetRegistry,DeveloperSettingsand plugin modules toBuild.csue-audio-system— UAudioComponent, MetaSounds, submixes, attenuation and concurrencyue-blueprint-cpp-interop— exposing C++ to Blueprint: UFUNCTION/UPROPERTY meta keys, latent actions and async nodesue-editor-tools— detail customizations, editor utility widgets, UToolMenus and editor subsystemsue-gameplay-tags-messaging— native and ini gameplay tags, containers, queries and the async message systemue-materials-rendering— material instances, parameter collections, render targets and post processue-procedural-generation— PCG graphs, procedural and dynamic meshes, instancing and splinesue-ui-umg-slate— UMG widgets, Slate, Common UI and MVVM


