Skip to content
oRPC
Esc
navigateopen⌘Jpreview
On this page

Static File Plugin

Use StaticFileHandlerPlugin to serve static files alongside your procedures with standard HTTP semantics: ETag and Last-Modified conditional requests, range requests, index files, and directory traversal protection.

Installation

npm install @orpc/node@beta
pnpm add @orpc/node@beta
yarn add @orpc/node@beta
bun add @orpc/node@beta

How It Works

After routing, when no procedure matches a GET or HEAD request, the plugin maps the request path to a file inside rootDir and serves it. Matched procedures always take precedence. Requests that resolve to a directory are redirected to their trailing slash form and answered with the directory’s index.html.

Every file response carries an ETag and Last-Modified header, so clients sending If-None-Match or If-Modified-Since receive 304 Not Modified when the file is unchanged, and If-Match or If-Unmodified-Since receive 412 Precondition Failed. Single range requests are answered with 206 Partial Content, which enables media seeking and resumable downloads.

Dot segments like .. are resolved in URL space, following the RFC 3986 normalization browsers and proxies apply. One that would climb above the served path is refused rather than clamped, so a proxy in front of the handler can never disagree with it about which path was requested, and the resolved path is checked against rootDir again before any file is opened. Symbolic links whose target leaves rootDir are refused unless allowSymlinks is set. Dotfiles are treated as not found unless explicitly enabled, which is a request-path policy, so it does not apply to a configured indexFile or fallbackFile.

Content types are detected from the file extension with mime, and text types are served as UTF-8 so the browser never has to guess an encoding. Use mimeTypes to override or add to the detection.

Setup

The plugin reads files through the Node.js filesystem API but only interacts with the handler through standard oRPC interfaces, so it works with any handler on a Node.js compatible runtime, whether it uses the Node HTTP Adapter or the Fetch API Adapter.

import { StaticFileHandlerPlugin } from '@orpc/node'
import { RPCHandler } from '@orpc/server/node'

const handler = new RPCHandler(router, {
  plugins: [
    new StaticFileHandlerPlugin({
      /**
       * The directory files are served from. Resolved against the working
       * directory when relative.
       */
      rootDir: './public',

      /**
       * The URL path files are served under, appended to the handler prefix
       * when one is set.
       *
       * @default '/'
       */
      path: '/',

      /**
       * The file served when the request path resolves to a directory.
       * Set to `false` to disable directory index files.
       *
       * @default 'index.html'
       */
      indexFile: 'index.html',

      /**
       * A file served with status 200 when no file matches the request path,
       * relative to `rootDir`. Useful for single-page application routing.
       *
       * @default undefined
       */
      fallbackFile: 'index.html',

      /**
       * The `Cache-Control` response header value. Set to `false` to omit the header.
       *
       * @default 'public, max-age=0'
       */
      cacheControl: 'public, max-age=0',

      /**
       * Whether files and directories whose name starts with a dot can be served.
       *
       * @default false
       */
      dotfiles: false,

      /**
       * Whether precompressed sidecar files (`.br`, `.zst`, `.gz`) can be served
       * when the client accepts their encoding and the content type is compressible.
       *
       * @default false
       */
      precompressed: false,

      /**
       * Whether symbolic links whose target lies outside `rootDir` can be served.
       * Enabling this makes every file the links reach publicly readable.
       *
       * @default false
       */
      allowSymlinks: false,

      /**
       * Content types keyed by lowercase file extension without the dot, taking precedence
       * over the type detected from the extension. Values are sent verbatim, so a text type
       * needs its own charset. Extensions that neither this nor the detection recognises are
       * served as `application/octet-stream`.
       */
      mimeTypes: {},
    }),
  ],
})

Learn More

For implementation details, see the source code.

Last updated on August 10, 2026

Was this page helpful?