# Download image button

**URL:** https://community.glideapps.com/t/download-image-button/57996
**Category:** Ask for Help
**Created:** [February 15, 2023, 6:38pm UTC](https://community.glideapps.com/t/download-image-button/57996 "2023-02-15T18:38:08Z")
**Posts on this page:** 14
**Page:** 1

<div class="post-metadata">

### Author: ![roope257](https://avatars.discourse-cdn.com/v4/letter/r/90ced4/32.png) [@roope257](https://community.glideapps.com/u/roope257)
#### Post date: [February 15, 2023, 6:38pm UTC](https://community.glideapps.com/t/download-image-button/57996/1 "2023-02-15T18:38:08Z")

</div>

Hi community!

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

---

<div class="post-metadata">

### Author: ![Jeff\_Hager](https://sea2.discourse-cdn.com/flex002/user_avatar/community.glideapps.com/jeff_hager/32/43_2.png) [@Jeff\_Hager](https://community.glideapps.com/u/Jeff_Hager)
#### Post date: [February 15, 2023, 6:49pm UTC](https://community.glideapps.com/t/download-image-button/57996/2 "2023-02-15T18:49:23Z")

</div>

> [@Download video on iPhone](https://community.glideapps.com/t/download-video-on-iphone/22776/2):
>
> You could try something like this. Create a full \<a href\> tag with a template column that includes the ‘download’ tag. You might have to display this link using a rich text component. I’m not sure you could use it with a button. From what I’ve read, this may not always work.

---

<div class="post-metadata">

### Author: ![user19](https://sea2.discourse-cdn.com/flex002/user_avatar/community.glideapps.com/user19/32/52319_2.png) [@user19](https://community.glideapps.com/u/user19)
#### Post date: [February 15, 2023, 7:02pm UTC](https://community.glideapps.com/t/download-image-button/57996/3 "2023-02-15T19:02:23Z")

</div>

is it work?  
what is in template finally?

---

<div class="post-metadata">

### Author: ![roope257](https://avatars.discourse-cdn.com/v4/letter/r/90ced4/32.png) [@roope257](https://community.glideapps.com/u/roope257)
#### Post date: [February 15, 2023, 7:17pm UTC](https://community.glideapps.com/t/download-image-button/57996/4 "2023-02-15T19:17:59Z")

</div>

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

---

<div class="post-metadata">

### Author: ![Jeff\_Hager](https://sea2.discourse-cdn.com/flex002/user_avatar/community.glideapps.com/jeff_hager/32/43_2.png) [@Jeff\_Hager](https://community.glideapps.com/u/Jeff_Hager)
#### Post date: [February 15, 2023, 9:04pm UTC](https://community.glideapps.com/t/download-image-button/57996/5 "2023-02-15T21:04:00Z")

</div>

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.

---

<div class="post-metadata">

### Author: ![slscustom.ru](https://sea2.discourse-cdn.com/flex002/user_avatar/community.glideapps.com/slscustom.ru/32/77336_2.png) [@slscustom.ru](https://community.glideapps.com/u/slscustom.ru)
#### Post date: [February 15, 2023, 9:15pm UTC](https://community.glideapps.com/t/download-image-button/57996/6 "2023-02-15T21:15:00Z")

</div>

> [@Jeff\_Hager](#):
>
> Download File

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

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

---

<div class="post-metadata">

### Author: ![Jeff\_Hager](https://sea2.discourse-cdn.com/flex002/user_avatar/community.glideapps.com/jeff_hager/32/43_2.png) [@Jeff\_Hager](https://community.glideapps.com/u/Jeff_Hager)
#### Post date: [February 15, 2023, 9:34pm UTC](https://community.glideapps.com/t/download-image-button/57996/7 "2023-02-15T21:34:37Z")

</div>

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.

---

<div class="post-metadata">

### Author: ![slscustom.ru](https://sea2.discourse-cdn.com/flex002/user_avatar/community.glideapps.com/slscustom.ru/32/77336_2.png) [@slscustom.ru](https://community.glideapps.com/u/slscustom.ru)
#### Post date: [February 15, 2023, 9:40pm UTC](https://community.glideapps.com/t/download-image-button/57996/8 "2023-02-15T21:40:16Z")

</div>

> [@Jeff\_Hager](#):
>
> You’re using a construct url colum

Exactly Jeff im using template column

---

<div class="post-metadata">

### Author: ![roope257](https://avatars.discourse-cdn.com/v4/letter/r/90ced4/32.png) [@roope257](https://community.glideapps.com/u/roope257)
#### Post date: [February 15, 2023, 9:47pm UTC](https://community.glideapps.com/t/download-image-button/57996/9 "2023-02-15T21:47:50Z")

</div>

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).”.

---

<div class="post-metadata">

### Author: ![slscustom.ru](https://sea2.discourse-cdn.com/flex002/user_avatar/community.glideapps.com/slscustom.ru/32/77336_2.png) [@slscustom.ru](https://community.glideapps.com/u/slscustom.ru)
#### Post date: [February 15, 2023, 9:49pm UTC](https://community.glideapps.com/t/download-image-button/57996/10 "2023-02-15T21:49:02Z")

</div>

> [@roope257](#):
>
> In the latest versions of Chrome

im using Edge not Chrome

---

<div class="post-metadata">

### Author: ![Jeff\_Hager](https://sea2.discourse-cdn.com/flex002/user_avatar/community.glideapps.com/jeff_hager/32/43_2.png) [@Jeff\_Hager](https://community.glideapps.com/u/Jeff_Hager)
#### Post date: [February 15, 2023, 9:57pm UTC](https://community.glideapps.com/t/download-image-button/57996/11 "2023-02-15T21:57:25Z")

</div>

@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.

---

<div class="post-metadata">

### Author: ![Dilip\_Adiga](https://sea2.discourse-cdn.com/flex002/user_avatar/community.glideapps.com/dilip_adiga/32/60330_2.png) [@Dilip\_Adiga](https://community.glideapps.com/u/Dilip_Adiga)
#### Post date: [September 20, 2024, 4:38am UTC](https://community.glideapps.com/t/download-image-button/57996/12 "2024-09-20T04:38:04Z")

</div>

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

---

<div class="post-metadata">

### Author: ![Dilip\_Adiga](https://sea2.discourse-cdn.com/flex002/user_avatar/community.glideapps.com/dilip_adiga/32/60330_2.png) [@Dilip\_Adiga](https://community.glideapps.com/u/Dilip_Adiga)
#### Post date: [September 20, 2024, 10:14am UTC](https://community.glideapps.com/t/download-image-button/57996/13 "2024-09-20T10:14:16Z")

</div>

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

```auto
<!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](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

---

<div class="post-metadata">

### Author: ![Christopher\_Geyer](https://sea2.discourse-cdn.com/flex002/user_avatar/community.glideapps.com/christopher_geyer/32/85766_2.png) [@Christopher\_Geyer](https://community.glideapps.com/u/Christopher_Geyer)
#### Post date: [November 21, 2025, 1:51pm UTC](https://community.glideapps.com/t/download-image-button/57996/14 "2025-11-21T13:51:45Z")

</div>

## 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)**

```auto
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**

```auto
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**

```auto
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)

```auto
// 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)

```auto
// 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. ✌
