BoxLang 🚀 A New JVM Dynamic Language Learn More...
This SDK will provide you with Amazon S3 connectivity for any ColdBox, BoxLang or CFML Application. It also works great as a standalone library outside of ColdBox, and is fully compatible with S3-compatible services like DigitalOcean Spaces, Google Cloud Storage and MinIO.
Built natively for BoxLang, and fully backwards compatible with Lucee and Adobe ColdFusion (ACF).
AmazonS3@s3sdk
GET and
PUT operations, so clients can upload/download directly
to/from S3 without proxying through your server500/503 responses
with configurable retry countsThis SDK can be installed as a standalone library or as a ColdBox Module. Either approach requires a simple CommandBox command:
box install s3sdk
The repository's BoxLang AI skills are ignored from version control. Install them locally with:
npx skills experimental_install
Then follow either the standalone or module instructions below.
This SDK will be installed into a directory called s3sdk
and can be instantiated directly via new s3sdk.models.AmazonS3():
s3 = new s3sdk.models.AmazonS3(
accessKey = "your-access-key",
secretKey = "your-secret-key",
awsRegion = "us-east-1"
);
// Create a bucket
s3.createBucket( bucketName = "my-bucket" );
// Upload a file
s3.putObjectFile(
bucketName = "my-bucket",
filepath = "/path/to/file.pdf",
uri = "documents/file.pdf"
);
// Generate a pre-signed download URL valid for 5 minutes
url = s3.getAuthenticatedURL(
bucketName = "my-bucket",
uri = "documents/file.pdf",
minutesValid = 5
);
// Delete an object
s3.deleteObject( bucketName = "my-bucket", uri = "documents/file.pdf" );
Full constructor reference:
/**
* Create a new S3SDK Instance
*
* @accessKey The Amazon access key.
* @secretKey The Amazon secret key.
* @awsDomain The Domain used S3 Service (amazonws.com, digitalocean.com, storage.googleapis.com). Defaults to amazonws.com
* @awsRegion The Amazon region. Defaults to us-east-1 for amazonaws.com
* @encryptionCharset The charset for the encryption. Defaults to UTF-8.
* @signature The signature version to calculate, "V2" is deprecated but more compatible with other endpoints. "V4" requires Sv4Util.cfc & ESAPI on Lucee. Defaults to V4
* @ssl True if the request should use SSL. Defaults to true.
* @defaultTimeOut Default HTTP timeout for all requests. Defaults to 300.
* @defaultDelimiter Delimter to use for getBucket calls. "/" is standard to treat keys as file paths
* @defaultBucketName Bucket name to use by default
* @defaultCacheControl Default caching policy for objects. Defaults to: no-store, no-cache, must-revalidate
* @defaultStorageClass Default storage class for objects that affects cost, access speed and durability. Defaults to STANDARD.
* @defaultACL Default access control policy for objects and buckets. Defaults to public-read.
* @autoContentType Tries to determine content type of file by file extension. Defaults to false.
* @autoMD5 Calculates MD5 hash of content automatically. Defaults to false.
* @debug Used to turn debugging on or off outside of logbox. Defaults to false.
* @defaultEncryptionAlgorithm The default server side encryption algorithm to use. Usually "AES256". Not needed if using custom defaultEncryptionKey
* @defaultEncryptionKey The default base64 encoded AES 356 bit key for server side encryption.
* @urlStyle Specifies the format of the URL whether it is the `path` format or `virtual` format. Defaults to path. For more information see https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html
*
* @return An AmazonS3 instance.
*/
public AmazonS3 function init(
required string accessKey,
required string secretKey,
string awsDomain = "amazonaws.com",
string awsRegion = "us-east-1",
string encryptionCharset = "UTF-8",
string signature = "V4",
boolean ssl = true,
string defaultTimeOut= 300,
string defaultDelimiter='/',
string defaultBucketName='',
string defaultCacheControl= "no-store, no-cache, must-revalidate",
string defaultStorageClass= "STANDARD",
string defaultACL= "public-read",
boolean autoContentType= false,
boolean autoMD5= false,
boolean debug= false,
string defaultEncryptionAlgorithm = "",
string defaultEncryptionKey = "",
string urlStyle = "path"
)
This package is also a ColdBox module. Configure it by creating an
s3sdk configuration structure in your
moduleSettings struct in config/Coldbox.cfc:
moduleSettings = {
s3sdk = {
// Your amazon, digital ocean access key
accessKey = "",
// Tries to determine content type of file by file extension when putting files. Defaults to false.
autoContentType = false,
// Calculates MD5 hash of content automatically. Defaults to false.
autoMD5 = false,
// Your AWS/Digital Ocean Domain Mapping: defaults to amazonaws.com
awsDomain = "amazonaws.com",
// Your AWS/Digital Ocean Region: Defaults to us-east-1
awsregion = "us-east-1",
// Used to turn debugging on or off outside of logbox. Defaults to false.
debug = false,
// Default access control policy for objects and buckets. Defaults to public-read.
defaultACL = "public-read",
// The default bucket name to root the operations on.
defaultBucketName = "",
// Default caching policy for objects. Defaults to: no-store, no-cache, must-revalidate
defaultCacheControl = "no-store, no-cache, must-revalidate",
// The default delimiter for folder operations
defaultDelimiter = "/",
// Default storage class for objects that affects cost, access speed and durability. Defaults to STANDARD.
// AWS classes are: STANDARD,STANDARD_IA,INTELLIGENT_TIERING,ONEZONE_IA,GLACIER,DEEP_ARCHIVE
// Google Cloud Storage Clases: regional,multi_regional,nearline,coldline,
defaultStorageClass = "STANDARD",
// Default HTTP timeout in seconds for all requests. Defaults to 300 seconds.
defaultTimeOut = 300,
// The default encryption character set: defaults to utf-8
encryptionCharset = "utf-8",
// How many times to retry the request before failing if the response is a 500 or 503
retriesOnError : 3,
// Your amazon, digital ocean secret key
secretKey = "",
// Service name that is part of the service's endpoint (alphanumeric). Example: "s3"
// Only used for the v4 signatures
serviceName : "s3",
// The signature version to calculate, "V2" is deprecated but more compatible with other endpoints. "V4" requires Sv4Util.cfc & ESAPI on Lucee. Defaults to V4
signature = "V4",
// SSL mode or not on cfhttp calls and when generating put/get authenticated URLs: Defaults to true
ssl = true,
// Throw exceptions when s3 requests fail, else it swallows them up.
throwOnRequestError : true,
// What format of endpoint to use whether path or virtual
urlStyle = "path"
}
};
Then leverage the SDK via the WireBox injection DSL: AmazonS3@s3sdk
component {
property name="s3" inject="AmazonS3@s3sdk";
function index( event, rc, prc ){
s3.putObjectFile(
bucketName = "my-bucket",
filepath = "/path/to/file.pdf"
);
}
}
Since this SDK speaks the standard S3 REST API, it works out of the
box with any S3-compatible storage provider by simply changing the
awsDomain setting:
| Provider |
awsDomain
|
|---|---|
| Amazon S3 | amazonaws.com (default) |
| DigitalOcean Spaces | digitaloceanspaces.com
|
| Google Cloud Storage | storage.googleapis.com
|
| MinIO / self-hosted | your MinIO endpoint hostname |
Please check out the full API docs: https://apidocs.ortussolutions.com/#/coldbox-modules/s3sdk/, choose your version and code away!
This module ships with a test-harness and can be tested
against any of the supported engines using CommandBox:
box install
box server start serverConfigFile="[email protected]"
box testbox run
See .github/workflows/tests.yml for the full CI matrix,
which runs against native BoxLang, BoxLang with CFML compatibility,
Lucee and Adobe ColdFusion.
See Contributing and AGENTS.md for guidance on developing and testing this module, including for AI coding agents.
Pull requests are welcome! Please make sure any changes pass
box run-script format:check and the full test suite
before submitting.
© Ortus Solutions, Corp
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.
boxlang@1) server and CI matrix entry, in addition to the existing boxlang-cfml@1 (CFML compatibility) entryboxlang@1, boxlang-cfml@1, lucee@6 and adobe@2023/adobe@2025. Dropped lucee@5 and adobe@2018/adobe@2021 (EOL)^8devDependencies : removed commandbox-dotenv and commandbox-cfconfig, added commandbox-boxlangreadme.md rewritten and expanded, with BoxLang as the preferred/first-class enginetest-harness/box.json : testbox devDependency bumped from be to * to pick up TestBox's isBoxLang()/isLucee()/isAdobe() engine-detection helpers (and the 7.1.0 fix for isLucee() incorrectly returning true on BoxLang)test-harness/tests/specs/AmazonS3Spec.cfc : replaced ad-hoc engine checks (structKeyExists( server, "lucee" ), isNull( server.lucee ), server.keyExists( "boxlang" )) with TestBox's isAdobe()/isLucee()/isBoxLang()AmazonS3Spec.cfc now exercise SSE-S3 (encryptionAlgorithm) instead of SSE-C (encryptionKey), since the CI test bucket's policy blocks SSE-C uploads. The SDK's SSE-C support itself (encryptionKey argument) is unchanged for callers whose bucket allows ithash usage algorithms to MD5 for Adobe change to default algorithmSv4Util.cfc, Sv2Util.cfc : optional amzDate/dateStamp override arguments were checked with structKeyExists( arguments, ... ), which is unreliable on engines with full-null support like BoxLang (an unpassed argument still exists as a null key). Now checked independently with isNull(), fixing spurious SignatureDoesNotMatch errors on BoxLangMiniLogBox.cfc : same isNull() fix applied to the optional data argument on debug(), error() and warn()Sv4Util.cfc, Sv2Util.cfc : the UTC date stamp was generated with dateFormat( utcDateTime, "yyyymmdd" ). Lowercase mm is minutes, not month, on some engines. Corrected to yyyyMMddcopyObject() ( and therefore renameObject(), which calls it internally ) manually set a Content-Length: 0 header that duplicated the header CFHTTP already sends for a bodyless request. Adobe CF and BoxLang do not de-duplicate this, sending content-length: 0,0 on the wire and breaking AWS's SignatureDoesNotMatch validation. Removed the redundant header[email protected] had leftover module aliases (/moduleroot/cbfs) copied from another module; corrected to /moduleroot/s3sdktest-harness/tests/specs/AmazonS3Spec.cfc : isOldACF() unconditionally read server.coldfusion.productVersion, which doesn't exist on native BoxLang, crashing the whole test bundle on boxlang@1. Now guarded with structKeyExists( server, "coldfusion" )commandbox-cfconfig before starting servers, since the version bundled with the CommandBox CLI has no config provider for adobe@2025 yetSv4UtilSpec.cfc test fixture helpers used .listToArray() member-function syntax, which Adobe ColdFusion doesn't resolve the same way Lucee/BoxLang do ("The listToArray method was not found"). Switched to the top-level listToArray( string, delimiter ) function call, which is portable across all three enginesputObjectFile()'s multi-part upload path called java.nio.file.Files.newByteChannel( path, [] ) with an untyped, empty CFML array for the varargs OpenOption... parameter. Explicitly javacast( "java.nio.file.OpenOption[]", [] ) now, for safer Java interop (this alone did not fix the underlying multi-part failure on Adobe; see below)Setup Java was pinned to Java 11, but Adobe ColdFusion 2025's cfpm tooling requires Java 17+ (UnsupportedClassVersionError: ... class file version 61.0 ... only recognizes ... up to 55.0). Bumped to Temurin 17[email protected] (native BoxLang) didn't install the bx-esapi module, so any call to encodeForURL() (used by Sv4Util.cfc's urlEncodePath()) failed with Function [encodeForURL] not found, crashing the entire AmazonS3Spec bundle at beforeAll(). Added onServerInitialInstall: install bx-esapi, matching [email protected]requireBucketName(), getBucketLocation(), createBucket(), objectExists(), getAuthenticatedURL() and applyACLHeaders() called throw() without an explicit type. Adobe/Lucee default the type to Application, but BoxLang defaults it to Custom, breaking tests asserting toThrow( type = "application" ). All now throw an explicit type = "Application"[email protected]/[email protected] : pinned the server's own JVM to javaVersion: openjdk21_jre, matching the BoxLang server configs, for consistent Java 21 runtime behavior across enginesSv4UtilSpec.cfc test fixture helpers named a parameter file, which is treated specially on Adobe ColdFusion ("Complex object types cannot be converted to simple values" when passed into listToArray()). Renamed to requestContent[email protected]/[email protected] : added JVM arg --add-opens java.base/sun.nio.fs=ALL-UNNAMED, matching the working config in coldbox-modules/cbfs, for safer Java NIO reflection on Java 17+Ortus-Solutions/setup-commandbox action plus manual box install --force commandbox-boxlang/commandbox-cfconfig steps with ortus-boxlang/setup-boxlang@main (with-commandbox: true, installing commandbox-boxlang, commandbox-cfconfig and testbox-cli), matching coldbox-modules/cbfs's setup. This also fixed adobe@2025's server failing to start. Also bumped Setup Java from 17 to 21, matching cbfs and the server JVM pinsputObjectFile()'s multi-part upload path optionally routed concurrent part uploads through variables.asyncManager.allApply(). On Adobe, ColdBox's async cbproxies Function wrapper does not correctly marshal the part struct argument across the async boundary, throwing coldfusion.runtime.UndefinedElementException: Element UPLOADID is undefined in PART inside the closure. This was silently caught by the surrounding try/catch and fell back to a non-multipart upload, with no visible error ("can perform a multi-part upload on a file over 5MB" failing only with the response not containing "multipart"). Always use the synchronous part-upload path now, until the ColdBox/Adobe async interop issue is resolved upstreamAmazonS3Spec.cfc's multi-part upload test pre-computed the expected uploaded file size before calling fileWrite(), then asserted the S3 object's Content-Length against that pre-computed value. On adobe@2025 the file written to disk was 1 byte larger than expected, failing the assertion. Now reads the actual on-disk size via getFileInfo() after writing, so the assertion is correct regardless of any engine-specific fileWrite() behaviorentryPoint, modelNamespace and cfmapping keys to ModuleConfig, to ensure mappings for downstream modules are available during framework loadencryption_charset changed to encryptionCharset for consistency. Breaking changePUT signed URLs so you don't have to upload to a middle server and then S3. You can now create a signed PUT operation that you can upload directly to S3.retriesOnError and it defaults to 3.objectExists()putObject() was incorrect "arguments.content" instead of "arguments.data", this only happens when md5 == "auto" so it probably slipped by for some time.downloadObject( getAsBinary : 'no' ) so you can get binary or non binary objects. Defaults to non binary..cfformat.jsonsetAccessControlPolicy() so you can add ACLs to bucketsgetBucket() has been updated to use the ListObjectsv2 API - which is recommended by AWS for more detailed information.gitattributes for cross OS compatibilitiesmarkdownlint.json for more control over markdownformat:watch to format and watch :)Feature : SV4Util is now a singleton for added performance and more configuration expansion by adding the sdk referenceImprovement : Better error messages when s3 goes 💥 Bug : Fix for ACF double encodingcopy, putObjectFile, and delete() operationsvariables instead of argumentsdefaultDelimiter for folder operations, defaultBucketname so you can set a default bucket for all bucket related operations.objectExists() boolean check for objectsawsRegion argument to the constructor to select the AWS or DO regionawsRegion and awsDomain to support regions and multi-domains for AWS and Digital Oceandebug levels3sdk top level settings in ColdBox Config to moduleSettings.s3sdkdeleteBucket() returns false if bucket doesn't exist instead of throwing an exception
$
box install s3sdk