Flag of Ukraine

Assembly Instructions

We briefly introduced Assembly Instructions while going over Transloadit's concepts and in My first App. Now, let's dig a little deeper ⛏ and take a look at this example:

  "steps": {
    "imported": {
      "robot": "/http/import",
      "url": "https://assets.transloadit.com/assets/images/face.jpg"
    "resized": {
      "robot": "/image/resize",
      "use": "imported",
      "width": 100,
      "height": 100,
      "resize_strategy": "fillcrop"
    "exported": {
      "robot": "/s3/store",
      "use": ["imported", "resized"],
      "bucket": "YOUR_S3_BUCKET",
      "key": "YOUR_S3_KEY",
      "secret": "YOUR_S3_SECRET",
      "path": "/my_images/${file.id}/${file.url_name}"

As indicated, Assembly Instructions consist of Steps. If one or more Steps fail, the whole Assembly will end and show an error in its Assembly Status JSON. The example shows three Steps: imported, resized and exported. You can name your Steps anything you like. Giving them good names makes it easier to reference and understand what's going on. One exception is 🤖/upload/handle: it can only be used in a single Step, and that must be called :original.

Please also note that you must not name any Step :original that is not also using 🤖/upload/handle, as that can lead to weird job spawning behavior. Your Assembly will error out with a validation error if you do this.

For example, notice how the imported Step is used as an input to the resized Step. And both the imported and the resized Steps are used as the input for the exported Step. This way, we'll import an image from a web server, have it resized, and export both the imported image and the resized version to S3.

Not all Steps require inputs. Our imported Step, for instance, provides the first input by downloading it, so that's where we'll omit use. Other examples of Robots that don't require input files are 🤖/html/convert, which can take a screenshot from a website and create the first file that way, or the 🤖/upload/handle, which takes its files from your app's visitors, instead of from another Step.

Step parameters

As you can see, each Step is defined as an object with a handful of properties, or parameters. Most of them are in fact Robot Parameters as they instruct, for instance, the width of an image after a resize. Those are all covered in the respective Robot docs. There are, however, also 4 parameters that instruct the Assembly engine itself, defining which Robots are invoked and how they are interconnected:

  • use

    String / Array of Strings / Objectrequired

    Specifies which Step(s) to use as input.

    • You can pick any names for Steps except ":original" (reserved for user uploads handled by Transloadit)

    • You can provide several Steps as input with arrays:

      "use": [

    💡 That’s likely all you need to know about use, but you can view advanced use cases:

    › Advanced use cases
    • Step bundling. Some Robots can gather several Step results for a single invocation. For example, 🤖/file/compress would normally create one archive for each file passed to it. If you'd set bundle_steps to true, however, it will create one archive containing all the result files from all Steps you give it. To enable bundling, provide an object like the one below to the use parameter:

      "use": {
        "steps": [
        "bundle_steps": true

      This is also a crucial parameter for 🤖/video/adaptive, otherwise you'll generate 1 playlist for each viewing quality.
      Keep in mind that all input Steps must be present in your Template. If one of them is missing (for instance it is rejected by a filter), no result is generated because the Robot waits indefinitely for all input Steps to be finished.

      Here’s a demo that showcases Step bundling.

    • Group by original. Sticking with 🤖/file/compress example, you can set group_by_original to true, in order to create a separate archive for each of your uploaded or imported files, instead of creating one archive containing all originals (or one per resulting file). This is important for for 🤖/media/playlist where you'd typically set:

      "use": {
        "steps": [
        "bundle_steps": true,
        "group_by_original": true
    • Fields. You can be more discriminatory by only using files that match a field name by setting the fields property. When this array is specified, the corresponding Step will only be executed for files submitted through one of the given field names, which correspond with the strings in the name attribute of the HTML file input field tag for instance. When using a back-end SDK, it corresponds with myFieldName1 in e.g.: $transloadit->addFile('myFieldName1', './chameleon.jpg').

      This parameter is set to true by default, meaning all fields are accepted.


      "use": {
        "steps": [ ":original" ],
        "fields": [ "myFieldName1" ]
    • Use as. Sometimes Robots take several inputs. For instance, 🤖/video/merge can create a slideshow from audio and images. You can map different Steps to the appropriate inputs.


      "use": {
        "steps": [
          { "name": "audio_encoded", "as": "audio" },
          { "name": "images_resized", "as": "image" }

      Sometimes the ordering is important, for instance, with our concat Robots. In these cases, you can add an index that starts at 1. You can also optionally filter by the multipart field name. Like in this example, where all files are coming from the same source (end-user uploads), but with different <input> names:


      "use": {
        "steps": [
          { "name": ":original", "fields": "myFirstVideo", "as": "video_1" },
          { "name": ":original", "fields": "mySecondVideo", "as": "video_2" },
          { "name": ":original", "fields": "myThirdVideo", "as": "video_3" }

      For times when it is not apparent where we should put the file, you can use Assembly Variables to be specific. For instance, you may want to pass a text file to 🤖/image/resize to burn the text in an image, but you are burning multiple texts, so where do we put the text file? We specify it via ${use.text_1}, to indicate the first text file that was passed.


      "watermarked": {
        "robot": "/image/resize",
        "use"  : {
          "steps": [
            { "name": "resized", "as": "base" },
            { "name": "transcribed", "as": "text" },
        "text": [
            "text"  : "Hi there",
            "valign": "top",
            "align" : "left",
            "text"    : "From the 'transcribed' Step: ${use.text_1}",
            "valign"  : "bottom",
            "align"   : "right",
            "x_offset": 16,
            "y_offset": -10,
  • robot


    Specifies which Robot should process files passed to this Step.

    There are 65 Robots, each with their own parameters, such as width to control how an image is resized. The full list of parameters per Robot can be taken from the Robot docs.

  • result

    Boolean ⋅ default: Automatic

    Controls whether the results of this Step should be present in the Assembly Status JSON.

    If set to true, the result of this Step will be present. If files from that Step weren't exported to your storage, their location will be set to a temporary URL.

    By default, we set this to true for leaf Steps and false for any intermediate Step.

    Explicitly setting it to false can be a useful tool in keeping the Assembly Status JSON small.

    Setting result: true on storage Steps does not add those Steps to the Assembly JSON, but only changes the returned URL values for the results of any transcoding Steps passed into those storage Steps. If you pipe a transcoding Step into multiple storage Steps (for example /s3/store) with each having result: true, then multiple results for this transcoding Step will be added to the Assembly JSON, giving you a quick overview of all file URLs for the various S3 buckets (in this example).

  • force_accept

    Boolean ⋅ default: false

    Force a Robot to accept a file type it would have ignored.

    By default Robots ignore files they are not familiar with. 🤖/video/encode, for example, will happily ignore and refuse to emit images.

    With the force_accept parameter set to true you can force Robots to accept all files thrown at them. This will typically lead to errors and should only be used for debugging or combatting edge cases.

Order of execution

In order to speed up Assemblies, Steps will be executed as soon as their input Steps emit files. In other words, many things are processed in parallel. For example, let's say you want to encode an uploaded video and would also like to extract thumbnails from it:

  "steps": {
    ":original": {
      "robot": "/upload/handle"
    "encoded": {
      "use": ":original",
      "robot": "/video/encode",
      "preset": "ipad-high"
    "thumbed": {
      "use": ":original",
      "robot": "/video/thumbs",
      "count": 4
    "exported": {
      "use": ["encoded", "thumbed"],
      "robot": "/s3/store",
      "credentials": "YOUR_S3_CREDENTIALS"

Both the encoded and the thumbed Steps will be executed in parallel as soon as the first file upload is complete. The exported Step is fired for each of the files coming from encoded and thumbed. It is likely that the thumbnails will hit your S3 bucket before the video that was optimized for iPad, even though thumbnails were defined later. So, the order of Steps does not really matter. The use parameter defines the input for each Step and this ultimately dictates how our Steps are chained.

Filtering to make Steps conditional

Using 🤖/file/filter, you can execute Steps based on a file's properties. This allows you to create Assembly Instructions that: cater to both video and audio uploads, reject files that are too small, only apply an effect on images that have transparent areas, etc. These and more things are also covered in the Robot's docs.

Assembly Variables

Transloadit supports variables so you can create more powerful workflows. You can, for instance, filter files based on width, influence storage location based on type, etc. Here is a full list of placeholder variables available to Assembly Instructions. They can be used in any parameter value in any Robot:

  • ${assembly.id}
    The ID of the Assembly representing the current upload, which is a UUIDv4 without dashes.
  • ${unique_prefix}
    A unique 33-character prefix used to avoid file name collisions, such as "f2/d3eeeb67479f11f8b091b04f6181ad".

    Please notice the / in the prefix. If you use ${unique_prefix} in the path parameter of 🤖/s3/store for example, then it will create sub-directories in your S3 bucket. This may or may not be desired. Use ${file.id} if you require a unique prefix without slashes.

  • ${unique_original_prefix}
    This is similar to ${unique_prefix}, with the exception that two different encoding results of the same uploaded file (the original file) will have the same prefix value here.
  • ${previous_step.name}
    The name of the previous Step that produced the current file.
  • ${file.id}
    The ID of the file being processed, which is a UUIDv4 without dashes.
  • ${file.original_id}
    The ID of the original file that a certain file derives from. For example, if you use an import Robot to import files and then encode them somehow, the encoding result files will have a ${file.original_id} that matches the ${file.id} of the imported file.
  • ${file.original_name}
    The name of the original file (including file extension) that a certain file derives from. For example, if you use an import Robot to import files and then encode them somehow, the encoding result files will have a ${file.original_name} that matches the ${file.name} of the imported file.
  • ${file.original_basename}
    The basename of the original file that a certain file derives from. For example, if you use an import Robot to import files and then encode them somehow, the encoding result files will have a ${file.original_basename} that matches the ${file.basename} of the imported file.
  • ${file.original_path}
    The import path of the original file that a certain file derives from. All of our import Robots set ${file.original_path} accordingly.

    For example, if you use 🤖/s3/import to import files from Amazon S3, the imported files, as well a all files that are derived from them, will have a file.original_path that equals the path to the file on S3, but without the filename. So if the S3 path was "path/to/file.txt", then file.original_path will be "/path/to/". If the path was "/a.txt", ${file.original_path} will be "/".

    file.original_path will always have sufficient slashes in order for you to safely use it in the path parameter of your export step, like this: "path": "${file.original_path}${file.name}". This is handy if you want to import files from, for example, S3, convert them somehow and store them again on S3 in the same (or similar) file structure.

  • ${file.name}
    The name of the file being processed, including the file extension.
  • ${file.url_name}
    The slugged name of the file.

    Any characters other than A-Z a-z 0-9 -_. are replaced with underscores, and spaces are replaced with dashes. This includes the file extension as well.

    Note that if you have two files ッッ.jpg and チチ.jpg, they will both be called __.jpg. So you'll want to take extra care to only use ${file.url_name} in conjunction with ${unique_prefix} or ${file.md5hash}.

  • ${file.basename}
    The name of the file being processed, without the file extension.
  • ${file.url_basename}
    The slugged basename of the file (the file name without the file extension).

    Any characters other than A-Z a-z 0-9 -_. are replaced with underscores, and spaces are replaced with dashes.

    Note that if you have two files ッッ.jpg and チチ.jpg they will both be called __.jpg. So you'll want to take extra care to only use ${file.url_basename} in conjunction with ${unique_prefix} or ${file.md5hash}.

  • ${file.ext}
    The file extension.
  • ${file.size}
    The file size in bytes.
  • ${file.mime}
    The file's MIME type.
  • ${file.md5hash}
    The file's MD5 hash. This is a hash over the file's contents, not only over the file's name.
  • ${file.*}
    Any file property available in the final results array, such as ${file.meta.width}. Not all meta keys are available for all file types.
  • ${fields.*}
    The fields submitted together with the upload.

    For example, in the case of a Form submission where Uppy was set to allow fields: ['myvar'], and the form had a tag like <input type="hidden" name="myvar" value="1" />, ${fields.myvar} would contain a value of 1.

    Alternatively, fields could also be populated programmatically like so: json { "steps": { "store": { "use": "encoded", "robot": "/s3/store", "credentials": "YOUR_S3_CREDENTIALS_NAME", "path": "${assembly.id}/${fields.subdir}/356" } }, "fields": { "subdir": "bar" } }

    In the case of a conflict, variables derived from form fields take precedence over those derived from the fields key.

Assembly Variables example

Let's say you don't like the location where files are stored. By default, Transloadit is careful not to overwrite anything. All export Robots have a path parameter with a default of "${unique_prefix}/${file.url_name}", resulting in locations such as: "f2/d3eeeb67479f11f8b091b04f6181ad/my-file-name.png".

We could, for example, change the parameter's value to "${previous_step.name}/${file.id}.${file.ext}", which would make the paths end up looking like "video-step-name/a8d3eeeb67479f11f8b091b04f6181ad.png".

Not all Assembly Variables are created equal and some are more unique (and hence suitable to solely base storage location on). here are some examples, ordered from less to more unique:

  • ${file.ext} is the same for many files
  • ${file.url_name} especially across users and time, a high likelihood of collisions, for instance: avatar.jpg
  • ${previous_step.name} is the same for all files that are results of the same Step
  • ${assembly.id} is the same for all files within a single Assembly
  • ${file.id} as well as ${unique_prefix} are unique for each file

20% off any plan for the Uppy community
Use the UPPY20 code when upgrading.
Sign up
20% off any plan for the tus community
Use the TUS20 code when upgrading.
Sign up
Product Hunt
20% off any plan for Product Hunters
Use the PRH20 code when upgrading.
Sign up