Kura
Early Access: Documentation is actively evolving and may change without notice.

KuraDB

Asynchronous PostgreSQL wrapper with a schema-driven Lua ORM for FiveM.

KuraDB is the PostgreSQL persistence layer for the Kura ecosystem. It combines:

  • asynchronous PostgreSQL access for FiveM
  • a chainable Lua query builder
  • generated Lua table and column references from schema.ts
  • schema-first migrations
  • raw SQL helpers when you need PostgreSQL features beyond the builder

Compatibility

KuraDB is not compatible with oxmysql, mysql-async, or ghmattimysql. It is built exclusively for PostgreSQL. Remove any MySQL-based database resources before installing.

Schema-driven by design

kura.db.tables.* is generated from your own schema.ts. The examples below use a users table only as an illustration. Replace it with whatever tables and field names your server defines.

Use generated table refs by default:

local users = kura.db.tables.users

local exists = kura.db.select()
  :from(users)
  :where(op.eq(users.id, 42))
  :exists()
  :await()

That pattern is the recommended default for normal KuraDB usage:

  • generated table and column refs first
  • string identifiers only for compatibility and convenience
  • raw SQL only through the explicit kura.db.raw.* escape hatch
  • :forUpdate() only through transaction-scoped builders such as tx.select()
  • :exists() for boolean existence checks

Install PostgreSQL

  1. Download from the official website
  2. Install and note your username, password, and port (default 5432)
  3. Create a database for your server
CREATE DATABASE fivem;

Download KuraDB

  1. Get the latest release from GitHub
  2. Extract into your server's resources directory
  3. Ensure the folder is named kuradb

Configure your server

Add to your server.cfg:

set kuradb_connection_string "postgres://root:password@localhost:5432/fivem"

ensure kuradb

Special Characters

Avoid ; , / ? : @ & = + $ # in your database password. They have special meaning in connection URIs.

For Kura-native servers, use this start order:

ensure kura-lib
ensure kuradb
ensure kura-ui
ensure kura-core

Add to your resource

Add to your fxmanifest.lua:

shared_scripts {
    '@kura-core/init.lua',
}

kura.db and op are available automatically.

Add to your fxmanifest.lua:

server_scripts {
    '@kuradb/lib/KuraDB.lua',
    '@kuradb/lib/QueryBuilder.lua',
}

Editor setup

For LuaLS, add kuradb/lib to your workspace library paths once. Kura source checkouts can use:

{
  "Lua.runtime.version": "Lua 5.4",
  "Lua.diagnostics.globals": ["kura", "op"],
  "Lua.workspace.checkThirdParty": false,
  "Lua.workspace.library": [
    "./kura-core",
    "./kura-lib",
    "./kura-ui",
    "./db/kuradb/lib"
  ]
}

For Kura-native resources, prefer generated table references from kura.db.tables.

String names are still supported for quick scripts and backwards compatibility, but generated tables give you:

  • autocomplete on tables and columns
  • safer refactors when your schema changes
  • better LuaLS information for row shapes
  • consistent Lua field names even when SQL names are snake_case
local users = kura.db.tables.users -- example only; comes from your schema.ts

local rows = kura.db.select({ users.id, users.username, users.createdAt })
  :from(users)
  :where(op.eq(users.id, userId))
  :await()

print(rows[1].createdAt)

Quick-script and compatibility path:

local rows = kura.db.select({ 'id', 'username' })
  :from('users')
  :where(op.eq('id', userId))
  :await()

Those string inputs are still quoted as SQL identifiers. They are not raw SQL fragments. For custom SQL expressions, aliases, or PostgreSQL-specific statements, use the explicit raw SQL APIs.

Your first query

kura.db.ready(function()
  local users = kura.db.tables.users
  local rows = kura.db.select({ users.id, users.username })
    :from(users)
    :limit(10)
    :await()

  print(('Loaded %s users'):format(#rows))
end)
local users = kura.db.tables.users

-- Insert
local insertedId = kura.db.insert(users)
  :values({
    username = 'john',
    email = 'john@example.com',
  })
  :returningValue(users.id)
  :await()

-- Select
local row = kura.db.select({ users.id, users.username, users.createdAt })
  :from(users)
  :where(op.eq(users.id, insertedId))
  :single()
  :await()

-- Update
local updated = kura.db.update(users)
  :set({ username = 'jane' })
  :where(op.eq(users.id, insertedId))
  :await()

-- Delete
local deleted = kura.db.delete(users)
  :where(op.eq(users.id, insertedId))
  :await()

Raw SQL is still available

You do not need raw SQL for normal gameplay scripts.

Raw SQL remains available as an escape hatch for:

  • complex joins and CTEs
  • COPY, LISTEN, NOTIFY, and other PostgreSQL-specific workflows
  • one-off migrations or maintenance tasks
  • framework-specific queries that do not fit the builder cleanly
local rows = kura.db.raw.query.await(
  'SELECT * FROM "public"."users" WHERE id = $1',
  { userId }
)

Roadmap

The current builder covers the common CRUD and filtering path. These items are still planned and should be treated as roadmap, not current API:

  • builder-native valuesMany() for multi-row insert flows
  • explicit allowAllRows()-style guardrails for update() and delete()
  • PostgreSQL JSON and array operators in the builder
  • arithmetic and expression helpers for advanced updates

Kura architecture note

Use KuraDB directly for persistence and custom resources.

For core gameplay systems such as economy, inventory, vehicles, and characters, prefer higher-level Kura APIs when they exist. Those APIs can add validation, permissions, transactions, locks, sync, and audit behavior on top of the database layer.


What's next

  • Configuration — Convars, pool tuning, debug mode
  • Schema — Define tables in TypeScript and generate Lua refs
  • Migrations — Generate and apply SQL migrations
  • Select — Query builder usage and helpers
  • Insert — Inserts, returning helpers, and bulk insert notes
  • Operators — Filter operators with string and generated refs
  • Transactions — Raw and builder-style transaction flows
  • Raw SQL — Full PostgreSQL escape hatch

On this page