How to Merge Multiple PDF Reports into a Single File (Node.js & PHP)

One of the most annoying tasks in enterprise reporting is the "End of Month" shuffle. Your system generates a daily PDF report (invoice, log, or analytics summary), resulting in 30 separate files by the end of the month.

Nobody wants to open 30 email attachments. Your boss wants one file containing everything.

In this tutorial, we will write a script to automatically take a list of daily PDF URLs and combine them into a single, organized monthly Master Report using the aPDF.io Merge API.

The Problem with Local Merging

You could use libraries like pdf-lib (Node) or FPDI (PHP) to do this on your server. But there are risks:
  1. Memory Leaks: Loading 30 PDFs into memory at once can crash a standard web server container (OOM errors).
  2. Corrupted Files: Local libraries often struggle if the source PDFs have different versions (e.g., merging a PDF 1.4 with a PDF 1.7).
Using an API offloads this heavy processing. You send the URLs, and the API handles the merging.

Step 1: Get Your API Token

  1. Log in to aPDF.io.
  2. Grab your API Token from the dashboard.

Step 2: The Logic

We assume you have a list of URLs for your daily reports. This could come from your database or an AWS S3 bucket listing.

Endpoint: https://apdf.io/api/pdf/file/merge
Method: POST

The API accepts a files array. For each file, you can specify the URL and (optionally) which pages to keep.

Option A: Node.js Implementation

We will use axios to make the request.

First, install axios:

npm install axios

Create merge_reports.js:

const axios = require('axios');

const API_TOKEN = 'YOUR_API_TOKEN_HERE';
const API_URL = 'https://apdf.io/api/pdf/file/merge';

// 1. The list of daily reports stored on your server/S3
const dailyReports = [
    "https://examples.apdf.io/daily-report-2025-11-01.pdf",
    "https://examples.apdf.io/daily-report-2025-11-02.pdf",
    "https://examples.apdf.io/daily-report-2025-11-03.pdf"
];

async function mergeReports() {
    try {
        console.log(`Merging ${dailyReports.length} files...`);

        // 2. Construct the payload
        // We map our simple URL array into the object structure the API expects
        const filesPayload = dailyReports.map(url => ({
            file: url
        }));

        // 3. Send Request
        const response = await axios.post(
            API_URL,
            {
                files: filesPayload
            },
            {
                headers: {
                    'Authorization': `Bearer ${API_TOKEN}`,
                    'Content-Type': 'application/json'
                }
            }
        );

        // 4. Handle Success
        console.log("Success! Monthly Report Generated:");
        console.log(response.data.file); // The download URL of the merged PDF

    } catch (error) {
        if (error.response) {
            console.error(`Error ${error.response.status}:`, error.response.data);
        } else {
            console.error("Error:", error.message);
        }
    }
}

mergeReports();

Option B: PHP Implementation

PHP is still the king of backend reporting for many businesses. We will use Guzzle, the standard PHP HTTP client.
composer require guzzlehttp/guzzle

Create merge_reports.php:

<?php

require 'vendor/autoload.php';

use GuzzleHttp\Client;

$apiToken = 'YOUR_API_TOKEN_HERE';
$apiUrl = 'https://apdf.io/api/pdf/file/merge';

// 1. The list of daily reports
$dailyReports = [
    "https://examples.apdf.io/daily-report-2025-11-01.pdf",
    "https://examples.apdf.io/daily-report-2025-11-02.pdf",
    "https://examples.apdf.io/daily-report-2025-11-03.pdf"
];

// 2. Format the data for the API
$filesPayload = [];
foreach ($dailyReports as $url) {
    $filesPayload[] = [
        'file' => $url
    ];
}

$client = new Client();

try {
    echo "Merging files...\n";

    // 3. Send Request
    $response = $client->post($apiUrl, [
        'headers' => [
            'Authorization' => 'Bearer ' . $apiToken,
            'Accept'        => 'application/json',
            'Content-Type'  => 'application/json',
        ],
        'json' => [
            'files' => $filesPayload
        ]
    ]);

    $result = json_decode($response->getBody(), true);

    // 4. Handle Success
    echo "Success! Monthly Report Generated:\n";
    echo $result['file'] . "\n";

} catch (\Exception $e) {
    echo "Error: " . $e->getMessage();
    if ($e->hasResponse()) {
        echo "\nResponse: " . $e->getResponse()->getBody();
    }
}

Advanced Tip: Clean Up Your Merges

A common issue when merging reports is that every individual report has its own Cover Page. When you merge 30 of them, you end up with 30 cover pages interspersed throughout the document.

You can fix this using the pages parameter.

If page 1 is always the cover page, you can tell the API to only merge from page 2 onwards for every file:

// Node.js Example
const filesPayload = dailyReports.map(url => ({
    file: url,
    pages: "2-z" // "2" through "z" (end of file)
}));

This simple parameter transforms a messy collection of files into a cohesive, professional document.

Conclusion

Automating document workflows saves hours of manual work. By using the aPDF.io Merge API, you ensure your application remains lightweight and fast, regardless of how many files you need to combine.
Ready to build?
Get your free API token here
Most APIs charge you per document. aPDF.io is built to be a developer-friendly, free alternative that handles the heavy lifting without the monthly subscription.