Workflow automation

# Customizable media processing workflows with Transloadit

Design a reusable Template with validation, variables, parallel derivatives, secure storage, and observable completion.

Published August 26, 2026

## Key takeaways

* Model the workflow as a graph whose `use` relationships make ordering explicit.
* Keep stable processing policy in a saved Template and expose only validated fields.
* Run independent derivatives in parallel. Per-file Steps process every file emitted by the upstream Steps they read; only merge or bundling Steps wait for a complete set.

A customizable workflow should expose the few values that legitimately change per job while keeping its processing graph controlled. Transloadit Assembly Instructions express that graph as named Steps, and a saved Template lets an application run it repeatedly without resending storage credentials or transformation policy.

## In this guide

1. [Draw dependencies before writing JSON](#customizable-media-processing-workflows-section-1)
2. [Separate fixed policy from runtime fields](#customizable-media-processing-workflows-section-2)
3. [Validate inputs before expensive processing](#customizable-media-processing-workflows-section-3)
4. [Export with credentials and paths you can rotate](#customizable-media-processing-workflows-section-4)
5. [Operate every run as an asynchronous state machine](#customizable-media-processing-workflows-section-5)
6. [Test changes with representative fixtures](#customizable-media-processing-workflows-section-6)

## What matters most

* Reference stored Template Credentials instead of putting cloud secrets in Assembly Instructions.
* Record the Template ID, application workflow label, Assembly ID, inputs, and terminal result for every run.

## Draw dependencies before writing JSON

Start with files and decisions, not Robot names. Identify the files and derivatives the workflow produces, the validation policies it enforces, the destinations it writes to, and the failure cases the product must handle, such as rejected input or an export failure. Then give each operation a Step name that describes its outcome. In Assembly Instructions, the `use` value creates the edge between an upstream Step and its consumer; the order of keys in the JSON object does not create an execution sequence.

Independent Steps can read the same upstream file and run concurrently. Two resize Steps that both draw from one shared filter Step do not wait for each other; each begins when the filter emits a file. The concrete JSON for this shape appears in the next section. Each derivative is exported to its own prefix, so the parallel renditions never share a key. A merge Robot is different: it may need a bundled set of named inputs before it can produce anything. Model that dependency explicitly instead of relying on apparent JSON order.

### Nodes are Steps

Each named Step invokes one Robot with a bounded set of parameters.

### Edges come from use

The declared upstream input determines readiness and data flow.

### Branches can overlap

Derivatives that share an input can run independently instead of becoming a serial chain.

## Separate fixed policy from runtime fields

Keep validation, allowed Robots, output roles, and destinations in a saved Template. Values that truly vary per request can arrive as fields and be referenced through `${fields.*}` variables. A tenant ID, requested rendition profile, or stable asset ID may be reasonable; an arbitrary Robot name, destination credential, or unrestricted output dimension usually is not.

Set `allow_steps_override` to false in the Template when an untrusted caller must not merge new Steps into the saved graph. That setting protects the graph, not the meaning of every field. Validate fields in the application before creating the Assembly: authorize the tenant, accept only known profiles, clamp numeric ranges, and reject unknown keys. The Template should also use safe defaults or filters where a bad value could create excessive work. The complete Template below includes input validation, export, and notification parameters that the following sections explain.

A locked, parameterized image workflow

```
{
  "allow_steps_override": false,
  "max_number_of_files": 1,
  "max_size": 52428800,
  "notify_url": "https://app.example.com/webhooks/transloadit",
  "steps": {
    ":original": {
      "robot": "/upload/handle"
    },
    "accepted_images": {
      "use": ":original",
      "robot": "/file/filter",
      "accepts": [
        ["${file.mime}", "regex", "^(image/jpeg|image/png|image/webp|image/avif)$"]
      ],
      "error_on_decline": true,
      "error_msg": "Upload a JPEG, PNG, WebP, or AVIF image."
    },
    "web_image": {
      "use": "accepted_images",
      "robot": "/image/resize",
      "width": 1600,
      "height": 1200,
      "resize_strategy": "fit",
      "format": "webp"
    },
    "thumbnail": {
      "use": "accepted_images",
      "robot": "/image/resize",
      "width": 320,
      "height": 320,
      "resize_strategy": "fillcrop",
      "format": "webp"
    },
    "export_web": {
      "use": "web_image",
      "robot": "/s3/store",
      "acl": "private",
      "credentials": "media-output",
      "path": "${fields.tenant_id}/${assembly.id}/web/${unique_prefix}/${file.url_name}"
    },
    "export_thumb": {
      "use": "thumbnail",
      "robot": "/s3/store",
      "acl": "private",
      "credentials": "media-output",
      "path": "${fields.tenant_id}/${assembly.id}/thumb/${unique_prefix}/${file.url_name}"
    }
  }
}
```

Run the saved Template with validated fields

```
// Illustrative fragment: these objects come from your application, not the SDK.
const assembly = await transloadit.createAssembly({
  files: { image: inputPath },
  params: {
    template_id: process.env.TRANSLOADIT_TEMPLATE_ID,
    fields: {
      tenant_id: tenant.id,
    },
  },
})

await jobs.attachAssembly({
  jobId: job.id,
  assemblyId: assembly.assembly_id,
})
```

### Stable policy

Robot choices, validation, export destinations, and result roles belong in controlled configuration.

### Bounded variation

Fields expose a small contract rather than making the entire graph caller-controlled.

### Two validation layers

Application authorization and Template safeguards address different failure and abuse paths.

## Validate inputs before expensive processing

Put cheap, deterministic checks before derivative generation. Assembly- and Template-level `max_size` caps the combined size of the whole upload—the entire upload is canceled if the total exceeds it, even when every individual file is under it—and `max_number_of_files` caps how many files the request may contain. For a per-file size limit, use `/file/filter` on `${file.size}`, which evaluates each file individually and can also inspect server-detected MIME type and extracted metadata. When unsupported input should fail the whole job, set `error_on_decline` and provide a message that tells the user what to change.

The sample deliberately caps the walkthrough at one uploaded file. Raising `max_number_of_files` makes multi-file decline behavior relevant. Prefer an explicit allowlist such as JPEG, PNG, WebP, and AVIF when the downstream operation only supports browser images. A broad `image/*` rule accepts formats the destination may not render, while a filename extension and browser-reported MIME value are only client claims. Decide separately whether a rejected file should fail an entire multi-file Assembly, disappear from one branch, or enter another branch; those are product behaviors, not incidental filter settings.

### Cheap checks first

Reject unsuitable inputs before paid or slow transformations begin.

### Server-detected properties

Use extracted MIME and metadata rather than trusting extensions alone.

### Explicit rejection behavior

Choose whether one declined file ends the job or simply stops flowing through a branch.

## Export with credentials and paths you can rotate

Create Template Credentials for the storage destination and reference their name from the export Robot. The Assembly Instructions then contain a stable credential label rather than an access key and secret. Rotating the stored credential updates future runs without copying a new secret into source code, browser parameters, or every Template that uses it. `/s3/store` defaults `acl` to `public-read`, so set it to `private` unless the exported files are meant to be publicly readable.

Build destination paths from validated tenant or asset identifiers, the Assembly ID, the rendition role, and Transloadit’s platform-generated `${unique_prefix}`. This unique 33-character per-file prefix contains a forward slash, so it expands to a two-level subdirectory in the storage key and prevents same-named inputs within one Assembly from colliding. Give every parallel derivative a distinct rendition-role path segment, such as `web/` or `thumb/`, so their exports never collide on the same key. Do not use an unsanitized upload filename as the only key, and decide what a retry should do if the object already exists. An idempotent export either writes the same intended object or checks and reconciles the destination before creating another. Record the final storage key for every result so deletion and replacement can find all copies later.

### Credential label

Separates secret rotation from the workflow JSON that refers to it.

### Stable path inputs

Tenant, asset, Assembly, and rendition identifiers make outputs traceable.

### Overwrite contract

Define whether an existing destination key is replaced, rejected, versioned, or reconciled.

## Operate every run as an asynchronous state machine

Store an application job record before starting the Assembly. Include the actor, tenant, input file identifier, Template ID, validated fields, application workflow label, and a stable operation key. The application workflow label and operation key are local-only identifiers stored in your own database; they are never sent to Transloadit. Add the Assembly ID as soon as it is returned. This record lets retries ask whether equivalent work is already active or complete instead of creating a second export after a timeout.

Configure `notify_url` when the job should finish in the background; as shown in the Template above, set it in the stored Template so each run inherits it. Verify the notification signature with the Auth Secret belonging to the Auth Key used for that Assembly, return HTTP 200 promptly for a valid delivery, and process duplicates idempotently. A periodic reconciliation job should compare locally active work with Assembly Status so a lost callback cannot strand a record. Monitor latency, failure class, bytes processed, outputs, and webhook retries by the application workflow label.

### Operation key

Prevents a caller retry from silently creating duplicate processing and exports.

### Verified webhook

Authenticates completion data while allowing the original request to end quickly.

### Reconciliation

Repairs local state when notifications are delayed, duplicated, or missed.

## Test changes with representative fixtures

A workflow is only as stable as the inputs used to test it. Keep small fixtures for every accepted format, boundary dimensions, transparency, orientation, animation, oversized input, and an explicitly rejected type. Assert the output role, format, dimensions, storage path, and terminal state rather than only checking that the Assembly completed. Include a destination failure and a duplicate notification so the recovery path is exercised before an incident.

Record the intended behavior in application configuration or source control and associate that label with each run. When the saved Template changes, test it in a non-production Workspace or with isolated destinations, inspect cost and metadata, and move a bounded share of traffic first. If results regress, route new work back to the previous controlled behavior and reconcile any Assemblies already in flight instead of assuming they stopped.

### Format matrix

Covers the inputs and metadata variations the product promises to accept.

### Failure fixtures

Prove rejection, export failure, duplicate delivery, and replay behavior as well as success.

### Bounded rollout

Limits cost and customer impact while a changed workflow is measured under real traffic.

## Technical details worth knowing

* A per-file Step begins as soon as an upstream Step named in its `use` value emits a file; only merge or bundling Robots wait for a complete set of named inputs. A Step’s position in the JSON object does not determine execution order.
* Assembly Variables such as `${fields.tenant_id}`, `${assembly.id}`, and `${file.url_name}`—a URL-safe (slugged) version of the current Step file’s name, including its extension—are resolved at execution time and can parameterize dimensions, paths, and other Robot values. In the sample export paths, `${assembly.id}` provides per-run uniqueness and the slash-containing `${unique_prefix}` keeps files within a run distinct.
* Setting `allow_steps_override` to false in a saved Template prevents callers from merging replacement Steps into that Template. Runtime fields still need application-side validation and authorization.
* /file/filter can compare server-detected file properties with array conditions. A specific MIME allowlist is safer than trusting a filename extension or client-reported type.
* Template Credentials keep destination secrets separate from the Template JSON and can be updated without copying keys through every integration.
* Assembly Notifications are retried when the receiver does not return HTTP 200. Consumers must verify the signature and tolerate duplicate, delayed, or out-of-order delivery.

## A practical approach

1. 1\
   Draw the required inputs, derivatives, Steps that read from multiple derivatives, exports, and failure boundaries.
2. 2\
   Save and lock a Template whose variable inputs are deliberately limited.
3. 3\
   Submit one representative file and inspect every Step in Assembly Status.
4. 4\
   Add verified webhook handling, idempotent persistence, fixtures, and a controlled rollout.

A four-stage media workflow

## When Transloadit is useful

Use a saved Template to connect /upload/handle, /file/filter, parallel /image/resize Steps, and /s3/store. Lock the processing graph by setting allow\_steps\_override to false, pass bounded fields at runtime, and reconcile completion through Assembly Status and verified webhooks.

## Architecture boundary

Assembly Templates describe file processing and movement. They do not own product approvals, tenant authorization, business state, or an application’s configuration history; keep those decisions in the system that starts and records each job.

## Frequently asked questions

### Does the order of Steps in the JSON control execution?

No. The `use` dependencies control when a Step can run. Independent Steps can run in parallel even when one appears later in the object.

### Can a locked Template still accept custom values?

Yes. `allow_steps_override: false` prevents callers from replacing the processing Steps. The application can still send fields used by `${fields.*}` variables, and it must validate and authorize those values.

### Should cloud storage keys appear in Assembly Instructions?

No. Store them as Template Credentials and reference the credential name from the storage Robot. This keeps secrets out of the workflow JSON and makes rotation independent.

### How should a workflow change safely?

Keep the application workflow label and configuration history in your own system, test changes with representative fixtures, and shift traffic deliberately. Preserve enough information on every job to explain which behavior produced its outputs.

### What happens when a webhook is delivered twice?

Treat duplicate delivery as normal. Verify the signature, look up the Assembly or operation key, and make the same terminal update safe to apply again without duplicating assets or user-visible events.

## Build the workflow

Move from the concept to a tested Assembly with Robot documentation and working demos.

### Relevant Robots

* [/upload/handle](/docs/robots/upload-handle.md)
* [/file/filter](/docs/robots/file-filter.md)
* [/image/resize](/docs/robots/image-resize.md)
* [/s3/store](/docs/robots/s3-store.md)
* [Understand Assembly Instructions](/docs/topics/assembly-instructions.md)
* [Create and lock a Template](/docs/topics/templates.md)
* [Use Assembly Variables](/docs/topics/assembly-variables.md)
* [Handle verified webhooks](/docs/topics/webhooks.md)
* [Read the API documentation](/docs.md)
* [Explore working demos](/demos.md)
* [Create a free workspace](/c/signup/)

Workflow automation

## Continue with related guides

* [Integrate Transloadit in five minutes](/guides/transloadit-five-minute-integration.md)\
  Install the Node SDK, resize one image, and inspect a real Assembly result in about five minutes.
* [Media automation: from upload to reliable output](/guides/media-automation.md)\
  Automate repeatable media intake, transformation, validation, and export while preserving observability and control.
* [A complete guide to digital-asset workflows](/guides/digital-asset-workflows.md)\
  Design a digital-asset workflow from intake and processing through review, publication, retention, and deletion.
* [AI content moderation in an upload workflow](/guides/ai-content-moderation-workflows.md)\
  Place AI moderation inside a controlled upload workflow with confidence thresholds and human review.
* [Automated content moderation: architecture and failure handling](/guides/automated-content-moderation.md)\
  Build automated moderation as a layered system of file checks, classifiers, policy decisions, and review queues.
* [Automated image analysis with observable workflows](/guides/automated-image-analysis.md)\
  Turn image analysis into a repeatable, asynchronous workflow rather than a blocking application request.
