# Custom Formatter Extension API in fmtlib: How to Format User-Defined Types

> Learn to format user-defined types in fmtlib using the custom formatter extension API. Explore specializing fmt::formatter or defining format_as for full control over parsing and output.

- Repository: [Hello World Foundation/fmt](https://github.com/fmtlib/fmt)
- Tags: deep-dive
- Published: 2026-09-05

---

**The custom formatter extension API in fmtlib enables type formatting by specializing the `fmt::formatter<T>` template or by defining a `format_as` function, with the former providing full control over parsing and output generation.**

The fmtlib/fmt library provides a type-safe formatting system for C++ that can be extended to user-defined types through a well-documented extension interface. Understanding the custom formatter extension API is essential for integrating custom data structures into fmtlib's formatting pipeline, whether you are working with simple enums or complex nested objects.

## The `fmt::formatter` Specialization Interface

The primary mechanism for extending fmtlib with user-defined types is the `fmt::formatter<T>` class template specialization defined in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h).

### Required Member Functions

According to the source code in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h) and the documentation in [`doc/api.md`](https://github.com/fmtlib/fmt/blob/main/doc/api.md) (lines 79-87), a valid specialization must implement two const member functions:

- **`parse(format_parse_context& ctx)`**: Parses format specifiers appearing after the colon in a format field (e.g., `{:<10}`) and returns an iterator pointing to the closing brace.
- **`format(const T& value, format_context& ctx)`**: Writes the formatted representation of `value` into the output iterator supplied by `ctx`.

Both functions participate in the library's compile-time validation system when marked as `constexpr` or `consteval`.

## Reusing Existing Formatters via Inheritance

To avoid re-implementing common parsing logic for specifiers like fill, alignment, and width, custom formatters can inherit from existing formatters. The documentation in [`doc/api.md`](https://github.com/fmtlib/fmt/blob/main/doc/api.md) (lines 34-40) demonstrates inheriting from `fmt::formatter<std::string_view>` to reuse its `parse` implementation.

```cpp
#include <fmt/format.h>

enum class color { red, green, blue };

template <> struct fmt::formatter<color> : fmt::formatter<std::string_view> {
  // parse is inherited
  auto format(color c, fmt::format_context& ctx) const
      -> fmt::format_context::iterator {
    const char* name = "unknown";
    switch (c) {
      case color::red:   name = "red";   break;
      case color::green: name = "green"; break;
      case color::blue:  name = "blue";  break;
    }
    return fmt::formatter<std::string_view>::format(name, ctx);
  }
};

int main() {
  fmt::print("Color: {:>10}\n", color::blue);   // prints “      blue”
}

```

This pattern leverages the base formatter's specification parsing while customizing only the output generation in the derived `format` method.

## Handling Nested Structures with `fmt::nested_formatter`

For types containing sub-objects that require individual formatting specifications, fmtlib provides `fmt::nested_formatter`. As implemented in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h) (lines 99-104) and documented in [`doc/api.md`](https://github.com/fmtlib/fmt/blob/main/doc/api.md) (lines 70-78), this utility parses outer specifications and forwards member formatting to nested instances.

```cpp
#include <fmt/format.h>

struct point {
  double x, y;
};

template <>
struct fmt::formatter<point> : fmt::nested_formatter<double> {
  auto format(point p, fmt::format_context& ctx) const
      -> fmt::format_context::iterator {
    return write(ctx, "(", nested(p.x), ", ", nested(p.y), ")");
  }
};

int main() {
  fmt::print("[{:>20.2f}]\n", point{1, 2});   // prints “[          (1.00, 2.00)]”
}

```

The `nested()` helper applies the parsed format specifications (such as width `20` and precision `.2f`) to each individual member of the composite type.

## Alternative: The `format_as` Function

The custom formatter extension API also supports a simpler mechanism for types that need only basic conversion without custom parsing logic. By defining a `format_as` function in the same namespace as the type, you can delegate formatting to an existing type:

```cpp
#include <fmt/format.h>

struct wrapper {
  int value;
};

auto format_as(const wrapper& w) { return w.value; }

int main() {
  fmt::print("{}\n", wrapper{42});   // prints “42”
}

```

As noted in [`doc/api.md`](https://github.com/fmtlib/fmt/blob/main/doc/api.md) (lines 65-68), you cannot provide both `format_as` and a `fmt::formatter` specialization for the same type, as this creates an ambiguity in the resolution mechanism.

## Compile-Time Safety and Validation

The `parse` member function can be declared as `constexpr` (or `consteval` in C++20), enabling the custom formatter extension API to participate in compile-time format string validation. This ensures that invalid format specifications are diagnosed at compile time rather than runtime when using `FMT_STRING` macros or C++20 `consteval` contexts.

## Restrictions and Limitations

The fmtlib custom formatter extension API imposes specific constraints to maintain type safety and prevent ambiguity:

- **Pointer restrictions**: Formatting of raw non-void pointer types is deliberately disabled. You cannot specialize `fmt::formatter<T>` for pointer types.
- **Exclusive mechanisms**: As documented in the source, providing both `format_as` and a `formatter` specialization for the same type results in a compile-time ambiguity error.

## Summary

- The **custom formatter extension API** in fmtlib centers on specializing `fmt::formatter<T>` with `parse()` and `format()` member functions.
- **Inheritance** from existing formatters (like `fmt::formatter<std::string_view>`) allows reuse of parsing logic for standard specifiers.
- **`fmt::nested_formatter`** simplifies formatting composite types by applying specifications to individual members.
- **`format_as`** provides a lightweight alternative for simple type conversions without custom parsing.
- Both approaches support **compile-time validation** when implemented as `constexpr`.

## Frequently Asked Questions

### What is the difference between `format_as` and specializing `fmt::formatter`?

**`format_as`** is a non-intrusive function that converts your type to a formattable type, suitable for simple cases without custom format specifiers. **Specializing `fmt::formatter`** provides full control over parsing format strings and output generation, supporting custom alignment, width, and precision specifications.

### Can I create a custom formatter for pointer types?

No. The fmtlib source code explicitly disables formatting of non-void pointer types through the custom formatter extension API. This restriction prevents accidental formatting of raw memory addresses and encourages explicit handling of pointer semantics.

### How do I handle format specifiers like width and alignment in my custom formatter?

Inherit from an existing formatter (such as `fmt::formatter<std::string_view>`) to reuse its `parse` implementation, or implement `parse(format_parse_context& ctx)` manually to interpret specifiers. The `parse` method receives an iterator positioned after the colon and must advance to the closing brace, storing any parsed values in member variables for use in `format()`.

### Is compile-time format string checking available for custom formatters?

Yes. By declaring your `parse` method as `constexpr` or `consteval`, your custom formatter participates in fmtlib's compile-time validation. This catches invalid format specifications at compile time when using `FMT_STRING` or C++20 format strings.