Hey all, today we’re going over Depot and how to use it as a basic database for your Haxe Project. This is something I use for m own games, and it has been instrumental in creating enemies, items, dialogue, and more easily. Let’s get into it.
What Is Depot?
Depot is a flat-file data storage extension that works with Visual Studio Code. It allows you to store data in a JSON format, but add data visually like an Excel or Google spreadsheet. Here’s an example of the layout!
Layout Example Image
For more information, here’s a video on the technology!
So now that we know what it is, how do we mak it work for us?
Haxe Black Magic
In Haxe, there is a set of tools to create macros. A macro is a piece of code that runs before the code is compiled for the final program output. This allows you to bake code into your program like black magic. For example, we could bake in the result of parsing a certain Depot File.

Database References In Code
By using macros we can create references in the code by adding the result of parsing the file to the source code itself. This is how macros work in Haxe and allows us to do things we usually can’t in several languages. So, how do we do it with a Depot File? See below, it’s a pretty crazy macro.
package macros; | |
#if macro | |
import haxe.DynamicAccess; | |
import Types.DepotFile; | |
import sys.io.File; | |
import sys.FileSystem; | |
import haxe.macro.Expr.Field; | |
import haxe.Json; | |
import haxe.macro.Context; | |
import haxe.macro.Expr; | |
import haxe.macro.Type; | |
using haxe.macro.Tools; | |
#end | |
import haxe.extern.EitherType; | |
using Lambda; | |
using StringTools; | |
using ext.StringExt; | |
// Depot Information | |
typedef DepotFile = { | |
var sheets:Array<DepotSheet>; | |
} | |
typedef DepotSheet = { | |
var name:String; | |
var description:String; | |
var displayColumn:String; | |
var guid:String; | |
var columns:Array<ColumnInfo>; | |
var lines:Array<LineInfo>; | |
var hidden:Bool; | |
var configurable:{ | |
var name:String; | |
var description:String; | |
var displayColumn:String; | |
} | |
var ?parentSheetGUID:String; | |
var ?columnGUID:String; | |
} | |
typedef ColumnInfo = { | |
var typeStr:TypeString; | |
var guid:String; | |
var name:String; | |
var description:String; | |
var defaultValue:String; | |
var sheet:String; | |
var iconName:String; | |
var configurable:ConfigInfo; | |
} | |
typedef ConfigInfo = { | |
var name:String; | |
var description:String; | |
var defaultValue:EitherType<String, Array<Dynamic>>; | |
} | |
typedef LineInfo = { | |
var guid:String; | |
var id:String; | |
} | |
enum abstract TypeString(String) from String to String { | |
public var TEXT = 'text'; | |
public var LONGTEXT = 'longtext'; | |
public var BOOL = 'bool'; | |
public var INT = 'int'; | |
public var FLOAT = 'float'; | |
public var IMAGE = 'image'; | |
public var LIST = 'list'; | |
public var ENUM = 'enum'; | |
} | |
// using StringExtensions; | |
/** | |
* Contains the build macros for creating Depot | |
* integration within the Haxe programming language. | |
*/ | |
class DepotMacros { | |
#if macro | |
public static macro function buildDepotFile(filePath:String):Array<Field> { | |
//Get the fields on the associated class | |
var buildFields = Context.getBuildFields(); | |
if (FileSystem.exists(filePath)) { | |
var fileData = File.getContent(filePath); | |
var depotData:DepotFile = Json.parse(fileData); | |
// Depot Starts from inside the data element | |
//Iterates through each sheet | |
depotData.sheets.iter((sheet) -> { | |
var dynamicSheet:DynamicAccess<Dynamic> = cast sheet; | |
dynamicSheet.remove('columns'); // Remove unnecessary columns | |
var cleanName = ~/!|\$|–|\s+/g.replace(sheet.name, '_') | |
.capitalize(); | |
var sheetFields = new Map<String, ObjectField>(); | |
for (key => value in dynamicSheet) { | |
var cleanKey = ~/!|\$|–|\s+/g.replace(key, '_'); | |
// Handles Finding Important Fields | |
var endVal = macro $v{value}; | |
var includeField = true; | |
switch (endVal.expr) { | |
case EObjectDecl(fields): | |
endVal = { | |
expr: EObjectDecl(fields.filter((field) -> { | |
return switch (field.expr.expr) { | |
case EArrayDecl(values): | |
values.length > 0 ? true : false; | |
case _: | |
true; | |
} | |
})), | |
pos: Context.currentPos() | |
}; | |
// Start Of Lines and other array declarations | |
case EArrayDecl(values): | |
if (values.length < 1) { | |
includeField = false; | |
} else { | |
var newValues = []; | |
values.iter((arrExpr) -> { | |
// trace(arrExpr); | |
switch (arrExpr.expr) { | |
case EArrayDecl(values): | |
if (values.length > 0) { | |
newValues.push(arrExpr); | |
} | |
case EObjectDecl(fields): | |
// Creates the lines in the DepotData | |
var lineName = ''; | |
var newDecl = EObjectDecl(fields.filter((field) -> | |
{ | |
return | |
switch (field.expr.expr) { | |
case EArrayDecl(values): | |
values.length > 0 ? true : false; | |
case _: | |
true; | |
} | |
}) | |
.map((field -> { | |
field.field = ~/!|\$|–|\s+/g.replace(field.field, | |
'_'); | |
// Name Individual Lines | |
lineName = field.field.contains('name') | |
&& lineName == '' ? field.expr.toString() : lineName; | |
return field; | |
}))); | |
// Final Result for the line | |
var objExpr = { | |
expr: newDecl, | |
pos: Context.currentPos() | |
}; | |
// Take Result and Convert to individual element | |
var valueComplexType = Context.toComplexType(Context.typeof(objExpr)); | |
var cleanLineName = lineName.replace("\"", | |
""); | |
cleanLineName = ~/!|\$|–|\s+/g.replace(cleanLineName, | |
"_"); | |
var newField:Field = { | |
name: '${cleanName}_${cleanLineName}', | |
pos: Context.currentPos(), | |
kind: FVar(valueComplexType, | |
objExpr), | |
access: [Access.APublic, Access.AStatic] | |
}; | |
// Push line as a class property with sheet prefix | |
buildFields.push(newField); | |
// Push Lines to their individual Sheets | |
newValues.push(objExpr); | |
case _: | |
newValues.push(arrExpr); | |
} | |
}); | |
endVal = { | |
expr: EArrayDecl(newValues), | |
pos: Context.currentPos() | |
} | |
} | |
case _: | |
// Do nothing | |
} | |
// Includes a Field in the sheet, such as lines | |
if (includeField) { | |
sheetFields.set(cleanKey, { | |
field: cleanKey, | |
expr: endVal, | |
}); | |
} | |
} | |
var valueExpr = EObjectDecl(sheetFields.array()); | |
var result = {expr: valueExpr, pos: Context.currentPos()}; | |
var valueComplexType = Context.toComplexType(Context.typeof(result)); | |
var newField:Field = { | |
name: cleanName, | |
pos: Context.currentPos(), | |
kind: FVar(valueComplexType, result), | |
access: [Access.APublic, Access.AStatic] | |
}; | |
buildFields.push(newField); | |
}); | |
} | |
//Returns the fields of the class with the newly added elements included. | |
return buildFields; | |
} | |
#end | |
} |
This macro splits a Depot file into several sheets similar to excel and goes through the structure of each one to create a type for that sheet. Because, Depot sets up everything as an individual spreadsheet, parsing the information is straightforward. The rest of the macro is ignoring irrelevant information that we don’t need for our static data.
Now that we’re done parsing, we can use the macro like so:
package; | |
// Path to your own depot file | |
@:build(macros.DepotMacros.buildDepotFile('assets/data/database.dpo')) | |
class DepotData {} |
This then adds all the fields from the Depot data directly to the class itself. In Haxe this is known as a build macro and is extremely useful for things like this.
Now, here’s the final result of our macro being used in Visual Studio Code:

Now we can reference any of the database data in our code without any hassle of looking up the data from a map or table through the Visual Studio Code Intellisense/autocomplete.
Conclusion
I hope this post helps you! With that said, good luck with creating your game. If you’d like to use the macro yourself, you can find a single file version here or copy the example above!