file_exists
Checks whether a file or directory exists at the given path.
file_exists(string $filename): bool- Added in PHP 4.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$filename | string | Path to the file or directory. On Windows, network shares can be checked with //computername/share/filename. |
Return value
booltrue if the file or directory specified by filename exists, false otherwise.
Errors
Does not throw. Returns false and may emit E_WARNING when the path cannot be checked, for example because of open_basedir restrictions.
file_exists() answers one narrow question: does something live at this path? It returns true for directories as well as files, so it is not the check to use when you mean "is there a readable file here" — that is is_file(), optionally with is_readable().
What it does not tell you
Existence is not permission. A file you cannot open still exists. And a path can appear not to exist simply because your process cannot traverse a parent directory — the answer is false either way, which makes debugging permission problems confusing.
It follows symlinks, so a link pointing at a deleted target returns false. Use is_link() or lstat() when you care about the link itself.
The race condition
Checking and then acting is two operations with a gap between them. Between your file_exists() and your fopen(), another process can create or delete the file. For reads, just attempt the operation and handle the failure — @file_get_contents() returning false is one syscall and cannot be raced. Reserve file_exists() for cases where you genuinely need the answer as information, such as deciding whether to build a cache entry.
Caching
Results are stored in PHP's stat cache for the rest of the request. A miss is never cached, but a hit is — including after the file is deleted.
Example
When to use it
- Decide whether a generated thumbnail already exists before rebuilding it
- Fall back to a default template when a per-tenant override file is absent
- Verify that an optional .env or local config file is present at boot
- Report a clear error when a configured upload directory has not been created
Frequently asked questions
Does file_exists work for directories?
Yes — it returns true for both files and directories. Use is_file() when you specifically need a regular file, or is_dir() for a directory.
Why does file_exists return false for a file I can see on disk?
Usually a permissions problem on a parent directory, an open_basedir restriction, or a relative path resolved against a different working directory than you expect. A broken symlink also returns false, because the function follows links.
Should I call file_exists before fopen or file_get_contents?
Generally no. The file can change between the two calls, and the read already reports failure, so just attempt the read and handle a false return. Use file_exists() when the existence answer itself is what you need.
More examples
Telling files and directories apart
The directory and the file both report exists=1, which is why file_exists() alone is a poor guard before fopen() — passing a directory path to fopen() fails. is_file() is the check that distinguishes them, and the missing path is 0 across the board.
<?php
mkdir("uploads");
file_put_contents("uploads/avatar.png", "PNG");
foreach (["uploads", "uploads/avatar.png", "uploads/missing.png"] as $path) {
printf("%-22s exists=%d file=%d dir=%d\n",
$path, file_exists($path), is_file($path), is_dir($path));
}
unlink("uploads/avatar.png");
rmdir("uploads");Skip the check and handle the failure
The false return from file_get_contents() carries the same information as file_exists() would have, in one syscall instead of two, and nothing can change on disk in between. Check-then-open is only worth the extra call when you need the existence answer for its own sake.
<?php
$path = "maybe-config.json";
var_dump(file_exists($path));
$data = @file_get_contents($path); // one operation, no race
var_dump($data);
file_put_contents($path, '{"debug":true}');
$data = @file_get_contents($path);
var_dump($data);
$config = json_decode($data === false ? "{}" : $data, true);
print_r($config);
unlink($path);A cache-or-build guard
The first pass builds the file and the second finds it, which is the legitimate use of file_exists(): the answer itself drives a decision rather than merely guarding a read. clearstatcache() is called each iteration because the loop both checks and changes the same path within one request.
<?php
$cache = "page-42.html";
for ($i = 1; $i <= 2; $i++) {
clearstatcache();
if (file_exists($cache)) {
echo "run $i: cache hit\n";
} else {
echo "run $i: building\n";
file_put_contents($cache, "<h1>page 42</h1>");
}
}
echo file_get_contents($cache), "\n";
unlink($cache);
Discussion