Developers / Analytics / Data

How to Export Salesforce Reports to PowerPoint: A Step-by-Step Guide

By Harinarayana Swaroop Ponnada

Exporting Salesforce reports directly into PowerPoint (PPT) can be extremely useful for business reviews, executive presentations, or stakeholder meetings. Instead of manually exporting data and re-creating charts in PowerPoint, we can automate this using LWC, Apex, Visualforce, and JavaScript libraries (PptxGenJS + html2canvas).

This guide walks you through the entire implementation, from preparing libraries, uploading them into Salesforce, and wiring everything together.

We’ll put everything together step by step, using:

  • PptxGenJS: To generate the PowerPoint file.
  • Visualforce + html2canvas: To render and capture report charts.
  • Apex: To fetch report IDs dynamically.
  • LWC: To build the UI and Export button.

Step 1: Prepare the Libraries 

1. Create the folder

Make a folder called pptlibs.

2. Download the Files

3. Organize

Put both inside pptlibs/ → compress into pptlibs.zip.

Step 2: Upload to Salesforce

1. Go to Static Resources

Navigate to Setup → Static Resources → New.

2. Enter the Following Details

  • Name: pptlibs
  • File: pptlibs.zip
  • Cache Control: Public

Step 3: Apex Service to Fetch Report IDs

We’ll fetch report IDs dynamically using DeveloperName, making the solution reusable across multiple reports.

Apex Class: 

public with sharing class ReportService {
    @AuraEnabled(cacheable=true)
    public static String getReportIdByName(String developerName) { 
        return [SELECT Id FROM Report WHERE DeveloperName = :developerName LIMIT 1].Id;
    }
}

Step 4: Visualforce Page to Render Charts

We use <analytics:reportChart> to render reports and html2canvas to capture them as PNG.

Captured images are sent back to the LWC using window.postMessage.

  • Why Visualforce here?: Because Salesforce only allows the analytics:reportChart component in Visualforce pages, not in LWC or Aura. This makes VFP the bridge for chart rendering.
  • Why html2canvas?: The report chart rendered on VFP is essentially DOM content. html2canvas converts it into a high-quality PNG, which we can send back to LWC for embedding in PPT slides.

Visualforce:

<apex:page contentType="text/html" showHeader="false" sidebar="false">
    <apex:includeScript value="{!$Resource.pptlibs}/pptlibs/html2canvas.min.js"/>

    <!-- Only this div will be captured -->

<div id="captureTarget" >
        <analytics:reportChart reportId="{!$CurrentPage.parameters.reportId}" size="large"/>
</div> 
<script>
const wait = ms => new Promise(r => setTimeout(r, ms));
async function generateBlob() {
    await wait(10000); 

    const target = document.getElementsByClassName('outerBound')[0];
    if (!window.html2canvas || !target) return;

    const canvas = await html2canvas(target, { useCORS: true, backgroundColor: '#ffffff', scale: 2 });
    canvas.toBlob(blob => {
        const reader = new FileReader();
        reader.onload = function() {
            const arrayBuffer = reader.result;
            window.parent.postMessage(
                { chartBlob: arrayBuffer, tag: '{!$CurrentPage.parameters.tag}' },
                '*'
            );
        };
        reader.readAsArrayBuffer(blob);
    }, 'image/png');
}
window.onload = generateBlob;
</script>
</apex:page>

Step 5: LWC to Orchestrate Everything

The LWC does three things:

  1. Loads PptxGenJS library from Static Resource.
  2. Creates hidden iframes for reports.
  3. Listens for chart images, and generates the PowerPoint file.

HTML

<template>
    <lightning-card title="Create Executive Deck">
        <div class="slds-m-around_medium">
            <template if:true={showSpinner}>
                <lightning-spinner alternative-text="Loading charts..." size="medium"></lightning-spinner>
            </template>

            <lightning-button
                label="Generate Presentation"
                onclick={generatePPT}
                disabled={isChartMissing}>
            </lightning-button>
        </div>
    </lightning-card>
</template>

JavaScript

import { LightningElement, track } from 'lwc';
import { loadScript } from 'lightning/platformResourceLoader';
import pptLibs from '@salesforce/resourceUrl/pptlibs';
import getReportIdByName from '@salesforce/apex/ReportService.getReportIdByName';

export default class LwcPptGenerator extends LightningElement {
    @track chartBase64_1; // first report
    @track chartBase64_2; // second report    
    @track isChartMissing = true;
    @track showSpinner = true;

    pptInitialized = false;

    renderedCallback() {
        if (this.pptInitialized) return;
        this.pptInitialized = true;

        loadScript(this, pptLibs + '/pptlibs/pptxgen.bundle.min.js')
            .then(() => console.log('PPTXGenJS loaded'))
            .catch(error => console.error('Error loading PPTXGenJS', error));
    }

    connectedCallback() {
        // Fetch first report
        getReportIdByName({ developerName: 'account_report_EDB' })
            .then(reportId => this.createIframe(reportId, 'report1'))
            .catch(error => console.error('Error fetching account_report_EDB', error));

        // Fetch second report
        getReportIdByName({ developerName: 'OpptyReport_Dvy' })
            .then(reportId => this.createIframe(reportId, 'report2'))
            .catch(error => console.error('Error fetching OpptyReport_Dvy', error));

        this.boundHandleMessage = this.handleMessage.bind(this);
        window.addEventListener('message', this.boundHandleMessage);
    }

    disconnectedCallback() {
        window.removeEventListener('message', this.boundHandleMessage);
    }

    createIframe(reportId, reportTag) {
        const iframe = document.createElement('iframe');
        iframe.src = `/apex/ReportChartCaptureVF?reportId=${reportId}&tag=${reportTag}`;
        document.body.appendChild(iframe);
    }

    handleMessage(event) {
        if (event.data && event.data.chartBlob) {
            const arrayBuffer = event.data.chartBlob;
            const reportTag = event.data.tag;

            this.arrayBufferToBase64(arrayBuffer)
                .then(base64 => {
                    if (reportTag === 'report1') {
                        this.chartBase64_1 = base64;
                        console.log('Chart 1 ready, length:', base64.length);
                    } else if (reportTag === 'report2') {
                        this.chartBase64_2 = base64;
                        console.log('Chart 2 ready, length:', base64.length);
                    }

                    // Check if both charts are ready
                    if (this.chartBase64_1 && this.chartBase64_2) {
                        this.isChartMissing = false;
                        this.showSpinner = false;
                    }
                })
                .catch(err => console.error('Error converting ArrayBuffer to Base64:', err));
        }
    }


    arrayBufferToBase64(buffer) {
        return new Promise((resolve, reject) => {
            const blob = new Blob([buffer], { type: 'image/png' });
            const reader = new FileReader();
            reader.onloadend = () => {
                const base64 = reader.result.split(',')[1];
                resolve(base64);
            };
            reader.onerror = reject;
            reader.readAsDataURL(blob);
        });
    }

    generatePPT() {
        if (!window.PptxGenJS) {
            console.error('PptxGenJS not loaded yet.');
            return;
        }

        if (!this.chartBase64_1 || !this.chartBase64_2) {
            console.error('Both charts are not ready.');
            return;
        }

        const pptx = new PptxGenJS();
        const slide = pptx.addSlide();

        // Title
        slide.addText('Customer & Pipeline Insights by Rating', {
            x: 0.5,
            y: 0.3,
            w: 9,
            fontSize: 32,
            bold: true,
            color: '203864',  
            align: 'center'
        });

        // Subtitle / Context
        slide.addText('A snapshot of account health and pipeline quality across industries & stages', {
            x: 0.5,
            y: 0.9,
            w: 9,
            fontSize: 16,
            italic: true,
            color: '606060',
            align: 'center'
        });

        // First chart: Accounts by Industry & Rating
        slide.addImage({
            data: `data:image/png;base64,${this.chartBase64_1}`,
            x: 0.5,
            y: 1.5,
            w: 4.5,
            h: 3
        });
        slide.addText('Accounts by Industry & Rating', {
            x: 0.5,
            y: 4.6,
            w: 4.5,
            fontSize: 14,
            bold: true,
            color: '404040',
            align: 'center'
        });

        // Second chart: Opportunities by Stage & Rating
        slide.addImage({
            data: `data:image/png;base64,${this.chartBase64_2}`,
            x: 5.2,
            y: 1.5,
            w: 4.5,
            h: 3
        });
        slide.addText('Pipeline Opportunities by Stage & Rating', {
            x: 5.2,
            y: 4.6,
            w: 4.5,
            fontSize: 14,
            bold: true,
            color: '404040',
            align: 'center'
        });

        // Optional: Footer for polish
        slide.addText('Salesforce Insights Dashboard - Executive Preview', {
            x: 0.5,
            y: 5.2,
            w: 9,
            fontSize: 10,
            color: '808080',
            align: 'right'
        });

        pptx.write('base64').then(base64 => {
            const link = document.createElement('a');
            link.href = 'data:application/vnd.openxmlformats-officedocument.presentationml.presentation;base64,' + base64;
            link.download = 'GeneratedPPT.pptx';
            link.click();
        });
    }
}

Generated PowerPoint:

Key Considerations

  • Performance: Loading multiple report charts may require longer wait times (setTimeout / await wait). Adjust as needed.
  • Security: Only use Public static resource caching if you’re not exposing sensitive logic.
  • Extensibility: You can add multiple slides dynamically if you have more reports.

Final Thoughts

With this setup, you can automate Salesforce report exports directly into PowerPoint, saving hours of manual work while keeping charts consistent with Salesforce data.

READ MORE: Salesforce Document Generation Made Easy With Word, PowerPoint, and Flow Automation

The Author

Harinarayana Swaroop Ponnada

Harinarayana is a Senior Analyst at Capgemini and a passionate Salesforce Developer with many years of experience with Data Cloud.

Leave a Reply