# How to Create User-Defined Formatters in fmtlib: A Complete Guide

> Learn how to create user-defined formatters in fmtlib with this complete guide. Implement custom parse and format functions or provide a format_as overload for seamless integration.

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

---

**You can create user-defined formatters in fmtlib by specializing the `fmt::formatter<T, Char>` template to implement custom `parse` and `format` member functions, or by providing a lightweight `format_as` overload that converts your type to a natively supported format.**

fmtlib/fmt is a modern C++ formatting library that provides fast, type-safe string formatting similar to Python's `str.format`. While the library natively handles strings, numbers, and standard containers, formatting user-defined types requires extending the library's formatting machinery through template specializations or conversion functions.

## Understanding the Formatter Architecture

The `{fmt}` library discovers how to format a type `T` by looking for a specialization of the class template `fmt::formatter<T, Char>`. If no specialization exists, the primary template attempts to forward the value to `fmt::format_as(T)` when that function is available.

### Where the Machinery Lives

The formatting infrastructure is distributed across three primary headers in the repository:

- **[`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h)** – Contains the primary definition of the `formatter` template and the hook points for user-defined specializations.
- **[`include/fmt/format-inl.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format-inl.h)** – Houses the default `formatter` specializations for built-in types like integers and floating-point numbers.
- **[`include/fmt/core.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/core.h)** – Defines core utilities including `format_as` and type traits used by the formatter machinery.

## Method 1: Specializing `fmt::formatter` for Full Control

For complete control over parsing format specifiers and generating output, specialize the `formatter` template for your type. This approach requires implementing two member functions: `parse` to handle the format specification string, and `format` to write the representation.

### Implementing the `parse` Function

The `parse` function receives a `format_parse_context` and must advance the iterator past any format specifiers your formatter recognizes, stopping at the closing brace `}`. The context provides access to the format string through `ctx.begin()` and `ctx.end()`.

### Implementing the `format` Function

The `format` function receives a constant reference to your object and a `FormatContext`. It must write to the output iterator returned by `ctx.out()` and return the iterator position after writing.

### Complete Example: Formatting a Point Structure

The following example demonstrates a custom formatter for a `Point` struct that supports width and alignment specifiers:

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

struct Point {
  int x, y;
};

template <typename Char>
struct fmt::formatter<Point, Char> {
  constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
    // Accept default specifiers; advance to closing '}'
    auto it = ctx.begin();
    while (it != ctx.end() && *it != '}') ++it;
    return it;
  }

  template <typename FormatContext>
  auto format(const Point& p, FormatContext& ctx) const -> decltype(ctx.out()) {
    return fmt::format_to(ctx.out(), "({},{})", p.x, p.y);
  }
};

int main() {
  Point pt{3, 7};
  fmt::print("Point = {}\n", pt);        // Output: Point = (3,7)
  fmt::print("Point = {:>12}\n", pt);    // Output: Point =         (3,7)
}

```

## Method 2: Using `format_as` for Simple Conversions

When you only need to convert your type to an already-supported format (such as a string or integer), implementing a full formatter specialization is unnecessary. Instead, provide a `format_as` overload in the `fmt` namespace.

According to the source code in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h) at line 2272, the primary `formatter` specialization automatically looks up and calls `format_as` when no user-defined specialization exists.

### When to Use `format_as`

Use this approach when your type has a clear string or numeric representation and you do not need to support custom format specifiers like width, precision, or alignment flags.

### Example: Converting a Person to String

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

struct Person {
  std::string name;
  int age;
};

inline std::string fmt::format_as(const Person& p) {
  return p.name + " (" + std::to_string(p.age) + ")";
}

int main() {
  Person alice{"Alice", 30};
  fmt::print("{}\n", alice);   // Output: Alice (30)
  fmt::print("{:>20}\n", alice); // Width works via string formatter
}

```

## Advanced: Custom Format Specifiers

To support custom format options (such as outputting polar coordinates with a `'p'` flag), store parsing state as data members in your formatter specialization:

```cpp
template <typename Char>
struct fmt::formatter<Point, Char> {
  bool polar = false;

  constexpr auto parse(format_parse_context& ctx) {
    auto it = ctx.begin();
    if (it != ctx.end() && *it == 'p') {
      polar = true;
      ++it;
    }
    return it;
  }

  template <typename Ctx>
  auto format(const Point& p, Ctx& ctx) const {
    if (polar) {
      return fmt::format_to(ctx.out(), "{:.2f}∠{:.2f}", 
                           std::hypot(p.x, p.y), 
                           std::atan2(p.y, p.x));
    }
    return fmt::format_to(ctx.out(), "({},{})", p.x, p.y);
  }
};

// Usage: fmt::print("{:p}\n", point);

```

## Summary

- **`fmt::formatter<T, Char>`** is the template you specialize to create user-defined formatters, requiring `parse` and `format` member functions.
- **[`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h)** contains the primary template definition and hook points for customization.
- **`format_as`** provides a lightweight alternative when you only need to convert your type to a natively supported representation.
- The **`format_parse_context`** handles parsing specifiers after the colon, while **`FormatContext`** provides the output iterator via `ctx.out()`.
- Custom formatters automatically inherit support for standard specifiers like width and alignment when properly implemented.

## Frequently Asked Questions

### What header files do I need to include to create a custom formatter?

You must include `<fmt/format.h>` or `<fmt/core.h>`. The primary `formatter` template and `format_as` hook are defined in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h), while core utilities and type traits live in [`include/fmt/core.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/core.h). For implementation details of built-in formatters, reference [`include/fmt/format-inl.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format-inl.h).

### Can I use `format_as` and a custom formatter specialization together?

No, the library selects one mechanism based on availability. The primary `formatter` specialization checks for `format_as` only when no user-defined `formatter<T, Char>` specialization exists. If you provide both, the explicit specialization takes precedence.

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

The default width and alignment handling occurs before your `format` function receives control when you use `fmt::format_to` with the provided context. To manually handle these specifiers, inspect the `ctx` argument for width information or delegate to the library's internal formatting utilities defined in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h).

### Where is the `format_as` lookup performed in the fmtlib source?

The lookup occurs in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h) at approximately line 2272 within the primary `formatter` template specialization. This location defines the fallback mechanism that calls `format_as` when no explicit formatter specialization is found for a given type.