BoxLang π A New JVM Dynamic Language Learn More...
|:------------------------------------------------------: |
| β‘οΈ B o x L a n g β‘οΈ
| Dynamic : Modular : Productive
|:------------------------------------------------------: |
π A comprehensive FTP client module for BoxLang that brings seamless file transfer capabilities to your applications!
This module provides powerful FTP client functionality to the BoxLang language, making it easy to connect to FTP servers, transfer files, and manage remote directories with minimal code.
If you are using CommandBox for your web applications, you can install it via CommandBox:
box install bx-ftp
If you want to install it globally for the BoxLang OS binary, use the
install-bx-module command:
install-bx-module bx-ftp
The module will automatically register and be available as
bx:ftp or <bx:ftp> in your BoxLang
script and template applications.
# Clone the repository
git clone https://github.com/ortus-boxlang/bx-ftp.git
cd bx-ftp
# Build the module
./gradlew build
# The built module will be in build/distributions/
Here's how to connect to an FTP server and upload a file in just a few lines:
// Connect to FTP server
bx:ftp
action="open"
connection="myFTP"
server="ftp.example.com"
username="myuser"
password="mypass";
// Upload a file
bx:ftp
action="putfile"
connection="myFTP"
localFile="/path/to/local/file.txt"
remoteFile="/remote/file.txt";
// Close connection
bx:ftp action="close" connection="myFTP";
That's it! π You now have a fully functional FTP client.
// Open connection
bx:ftp
action="open"
connection="myConn"
server="ftp.example.com"
username="user"
password="pass";
// List directory contents
bx:ftp
action="listdir"
connection="myConn"
directory="/"
name="files";
// Display results
writeDump(files);
// Close connection
bx:ftp action="close" connection="myConn";
bx:ftp action="open" connection="uploader" server="ftp.example.com" username="user" password="pass";
bx:ftp
action="putfile"
connection="uploader"
localFile="/Users/documents/report.pdf"
remoteFile="/uploads/monthly-report.pdf"
result="uploadResult";
if (uploadResult.succeeded) {
writeOutput("File uploaded successfully!");
} else {
writeOutput("Upload failed: #uploadResult.statusText#");
}
bx:ftp action="close" connection="uploader";
bx:ftp action="open" connection="downloader" server="ftp.example.com" username="user" password="pass";
bx:ftp
action="getfile"
connection="downloader"
remoteFile="/reports/sales-2025.csv"
localFile="/Users/downloads/sales-2025.csv"
failIfExists="false"
result="downloadResult";
writeOutput("Download complete: #downloadResult.succeeded#");
bx:ftp action="close" connection="downloader";
try {
// Open secure connection
bx:ftp
action="open"
connection="secureFTP"
server="secure.ftp.example.com"
port="990"
username="admin"
password="secretpass"
secure="true"
passive="true"
timeout="60"
result="ftpResult";
if (ftpResult.succeeded) {
writeOutput("Connected to secure FTP server");
// Perform operations...
} else {
throw("Connection failed: #ftpResult.statusText#");
}
} catch (any e) {
writeOutput("FTP Error: #e.message#");
} finally {
bx:ftp action="close" connection="secureFTP";
}
// Connect via proxy with custom port
bx:ftp
action="open"
connection="proxyConn"
server="ftp.example.com"
username="user"
password="pass"
proxyServer="proxy.company.com:8080";
// Connect via proxy with default port (1080)
bx:ftp
action="open"
connection="proxyConn"
server="ftp.example.com"
username="user"
password="pass"
proxyServer="proxy.company.com";
bx:ftp action="open" connection="dirManager" server="ftp.example.com" username="user" password="pass";
// Create a new directory
bx:ftp
action="createdir"
connection="dirManager"
new="/uploads/2025"
result="createResult";
// Check if directory exists
bx:ftp
action="existsdir"
connection="dirManager"
directory="/uploads/2025"
result="existsResult";
writeOutput("Directory exists: #existsResult.returnValue#");
// Change to directory
bx:ftp
action="changedir"
connection="dirManager"
directory="/uploads/2025";
// Get current directory
bx:ftp
action="getcurrentdir"
connection="dirManager"
result="cwdResult";
writeOutput("Current directory: #cwdResult.returnValue#");
bx:ftp action="close" connection="dirManager";
bx:ftp action="open" connection="lister" server="ftp.example.com" username="user" password="pass";
// Get directory listing as array of structs
bx:ftp
action="listdir"
connection="lister"
directory="/uploads"
name="fileList"
returnType="array";
// Process each file
for (file in fileList) {
writeOutput("File: #file.name# - Size: #file.size# bytes - Type: #file.type#<br>");
if (file.isDirectory) {
writeOutput(" β This is a directory<br>");
} else {
writeOutput(" β Last modified: #file.lastModified#<br>");
}
}
bx:ftp action="close" connection="lister";
bx:ftp action="open" connection="bulk" server="ftp.example.com" username="user" password="pass";
files = ["report1.pdf", "report2.pdf", "report3.pdf"];
successCount = 0;
failCount = 0;
for (fileName in files) {
bx:ftp
action="putfile"
connection="bulk"
localFile="/local/reports/#fileName#"
remoteFile="/remote/reports/#fileName#"
result="uploadResult";
if (uploadResult.succeeded) {
successCount++;
writeOutput("β Uploaded: #fileName#<br>");
} else {
failCount++;
writeOutput("β Failed: #fileName# - #uploadResult.statusText#<br>");
}
}
writeOutput("<br>Summary: #successCount# successful, #failCount# failed");
bx:ftp action="close" connection="bulk";
bx:ftp action="open" connection="renamer" server="ftp.example.com" username="user" password="pass";
// Rename a file
bx:ftp
action="renamefile"
connection="renamer"
existing="/uploads/temp.txt"
new="/uploads/final-report.txt"
result="renameResult";
if (renameResult.returnValue) {
writeOutput("File renamed successfully");
}
// Rename a directory
bx:ftp
action="renamedir"
connection="renamer"
existing="/old-folder"
new="/new-folder"
result="dirRenameResult";
bx:ftp action="close" connection="renamer";
All actions can use a result attribute to store the
result of the action in a variable. If not provided, the result will
be stored in a variable called bxftp (or
cftp if you are in CFML compat mode).
open - Connect to
FTP ServerOpens a connection to an FTP server and tracks it in the FTPService.
Attributes:
| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
connection
| string | β Yes | - | Name of the connection to track |
server
| string | β Yes | - | Server IP or hostname |
port
| numeric | No | 21 | FTP port number |
username
| string | β Yes | - | Authentication username |
password
| string | β Yes | - | Authentication password |
timeout
| numeric | No | 30 | Connection timeout in seconds |
secure
| boolean | No | false | Use secure FTP (FTPS) |
passive
| boolean | No | true | Use passive mode |
proxyServer
| string | No | - | Proxy server (hostname:port) |
Examples:
// Basic connection
bx:ftp
action="open"
connection="myConn"
server="ftp.example.com"
username="user"
password="pass";
// Secure connection with custom port
bx:ftp
action="open"
connection="secureConn"
server="secure.ftp.example.com"
port="990"
username="admin"
password="pass"
secure="true"
timeout="60";
// Connection via proxy
bx:ftp
action="open"
connection="proxyConn"
server="ftp.example.com"
username="user"
password="pass"
proxyServer="proxy.company.com:8080";
close - Close FTP ConnectionCloses an open FTP connection and removes it from the FTPService.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection
| string | β Yes | Name of the connection to close |
Example:
bx:ftp action="close" connection="myConn";
changedir
- Change Working DirectoryChanges the current working directory on the FTP server.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection
| string | β Yes | Connection name |
directory
| string | β Yes | Directory path to change to |
Example:
bx:ftp action="changedir" connection="myConn" directory="/uploads/2025";
createdir -
Create DirectoryCreates a new directory on the FTP server.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection
| string | β Yes | Connection name |
new
| string | β Yes | Path of directory to create |
Example:
bx:ftp action="createdir" connection="myConn" new="/uploads/reports" result="createResult";
if (createResult.returnValue) {
writeOutput("Directory created successfully");
}
existsdir
- Check Directory ExistenceChecks if a directory exists on the FTP server.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection
| string | β Yes | Connection name |
directory
| string | β Yes | Directory path to check |
Example:
bx:ftp action="existsdir" connection="myConn" directory="/uploads" result="existsResult";
if (existsResult.returnValue) {
writeOutput("Directory exists");
} else {
writeOutput("Directory not found");
}
getcurrentdir
- Get Working DirectoryRetrieves the current working directory path.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection
| string | β Yes | Connection name |
Example:
bx:ftp action="getcurrentdir" connection="myConn" result="cwdResult";
writeOutput("Current directory: #cwdResult.returnValue#");
listdir -
List Directory ContentsLists files and directories in the specified directory.
Attributes:
| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
connection
| string | β Yes | - | Connection name |
directory
| string | β Yes | - | Directory to list |
name
| string | β Yes | - | Variable name to store results |
returnType
| string | No | "query" | Return format: "query" or "array" |
Query Columns:
name - File/directory nameisDirectory - Boolean indicating if item is a directorylastModified - Last modification timestampsize - File size in bytes (aliased as
length for CFML compatibility)mode - File permissions modepath - File path without drive designationurl - Complete URL for the itemtype - Type: "file", "directory",
"symbolic link", or "unknown"raw - Raw FTP listing representationattributes - File attributesisReadable - Boolean for read permissionisWritable - Boolean for write permissionisExecutable - Boolean for execute permissionExamples:
// List as query (default)
bx:ftp action="listdir" connection="myConn" directory="/" name="files";
// List as array of structs
bx:ftp action="listdir" connection="myConn" directory="/" name="files" returnType="array";
// Process results
for (file in files) {
writeOutput("#file.name# - #file.size# bytes<br>");
}
removedir -
Remove DirectoryRemoves a directory from the FTP server.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection
| string | β Yes | Connection name |
directory
| string | β Yes | Directory path to remove |
Example:
bx:ftp action="removedir" connection="myConn" directory="/temp/old-data" result="removeResult";
if (removeResult.returnValue) {
writeOutput("Directory removed successfully");
}
renamedir -
Rename DirectoryRenames a directory on the FTP server.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection
| string | β Yes | Connection name |
existing
| string | β Yes | Current directory path |
new
| string | β Yes | New directory path |
Example:
bx:ftp
action="renamedir"
connection="myConn"
existing="/old-name"
new="/new-name"
result="renameResult";
existsfile -
Check File ExistenceChecks if a file exists on the FTP server.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection
| string | β Yes | Connection name |
remoteFile
| string | β Yes | Remote file path to check |
Example:
bx:ftp action="existsfile" connection="myConn" remoteFile="/data/report.pdf" result="existsResult";
if (existsResult.returnValue) {
writeOutput("File exists");
}
getfile - Download FileDownloads a file from the FTP server to the local filesystem.
Attributes:
| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
connection
| string | β Yes | - | Connection name |
remoteFile
| string | β Yes | - | Remote file path to download |
localFile
| string | β Yes | - | Local file path to save to |
failIfExists
| boolean | No | true | Fail if local file already exists |
Example:
// Download with overwrite protection
bx:ftp
action="getfile"
connection="myConn"
remoteFile="/reports/sales.csv"
localFile="/Users/downloads/sales.csv";
// Download and overwrite if exists
bx:ftp
action="getfile"
connection="myConn"
remoteFile="/reports/sales.csv"
localFile="/Users/downloads/sales.csv"
failIfExists="false"
result="downloadResult";
if (downloadResult.succeeded) {
writeOutput("Download complete");
}
putfile - Upload FileUploads a file from the local filesystem to the FTP server.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection
| string | β Yes | Connection name |
localFile
| string | β Yes | Local file path to upload |
remoteFile
| string | β Yes | Remote file path destination |
Example:
bx:ftp
action="putfile"
connection="myConn"
localFile="/Users/documents/report.pdf"
remoteFile="/uploads/report.pdf"
result="uploadResult";
if (uploadResult.succeeded) {
writeOutput("Upload successful: #uploadResult.statusText#");
}
removefile
(or remove) - Delete FileDeletes a file from the FTP server.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection
| string | β Yes | Connection name |
remoteFile
| string | β Yes | Remote file path to delete |
Example:
bx:ftp action="removefile" connection="myConn" remoteFile="/temp/old-file.txt" result="removeResult";
if (removeResult.returnValue) {
writeOutput("File deleted successfully");
}
renamefile - Rename FileRenames a file on the FTP server.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection
| string | β Yes | Connection name |
existing
| string | β Yes | Current file path |
new
| string | β Yes | New file path |
Example:
bx:ftp
action="renamefile"
connection="myConn"
existing="/uploads/temp.txt"
new="/uploads/final-report.txt"
result="renameResult";
if (renameResult.returnValue) {
writeOutput("File renamed successfully");
}
All FTP actions return a result object with the following structure:
{
statusCode : 200, // Numeric FTP status code
statusText : "Success", // Human-readable status message
errorCode : 0, // Error code (if any)
errorText : "", // Error message (if any)
returnValue : true, // Action-specific return value
succeeded : true // Boolean indicating success/failure
}
| Code | Meaning | Description |
|---|---|---|
| 200 | Success | Command successful |
| 221 | Goodbye | Service closing control connection |
| 226 | Transfer complete | Closing data connection, file transfer successful |
| 230 | Login successful | User logged in |
| 250 | Success | Requested file action okay, completed |
| 550 | Failed | File unavailable or permission denied |
| 552 | Exceeded | Storage allocation exceeded |
| 553 | Not allowed | File name not allowed |
// Store result in custom variable
bx:ftp action="putfile" connection="myConn" localFile="test.txt" remoteFile="/test.txt" result="uploadResult";
if (uploadResult.succeeded) {
writeOutput("Success! Status: #uploadResult.statusCode#");
} else {
writeOutput("Failed: #uploadResult.errorText#");
}
// Default result variable (bxftp)
bx:ftp action="putfile" connection="myConn" localFile="test.txt" remoteFile="/test.txt";
if (bxftp.succeeded) {
writeOutput("Upload successful");
}
The FTP module announces several interception points that allow you to hook into the FTP operation lifecycle for logging, monitoring, metrics, or custom logic.
beforeFTPCall
Announced before any FTP action is executed.
Interceptor Data:
{
connection : FTPConnection, // The FTP connection object
action : "putfile", // The action being performed
result : FTPResult, // The result object (empty at this point)
attributes : { // All attributes passed to the component
connection : "myConn",
localFile : "/path/to/file.txt",
remoteFile : "/remote/file.txt"
// ... other attributes
}
}
Use Cases:
Example:
class {
function beforeFTPCall( event, interceptData ) {
var logger = getLogger();
logger.info(
"FTP Action Starting: #interceptData.action# on connection #interceptData.attributes.connection#"
);
// Store start time for performance tracking
interceptData.startTime = now();
}
}
afterFTPCall
Announced after an FTP action completes successfully.
Interceptor Data:
{
connection : FTPConnection, // The FTP connection object
action : "putfile", // The action that was performed
result : FTPResult, // The populated result object
attributes : { // All attributes passed to the component
connection : "myConn",
localFile : "/path/to/file.txt",
remoteFile : "/remote/file.txt"
}
}
Use Cases:
Example:
class {
function afterFTPCall( event, interceptData ) {
var logger = getLogger();
var result = interceptData.result;
logger.info(
"FTP Action Completed: #interceptData.action# - Status: #result.statusCode# - Success: #result.succeeded#"
);
// Track successful transfers
if ( result.succeeded && interceptData.action == "putfile" ) {
metrics.recordUpload( interceptData.attributes.remoteFile );
}
}
}
onFTPConnectionOpen
Announced when a new FTP connection is opened.
Interceptor Data:
{
connection : FTPConnection, // The newly opened connection
attributes : { // Connection attributes
server : "ftp.example.com",
username : "user",
port : 21,
passive : true
// ... other connection attributes
}
}
Use Cases:
Example:
class {
function onFTPConnectionOpen( event, interceptData ) {
var logger = getLogger();
var attrs = interceptData.attributes;
logger.info(
"FTP Connection Opened: #attrs.server#:#attrs.port# as #attrs.username# (Passive: #attrs.passive#)"
);
// Track active connections
connectionMonitor.recordConnection(
server = attrs.server,
user = attrs.username,
timestamp = now()
);
}
}
onFTPConnectionClose
Announced when an FTP connection is closed.
Interceptor Data:
{
connection : FTPConnection // The connection being closed
}
Use Cases:
Example:
component {
function onFTPConnectionClose( event, interceptData ) {
var logger = getLogger();
var conn = interceptData.connection;
logger.info(
"FTP Connection Closed: #conn.getName()# - Status: #conn.getStatus()#"
);
// Track connection closures
connectionMonitor.recordClosure(
connection = conn.getName(),
timestamp = now()
);
}
}
onFTPError
Announced when an FTP operation encounters an error.
Interceptor Data:
{
connection : FTPConnection, // The FTP connection object
action : "putfile", // The action that failed
error : IOException, // The exception object
attributes : { // All attributes passed to the component
connection : "myConn",
localFile : "/path/to/file.txt",
remoteFile : "/remote/file.txt"
}
}
Use Cases:
Example:
component {
function onFTPError( event, interceptData ) {
var logger = getLogger();
var error = interceptData.error;
logger.error(
"FTP Action Failed: #interceptData.action# - Error: #error.getMessage()#",
error
);
// Send alert for critical errors
if ( interceptData.action == "putfile" ) {
alertService.send(
message = "FTP upload failed: #error.getMessage()#",
severity = "high",
details = interceptData
);
}
}
}
The FTP module uses the FTPService to manage named connections globally across your application.
// Open once
bx:ftp action="open" connection="myConn" server="ftp.example.com" username="user" password="pass";
// Use multiple times
bx:ftp action="putfile" connection="myConn" localFile="file1.txt" remoteFile="/file1.txt";
bx:ftp action="putfile" connection="myConn" localFile="file2.txt" remoteFile="/file2.txt";
bx:ftp action="listdir" connection="myConn" directory="/" name="files";
// Close when done
bx:ftp action="close" connection="myConn";
After opening a connection, it's stored in a variable with the connection name:
bx:ftp action="open" connection="info" server="ftp.example.com" username="user" password="pass";
writeDump( info );
// Outputs connection details: server, port, username, status, etc.
Connection Refused Errors:
Passive vs Active Mode:
try {
bx:ftp action="open" connection="safeConn" server="ftp.example.com" username="user" password="pass";
bx:ftp action="putfile" connection="safeConn" localFile="file.txt" remoteFile="/file.txt" result="uploadResult";
if (!uploadResult.succeeded) {
throw("Upload failed: #uploadResult.statusText#");
}
} catch (any e) {
writeOutput("FTP Error: #e.message#<br>");
// Log error, send alert, etc.
} finally {
// Always close connection
bx:ftp action="close" connection="safeConn";
}
bx:ftp action="putfile" connection="conn" localFile="test.txt" remoteFile="/test.txt" result="ftpResult";
switch (ftpResult.statusCode) {
case 226:
writeOutput("Transfer successful");
break;
case 550:
writeOutput("File not found or permission denied");
break;
case 552:
writeOutput("Storage allocation exceeded");
break;
case 553:
writeOutput("File name not allowed");
break;
default:
writeOutput("Operation failed: #ftpResult.statusText#");
}
Problem: FTP operations fail with "Connection refused" errors.
Solutions:
nc -v hostname port
Problem: Connection succeeds but file transfers fail.
Solutions:
passive="false"
Problem: Files fail to upload with permission errors.
Solutions:
Problem: Connection attempts timeout.
Solutions:
timeout="60"
Problem: Cannot connect through proxy server.
Solutions:
proxyServer="proxy.company.com:8080"
This module includes Docker configuration for local FTP server testing:
# Start FTP test server
docker-compose up -d --build
# Run tests
./gradlew test
# Stop FTP server
docker-compose down
# Clone repository
git clone https://github.com/ortus-boxlang/bx-ftp.git
cd bx-ftp
# Download BoxLang dependency
./gradlew downloadBoxLang
# Build module
./gradlew build
# Run tests (requires Docker FTP server)
docker-compose up -d
./gradlew test
# Create distribution
./gradlew zipModuleStructure
@BoxComponent) providing FTP operationsThe included Docker setup provides a consistent FTP testing environment:
# Start FTP server with specific configuration
docker-compose up -d --build
# View logs
docker-compose logs -f
# Stop and remove containers
docker-compose down
# Force recreate (after vsftpd.conf changes)
docker-compose up -d --build --force-recreate
Important: The vsftpd.conf file includes
critical passive mode configuration:
pasv_enable=YES
pasv_min_port=10000
pasv_max_port=10010
pasv_address=127.0.0.1
This module will require the bx-cfml-compat module if
you want it to work like Adobe ColdFusion/Lucee in your CFML applications.
bxftp by
default, CFML compat mode uses cftp
bx:ftp in BoxLang,
cftp with compat moduleAll CFML <cfftp> should work with no changes when
using bx-cfml-compat. However, for native BoxLang usage,
update tags as follows:
// CFML
<cfftp action="open" connection="myConn" server="ftp.example.com" username="user" password="pass">
// BoxLang (with bx-cfml-compat)
<cftp action="open" connection="myConn" server="ftp.example.com" username="user" password="pass">
// BoxLang (native)
<bx:ftp action="open" connection="myConn" server="ftp.example.com" username="user" password="pass">
We β€οΈ contributions! This project is open source and welcomes your help to make it even better.
If you discover a bug, please:
We'd love to hear your ideas! Please:
Documentation improvements are always welcome:
You can support BoxLang and all Ortus Solutions open source projects:
Patrons get exclusive benefits like:
Need help? Don't create an issueβuse our support channels:
Thank you to all our amazing contributors! β€οΈ
Made with contributors-img
If you discover a security vulnerability:
#security channel on Box Team SlackAll vulnerabilities will be promptly addressed.
This project is licensed under the Apache License 2.0.
Copyright 2025 Ortus Solutions, Corp
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
See LICENSE file for full details.
"I am the way, and the truth, and the life; no one comes to the Father, but by me (JESUS)" Jn 14:1-12
Copyright Since 2025 by Ortus Solutions, Corp
www.boxlang.io | www.ortussolutions.com
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
onFTPError interception point for handling FTP errors globallytests.yml to latest versionsbx-ftp module
$
box install bx-ftp