Data Tables for Game Designers: Spreadsheet-Driven Game Data in UE5
If you’ve ever maintained a Google Sheet of game stats “for reference” while manually copying those numbers into Blueprints, Data Tables are the system that makes that spreadsheet actually drive your game.

What Data Tables actually are
A Data Table is a UE5 asset that stores rows of structured data. Think of it as a database table living inside your project: you define the columns (called a struct), then fill in rows. Each row gets a unique name — its lookup key — and your Blueprints fetch rows by that key at runtime.The struct is the template. It says “every row in this table has a Name, an AttackPower, a Price, and an Icon.” The Data Table is the container that holds all the actual entries following that template. Internally, UE5 stores rows in a hash map keyed by
FName, which means looking up a row by name is effectively instant — even with thousands of entries.The practical upshot: designers edit a table, Blueprints read from that table, and nobody has to recompile anything.
Step 1: Define your struct (the column template)
Before you create a Data Table, you need a struct that defines what each row looks like. You have two options here.
Blueprint-only approach
Right-click in the Content Browser → Blueprints → Structure. Name it something clear like S_ItemData. In the struct editor, add variables for each field:- ItemName (Text) — the display name, localizable
- AttackPower (Integer) — base damage stat
- Price (Float) — shop price
- Rarity (Enum) — if you’ve created a rarity enum, use it here
- Icon (Soft Object Reference → Texture 2D) — the item thumbnail

C++ approach
If your project uses C++, define the struct inheriting from FTableRowBase with UPROPERTY fields for each column. The C++ route is more stable for CSV import workflows and won't occasionally cause the editor corruption issues that Blueprint-defined structs can. If you're comfortable with C++, we recommend it.
A critical note on soft references
Notice that Icon field uses TSoftObjectPtr (or Soft Object Reference in Blueprints) rather than a direct object reference. This matters more than almost any other decision you'll make with Data Tables.Hard references load every referenced asset the moment the Data Table loads. A table with 200 items pointing to 200 textures and 200 meshes will shove all of that into memory immediately — whether you need it or not. Soft references store only the asset path as a string and load on demand.
Use soft references for any asset fields: textures, meshes, sounds, particle systems. Your memory budget will thank you.
Step 2: Create and populate your Data Table
Right-click in the Content Browser → Miscellaneous → Data Table. Select your struct from the dropdown. Name it with a DT_ prefix for clarity — DT_Items, DT_Enemies, DT_WeaponStats.The Data Table editor opens with a spreadsheet view across the top and a detail panel below. Click Add to insert a row. Each row needs a unique Row Name — this is the key your Blueprints will use to look up data, so make them descriptive:
Sword_Iron, Potion_Health_Small, Enemy_Goblin.Fill in the fields using the detail panel below. You can right-click rows to duplicate, delete, or copy them.
The editor works well for small-to-medium tables. Past roughly 100–500 rows, the editor UI starts getting sluggish (this is a UI limitation, not a runtime one — your game can handle thousands of rows without issue). For larger datasets or team collaboration, you’ll want the CSV pipeline.

Step 3: The Google Sheets → CSV → UE5 pipeline
This is where Data Tables become a genuine production tool. Google Sheets gives you free, collaborative, version-historied spreadsheet editing. The pipeline is: structure your sheet, export as CSV, import into UE5, and reimport whenever you update.
Structure your sheet correctly
Your Google Sheet needs to follow specific rules:First column = Row Names. These are your unique lookup keys (
Sword_Iron, Enemy_Goblin). No spaces, no duplicates.Column headers = exact struct field names. Case-sensitive. If your struct has
AttackPower, your header must be AttackPower — not attackpower, not Attack Power, not attack_power.Data formatting by type:
- Integers and floats: plain numbers (
12,3.5) - Booleans:
trueorfalse - Enums: the value name (
Common,Rare,Legendary) - Vectors: parenthesized format in quotes —
"(X=1.0,Y=2.0,Z=3.0)"(the quotes prevent internal commas from breaking CSV parsing)
Here’s what a clean item sheet looks like:
RowName,ItemName,AttackPower,Price,Rarity
Sword_Iron,Iron Sword,12,100,Common
Sword_Fire,Flame Blade,25,450,Rare
Shield_Oak,Oak Shield,3,60,Common
Potion_Health,Health Potion,0,25,Common
Export and handle encoding
Go to File → Download → Comma-separated values (.csv). This works out of the box for English/ASCII content.
If your data includes accented characters, CJK text, or any non-ASCII characters, you’ll hit encoding problems. Google Sheets exports UTF-8 without a byte-order mark (BOM), and UE5 needs that BOM to read multibyte characters correctly. The fix: open the exported CSV in Notepad++ or VS Code and re-save as “UTF-8 with BOM” before importing.
Import into UE5
Drag the CSV into the Content Browser. In the import dialog, set the type to DataTable and select your struct. For updates, right-click your existing Data Table → Reimport (same file) or Reimport With New File (new CSV).The reimport warning nobody tells you first: reimporting completely overwrites the Data Table. Any manual edits you made in the UE5 editor will vanish. Pick one source of truth — either the spreadsheet or the editor — and stick with it. We strongly recommend the spreadsheet as your canonical source when using the CSV pipeline.

Common CSV import gotchas
- “Column not found” errors: header doesn’t exactly match the struct field name — check capitalization and trailing spaces
- Blank rows: caused by empty rows at the bottom of your Google Sheet — delete them before exporting
- The field name
Namebreaks things: it collides with the internal Row Name column — useDisplayNameorItemNameinstead - Enum values not importing: use the exact enum value name as it appears in the editor, not the display name
Step 4: Reading Data Tables in Blueprints
Three nodes cover nearly every use case you’ll encounter.
Get Data Table Row — the workhorse
This is the node you’ll use 90% of the time. It takes a Data Table reference and a Row Name (FName), and outputs two execution pins: Row Found and Row Not Found, plus the row data as a struct.
Always handle both paths. If a row name gets mistyped or deleted from the table, the Row Not Found path saves you from a silent failure that’s miserable to debug.
From the output struct pin, drag off and select Break [YourStruct] to fan out each field as a separate output. A typical item lookup:
- Get Data Table Row → DT_Items, Row Name: “Sword_Iron”
- Row Found → Break S_ItemData
- Read AttackPower, Price, Icon from the broken struct pins

Get Data Table Row Names — for iteration
Returns an array of every Row Name in the table. Pair it with a For Each Loop to iterate all rows — essential for building shop UIs, inventory screens, or loot tables.
The pattern: Get Data Table Row Names → For Each Loop → Get Data Table Row (using the current array element as the Row Name) → Break struct → create your widget or process the data.

Does Data Table Row Exist — for validation
Returns a simple boolean. Useful for checking whether a row name is valid before trying to retrieve it — cleaner than relying on the Row Not Found execution path when you just need a yes/no check.
Performance considerations
Data Table lookups are hash-based and fast enough for BeginPlay, OnConstruct, or any event-driven code. Don’t call Get Data Table Row on Tick — not because it’ll tank performance, but because there’s no reason to. Cache the result in a variable at spawn time and read from that.
Common patterns that save time
Enemy spawning with designer-tunable stats: Store a Row Name variable on your enemy spawner (exposed with Instance Editable so level designers can set it per-instance in the viewport). On BeginPlay, look up that row, break the struct, and apply health, damage, speed, and any other values to the enemy. The designer changes a number in the Data Table, and the enemy changes next playtest.Cross-referencing between tables: Use
FDataTableRowHandle to store both a Data Table reference and a Row Name. An enemy table can reference a loot table row, and with the RowType metadata specifier, the editor shows a filtered dropdown of valid rows. Clean and type-safe.Composite Data Tables: If multiple designers need to work on the same data type without merge conflicts, create separate Data Tables with the same struct and combine them into a Composite Data Table. Higher-index tables override rows from lower-index ones. Great for DLC content or keeping team members out of each other’s files.
When Data Tables aren’t the right tool
Data Tables are ideal for large volumes of uniformly shaped, static data — anything where every entry has the same fields. But they’re not the best fit for everything.
Use Data Assets instead when entries need different field sets (a sword needs different properties than a potion), when you want UObject inheritance, or when individual entries are complex enough to warrant their own asset files.
GameplayTags complement Data Tables rather than replacing them. Tags handle hierarchical categorization (
Item.Weapon.Sword, DamageType.Fire) and efficient filtering. Your Data Table rows can contain GameplayTag fields, giving you structured numeric data plus flexible categorization.Getting started in thirty minutes
Here’s the fastest path to a working Data Table pipeline:
- Create a struct for your most-edited data type (items or enemies)
- Create a Data Table using that struct, add 3–5 test rows manually
- Wire up a Get Data Table Row node in a test Blueprint and confirm you’re reading values
- Set up your Google Sheet with matching column headers
- Export CSV, import into UE5, verify the data matches
- Change a value in the sheet, re-export, reimport, and confirm the change appears in-game
That loop — edit sheet, export, reimport, test — becomes your iteration workflow. The initial setup takes about thirty minutes. The time saved over manually editing Blueprint variables for every balance change lasts the entire project.