Extensions and Scripting

Extensions allow you to extend CompleteFTP's functionality with custom logic written in JSS (JavaScript Server-Side) or compiled .NET assemblies. You can add custom authentication, virtual file systems, FTP/SFTP commands, event handlers, and IP filtering — all managed through the CLI.

This chapter covers:

  • Extension types - Authentication, file system, custom command, event, and IP filter extensions
  • JSS extensions - Adding and registering JavaScript Server-Side scripts
  • .NET assembly extensions - Registering compiled .NET libraries
  • addscript vs registerscript - Choosing how script content is managed
  • JSS API - Built-in objects for file operations, FTP/SFTP, HTTP, email, and database access
  • Custom commands - Calling extension functions from the CLI
  • Extension lifecycle - Enabling, disabling, and removing extensions

All extension operations use the completeftp extension command group.

Extension System Overview

CompleteFTP's extension system supports two development approaches:

Approach Language Command Best For
JSS scripts JavaScript (server-side) addscript or registerscript Rapid development, simple logic, scripting
.NET assemblies C#, F#, VB.NET registerassembly Complex logic, external library integration, performance

Extension Types

Each extension must declare a type that determines when and how it is invoked:

Type Description Use Cases
authentication Custom authentication logic LDAP lookups, API-based login, multi-source auth
filesystem Custom virtual file system provider Database-backed folders, API-driven file listings
customcommand Custom FTP/SFTP commands SITE commands, custom server operations
event Event handler that fires on server events Logging, notifications, workflow automation
ipfilter Custom IP filtering logic Dynamic allow/deny lists, geo-blocking (.NET assemblies only)

Listing and Viewing Extensions

List Extensions

# List all custom extensions
completeftp extension list

# List with specific properties
completeftp extension list name type

# Include built-in system extensions
completeftp extension list --includeSystem

Show Extension Details

# Show all properties of an extension
completeftp extension show MyExtension

# Show specific properties
completeftp extension show MyExtension name type configurationPath

Adding JSS Extensions

JSS extensions can be added in two ways: addscript and registerscript. The choice determines how the script content is stored and updated.

addscript — Store Script in Configuration Database

With addscript, the script file is read once and its content is stored in CompleteFTP's configuration database. Changes to the original file after adding are not reflected.

# Basic syntax
completeftp extension addscript <extensionName> <extensionType> <scriptFilePath> [properties]

# Add an authentication extension from a script file
completeftp extension addscript MyAuth authentication /opt/cftp/scripts/my-auth.jss

# Add an event extension with properties
completeftp extension addscript AuditLogger event /opt/cftp/scripts/audit.jss

# Read script from stdin
echo 'console.log("Hello from extension");' | \
  completeftp extension addscript HelloEvent event -

Note: After using addscript, the original script file is no longer needed. The script content lives entirely in the configuration database. To update the script, use extension set or remove and re-add the extension.

registerscript — Reference Script File Path

With registerscript, only the file path is stored. The script is re-read from the file every time the extension is used. Changes to the file are reflected immediately.

# Basic syntax
completeftp extension registerscript <extensionName> <extensionType> <scriptFilePath> [properties]

# Register an authentication extension
completeftp extension registerscript MyAuth authentication /opt/cftp/scripts/my-auth.jss

# Register an IP filter extension
completeftp extension registerscript GeoFilter ipfilter /opt/cftp/scripts/geofilter.jss

# Register a custom command extension
completeftp extension registerscript DiskUsage customcommand /opt/cftp/scripts/disk-usage.jss

Important: The script file must remain accessible at the registered path. If the file is moved or deleted, the extension will fail at runtime.

Choosing Between addscript and registerscript

Consideration addscript registerscript
Script storage Configuration database File system
Updates after adding Must re-add or use extension set Edit the file directly
File dependency None after adding File must remain at path
Deployment Self-contained in config Requires file on disk
Development workflow Better for stable, production scripts Better for active development

Registering .NET Assembly Extensions

Compiled .NET assemblies provide full access to the .NET runtime and external libraries.

# Basic syntax
completeftp extension registerassembly <extensionName> <extensionType> <assemblyPath> <className> [properties]

# Register an authentication assembly
completeftp extension registerassembly LdapAuth authentication \
  /opt/cftp/extensions/LdapAuth.dll \
  MyCompany.Extensions.LdapAuthProvider

# Register a file system assembly
completeftp extension registerassembly DbFileSystem filesystem \
  /opt/cftp/extensions/DbFileSystem.dll \
  MyCompany.Extensions.DatabaseFileSystem

# Register a custom command assembly
completeftp extension registerassembly AdminTools customcommand \
  /opt/cftp/extensions/AdminTools.dll \
  MyCompany.Extensions.AdminCommands

Note: The assembly DLL must remain accessible at the registered path. For full documentation on developing .NET extensions, including the required interfaces and base classes, refer to the CompleteFTP Windows User Guide.

Extension Types with Examples

Authentication Extensions

Authentication extensions implement custom login logic. A JSS authentication extension must implement loadUserInfo(userName, userInfo), which is called when a user attempts to log in. Return an object with a password (or passwordHash) field to authenticate, or null to reject/fall through to the next authenticator.

# Create an authentication script file
cat > /opt/cftp/scripts/api-auth.jss << 'SCRIPT'
// Authenticate against an external REST API
// Authentication extensions must implement loadUserInfo(userName, userInfo)
// Return an object with "password" (or "passwordHash") to authenticate,
// or null to reject / fall through to the next authenticator.
function loadUserInfo(userName, userInfo) {
  try {
    var response = http.get("https://auth.example.com/validate?user=" +
      encodeURIComponent(userName));
    var result = JSON.parse(response.body);
    if (result.valid) {
      return { password: result.password };
    }
  } catch (e) {
    console.log("API auth error: " + e);
  }
  return null;
}
SCRIPT

# Register the authentication extension
completeftp extension registerscript ApiAuth authentication /opt/cftp/scripts/api-auth.jss

File System Extensions

File system extensions provide virtual folders backed by custom logic. At minimum, implement getFileInfos(path) returning an array of {name, isDirectory, length} objects. Optionally implement read(path) for downloads (return {text: "..."}, {file: "/path"}, or {base64: "..."}) and write(path, data) for uploads.

# Create a file system script
cat > /opt/cftp/scripts/report-fs.jss << 'SCRIPT'
// Virtual file system that generates reports on the fly
// File system extensions must implement getFileInfos(path) at minimum.
// The read(path) function enables file downloads.
function getFileInfos(path) {
  var files = [];
  files.push({name: "daily-report.csv", length: 0, isDirectory: false});
  files.push({name: "weekly-report.csv", length: 0, isDirectory: false});
  return files;
}

function read(path) {
  if (path === "daily-report.csv") {
    return { text: "date,total\n2026-03-16,42\n" };
  }
  if (path === "weekly-report.csv") {
    return { text: "week,total\n2026-W11,280\n" };
  }
  throw "No such file: " + path;
}
SCRIPT

# Add the file system extension
completeftp extension addscript ReportFS filesystem /opt/cftp/scripts/report-fs.jss

Custom Command Extensions

Custom command extensions add new FTP SITE commands or SFTP extended commands that clients can invoke.

# Create a custom command script
cat > /opt/cftp/scripts/disk-usage.jss << 'SCRIPT'
// Custom SITE DISKUSAGE command
// Custom command extensions define functions that become SITE commands in FTP
// and shell commands in SSH. Each function is a command; arguments are strings.
function diskUsage(path) {
  if (!path) path = "/";
  var folder = system.getFolder(path);
  var files = folder.getFiles();
  var totalSize = 0;
  for (var i = 0; i < files.length; i++) {
    totalSize += files[i].size;
  }
  return "Total size: " + totalSize + " bytes (" + files.length + " files)";
}
SCRIPT

# Register the custom command extension
completeftp extension registerscript DiskUsage customcommand /opt/cftp/scripts/disk-usage.jss

Event Extensions

Event extensions fire in response to server events such as file uploads, logins, and disconnections. They are similar to JSS process triggers but are registered as extensions rather than configured through the trigger command.

Note: For most event-driven automation, Process Triggers are the recommended approach. Event extensions are best suited for cases where you need the logic to be part of the extension system (e.g., for packaging or distribution).

# Create an event handler script
# Event extensions receive event details through the event object,
# the same as JSS process triggers (see Chapter 10).
cat > /opt/cftp/scripts/upload-notify.jss << 'SCRIPT'
if (event.eventType === "UploadFile") {
  mail.send(
    "admin@example.com",
    "File Upload Notification",
    "User " + event.loginUserName + " uploaded " + event.fileName +
    " at " + event.time
  );
}
SCRIPT

# Add the event extension
completeftp extension addscript UploadNotify event /opt/cftp/scripts/upload-notify.jss

IP Filter Extensions

IP filter extensions implement custom allow/deny logic evaluated for each incoming connection. The extension must implement GetFilterAction(ipAddress) which returns whether to allow or deny the connection.

Note: IP filter extensions require a compiled .NET assembly. JSS scripts are not supported for this extension type. For the required .NET interface and development instructions, refer to the CompleteFTP Windows User Guide.

# Register a .NET IP filter assembly
completeftp extension registerassembly BusinessHours ipfilter \
  /opt/cftp/extensions/BusinessHoursFilter.dll \
  MyCompany.Extensions.BusinessHoursFilter

Calling Custom Commands via CLI

Extensions that define custom command functions can be invoked directly from the CLI using extension call.

# Basic syntax
completeftp extension call <functionName> [arguments]

# Call a custom function with no arguments
completeftp extension call diskUsage

# Call a custom function with arguments
completeftp extension call generateReport "2026-03-01" "2026-03-16"

# Call a function and capture output
result=$(completeftp extension call checkStatus)
echo "Status: $result"

Note: Only functions defined in customcommand extensions are callable via extension call. The function must be exported by the extension script.

JSS API Overview

JSS (JavaScript Server-Side) scripts have access to a rich set of built-in objects and functions for interacting with CompleteFTP and external systems.

The system Object

The system object provides access to the CompleteFTP virtual file system:

# Example: file operations in a JSS script
cat > /opt/cftp/scripts/file-ops.jss << 'SCRIPT'
// Get a file reference
var file = system.getFile("/Home/alice/report.csv");

// Get file properties
console.log("Size: " + file.size);
console.log("Modified: " + file.lastModified);

// Move and copy files
file.copyTo("/Archive");
file.moveTo("/Processed");

// Get a folder reference
var folder = system.getFolder("/Home/alice");
var files = folder.getFiles();
for (var i = 0; i < files.length; i++) {
  console.log(files[i].name + " - " + files[i].size + " bytes");
}

// Delete a file
system.getFile("/Temp/old-data.csv").remove();
SCRIPT

FTP/SFTP Client

JSS includes a built-in FTP/SFTP client for transferring files to and from remote servers:

cat > /opt/cftp/scripts/remote-transfer.jss << 'SCRIPT'
// Connect to a remote SFTP server
var ftp = new Ftp("partner.example.com", "xferuser", "xferpass", "sftp");
ftp.connect();

// Upload a file
ftp.upload("/Outbound/data.csv", "data.csv");

// Download a file
ftp.download("incoming/report.csv", "/Inbound/report.csv");

// List remote directory
var files = ftp.getFiles("incoming/");
for (var i = 0; i < files.length; i++) {
  console.log(files[i]);
}

ftp.close();
SCRIPT

HTTP Client

Make HTTP requests to external APIs and web services:

cat > /opt/cftp/scripts/http-example.jss << 'SCRIPT'
// GET request
var response = http.get("https://api.example.com/status");
console.log("Status: " + response.statusCode);
console.log("Body: " + response.body);

// POST request with JSON body
var data = JSON.stringify({user: loginUserName, action: "upload"});
var response = http.post("https://api.example.com/audit", data, "application/json");

// POST with custom headers
var headers = {"Authorization": "Bearer mytoken123"};
var response = http.post("https://api.example.com/notify", data, "application/json", headers);
SCRIPT

Email

Send email notifications from JSS scripts:

cat > /opt/cftp/scripts/email-example.jss << 'SCRIPT'
// Send a simple email
mail.send("admin@example.com", "Alert: Upload Complete",
  "File " + fileName + " was uploaded by " + loginUserName);

// Send to multiple recipients
mail.send("admin@example.com,ops@example.com", "Transfer Report",
  "Daily transfer report attached.");
SCRIPT

Note: Email sending requires SMTP configuration. Configure the notification SMTP settings using completeftp notification smtp set.

Database Access

Query and update databases from JSS scripts:

cat > /opt/cftp/scripts/db-example.jss << 'SCRIPT'
// Connect to a database
var db = new DatabaseSync("Server=dbhost;Database=ftplog;User=logger;Password=secret");

// Execute a query
db.execute("INSERT INTO transfer_log (username, filename, timestamp) VALUES ('" +
  loginUserName + "', '" + fileName + "', NOW())");

// Query data
var rows = db.query("SELECT COUNT(*) as cnt FROM transfer_log WHERE username='" +
  loginUserName + "'");
console.log("Total transfers: " + rows[0].cnt);

db.close();
SCRIPT

PGP Encryption

The pgp object provides PGP encryption and decryption for both strings and files. Keys must be in armored ASCII format.

String Encryption and Decryption

cat > /opt/cftp/scripts/pgp-string.jss << 'SCRIPT'
var publicKey = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...\n-----END PGP PUBLIC KEY BLOCK-----";
var privateKey = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n...\n-----END PGP PRIVATE KEY BLOCK-----";
var passphrase = "my passphrase";

// Encrypt a string
var encrypted = pgp.encrypt("Sensitive data", publicKey);
console.log("Encrypted:\n" + encrypted);

// Decrypt it back
var decrypted = pgp.decrypt(encrypted, privateKey, passphrase);
console.log("Decrypted: " + decrypted);
SCRIPT

File Encryption and Decryption

cat > /opt/cftp/scripts/pgp-file.jss << 'SCRIPT'
var publicKey = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...\n-----END PGP PUBLIC KEY BLOCK-----";
var privateKey = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n...\n-----END PGP PRIVATE KEY BLOCK-----";
var passphrase = "my passphrase";

// Encrypt a file
pgp.encryptFileWithKey("/Data/report.csv", "/Data/report.csv.pgp", publicKey);

// Decrypt a file
pgp.decryptFileWithKey("/Data/report.csv.pgp", "/Data/report.csv", privateKey, passphrase);
SCRIPT

File arguments can be either a path string or a File object obtained from system.getFile().

Using Folder PGP Keys

PGP keys can be stored on folders (see Folder Management). When encryptFile or decryptFile are called without explicit keys, CompleteFTP automatically searches the input file's folder and its ancestors for the appropriate key. This means triggers and scripts can encrypt and decrypt files without needing admin access:

cat > /opt/cftp/scripts/pgp-folder-keys.jss << 'SCRIPT'
// Encrypt using the folder's public key — no key argument needed
pgp.encryptFile(filePath, filePath + ".pgp");
system.getFile(filePath).remove();
console.log("Encrypted: " + filePath + ".pgp");

// Decrypt using the folder's private key — no key argument needed
pgp.decryptFile(filePath, outputPath);
system.getFile(filePath).remove();
console.log("Decrypted: " + outputPath);
SCRIPT

You can still pass explicit keys when needed — for example, when using a key that isn't associated with any folder.

PGP Methods Reference

Method Description
pgp.encrypt(plaintext, publicKey) Encrypt a string, returns armored ASCII ciphertext
pgp.decrypt(ciphertext, privateKey, passphrase) Decrypt an armored ASCII string
pgp.encryptFile(inputFile, outputFile) Encrypt a file using the folder's public key
pgp.encryptFileWithKey(inputFile, outputFile, publicKey) Encrypt a file with an explicit public key
pgp.decryptFile(inputFile, outputFile) Decrypt a file using the folder's private key
pgp.decryptFileWithKey(inputFile, outputFile, privateKey, passphrase) Decrypt a file with an explicit private key

Using .NET Classes in JSS

JSS scripts can instantiate and use .NET classes directly:

cat > /opt/cftp/scripts/dotnet-in-jss.jss << 'SCRIPT'
// Use .NET System.IO for local file operations
var content = System.IO.File.ReadAllText("/etc/hostname");
console.log("Hostname: " + content.trim());

// Use .NET XML parsing
var doc = new System.Xml.XmlDocument();
doc.Load("/opt/cftp/config/settings.xml");
console.log("Root: " + doc.DocumentElement.Name);

// Use .NET date/time
var now = System.DateTime.Now;
console.log("Server time: " + now.ToString("yyyy-MM-dd HH:mm:ss"));
SCRIPT

.NET Extension Development

For complex extension logic, you can develop extensions as compiled .NET assemblies. This provides full access to the .NET runtime, NuGet packages, and strongly-typed APIs.

Overview

.NET extensions are compiled DLLs that implement specific CompleteFTP interfaces depending on the extension type:

Extension Type Interface / Base Class
authentication Authentication provider interface
filesystem File system provider interface
customcommand Custom command interface
event Event handler interface
ipfilter IP filter interface

Registering a .NET Extension

# Compile your .NET project (on a development machine)
# dotnet build -c Release

# Copy the DLL to the server
# scp bin/Release/net6.0/MyExtension.dll server:/opt/cftp/extensions/

# Register the assembly
completeftp extension registerassembly MyExtension authentication \
  /opt/cftp/extensions/MyExtension.dll \
  MyCompany.Extensions.CustomAuthProvider

Using .NET from JSS

JSS scripts can instantiate .NET classes directly using the new keyword with the fully qualified class name (e.g., new System.Xml.XmlDocument()). This allows JSS to leverage the full .NET framework without needing a bridge extension.

Note: For complete .NET extension development documentation, including interface definitions, project setup, and debugging, refer to the CompleteFTP Windows User Guide.

Managing Extensions

Extensions cannot be enabled or disabled individually — they are active as long as they are registered. To temporarily deactivate an extension, remove it and re-add it later.

Modifying Extension Properties

# Update the script content for an addscript extension
completeftp extension set MyExtension configuration="console.log('updated');"

# Update the file path for a registerscript extension
completeftp extension set MyExtension configurationPath=/opt/cftp/scripts/updated-auth.jss

Removing Extensions

# Remove an extension
completeftp extension remove MyExtension

Note: Removing an extension deletes its configuration from the database but does not delete the associated script or assembly files from disk.

Workflow Examples

Custom Authentication with Fallback

Set up an authentication extension that checks an external API, falling back to CompleteFTP's built-in authentication:

# 1. Create the authentication script
cat > /opt/cftp/scripts/api-auth-fallback.jss << 'SCRIPT'
// Try external API authentication first.
// Return user info object to authenticate, or null to fall through
// to the next authenticator (e.g., built-in auth).
function loadUserInfo(userName, userInfo) {
  try {
    var response = http.get("https://auth.internal.example.com/check?user=" +
      encodeURIComponent(userName));
    var result = JSON.parse(response.body);
    if (result.authenticated) {
      return { password: result.password, homeDirectory: "/Home/" + userName };
    }
  } catch (e) {
    // API unavailable — fall through to built-in auth
    console.log("External auth API error: " + e);
  }
  return null;
}
SCRIPT

# 2. Register the extension
completeftp extension registerscript ApiAuth authentication \
  /opt/cftp/scripts/api-auth-fallback.jss

# 3. Verify registration
completeftp extension show ApiAuth name type

Event-Driven File Processing Pipeline

Automatically process, archive, and notify on file uploads:

# 1. Create the event handler script
# Event extensions use the event object for event details (same as JSS triggers)
cat > /opt/cftp/scripts/upload-pipeline.jss << 'SCRIPT'
if (event.eventType === "UploadFile") {
  try {
    // Copy to archive
    var file = system.getFile(event.virtualPath);
    file.copyTo("/Archive");

    // Forward to partner via SFTP
    var sftp = new Ftp("partner.example.com", "xfer", "xferpass", "sftp");
    sftp.connect();
    sftp.upload(event.virtualPath, event.fileName);
    sftp.close();

    // Log to database
    var db = new DatabaseSync("Server=localhost;Database=ftplog;User=logger;Password=secret");
    db.execute("INSERT INTO uploads (username, filename, timestamp) VALUES ('" +
      event.loginUserName + "', '" + event.fileName + "', NOW())");
    db.close();

    // Send notification
    mail.send("ops@example.com", "Upload Processed",
      "File: " + event.fileName + "\nUser: " + event.loginUserName + "\nStatus: OK");
  } catch (e) {
    mail.send("ops@example.com", "Upload Processing Error",
      "File: " + event.fileName + "\nError: " + e);
  }
}
SCRIPT

# 2. Register the event extension
completeftp extension registerscript UploadPipeline event \
  /opt/cftp/scripts/upload-pipeline.jss

# 3. Verify registration
completeftp extension show UploadPipeline

Dynamic IP Filtering from a Database

Block or allow IPs based on a database table that can be updated externally. Since IP filter extensions require .NET assemblies, this example uses the built-in rule-based IP filtering combined with a scheduled trigger to sync rules from a database:

# 1. Create a script that syncs IP rules from a database into CompleteFTP's IP filter
cat > /opt/cftp/scripts/sync-ip-rules.sh << 'SCRIPT'
#!/bin/bash
# Query blocked IPs from database and add as deny rules
# This is a simplified example — adapt the database query for your setup
mysql -h localhost -u reader -psecret ftpconfig \
  -N -e "SELECT ip_address FROM ip_rules WHERE blocked=1" | \
while read ip; do
  completeftp site ipfilter add default deny "$ip" 2>/dev/null
done
SCRIPT
chmod +x /opt/cftp/scripts/sync-ip-rules.sh

# 2. Create a scheduled trigger to sync every 15 minutes
completeftp trigger add "SyncIPRules" Timer Program /opt/cftp/scripts/sync-ip-rules.sh
completeftp trigger set SyncIPRules 'schedule=cron: 0 */15 * * * *'

Administrative Custom Commands

Add custom SITE commands for server administration:

# 1. Create the custom command script
cat > /opt/cftp/scripts/admin-commands.jss << 'SCRIPT'
function serverInfo() {
  var hostname = System.IO.File.ReadAllText("/etc/hostname").trim();
  var now = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
  return "Host: " + hostname + ", Time: " + now;
}

function userCount() {
  var db = new DatabaseSync("Server=localhost;Database=ftplog;User=reader;Password=secret");
  var rows = db.query("SELECT COUNT(DISTINCT username) as cnt FROM sessions WHERE active=1");
  db.close();
  return "Active users: " + rows[0].cnt;
}
SCRIPT

# 2. Register the custom command extension
completeftp extension registerscript AdminTools customcommand \
  /opt/cftp/scripts/admin-commands.jss

# 3. Call the commands via CLI
completeftp extension call serverInfo
completeftp extension call userCount

Troubleshooting

Extension Not Loading

# Check that the extension is registered
completeftp extension list name type
completeftp extension show MyExtension

# Verify the script file exists and is readable (for registerscript)
ls -la /opt/cftp/scripts/my-extension.jss

# Check server logs for extension loading errors
completeftp monitor set logging.level=Debug

Authentication Extension Not Working

# Verify the extension type is authentication
completeftp extension show MyAuth type

# Verify the extension is registered
completeftp extension show MyAuth

# Test with a simple script that always authenticates
cat > /tmp/test-auth.jss << 'SCRIPT'
function loadUserInfo(userName, userInfo) {
  console.log("Auth extension called for user: " + userName);
  return { password: "test123" };
}
SCRIPT
completeftp extension registerscript TestAuth authentication /tmp/test-auth.jss

Script Errors at Runtime

# Check server logs for JSS errors
# Extension errors are logged with the extension name

# Verify script syntax by reviewing the file
cat /opt/cftp/scripts/my-extension.jss

# Test with minimal script first, then add complexity

.NET Assembly Not Found

# Verify the assembly file exists
ls -la /opt/cftp/extensions/MyExtension.dll

# Check the registered path matches the actual path
completeftp extension show MyExtension assemblyPath

# Verify the class name is correct (fully qualified)
completeftp extension show MyExtension

Custom Command Not Callable

# Verify the extension type is customcommand
completeftp extension show MyExtension type

# Verify the extension is registered
completeftp extension show MyExtension

# Check that the function name matches exactly
completeftp extension call myFunction

Best Practices

Development

  1. Use registerscript during development - Changes are picked up immediately without re-registering
  2. Use addscript for production - Script is self-contained in the configuration, no external file dependency
  3. Start simple - Begin with minimal scripts and add complexity incrementally
  4. Add error handling - Wrap logic in try/catch blocks to prevent silent failures
  5. Log diagnostic information - Use console.log() to trace execution in server logs

Security

  1. Validate all input - Never trust user-supplied values in authentication or command extensions
  2. Sanitize database queries - Avoid SQL injection by validating parameters before building queries
  3. Limit file access - Keep extension scripts in a dedicated directory with restricted permissions
  4. Protect credentials - Do not hard-code passwords in scripts; use configuration or environment variables where possible
  5. Review IP filter logic - Test IP filter extensions thoroughly to avoid locking out legitimate users

Organization

  1. Use descriptive names - Name extensions clearly (e.g., LdapAuth, UploadNotify, GeoIpFilter)
  2. Separate concerns - Create one extension per responsibility rather than combining unrelated logic
  3. Document scripts - Add comments explaining the purpose and expected behavior
  4. Store scripts in a dedicated directory - Use a consistent path such as /opt/cftp/scripts/ or /opt/cftp/extensions/
  5. Version control scripts - Track extension scripts in source control for auditability

Performance

  1. Keep scripts efficient - Avoid long-running operations in authentication and IP filter extensions
  2. Close connections - Always close FTP, database, and HTTP connections when done
  3. Minimize external calls - Cache results where possible to reduce latency
  4. Use .NET assemblies for heavy logic - Compiled code runs faster than interpreted JSS for complex operations
  5. Disable unused extensions - Disabled extensions have zero runtime overhead

Quick Reference

Extension Management Commands

# List extensions
completeftp extension list [properties]

# Show extension details
completeftp extension show <extensionName> [properties]

# Add JSS extension (stored in config DB)
completeftp extension addscript <extensionName> <extensionType> <scriptFilePath> [properties]

# Register JSS extension (file path stored, re-read each use)
completeftp extension registerscript <extensionName> <extensionType> <scriptFilePath> [properties]

# Register .NET assembly extension
completeftp extension registerassembly <extensionName> <extensionType> <assemblyPath> <className> [properties]

# Modify extension
completeftp extension set <extensionName> property=value

# Remove extension
completeftp extension remove <extensionName>

# Call custom command function
completeftp extension call <functionName> [arguments]

Extension Types

authentication    = Custom authentication logic
filesystem        = Custom virtual file system provider
customcommand     = Custom FTP/SFTP commands
event             = Event handler (fires on server events)
ipfilter          = Custom IP filtering logic
ipfilter         = Custom IP filtering (.NET assemblies only)

addscript vs registerscript

# addscript: script content stored in config DB
#   - Changes to file NOT reflected after adding
#   - No file dependency at runtime
completeftp extension addscript MyExt event /opt/cftp/scripts/handler.jss

# registerscript: file path stored, re-read each use
#   - Changes to file ARE reflected immediately
#   - File must remain at registered path
completeftp extension registerscript MyExt event /opt/cftp/scripts/handler.jss

Common JSS Objects

system.getFile(path)      # Get file reference in virtual file system
system.getFolder(path)    # Get folder reference in virtual file system
new Ftp(host, user, pass) # FTP/SFTP client
http.get(url)             # HTTP GET request
http.post(url, body, ct)  # HTTP POST request
mail.send(to, subj, body) # Send email
new DatabaseSync(connStr) # Database connection
console.log(message)      # Log to server logs