Library
The flypod npm package deploys a folder of static files to a live URL from Node or Bun. Reference for deploySite, collectDist, resolveDeployDir, and the DeployOptions / DeployResult types.
flypod is a tiny, dependency-light npm package that turns a folder of static
files into a live URL from JS/TS. It's the same deploy primitive the
CLI wraps, exposed as functions you can call directly.
npm install flypod # or: bun add flypodEverything ships from one entry point. It's ESM-only and runs on Node 18+ and Bun:
import { deploySite, collectDist, resolveDeployDir, BUILD_DIR_CANDIDATES } from "flypod";
import type { DeployResult, DeployOptions } from "flypod";The mental model
A deploy is two steps: read files into a map, then ship the map.
const files = await collectDist("./dist"); // { "index.html": Uint8Array, ... }
const { url, manage_token } = await deploySite({ files });deploySite creates a new site every time it's called. Save the
manage_token it returns — that token is what lets you update or roll back the
site later. Two things worth knowing up front:
- Deploys are anonymous by default (public, expires in ~14 days). Pass an account token to make a site owned and permanent — see Owned deploys.
- The library only deploys. There's no
update()orrollback()function. To ship a new version to an existing site or roll one back, use the HTTP API (POST /sites/:id/deploys) or the CLI with themanage_token.
Want to run something now? The TypeScript demo is a downloadable, working example you can deploy in one command.
deploySite
deploySite(opts: DeployOptions): Promise<DeployResult>Zips a files map, uploads it as a new site, and returns the live URL plus the
tokens you need to manage it. Throws Error("deploy failed: <status>") if
the server responds with a non-2xx status, so wrap it in try/catch if you want
to handle failures.
DeployOptions
| Field | Type | Required | Description |
|---|---|---|---|
files | Record<string, Uint8Array> | Yes | Relative path → file bytes. Build it with collectDist, or construct it by hand. |
apiKey | string | No | An account token. Pass it to make the deploy owned and permanent. See Owned deploys. |
baseUrl | string | No | API host to deploy against. Defaults to https://flypod.dev; override it to target a self-hosted server. |
handle | (req: Request) => Promise<Response> | No | Advanced: a custom transport used in place of global fetch (for tests, in-process calls, or a proxying fetch). Defaults to fetch. Most callers never set this. |
apiKey is a bit of a misnomer — it's sent verbatim as Authorization: Bearer <value>, so the value is an account session token (from flypod login or
FLYPOD_TOKEN), not a separate API key. The field name is kept for backward
compatibility. See Tokens & ownership.
DeployResult
| Field | Type | Description |
|---|---|---|
url | string | The live URL, e.g. https://ab12cd34.flypod.dev. Serves immediately. |
manage_token | string | Save this. Bearer token that authorizes updates, rollback, and reads for this one site. |
site_id | string | Stable site identifier. Used in HTTP API paths (/sites/:id/...). |
slug | string | The site's URL slug (the subdomain in url). |
version_id | string | Identifier of the version this deploy created — pass it to rollback later. |
expires_at | number | null | Epoch-ms expiry for anonymous deploys; null when the site is owned (never expires). |
owner_account_id | string? | The owning account's ID. Present only on owned (authenticated) deploys. |
claim_token | string? | Anonymous deploys only. A fallback for claiming this site from a different machine via flypod claim. The usual login flow attaches sites with the manage_token and never needs this. |
next_actions | unknown[] | Suggested follow-up actions returned by the server (informational). |
Example
import { deploySite, collectDist } from "flypod";
const files = await collectDist("./dist");
const result = await deploySite({ files });
console.log(result.url); // https://ab12cd34.flypod.dev — live now
console.log(result.manage_token); // save this to update / roll back latercollectDist
collectDist(path: string): Promise<Record<string, Uint8Array>>Reads files off disk into the { path → bytes } map that deploySite expects.
Pass a directory and it's walked recursively (paths are relative to the
directory, with / separators); pass a single file and you get a
one-entry map.
const site = await collectDist("./dist"); // whole folder
const one = await collectDist("./index.html"); // { "index.html": Uint8Array }You don't have to use collectDist — any Record<string, Uint8Array> works, so
you can generate files in memory (see the inline demo).
resolveDeployDir
resolveDeployDir(opts?: { cwd?: string; explicit?: string }): Promise<string>Figures out which folder to deploy so callers don't have to hardcode dist.
It's async. Behavior:
- With
explicitset, that path is returned as-is. - Otherwise it scans
BUILD_DIR_CANDIDATESin order (relative tocwd, defaultprocess.cwd()) and returns the first folder that exists and contains anindex.html. - If nothing matches, it throws — pass the folder explicitly in that case.
import { resolveDeployDir, collectDist, deploySite } from "flypod";
const dir = await resolveDeployDir(); // auto-detect, e.g. "/proj/dist"
const files = await collectDist(dir);
await deploySite({ files });BUILD_DIR_CANDIDATES
The ordered list resolveDeployDir searches. public ranks low because some
tools use it for source assets — requiring an index.html disambiguates a
built site from source.
["dist", "build", "out", ".output/public", ".vercel/output/static", "public", "_site"]Owned deploys
By default a deploy is anonymous: public, noindex, and it expires in ~14 days.
To make a site owned and permanent, pass an account session token as
apiKey. The standard source is the FLYPOD_TOKEN environment variable, which
flypod login and CI both use:
const result = await deploySite({
files,
apiKey: process.env.FLYPOD_TOKEN,
});
// result.expires_at === null, result.owner_account_id is setSee Tokens & ownership for how tokens and ownership fit together, and Accounts & claiming to attach sites you already deployed anonymously.