Build and run a Logos core module
Scaffold, build, package, and test a core module on Logos.
This document is accurate for Testnet v0.3.
Logos is a modular application framework built on Qt 6. Applications are composed of dynamically loaded modules that provide features and functionality. Logos core modules are non-UI modules that provide backend functionality, loaded by a Logos runtime such as logosctl or Logos Basecamp.
You write a core module as one plain C++ class: its public methods are the module's API, and logos-module-builder generates the Qt plugin around it. This procedure scaffolds a module from the builder's template, builds and inspects it, packages it, and runs it with logosctl and with Logos Basecamp.
-
A supported OS:
- Linux x86_64 or aarch64
- macOS arm64 (Apple Silicon)
- Windows 10 or 11 x86_64, building inside WSL2. Nix does not run on Windows itself, so Windows packages are cross-compiled from Linux. See Build a Windows package.
-
At least 10 GB of disk space
-
Git
-
logosctlinstalled.- Install it by running
curl -fsSL https://raw.githubusercontent.com/logos-co/logos-docs/main/resources/scripts/install-logosctl.sh | sudo sh
- Install it by running
-
Nix with flakes enabled.
- Install from nixos.org, then enable flakes:
mkdir -p ~/.config/nixecho 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf -
Basic familiarity with C++ (C++17), CMake, and Nix concepts
What to expect
- You can scaffold and build a Logos core module from the
logos-module-buildertemplate. - You can inspect the compiled module's metadata, methods and events with the
lmCLI tool. - You can package the module, install it with
logosctland call its methods, or call them from Logos Basecamp.
Step 1: Scaffold the module project
The logos-module-builder provides a template for each kind of module. The default template produces a minimal core module: one C++ class and no UI.
-
Create a new directory using your module's name and initialise it from the module builder template. Replace
<module-name>with your module's name, for example,my_module.mkdir <module-name> && cd <module-name>nix flake init -t github:logos-co/logos-module-builder/0.3.2- The other templates are
#with-external-lib(see Wrap a C library as a Logos module),#ui-qml-backend(see Build a Logos C++ UI module),#ui-qml,#rustand#rust-with-external-lib.
- The other templates are
-
Review the project directory. The generated project structure looks like this:
<module-name>/├── flake.nix├── metadata.json├── CMakeLists.txt└── src/├── minimal_impl.h└── minimal_impl.cppThe template is a complete module named
minimal.src/minimal_impl.hdeclares its API, two methods (greetandgetStatus) and an event (greeted), and the build generates the Qt plugin from it. You replace theminimalplaceholders with your module's name in Step 2.infoThe
metadata.jsonfile is the single source of truth for your module. Read LGX package format and bundling reference for more details.
Step 2: Adapt the template for your module
Rename the minimal placeholders in the generated files to your module's name, then replace the example methods with your own. In this step, <ModuleName> is your module's name in PascalCase, for example, MyModule for my_module.
-
Edit
metadata.jsonand setname,display_name,description, andmainto match your module.namemust be a valid C identifier; it is used in file names, method calls, and module loading.mainis<module-name>_plugin, the plugin file name without its extension (.soor.dylib).- Leave the other fields in place, including
"interface": "universal"and thenixblock, which the build and the bundler rely on.
-
Rename the source files in
src/to match your module name.mv src/minimal_impl.h src/<module-name>_impl.hmv src/minimal_impl.cpp src/<module-name>_impl.cpp- The build looks for
src/<module-name>_impl.hand a class named<ModuleName>Impl. To use other names, setcodegen.impl_headerandcodegen.impl_classinmetadata.json.
- The build looks for
-
Edit both source files: rename the class
MinimalImplto<ModuleName>Impl, and makesrc/<module-name>_impl.cppinclude<module-name>_impl.h. -
Edit
CMakeLists.txtand update theproject()name and the twoSOURCESentries to the renamed files.logos_module()readsNAMEfrommetadata.json, so the two cannot drift apart.
-
Edit
flake.nixand update thedescriptionfield.- The generated
flake.nixuses an unpinnedlogos-module-builderURL. For reproducible builds, pin it:logos-module-builder.url = "github:logos-co/logos-module-builder/0.3.2";.
- The generated
-
Replace the example methods and event in
src/<module-name>_impl.handsrc/<module-name>_impl.cppwith your own, or keep them for now to follow the examples in this procedure.- Every
publicmethod is part of the module's API. Use the parameter and return types in the table in Wrap a C library as a Logos module, for exampleint64_tandstd::string, notintorQString. - A
///comment directly above a method or event becomes its description, whichlm,logosctl module showand Logos Basecamp display.
- Every
Run grep -ri "minimal" . after editing to catch any remaining placeholder references (minimal, Minimal, MinimalImpl) before building.
Step 3: Build the module
-
Initialise a Git repository.
git init && git add -A- Nix only sees files that Git tracks. Run
git addagain after you add new files.
- Nix only sees files that Git tracks. Run
-
Build the full module output (library and generated SDK headers).
nix build- Use
nix build '.#lib'to build only the plugin shared library. - Use
nix build '.#include'to build only the generated SDK headers. - The first build downloads Qt, the Logos SDK, and the code generators, and can take several minutes.
- Use
-
Verify the build output contains the plugin binary and generated headers:
result/├── lib/│ └── <module-name>_plugin.so # (or .dylib on macOS)└── include/├── <module-name>_api.h # Generated type-safe wrapper header└── <module-name>_api.cpp # Generated wrapper implementation
Step 4: Inspect your module
The lm tool (from logos-module) inspects compiled module binaries without loading them into a runtime.
-
Build the
lmtool from thelogos-modulerepository.nix build 'github:logos-co/logos-module/0.3.0#lm' --out-link ./lm -
View the module metadata and confirm the information is correct.
# Linux./lm/bin/lm metadata result/lib/<module-name>_plugin.so# macOS./lm/bin/lm metadata result/lib/<module-name>_plugin.dylib- Append
--jsonfor JSON output.
For example, for
my_modulewith the display nameMy Module:Plugin Metadata:================Name: my_moduleDisplay name: My ModuleVersion: 1.0.0Description: My first Logos moduleAuthor:Type: coreProtocol: 0.9.0Dependencies: (none) - Append
-
View the module methods.
# Linux./lm/bin/lm methods result/lib/<module-name>_plugin.so# macOS./lm/bin/lm methods result/lib/<module-name>_plugin.dylib- Append
--jsonfor JSON output. lm eventslists the module's events the same way.
With the template's example methods, the output looks like this:
Plugin Methods:===============tstr greet(tstr name)Signature: greet(tstr)Invokable: yesDescription: Returns a greeting and announces it as a typed `greeted` event.tstr getStatus()Signature: getStatus()Invokable: yesDescription: Returns a short status string.tstr name()Signature: name()Invokable: yesDescription: The module's name, as declared in its metadata.tstr version()Signature: version()Invokable: yesDescription: The module's version, as declared in its metadata.tstr lidl()Signature: lidl()Invokable: yesDescription: The module's canonical LIDL interface document.- Types are shown as the module's interface contract publishes them:
tstris a string, andintis a 64-bit integer. name(),version()andlidl()are generated for every module.lidl()returns the module's interface contract.
- Append
-
To browse the methods in a window instead, build the graphical
logos-module-viewerand open the module with it:nix build 'github:logos-co/logos-module-viewer/f8914691f3ac8be78b894d01b5d5ec6ce25d44f3#app' --out-link ./logos-viewer# Linux./logos-viewer/bin/logos-module-viewer -m ./result/lib/<module-name>_plugin.so# macOS./logos-viewer/bin/logos-module-viewer -m ./result/lib/<module-name>_plugin.dylib- The viewer has no release yet, so the command pins the commit this procedure was tested with.
- The viewer only loads modules that don't call other modules.
Step 5: Package the module
Before you can run your module with logosctl or install it into Logos Basecamp, you need to package the build output into an LGX package. Check out the LGX package format and bundling reference for more details on the format and bundling options.
The manifest.json is auto-generated from your module's metadata.json by the bundler. It maps each variant to its main entry point.
-
Build the portable package, which bundles every dependency and has no
/nix/storereferences:nix build '.#lgx-portable' --out-link result-lgx-portable- Released builds of
logosctland Logos Basecamp install only portable variants (for example,linux-amd64ordarwin-arm64).
- Released builds of
-
Build the development package, for development builds of Logos Basecamp:
nix build '.#lgx' --out-link result-lgx- The development variant (for example,
darwin-arm64-dev) references/nix/storepaths, so it works only on a machine with that Nix store. Development builds oflogosctland Logos Basecamp install only-devvariants.
- The development variant (for example,
-
Check that each output holds the package,
logos-<module-name>-module-lib.lgx:ls result-lgx-portable/ result-lgx/
Build a Windows package
Windows packages are cross-compiled: Nix runs on Linux and targets x86_64-windows. On a Windows machine, that Linux is WSL2. The result installs with the Windows build of logosctl.
-
Set up WSL2 with Ubuntu. In PowerShell, run:
wsl --install -d Ubuntu-24.04- Restart Windows if prompted, then open Ubuntu 24.04 from the Start menu and create your Linux user.
- WSL2 needs hardware virtualisation. If Windows itself runs in a virtual machine, enable nested virtualisation for that VM.
-
In the Ubuntu terminal, install Nix and open a new terminal afterwards:
sh <(curl -L https://nixos.org/nix/install) --daemon -
Enable flakes and the Logos binary cache, which serves the prebuilt Windows toolchain (Qt, the MinGW libraries, and the Logos SDK) so that you don't compile it yourself. The Nix daemon reads these settings from
/etc/nix/nix.conf:sudo tee -a /etc/nix/nix.conf <<'EOF'experimental-features = nix-command flakesextra-substituters = https://cache.nix.logos.co/publicextra-trusted-public-keys = public:l4HrXgL4nw246+LBh2SOJyhz64BoGegOYLheT/iIAPU=EOFsudo systemctl restart nix-daemon -
Scaffold or clone your module in the Linux file system, for example under
~/, rather than under/mnt/c. Nix and Git are much slower on the Windows drive. -
Build the portable Windows package:
nix build .#packages.x86_64-windows.lgx-portableresult/logos-<module-name>-module-lib.lgxholds awindows-x86_64variant whose plugin is a.dll.- Build the portable variant: a
-devvariant references/nix/storepaths that don't exist on Windows. - Every dependency of your module needs a Windows build too.
-
Copy the package to your Windows user folder:
cp -L result/logos-<module-name>-module-lib.lgx /mnt/c/Users/<windows-user>/Downloads/ -
In PowerShell, with
logosctlinstalled byinstall-logosctl.ps1, install, load, and call the module. Confirm the install prompt, or pass-y:logosctl daemon start --detachlogosctl package install --file $HOME\Downloads\logos-<module-name>-module-lib.lgxlogosctl module load <module-name>logosctl call <module-name> <method> <args>The first build in a fresh WSL2 Ubuntu downloads about 500 MB from the binary cache and takes a few minutes; later builds reuse it.
Step 6: Install the module
Before you can run your module with logosctl, install the LGX package. logosctl package install unpacks it into the current session's modules/ directory (~/.logosctl/modules/ unless --config-dir or LOGOSCTL_CONFIG_DIR says otherwise), the same session that logosctl daemon start reads from.
There are two ways to install a module:
- Install a locally built
.lgxpackage - Install a package by name from the catalogue
Install a locally built .lgx package
-
Start the
logosctldaemon in detached mode.logosctl daemon start --detach- The command returns once the daemon accepts commands.
-
Install the portable
.lgxpackage:logosctl package install --file ./result-lgx-portable/*.lgx -y-yapplies the install without asking for confirmation.- Use
--dirinstead of--fileto install all LGX packages in a directory at once:logosctl package install --dir ./packages/ -y
-
Verify the installed module:
logosctl package lsYour module is listed with the source
user, next to the packages embedded inlogosctl. The session'smodules/directory now contains a subdirectory withmanifest.json, the plugin binary (.soor.dylib), and avariantfile.
Install a package by name from the catalogue
The Logos module catalogue is hosted on GitHub Releases in the logos-modules-release repository. logosctl package install fetches a named package straight from the catalogue and installs it in one step.
Catalogue packages ship portable variants only (for example, linux-amd64, darwin-arm64). They install into logosctl and into released builds of Logos Basecamp, but not into a development build of Logos Basecamp, which expects -dev variants.
-
Start the
logosctldaemon in detached mode, if it is not running yet.logosctl daemon start --detach -
Refresh the catalogue, then search it for the module you want to install. Replace
<query>with what you're looking for (for example,chat).logosctl catalog refreshlogosctl package search <query>tipRun
logosctl package searchwith no query to browse all available packages. -
Install it by the name from the search results (for example,
chat_module).logosctlresolves the name against the catalogue, downloads the package and its dependencies, and unpacks them into the session'smodules/(orplugins/, for a UI module) in one step.logosctl package install <module-name> --yes- Add
--versionto pin a specific release instead of the newest one, and--root-hashto pin the exact published package identity.
- Add
Step 7: Run the module
There are two Logos runtimes that can load and run your module: logosctl and Logos Basecamp.
Run with logosctl
The logosctl CLI (from logos-logoscore-cli) is a headless runtime that can load modules and invoke their methods from the command line. It runs as a daemon that stays alive to host modules.
-
Load the module and call a method. Replace
<method>and<args>with the method name and arguments you want to call.logosctl module load <module-name>logosctl call <module-name> <method> <args>- With the template's example methods,
logosctl call my_module greet WorldprintsHello, World! Greetings from the minimal module. logosctl module show <module-name>lists the module's methods and events with their descriptions.
- With the template's example methods,
-
Stop the daemon when finished.
logosctl daemon stop
Check out Logos CLI Reference for more details on available commands and options.
Run with Logos Basecamp
Logos Basecamp is a local-first launcher for peer-to-peer apps built on Logos, with a graphical interface for managing and running modules. Its Module Inspector lists every loaded core module and lets you call its methods, so you can try your module without writing a UI module for it.
These steps use a development build of Logos Basecamp, which installs the development package from Step 5, with a data directory of its own. To use the released Logos Basecamp instead, install the portable package as described in Install and load a module in Basecamp.
-
Build the development version of Logos Basecamp and the
lgpmpackage manager, which installs packages into a directory of your choice:nix build 'github:logos-co/logos-basecamp/0.3.2' --out-link ./logos-basecampnix build 'github:logos-co/logos-package-manager/0.3.0#cli' --out-link ./pm -
Create a data directory for Logos Basecamp, and install the development package into its
modules/directory:mkdir -p basecamp-data/modules basecamp-data/plugins./pm/bin/lgpm --modules-dir basecamp-data/modules install --file ./result-lgx/*.lgxlgpmwarns that the package is unsigned, which is expected for a package you built yourself.
-
Launch Logos Basecamp with that data directory:
./logos-basecamp/bin/LogosBasecamp --user-dir "$PWD/basecamp-data"--user-dirkeeps this data apart from any other Logos Basecamp you run. Logos Basecamp reads core modules from itsmodules/subdirectory and UI modules fromplugins/.
-
Open Settings, select Module Inspector, and click Load in your module's row.
Expected result: the button changes to Unload once the module is loaded.
-
Click Interface in your module's row.
Expected result: each method is listed with its signature, its description and a Call button.
-
Click Call next to a method that takes no arguments, such as the template's
getStatus.Expected result: the Result area shows what the method returned, as JSON. For
getStatus, that is{"result":"Minimal module is running."}.- Call passes no arguments, so a method that takes parameters reports
invalid_args. Call those methods withlogosctl callinstead.
- Call passes no arguments, so a method that takes parameters reports
Try running the Blockchain module, Storage module or Chat module or browse the Logos module catalogue. logosctl package search lists it from the command line.
Troubleshooting
Known constraints
A single logosctl daemon instance supports only one instance of a module, so all dependent apps share that state. To run multiple independent instances from the CLI, start separate daemons under separate sessions (logosctl daemon start --config-dir <dir> for each), so each gets its own installed modules and state. From Logos Basecamp, start separate instances with their own user directories (--user-dir); each one runs its own embedded runtime and module instances in isolation.
Nix reports an "experimental features" error
If you see errors about experimental features, either pass the flag:
nix --extra-experimental-features "nix-command flakes" build
Or add the following to ~/.config/nix/nix.conf:
experimental-features = nix-command flakes
The build fails with Failed to open header file or unknown type name
The build derives the implementation from the module's name in metadata.json: it parses src/<module-name>_impl.h and expects a class named <ModuleName>Impl in it.
- A header with another name stops the build with
Error parsing impl header: Failed to open header file: src/<module-name>_impl.h. - A class with another name fails in the generated code with
error: unknown type name '<ModuleName>Impl'.
Rename the file or the class to match name, or set codegen.impl_header and codegen.impl_class in metadata.json to the names you use.
The build reports that the module declares no interface
A core module must keep "interface": "universal" in metadata.json. Without it, the build stops with logos-module-builder: module '<module-name>' is a core module shipping a plugin (main: <module-name>_plugin) but declares no interface. Add the field back.
Module not discovered by Logos Basecamp
Confirm that you launched Logos Basecamp with the --user-dir you installed into, and that the module is in a subdirectory of its modules/ directory (for example, basecamp-data/modules/my_module/) containing the module binary and manifest.json. A development build of Logos Basecamp needs the -dev variant from .#lgx; a released build needs the portable variant from .#lgx-portable.
Also confirm you installed the module into the base directory the running instance actually reads, and restarted it afterwards. A rebuilt package never reaches a running Basecamp on its own. See Troubleshoot Logos module development with Basecamp.
Module not discovered by logosctl
Confirm the module is in a subdirectory of the session's modules/ directory (for example, modules/my_module/) and that the subdirectory contains a manifest.json with a main object matching your OS and architecture. Run logosctl package ls to see what the current session actually has installed.
logosctl package install fails
Verify the session's modules/ directory (under --config-dir, default ~/.logosctl) exists and is writable. If installing from a local .lgx file, confirm the file path is correct (the bundler writes logos-<module-name>-module-lib.lgx, not <module-name>.lgx). If installing from the catalogue, check your internet connection and that you've run logosctl catalog refresh recently. To pin a specific version instead of the newest one, pass --version (and optionally --root-hash) to logosctl package install.
If installing into Logos Basecamp's data directory with the standalone lgpm, verify that the target directory exists and is writable instead.
LGX variant mismatch
If logosctl package install fails with an error such as Package does not contain variant for platform: darwin-arm64 (package provides: darwin-arm64-dev), the package has no variant of the kind that logosctl installs.
nix build '.#lgx'produces a single-devvariant (for example,linux-amd64-dev) for development builds oflogosctland Logos Basecamp. Released builds reject it.nix build '.#lgx-portable'produces a single portable variant (for example,linux-amd64) for released builds oflogosctland Logos Basecamp.
Catalogue packages ship portable variants only. To put both variants, or variants for several platforms, in one package, see the LGX package format and bundling reference.
nix build .#lib does nothing or fails silently
Some shells (notably zsh) treat # as a comment character outside quotes. Quote the flake reference so the # reaches Nix intact.
nix build '.#lib'
First build is slow
The first nix build downloads Qt 6, the Logos C++ SDK, the code generator, and the rest of the build dependencies. This is a one-time cost; subsequent builds reuse the cache.