# Creating Custom Pattern Flags with spdlog's PatternFormatter: A Complete Implementation Guide

> Learn to create custom pattern flags in spdlog with our complete guide. Extend spdlog's formatting by inheriting from custom_flag_formatter and registering your new flags.

- Repository: [Gabi Melman/spdlog](https://github.com/gabime/spdlog)
- Tags: how-to-guide
- Published: 2026-07-12

---

**You can extend spdlog's formatting capabilities by inheriting from `spdlog::custom_flag_formatter`, implementing the required `clone()` and `format()` methods, and registering your custom flag with `pattern_formatter::add_flag()` to use it in pattern strings alongside built-in flags like `%l` and `%v`.**

The `gabime/spdlog` library formats log lines through the **`pattern_formatter`** class, which parses pattern strings into a vector of **`flag_formatter`** objects. While spdlog ships with standard flags for timestamps, log levels, and messages, the architecture allows you to inject custom formatting logic by extending the `custom_flag_formatter` base class defined in [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h).

## How spdlog Pattern Formatting Works

When a log record is emitted, `pattern_formatter::format` iterates over an internal vector of formatter objects (`formatters_`) and calls each formatter's `format` method. The parsing happens when you call `set_pattern`, which resolves both built-in flags and user-registered custom flags.

The key components involved in custom flag creation include:

- **`spdlog::details::flag_formatter`** — The abstract base class that receives a log message, a `std::tm` time structure, and a destination buffer.
- **`spdlog::custom_flag_formatter`** — Extends `flag_formatter` and adds a pure-virtual `clone()` method required for deep-copying when formatters are duplicated.
- **`pattern_formatter::add_flag<T>(char flag, Args&&…args)`** — Stores a `std::unique_ptr<T>` in the internal `custom_handlers_` map, where `T` must derive from `custom_flag_formatter`. The character key (`flag`) is what you use in your pattern string (e.g., `%x`).
- **`custom_handlers_`** — A map inside `pattern_formatter` that associates flag characters with factory functions for creating formatter instances.

Custom formatters participate in the same pipeline as built-in ones and can leverage the same padding, truncation, and timezone facilities that spdlog provides.

## Step-by-Step: Creating a Custom Pattern Flag

### Step 1: Inherit from `custom_flag_formatter`

Create a class that inherits from `spdlog::custom_flag_formatter` and implement the two pure virtual methods: `clone()` and `format()`.

```cpp
#include <spdlog/pattern_formatter.h>

class hello_flag : public spdlog::custom_flag_formatter {
public:
    explicit hello_flag(std::string txt) : txt_(std::move(txt)) {}
    
    // Required implementation
    std::unique_ptr<spdlog::custom_flag_formatter> clone() const override;
    void format(const spdlog::details::log_msg&, const std::tm&, spdlog::memory_buf_t& dest) override;
    
private:
    std::string txt_;
};

```

### Step 2: Implement the `clone()` Method

The `clone()` method enables spdlog to deep-copy your formatter when the pattern formatter is duplicated. Use `spdlog::details::make_unique` to maintain compatibility with the library's memory management.

```cpp
std::unique_ptr<spdlog::custom_flag_formatter> hello_flag::clone() const override {
    return spdlog::details::make_unique<hello_flag>(txt_);
}

```

### Step 3: Implement the `format()` Method

The `format()` method receives the log message details, the time structure, and a reference to the destination buffer. Write your custom output directly to this buffer.

```cpp
void hello_flag::format(const spdlog::details::log_msg&, const std::tm&, spdlog::memory_buf_t& dest) override {
    dest.append(txt_.data(), txt_.data() + txt_.size());
}

```

### Step 4: Register with `pattern_formatter`

Instantiate a `pattern_formatter` and call `add_flag<T>()` with your flag character and any constructor arguments, then set the pattern using your new flag.

```cpp
auto fmt = std::make_shared<spdlog::pattern_formatter>();
fmt->add_flag<hello_flag>('h', "👋 Hello")
    .set_pattern("[%n] [%h] %v");

auto logger = spdlog::stdout_logger_mt("demo");
logger->set_formatter(fmt);
logger->info("world");

```

**Output:**

```

[demo] [👋 Hello] world

```

## Practical Examples

### Example 1: 12-Hour Time Format Flag

This custom flag formats the current time in 12-hour notation with AM/PM indicators.

```cpp
class time12_flag : public spdlog::custom_flag_formatter {
public:
    std::unique_ptr<spdlog::custom_flag_formatter> clone() const override {
        return spdlog::details::make_unique<time12_flag>();
    }

    void format(const spdlog::details::log_msg&, const std::tm& tm,
                spdlog::memory_buf_t& dest) override {
        auto formatted = spdlog::fmt_lib::format("{:d}:{:02d}{}",
            tm.tm_hour % 12 == 0 ? 12 : tm.tm_hour % 12,
            tm.tm_min,
            tm.tm_hour >= 12 ? "PM" : "AM");
        dest.append(formatted.data(), formatted.data() + formatted.size());
    }
};

```

Usage:

```cpp
auto fmt = std::make_shared<spdlog::pattern_formatter>();
fmt->add_flag<time12_flag>('t')
    .set_pattern("[%n] %t > %v");

```

**Result** (assuming local time is 14:05):

```

[demo] 2:05PM > Hello world

```

### Example 2: Supporting Padding and Truncation

The base class `custom_flag_formatter` inherits the `padinfo_` member of type `padding_info`. When your pattern includes width specifiers like `%5x` or `%-10x`, spdlog populates this structure. You can manually apply padding or use the base class utilities.

```cpp
class padded_flag : public spdlog::custom_flag_formatter {
public:
    explicit padded_flag(std::string txt) : txt_(std::move(txt)) {}

    std::unique_ptr<spdlog::custom_flag_formatter> clone() const override {
        return spdlog::details::make_unique<padded_flag>(txt_);
    }

    void format(const spdlog::details::log_msg&, const std::tm&, spdlog::memory_buf_t& dest) override {
        if (padinfo_.enabled()) {
            std::string padded(txt_.size() + padinfo_.width_, ' ');
            if (padinfo_.side_ == spdlog::details::padding_info::pad_side::right) {
                padded.replace(0, txt_.size(), txt_);
            } else {
                padded.replace(padded.size() - txt_.size(), txt_.size(), txt_);
            }
            dest.append(padded.data(), padded.data() + padded.size());
        } else {
            dest.append(txt_.data(), txt_.data() + txt_.size());
        }
    }

private:
    std::string txt_;
};

```

Usage:

```cpp
auto fmt = std::make_shared<spdlog::pattern_formatter>();
fmt->add_flag<padded_flag>('p', "data")
    .set_pattern("[%n] [%5p] %v");  // yields "[demo] [ data] message"

```

### Example 3: Error Handling with Exceptions

Custom flags can throw exceptions during formatting, which spdlog propagates as `spdlog::spdlog_ex`. This implementation triggers an error when a specific condition is met.

```cpp
class exploding_flag : public spdlog::custom_flag_formatter {
public:
    explicit exploding_flag(std::string trigger) : trigger_(std::move(trigger)) {}

    std::unique_ptr<spdlog::custom_flag_formatter> clone() const override {
        return spdlog::details::make_unique<exploding_flag>(trigger_);
    }

    void format(const spdlog::details::log_msg&, const std::tm&, spdlog::memory_buf_t&) override {
        if (trigger_ == "boom") {
            throw spdlog::spdlog_ex("exploding_flag triggered");
        }
    }

private:
    std::string trigger_;
};

```

When used with `add_flag<exploding_flag>('e', "boom")`, calling `format` throws a `spdlog::spdlog_ex` that propagates to the caller, as demonstrated in the test suite.

## Key Source Files and Implementation Details

The implementation of custom pattern flags spans the following files in the `gabime/spdlog` repository:

- **[`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h)** — Declares `pattern_formatter`, `custom_flag_formatter`, the `add_flag` template API, and the `padding_info` structure.
- **[`include/spdlog/pattern_formatter-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter-inl.h)** — Contains inline implementations used when `SPDLOG_HEADER_ONLY` is defined.
- **[`tests/test_pattern_formatter.cpp`](https://github.com/gabime/spdlog/blob/main/tests/test_pattern_formatter.cpp)** — Comprehensive unit tests demonstrating custom flags, padding behavior, truncation, and exception handling.

The `add_flag` template stores your custom formatter in the `custom_handlers_` map using perfect forwarding of constructor arguments, enabling flexible initialization of your flag objects.

## Summary

- **Inherit from `spdlog::custom_flag_formatter`** to create custom pattern flags that integrate with spdlog's formatting pipeline.
- **Implement `clone()`** to enable deep-copying of your formatter, using `spdlog::details::make_unique` for consistency.
- **Implement `format()`** to write custom output to the provided `memory_buf_t` buffer using the log message and time data.
- **Register with `add_flag<T>()`** on a `pattern_formatter` instance, specifying the flag character and constructor arguments.
- **Leverage `padinfo_`** to support standard spdlog padding and alignment specifiers in your custom flags.
- **Handle exceptions** carefully, as errors thrown in `format()` propagate as `spdlog::spdlog_ex`.

## Frequently Asked Questions

### What is the difference between `flag_formatter` and `custom_flag_formatter`?

The `flag_formatter` class is the internal abstract base used by all formatters, while `custom_flag_formatter` extends it specifically for user-defined flags by adding the pure-virtual `clone()` method. You must inherit from `custom_flag_formatter` (not `flag_formatter`) when creating custom flags to ensure proper deep-copy semantics when the pattern formatter is cloned.

### How do I handle padding and alignment in my custom flag?

The `custom_flag_formatter` base class provides a protected member `padinfo_` of type `padding_info`. When users specify width in the pattern (e.g., `%8x` or `%-5x`), spdlog automatically populates this structure. You can check `padinfo_.enabled()` to determine if padding is required, and access `padinfo_.width_` and `padinfo_.side_` to apply left, right, or center alignment manually in your `format()` implementation.

### Can I register multiple custom flags with the same pattern formatter?

Yes. The `pattern_formatter::add_flag()` method returns a reference to the formatter instance, allowing chained registration calls. Each custom flag must use a unique character identifier, and you can mix custom flags with built-in ones in the same pattern string.

### What happens if my custom flag throws an exception during formatting?

If your custom flag's `format()` method throws an exception, spdlog catches it and rethrows as a `spdlog::spdlog_ex`. This exception propagates up through the logging call stack, potentially terminating the application if not handled. The test suite in [`tests/test_pattern_formatter.cpp`](https://github.com/gabime/spdlog/blob/main/tests/test_pattern_formatter.cpp) includes specific test cases for exception handling in custom flags to ensure this behavior remains consistent across versions.