# How to Specialize `fmt::formatter<T>` for Custom Formatting in {fmt}

> Learn to specialize fmt::formatter<T> for custom types in the {fmt} library. Implement parse and format functions to control your type's output. Get started today.

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

---

**To format user-defined types with the {fmt} library, you must provide a full template specialization of `fmt::formatter<T>` that implements the `parse` and `format` member functions.**

The {fmt} library (available at `fmtlib/fmt`) formats values by searching for a specialization of the class template declared in [`include/fmt/core.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/core.h). The primary template is intentionally deleted (see line 2758), forcing users to explicitly define how their types should be rendered. This guide demonstrates the exact interface requirements and three common implementation patterns found in the source code.

## The `formatter<T>` Interface

A valid specialization must live in the `fmt` namespace and implement two specific member functions. The library invokes these automatically during format string processing.

| Function | Signature | Purpose |
|----------|-----------|---------|
| **parse** | `constexpr auto parse(fmt::format_parse_context& ctx) -> fmt::format_parse_context::iterator` | Parses the format specification (the content between `:` and `}`) and stores options like width or precision. |
| **format** | `auto format(const T& value, fmt::format_context& ctx) const -> fmt::format_context::iterator` | Writes the formatted representation of `value` to the output iterator `ctx.out()`. |

In [`include/fmt/core.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/core.h) (lines 2756–2762), the library provides a generic "native" formatter for built-in types via `detail::native_formatter`. For your custom type, you replace this with your own specialization.

## Method 1: Inheriting from Existing Formatters

When your type maps cleanly to a built-in representation—such as converting an enum to a string—inherit from an existing `formatter` to reuse its parsing logic.

According to [`doc/api.md`](https://github.com/fmtlib/fmt/blob/main/doc/api.md) (lines 34–56), you can inherit `fmt::formatter<std::string_view>` and override only the `format` method. The base class handles standard specifiers like width and alignment automatically.

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

enum class color { red, green, blue };

template <> struct fmt::formatter<color> : fmt::formatter<std::string_view> {
  auto format(color c, fmt::format_context& ctx) const 
      -> fmt::format_context::iterator {
    constexpr const char* names[] = {"red", "green", "blue"};
    return fmt::formatter<std::string_view>::format(
        names[static_cast<int>(c)], ctx);
  }
};

```

**Usage:**

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

int main() {
  // Uses inherited parse() to handle {:>10} alignment and width
  fmt::print("Color: {:>10}\n", color::green);
}
// Output: "Color:      green"

```

This approach avoids reimplementing standard format specifier parsing while giving you control over the final string conversion.

## Method 2: Implementing Custom Parsing Logic

For complex types requiring unique syntax—like a geometric point with coordinate formatting—you must implement `parse` manually to consume custom flags.

The following example from the source analysis demonstrates parsing a dynamic or static width specifier:

```cpp
#include <fmt/core.h>
#include <cctype>

struct point { double x, y; };

template <> struct fmt::formatter<point> {
  int width = 0;  // 0 means no width, -1 means dynamic width '*'

  constexpr auto parse(fmt::format_parse_context& ctx)
      -> fmt::format_parse_context::iterator {
    auto it = ctx.begin();
    
    if (it != ctx.end() && *it == '*') {
      ++it;
      width = -1;  // Signal dynamic width
    } else {
      const char* start = it;
      while (it != ctx.end() && std::isdigit(*it)) ++it;
      if (it != start) 
        width = std::stoi(std::string(start, it));
    }
    
    if (it != ctx.end() && *it != '}')
      throw fmt::format_error("invalid format for point");
    return it;
  }

  auto format(const point& p, fmt::format_context& ctx) const
      -> fmt::format_context::iterator {
    auto out = ctx.out();
    std::string spec = (width > 0) ? fmt::format(">{}", width) : "";
    
    out = fmt::format_to(out, "({:" + spec + "}", p.x);
    out = fmt::format_to(out, ", {:" + spec + "}", p.y);
    return fmt::format_to(out, ")");
  }
};

```

**Key requirements for `parse`:**
- It must return an iterator pointing to the closing `}`.
- It should be marked `constexpr` when possible.
- Errors should be reported via throwing `fmt::format_error` rather than other exceptions, or by calling `fmt::report_error` in constant evaluation contexts.

## Method 3: Conditionally Enabling Formatters with SFINAE

You can create generic formatters for families of types using SFINAE. As shown in [`include/fmt/core.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/core.h) (lines 2756–2762), the library uses `std::enable_if_t` to enable certain formatters only when `T` satisfies specific type traits.

```cpp
#include <type_traits>

template <typename T>
struct fmt::formatter<T, char, std::enable_if_t<std::is_enum_v<T>>> {
  // Implementation for all enums...
};

```

This pattern allows you to define formatting behavior for template metaprogramming patterns without explicitly listing every type.

## Critical Constraints and Best Practices

When specializing `fmt::formatter<T>`, adhere to these rules derived from the source code:

- **Namespace placement:** The specialization must reside in the global `fmt` namespace, not a nested namespace or your own.
- **Conflict prohibition:** You cannot provide both a `formatter<T>` specialization *and* a `format_as` overload for the same type (see [`doc/api.md`](https://github.com/fmtlib/fmt/blob/main/doc/api.md), lines 65–67). Choose one mechanism.
- **Output destination:** The `format` function must write to `ctx.out()`. Use `fmt::format_to` or the `detail::write` utilities to emit content.
- **Const correctness:** The `format` method should be marked `const` because the formatter object may be reused across multiple formatting calls.

## Summary

- **Specialize `fmt::formatter<T>`** in the `fmt` namespace to enable formatting for custom types.
- Implement **`parse`** to consume format specifiers (width, alignment, etc.) and **`format`** to write the output.
- **Inherit** from existing formatters like `fmt::formatter<std::string_view>` to reuse standard parsing logic for simple conversions.
- **Implement manually** when you need bespoke syntax, ensuring `parse` returns an iterator to `}` and `format` writes to `ctx.out()`.
- Do not combine `formatter<T>` specializations with `format_as` functions for the same type.

## Frequently Asked Questions

### Can I partially specialize `fmt::formatter` for a template class?

No, the {fmt} library requires full specializations. For template classes, you must fully specialize for each concrete instantiation or use SFINAE in the third template parameter (the `Enable` parameter) to conditionally match a family of types, as demonstrated in [`include/fmt/core.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/core.h) line 2756.

### Why does my compiler say the `formatter` base class is deleted?

The primary template `fmt::formatter<T, Char, Enable>` is defined with a deleted constructor at line 2758 of [`core.h`](https://github.com/fmtlib/fmt/blob/main/core.h). This error indicates you haven't provided a full specialization for your specific type `T`, or the specialization is not visible in the `fmt` namespace where the library looks it up.

### Should `parse` be `constexpr` and `format` be `const`?

Yes. Mark `parse` as `constexpr` to support compile-time format string validation. Mark `format` as `const` because the formatter instance may be reused or stored by the library. The signatures should match: `constexpr auto parse(format_parse_context&)` and `auto format(const T&, format_context&) const`.

### How do I report errors from the `parse` function?

Use `throw fmt::format_error("message")` for runtime errors, which the library catches and converts to appropriate exceptions. In C++20 constexpr contexts, you should call `fmt::report_error("message")` to trigger a compile-time error for invalid format strings.