Quick start
This guide will help you connect to an FTP server and perform common operations using workerd-ftp.
Basic setup
Import FTPClient
Start by importing the client in your Worker:import { FTPClient } from "workerd-ftp";
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.
Connect to the server
Establish the connection: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}`);
}
Navigate directories
// 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 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
- Always close connections: Call
await ftp.close() when you’re done to avoid leaving connections open
- Use environment variables: Store FTP credentials as Worker secrets, never hardcode them
- Handle errors gracefully: FTP operations can fail due to network issues, permissions, or server errors
- Use streaming for large files: Avoid memory issues by using
downloadReadable() and uploadWritable() for large files
- 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.