This page provides flowcharts for each stage of find_package(), followed by troubleshooting
tips. This page does not attempt to document find_package(), for that, see the official
documentation and guides:
Module mode searches for a FindFoo.cmake file (a "find module")
that contains the logic to locate a package. Find modules are either provided by CMake itself (e.g.
FindOpenSSL.cmake) or by your project via CMAKE_MODULE_PATH. Module mode is
only available with the basic signature.
Config mode searches for a FooConfig.cmake or
foo-config.cmake file (a "config file") that the package itself installs. Config
mode supports the full range of find_package() arguments and is the preferred mechanism for
modern CMake packages.
When you call find_package(Foo) with the basic signature (no CONFIG,
MODULE, or full-signature arguments), CMake tries Module mode first and falls back to
Config mode. This default can be reversed with CMAKE_FIND_PACKAGE_PREFER_CONFIG.
This flowchart shows how find_package() decides which mode(s) to try.
flowchart TD
classDef entrypoint fill:#1a1a2e,color:#e0e0ff,stroke:#7b68ee,stroke-width:3px,font-weight:bold
classDef decision fill:#16213e,color:#e0e0ff,stroke:#4fc3f7,stroke-width:2px
classDef configmode fill:#0d3b66,color:#e0fff0,stroke:#00c853,stroke-width:2px
classDef modulemode fill:#3b1f2b,color:#ffe0f0,stroke:#f06292,stroke-width:2px
classDef result fill:#1a3c1a,color:#c8e6c9,stroke:#66bb6a,stroke-width:2px
classDef failure fill:#4a1a1a,color:#ffcdd2,stroke:#ef5350,stroke-width:2px
START["find_package(Foo ...)"]:::entrypoint
START --> DISABLED
DISABLED{"CMAKE_DISABLE_FIND_PACKAGE_Foo<br>= TRUE?"}:::decision
DISABLED -->|Yes| SKIP["find_package skipped<br>(silently does nothing)"]:::failure
DISABLED -->|No| EXPLICIT
EXPLICIT{"Explicit MODE<br>in the call?"}:::decision
EXPLICIT -->|"MODULE keyword"| FORCE_MOD["Module Mode only<br>(no fallback)"]:::modulemode
EXPLICIT -->|"CONFIG or NO_MODULE<br>keyword"| FORCE_CFG["Config Mode only<br>(no fallback)"]:::configmode
EXPLICIT -->|"No mode keyword"| SIGNATURE
SIGNATURE{"Any full-signature args used?"}:::decision
SIGNATURE -->|"Yes - implies Config Mode<br>(full signature = CONFIG)"| FORCE_CFG
SIGNATURE -->|"No - basic signature"| PREFER
classDef note fill:#1a1a2e,color:#b0b0d0,stroke:#555,stroke-width:1px,font-size:11px
FULL_SIG_NOTE["Full-signature args:<br>NAMES, CONFIGS, HINTS, PATHS, PATH_SUFFIXES,<br>NO_DEFAULT_PATH, NO_PACKAGE_ROOT_PATH,<br>NO_CMAKE_PATH, NO_CMAKE_ENVIRONMENT_PATH,<br>NO_SYSTEM_ENVIRONMENT_PATH,<br>NO_CMAKE_PACKAGE_REGISTRY,<br>NO_CMAKE_SYSTEM_PATH,<br>NO_CMAKE_SYSTEM_PACKAGE_REGISTRY,<br>CMAKE_FIND_ROOT_PATH_BOTH,<br>ONLY_CMAKE_FIND_ROOT_PATH,<br>NO_CMAKE_FIND_ROOT_PATH"]:::note
EXPLICIT ~~~ FULL_SIG_NOTE
PREFER{"CMAKE_FIND_PACKAGE_PREFER_CONFIG<br>= TRUE?"}:::decision
PREFER -->|"No (default)"| MOD_FIRST
subgraph DEFAULT_ORDER ["Default: Module first, Config fallback"]
direction LR
MOD_FIRST["Try Module Mode"]:::modulemode
MOD_FIRST -->|"Not found"| FALL_CFG["Fall back to<br>Config Mode"]:::configmode
MOD_FIRST -->|"Found"| DONE1["Done"]:::result
FALL_CFG -->|"Found"| DONE2["Done"]:::result
FALL_CFG -->|"Not found"| FAIL1["FAIL"]:::failure
end
PREFER -->|"Yes"| CFG_FIRST
subgraph PREFER_ORDER ["Prefer Config: Config first, Module fallback"]
direction LR
CFG_FIRST["Try Config Mode"]:::configmode
CFG_FIRST -->|"Not found"| FALL_MOD["Fall back to<br>Module Mode"]:::modulemode
CFG_FIRST -->|"Found"| DONE3["Done"]:::result
FALL_MOD -->|"Found"| DONE4["Done"]:::result
FALL_MOD -->|"Not found"| FAIL2["FAIL"]:::failure
end
FORCE_MOD -->|"Found"| DONE5["Done"]:::result
FORCE_MOD -->|"Not found"| FAIL3["FAIL<br>(no fallback)"]:::failure
FORCE_CFG -->|"Found"| DONE6["Done"]:::result
FORCE_CFG -->|"Not found"| FAIL4["FAIL<br>(no fallback)"]:::failure
If REQUIRED was specified, FAIL is a fatal error. Otherwise Foo_FOUND is
set to FALSE and configuration continues.
Module mode searches for a FindFoo.cmake file and executes it. The find module is
responsible for locating the package's headers, libraries, etc. and setting Foo_FOUND.
flowchart TD
classDef entrypoint fill:#3b1f2b,color:#ffe0f0,stroke:#f06292,stroke-width:3px,font-weight:bold
classDef decision fill:#16213e,color:#e0e0ff,stroke:#4fc3f7,stroke-width:2px
classDef pathnode fill:#1b2631,color:#ffeedd,stroke:#ffb74d,stroke-width:1px
classDef action fill:#3b1f2b,color:#ffe0f0,stroke:#f06292,stroke-width:2px
classDef result fill:#1a3c1a,color:#c8e6c9,stroke:#66bb6a,stroke-width:2px
classDef failure fill:#4a1a1a,color:#ffcdd2,stroke:#ef5350,stroke-width:2px
classDef warning fill:#4a3a1a,color:#fff3cd,stroke:#ffc107,stroke-width:2px
ENTRY["Module Mode<br>Searching for FindFoo.cmake"]:::entrypoint
ENTRY --> NESTED{"Is this find_package call<br>inside a CMake built-in module?<br>(e.g. FindOpenSSL calls<br>find_package internally)"}:::decision
NESTED -->|"No - normal call<br>from project code"| NORMAL_ORDER
NESTED -->|"Yes - nested call<br>from built-in module"| CMP0017
subgraph CMP0017_BOX ["CMP0017 - Priority Inversion for Nested Calls"]
direction TB
CMP0017["Policy CMP0017 (NEW, mandatory since CMake 4.0):<br>Built-in modules calling include() or find_package()<br>search the CMake module directory FIRST,<br>before CMAKE_MODULE_PATH"]:::warning
CMP0017 --> BUILTIN_FIRST["1. CMake built-in module dir<br>(Kitware-maintained)"]:::pathnode
BUILTIN_FIRST --> BF_Q{"FindFoo.cmake<br>found here?"}:::decision
BF_Q -->|Yes| EXEC_BUILTIN_N["Execute CMake's built-in<br>FindFoo.cmake"]:::action
BF_Q -->|No| USER_SECOND["2. CMAKE_MODULE_PATH<br>(user/project dirs)"]:::pathnode
USER_SECOND --> US_Q{"FindFoo.cmake<br>found here?"}:::decision
US_Q -->|Yes| EXEC_USER_N["Execute project-provided<br>FindFoo.cmake"]:::action
US_Q -->|No| NONE_N["No Find module exists"]:::failure
end
subgraph NORMAL_BOX ["Normal Priority Order"]
direction TB
NORMAL_ORDER["Standard search order<br>(called from project code)"]:::action
NORMAL_ORDER --> USER_FIRST["1. CMAKE_MODULE_PATH<br>(user/project dirs - checked FIRST)"]:::pathnode
USER_FIRST --> UF_Q{"FindFoo.cmake<br>found here?"}:::decision
UF_Q -->|Yes| EXEC_USER["Execute project-provided<br>FindFoo.cmake"]:::action
UF_Q -->|No| BUILTIN_SECOND["2. CMake built-in module dir<br>(Kitware-maintained)"]:::pathnode
BUILTIN_SECOND --> BS_Q{"FindFoo.cmake<br>found here?"}:::decision
BS_Q -->|Yes| EXEC_BUILTIN["Execute CMake's built-in<br>FindFoo.cmake"]:::action
BS_Q -->|No| NONE["No Find module exists"]:::failure
end
EXEC_USER --> RESULT
EXEC_BUILTIN --> RESULT
EXEC_USER_N --> RESULT
EXEC_BUILTIN_N --> RESULT
RESULT{"Foo_FOUND = FALSE?"}:::decision
RESULT -->|Yes| FAIL["Module Mode failed"]:::failure
RESULT -->|"No (TRUE or unset)"| DONE["Module Mode succeeded"]:::result
NONE --> FAIL
NONE_N --> FAIL
Find modules communicate success or failure by setting Foo_FOUND. Most use
find_package_handle_standard_args()
to do this, which checks that required variables like Foo_INCLUDE_DIR and
Foo_LIBRARY were populated, and handles REQUIRED and QUIET.
Find modules don't need to handle REQUIRED themselves -
find_package() reads Foo_FOUND after the module returns and emits the
fatal error itself if the package wasn't found and REQUIRED was specified.
If a find module does not set Foo_FOUND at all, find_package() assumes the
package was found. It only treats the package as not found if Foo_FOUND was
explicitly set to FALSE. This is surprising behavior, so I recommend
setting Foo_FOUND explicitly in your find modules rather than implicitly assuming
success.
Config mode searches for a FooConfig.cmake or foo-config.cmake file that the
package itself installed. It searches a well-defined sequence of prefixes, trying a set of subdirectory
patterns under each one.
flowchart TD
classDef entrypoint fill:#0d3b66,color:#e0fff0,stroke:#00c853,stroke-width:3px,font-weight:bold
classDef decision fill:#16213e,color:#e0e0ff,stroke:#4fc3f7,stroke-width:2px
classDef pathnode fill:#1b2631,color:#ffeedd,stroke:#ffb74d,stroke-width:1px
classDef action fill:#0d3b66,color:#e0fff0,stroke:#00c853,stroke-width:2px
classDef result fill:#1a3c1a,color:#c8e6c9,stroke:#66bb6a,stroke-width:2px
classDef failure fill:#4a1a1a,color:#ffcdd2,stroke:#ef5350,stroke-width:2px
ENTRY["Config Mode<br>Searching for FooConfig.cmake<br>or foo-config.cmake"]:::entrypoint
ENTRY --> CACHE
CACHE{"Foo_DIR already in cache<br>from a previous run?"}:::decision
CACHE -->|Yes| CACHE_HIT["Use cached Foo_DIR<br>(skip search, re-load config)"]:::result
CACHE -->|No| SEARCH_ORDER
subgraph SEARCH_ORDER ["Search prefixes in order"]
direction TB
PR["0. CMAKE_FIND_PACKAGE_REDIRECTS_DIR<br>(FetchContent redirect configs)"]:::pathnode
PR --> P0
P0["1. Foo_ROOT variable and env var<br>(CMake 3.12+, also FOO_ROOT via CMP0144)<br>Skipped by: NO_PACKAGE_ROOT_PATH"]:::pathnode
P0 --> P1["2. CMAKE_PREFIX_PATH (variable)<br>CMAKE_FRAMEWORK_PATH, CMAKE_APPBUNDLE_PATH<br>Skipped by: NO_CMAKE_PATH"]:::pathnode
P1 --> P2["3. Foo_DIR, CMAKE_PREFIX_PATH (env vars)<br>CMAKE_FRAMEWORK_PATH, CMAKE_APPBUNDLE_PATH (env)<br>Skipped by: NO_CMAKE_ENVIRONMENT_PATH"]:::pathnode
P2 --> P2H["4. HINTS (from the find_package call)"]:::pathnode
P2H --> P3["5. PATH env variable<br>(parent dirs of /bin and /sbin entries)<br>Skipped by: NO_SYSTEM_ENVIRONMENT_PATH"]:::pathnode
P3 --> P3R["6. User Package Registry<br>(~/.cmake/packages/ on Linux)<br>Skipped by: NO_CMAKE_PACKAGE_REGISTRY"]:::pathnode
P3R --> P4["7. CMAKE_SYSTEM_PREFIX_PATH<br>CMAKE_SYSTEM_FRAMEWORK_PATH<br>CMAKE_SYSTEM_APPBUNDLE_PATH<br>(includes CMAKE_INSTALL_PREFIX, platform defaults)<br>Skipped by: NO_CMAKE_SYSTEM_PATH"]:::pathnode
P4 --> P4R["8. System Package Registry<br>Skipped by: NO_CMAKE_SYSTEM_PACKAGE_REGISTRY"]:::pathnode
P4R --> P5["9. PATHS (from the find_package call)<br>(hard-coded guesses)"]:::pathnode
end
P5 --> SUFFIX_SEARCH
subgraph SUFFIX_SEARCH ["At each prefix, try these subdirectory patterns"]
direction TB
SUFFIXES["For each prefix, search subdirs:<br><br>Windows-only (W):<br>- prefix/<br>- prefix/(cmake|CMake)/<br>- prefix/Foo*/<br>- prefix/Foo*/(cmake|CMake)/<br><br>Unix-only (U):<br>- prefix/(lib/<arch>|lib*|share)/cmake/Foo*/<br>- prefix/(lib/<arch>|lib*|share)/Foo*/<br>- prefix/(lib/<arch>|lib*|share)/Foo*/(cmake|CMake)/<br><br>Both (W/U):<br>- prefix/Foo*/(lib/<arch>|lib*|share)/cmake/Foo*/<br>- prefix/Foo*/(lib/<arch>|lib*|share)/Foo*/<br>- prefix/Foo*/(lib/<arch>|lib*|share)/Foo*/(cmake|CMake)/"]:::action
SUFFIXES --> FILENAMES["In each subdir, look for:<br>- FooConfig.cmake (case-preserved)<br>- foo-config.cmake (lowercase)"]:::action
end
FILENAMES --> FOUND{"Config file found?"}:::decision
FOUND -->|No| NOTFOUND["Config Mode failed"]:::failure
FOUND -->|Yes| VERSION_CHECK
VERSION_CHECK{"Version file exists?<br>(FooConfigVersion.cmake or<br>Foo-config-version.cmake)"}:::decision
VERSION_CHECK -->|"No version file"| ASSUME_OK["Assumed compatible<br>(no version check possible)"]:::action
VERSION_CHECK -->|"Version file found"| VER_MATCH{"Requested version<br>compatible?"}:::decision
VER_MATCH -->|Yes| LOAD_CONFIG
VER_MATCH -->|"No - mismatch"| CONTINUE["Skip this candidate,<br>continue searching<br>remaining paths"]:::action
CONTINUE --> FOUND
ASSUME_OK --> LOAD_CONFIG
LOAD_CONFIG["Load FooConfig.cmake<br>Sets targets, variables, etc."]:::action
LOAD_CONFIG --> COMPONENTS
COMPONENTS{"COMPONENTS requested<br>and config checks them?"}:::decision
COMPONENTS -->|"All satisfied<br>(or none requested)"| DONE["Config Mode succeeded<br>Foo_DIR cached for future runs"]:::result
COMPONENTS -->|"Component missing<br>(config sets Foo_FOUND=FALSE)"| COMP_FAIL["Config Mode failed<br>(component not found)"]:::failure
Config file naming: CMake searches for two naming conventions:
FooConfig.cmake (the original CamelCase convention) and
foo-config.cmake (added in CMake 2.6 to mirror the Unix
pkg-config/autotools naming style). Packages ship one or the other; CMake searches for
both. Packages normally pick one that matches their conventions.
FetchContent redirects: FetchContent_MakeAvailable() generates config
files in CMAKE_FIND_PACKAGE_REDIRECTS_DIR (step 0 above) and sets
CMAKE_FIND_PACKAGE_PREFER_CONFIG to TRUE so that Config mode runs before
Module mode. This ensures find_package() picks up the FetchContent-provided package.
Root variable stacking: If this Config mode search happens inside a find module
(nested call), the parent package's _ROOT paths are also searched, after the current
package's _ROOT paths.
Global suppression variables (CMake 3.16+) can disable individual search steps:
CMAKE_FIND_USE_PACKAGE_ROOT_PATH, CMAKE_FIND_USE_CMAKE_PATH,
CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH,
CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH, CMAKE_FIND_USE_CMAKE_SYSTEM_PATH.
NO_DEFAULT_PATH enables all NO_* flags at once.
Before anything else, re-run your CMake configure with --debug-find-pkg=Foo (or
--debug-find for all packages):
cmake -S . -B build --debug-find-pkg=Foo
This prints the complete search log: which mode was selected, every directory searched, and why each
candidate was accepted or rejected.
It's the single most useful tool for diagnosing find_package() failures.
If you're not sure which mode CMake is using:
CONFIG, NO_MODULE, or full-signature args (e.g.
PATHS, NAMES, CONFIGS, HINTS,
PATH_SUFFIXES, NO_* flags) - Config mode only.
MODULE - Module mode only.
CMAKE_FIND_PACKAGE_PREFER_CONFIG=TRUE - Config mode first, Module mode
fallback.
A common surprise: using any full-signature argument silently forces Config mode with no Module fallback.
If the package is installed to e.g. /opt/foo, tell CMake where to look. The best options,
in order of preference:
CMAKE_PREFIX_PATH - the most common and general-purpose approach:
cmake -S . -B build -DCMAKE_PREFIX_PATH=/opt/foo
This works for both Config mode and the find_library/find_path calls
inside Module mode.
Foo_ROOT (CMake 3.12+) - package-specific, searched first:
cmake -S . -B build -DFoo_ROOT=/opt/foo
Foo_DIR - points directly to the directory containing
FooConfig.cmake, not the install prefix:
cmake -S . -B build -DFoo_DIR=/opt/foo/lib/cmake/Foo
CMAKE_PREFIX_PATH and Foo_ROOT point to the install prefix (e.g.
/opt/foo). Foo_DIR points to the cmake config directory (e.g.
/opt/foo/lib/cmake/Foo). Confusing these is a common find_package() mistake.
If find_package() previously found a package at a location that no longer exists (or found
the wrong version), CMake may keep using the stale cached Foo_DIR. The
--debug-find output will show it using the cached path without searching. The fix is to
reconfigure.
If you know a FooConfig.cmake exists but CMake can't find it, check:
Is the config file in a searched subdirectory pattern? Config mode looks for
the file under specific subdirectories of each prefix (e.g. lib/cmake/Foo/,
share/Foo/, lib/Foo*/). If the file is at
prefix/some/unusual/path/FooConfig.cmake, CMake won't find it.
Is the prefix in the search path? The config file's install prefix must be reachable through one of the nine search steps in the Config mode flowchart.
Check the filename. CMake looks for FooConfig.cmake and
foo-config.cmake Note that FooConfig.cmake uses the caller's
capitalization from find_package(Foo), so on case-sensitive filesystems,
find_package(foo) searches for fooConfig.cmake, which won't match
FooConfig.cmake. The lowercase-hyphen variant doesn't have this problem.
Version mismatch. If a version was requested but the
FooConfigVersion.cmake file reports an incompatible version, that candidate is
silently skipped. Use --debug-find to see version check results.
If a FindFoo.cmake exists but doesn't find the package, the problem is usually inside the
find module. Most find modules use find_library(), find_path(), and
find_program(), which have their own search paths.