Skip to content
Files SDK
Esc
↑↓navigate↵open⌘Jpreview
On this page

FTP

FTP / FTPS via basic-ftp. Node-only. Connect-per-operation with an injectable client for batch work; url() needs an HTTP front.

Installation

basic-ftp is an optional peer dependency of files-sdk - install alongside the SDK so the adapter’s imports resolve at runtime.

npm install files-sdk basic-ftp
pnpm add files-sdk basic-ftp
yarn add files-sdk basic-ftp
bun add files-sdk basic-ftp
nub add files-sdk basic-ftp
aube add files-sdk basic-ftp

Usage

FTP and FTPS via the basic-ftp library. Virtual keys map to paths under a configurable root on the server, with a .. traversal guard. Node-only — FTP uses raw sockets, so this adapter does not run on edge/browser/Workers runtimes.

By default the adapter opens a fresh connection per operation and closes it afterwards. For batch work, connect once and pass the client so every call reuses the same connection — you own its lifecycle.

import { Files } from "files-sdk";
import { ftp } from "files-sdk/ftp";

const files = new Files({
  adapter: ftp({
    host: "ftp.example.com",
    user: process.env.FTP_USERNAME!,
    password: process.env.FTP_PASSWORD!,
    secure: true, // FTPS over explicit TLS — strongly recommended
    root: "/uploads", // virtual keys resolve under here; defaults to "."
  }),
});

await files.upload("reports/q1.csv", csv, { contentType: "text/csv" });
const file = await files.download("reports/q1.csv");

Auth falls back to FTP_HOST, FTP_USERNAME (alias FTP_USER), FTP_PASSWORD, FTP_SECURE ("true" or "implicit"), and FTP_PORT (default 21) when the matching option is omitted.

Plain FTP is cleartext. Without secure, credentials and file contents are transmitted unencrypted. Prefer secure: true (explicit TLS / AUTH TLS); use "implicit" only for legacy servers.

Options

PropType
host?string

FTP host. Falls back to `FTP_HOST`.

Typestring
port?number

Port. Falls back to `FTP_PORT`, then `21`.

Typenumber
user?string

Username. Falls back to `FTP_USERNAME` (alias `FTP_USER`), then `anonymous`.

Typestring
password?string

Password. Falls back to `FTP_PASSWORD`.

Typestring
secure?boolean | "implicit"

FTPS over TLS. `true` is preferred explicit TLS (AUTH TLS); `"implicit"` is legacy implicit TLS. Defaults to `false` — **plain FTP transmits credentials and data in cleartext; prefer `secure: true`.** Falls back to `FTP_SECURE` (`"true"` or `"implicit"`).

Typeboolean | "implicit"
secureOptions?TLSConnectionOptions

TLS options forwarded to the secure connection (e.g. `rejectUnauthorized`).

TypeTLSConnectionOptions
root?string

Remote base directory. Virtual keys resolve under it; keys that escape it (e.g. `../etc/passwd`) throw `Provider`. Defaults to `"."` (the login directory). An absolute root (`/uploads`) yields absolute paths.

Typestring
publicBaseUrl?string

Origin used to build URLs from `url()`. When set, `url(key)` returns `${publicBaseUrl}/${key}`. When unset, `url()` throws: FTP serves no HTTP and has no signing primitive.

Typestring
timeout?number

Socket timeout in milliseconds for new connections (basic-ftp default 30s).

Typenumber
client?Client

Pre-connected `basic-ftp` `Client`. When passed, the adapter reuses it for every call and never opens or closes a connection — the caller owns the socket lifecycle. The high-throughput path: connect once and inject rather than paying a handshake per operation.

TypeClient

Limitations

Connect-per-operation means a high call rate becomes a high connection rate, and FTP servers commonly cap connections per IP - inject a pre-connected client for batch jobs.

Resumable uploads (upload({ control })) append each chunk to a <key>.fls-part staging file next to the target and rename it over the key only when the upload completes, so an existing object stays readable until then and abort() removes just the partial. list() hides staging files, and writes to keys ending in .fls-part throw. Servers that refuse to rename onto an existing file (IIS, for one) have the target deleted and the rename retried, so the key is briefly absent at that moment.

Compatibility

Method Status Notes
upload ⚠️ User metadata and cacheControl throw - FTP files have no arbitrary-metadata or cache-header field. contentType is accepted for the return value but not stored (it’s inferred from the key’s extension on read). Stream bodies upload directly. Node-only (raw sockets).
download ✅
delete ✅ Idempotent: a key that doesn’t exist is a no-op. FTP answers “not found” and “permission denied” with the same 550, so the adapter checks the parent listing: a file that is still there throws Unauthorized, and any other error reply (450 busy, 530 not logged in, …) throws too.
list ⚠️ Walks the directory tree recursively on every call - FTP has no native prefix scan or pagination - and skips symlinks. A subdirectory that disappears mid-walk is skipped; a missing root lists as empty. prefix/limit/cursor are applied client-side over the full walk, so they’re accurate but a large tree means a full traversal per call. Content type is inferred from each key’s extension; lastModified comes from the listing.
search ⚠️ Built on listAll — inherits this adapter’s list behavior above. Client-side key match (glob, regex, substring, exact).
head ⚠️ FTP stores no content type, etag, or user metadata - head() infers the type from the key’s extension (or application/octet-stream) and returns no etag. size comes from SIZE; lastModified is an MDTM probe that many servers don’t support, so it can be absent.
exists ✅
copy ⚠️ Read-then-write - FTP has no server-side copy, so the source is downloaded and re-uploaded over one connection. The whole object is buffered in memory; not atomic.
url ❌ Throws unless publicBaseUrl is set (an HTTP server fronting the same tree), in which case it returns <publicBaseUrl>/<key>. FTP serves no HTTP and has no signing primitive. responseContentDisposition always throws because the HTTP-front URL cannot bind the override.
signedUploadUrl ❌ Throws - FTP has no presigned-upload concept. Use upload(), or inject a pre-connected client for batch transfers.

Was this page helpful?