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 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.
- 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.
Conclusion
Related tutorials
How to Merge PDFs by Asking Claude
Connect Claude to the Apdf MCP server once, and merging PDFs becomes a sentence: describe the result, the agent calls the merge tool, and the combined file comes back seconds later — including per-file page ranges and async jobs for big batches. No upload sites, no scripts.
Combine Monthly Reports into Quarterly Summaries with Go
Automate report compilation by merging multiple PDF files into quarterly or annual summaries using Go. Learn how to create cover pages, skip existing covers, and build professional combined reports automatically.
Extract Invoice Pages from Bank Statements with PHP
Split combined PDF documents and extract specific page ranges using PHP and the Apdf Split API. Perfect for separating invoices from bank statements, extracting sections from vendor documents, or isolating specific pages.
Your code made the PDF.
Then it went dark.
Opened, read, re-read, dropped on page 4 — you never see any of it. Share the PDFs you generate through Apdf recipient links, and every signal becomes something you can act on: ping Slack, update the CRM, let an agent follow up. Same account, same API token, one more call.
API · Webhook · MCP