Book a Call
Get a Quote

Building a Multi-Image Uploader in Retool

Phu Nguyen
June 1, 2024
15 min read
Building a Multi-Image Uploader in Retool

In the previous blog, we learned how to create a single image/file uploader in Retool. Today, we'll create a multiple image/file uploader, explore its differences, and guide you through the entire process. Get ready to unleash the power of bulk uploads in Retool!

Creating uploader module in Retool

Add components to the module

Here's how to craft a reusable module for uploading multiple images or files in Retool:

  • Directly onto the module canvas, drag a "Text" component (text1). Enter a clear and descriptive title for your uploader module.
  • Below the text component, drag a "File Dropzone" (fileDropzone1) component.
  • Here comes the magic! Underneath the File Dropzone, drag a "Grid View" component (gridView1). This will display uploaded files with informative details. Within the Grid View, you can create:
    • Image: Drag an "Image" component (image1) to display thumbnails.
    • Editable Text: Drag an "Editable Text" component (editableText1) to allow renaming files.
    • Checkbox: Drag a "Checkbox" component (checkbox1) for indicating successful uploads.
  • To keep users informed about their progress, drag a "Text" component (text2) above the Grid View. Set the text to display the number of uploaded files in a format like "0/2 Uploaded" (initially showing 0). We'll connect this text component to the upload functionality later.
  • Finally, place a "Button" component (button1) and set the button text to "Upload" to trigger the upload process.
Add upload component to the module

Add inputs to the module

Open the Module settings and add two data inputs:

  • Title: Set a default title to display when your app doesn't provide one. Use {{ title.value }} within text1 component to dynamically display the provided title.
Input Title
  • Initial URL: Allow you to pre-fill the URL field with existing images/files URL, useful when editing them.
Input URL

To ensure the initial values from the "Initial URL" input are seamlessly reflected on the UI and used for populating existing data, we'll leverage a combination of Retool features:

  • Dynamic Data Fetch on Load: Configure the "Initial URL" data input to automatically trigger a Query JSON with SQL script on module load. This dynamic script constructs a SQL query using template literals {{initialUrl.value}}.
  • Success Handling: Loops through fetched data, assigning unique IDs (uid) and storing each element with initial include: false (upload status) and id: uid (reference) within listViewData (Retool’s variable) using uid as the key:
initialUrlsGiven.data?.map(e => {
  const uid = e.name ?? uuid.v4();
  listViewData.setIn([uid], e);
  listViewData.setIn([uid, 'include'], false); 
listViewData.setIn([uid, 'id'], uid);
});
Success Handling
  • Data Source for Grid View: Finally, we'll set the data source of the gridView1 to {{Object.values(listViewData.value)}}.

In gridView1, we will change:

  • Change content of image1 and add “Click” event handler to open URL in a new tab.
Change content of image
  • Change default value of editableText1 and add “Blur” event handler to create new or update field name in listViewData.
Change default value text
  • Change default value of checkbox1 and add “Change” event handler to update field include in listViewData.
Change default value of checkbox

In fileDropzone1, we will also add a script in “Parse” event handler, it iterates through selected files, creating new entries in listViewData (if needed) with name, type, id (set to name), and include: false (initial upload status) using the file's name as the key:

fileDropzone1.files?.map(e => {
  if(listViewData.value?.[e.name] == null){
      const uid = e.name;
    listViewData.setIn([uid, 'id'], e.name);
    listViewData.setIn([uid, 'name'], e.name);
    listViewData.setIn([uid, 'type'], e.type);
    listViewData.setIn([uid, 'include'], false);
  }
});

Add outputs to the module

Open the Module settings and add an output. Here's the exciting part: we'll use a transformer listData to transform the uploaded images data (listViewData) into a structured array format. Each element in the array will be an object containing:

  • createdAt: Timestamp of the upload.
  • name: Name of the uploaded image.
  • url: URL of the uploaded image.
  • type: Type of the uploaded file (e.g., image/jpeg).
return {{ Object.values(listViewData.value)?.filter(f => f.include === true && f.url !== null).map((e) => Object({
  "createdAt" : e.createdAt,
  "name" : e.name,
  "url" : e.url, 
  "type" : e.type, 
}))
}};

Add outputs to the module

Creating query to upload multiple images/files to Google Cloud Storage

We've built a user-friendly interface within Retool, and now it's time to tackle the final challenge – uploading files and images to your Google Cloud Storage bucket.

Before diving into the upload query, ensure you've established a secure connection between Retool and Google Cloud Storage. This typically involves creating service accounts and assigning the necessary permissions. You can find a helpful guide on this process in the following blog post "How to connect Retool with Google Cloud Storage" (https://retoolers-io.webflow.io/blog-posts/how-to-connect-retool-with-google-cloud-and-manage-api-keys).

Assuming you've successfully linked Retool and Google Cloud Storage, let's create a query named uploadImages specifically designed to upload images along with their metadata:

async function uploadImages() {
  for (let i = 0; i < fileDropzone1.value?.length; i++) {
    const id = fileDropzone1.value[i].name;
    if (
      listViewData.value?.[id]?.include === false &&
      !listViewData.value?.[id]?.url
    ) {
      const mimeType = fileDropzone1.value[i].type;
      const extension = mimeType.split("/").pop();
      localStorage.setValue("fileInput", `${uuid.v4()}.${extension}`);
      localStorage.setValue("fileExtension", mimeType);
      await uploadToGCS.trigger({
        additionalScope: { id: id },
        onSuccess: (data) => {
          listViewData.setIn(
            [id, "url"],
            data.signedUrl.split("?GoogleAccessId")[0]
          );
          listViewData.setIn([id, "createdAt"], new Date());
          listViewData.setIn([id, "include"], true);
        },
        onFailure: (error) => {
          utils.showNotification("Error", error, "error", "3s");
        },
      });
      await setMetadata.trigger();
    }
  }
}

await uploadImages();

This query iterates through fileDropzone1 files. For each file needing upload (not uploaded yet and no URL):

  • Retrieving file information (MIME type, extension).
  • Uploading the image to Google Cloud Storage:
    • On success, sets url, timestamp and include in listViewData.
    • On failure, shows an error notification.
  • Setting additional image metadata.

Now, let's delve deeper into the specifics of each step within the Retool query.

Retrieving file infomation

Grabs MIME type and extension from the selected file, store a unique filename for uploading by using Retool's built-in functions uuid.v4().

Uploading the image to Google Cloud Storage

Let's build the upload functionality using GUI, we use additionalScope id from query into Uploaded data field:

Uploading the image to Google Cloud Storage

Setting additional image metadata.

Enhance your upload functionality by including metadata for the uploaded files/images:

  • Cache-Control: This header instructs web browsers and caching servers to cache the uploaded image publicly for one year (31536000 seconds). This can improve website performance by reducing the need to re-download the image for subsequent user visits within a year.
  • Content-Type: This header specifies the MIME type (e.g., image/jpeg, image/png) of the uploaded file. This ensures browsers can correctly display the uploaded image.
Content-Type

Finally, create Click event handler uploadImages.trigger() in button1 and update value in text2.

Click event handler

Testing uploader module

Now, let's test the module's functionality by creating an app in Retool using this module and uploading a PDF file, a png image and a jpg image.

Test the module's functionality

If uploading successfully, you should see the files listed with their respective thumbnails, names, and upload statuses updated in real-time. This confirms that the module is functioning as intended and that your files are successfully uploaded to Google Cloud Storage.

Test the module's functionality

Congratulation! You've successfully created a Multiple Image/File Uploader Module in Retool, enhancing your application with robust bulk upload capabilities. Now, you can easily manage and upload multiple files, streamlining your workflow and improving efficiency.

Get in Touch

Ready to bring your dashboard vision to life? Contact Retoolers today, and let us help you create a powerful, intuitive dashboard that meets your exact requirements.

Phu Nguyen
Retool Developer