568

I can't seem to get any search results that explain how to do this.

All I want to do is be able to know if a given path is a file or a directory (folder).

hippietrail
  • 15,848
  • 18
  • 99
  • 158
ThomasReggi
  • 55,053
  • 85
  • 237
  • 424
  • Note that there are also symlinks, which can link to files, link to directories, or be broken. There are also other kinds of paths besides files, directories, and symlinks. So you have to be careful not to just check for "directory" and assume everything else is "file", etc. And you have to think about whether you want symlinks to be followed transparently. One gotcha is that a `Dirent` returned by `scandir()` and a `Stat` returned by `stat()` both have `isFile()` and `isDirectory()` methods but the former don't follow symlinks and the latter do. – hippietrail Jul 17 '22 at 05:52

9 Answers9

884

The following should tell you. From the docs:

fs.lstatSync(path_string).isDirectory() 

Objects returned from fs.stat() and fs.lstat() are of this type.

stats.isFile()
stats.isDirectory()
stats.isBlockDevice()
stats.isCharacterDevice()
stats.isSymbolicLink() // (only valid with fs.lstat())
stats.isFIFO()
stats.isSocket()

NOTE:

The above solution will throw an Error if; for ex, the file or directory doesn't exist.

If you want a true or false approach, try fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory(); as mentioned by Joseph in the comments below.

ZKV
  • 17
  • 6
Jason Sperske
  • 29,816
  • 8
  • 73
  • 124
  • 20
    Note that the asynchronous version is usually preferable if you care about general app performance. – AlexMA Mar 14 '14 at 20:10
  • 49
    Keep in mind that if the directory or file does not exist, then you will get an error back. – Ethan Mick Dec 27 '14 at 20:12
  • 26
    ```let isDirExists = fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory();``` – Jossef Harush Kadouri Jul 29 '18 at 09:15
  • Keep in mind that if file or directory does not exists it will throw exception that needs to be caught otherwise it will cause abnormal exit. – Sergey Kuznetsov Nov 14 '18 at 16:41
  • @JossefHarush What happens if the file gets deleted between those steps? – David Callanan Aug 05 '20 at 21:48
  • Does `isDirectory` return true if it's a symlinked directory? – David Callanan Aug 06 '20 at 10:31
  • 2
    I find it odd that when they first made lstat they didnt just include an exists() function in there? I guess this is why node_modules is deeper than a black hole. – Johncl Feb 17 '21 at 07:50
  • 2
    Why is everyone using `fs.lstat()`? The docs say it will always be `false`: "If the `` object was obtained from `fs.lstat()`, this method [`.isDirectory()`] will always return `false`. This is because `fs.lstat()` returns information about a symbolic link itself and not the path it resolves to." – snickle Feb 03 '22 at 21:33
  • @snickle `fs.stat` will resolve all symlinks, and return stats for the real path. so it depends on the use case – milahu Mar 28 '22 at 14:32
98

Update: Node.Js >= 10

We can use the new fs.promises API

const fs = require('fs').promises;

(async() => {
    const stat = await fs.lstat('test.txt');
    console.log(stat.isFile());
})().catch(console.error)

Any Node.Js version

Here's how you would detect if a path is a file or a directory asynchronously, which is the recommended approach in node. using fs.lstat

const fs = require("fs");

let path = "/path/to/something";

fs.lstat(path, (err, stats) => {

    if(err)
        return console.log(err); //Handle error

    console.log(`Is file: ${stats.isFile()}`);
    console.log(`Is directory: ${stats.isDirectory()}`);
    console.log(`Is symbolic link: ${stats.isSymbolicLink()}`);
    console.log(`Is FIFO: ${stats.isFIFO()}`);
    console.log(`Is socket: ${stats.isSocket()}`);
    console.log(`Is character device: ${stats.isCharacterDevice()}`);
    console.log(`Is block device: ${stats.isBlockDevice()}`);
});

Note when using the synchronous API:

When using the synchronous form any exceptions are immediately thrown. You can use try/catch to handle exceptions or allow them to bubble up.

try{
     fs.lstatSync("/some/path").isDirectory()
}catch(e){
   // Handle error
   if(e.code == 'ENOENT'){
     //no such file or directory
     //do something
   }else {
     //do something else
   }
}
Marcos Casagrande
  • 37,983
  • 8
  • 84
  • 98
  • Is this still considered experimental as of Mar. 2020? Where can we look to see? -- Oops I see when I click the link above that it's now stable (which implies no longer experimental). – alfreema Mar 09 '20 at 18:54
34

Seriously, question exists five years and no nice facade?

function isDir(path) {
    try {
        var stat = fs.lstatSync(path);
        return stat.isDirectory();
    } catch (e) {
        // lstatSync throws an error if path doesn't exist
        return false;
    }
}
Hubro
  • 56,214
  • 69
  • 228
  • 381
kungfooman
  • 4,473
  • 1
  • 44
  • 33
18

If you need this when iterating over a directory (Because that's how I've found this question):

Since Node 10.10+, fs.readdir has a withFileTypes option which makes it return directory entry fs.Dirent instead of strings. Directory entries has a name property, and useful methods such as isDirectory or isFile, so you don't need to call fs.lstat explicitly.

import { promises as fs } from 'fs';

// ./my-dir has two subdirectories: dir-a, and dir-b

const dirEntries = await fs.readdir('./my-dir', { withFileTypes: true });

// let's filter all directories in ./my-dir

const onlyDirs = dirEntries.filter(de => de.isDirectory()).map(de => de.name);

// onlyDirs is now [ 'dir-a', 'dir-b' ]
Zdenek F
  • 1,649
  • 1
  • 15
  • 27
16

Depending on your needs, you can probably rely on node's path module.

You may not be able to hit the filesystem (e.g. the file hasn't been created yet) and tbh you probably want to avoid hitting the filesystem unless you really need the extra validation. If you can make the assumption that what you are checking for follows .<extname> format, just look at the name.

Obviously if you are looking for a file without an extname you will need to hit the filesystem to be sure. But keep it simple until you need more complicated.

const path = require('path');

function isFile(pathItem) {
  return !!path.extname(pathItem);
}
cndw
  • 185
  • 1
  • 2
  • 3
    Obviously this won't work in all situations but it's much quicker and easier than the other answers if you can make the needed assumptions. – derpedy-doo Apr 09 '19 at 02:47
  • 4
    the directory could be named `folder.txt` and this would say its a file, or the file could be `LICENSE` with no extensin – wow ow Jun 23 '20 at 11:43
3

Here's a function that I use. Nobody is making use of promisify and await/async feature in this post so I thought I would share.

const promisify = require('util').promisify;
const lstat = promisify(require('fs').lstat);

async function isDirectory (path) {
  try {
    return (await lstat(path)).isDirectory();
  }
  catch (e) {
    return false;
  }
}

Note : I don't use require('fs').promises; because it has been experimental for one year now, better not rely on it.

vdegenne
  • 12,272
  • 14
  • 80
  • 106
1

The answers above check if a filesystem contains a path that is a file or directory. But it doesn't identify if a given path alone is a file or directory.

The answer is to identify directory-based paths using "/." like --> "/c/dos/run/." <-- trailing period.

Like a path of a directory or file that has not been written yet. Or a path from a different computer. Or a path where both a file and directory of the same name exists.

// /tmp/
// |- dozen.path
// |- dozen.path/.
//    |- eggs.txt
//
// "/tmp/dozen.path" !== "/tmp/dozen.path/"
//
// Very few fs allow this. But still. Don't trust the filesystem alone!

// Converts the non-standard "path-ends-in-slash" to the standard "path-is-identified-by current "." or previous ".." directory symbol.
function tryGetPath(pathItem) {
    const isPosix = pathItem.includes("/");
    if ((isPosix && pathItem.endsWith("/")) ||
        (!isPosix && pathItem.endsWith("\\"))) {
        pathItem = pathItem + ".";
    }
    return pathItem;
}
// If a path ends with a current directory identifier, it is a path! /c/dos/run/. and c:\dos\run\.
function isDirectory(pathItem) {
    const isPosix = pathItem.includes("/");
    if (pathItem === "." || pathItem ==- "..") {
        pathItem = (isPosix ? "./" : ".\\") + pathItem;
    }
    return (isPosix ? pathItem.endsWith("/.") || pathItem.endsWith("/..") : pathItem.endsWith("\\.") || pathItem.endsWith("\\.."));
} 
// If a path is not a directory, and it isn't empty, it must be a file
function isFile(pathItem) {
    if (pathItem === "") {
        return false;
    }
    return !isDirectory(pathItem);
}

Node version: v11.10.0 - Feb 2019

Last thought: Why even hit the filesystem?

TamusJRoyce
  • 817
  • 1
  • 12
  • 25
  • what if the folder name has a dot at the end of it, like `.git` or even `myFolder.txt`? – wow ow Jun 23 '20 at 11:44
  • You have to understand posix filepath conventions (which windows, in part, adheres to since Windows is posix complient in the kernel level). Please read https://stackoverflow.com/questions/980255/should-a-directory-path-variable-end-with-a-trailing-slash and https://en.wikipedia.org/wiki/Talk:Path_(computing)#Filepath_Misconceptions – TamusJRoyce Jun 23 '20 at 16:06
  • Didn't really answer this did I? .git and myFolder.txt can be either a folder or a file. You don't know until you check. Since folders are also considered file, you cannot have a folder and a file named the same. .git/. and myFolder.txt/. are both folders. .git/ and myFolder.txt/ are all the files within that folder. man readline documents this (obscurely). The lone . is special. files/folders containing . are not. – TamusJRoyce Apr 16 '21 at 16:08
  • . and .. are both special – TamusJRoyce Apr 16 '21 at 16:14
1

I could check if a directory or file exists using this:

// This returns if the file is not a directory.
if(fs.lstatSync(dir).isDirectory() == false) return;

// This returns if the folder is not a file.
if(fs.lstatSync(dir).isFile() == false) return;
-1

Function that returns type

I like coffee

type: (uri)-> (fina) ->
  fs.lstat uri, (erro,stats) ->
    console.log {erro} if erro
    fina(
      stats.isDirectory() and "directory" or
      stats.isFile() and "document" or
      stats.isSymbolicLink() and "link" or
      stats.isSocket() and "socket" or
      stats.isBlockDevice() and "block" or
      stats.isCharacterDevice() and "character" or
      stats.isFIFO() and "fifo"
    )

usage:

dozo.type("<path>") (type) ->
  console.log "type is #{type}"
Tyler2P
  • 2,324
  • 26
  • 22
  • 31