Skip to main content
The Database component provides access to MongoDB for all persistent data storage. It uses a dual-database architecture with separate databases for authentication and game data.

Overview

Access the Database component via Database.Game (for game data) or Database.Auth (for authentication data).
Database.Game    -- Characters, vehicles, inventory, properties, phone data, etc.
Database.Auth    -- Accounts, bans
Server-Side Only: Database operations are only available on the server. Never attempt database queries from client-side code.
Params Table Pattern: All MongoDB methods use a single params table as the first argument — NOT separate positional arguments. See examples below.

Call Signature

Every database method follows the same pattern:
Database.Game:methodName({
    collection = "collectionName",
    query = { ... },        -- for find/update/delete
    document = { ... },     -- for insertOne
    documents = { ... },    -- for insert (bulk)
    update = { ... },       -- for update/updateOne
    options = { ... },      -- optional MongoDB options
    limit = 10,             -- optional result limit (find)
    aggregate = { ... },    -- pipeline stages (aggregate only)
}, function(success, result, insertedIds)
    -- success (boolean) - whether the operation succeeded
    -- result - data on success, error message string on failure
    -- insertedIds - array of IDs (insert operations only)
end)

Methods

findOne

Find a single document matching the query.
Database.Game:findOne({
    collection = "characters",
    query = { SID = 1 }
}, function(success, results)
    if success and results and #results > 0 then
        local character = results[1]
        print("Found:", character.First, character.Last)
    end
end)
findOne internally calls find with limit = 1. The callback receives an array — access the first element with results[1].
With projection (return only specific fields):
Database.Game:findOne({
    collection = "characters",
    query = { SID = 1 },
    options = {
        projection = { First = 1, Last = 1, Cash = 1 }
    }
}, function(success, results)
    if success and results and #results > 0 then
        -- results[1] only contains First, Last, Cash, _id
    end
end)

find

Find multiple documents matching the query.
Database.Game:find({
    collection = "characters",
    query = { ["Jobs.Id"] = "police" }
}, function(success, results)
    if success then
        print("Found", #results, "police officers")
    end
end)
With limit:
Database.Game:find({
    collection = "characters",
    query = {},
    limit = 10
}, function(success, results)
    if success then
        for _, char in ipairs(results) do
            print(char.First, char.Last)
        end
    end
end)
With options (sort, projection):
Database.Game:find({
    collection = "characters",
    query = {},
    options = {
        sort = { createdAt = -1 },
        projection = { First = 1, Last = 1, _id = 0 }
    },
    limit = 20
}, function(success, results)
    -- 20 most recent characters, names only
end)

insertOne

Insert a single document.
Database.Game:insertOne({
    collection = "characters",
    document = {
        SID = 1,
        First = "John",
        Last = "Doe",
        DOB = "1990-01-15",
        Gender = "Male",
        createdAt = os.time()
    }
}, function(success, count, ids)
    if success then
        print("Inserted with ID:", ids[1])
    end
end)
insertOne wraps the document into a single-element array and calls insert internally. The callback signature is (success, insertedCount, arrayOfIds).

insert

Insert multiple documents at once (bulk insert).
Database.Game:insert({
    collection = "logs",
    documents = {
        { type = "join", player = "John", timestamp = os.time() },
        { type = "join", player = "Jane", timestamp = os.time() },
        { type = "join", player = "Bob", timestamp = os.time() }
    }
}, function(success, insertedCount, ids)
    if success then
        print("Inserted", insertedCount, "documents")
    end
end)
The method name is insert, NOT insertMany. The params.documents field must be an array of document tables.

updateOne

Update a single document matching the query.
Database.Game:updateOne({
    collection = "characters",
    query = { SID = 1 },
    update = {
        ["$set"] = {
            job = "police",
            grade = "officer"
        }
    }
}, function(success, modifiedCount)
    if success then
        print("Modified", modifiedCount, "documents")
    end
end)
Increment values:
Database.Game:updateOne({
    collection = "characters",
    query = { SID = 1 },
    update = {
        ["$inc"] = { cash = 500 }
    }
})
Push to array:
Database.Game:updateOne({
    collection = "characters",
    query = { SID = 1 },
    update = {
        ["$push"] = { licenses = "drivers_license" }
    }
})
Multiple operations at once:
Database.Game:updateOne({
    collection = "characters",
    query = { SID = 1 },
    update = {
        ["$set"] = { job = "police", lastUpdated = os.time() },
        ["$inc"] = { arrests = 1 }
    }
})

update

Update all documents matching the query (bulk update).
Database.Game:update({
    collection = "characters",
    query = { job = "police", onDuty = true },
    update = {
        ["$set"] = { onDuty = false }
    }
}, function(success, modifiedCount)
    if success then
        print("Clocked out", modifiedCount, "officers")
    end
end)
The method name is update, NOT updateMany. Without the internal isUpdateOne flag, update applies to ALL matching documents.

deleteOne

Delete a single document matching the query.
Database.Game:deleteOne({
    collection = "characters",
    query = { SID = 1 }
}, function(success, deletedCount)
    if success and deletedCount > 0 then
        print("Character deleted")
    end
end)

delete

Delete all documents matching the query (bulk delete).
local thirtyDaysAgo = os.time() - (30 * 24 * 60 * 60)

Database.Game:delete({
    collection = "logs",
    query = { timestamp = { ["$lt"] = thirtyDaysAgo } }
}, function(success, deletedCount)
    if success then
        print("Deleted", deletedCount, "old logs")
    end
end)
The method name is delete, NOT deleteMany. Without the internal isDeleteOne flag, delete removes ALL matching documents.

findOneAndUpdate

Atomically find a document, update it, and return the result.
Database.Game:findOneAndUpdate({
    collection = "sequences",
    query = { _id = "characterSID" },
    update = {
        ["$inc"] = { value = 1 }
    },
    options = {
        returnDocument = "after"  -- return the updated document
    }
}, function(success, document)
    if success and document then
        print("New sequence value:", document.value)
    end
end)
This method is useful for atomic operations like generating sequential IDs.

count

Count documents matching the query.
Database.Game:count({
    collection = "characters",
    query = { job = "police", onDuty = true }
}, function(success, count)
    if success then
        print("Online police:", count)
    end
end)

aggregate

Run an aggregation pipeline.
Database.Game:aggregate({
    collection = "characters",
    aggregate = {
        {
            ["$group"] = {
                _id = "$job",
                count = { ["$sum"] = 1 }
            }
        },
        {
            ["$sort"] = { count = -1 }
        }
    }
}, function(success, results)
    if success then
        for _, result in ipairs(results) do
            print(result._id, "has", result.count, "members")
        end
    end
end)
The pipeline stages go in params.aggregate (NOT a separate pipeline argument).

Auth Database

The Auth database has the same methods as Game. Use it for accounts and bans:
-- Find account
Database.Auth:findOne({
    collection = "accounts",
    query = { Identifier = identifier }
}, function(success, results)
    if success and results and #results > 0 then
        local account = results[1]
        print("Account:", account.AccountID)
    end
end)

-- Check for ban
Database.Auth:findOne({
    collection = "bans",
    query = { Identifier = identifier, active = true }
}, function(success, results)
    if success and results and #results > 0 then
        print("Player is banned:", results[1].reason)
    end
end)

Deprecated Methods

The top-level Database:method() calls (without .Game or .Auth) still work but print deprecation warnings. Always use Database.Game:method() or Database.Auth:method().
-- ❌ Deprecated (prints warning)
Database:findOne({ collection = "characters", query = { SID = 1 } }, callback)

-- ✅ Correct
Database.Game:findOne({ collection = "characters", query = { SID = 1 } }, callback)

MySQL (oxmysql)

MySQL is available via the oxmysql resource for the inventory system and compatibility. It is NOT accessed through the Database component — use oxmysql exports directly:
-- oxmysql exports (not part of Database component)
exports.oxmysql:execute("SELECT * FROM inventory WHERE id = ?", { itemId }, function(results)
    -- Process results
end)

exports.oxmysql:scalar("SELECT COUNT(*) FROM inventory", {}, function(count)
    print("Total items:", count)
end)

exports.oxmysql:insert("INSERT INTO inventory (name, item_id, slot) VALUES (?, ?, ?)", {
    name, itemId, slot
}, function(insertId)
    print("Inserted row:", insertId)
end)
See oxmysql documentation for full API reference.

Best Practices

Never use the top-level deprecated methods:
-- ✅ Good
Database.Game:findOne({ collection = "characters", query = { SID = 1 } }, cb)
Database.Auth:findOne({ collection = "accounts", query = { id = 1 } }, cb)

-- ❌ Bad (deprecated, prints warning)
Database:findOne({ collection = "characters", query = { SID = 1 } }, cb)
Database.Game:findOne({
    collection = "characters",
    query = { SID = id }
}, function(success, results)
    if not success then
        print("Database error:", results) -- results is error message on failure
        return
    end

    if not results or #results == 0 then
        print("Character not found")
        return
    end

    local character = results[1]
    -- Safe to use character
end)
All methods take a single params table. The fields vary by method:
MethodRequired FieldsOptional Fields
find / findOnecollection, queryoptions, limit
insertOnecollection, documentoptions
insertcollection, documentsoptions
updateOne / updatecollection, query, updateoptions
deleteOne / deletecollection, queryoptions
findOneAndUpdatecollection, query, updateoptions
countcollection, queryoptions
aggregatecollection, aggregate
Create indexes for frequently queried fields:
// MongoDB shell
db.characters.createIndex({ SID: 1 }, { unique: true })
db.inventory.createIndex({ owner: 1 })
db.vehicles.createIndex({ owner: 1, plate: 1 })
db.logs.createIndex({ timestamp: -1 })

Next Steps

Base API

Core framework exports

Logger API

Logging component

Database Architecture

Understanding the database system

Database Setup

Configure databases