Quick answer: FindRow returns null when the row name you pass does not exactly match a row name in the DataTable. Row names are case-sensitive FName values. A common cause is trailing whitespace imported from a CSV, or a mismatch between the FName you construct in code and the row name stored in the asset.
Here is how to fix Unreal data table row not found. You call FindRow on your UDataTable and it returns nullptr. Your DataTable clearly has rows — you can see them in the editor — but at runtime the lookup silently fails. No crash, no ensure, just a null pointer that eventually causes a downstream access violation. This problem has a few distinct causes, and each one requires a different fix.
The Symptom
You have a UDataTable asset populated with rows in the Unreal Editor. Each row uses a custom USTRUCT that inherits from FTableRowBase. When you call DataTable->FindRow<FYourStruct>(RowName, TEXT("")) in C++, the return value is nullptr. The row name appears correct, the DataTable pointer is valid, and the struct definition looks right. Yet FindRow consistently fails to locate the row.
In Blueprint, the equivalent symptom appears when you use a Get Data Table Row node and the output pin produces a default-initialized struct with all zeroes. The "Row Not Found" output execution pin fires instead of the success pin. If you are using FDataTableRowHandle as a UPROPERTY, the handle's GetRow method returns null even though the handle appears correctly configured in the Details panel.
In packaged builds, a variation of this problem occurs where the DataTable itself is null. The editor works fine because the asset is loaded on demand, but after cooking and packaging, the asset reference resolves to nothing. The Output Log may show a warning about a missing asset, but only if you have the log verbosity set high enough to catch it.
What Causes This
1. Row name mismatch. This is the most frequent cause. Row names in a DataTable are FName values, and while FName comparisons are case-insensitive in most contexts, the row names stored in a DataTable imported from CSV can include invisible trailing whitespace, byte-order marks, or encoding artifacts. If your CSV was edited in Excel, the row name column may have trailing spaces that are invisible in the editor's DataTable viewer but cause FindRow to fail because the FName you construct in code does not carry that whitespace.
2. DataTable asset not loaded. When you reference a DataTable through a TSoftObjectPtr<UDataTable> or a string asset path, the asset is not automatically loaded into memory. In the editor, soft references are often resolved transparently because the asset registry keeps things warm. But in a packaged build, a soft reference that has not been explicitly loaded resolves to null. Additionally, if the DataTable lives in a directory that the cooker does not scan, it will be excluded from the build entirely.
3. FindRow template type mismatch. The FindRow function is a template method. It casts the internal row data to the struct type you specify. If you pass FindRow<FWeaponData> but the DataTable was created with FItemData, the cast fails and FindRow returns null. This also happens when you have two structs with similar names in different modules and you accidentally include the wrong header. The compiler does not catch this because both structs inherit from FTableRowBase and the template instantiation is valid either way.
4. DataTable not reimported after struct changes. If you add or rename a field in your row struct but do not reimport the DataTable from its source CSV or JSON, the existing rows in the asset will not have the new field. In some cases this causes the entire row mapping to become stale, and FindRow fails because the internal structure no longer matches the compiled struct layout. Unreal may also silently discard rows that fail deserialization.
The Fix
Step 1: Log all row names and compare them exactly. Before assuming your row name is correct, dump every row name in the table and compare byte-for-byte against the name you are searching for.
void UMySubsystem::DebugPrintRowNames(const UDataTable* DataTable)
{
if (!DataTable)
{
UE_LOG(LogTemp, Error, TEXT("DataTable is null. Check asset reference."));
return;
}
TArray<FName> RowNames = DataTable->GetRowNames();
for (const FName& Name : RowNames)
{
UE_LOG(LogTemp, Log, TEXT(" Row: [%s] (len=%d)"),
*Name.ToString(), Name.ToString().Len());
}
// Now test the lookup
FName TestName = FName(TEXT("SwordOfFlame"));
const FWeaponData* Row = DataTable->FindRow<FWeaponData>(TestName, TEXT("DebugLookup"));
if (!Row)
{
UE_LOG(LogTemp, Error, TEXT("FindRow failed for [%s]. Compare against names above."),
*TestName.ToString());
}
}
If the logged row names reveal trailing spaces or unexpected characters, clean your source CSV. Open it in a plain text editor, trim each row name, and reimport the DataTable. Avoid editing DataTable CSVs in Excel whenever possible — use a text editor or a CSV-aware tool that does not inject hidden characters.
Step 2: Ensure the DataTable is loaded before access. If you are using soft references, you must explicitly load the asset before calling FindRow. For packaged builds, also verify the asset directory is in the cook list.
// UPROPERTY in your class header
UPROPERTY(EditDefaultsOnly, Category = "Data")
TSoftObjectPtr<UDataTable> WeaponTableSoft;
void AMyActor::LoadAndQuery()
{
// Synchronous load - blocks until asset is in memory
UDataTable* WeaponTable = WeaponTableSoft.LoadSynchronous();
if (!WeaponTable)
{
UE_LOG(LogTemp, Error, TEXT("Failed to load DataTable. Is it in the cook list?"));
return;
}
const FWeaponData* Row = WeaponTable->FindRow<FWeaponData>(
FName(TEXT("SwordOfFlame")), TEXT("LoadAndQuery"));
if (Row)
{
UE_LOG(LogTemp, Log, TEXT("Found weapon: %s, Damage: %f"),
*Row->DisplayName.ToString(), Row->BaseDamage);
}
}
// Alternative: hard reference that the cooker always includes
UPROPERTY(EditDefaultsOnly, Category = "Data")
UDataTable* WeaponTableHard;
For soft references in asynchronous contexts, use the FStreamableManager to request the load and bind a callback. This avoids hitches on the game thread while still guaranteeing the DataTable is resident before you query it.
Step 3: Confirm the struct type matches the DataTable asset. Open the DataTable asset in the editor and check the Row Structure field at the top. The struct listed there must be the exact same struct you pass as the template parameter to FindRow.
// Define your row struct - must match what the DataTable asset uses
USTRUCT(BlueprintType)
struct FWeaponData : public FTableRowBase
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FText DisplayName;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float BaseDamage = 0.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 RequiredLevel = 1;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TSoftObjectPtr<UTexture2D> Icon;
};
// Safe lookup wrapper with type validation
template<typename T>
const T* SafeFindRow(const UDataTable* Table, FName RowName)
{
if (!Table)
{
UE_LOG(LogTemp, Error, TEXT("DataTable is null"));
return nullptr;
}
// Verify the struct type matches
if (Table->GetRowStruct() != T::StaticStruct())
{
UE_LOG(LogTemp, Error, TEXT("Struct mismatch: table uses %s, requested %s"),
*Table->GetRowStruct()->GetName(),
*T::StaticStruct()->GetName());
return nullptr;
}
const T* Row = Table->FindRow<T>(RowName, TEXT("SafeFindRow"));
if (!Row)
{
UE_LOG(LogTemp, Warning, TEXT("Row '%s' not found in %s"),
*RowName.ToString(), *Table->GetName());
}
return Row;
}
Related Issues
If your DataTable rows load correctly but your Blueprint events are not firing when the data changes, see our guide on Blueprint events not firing. If the DataTable works in the editor but your packaged build crashes before reaching the lookup code, check out fixing packaged build crashes on startup.
Log your row names. The one you think is right probably has a trailing space.