How to Add New Modules to Fastfetch: A Complete Developer Guide
Adding a new module to fastfetch requires creating C source files that implement detection and output logic, defining an FFModuleBaseInfo struct with lifecycle callbacks, and registering the module pointer in the global module table located in src/modules/modules.c.
The fastfetch-cli/fastfetch repository uses a plug-in-like architecture where every system information category—such as OS, CPU, or GPU—is implemented as a discrete module. By following the established patterns in the codebase, you can extend fastfetch with custom detection logic that supports command-line flags, JSON output, and format strings without modifying the core engine.
Architectural Overview
Fastfetch modules follow a strict contract defined by the FFModuleBaseInfo struct in src/common/option.h. This struct holds function pointers for initialization, destruction, printing, JSON parsing, and JSON generation, along with metadata like the module name and description.
| Component | Role | Key Source File |
|---|---|---|
FFModuleBaseInfo |
Struct holding function pointers and metadata (name, description, format arguments). | src/common/option.h (lines 21-38) |
Module header (foo.h) |
Declares the public API and the external ffFooModuleInfo symbol. |
src/modules/foo/foo.h |
Module implementation (foo.c) |
Implements detection logic, printing, JSON handling, and fills the FFModuleBaseInfo instance. |
src/modules/foo/foo.c |
modules.h |
Central include file that exposes all module headers to the compiler. | src/modules/modules.h |
modules.c |
Contains the alphabetic module tables (A[] through Z[]) combined into ffModuleInfos[], used by the CLI to discover modules. |
src/modules/modules.c |
CMakeLists.txt |
Build configuration that must list new source files to ensure compilation. | Top-level CMakeLists.txt |
When fastfetch initializes, it iterates over ffModuleInfos[], calling each module's initOptions callback and later invoking printModule or generateJsonResult based on user input.
Step-by-Step Implementation
Create the Module Directory
Create a new folder under src/modules/ for your module. Use a concise, lowercase name representing the information you are detecting.
mkdir -p src/modules/foo
touch src/modules/foo/foo.h
touch src/modules/foo/foo.c
Write the Module Header
The header file declares the print function, option lifecycle functions, and the external FFModuleBaseInfo instance. Follow the pattern established in existing modules like src/modules/os/os.h.
#pragma once
#include "common/option.h"
#define FF_FOO_MODULE_NAME "Foo"
bool ffPrintFoo(FFFooOptions* options);
void ffInitFooOptions(FFFooOptions* options);
void ffDestroyFooOptions(FFFooOptions* options);
/* Exported description used by the module table */
extern FFModuleBaseInfo ffFooModuleInfo;
Implement the Module Logic
The implementation file must provide five core components: detection logic, printing, JSON config parsing, JSON result generation, and option lifecycle management.
#include "common/printing.h"
#include "common/jsonconfig.h"
#include "common/option.h"
#include "modules/foo/foo.h"
/* 1. Detection logic */
static const char* detectFoo(void)
{
/* Replace with actual detection (e.g., reading /proc, sysctls, or APIs) */
return "FooOS 1.2.3";
}
/* 2. Printing */
bool ffPrintFoo(FFFooOptions* options)
{
const char* value = detectFoo();
if (!value) {
ffPrintError(FF_FOO_MODULE_NAME, 0, &options->moduleArgs,
FF_PRINT_TYPE_DEFAULT, "Could not detect Foo");
return false;
}
FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();
if (options->moduleArgs.key.length == 0) {
ffStrbufSetStatic(&key, FF_FOO_MODULE_NAME);
} else {
FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key,
((FFformatarg[]) { FF_ARG(value, "value") }));
}
FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs,
FF_PRINT_TYPE_NO_CUSTOM_KEY,
((FFformatarg[]) { FF_ARG(value, "value") }));
return true;
}
/* 3. JSON config parsing */
void ffParseFooJsonObject(FFFooOptions* options, yyjson_val* module)
{
yyjson_val *key, *val;
size_t idx, max;
yyjson_obj_foreach (module, idx, max, key, val) {
if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs))
continue;
ffPrintError(FF_FOO_MODULE_NAME, 0, &options->moduleArgs,
FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s",
unsafe_yyjson_get_str(key));
}
}
/* 4. JSON result generation */
bool ffGenerateFooJsonResult(FFFooOptions* options,
yyjson_mut_doc* doc, yyjson_mut_val* module)
{
const char* value = detectFoo();
if (!value) {
yyjson_mut_obj_add_str(doc, module, "error", "Could not detect Foo");
return false;
}
yyjson_mut_obj_add_str(doc, module, "result", value);
return true;
}
/* 5. JSON config generation */
void ffGenerateFooJsonConfig(FFFooOptions* options,
yyjson_mut_doc* doc, yyjson_mut_val* module)
{
ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);
}
/* 6. Options lifecycle */
void ffInitFooOptions(FFFooOptions* options)
{
ffOptionInitModuleArg(&options->moduleArgs, ""); /* Icon glyph */
}
void ffDestroyFooOptions(FFFooOptions* options)
{
ffOptionDestroyModuleArg(&options->moduleArgs);
}
/* 7. Module registration */
FFModuleBaseInfo ffFooModuleInfo = {
.name = FF_FOO_MODULE_NAME,
.description = "Print information about Foo",
.initOptions = (void*) ffInitFooOptions,
.destroyOptions = (void*) ffDestroyFooOptions,
.parseJsonObject = (void*) ffParseFooJsonObject,
.printModule = (void*) ffPrintFoo,
.generateJsonResult = (void*) ffGenerateFooJsonResult,
.generateJsonConfig = (void*) ffGenerateFooJsonConfig,
.formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {
{"Detected Foo value", "value"},
}))
};
Register the Module in the Build System
Three files must be updated to expose your module to the fastfetch CLI:
-
Update
src/modules/modules.hto include your header (maintain alphabetical order):#include "modules/foo/foo.h" -
Update
src/modules/modules.cto add the module pointer to the appropriate alphabetic array. For a module named "Foo", insert&ffFooModuleInfointo theF[]array before the terminatingNULL:static FFModuleBaseInfo* F[] = { &ffFooModuleInfo, /* ... other F-modules ... */ NULL, }; -
Update
CMakeLists.txtto add your source file to theFASTFETCH_SOURCESlist:src/modules/foo/foo.c
Minimal "Hello" Module Example
For a quick test, here is a minimal module that prints a static string without complex detection logic. Create src/modules/hello/hello.h and src/modules/hello/hello.c:
hello.h
#pragma once
#include "common/option.h"
#define FF_HELLO_MODULE_NAME "Hello"
bool ffPrintHello(FFHelloOptions* options);
void ffInitHelloOptions(FFHelloOptions* options);
void ffDestroyHelloOptions(FFHelloOptions* options);
extern FFModuleBaseInfo ffHelloModuleInfo;
hello.c
#include "common/printing.h"
#include "common/option.h"
#include "modules/hello/hello.h"
bool ffPrintHello(FFHelloOptions* options)
{
const char* msg = "Hello, Fastfetch!";
FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();
if (options->moduleArgs.key.length == 0)
ffStrbufSetStatic(&key, FF_HELLO_MODULE_NAME);
else
FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key,
((FFformatarg[]) { FF_ARG(msg, "msg") }));
FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs,
FF_PRINT_TYPE_NO_CUSTOM_KEY,
((FFformatarg[]) { FF_ARG(msg, "msg") }));
return true;
}
void ffInitHelloOptions(FFHelloOptions* options)
{
ffOptionInitModuleArg(&options->moduleArgs, "👋");
}
void ffDestroyHelloOptions(FFHelloOptions* options)
{
ffOptionDestroyModuleArg(&options->moduleArgs);
}
FFModuleBaseInfo ffHelloModuleInfo = {
.name = FF_HELLO_MODULE_NAME,
.description = "Print a hello message",
.initOptions = (void*) ffInitHelloOptions,
.destroyOptions = (void*) ffDestroyHelloOptions,
.printModule = (void*) ffPrintHello,
.formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {
{"Static message", "msg"},
}))
};
After updating modules.h, modules.c (adding to the H[] array), and CMakeLists.txt, rebuild and test.
Testing Your New Module
Build the project and verify your module integrates correctly with the command-line interface:
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
# Test basic output
./fastfetch --module Foo
# Test format strings
./fastfetch --Foo-format "{value}"
# Test JSON output
./fastfetch --json --module Foo
# Verify module appears in the list
./fastfetch --list-modules | grep -i foo
Key Files to Reference
src/common/option.h— DefinesFFModuleBaseInfoand formatting macros likeFF_PRINT_FORMAT_CHECKEDandFF_FORMAT_ARG_LIST.src/modules/os/os.c— Reference implementation showing detection, printing, and JSON handling patterns (seeffPrintOS,ffParseOSJsonObject, andffOSModuleInfo).src/modules/modules.c— Contains the alphabetic module tables and the masterffModuleInfos[]array.
Summary
- Create source files (
foo.candfoo.h) implementing detection, printing, JSON handling, and option lifecycle functions. - Populate
FFModuleBaseInfowith function pointers forinitOptions,destroyOptions,printModule,parseJsonObject,generateJsonResult, andgenerateJsonConfig, plus a list of format arguments. - Include the header in
src/modules/modules.hto expose symbols during compilation. - Register the pointer in the appropriate alphabetic array in
src/modules/modules.cso the CLI can discover the module. - Update
CMakeLists.txtto include the new source file inFASTFETCH_SOURCES. - Rebuild and test using
--module,--format, and--jsonflags to verify full functionality.
Frequently Asked Questions
What is the minimum code required to add a new module to fastfetch?
The absolute minimum requires a header file declaring ffPrintModule and FFModuleBaseInfo, an implementation file defining these symbols, and registration in modules.c. However, to support fastfetch's full feature set—including JSON output and format strings—you must also implement ffParseJsonObject, ffGenerateJsonResult, and ffGenerateJsonConfig functions and populate the formatArgs field in your FFModuleBaseInfo struct.
How does fastfetch discover available modules at runtime?
Fastfetch does not use dynamic loading. Instead, it relies on a static array ffModuleInfos[] defined in src/modules/modules.c, which aggregates alphabetic sub-arrays (A[] through Z[]). When you add your module's FFModuleBaseInfo pointer to the appropriate letter array, the CLI iterator can find it during startup for help text generation and command-line parsing.
Can I add a module without implementing JSON support?
Technically, you can omit the JSON-related callbacks, but this is not recommended. The parseJsonObject field is required for configuration file support, and generateJsonResult is required for --json output. If you omit these, set the corresponding FFModuleBaseInfo fields to NULL, though this will limit functionality. Existing modules like os.c demonstrate the standard pattern for full JSON compliance.
Where should I place the icon glyph for my module?
Pass the icon string as the second argument to ffOptionInitModuleArg() inside your ffInitOptions function. For example, ffOptionInitModuleArg(&options->moduleArgs, "") sets the default icon displayed when users run fastfetch with icons enabled. Choose an appropriate Nerd Font or Unicode glyph that represents the hardware or software your module detects.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →