Skip to main content

Quick start

This guide will help you connect to an FTP server and perform common operations using workerd-ftp.

Basic setup

1

Import FTPClient

Start by importing the client in your Worker:
import { FTPClient } from "workerd-ftp";
2

Create a client instance

Initialize the client with your FTP server details:
const ftp = new FTPClient('ftp.example.com', {
  port: 21,
  user: 'your-username',
  pass: 'your-password',
  secure: false
});
Set secure: true to enable FTPS with TLS encryption.
3

Connect to the server

Establish the connection:
await ftp.connect();
This method handles authentication, feature discovery, and protocol negotiation automatically.

Connection options

The FTPClient constructor accepts these options:
interface ConnectionOptions {
  user?: string;        // Default: 'anonymous'
  pass?: string;        // Default: 'anonymous'
  port?: number;        // Default: 21
  activePort?: number;  // Default: 20 (not used in passive mode)
  activeIp?: string;    // Default: '127.0.0.1' (not used in passive mode)
  activeIpv6?: boolean; // Default: false
  secure?: boolean;     // Default: false (set true for FTPS)
}

Common operations

Upload a file

Upload a file from a string or Uint8Array:
// Upload text content
const content = 'Hello, World!';
await ftp.upload('hello.txt', new TextEncoder().encode(content));

// Upload binary data
const binaryData = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
await ftp.upload('data.bin', binaryData);

Download a file

Download a file as a Uint8Array:
const fileData = await ftp.download('hello.txt');
const text = new TextDecoder().decode(fileData);
console.log(text); // 'Hello, World!'

List directory contents

Get a simple list of files and directories:
const files = await ftp.list();
console.log(files);
// ['file1.txt', 'file2.pdf', 'subdirectory']

// List a specific directory
const uploads = await ftp.list('/uploads');
Get detailed file information:
const entries = await ftp.extendedList();
for (const [name, info] of entries) {
  console.log(`${name}: ${info.size} bytes, modified ${info.mtime}`);
  console.log(`  Is file: ${info.isFile}, Is directory: ${info.isDirectory}`);
}
// Get current working directory
const currentDir = await ftp.cwd();
console.log(`Current directory: ${currentDir}`);

// Change directory
await ftp.chdir('/uploads');

// Go up one level (like 'cd ..')
await ftp.cdup();

Create and remove directories

// Create a directory
await ftp.mkdir('new-folder');

// Remove an empty directory
await ftp.rmdir('old-folder');

Rename files or directories

await ftp.rename('old-name.txt', 'new-name.txt');
await ftp.rename('old-folder', 'new-folder');

Delete files

await ftp.rm('unwanted-file.txt');

Get file information

// Get file size in bytes
const size = await ftp.size('document.pdf');
console.log(`File size: ${size} bytes`);

// Get modification time
const modifiedDate = await ftp.modified('document.pdf');
console.log(`Last modified: ${modifiedDate}`);

// Get complete file stats
const stats = await ftp.stat('document.pdf');
console.log(stats.size, stats.mtime, stats.isFile);

Advanced: Streaming uploads and downloads

For large files, use streaming interfaces to avoid loading entire files into memory.

Streaming downloads

const readable = await ftp.downloadReadable('large-file.zip');

// Process the stream
const reader = readable.getReader();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  // Process chunk (value is Uint8Array)
  console.log(`Received ${value.length} bytes`);
}

// Important: finalize the stream when done
await ftp.finalizeStream();

Streaming uploads

const writable = await ftp.uploadWritable('large-upload.zip', 1024000);
const writer = writable.getWriter();

// Write chunks
await writer.write(new Uint8Array([/* chunk 1 */]));
await writer.write(new Uint8Array([/* chunk 2 */]));

// Close the writer
await writer.close();

// Important: finalize the stream when done
await ftp.finalizeStream();
Always call ftp.finalizeStream() after using streaming methods to release locks and close connections properly.

Complete example Worker

Here’s a complete Cloudflare Worker that handles FTP operations:
import { FTPClient } from "workerd-ftp";

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const ftp = new FTPClient(env.FTP_HOST, {
      port: 21,
      user: env.FTP_USER,
      pass: env.FTP_PASS,
      secure: false
    });

    try {
      // Connect to the server
      await ftp.connect();

      // Get current directory
      const cwd = await ftp.cwd();

      // Upload a file
      const timestamp = new Date().toISOString();
      const content = `Worker executed at ${timestamp}`;
      await ftp.upload('log.txt', new TextEncoder().encode(content));

      // List files
      const files = await ftp.list();

      // Download the file we just uploaded
      const downloaded = await ftp.download('log.txt');
      const text = new TextDecoder().decode(downloaded);

      return new Response(JSON.stringify({
        success: true,
        currentDirectory: cwd,
        files: files,
        uploadedContent: text
      }), {
        headers: { 'Content-Type': 'application/json' }
      });
    } catch (error) {
      return new Response(JSON.stringify({
        success: false,
        error: error.message
      }), {
        status: 500,
        headers: { 'Content-Type': 'application/json' }
      });
    } finally {
      // Always close the connection
      await ftp.close();
    }
  }
};

Environment variables

Add these secrets to your Worker using Wrangler:
wrangler secret put FTP_HOST
wrangler secret put FTP_USER
wrangler secret put FTP_PASS
Define the types in your env interface:
interface Env {
  FTP_HOST: string;
  FTP_USER: string;
  FTP_PASS: string;
}

Error handling

Always wrap FTP operations in try-catch blocks and call ftp.close() in a finally block to ensure proper cleanup.
try {
  await ftp.connect();
  await ftp.upload('file.txt', data);
} catch (error) {
  console.error('FTP operation failed:', error);
  // Handle error appropriately
} finally {
  await ftp.close();
}

Best practices

  1. Always close connections: Call await ftp.close() when you’re done to avoid leaving connections open
  2. Use environment variables: Store FTP credentials as Worker secrets, never hardcode them
  3. Handle errors gracefully: FTP operations can fail due to network issues, permissions, or server errors
  4. Use streaming for large files: Avoid memory issues by using downloadReadable() and uploadWritable() for large files
  5. Finalize streams: Always call finalizeStream() after using streaming methods

Next steps

Now that you’re familiar with the basics, explore the full API reference to learn about all available methods and options.

Build docs developers (and LLMs) love