Platform
Most real programs eventually need to touch the world outside of pure computation: read a file, resolve a path, prompt the user, or shell out to another command. Effect models each of these capabilities as a service with a typed error channel, so platform access stays referentially transparent, testable, and decoupled from any particular runtime.
The core services live in the main effect package as abstract interfaces:
FileSystem— read and write files and directories, stream large files, and watch for changes.Path— join, resolve, normalize, and parse file paths.Terminal— read user input and display output on the command line.ChildProcessSpawner— spawn and stream child processes (undereffect/unstable/process).
Because these are abstract services, your code never depends on Node, Bun, or the browser directly — it depends only on the interface. You choose a concrete implementation at the edge of your application by providing a Layer.
Providing a platform
Section titled “Providing a platform”On Node.js, the @effect/platform-node package supplies every implementation.
Rather than wiring each service individually, NodeServices.layer bundles the
file system, path, terminal, and child-process spawner into one layer:
import { NodeRuntime, NodeServices } from "@effect/platform-node"import { Effect, FileSystem, Path } from "effect"
const program = Effect.gen(function*() { // Pull the abstract services out of context — no Node import in sight. const fs = yield* FileSystem.FileSystem const path = yield* Path.Path
const target = path.join("/tmp", "effect-platform.txt") yield* fs.writeFileString(target, "hello from Effect\n")
const contents = yield* fs.readFileString(target) yield* Effect.log(contents.trim())}).pipe( // Provide all Node platform services in one shot. Effect.provide(NodeServices.layer))
// `runMain` runs the program and reports failures with a clean exit code.NodeRuntime.runMain(program)The program above mentions Node in exactly one place — the call to
NodeServices.layer. Everything else is written against the abstract services,
so the same logic runs unchanged on a different platform layer or against an
in-memory test implementation.
Typed errors
Section titled “Typed errors”Platform operations fail in the typed error channel rather than throwing. Every
file system operation fails with a PlatformError, which
wraps a reason that is either a SystemError (an OS-level failure such as a
missing file or permission denied) or a BadArgument (an invalid input). The
Path service is simpler: its only fallible methods, fromFileUrl and
toFileUrl, fail with a BadArgument directly. Because the failure is in the
error channel, you handle it with the usual combinators like Effect.catchTag:
import { Effect, FileSystem } from "effect"
const readConfig = Effect.gen(function*() { const fs = yield* FileSystem.FileSystem return yield* fs.readFileString("config.json")}).pipe( // Missing-file failures surface as a typed PlatformError, not an exception. Effect.catchTag("PlatformError", () => Effect.succeed("{}")))Granular layers
Section titled “Granular layers”NodeServices.layer is the convenient default, but each service also has its
own narrow layer when you only need one capability:
NodeFileSystem.layerprovidesFileSystem.NodePath.layerprovidesPath(withlayerPosix/layerWin32variants).NodeTerminal.layerprovidesTerminal.NodeChildProcessSpawner.layerprovidesChildProcessSpawner.
Continue with the page for each capability: