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
- Memory Leaks: Loading 30 PDFs into memory at once can crash a standard web server container (OOM errors).
- 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).
Step 1: Get Your API Token
- Log in to aPDF.io.
- Grab your API Token from the dashboard.
Step 2: The Logic
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
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
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.