# How to Add New Modules to Fastfetch: A Complete Developer Guide

> Learn how to add new modules to fastfetch with this developer guide. Implement detection and output logic in C, define module info, and register your module.

- Repository: [fastfetch-cli/fastfetch](https://github.com/fastfetch-cli/fastfetch)
- Tags: how-to-guide
- Published: 2026-03-30

---

**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`](https://github.com/fastfetch-cli/fastfetch/blob/main/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`](https://github.com/fastfetch-cli/fastfetch/blob/main/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`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/common/option.h) (lines 21-38) |
| Module header ([`foo.h`](https://github.com/fastfetch-cli/fastfetch/blob/main/foo.h)) | Declares the public API and the external `ffFooModuleInfo` symbol. | [`src/modules/foo/foo.h`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/modules/foo/foo.h) |
| Module implementation ([`foo.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/foo.c)) | Implements detection logic, printing, JSON handling, and fills the `FFModuleBaseInfo` instance. | [`src/modules/foo/foo.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/modules/foo/foo.c) |
| [`modules.h`](https://github.com/fastfetch-cli/fastfetch/blob/main/modules.h) | Central include file that exposes all module headers to the compiler. | [`src/modules/modules.h`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/modules/modules.h) |
| [`modules.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/modules.c) | Contains the alphabetic module tables (`A[]` through `Z[]`) combined into `ffModuleInfos[]`, used by the CLI to discover modules. | [`src/modules/modules.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/modules/modules.c) |
| [`CMakeLists.txt`](https://github.com/fastfetch-cli/fastfetch/blob/main/CMakeLists.txt) | Build configuration that must list new source files to ensure compilation. | Top-level [`CMakeLists.txt`](https://github.com/fastfetch-cli/fastfetch/blob/main/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.

```bash
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`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/modules/os/os.h).

```c
#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.

```c
#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`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/modules/modules.h)** to include your header (maintain alphabetical order):
   ```c
   #include "modules/foo/foo.h"
   ```

2. **Update [`src/modules/modules.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/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`:
   ```c
   static FFModuleBaseInfo* F[] = {
       &ffFooModuleInfo,
       /* ... other F-modules ... */
       NULL,
   };
   ```

3. **Update [`CMakeLists.txt`](https://github.com/fastfetch-cli/fastfetch/blob/main/CMakeLists.txt)** to add your source file to the `FASTFETCH_SOURCES` list:
   ```cmake
   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`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/modules/hello/hello.h) and [`src/modules/hello/hello.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/modules/hello/hello.c):

**hello.h**

```c
#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**

```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`](https://github.com/fastfetch-cli/fastfetch/blob/main/modules.h), [`modules.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/modules.c) (adding to the `H[]` array), and [`CMakeLists.txt`](https://github.com/fastfetch-cli/fastfetch/blob/main/CMakeLists.txt), rebuild and test.

## Testing Your New Module

Build the project and verify your module integrates correctly with the command-line interface:

```bash
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`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/common/option.h)** — Defines `FFModuleBaseInfo` and formatting macros like `FF_PRINT_FORMAT_CHECKED` and `FF_FORMAT_ARG_LIST`.
- **[`src/modules/os/os.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/modules/os/os.c)** — Reference implementation showing detection, printing, and JSON handling patterns (see `ffPrintOS`, `ffParseOSJsonObject`, and `ffOSModuleInfo`).
- **[`src/modules/modules.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/modules/modules.c)** — Contains the alphabetic module tables and the master `ffModuleInfos[]` array.

## Summary

- **Create source files** ([`foo.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/foo.c) and [`foo.h`](https://github.com/fastfetch-cli/fastfetch/blob/main/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`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/modules/modules.h) to expose symbols during compilation.
- **Register the pointer** in the appropriate alphabetic array in [`src/modules/modules.c`](https://github.com/fastfetch-cli/fastfetch/blob/main/src/modules/modules.c) so the CLI can discover the module.
- **Update [`CMakeLists.txt`](https://github.com/fastfetch-cli/fastfetch/blob/main/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`](https://github.com/fastfetch-cli/fastfetch/blob/main/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`](https://github.com/fastfetch-cli/fastfetch/blob/main/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`](https://github.com/fastfetch-cli/fastfetch/blob/main/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.