Key takeaways
- Use an Assembly to receive an upload, validate it, create fixed outputs, and export the selected originals or derivatives to S3.
- Use separate upload-only, image, and video Templates so each accepted media type has an explicit processing and failure contract.
- Use Smart CDN to import an existing S3 object through a saved Template, transform it on a cache miss, and cache the served result.
A request for “file uploads, image optimization, video encoding, and our own S3 bucket” hides several contracts. An upload may be stored unchanged, an image may be optimized before export, or a video may be encoded into a tested rendition. Some image sizes or formats are cheaper to create only when a browser asks for them. Transloadit supports each path, but they have different storage, latency, cache, and security boundaries.
What matters most
- Keep AWS credentials in scoped Template Credentials and generate upload or Smart CDN authorization on a trusted server.
- Treat S3 as the durable system of record, not as proof that file bytes never pass through Transloadit’s processing and cache infrastructure.
- Preprocess outputs that must exist before publication; use bounded on-demand variants when derivative demand is unpredictable.
- Store originals, fixed derivatives, or both according to the application’s recovery and migration requirements.
Separate upload-time work from request-time work
Begin with two paths rather than one vague “optimization pipeline.” In the upload-time path, a browser sends a file through Uppy and tus to a Transloadit Assembly. The saved Template validates the observed file, creates any outputs that must exist immediately, and exports selected files to S3. The application stores the Assembly ID and durable S3 object identity with its asset record.
In the request-time path, a browser requests a Smart CDN URL. The URL identifies a saved Template and an input path in S3. On a cache miss, Transloadit imports that object, applies the Template with the permitted Assembly Variables, and serves one result through /file/serve. The delivery layer caches the response; a warm request can reuse the derivative without running the transformation again.
Upload-time path
Browser → Uppy/tus → Assembly → validation and fixed transformations → customer S3.
Request-time path
Browser → Smart CDN URL → cache miss → S3 import and transformation → edge cache → browser.
Ownership boundary
Customer S3 holds the application’s durable objects; temporary processing files and cached derivatives have separate retention contracts.
Store an uploaded file in S3 without transforming it
Use an upload-only Template when Transloadit should receive and export the accepted file without changing its media bytes. /upload/handle receives the input, and /s3/store selects :original. The destination path uses Assembly Variables so concurrent uploads do not overwrite one another while the original URL-safe filename remains recognizable.
The sample references Template Credentials and sets acl: "private" because /s3/store defaults to public-read. Set bucket_region to the bucket’s actual AWS region to avoid a GetBucketLocation lookup and remove s3:GetBucketLocation from the credentials’ IAM policy. Treat a public object as an explicit product decision, and confirm effective S3 access separately from the Robot setting.
{
"allow_steps_override": false,
"steps": {
":original": { "robot": "/upload/handle" },
"stored": {
"use": ":original",
"robot": "/s3/store",
"credentials": "my_s3_credentials",
"bucket_region": "us-east-1",
"acl": "private",
"path": "uploads/${unique_prefix}/${file.url_name}"
}
}
}Receive, validate, optimize, and export the upload
Use a saved image Template to constrain what browser code may request. /upload/handle receives the file, /file/filter can reject files whose detected properties do not satisfy policy, /image/resize creates a fixed WebP derivative, and /image/optimize reduces that output. /s3/store then exports exactly the Steps named by its use value. Point it at only optimized to store the derivative, only accepted_images to store the accepted original, or both to retain both objects. preserve_meta_data is left at its default true deliberately to retain image metadata such as photographer copyright information, accepting a small size increase even under the compression-ratio priority.
Uppy is the browser upload layer, not the authorization boundary. Let the Transloadit plugin request short-lived signed Assembly parameters from the application server. Keep allow_steps_override disabled when the browser must not replace the saved Steps or select another destination. Reuse the private ACL and actual AWS bucket region established in the upload-only sample. Both ${file.id} and ${unique_prefix} are unique per exported file; this image sample uses the slash-free ${file.id} so the middle path segment stays a single component, giving predictable images/${file.id}/${file.url_name} keys that Smart CDN can reference, while the upload and video samples use ${unique_prefix} as a collision-avoiding directory. Because ${file.id} is unique for each exported file, accepted_images and optimized resolve to different keys under images/; the shared path template does not overwrite, so record each exported key separately in application state. Treat the upload as complete for the product only after the application has reconciled the Assembly result and durable S3 keys.
{
"allow_steps_override": false,
"steps": {
":original": { "robot": "/upload/handle" },
"accepted_images": {
"use": ":original",
"robot": "/file/filter",
"accepts": [["${file.mime}", "regex", "^(image/jpeg|image/png|image/gif|image/webp|image/avif)$"]],
"error_on_decline": true
},
"resized": {
"use": "accepted_images",
"robot": "/image/resize",
"resize_strategy": "fit",
"width": 1600,
"height": 1600,
"format": "webp"
},
"optimized": {
"use": "resized",
"robot": "/image/optimize",
"priority": "compression-ratio",
"preserve_meta_data": true
},
"stored": {
"use": ["accepted_images", "optimized"],
"robot": "/s3/store",
"credentials": "my_s3_credentials",
"bucket_region": "us-east-1",
"acl": "private",
"path": "images/${file.id}/${file.url_name}"
}
}
}Encode a video before storing it in S3
Use a video-only Template when the durable S3 object should be a playback rendition rather than the uploaded source. /video/encode reads :original and applies a tested preset. web/mp4/720p is a concrete 1280×720 MP4 starting point, not a universal recommendation; validate source dimensions, frame rate, audio, captions, quality, device support, processing time, and cost for the product’s playback contract.
The sample exports the encoded Step and leaves the source out of that store operation. Add a second /s3/store Step when the source must remain available for higher-quality re-encoding, audit, or migration. Encoding can outlive an application request, so reconcile the Assembly Status or a verified completion callback before treating the S3 key as publishable.
{
"allow_steps_override": false,
"steps": {
":original": { "robot": "/upload/handle" },
"encoded": {
"use": ":original",
"robot": "/video/encode",
"preset": "web/mp4/720p"
},
"stored": {
"use": "encoded",
"robot": "/s3/store",
"credentials": "my_s3_credentials",
"bucket_region": "us-east-1",
"acl": "private",
"path": "videos/${unique_prefix}/${file.url_name}"
}
}
}Transform an S3 original on demand through Smart CDN
A Smart CDN integration still starts with a saved Template. /s3/import resolves the input path using scoped Template Credentials, /image/resize reads the URL-supplied ${fields.w} value and the request-negotiated ${browser.wanted_image_format} value, and /file/serve selects the response. On this path, Transloadit’s Smart CDN edge derives a trusted, pre-normalized x-tl-image-format header from the client’s Accept header. The header is written server-side and is not client-controllable; developers reference only ${browser.wanted_image_format} in Instructions, which resolves to the same avif, webp, or jpg value.
A URL such as https://my-workspace.tlcdn.com/responsive-image/images/a8d3eeeb67479f11f8b091b04f6181ad/canoe.jpg?w=640 supplies images/a8d3eeeb67479f11f8b091b04f6181ad/canoe.jpg as the implicit ${fields.input} — the path after the Template name, with no leading slash — and 640 as ${fields.w}. That input is the recorded images/${file.id}/${file.url_name} key for the durable accepted_images original, which /s3/import reads for on-demand resizing. The optimized derivative is a separate object under its own ${file.id} prefix. Because the two outputs do not share a directory, record each exported key in application state rather than deriving one key from the other or from the request URL.
Keep the full images/ prefix in the Smart CDN URL; do not add it again in the Template. Smart CDN query values arrive as strings, so the width allowlist compares against the string literals "320" and "640"; an unmatched width falls back to 1280. Keeping the path mapping and transformation allowlist separate makes it easier to audit which S3 object can be read and which rendition can be requested.
Do not interpolate an unrestricted path, width, quality, or format merely because it can arrive as a field. Restrict the S3 credential to an intended prefix, have the trusted application look up the exported key in its asset record, and sign a URL containing that exact key. The request-time path selects this saved Template through its Smart CDN URL instead of accepting browser-submitted Assembly Instructions, so this sample does not use the upload-time allow_steps_override setting. Use the actual AWS bucket region, as in the upload-only sample. Validate or map transformation values in the Template. Bare lookups such as ${fields.input} are free, while comparisons, arithmetic, and ternaries are complex Dynamic Evaluation: the width and format ternaries in this sample invoke /script/run and are billed on each cache miss. The format expression maps the jpg fallback to null, so requests without a modern-format preference keep the original format instead of flattening transparency or animation through needless re-encoding. See the ${browser.wanted_image_format} entry in the Assembly Variables reference. Measure the first uncached transformation separately from subsequent cached delivery.
{
"steps": {
"imported": {
"robot": "/s3/import",
"credentials": "my_s3_credentials",
"bucket_region": "us-east-1",
"path": "${fields.input}"
},
"resized": {
"use": "imported",
"robot": "/image/resize",
"resize_strategy": "fit",
"width": "${fields.w === '320' ? 320 : fields.w === '640' ? 640 : 1280}",
"format": "${browser.wanted_image_format === 'jpg' ? null : browser.wanted_image_format}"
},
"served": {
"use": "resized",
"robot": "/file/serve",
"cache_duration": 604800
}
}
}Decide what to store permanently versus generate on demand
Keeping an original in S3 provides a stable source for reprocessing, but it does not require storing every responsive rendition. Export canonical outputs that the product needs independently of a cache: an approved master, marketplace listing image, print asset, or immutable release derivative. Let Smart CDN create bounded presentation variants whose dimensions depend on the requesting device or layout.
Conversely, do not rely only on an on-demand path when the first request cannot tolerate processing latency, an editor must approve the exact pixels, or downstream systems require a durable object before publication. In that case, create and export the derivative in the upload-time Assembly. The same application can use both approaches for different output classes without changing the durable owner of the source.
Durable originals
Keep sources required for future transformations, recovery, audit, or migration.
Fixed derivatives
Store outputs that must be reviewed, referenced by other systems, or available without a cold transform.
On-demand derivatives
Cache safe presentation variants whose combinations are bounded but difficult to predict before a user requests them.
Protect the application, bucket, and transformation surface
Create separate least-privilege Template Credentials when upload exports and Smart CDN imports need different AWS actions or prefixes. Keep raw AWS keys, the Transloadit Auth Secret, and unrestricted Instructions out of browser bundles. The application server should authorize the user, select the Template, and issue only the short-lived upload parameters or signed Smart CDN URL appropriate for that asset.
A successful upload or valid signature is not publication approval. Validate detected MIME type and size in the Assembly, associate callbacks idempotently with the expected tenant and Assembly, and expose a result only after the durable object and application record agree. For Smart CDN, design source versioning, URL expiry, cache lifetime, and deletion together so replacing an S3 key cannot leave an unintended derivative address active.
Compare against a native AWS pipeline
A native design can upload through an S3 presigned URL, react to object-created events, process with Lambda or another compute service, store derivatives, and deliver them through a CDN. That can be a strong fit when the workload stays inside supported runtime limits and the team wants to operate authorization, retries, queues, codecs, concurrency, observability, and failure recovery itself.
Compare complete production paths rather than one successful resize. Test interrupted uploads, large sources, malformed images, orientation and color, duplicate events, partial exports, concurrency spikes, cold transforms, cache invalidation, regional latency, and deletion. Include engineering and operations time alongside upload, processing, storage, request, and egress charges. The meaningful choice is which operational responsibilities the team wants to own.
Technical details worth knowing
- Uppy’s Transloadit plugin creates an Assembly and uploads files to its tus endpoint, while application code can request signed Assembly parameters from a trusted back end.
- An Assembly Template can connect
/upload/handle, validation or transformation Steps, and/s3/store; theuserelationships determine whether the original, derivatives, or both are exported. /image/optimizecan reduce supported image files before/s3/store, while unsupported image types pass through unchanged./video/encodeaccepts presets such asweb/mp4/720p; the encoded Step can be exported to S3 independently of the uploaded source.- Template Credentials store AWS access separately from Assembly Instructions and are referenced by name from saved Templates.
- Providing
bucket_regionavoids theGetBucketLocationlookup and lets the Template Credentials IAM policy omits3:GetBucketLocation. - A Smart CDN URL identifies a workspace, Template, input path, and optional URL fields. The input path is available to the Template as
${fields.input}. On a cache miss, the Template runs and/file/servesupplies the response that the delivery layer caches. - A Smart CDN Template can use
/s3/importto read an object from customer-owned S3,/image/resizeto transform it, and/file/serveto return the selected derivative. - For Smart CDN requests, URL query parameters populate
${fields.*}, while the path after the Template name becomes the implicit${fields.input}value. This differs from upload-time form fields and the Assemblyfieldskey. The Template decides which values it reads, but each still needs validation, mapping, or authorization through a signed URL. - Transloadit temporary result storage is not permanent application storage. Results are retained for at least 24 hours regardless of settings; current R2 storage does not support purging sooner. Production workflows should export every object that must persist.
- A cached Smart CDN derivative is separate from the durable original in S3. Source replacement, URL versioning, signature expiry, and cache lifetime must be designed together.
A practical approach
- 1
Map the upload, processing, storage, and delivery paths, including who owns every durable object and public URL.
- 2
Create least-privilege Template Credentials and saved Templates for upload-time and on-demand work.
- 3
Test fixed exports, cold Smart CDN misses, warm cache hits, invalid parameters, replaced sources, and unavailable origins.
- 4
Record Assembly IDs and stable S3 object versions in the application, then monitor processing, export, cache, and delivery costs separately.
When Transloadit is useful
Use Transloadit when one product needs Uppy and tus uploads, asynchronous image or video workflows, exports to its own S3 bucket, and optional URL-driven image variants through Smart CDN. Use only the parts the application needs: an Assembly can preserve an upload, preprocess and export fixed media assets, while a Smart CDN Template can import an image original from S3 and create a bounded derivative—restricted to an explicit allowlist of permitted values—on demand.
Architecture boundary
Your application owns user authorization, asset records, publication policy, and the durable copies in S3. Transloadit receives or imports files, temporarily holds data while processing, executes the saved workflow, exports selected results, and may cache Smart CDN derivatives. Customer-owned storage therefore does not mean that bytes remain exclusively inside the customer’s AWS account.
Frequently asked questions
Does using my own S3 bucket keep every byte inside my AWS account?
No. S3 can remain the durable system of record, but uploads, imported originals, temporary results, and Smart CDN derivatives pass through Transloadit infrastructure according to the configured workflow. Temporary results are retained for at least 24 hours regardless of settings, and current R2 storage does not support purging them sooner. Assembly Status JSON retention is configured separately, with options from No Save through 90 days; 90 days is the default. Retaining the Status JSON for 90 days does not make the temporary result files durable storage. Smart CDN caches served results separately.
Should I store the original media, processed derivatives, or both?
Export the original when it is needed for reprocessing, audit, or provider migration. Export fixed derivatives when they must exist before publication or be reviewed. You can export both by giving /s3/store both Steps as its use input.
Can one S3 Template handle images and videos?
Use separate saved Templates when images and videos have different validation, timeout, publication, or failure policies. A deliberately mixed-media Template can branch with /file/filter, but each branch should define what happens when it declines a file and which outputs /s3/store exports.
Is Smart CDN a separate processing system from Assemblies?
No. An upload-time Assembly and a Smart CDN request use the same Template-and-Robot execution model, even though each path defines its own Template. The upload path runs when bytes arrive and can export durable results. The Smart CDN path runs its Template on a cache miss and serves one selected result through /file/serve.
When should I preprocess instead of transforming on demand?
Preprocess assets that need approval, deterministic availability, several durable outputs, or predictable first-view latency. Transform on demand when requested sizes are difficult to predict and a bounded set of URL variables can express the safe variants. Many applications preprocess a canonical image and create presentation sizes on demand.
How should Transloadit receive access to a private S3 bucket?
Store the AWS credentials as least-privilege Template Credentials, reference their name from a saved Template, and prevent browsers from supplying arbitrary Assembly Instructions or storage destinations. Sign upload parameters and protected Smart CDN URLs on a trusted server.
Do I have to use Transloadit to use Uppy?
No. Uppy is open-source upload software and can use many back ends. The maintained Transloadit plugin is the direct integration when uploads should create an Assembly, use tus transfer, and report processing progress or results.