Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Elitriare/ByteNet-Max/llms.txt

Use this file to discover all available pages before exploring further.

Namespaces are the top-level organizational unit in ByteNet Max. Each namespace is a named container — created with defineNamespace — that holds one or both of a packets table and a queries table. When your game starts, the server assigns sequential numeric IDs to every packet and query inside every namespace, then replicates that mapping to all clients so both sides can encode and decode the same data without you manually tracking any IDs.

Creating a namespace

Call ByteNetMax.defineNamespace with a string name and a function that returns a table. The table can contain a packets key, a queries key, or both.
local ByteNetMax = require(path.to.ByteNetMax)

return ByteNetMax.defineNamespace("PlayerData", function()
    return {
        packets = {
            Notify = ByteNetMax.definePacket({
                value = ByteNetMax.struct({
                    message = ByteNetMax.string,
                }),
            }),
        },
        queries = {
            GetCoins = ByteNetMax.defineQuery({
                request = ByteNetMax.struct({
                    message = ByteNetMax.string,
                }),
                response = ByteNetMax.struct({
                    coins = ByteNetMax.uint8,
                }),
            }),
        },
    }
end)
The return value of defineNamespace is a table with packets and queries sub-tables. You require this module anywhere you need to send or listen to messages under that namespace.

How ID assignment works

When the server first requires a namespace module, it increments a global counter for each packet and for each query, writes those IDs into a replicated value store, then makes them available to the packet and query objects. When a client later requires the same module, it reads the already-replicated IDs from the store and wires them to its local packet and query objects. This is why both sides must require the module: the client cannot encode or decode messages until it has read the server-assigned IDs.
-- server (Script)
local PlayerData = require(game.ReplicatedStorage.PlayerData)

-- client (LocalScript)
local PlayerData = require(game.ReplicatedStorage.PlayerData)
Both calls point at the same ModuleScript. Requiring it on the server populates the ID store; requiring it on the client reads from that store.
You must require the namespace ModuleScript on both the server and the client. Skipping the require on either side means that side cannot send or receive any packets or queries defined in that namespace, because the ID mapping will never be initialized.

Accessing packets and queries

After requiring the namespace, access its members through the .packets and .queries sub-tables:
local PlayerData = require(game.ReplicatedStorage.PlayerData)

-- Accessing a packet
PlayerData.packets.Notify.listen(function(data, player)
    print(data.message)
end)

-- Accessing a query
PlayerData.queries.GetCoins.listen(function(data, player)
    return { coins = player.leaderstats.Coins.Value }
end)

Organizing namespaces

There is no limit on the number of namespaces you can define, and each namespace has its own independent ID counter for both packets and queries. A common pattern is one namespace per feature area — for example "Combat", "PlayerData", and "UI" — each living in its own ModuleScript under ReplicatedStorage.
If you have no packets in a namespace, you can omit the packets table entirely and only return queries. Likewise, if you have no queries, you can omit queries and only return packets. ByteNet Max treats a missing key the same as an empty table.

Build docs developers (and LLMs) love