Skip to main content
C++ modules are an advanced feature. They require familiarity with C++, the server’s internal architecture, and a full server rebuild before changes take effect. If a Lua or SQL module can achieve your goal, prefer those instead.
C++ modules let you hook directly into the server engine: intercept network packets, override engine-level behavior, and add functionality that cannot be expressed in Lua alone. They compile into the server binary and are registered with the REGISTER_CPP_MODULE macro.

How C++ modules work

A C++ module is a class that inherits from CPPModule. You override one or more lifecycle methods (OnInit, OnIncomingPacket, etc.) and register it at the bottom of the file. At compile time, CMake picks up any .cpp files listed in init.txt and links them into the server.
class MyModule : public CPPModule
{
    void OnInit() override
    {
        // Runs once when the server starts
    }

    auto OnIncomingPacket(MapSession* session, CCharEntity* PChar, CBasicPacket& packet) -> bool override
    {
        // Intercept incoming packets. Return true to consume the packet,
        // false to pass it to the original handler.
        return false;
    }
};

REGISTER_CPP_MODULE(MyModule);

Module registration pattern

The required headers and registration macro are in map/utils/moduleutils.h. Always include this header, plus any headers your module needs:
#include "map/utils/moduleutils.h"

// Include other headers as needed
#include "common/database.h"
#include "map/map_session.h"
#include "map/packet_system.h"

class MyModule : public CPPModule
{
    void OnInit() override
    {
        // initialization logic
    }
};

REGISTER_CPP_MODULE(MyModule);

Example: AH Announcement

This module intercepts Auction House purchase packets and sends a chat message to the seller when their item sells.
modules/custom/cpp/ah_announcement.cpp
#include "common/database.h"
#include "map/ipc_client.h"
#include "map/map_session.h"
#include "map/packet_system.h"
#include "map/packets/basic.h"
#include "map/utils/itemutils.h"
#include "map/utils/moduleutils.h"
#include "packets/c2s/0x04e_auc.h"
#include "utils/auctionutils.h"

extern std::function<void(MapSession* const, CCharEntity* const, CBasicPacket&)> PacketParser[512];

class AHAnnouncementModule : public CPPModule
{
    void OnInit() override
    {
        const auto originalHandler = PacketParser[0x04E];

        const auto newHandler = [originalHandler](
            MapSession* const PSession,
            CCharEntity* const PChar,
            CBasicPacket& data) -> void
        {
            // Only intercept action 0x0E: Purchasing Items
            const auto action = data.ref<uint8>(0x04);
            if (action == 0x0E)
            {
                // ... send notification to seller ...
            }
            else
            {
                originalHandler(PSession, PChar, data);
            }
        };

        PacketParser[0x04E] = newHandler;
    }
};

REGISTER_CPP_MODULE(AHAnnouncementModule);

Example: AH Pagination

This module overrides the AH listing behavior to support more than the client’s built-in 7-item limit by cycling through pages.
modules/custom/cpp/ah_pagination.cpp
#include "map/utils/moduleutils.h"

class AHPaginationModule : public CPPModule
{
    void OnInit() override
    {
        const auto originalAHListLimit = settings::get<uint32>("map.AH_LIST_LIMIT");
        totalPages_ = originalAHListLimit == 0 ? 99 : (originalAHListLimit / 6U) + 1;
    }

    auto OnIncomingPacket(
        MapSession* session,
        CCharEntity* PChar,
        CBasicPacket& packet) -> bool override
    {
        if (packet.getType() != 0x04E)
            return false;

        // ... pagination logic ...
        return true;
    }

    uint8 itemsPerPage_{ 6u };
    uint8 totalPages_{ 1 };
};

REGISTER_CPP_MODULE(AHPaginationModule);

Registering in init.txt

List your .cpp file in modules/init.txt:
init.txt
custom/cpp/ah_announcement.cpp
custom/cpp/ah_pagination.cpp
CMake scans init.txt at configure time and includes all listed .cpp files in the build. You must re-run CMake and recompile after any change to init.txt or your module source.

Building with Docker

Because C++ modules must be compiled into the binary, a volume mount is not sufficient. You need to build a custom Docker image that includes your module files.
1

Fork or clone the repository

Your .cpp files must be present in the source tree when the image is built. Place them in modules/custom/cpp/ and update init.txt.
2

Build the image

docker build -f docker/ubuntu.Dockerfile \
  --tag myserver/server:latest \
  --build-arg REPO_URL="https://github.com/USERNAME/server" \
  --build-arg COMMIT_SHA="$(git rev-parse HEAD)" \
  .
The REPO_URL and COMMIT_SHA build args are required for dbtool to function correctly and to satisfy the server’s license attribution requirements.
3

Run your custom image

Replace ghcr.io/landsandboat/server:latest with your custom image tag in your docker run command or docker-compose.yml.
The Docker build supports additional build args: COMPILER, GCC_VERSION, CMAKE_BUILD_TYPE, WARNINGS_AS_ERRORS, and others. See docker/README.md for the full list.

Key headers

HeaderPurpose
map/utils/moduleutils.hCPPModule base class and REGISTER_CPP_MODULE macro
map/packet_system.hPacketParser array for intercepting packets
map/map_session.hMapSession type
common/database.hdb::preparedStmt for database queries
map/packets/basic.hCBasicPacket and ref<T> accessor

Build docs developers (and LLMs) love