# How to Write a Custom Formatter for User-Defined Types in {fmt}

> Learn to write a custom formatter for user-defined types in fmtlib. Implement parse and format methods to control output and handle specifiers effectively.

- Repository: [Hello World Foundation/fmt](https://github.com/fmtlib/fmt)
- Tags: how-to-guide
- Published: 2026-09-04

---

**{fmt} formats user-defined types by specializations of the class template `fmt::formatter<T, Char>`, which you implement with `parse()` to handle format specifiers and `format()` to write the output representation.**

The {fmt} library (fmtlib/fmt) provides fast, type-safe formatting for C++, but integrating your own structs and classes requires teaching the library how to display them. According to the source code, this is accomplished by specializing the `fmt::formatter` template declared in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h) and implementing two required member functions.

## Understanding the fmt::formatter Specialization

The core mechanism relies on template specialization. When you call `fmt::print` or `fmt::format` with a custom type, the compiler searches for a specialization of `fmt::formatter<T, Char>` where `T` is your type and `Char` defaults to `char`.

The primary template resides in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h). To add support for your type, you provide a full specialization inside the `fmt` namespace containing two methods:

- **`parse`** – Reads the format specifier string (e.g., width, precision, or custom flags) from a `format_parse_context`
- **`format`** – Converts the value to text and writes it into a `format_context` using `fmt::format_to`

## Creating a Basic Custom Formatter

For types that do not require custom formatting flags, implement a minimal `parse` method that immediately returns and a `format` method that constructs the string representation.

Consider a simple `Point` struct:

```cpp
struct Point {
  int x, y;
};

/* Specialize fmt::formatter for Point */
template <>
struct fmt::formatter<Point> {
  // No format specifier supported – ignore the parse step.
  constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
    // Return an iterator pointing to the end of the specifier.
    return ctx.begin();
  }

  // Convert Point to a string like "(x,y)"
  template <typename FormatContext>
  auto format(const Point& p, FormatContext& ctx) const -> decltype(ctx.out()) {
    // Delegate to the library’s built-in integer formatter.
    return fmt::format_to(ctx.out(), "({},{})", p.x, p.y);
  }
};

```

Usage requires only the standard headers:

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

Point pt{3, 7};
fmt::print("The point is {}.\n", pt);   // → The point is (3,7).

```

### The parse Method

The `parse` function receives a `format_parse_context` iterator pointing to the character after the opening `{`. If you do not support custom specifiers, simply return `ctx.begin()` immediately. The library assumes the format specifier ends at the closing `}`.

### The format Method

The `format` function receives a const reference to your object and a `format_context`. Use `fmt::format_to(ctx.out(), format_string, args...)` to write output. This function returns an iterator to the insertion point, which you must return from your `format` method.

## Handling Format Specifiers

For advanced use cases—such as accepting precision or width—store parsed values as member variables and reference them during formatting.

The following `Vec3` formatter accepts an optional precision specifier like `{:.3f}`:

```cpp
struct Vec3 {
  double x, y, z;
};

template <>
struct fmt::formatter<Vec3> {
  // Store desired precision (default 2)
  int precision = 2;

  // Parse something like {:.3f}
  constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
    auto it = ctx.begin();
    if (it != ctx.end() && *it == '.') {
      ++it;
      precision = 0;
      while (it != ctx.end() && std::isdigit(*it)) {
        precision = precision * 10 + (*it - '0');
        ++it;
      }
    }
    // Reject unexpected characters.
    if (it != ctx.end() && *it != '}')
      throw fmt::format_error("invalid format for Vec3");
    return it;
  }

  // Format each component with the requested precision.
  template <typename FormatContext>
  auto format(const Vec3& v, FormatContext& ctx) const -> decltype(ctx.out()) {
    std::string fmt_str = "{:." + std::to_string(precision) + "f}";
    return fmt::format_to(
        ctx.out(),
        "(" + fmt_str + ", " + fmt_str + ", " + fmt_str + ")",
        v.x, v.y, v.z);
  }
};

```

This enables precision control:

```cpp
Vec3 v{1.2345, 6.789, 0.12345};
fmt::print("Default: {}\n", v);          // → (1.23, 6.79, 0.12)
fmt::print("Precise: {:.4f}\n", v);     // → (1.2345, 6.7890, 0.1235)

```

### Parsing Custom Flags

Inside `parse`, iterate the context iterator to examine characters. The iterator starts at the first character of the format specifier and ends at `ctx.end()`. Extract numerical values manually, then validate that the final character is `}`.

### Dynamic Format String Construction

When you need to apply parsed parameters (like precision) to sub-values, construct a temporary format string using `std::to_string` or similar utilities, then pass it to `fmt::format_to` along with the data members.

## Key Implementation Files

Reference the following source files in fmtlib/fmt to study production-grade patterns:

- **[`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h)** – Contains the declaration of `template <typename T, typename Char> struct formatter`, which you must specialize.
- **[`include/fmt/std.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/std.h)** – Defines formatters for standard library types like `std::chrono::duration` and `std::optional`. These demonstrate best practices for complex parsing and nested formatting.
- **[`doc/api.md`](https://github.com/fmtlib/fmt/blob/main/doc/api.md)** – Documents the formatter interface requirements and parsing rules.
- **`test/format.cc`** – Contains test cases with temporary types and their formatter specializations, providing practical usage examples.

## Summary

- **Specialize** `template<> struct fmt::formatter<YourType>` inside the `fmt` namespace to teach the library your type’s layout.
- **Implement `parse()`** to consume format specifiers from `format_parse_context`; return `ctx.begin()` if you require no custom flags.
- **Implement `format()`** to emit the desired representation via `fmt::format_to(ctx.out(), ...)`, optionally delegating to existing formatters for primitive members.
- **Study [`include/fmt/std.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/std.h)** for authoritative examples of handling optional values, ranges, and time points.
- Include `<fmt/format.h>` (or `<fmt/core.h>` for basic usage) before your specialization to ensure the primary template is visible.

## Frequently Asked Questions

### Where is the fmt::formatter template defined?

The primary template is declared in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h). The compiler attempts to instantiate `fmt::formatter<T, Char>` for every type you format; providing a specialization visible in the `fmt` namespace overrides this lookup.

### Do I need to implement both parse and format methods?

Yes, both are required interface members. However, if your formatter does not support custom flags, implement `parse` as a single line returning `ctx.begin()` to indicate the format specifier is empty.

### Can I reuse existing formatters inside my custom format method?

Absolutely. Call `fmt::format_to(ctx.out(), "{}", member)` or construct dynamic format strings. The library dispatches these arguments to their respective `formatter` specializations automatically, allowing you to compose complex formatters from primitive ones.

### How do I throw errors for invalid format specifiers?

Within `parse`, inspect the context iterator. If you encounter unexpected characters, throw `fmt::format_error("description")`. This exception propagates to the caller with a diagnostic message, matching the behavior of standard library formatters.