hs.fs provides a comprehensive set of filesystem operations covering file
I/O, directory management, path manipulation, metadata access, symbolic
links, Finder tags, and macOS-specific features like file bookmarks and
Uniform Type Identifiers.
It replaces both Hammerspoon v1's hs.fs module and the functionality that
was previously available through Lua's built-in io and file modules.
For incremental, positioned, binary, or locked access, open the file to get an
HSFile object (the equivalent of a Lua io file handle):
const f = hs.fs.open("~/notes.txt", "r+");
const firstLine = f.readLine();
f.seek(0, "end");
f.writeLine("appended");
f.close();
// Or let hs.fs.withFile() close it for you:
const header = hs.fs.withFile("~/image.png", "r", f => f.readBytes(8));
Errors
Functions that fail return null or false and set hs.fs.lastError to an object like
{code: "ENOENT", message: "No such file or directory"}. Most failures are also logged to the
Console; failures that are often expected (such as hs.fs.open() on a missing file) are not.
Directory operations
hs.fs.mkdir("~/Projects/new-thing");
const files = hs.fs.list("~/Documents");
const all = hs.fs.listRecursive("~/Documents");
The error from the most recent failed `hs.fs` function, or `null` if none has failed.
The object has a `code` (a POSIX error name such as `"ENOENT"`, `"EEXIST"`, or `"EACCES"`, or
`"EINVAL"` for invalid arguments) and a human-readable `message`. Like C's `errno`, it is not
cleared by successful calls, so only consult it after a function has reported failure.
Errors from methods on an open file are recorded on that file's own `lastError` instead.
Open a file, returning an `HSFile` object for reading and/or writing it.
| Mode | Read | Write | Creates | Truncates | Notes |
|------|------|-------|---------|-----------|-------|
| `"r"` | ✓ | | | | The default |
| `"r+"` | ✓ | ✓ | | | |
| `"w"` | | ✓ | ✓ | ✓ | |
| `"w+"` | ✓ | ✓ | ✓ | ✓ | |
| `"a"` | | ✓ | ✓ | | Every write goes to the end of the file |
| `"a+"` | ✓ | ✓ | ✓ | | Reads start at the beginning; every write goes to the end |
Add `x` to a `w` mode (`"wx"`, `"w+x"`) to fail if the file already exists. A `b` is accepted and
ignored, since all files can be read as text or bytes.
Directories cannot be opened. A file that can't be opened (for example because it doesn't exist) is
not logged to the Console, since that is often an expected outcome; check `hs.fs.lastError` instead.
Invalid arguments, such as an unknown mode, are logged.
Close the file with `close()` when you are finished with it; any files still open are closed
automatically when your configuration is reloaded.
POSIX permission bits applied if the file is created, e.g. `0o600` (`0` creates a file with no permissions). Defaults to `0o644` when omitted; any other non-integer value, including `NaN`, fails with `EINVAL`. The process umask is applied in either case.
Returns
HSFile | null
An `HSFile`, or `null` on failure (see `hs.fs.lastError`).
Example
const f = hs.fs.open("~/notes.txt", "a")
if (f) {
f.writeLine("Another note")
f.close()
}
Create and open a new, uniquely named temporary file.
The file is created in `hs.fs.tempDirectory()` with permissions `0o600` and opened in `"w+"` mode, so its
`path` always starts with `hs.fs.tempDirectory()`.
It is not deleted automatically; call `remove()` on it when you no longer need it on disk.
Declaration
hs.fs.tempFile(prefix) -> HSFile | null
Parameters
Name
Type
Description
prefix
string
A prefix for the file name. Must not contain `/`. Defaults to `"hs"`.
Returns
HSFile | null
An `HSFile`, or `null` on failure (see `hs.fs.lastError`).
Open a file, pass it to a function, and close it again afterwards.
The file is closed when the function returns, even if it throws (in which case the exception
propagates to you). The function must do all its work synchronously: an `async` function would
find the file already closed after its first `await`.
`hs.fs.withFile(path, f => ...)`.
Declaration
hs.fs.withFile(path, mode, callback) -> any
Parameters
Name
Type
Description
path
string
Path to the file. `~` is expanded.
mode
string
The open mode, as for `hs.fs.open()`. Defaults to `"r"`; if you omit it, pass the callback in its place.
callback
function
Called with the open file. Required unless it was passed in place of `mode`.
Returns
any
Whatever `callback` returns, or `null` if the file could not be opened or no callback was given (see `hs.fs.lastError`).
Example
const header = hs.fs.withFile("/etc/hosts", f => f.readLine())
hs.fs.withFile("~/counter.txt", "r+", f => {
const n = (parseInt(f.read()) || 0) + 1
f.truncate()
f.rewind()
f.write(String(n))
})
Call a function for each line of a file.
This behaves exactly like opening the file and calling `HSFile.eachLine()`: line endings (`\n` or
`\r\n`) are stripped; the callback may return `false` (and only `false`) to stop early; if the
callback throws, iteration stops and the exception propagates to you; and invalid UTF-8 stops
iteration with `hs.fs.lastError.code === "EILSEQ"`.
Declaration
hs.fs.eachLine(path, callback) -> boolean
Parameters
Name
Type
Description
path
string
Path to the file. `~` is expanded.
callback
function
Called once per line. Return `false` to stop.
Returns
boolean
`true` if iteration completed or was stopped by the callback returning `false`; `false` if the file could not be opened or read (see `hs.fs.lastError`), or if the callback threw.
Example
hs.fs.eachLine("/etc/hosts", (line) => {
if (line.startsWith("#")) return
console.log(line)
})
Write a UTF-8 string to a file, creating it or overwriting any existing content.
Intermediate directories are not created automatically; use `mkdir` first if needed.
Declaration
hs.fs.write(path, content, inPlace) -> boolean
Parameters
Name
Type
Description
path
string
Path to the file. `~` is expanded.
content
string
String to write.
inPlace
boolean
Whether to write the file in-place or atomically. Defaults to atomically (false).
Copy a file or directory to a new location.
The destination must not already exist. If `source` is a directory, its
entire contents are copied recursively.
Declaration
hs.fs.copy(source, destination) -> boolean
Parameters
Name
Type
Description
source
string
Path to the existing file or directory. `~` is expanded.
List the immediate contents of a directory.
Returns bare filenames (not full paths), sorted alphabetically.
The `.` and `..` entries are never included.
Declaration
hs.fs.list(path) -> [String]
Parameters
Name
Type
Description
path
string
Path to the directory. `~` is expanded.
Returns
[String]
Sorted array of filenames, or `null` if the path cannot be read.
Resolve a path to its absolute, canonical form.
Expands `~`, resolves `.` and `..`, and follows all symbolic links.
Returns `null` if any component of the path does not exist.
Declaration
hs.fs.pathToAbsolute(path) -> string
Parameters
Name
Type
Description
path
string
Path to resolve.
Returns
string
Absolute canonical path, or `null` if it cannot be resolved.
Return the localised display name for a file or directory as shown by Finder.
For example, `/Library` appears as `"Library"` in Finder even though its
on-disk name is the same.
Declaration
hs.fs.displayName(path) -> string
Parameters
Name
Type
Description
path
string
Path to the file or directory. `~` is expanded.
Returns
string
Display name string, or `null` if the path does not exist.
Returns the temporary directory for the current user.
The path is fully resolved (e.g. `/private/var/folders/...` rather than the `/var/folders/...`
symlink), so it matches the `path` of files created by `hs.fs.tempFile()`.
Declaration
hs.fs.tempDirectory() -> string
Returns
string
Absolute temporary directory path, with symlinks resolved (always ends with `/`).
Set the access and modification times of a file, creating it if it does not exist
(equivalent to the POSIX `touch` command).
The argument order matches Hammerspoon v1 (LuaFileSystem) and Node's `fs.utimes`: access time first.
Create a symbolic link at `destination` pointing at `source`.
Unlike hard links, symlinks may cross filesystem boundaries and may
point to paths that do not yet exist.
Encode a file path as a persistent bookmark that survives file moves and renames.
The returned string is base64-encoded bookmark data that can be stored and
later resolved with `pathFromBookmark`.
Declaration
hs.fs.pathToBookmark(path) -> string
Parameters
Name
Type
Description
path
string
Path to the file or directory. `~` is expanded.
Returns
string
Base64-encoded bookmark string, or `null` on failure.
Create a watcher for filesystem events at a given path.
Events are batched and delivered with a latency of approximately one second.
Call `setCallback()` and `start()` on the returned object to begin receiving events.
Get the value of an extended attribute for a file or directory.
Attribute values are returned as ISO Latin-1 encoded strings so that arbitrary byte
sequences are represented without loss. ASCII text attribute values appear readable as-is.
Array of option strings: `"noFollow"` (do not follow symlinks), `"hfsCompression"`, `"createOnly"`, `"replaceOnly"`, `"noSecurity"`, `"noDefault"`. Pass an empty array or omit to use no options.
position
number
Byte offset within the attribute data. Defaults to `0`. Non-zero values are only valid for `"com.apple.ResourceFork"`.
Returns
string
The attribute value as a string, `""` if the attribute exists but contains no data, or `null` if the attribute does not exist or an error occurs.
Set the value of an extended attribute for a file or directory.
The value is written as ISO Latin-1 bytes, providing a lossless round-trip with
`xattrGet`. Plain ASCII strings work directly without any encoding.
Array of option strings: `"noFollow"`, `"hfsCompression"`, `"createOnly"`, `"replaceOnly"`, `"noSecurity"`, `"noDefault"`. Pass an empty array or omit to use no options.
position
number
Byte offset within the attribute data. Defaults to `0`. Non-zero values are only valid for `"com.apple.ResourceFork"`.