Download image button

Hi community!

Is there way to create button for image so user will be able to download image by pressing the button? :slight_smile:

1 Like
1 Like

is it work?
what is in template finally?

:confused: I didn’t found any template having the “download” tag. :confused:

As I explain in the other post, you need to create a Template column in the data editor. In that template column, add the HTML that includes the ‘download’ attribute, as explained in the attached link in my other post. Instead of entering a url for the href attribute, set up a replacement value. Maybe something like {url}, and replace it with your actual url.

So the template should look something like this:
<a href="{url}" download>Download File</a>

And all you need to do is set up a replacement for {url}.

Finally display that HTML using a rich text component.

3 Likes

Hi Jeff! What im doing wrong?
upd: corrected video

Upd: yes in this video im showing construct column but using template colemn in act

You’re using a construct url column. What I explain uses a Template column. You need to build an ‘a href’ HTML tag. Not a url with query parameters.

1 Like

Exactly Jeff im using template column

I guess the problem is this:

“In the latest versions of Chrome, you cannot download cross-origin files (they have to be hosted on the same domain).”.

im using Edge not Chrome

@roope257 @slscustom.ru In all fairness, I haven’t tried this myself, or at least haven’t tried it in a couple of years. If I get a chance, I’ll test it out later tonight.

@slscustom.ru I’m not sure why you have the extra New Column C template column. The replacement in New Column F could directly refer to the photo column. But either way should give you the same result, so it shouldn’t make a difference.

2 Likes

Hi @Jeff_Hager ,

Is there something for user to be able to download video file as well, irrespective of device type, ?

By Using a button which initiates the download?

Also what if there are array of image link? Like carousel ?

Regards,
Dilip

Hi @Jeff_Hager ,

I got it to work by actioning it on a button.

My Approach :

I hosted a html code in GitHub Pages For Zipping the content file (Image or Video) then downloading it.

PS: Zipping is not reducing the quality, its just so that browsers register it to download file.

My HTML code is as follows : It includes loading indicator too

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Download File with Zipping</title>
    <!-- Include JSZip Library -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js"></script>
    <style>
        /* Simple spinner styles */
        .spinner {
            border: 6px solid #f3f3f3; /* Light grey */
            border-top: 6px solid #3498db; /* Blue */
            border-radius: 50%;
            width: 40px;
            height: 40px;
            animation: spin 1s linear infinite;
            display: none; /* Hidden by default */
            margin: 20px auto;
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    </style>
</head>
<body>
    <h1>File Download Trigger with Zipping</h1>
    <p id="status">Preparing to download...</p>
    
    <!-- Loading spinner -->
    <div class="spinner" id="loadingSpinner"></div>

    <script>
        window.onload = function() {
            const urlParams = new URLSearchParams(window.location.search);
            const fileUrl = urlParams.get('fileUrl');  // Get the file URL from the query parameter

            const loadingSpinner = document.getElementById('loadingSpinner');
            loadingSpinner.style.display = 'block';  // Show the loading spinner

            if (fileUrl) {
                // Extract the file name from the URL
                const fileName = fileUrl.substring(fileUrl.lastIndexOf('/') + 1);

                // Fetch the image or file as a blob (binary data)
                fetch(fileUrl)
                    .then(response => response.blob())  // Convert response to a binary blob
                    .then(blob => {
                        const zip = new JSZip();  // Create a new JSZip instance
                        zip.file(fileName, blob, { binary: true });  // Add the binary file to the zip with the correct filename

                        // Generate the zip file and trigger download
                        zip.generateAsync({ type: "blob" }).then(function(content) {
                            loadingSpinner.style.display = 'none';  // Hide the loading spinner once done
                            const zipLink = document.createElement('a');  // Create a hidden download link
                            zipLink.href = URL.createObjectURL(content);  // Create a URL for the zip
                            zipLink.setAttribute('download', 'files.zip');  // Name the downloaded zip as "files.zip"
                            document.body.appendChild(zipLink);  // Append the link to the body
                            zipLink.click();  // Trigger the download
                            zipLink.remove();  // Clean up by removing the link
                            
                            // Wait a short delay and attempt to close the tab
                            setTimeout(function() {
                                window.close();  // Close the current tab if opened by JavaScript
                            }, 2000);  // Adjust the delay as needed
                        });
                    })
                    .catch(error => {
                        loadingSpinner.style.display = 'none';  // Hide the spinner in case of error
                        document.getElementById('status').textContent = 'Error fetching the file. Check the URL.';
                    });
            } else {
                loadingSpinner.style.display = 'none';  // Hide the spinner if no URLs are provided
                document.getElementById('status').textContent = 'No file URL provided.';
            }
        }
    </script>
</body>
</html>

Then I use template column where I put the url of the page

https://Youraccountname.github.io/Yourrepositoryname/?fileUrl=YourFileURL

Then add a button and on click open link and direct it to this.

Let me know if this is acceptable approach , or you have some suggestions to improve it.

Thank you in advance.

Regards,
Dilip

Glide Webhook → Supabase → ZIP Download

Two clients asked me this week how I solved this problem, so here’s my quick take:

I wanted a solution where Glide only provides image URLs without generating ZIP files itself. At the same time, I didn’t want to store actual images in the database – just the references. The result is a small but reliable setup:


Setup

1. Webhook Endpoint (Supabase Edge Function)

POST https://[project-ref].supabase.co/functions/v1/webhook-receiver

  • Glide sends: item_id + image_urls array

  • Supabase stores only the URLs in Postgres (no images)

2. Download Endpoint

GET https://[project-ref].supabase.co/functions/v1/download-images?item_id={item_id}

  • Fetches images on-demand from original URLs

  • Builds ZIP in memory

  • Returns {item_id}_images.zip as file download

3. Glide Template Column

https://[project-ref].supabase.co/functions/v1/download-images?item_id={Row ID}

→ User clicks the link → browser downloads {item_id}_images.zip


Database Schema

Table: image_requests

Column Type Description
id UUID Primary key
item_id INTEGER Product/Row ID from Glide
created_at TIMESTAMP Request timestamp

Table: images

Column Type Description
id UUID Primary key
request_id UUID FK to image_requests
image_url TEXT Original image URL
item_name_numeric INTEGER Sort order (0, 1, 2
)
status TEXT pending/completed/failed
created_at TIMESTAMP Created timestamp

Edge Functions

webhook-receiver (simplified)

// Receives webhook, stores URLs in DB
const { item_id, image_urls } = await req.json();

// Create request record
const { data: request } = await supabase
  .from("image_requests")
  .insert({ item_id })
  .select()
  .single();

// Store each image URL
const images = image_urls.map((url, index) => ({
  request_id: request.id,
  image_url: url,
  item_name_numeric: index,
  status: "completed"
}));

await supabase.from("images").insert(images);

download-images (simplified)

// Get newest request for item_id
const { data: requests } = await supabase
  .from("image_requests")
  .select("id")
  .eq("item_id", parseInt(itemId))
  .order("created_at", { ascending: false })
  .limit(1);

// Get all image URLs
const { data: images } = await supabase
  .from("images")
  .select("image_url, item_name_numeric")
  .eq("request_id", requests[0].id)
  .order("item_name_numeric", { ascending: true });

// Create ZIP
const zip = new JSZip();
for (const img of images) {
  const response = await fetch(img.image_url);
  const blob = await response.blob();
  zip.file(`${img.item_name_numeric}.jpg`, blob);
}

return new Response(await zip.generateAsync({ type: "blob" }), {
  headers: { "Content-Disposition": `attachment; filename="${itemId}_images.zip"` }
});


What happens behind the scenes

  • Webhook stores only URLs (no files)

  • Download fetches images live – always the latest version

  • ZIP is created in-memory and streamed

  • Old records auto-delete after 90 days (cleanup cron job)


Happy to share. :victory_hand:

2 Likes