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:

  1. Update src/modules/modules.h to include your header (maintain alphabetical order):

    #include "modules/foo/foo.h"
  2. Update src/modules/modules.c to add the module pointer to the appropriate alphabetic array. For a module named "Foo", insert &ffFooModuleInfo into the F[] array before the terminating NULL:

    static FFModuleBaseInfo* F[] = {
        &ffFooModuleInfo,
        /* ... other F-modules ... */
        NULL,
    };
  3. Update CMakeLists.txt to add your source file to the FASTFETCH_SOURCES list:

    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 — Defines FFModuleBaseInfo and formatting macros like FF_PRINT_FORMAT_CHECKED and FF_FORMAT_ARG_LIST.
  • src/modules/os/os.c — Reference implementation showing detection, printing, and JSON handling patterns (see ffPrintOS, ffParseOSJsonObject, and ffOSModuleInfo).
  • src/modules/modules.c — Contains the alphabetic module tables and the master ffModuleInfos[] array.

Summary

  • Create source files (foo.c and foo.h) implementing detection, printing, JSON handling, and option lifecycle functions.
  • Populate FFModuleBaseInfo with function pointers for initOptions, destroyOptions, printModule, parseJsonObject, generateJsonResult, and generateJsonConfig, plus a list of format arguments.
  • Include the header in src/modules/modules.h to expose symbols during compilation.
  • Register the pointer in the appropriate alphabetic array in src/modules/modules.c so the CLI can discover the module.
  • Update CMakeLists.txt to include the new source file in FASTFETCH_SOURCES.
  • Rebuild and test using --module, --format, and --json flags 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →