# How to Implement Custom Formatters for Your Own Types in fmtlib

> Learn to implement custom formatters for your own types in fmtlib. Discover how to specialize fmt::formatter or provide format_as overloads for flexible output.

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

---

**You can implement custom formatters in fmtlib by specializing the `fmt::formatter<T>` template for your type and defining `parse()` and `format()` methods, or by providing a `format_as()` overload for simple type conversions.**

The {fmt} library formats user-defined types by dispatching to specializations of the `formatter` template. When you implement custom formatters for your own types in fmtlib, you replace the primary template's deleted constructor with a concrete implementation that knows how to serialize your data structure according to the library's compile-time interface.

## Understanding the Formatter Interface

All formatting dispatch logic begins in [`include/fmt/core.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/core.h). The library instantiates the primary template:

```cpp
template <typename T, typename Char = char, typename Enable = void>
struct formatter {
  formatter() = delete;  // forces compilation error until specialized
};

```

Because the primary constructor is deleted, any attempt to format a type without a valid specialization results in a compile-time error. To make your type formattable, you must provide a specialization that implements at least the `format()` method, and optionally `parse()` if you need to handle custom format specifiers.

## Method 1: Reusing an Existing Formatter

For types that map cleanly to string representations, inherit from `fmt::formatter<std::string_view>` or another existing formatter. This approach delegates specifier handling to the base class while you only implement the conversion logic.

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

struct Point { int x, y; };

template <> struct fmt::formatter<Point> : fmt::formatter<std::string_view> {
  auto format(const Point& p, fmt::format_context& ctx) const {
    std::string s = std::to_string(p.x) + "," + std::to_string(p.y);
    return fmt::formatter<std::string_view>::format(s, ctx);
  }
};

// Usage
fmt::print("The point is {}\n", Point{3, 7});  // Output: The point is 3,7

```

This specialization lives in the `fmt` namespace and inherits the base `format()` method, allowing it to handle width and alignment specifiers automatically.

## Method 2: Creating a Full Custom Formatter

When you need custom parsing logic for format specifiers (such as precision control), implement both `parse()` and `format()` methods. The `parse()` method receives a `format_parse_context` to read the format string, while `format()` receives the value and an output iterator.

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

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

template <> struct fmt::formatter<Vec3> {
  constexpr auto parse(fmt::format_parse_context& ctx) {
    auto it = ctx.begin(), end = ctx.end();
    if (it != end && *it == '.') {
      ++it;
      precision_ = 0;
      while (it != end && std::isdigit(*it)) {
        precision_ = precision_ * 10 + (*it - '0');
        ++it;
      }
    }
    return it;
  }

  template <typename FormatContext>
  auto format(const Vec3& v, FormatContext& ctx) const {
    std::ostringstream oss;
    oss.setf(std::ios::fixed);
    oss.precision(precision_);
    oss << '(' << v.x << ", " << v.y << ", " << v.z << ')';
    return fmt::format_to(ctx.out(), "{}", oss.str());
  }

private:
  int precision_ = 6;
};

// Usage with custom precision
fmt::print("Vec = {: .2}\n", Vec3{1.2345, 6.789, 0.12});  // Vec = (1.23, 6.79, 0.12)

```

The `parse()` method must return an iterator pointing past the format specifiers it consumed, allowing the library to verify that the format string is valid at compile time.

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

For cases requiring only a simple type conversion without complex specifier support, provide a `format_as()` overload in the same namespace as your type. This avoids the boilerplate of a full template specialization according to the [`doc/api.md`](https://github.com/fmtlib/fmt/blob/main/doc/api.md) guidelines.

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

struct Money { long cents; };

inline fmt::string_view format_as(const Money& m) {
  static thread_local std::string buf;
  buf = std::to_string(m.cents / 100) + '.' + std::to_string(m.cents % 100);
  return fmt::string_view(buf);
}

// Usage without explicit formatter specialization
fmt::print("Price: ${}\n", Money{1234});  // Output: Price: $12.34

```

The library detects this overload through argument-dependent lookup and formats the result using the standard formatter for `fmt::string_view`.

## Key Implementation Files

The following files define the mechanisms described above:

- **[`include/fmt/core.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/core.h)** — Contains the primary `formatter` template with its deleted constructor and the dispatch logic that instantiates user specializations.
- **[`doc/api.md`](https://github.com/fmtlib/fmt/blob/main/doc/api.md)** — Documents the official interface requirements for custom formatters and `format_as` overloads, including namespace placement rules.

## Summary

- **Inherit from existing formatters** when your type converts easily to a string or standard type, leveraging specifier handling from the base class.
- **Implement `parse()` and `format()` directly** when you need to interpret custom format specifiers like precision or alignment flags.
- **Use `format_as()` overloads** for lightweight conversions that do not require parsing format strings or maintaining state between `parse()` and `format()` calls.
- The primary template in [`include/fmt/core.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/core.h) ensures type safety by deleting the generic constructor, forcing explicit specialization for every formattable user type.

## Frequently Asked Questions

### Do I need to implement both `parse()` and `format()` in a custom formatter?

You must implement `format()`, but `parse()` is optional. If you omit `parse()`, the formatter uses the default implementation that verifies the format string is empty (no custom specifiers). Implement `parse()` only when you need to interpret format specifiers like width or precision.

### Can I reuse formatting specifiers from standard types like `float` or `int`?

Yes. By inheriting from `fmt::formatter<float>` or `fmt::formatter<int>`, your custom formatter gains support for all standard numeric format specifiers. You only need to implement the conversion from your type to the underlying type in your `format()` method.

### What is the difference between `format_as` and a full formatter specialization?

`format_as` provides a one-way conversion to a formattable type without supporting custom specifiers for your type. A full `formatter<T>` specialization allows you to parse format strings and control output character-by-character. Use `format_as` for simple projections and full specializations for complex formatting logic.

### Where does fmtlib look for my formatter specialization?

The library instantiates `fmt::formatter<T>` in the `fmt` namespace. Your specialization must be declared in the global namespace or the `fmt` namespace before the first call to `fmt::format` with your type. The primary template definition in [`include/fmt/core.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/core.h) performs substitution failure unless it finds your valid specialization.