path
Use effect Path abstraction for platform-abstract file path operations. Apply this skill when working with file paths, joining segments, resolving absolute paths, or converting between file URLs and paths. Path.layer supplies POSIX semantics from effect; Node.js and Bun platform layers provide host-specific path semantics. @effect/platform-browser does not provide a BrowserPath layer, so browser code should provide Path.layer or a custom layer explicitly.
Import Pattern
import { Path } from 'effect';
Service Injection
import { Path } from 'effect';
import { Effect } from 'effect';
const program = Effect.gen(function* () {
const path = yield* Path.Path;
});
Path Separator
import { Path } from 'effect';
import { Effect } from 'effect';
// Get platform-specific separator
const program = Effect.gen(function* () {
const path = yield* Path.Path;
const separator = path.sep; // "/" on Unix, "\" on Windows
});
Path Operations
join - Combine path segments
import { Path } from 'effect';
import { Effect } from 'effect';
const program = Effect.gen(function* () {
const path = yield* Path.Path;
const fullPath = path.join('src', 'components', 'Button.tsx');
// "src/components/Button.tsx" on Unix
// "src\components\Button.tsx" on Windows
});
resolve - Convert to absolute path
import { Path } from 'effect';
import { Effect } from 'effect';
const program = Effect.gen(function* () {
const path = yield* Path.Path;
const absolutePath = path.resolve('src', 'index.ts');
// "/Users/username/project/src/index.ts" on Unix
// "C:\Users\username\project\src\index.ts" on Windows
});
normalize - Clean up path
import { Path } from 'effect';
import { Effect } from 'effect';
const program = Effect.gen(function* () {
const path = yield* Path.Path;
const clean = path.normalize('/foo/bar//baz/asdf/quux/..');
// "/foo/bar/baz/asdf"
});
dirname - Get directory portion
import { Path } from 'effect';
import { Effect } from 'effect';
const program = Effect.gen(function* () {
const path = yield* Path.Path;
const dir = path.dirname('/foo/bar/baz.txt');
// "/foo/bar"
});
basename - Get file name
import { Path } from 'effect';
import { Effect } from 'effect';
const program = Effect.gen(function* () {
const path = yield* Path.Path;
const name = path.basename('/foo/bar/baz.txt');
// "baz.txt"
const nameWithoutExt = path.basename('/foo/bar/baz.txt', '.txt');
// "baz"
});
extname - Get file extension
import { Path } from 'effect';
import { Effect } from 'effect';
const program = Effect.gen(function* () {
const path = yield* Path.Path;
const ext = path.extname('/foo/bar/baz.txt');
// ".txt"
});
parse - Decompose path into components
import { Path } from 'effect';
import { Effect } from 'effect';
const program = Effect.gen(function* () {
const path = yield* Path.Path;
const parsed = path.parse('/home/user/dir/file.txt');
// {
// root: "/",
// dir: "/home/user/dir",
// base: "file.txt",
// ext: ".txt",
// name: "file"
// }
});
format - Construct path from components
import { Path } from 'effect';
import { Effect } from 'effect';
const program = Effect.gen(function* () {
const path = yield* Path.Path;
const fullPath = path.format({
root: '/',
dir: '/home/user/dir',
base: 'file.txt'
});
// "/home/user/dir/file.txt"
});
isAbsolute - Check if path is absolute
import { Path } from 'effect';
import { Effect } from 'effect';
const program = Effect.gen(function* () {
const path = yield* Path.Path;
const isAbs = path.isAbsolute('/foo/bar');
// true on Unix
const isRel = path.isAbsolute('foo/bar');
// false
});
relative - Get relative path
import { Path } from 'effect';
import { Effect } from 'effect';
const program = Effect.gen(function* () {
const path = yield* Path.Path;
const relPath = path.relative(
'/data/orandea/test/aaa',
'/data/orandea/impl/bbb'
);
// "../../impl/bbb"
});
URL Conversions (Effectful)
Both operations can fail with BadArgument error.
fromFileUrl - Convert file URL to path
import { Path } from 'effect';
import { Effect } from 'effect';
const program = Effect.gen(function* () {
const path = yield* Path.Path;
const filePath = yield* path.fromFileUrl(
new URL('file:///home/user/file.txt')
);
// "/home/user/file.txt" on Unix
// "C:\home\user\file.txt" on Windows
});
toFileUrl - Convert path to file URL
import { Path } from 'effect';
import { Effect } from 'effect';
const program = Effect.gen(function* () {
const path = yield* Path.Path;
const fileUrl = yield* path.toFileUrl('/home/user/file.txt');
// URL object with href "file:///home/user/file.txt"
});
Complete Example
import { Path } from 'effect';
import { Effect } from 'effect';
const buildOutputPath = Effect.gen(function* () {
const path = yield* Path.Path;
// Join segments
const srcPath = path.join('src', 'index.ts');
// Resolve to absolute
const absoluteSrc = path.resolve(srcPath);
// Get directory and basename
const dir = path.dirname(absoluteSrc);
const base = path.basename(absoluteSrc, '.ts');
// Build output path
const outputPath = path.join(dir, '..', 'dist', `${base}.js`);
// Normalize
return path.normalize(outputPath);
});
File-System Migration Loaders
In Effect v4, Migrator.fromFileSystem(directory) requires both FileSystem.FileSystem and Path.Path. Migration modules are imported through path.toFileUrl(path.join(directory, file)) so absolute Windows paths are valid ESM specifiers.
import { FileSystem, Path } from 'effect';
import * as Migrator from 'effect/unstable/sql/Migrator';
const loader: Migrator.Loader<FileSystem.FileSystem | Path.Path> =
Migrator.fromFileSystem('./migrations');
Aggregate layers such as NodeServices.layer and BunServices.layer already provide both requirements. If wiring individual layers, provide both the file-system layer and the host-aware path layer. On Windows, do not use the core POSIX Path.layer for a Windows migration directory; use the runtime platform's path layer so drive letters and separators are converted correctly.
toNamespacedPath - Convert to namespaced path
import { Path } from 'effect';
import { Effect } from 'effect';
const program = Effect.gen(function* () {
const path = yield* Path.Path;
// On POSIX, returns the path unchanged.
// On Windows, converts to a \\?\-prefixed namespaced path
// that allows longer path lengths.
const namespaced = path.toNamespacedPath('/foo/bar/baz.txt');
});
DO
- Import Path from effect
- Use
path.sepfor platform-specific separators - Use
path.join()to combine path segments - Use
path.resolve()to get absolute paths - Handle errors from
fromFileUrlandtoFileUrlwith Effect error handling - Pass a
URLobject (not a string) tofromFileUrl
DON'T
- Import
node:pathdirectly (breaks portability) - Hardcode "/" or "\" separators
- Manually concatenate paths with string operations
- Assume file URL format without using conversion utilities
- Pass a string to
fromFileUrl— it requires aURLobject