Skip to main content

How to View All Files and Attachments in Salesforce LWC: Step-by-Step Guide

In plain words: A Record File Viewer in LWC is a custom Lightning component that queries and displays all files, documents, and attachments linked to a specific Salesforce record. Instead of navigating through standard related lists, users get a consolidated grid where they can view file details, preview content, and open direct download links.

Salesforce records frequently accumulate documents such as contracts, spec sheets, invoices, and images. While the standard "Files" related list provides basic table access, business users often need a responsive card view with direct preview and download actions. Building this in Lightning Web Components (LWC) with modern Salesforce Files architecture delivers a clean, responsive document gallery directly on the record page.

1. Modern Architecture: Salesforce Files vs. Legacy Attachments

When developing file utilities in Salesforce, it is vital to target the modern file storage architecture rather than legacy objects:

  • Legacy Attachments (Attachment): Deprecated storage model tied directly to a single parent record. Lacks versioning, sharing settings, and modern preview handlers.
  • Salesforce Files (ContentDocumentLink & ContentVersion): The standard enterprise storage model. Files are independent entities linked to records via junction objects, enabling multi-record sharing, version tracking, and secure platform previews.
360 File Viewer Architecture Card:
  • Query Junction: ContentDocumentLink filtered by LinkedEntityId = :recordId.
  • Data Payload: ContentDocument.LatestPublishedVersion (Title, FileType, ContentSize, Extension).
  • Security Enforcement: WITH USER_MODE and cacheable wire methods.
  • Preview Mechanism: Standard Salesforce file preview via NavigationMixin.

2. Implementing the Apex File Controller

The backend controller queries all files linked to the current record ID, computes readable file sizes, and returns structured data to the front end.

Step 1: Apex Controller (RecordFilesController.cls)
public with sharing class RecordFilesController {

    public class FileCardWrapper {
        @AuraEnabled public Id documentId;
        @AuraEnabled public Id versionId;
        @AuraEnabled public String title;
        @AuraEnabled public String fileType;
        @AuraEnabled public String extension;
        @AuraEnabled public String formattedSize;
        @AuraEnabled public String downloadUrl;
    }

    @AuraEnabled(cacheable=true)
    public static List<FileCardWrapper> getRelatedFiles(Id recordId) {
        List<FileCardWrapper> results = new List<FileCardWrapper>();

        if (recordId == null) {
            return results;
        }

        // Query active Salesforce Files linked to this record
        List<ContentDocumentLink> links = [
            SELECT ContentDocumentId, 
                   ContentDocument.Title, 
                   ContentDocument.FileType, 
                   ContentDocument.FileExtension,
                   ContentDocument.LatestPublishedVersionId,
                   ContentDocument.ContentSize
            FROM ContentDocumentLink
            WHERE LinkedEntityId = :recordId
            WITH USER_MODE
            ORDER BY ContentDocument.CreatedDate DESC
            LIMIT 100
        ];

        for (ContentDocumentLink link : links) {
            FileCardWrapper item = new FileCardWrapper();
            item.documentId = link.ContentDocumentId;
            item.versionId = link.ContentDocument.LatestPublishedVersionId;
            item.title = link.ContentDocument.Title;
            item.fileType = link.ContentDocument.FileType;
            item.extension = link.ContentDocument.FileExtension;
            item.downloadUrl = '/sfc/servlet.shepherd/version/download/' + link.ContentDocument.LatestPublishedVersionId;
            
            // Format size to KB / MB
            Long bytes = link.ContentDocument.ContentSize;
            if (bytes != null) {
                if (bytes < 1024) {
                    item.formattedSize = bytes + ' B';
                } else if (bytes < 1048576) {
                    item.formattedSize = (bytes / 1024) + ' KB';
                } else {
                    item.formattedSize = ((Double) bytes / 1048576).setScale(2) + ' MB';
                }
            }
            results.add(item);
        }

        return results;
    }
}

3. Building the Lightning Web Component

The LWC component renders a responsive card grid using SLDS utilities and enables in-app file previews using the platform navigation service.

Step 2: Component HTML (viewAllAttachments.html)
<template>
    <lightning-card title="Related Files & Attachments" icon-name="standard:file">
        <div class="slds-p-around_medium">

            <!-- Loading State -->
            <template lwc:if={isLoading}>
                <lightning-spinner alternative-text="Loading files..." size="small"></lightning-spinner>
            </template>

            <!-- Files Grid -->
            <template lwc:if={hasFiles}>
                <div class="slds-grid slds-wrap slds-gutters">
                    <template for:each={files} for:item="file">
                        <div key={file.documentId} class="slds-col slds-size_1-of-1 slds-medium-size_1-of-2 slds-large-size_1-of-3 slds-m-bottom_small">
                            <div class="slds-box slds-box_x-small slds-theme_shade slds-radius_medium">
                                <div class="slds-media slds-media_center">
                                    <div class="slds-media__figure">
                                        <lightning-icon icon-name="doctype:attachment" size="small" alternative-text="Document"></lightning-icon>
                                    </div>
                                    <div class="slds-media__body slds-truncate">
                                        <a href="javascript:void(0);" 
                                           class="slds-text-heading_small slds-truncate" 
                                           title={file.title} 
                                           data-id={file.documentId} 
                                           onclick={handleFilePreview}>
                                            {file.title}
                                        </a>
                                        <p class="slds-text-body_small slds-text-color_weak">
                                            {file.extension} &bull; {file.formattedSize}
                                        </p>
                                    </div>
                                    <div class="slds-media__figure slds-media__figure_reverse">
                                        <a href={file.downloadUrl} target="_blank" download class="slds-button slds-button_icon slds-button_icon-border-filled" title="Download">
                                            <lightning-icon icon-name="utility:download" size="xx-small" alternative-text="Download"></lightning-icon>
                                        </a>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </template>
                </div>
            </template>

            <!-- Empty State -->
            <template lwc:elseif={noFilesFound}>
                <div class="slds-illustration slds-illustration_small slds-text-align_center slds-p-vertical_medium">
                    <p class="slds-text-body_regular slds-text-color_weak">No files or attachments linked to this record.</p>
                </div>
            </template>

        </div>
    </lightning-card>
</template>
Step 3: Component JavaScript (viewAllAttachments.js)
import { LightningElement, api, wire, track } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';
import getRelatedFiles from '@salesforce/apex/RecordFilesController.getRelatedFiles';

export default class ViewAllAttachments extends NavigationMixin(LightningElement) {
    @api recordId;
    @track files = [];
    isLoading = true;

    @wire(getRelatedFiles, { recordId: '$recordId' })
    wiredFiles({ error, data }) {
        this.isLoading = false;
        if (data) {
            this.files = data;
        } else if (error) {
            console.error('Error fetching record files:', error);
            this.files = [];
        }
    }

    get hasFiles() {
        return this.files && this.files.length > 0;
    }

    get noFilesFound() {
        return !this.isLoading && (!this.files || this.files.length === 0);
    }

    handleFilePreview(event) {
        const docId = event.currentTarget.dataset.id;
        
        // Open standard Salesforce File Preview Modal
        this[NavigationMixin.Navigate]({
            type: 'standard__namedPage',
            attributes: {
                pageName: 'filePreview'
            },
            state: {
                selectedRecordId: docId
            }
        });
    }
}
Step 4: Metadata Configuration (viewAllAttachments.js-meta.xml)
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>60.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
        <target>lightningCommunity__Page</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__RecordPage">
            <objects>
                <object>Account</object>
                <object>Contact</object>
                <object>Opportunity</object>
                <object>Case</object>
            </objects>
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

4. Common Traps & Development Best Practices

SOQL Query Trap: Querying the Attachment Object in Modern Orgs
Writing SOQL against the legacy Attachment object (SELECT Id FROM Attachment WHERE ParentId = :recordId) ignores files uploaded via Lightning Experience, Salesforce Mobile, and Chatter, which are stored as ContentDocument records. Always query ContentDocumentLink to capture all modern attachments.
Core Rule: Use ContentDocumentLink with WITH USER_MODE to enforce sharing rules, format file sizes on the server, and leverage NavigationMixin for native in-app file previews.
  • Leverage Cacheable Wire: Marking the Apex method @AuraEnabled(cacheable=true) ensures rapid client-side rendering and automatic caching across the Lightning Experience session.
  • Handle Direct Downloads: Construct download URLs using the standard platform servlet path (/sfc/servlet.shepherd/version/download/{versionId}) to enable one-click local downloads.
  • Responsive SLDS Grid: Use SLDS responsive sizing classes (slds-size_1-of-1 slds-medium-size_1-of-2 slds-large-size_1-of-3) so file cards adapt cleanly to mobile, narrow sidebars, and wide main panels.

Summary

Building a custom file and attachment gallery in Lightning Web Components provides a modern, user-friendly document viewing experience on Salesforce record pages. By querying ContentDocumentLink in Apex, formatting file metadata defensively, and wiring up native file previews via NavigationMixin, developers can deliver a fast, responsive document hub that aligns with modern Salesforce architecture.