How to Integrate {fmt} into a CMake Project: 3 Proven Methods
The {fmt} library exposes namespaced CMake targets (fmt::fmt for the compiled library and fmt::fmt-header-only for header-only use) that can be consumed via add_subdirectory, find_package, or FetchContent.
Integrating a modern C++ formatting library into your build system should be straightforward. The {fmt} library—hosted at fmtlib/fmt—provides a clean CMake configuration that makes adding it to CMake projects simple. This guide covers three integration methods, explains the available targets, and shows how to integrate {fmt} based on analysis of its official source code.
Available CMake Targets in {fmt}
The root CMakeLists.txt defines several well-structured targets designed for different use cases:
| Target | Type | Purpose | Source Location |
|---|---|---|---|
fmt::fmt |
Compiled (static/shared) | Core formatting library with compiled implementation | add_library(fmt …) at line 96 |
fmt::fmt-header-only |
Interface library | Header-only API with FMT_HEADER_ONLY=1 defined |
add_library(fmt-header-only INTERFACE) at line 54 |
fmt::fmt-c |
Compiled library | C-compatible wrapper around the C++ API | add_library(fmt-c src/fmt-c.cc) at line 60 |
fmt::fmt-module |
C++20 module | Optional C++20 modules support | add_module_library(fmt-module …) at line 37 |
All targets are exported with the fmt:: namespace, enabling consistent use across integration methods.
Why the Namespace Matters
The setup_target macro (lines 45–73 in CMakeLists.txt) centralizes common properties including include directories, version information, and symbol visibility. This ensures that linking against fmt::fmt automatically propagates all necessary compiler flags and paths, eliminating manual configuration.
Method 1: add_subdirectory Integration
Best for: Projects that vendor dependencies or maintain a monorepo structure.
When {fmt} lives inside your project's source tree, use add_subdirectory to pull its CMake configuration directly:
# Add {fmt} as a subdirectory
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/fmt)
# Link your executable or library
add_executable(my_app src/main.cpp)
target_link_libraries(my_app PRIVATE fmt::fmt)
This approach creates all targets in the current CMake scope. The fmt library is built as part of your project, and include paths are handled automatically through target_include_directories set by setup_target.
For header-only mode, substitute fmt::fmt-header-only:
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/fmt)
add_executable(my_app src/main.cpp)
target_link_libraries(my_app PRIVATE fmt::fmt-header-only) # No compiled binary
Method 2: find_package Integration
Best for: System-wide or package manager installations.
After installing {fmt} (via make install, vcpkg, conan, or your system package manager), CMake generates fmt-config.cmake in the installation prefix. This enables standard package discovery:
# Locate installed {fmt} package
find_package(fmt REQUIRED)
# Link against the discovered target
add_executable(my_app src/main.cpp)
target_link_libraries(my_app PRIVATE fmt::fmt)
The export(TARGETS … NAMESPACE fmt:: …) logic at line 73 in CMakeLists.txt ensures that installed targets maintain the same namespaced interface as subdirectory builds. The support/cmake/fmt-config.cmake.in template generates the configuration file that find_package consumes.
Verifying Installation Paths
By default, {fmt} installs to standard CMake locations. The FMT_INSTALL option (defined around lines 87–94) controls whether installation rules are generated—defaults to ON when {fmt} is the main project.
Method 3: FetchContent Integration
Best for: CMake-only projects that want automatic source fetching without manual checkout.
FetchContent downloads {fmt} at configure time, making it ideal for CI/CD pipelines or contributor-friendly repositories:
include(FetchContent)
# Declare {fmt} source location
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG v10.2.1 # Pin to specific release
)
# Make target available in current scope
FetchContent_MakeAvailable(fmt)
# Link as usual
add_executable(my_app src/main.cpp)
target_link_libraries(my_app PRIVATE fmt::fmt)
FetchContent_MakeAvailable effectively wraps add_subdirectory with automatic download, providing identical target semantics. All configurable options like FMT_TEST, FMT_DOC, and FMT_MODULE can be set before the MakeAvailable call to customize the build.
Choosing Between Compiled and Header-Only Modes
| Mode | Target | Trade-offs |
|---|---|---|
| Compiled | fmt::fmt |
Faster compile times for translation units; requires linking; recommended for most projects |
| Header-only | fmt::fmt-header-only |
No binary dependency; slower compile times; target_compile_definitions sets FMT_HEADER_ONLY=1 automatically |
The header-only interface at line 56 explicitly defines FMT_HEADER_ONLY=1 through target_compile_definitions, ensuring consistent behavior without manual macro management.
Summary
- Use
fmt::fmtfor the compiled library (default, best performance) orfmt::fmt-header-onlyfor zero binary dependencies - Three integration paths work identically:
add_subdirectoryfor vendored code,find_packagefor system installations,FetchContentfor automatic fetching - All methods expose the same namespaced targets thanks to centralized
setup_targetlogic and proper CMake export configuration - Key source files:
CMakeLists.txt(targets and export),support/cmake/fmt-config.cmake.in(package template),include/fmt/*.h(public API)
Frequently Asked Questions
What is the difference between fmt and fmt::fmt?
The unqualified fmt is the internal CMake target name; fmt::fmt is the namespaced alias used after export. Always use fmt::fmt in your target_link_libraries calls—it works consistently across all three integration methods and protects against target name collisions.
Can I use {fmt} without modifying my system's installed packages?
Yes. The FetchContent or add_subdirectory methods require no system installation. FetchContent is particularly convenient as it downloads {fmt} automatically during CMake configuration with no manual steps.
How do I disable optional components like tests or documentation?
Set CMake options before integrating {fmt}:
set(FMT_TEST OFF CACHE BOOL "" FORCE)
set(FMT_DOC OFF CACHE BOOL "" FORCE)
# Then add_subdirectory, FetchContent_MakeAvailable, etc.
These options (defined lines 87–94) default to ON when {fmt} is the top-level project but can be overridden when consumed as a dependency.
Does {fmt} support C++20 modules in CMake?
Yes, when FMT_MODULE is enabled and the compiler supports C++20 modules, the fmt-module target provides modularized access to formatting facilities. This is an advanced feature requiring compatible toolchain configuration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →