Database
HELIX provides a built-in database system through the Database module, backed by SQLite. This gives you persistent storage for player data, inventories, leaderboards, world state, and anything else that needs to survive a server restart.
Initializing the Database
The database file lives on your server and is created automatically when you first initialize. Call Database.Initialize once with the file path for your .db file.
- Blueprint
- Lua
- JavaScript
// Initialize (or create) a SQLite database
Database::Initialize("Saved/Database/my_world.db");
-- Initialize (or create) a SQLite database
Database.Initialize("Saved/Database/my_world.db")
// Initialize (or create) a SQLite database
Database.Initialize("Saved/Database/my_world.db");
Creating Tables
Define your schema with standard SQL. Run this on server start so your tables are always ready.
- Blueprint
- Lua
- JavaScript
Database::Execute(R"(
CREATE TABLE IF NOT EXISTS players (
id TEXT PRIMARY KEY,
name TEXT,
money INTEGER DEFAULT 0,
inventory TEXT DEFAULT '[]',
last_position TEXT
)
)");
Database.Execute([[
CREATE TABLE IF NOT EXISTS players (
id TEXT PRIMARY KEY,
name TEXT,
money INTEGER DEFAULT 0,
inventory TEXT DEFAULT '[]',
last_position TEXT
)
]])
Database.Execute(`
CREATE TABLE IF NOT EXISTS players (
id TEXT PRIMARY KEY,
name TEXT,
money INTEGER DEFAULT 0,
inventory TEXT DEFAULT '[]',
last_position TEXT
)
`);
CRUD Operations
Use Database.Execute for writes and Database.Select for reads. Parameterized queries keep your data safe from injection.
- Blueprint
- Lua
- JavaScript
// Insert a new player
Database::Execute("INSERT OR IGNORE INTO players (id, name) VALUES (?, ?)",
Player->GetSteamID(), Player->GetName());
// Update player money
Database::Execute("UPDATE players SET money = ? WHERE id = ?",
500, Player->GetSteamID());
// Read player data
TArray<FDatabaseRow> Rows = Database::Select("SELECT * FROM players WHERE id = ?",
Player->GetSteamID());
// Delete a player record
Database::Execute("DELETE FROM players WHERE id = ?", Player->GetSteamID());
-- Insert a new player
Database.Execute("INSERT OR IGNORE INTO players (id, name) VALUES (?, ?)",
{player:GetSteamID(), player:GetName()})
-- Update player money
Database.Execute("UPDATE players SET money = ? WHERE id = ?",
{500, player:GetSteamID()})
-- Read player data
local rows = Database.Select("SELECT * FROM players WHERE id = ?",
{player:GetSteamID()})
for _, row in ipairs(rows) do
print(row.Columns.name, row.Columns.money)
end
-- Delete a player record
Database.Execute("DELETE FROM players WHERE id = ?", {player:GetSteamID()})
// Insert a new player
Database.Execute("INSERT OR IGNORE INTO players (id, name) VALUES (?, ?)",
player.GetSteamID(), player.GetName());
// Update player money
Database.Execute("UPDATE players SET money = ? WHERE id = ?",
500, player.GetSteamID());
// Read player data
const rows = Database.Select("SELECT * FROM players WHERE id = ?",
player.GetSteamID());
// Delete a player record
Database.Execute("DELETE FROM players WHERE id = ?", player.GetSteamID());
Saving Player Data on Events
A common pattern is to save data when players disconnect and load it when they join.
- Blueprint
- Lua
- JavaScript
// Save position on disconnect
Events::Subscribe("PlayerDisconnect", [](APlayerController* Player) {
FVector Pos = Player->GetCharacter()->GetLocation();
Database::Execute("UPDATE players SET last_position = ? WHERE id = ?",
FString::Printf(TEXT("%f,%f,%f"), Pos.X, Pos.Y, Pos.Z),
Player->GetSteamID());
});
// Load position on join
Events::Subscribe("PlayerReady", [](APlayerController* Player) {
auto Rows = Database::Select("SELECT last_position FROM players WHERE id = ?",
Player->GetSteamID());
if (Rows.Num() > 0 && !Rows[0]["last_position"].IsEmpty()) {
// Parse and teleport to saved position
}
});
-- Save position on disconnect
RegisterServerEvent("HEvent:PlayerUnloaded", function(controller)
local char = controller:GetControlledCharacter()
if char then
local pos = char:K2_GetActorLocation()
Database.Execute("UPDATE players SET last_position = ? WHERE id = ?",
{tostring(pos), tostring(controller)})
end
end)
-- Load position on join
RegisterServerEvent("HEvent:PlayerReady", function(controller)
local rows = Database.Select("SELECT last_position FROM players WHERE id = ?",
{tostring(controller)})
if #rows > 0 and rows[1].Columns.last_position then
-- Parse and teleport to saved position
end
end)
// Using Helix server-side events for player lifecycle
Helix.server(() => {
Helix.on("PlayerDisconnect", (player) => {
// Save position on disconnect
const pos = player.GetCharacter().K2_GetActorLocation();
// Use your database ORM to persist
});
Helix.on("PlayerReady", (player) => {
// Load position on join
// Use your database ORM to query
});
});
Async Operations
For operations that shouldn't block the game loop, use Database.ExecuteAsync and Database.SelectAsync. These run in the background and call a callback function when complete.
Always use parameterized queries (? placeholders) instead of string concatenation. This prevents SQL injection and handles special characters automatically.