The CreatorCon Call for Content is officially open! Get started here.

Automating Attachment Download – One-Click Attachment Download for Form View

yashwanth_p
Tera Contributor

Form View — One-Click Attachment Download

 

We’ve all been there — opening a record, clicking through each attachment one by one, and manually downloading them.

Not fun, right?

Let’s simplify that.

This minor enhancement adds a Download Attachments button directly on the form.
One click, and boom — every attachment for that record is zipped up and ready for download.
No extra plugins, no background jobs, no leftover data. Just clean, instant downloads.

 

 

💡 Why Build This

 

The idea was straightforward:

  • Make attachment downloads faster and effortless.
  • Keep the logic simple and fully native.
  • Avoid storing unnecessary data or creating extra records.

With just one UI Action and one Processor, the job gets done beautifully.

 

 

🧩 What’s Inside

 

ComponentTypePurpose
Download AttachmentsUI Action (Form View)The button that triggers the processor
exportFormAttachmentsToZipProcessor (Global Scope)Gathers all attachments, zips them, and delivers the ZIP instantly

 

 

🔧 UI Action Setup

 

Condition: 

current.hasAttachments();

Ensures that the button shows up only when attachments exist, keeping the UI neat and meaningful.

 

Action Script:

action.setRedirectURL('exportFormAttachmentsToZip.do?sysparm_sys_id=' + current.sys_id + '&sysparm_table=' + current.getTableName());

It simply redirects to the processor, handing over the record’s sys_id and table name — no form reloads, no extra overhead.

 

 

🧠 Processor Logic — exportFormAttachmentsToZip

(function process(g_request, g_response, g_processor) {

    // Get record sys_id and table name from the request
    var sysid = g_request.getParameter('sysparm_sys_id');
    var table = g_request.getParameter('sysparm_table');

    // Query the record using provided sys_id and table
    var recordGR = new GlideRecord(table);
    if (!recordGR.get(sysid)) return;

    // Create ZIP file name using record number and caller name
    var zipName = recordGR.getValue('number') + '_' + 
                  recordGR.caller_id.getDisplayValue().replace(/\s+/g, "_") + '.zip';

    // Query all attachments related to the record
    var attachmentGR = new GlideRecord('sys_attachment');
    attachmentGR.addQuery('table_sys_id', recordGR.sys_id);
    attachmentGR.addQuery('table_name', recordGR.getTableName());
    attachmentGR.query();

    // If the record has attachments
    if (attachmentGR.hasNext()) {
        // Set response headers for file download
        g_response.setHeader('Pragma', 'public');
        g_response.addHeader('Cache-Control', 'max-age=0');
        g_response.setContentType('application/octet-stream');
        g_response.addHeader('Content-Disposition', 'attachment;filename=' + zipName);

        // Create a ZIP output stream
        var out = new Packages.java.util.zip.ZipOutputStream(g_response.getOutputStream());
        var sa = new GlideSysAttachment();
        var usedNames = {}; // Object to track duplicate filenames

        // Loop through all attachments
        while (attachmentGR.next()) {
            var binData = sa.getBytes(attachmentGR);  // Get file data
            var originalName = attachmentGR.file_name + ""; // Original filename
            var fileName = originalName;
            var counter = 1;

            // Handle duplicate filenames in ZIP
            while (usedNames[fileName]) {
                var extIndex = originalName.lastIndexOf(".");
                if (extIndex > -1) {
                    fileName = originalName.substring(0, extIndex) + "_" + counter + originalName.substring(extIndex);
                } else {
                    fileName = originalName + "_" + counter;
                }
                counter++;
            }

            // Mark filename as used
            usedNames[fileName] = true;

            // Add file to the ZIP
            addBytesToZip(out, fileName, binData);
        }

        // Close ZIP stream
        out.close();
    }

    // Helper function to write a file into the ZIP
    function addBytesToZip(out, file, stream) {
        out.putNextEntry(new Packages.java.util.zip.ZipEntry(file)); // Create new ZIP entry
        out.write(stream, 0, stream.length); // Write file data
        out.closeEntry(); // Close the entry
    }

})(g_request, g_response, g_processor);

The processor collects all attachments for the selected record, compresses them into a single ZIP file, handles duplicate filenames, and streams the ZIP directly to the browser for immediate download — all done in memory with no leftover data.

 

🔍 How It All Comes Together

 

  1. Record Lookup – Finds the record and its attachments.
  2. Attachment Check – Gracefully handles records without attachments.
  3. Smart Naming – Names the ZIP like INC0010054_John_Doe.zip.
  4. ZIP Creation – Streams attachments directly into the response.
  5. Duplicate Handling – Renames duplicates (file_2.png, file_3.png, etc.).
  6. Instant Download – Browser prompts immediately.
  7. Zero Cleanup – Everything happens in memory — once downloaded, it’s gone.

 

⚙️ Why This Works So Well

 

  • 🧠 Handles both with- and without-attachment cases gracefully.
  • No scheduled jobs or background processing needed.
  • 🧩 Reusable core logic for other download or email flows.
  • 🧼 Leaves zero footprint on your instance.

 

Performance Thoughts

 

The logic is light and memory-based, making it ideal for day-to-day use.

  • Works best for small to medium attachment sizes.
  • Since it streams the ZIP directly to the browser, there’s no disk or database usage.
  • For very large records or heavy simultaneous downloads, consider a background or asynchronous variant to distribute load.

Simple, efficient, and fast — exactly what you want for on-demand attachment downloads.

 

Final Outcome

 

A single click.
A single ZIP.
All your record’s attachments — instantly downloaded, handled cleanly, and gone without a trace.

 

🔗 What’s Next

 

This enhancement makes it easy to download attachments from a single record.
Up next, we’ll extend the same logic to the List View, so attachments from multiple records can be downloaded together with just one click.
Later, we’ll enhance it even more — sending the generated ZIP file by email and automatically clearing old files to keep the instance clean and optimized.

 

Hope this helps!


Regards,
Yashwanth Pepakayala.

0 REPLIES 0