How to Generate and Attach Payslip PDFs in Salesforce?

How to Generate and Attach Payslip PDFs in Salesforce?

On September 16, 2025, Posted by , In Uncategorized, With Comments Off on How to Generate and Attach Payslip PDFs in Salesforce?

Question:

I have an Invocable Apex class that I call from Flow to generate a PDF and automatically attach it to a custom Payslip record. The code was working perfectly last week, but now it suddenly fails with a “FATAL_ERROR Internal Salesforce.com Error”. The debug log shows that the crash happens exactly when the code tries to execute getContentAsPDF(). My Visualforce page is set to renderAs="pdf" and worked fine before. Why is this happening now, and how can I fix it?

Answer:

This error is a common problem when working with Salesforce PDF generation. What makes it tricky is that Salesforce doesn’t give a descriptive error message; instead, it just throws “Internal Salesforce.com Error”. This usually points to one of three issues: Salesforce doesn’t allow the method in the current execution context, there’s a hidden bug in the Visualforce template, or Salesforce has changed something internally.

Let’s analyze your code and the possible reasons for failure.

Issue with Asynchronous Context

Your original code looks like this:

@Future(callout=true)
private static void generatePayslipsFuture(List<Id> payslipIds) {
    for (Id payslipId : payslipIds) {
        generateAndAttachPDF(payslipId);
    }
}

Inside this @future method, you are calling:

pdfBlob = pdf.getContentAsPDF();

The problem here is that Salesforce does not support getContent() or getContentAsPDF() in asynchronous Apex. This means that if you try to run this method inside @future, Queueable, or Batch Apex, it will fail. In earlier releases, Salesforce sometimes allowed it to work, but in recent platform updates the enforcement has become stricter, which is why your code suddenly started failing.

Fix by Removing @future

The most reliable fix is to remove the @future annotation and keep the PDF generation synchronous. Here’s the corrected code:

public without sharing class PayslipPDFGenerator {

    @InvocableMethod(label='Generate Payslip PDF' description='Generates a PDF payslip and attaches it to the Payslip record')
    public static void generatePayslipPDF(List<Id> payslipIds) {
        for (Id payslipId : payslipIds) {
            generateAndAttachPDF(payslipId);
        }
    }

    private static void generateAndAttachPDF(Id payslipId) {
        Payslip__c payslip = [
            SELECT Id, Name, Employee__r.Name, Employee_Designation__c,
                   Payroll_Period__c, Payment_Date__c, Attendance__c,
                   Staff_ID_Location__c, Basic_Salary__c, Transport_Allowance__c,
                   Housing_Allowance__c, Leave_Allowance__c, Other_Allowance__c,
                   Overtime_Allowance_I__c, Overtime_Allowance_II__c,
                   Public_Holidays_Allowance__c, Employer_Pension_Contribution__c,
                   Employee_Pension_Contribution__c, Tax_Remittance__c,
                   NHF_Contribution__c, Total_Gross_Salary__c, Total_Net_Payment__c
            FROM Payslip__c
            WHERE Id = :payslipId
        ];

        PageReference pdf = Page.PayslipPDFTemplate;
        pdf.getParameters().put('id', payslip.Id);

        ContentVersion cv = new ContentVersion();
        cv.Title = payslip.Name;
        cv.PathOnClient = payslip.Name + '.pdf';
        cv.IsMajorVersion = true;

        try {
            Blob pdfBlob;
            if (Test.isRunningTest()) {
                pdfBlob = Blob.valueOf('Test PDF Content');
            } else {
                pdfBlob = pdf.getContentAsPDF(); // Works now in synchronous context
            }

            cv.VersionData = pdfBlob;
            insert cv;

            Id conDocId = [
                SELECT ContentDocumentId FROM ContentVersion WHERE Id = :cv.Id
            ].ContentDocumentId;

            ContentDocumentLink cdl = new ContentDocumentLink();
            cdl.ContentDocumentId = conDocId;
            cdl.LinkedEntityId = payslip.Id;
            cdl.ShareType = 'V';
            cdl.Visibility = 'AllUsers';
            insert cdl;

        } catch (Exception e) {
            throw new PayslipGenerationException('Failed to generate payslip PDF: ' + e.getMessage());
        }
    }

    public class PayslipGenerationException extends Exception {}
}

Code Explanation:
The PayslipPDFGenerator class is designed to generate a payslip PDF from a Visualforce template and attach it directly to the corresponding Payslip__c record. It is marked as without sharing so it bypasses record-level security, ensuring the automation always runs successfully. The entry point is the @InvocableMethod generatePayslipPDF, which can be called from a Flow and accepts a list of payslip record Ids. For each Id, the helper method generateAndAttachPDF is executed. Inside this method, Salesforce queries all the required fields from the Payslip__c object, such as employee details, salary, allowances, and contributions, ensuring the Visualforce template has all the data it needs. The code then creates a PageReference for the PayslipPDFTemplate Visualforce page, passing the payslip Id as a parameter so the page can render dynamically. A new ContentVersion record is prepared with the file name, title, and versioning details. To generate the actual file, the Visualforce page is converted into a PDF blob using getContentAsPDF() unless the code is running in a test context, where dummy content is used instead. The blob is stored in ContentVersion and inserted, which automatically creates a ContentDocument. The code retrieves the ContentDocumentId and creates a ContentDocumentLink to associate the file with the payslip record, setting the link to be viewable by all users with access. Exception handling is implemented to catch any issues during this process, and a custom exception PayslipGenerationException is thrown with a clear error message if something fails. In short, this class automates the entire flow of generating a payslip PDF, saving it in Salesforce Files, and attaching it to the payslip record, making it accessible for employees and admins directly from the system.

Possible Errors in the Visualforce Page

Another potential reason for the failure is within your Visualforce page template. For example, you had this line:

<td>{!Payslip__c._r.Name}</td>

This is not valid syntax. If you want to display the employee’s name from the related object, it should be:

<td>{!Payslip__c.Employee__r.Name}</td>

Code Explanation:
The _r suffix is incorrect; it should be Employee__r which is the relationship name. Using an invalid field reference can cause PDF rendering to fail internally. Correcting this ensures that your template renders properly both in HTML and PDF formats.

Alternative Approach with Queueable Apex

If you need to handle a large number of records, Queueable is usually preferred over @future. However, Salesforce still doesn’t allow PDF generation inside Queueable. The workaround here is to separate your process: run the Queueable job to gather and prepare data, but generate the PDF synchronously in smaller batches, possibly triggered by Flow or a synchronous Apex call.

Alternative Approach with LWC + External PDF Engine

Another modern solution is to move away from Visualforce PDF rendering entirely. You can build a Lightning Web Component that renders the payslip in HTML, and then use an external service (like Puppeteer running on Heroku or AWS Lambda) to convert the HTML to PDF. This avoids Salesforce PDF quirks, but it does add infrastructure complexity.

Final Answer

The “FATAL_ERROR Internal Salesforce.com Error” happens because Salesforce no longer allows getContentAsPDF() inside asynchronous Apex like @future. The solution is to generate your PDF synchronously by removing the @future call from your Apex class. You should also check and correct any invalid field references in your Visualforce template, such as replacing {!Payslip__c._r.Name} with {!Payslip__c.Employee__r.Name}.

If you need large-scale PDF generation, you can redesign the process to split into smaller synchronous jobs, or move to a modern HTML-to-PDF approach using external services.

By applying these fixes, your Flow-triggered Invocable Apex will once again be able to generate and attach payslip PDFs without errors.

Enroll for Salesforce Training Designed for Career Building Success

Our Salesforce Course is expertly designed to provide a comprehensive understanding of the Salesforce platform, equipping you with the essential skills to excel in the CRM industry. The program covers key modules such as Salesforce Admin, Developer, and AI, combining theoretical learning with hands-on practice. Through real-world projects and practical assignments, you will develop the expertise needed to solve complex business challenges effectively using Salesforce solutions. Our skilled instructors ensure you gain both technical proficiency and industry insights to thrive in the Salesforce ecosystem.

Beyond technical training, our Salesforce Training in San Francisco includes personalized mentorship, certification guidance, and interview preparation to enhance your career opportunities. You’ll receive access to extensive study materials, hands-on project experience, and dedicated support throughout your journey. By the end of the program, you will be well-prepared for certification exams and possess the real-world problem-solving skills employers value. Take the first step in your Salesforce career with us—join a Free Demo today!

Comments are closed.