CustomJS provides robust PDF merging capabilities that allow you to combine multiple PDF documents into a single file within your Make.com workflows. This is essential for document consolidation, report compilation, and automated document processing.
The PDF merging module can combine PDFs from various sources including:
// Basic merge of two PDF documents
const mergeConfig = {
pdfs: [
{
source: input.pdf1, // Base64 or binary data
name: 'document1.pdf',
pages: 'all' // or specific range like '1-5'
},
{
source: input.pdf2,
name: 'document2.pdf',
pages: 'all'
}
],
outputFilename: `merged-${Date.now()}.pdf`,
preserveBookmarks: true,
addPageNumbers: false
};
return {
mergeConfig,
success: true,
timestamp: new Date().toISOString()
};
// Merge multiple PDFs with custom ordering
const documents = input.documents || [];
const mergeConfig = {
pdfs: documents.map((doc, index) => ({
source: doc.content, // PDF content (base64 or binary)
name: doc.filename || `document-${index + 1}.pdf`,
pages: doc.pageRange || 'all',
order: doc.order || index,
metadata: {
title: doc.title,
author: doc.author,
subject: doc.subject
}
})).sort((a, b) => a.order - b.order), // Sort by order
outputFilename: input.outputName || `merged-documents-${new Date().toISOString().split('T')[0]}.pdf`,
// Merge options
preserveBookmarks: input.preserveBookmarks !== false,
addPageNumbers: input.addPageNumbers || false,
addTableOfContents: input.addTableOfContents || false,
// Output metadata
metadata: {
title: input.mergedTitle || 'Merged Document',
author: input.author || 'CustomJS Automation',
subject: input.subject || 'Automatically merged PDF',
creator: 'CustomJS PDF Merger',
creationDate: new Date().toISOString()
}
};
return mergeConfig;
// Merge specific pages from multiple documents
function createAdvancedMergeConfig(input) {
const { sources, ranges, options = {} } = input;
const pdfs = sources.map((source, index) => {
const range = ranges[index] || 'all';
return {
source: source.content,
name: source.filename,
pages: parsePageRange(range),
insertBlankPageAfter: source.insertBlankPageAfter || false,
rotate: source.rotation || 0, // 0, 90, 180, 270
scale: source.scale || 1.0
};
});
return {
pdfs,
outputFilename: options.filename || 'merged.pdf',
preserveBookmarks: options.preserveBookmarks !== false,
addPageNumbers: options.addPageNumbers || false,
pageNumberFormat: options.pageNumberFormat || 'Page {current} of {total}',
pageNumberPosition: options.pageNumberPosition || 'bottom-center'
};
}
function parsePageRange(range) {
if (range === 'all') return 'all';
// Handle ranges like "1-5", "1,3,5-7", "2-"
const parts = range.split(',').map(part => part.trim());
const parsedRanges = [];
for (const part of parts) {
if (part.includes('-')) {
const [start, end] = part.split('-').map(p => p.trim());
parsedRanges.push({
start: parseInt(start) || 1,
end: end ? parseInt(end) : 'end'
});
} else {
const pageNum = parseInt(part);
if (pageNum) {
parsedRanges.push({ start: pageNum, end: pageNum });
}
}
}
return parsedRanges;
}
// Usage
const mergeConfig = createAdvancedMergeConfig(input);
return mergeConfig;
// Merge PDFs based on conditions and rules
function conditionalMerge(input) {
const { documents, rules, options = {} } = input;
// Filter documents based on rules
const eligibleDocs = documents.filter(doc => {
// Check file size limits
if (rules.maxFileSize && doc.size > rules.maxFileSize) {
return false;
}
// Check document type/category
if (rules.allowedCategories && !rules.allowedCategories.includes(doc.category)) {
return false;
}
// Check date range
if (rules.dateRange) {
const docDate = new Date(doc.createdAt);
const startDate = new Date(rules.dateRange.start);
const endDate = new Date(rules.dateRange.end);
if (docDate < startDate || docDate > endDate) {
return false;
}
}
return true;
});
// Sort documents based on criteria
const sortedDocs = eligibleDocs.sort((a, b) => {
switch (rules.sortBy) {
case 'date':
return new Date(a.createdAt) - new Date(b.createdAt);
case 'name':
return a.filename.localeCompare(b.filename);
case 'size':
return a.size - b.size;
case 'priority':
return (b.priority || 0) - (a.priority || 0);
default:
return 0;
}
});
// Limit number of documents if specified
const finalDocs = rules.maxDocuments ?
sortedDocs.slice(0, rules.maxDocuments) :
sortedDocs;
if (finalDocs.length === 0) {
return {
error: 'No documents meet the merge criteria',
criteria: rules,
totalDocuments: documents.length
};
}
const mergeConfig = {
pdfs: finalDocs.map((doc, index) => ({
source: doc.content,
name: doc.filename,
pages: doc.pageRange || 'all',
metadata: {
originalIndex: documents.indexOf(doc),
category: doc.category,
priority: doc.priority
}
})),
outputFilename: options.filename || `filtered-merge-${Date.now()}.pdf`,
// Add summary page if requested
addSummaryPage: options.addSummaryPage || false,
summaryContent: options.addSummaryPage ? generateSummaryPage(finalDocs, rules) : null,
metadata: {
title: `Merged Document - ${finalDocs.length} files`,
subject: `Merged based on criteria: ${JSON.stringify(rules)}`,
keywords: finalDocs.map(doc => doc.category).filter(Boolean).join(', ')
}
};
return {
mergeConfig,
summary: {
totalInput: documents.length,
filtered: finalDocs.length,
excluded: documents.length - finalDocs.length,
criteria: rules
}
};
}
function generateSummaryPage(documents, rules) {
return `
<div style="font-family: Arial, sans-serif; padding: 40px;">
<h1>Document Merge Summary</h1>
<p><strong>Generated:</strong> ${new Date().toLocaleString()}</p>
<p><strong>Total Documents:</strong> ${documents.length}</p>
<h2>Merge Criteria</h2>
<ul>
${rules.maxFileSize ? `<li>Max file size: ${rules.maxFileSize} bytes</li>` : ''}
${rules.allowedCategories ? `<li>Categories: ${rules.allowedCategories.join(', ')}</li>` : ''}
${rules.dateRange ? `<li>Date range: ${rules.dateRange.start} to ${rules.dateRange.end}</li>` : ''}
${rules.sortBy ? `<li>Sorted by: ${rules.sortBy}</li>` : ''}
</ul>
<h2>Included Documents</h2>
<table style="width: 100%; border-collapse: collapse;">
<thead>
<tr style="background: #f0f0f0;">
<th style="border: 1px solid #ccc; padding: 8px;">Filename</th>
<th style="border: 1px solid #ccc; padding: 8px;">Category</th>
<th style="border: 1px solid #ccc; padding: 8px;">Size</th>
<th style="border: 1px solid #ccc; padding: 8px;">Date</th>
</tr>
</thead>
<tbody>
${documents.map(doc => `
<tr>
<td style="border: 1px solid #ccc; padding: 8px;">${doc.filename}</td>
<td style="border: 1px solid #ccc; padding: 8px;">${doc.category || 'N/A'}</td>
<td style="border: 1px solid #ccc; padding: 8px;">${formatFileSize(doc.size)}</td>
<td style="border: 1px solid #ccc; padding: 8px;">${new Date(doc.createdAt).toLocaleDateString()}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
`;
}
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// Comprehensive validation for PDF merge inputs
function validateMergeInputs(input) {
const errors = [];
const warnings = [];
// Check if PDFs array exists and is valid
if (!input.pdfs || !Array.isArray(input.pdfs)) {
errors.push('PDFs array is required and must be an array');
return { errors, warnings };
}
if (input.pdfs.length === 0) {
errors.push('At least one PDF is required for merging');
return { errors, warnings };
}
if (input.pdfs.length === 1) {
warnings.push('Only one PDF provided - merge will create a copy');
}
// Validate each PDF
input.pdfs.forEach((pdf, index) => {
const pdfErrors = [];
// Check source
if (!pdf.source) {
pdfErrors.push(`PDF ${index + 1}: source is required`);
}
// Validate page ranges
if (pdf.pages && pdf.pages !== 'all') {
if (!isValidPageRange(pdf.pages)) {
pdfErrors.push(`PDF ${index + 1}: invalid page range format`);
}
}
// Check rotation values
if (pdf.rotate && ![0, 90, 180, 270].includes(pdf.rotate)) {
pdfErrors.push(`PDF ${index + 1}: rotation must be 0, 90, 180, or 270 degrees`);
}
// Check scale values
if (pdf.scale && (pdf.scale <= 0 || pdf.scale > 5)) {
pdfErrors.push(`PDF ${index + 1}: scale must be between 0 and 5`);
}
errors.push(...pdfErrors);
});
// Validate output filename
if (input.outputFilename && !/^[^<>:"/\\|?*]+\.pdf$/i.test(input.outputFilename)) {
errors.push('Invalid output filename format');
}
// Check for potential memory issues
if (input.pdfs.length > 50) {
warnings.push('Merging more than 50 PDFs may cause performance issues');
}
return { errors, warnings };
}
function isValidPageRange(range) {
// Validate page range formats like "1-5", "1,3,5-7", "2-"
const rangePattern = /^(\d+(-\d*)?)(,\s*\d+(-\d*)?)*$/;
return rangePattern.test(range.replace(/\s/g, ''));
}
// Main merge function with validation
function performMergeWithValidation(input) {
const validation = validateMergeInputs(input);
if (validation.errors.length > 0) {
return {
success: false,
errors: validation.errors,
warnings: validation.warnings
};
}
try {
const mergeConfig = buildMergeConfig(input);
return {
success: true,
mergeConfig,
warnings: validation.warnings,
summary: {
totalPDFs: input.pdfs.length,
outputFilename: mergeConfig.outputFilename,
estimatedPages: estimatePageCount(input.pdfs)
}
};
} catch (error) {
return {
success: false,
error: 'Failed to create merge configuration',
details: error.message,
timestamp: new Date().toISOString()
};
}
}
function estimatePageCount(pdfs) {
// Rough estimation - in real implementation, this would analyze actual PDFs
return pdfs.reduce((total, pdf) => {
if (pdf.pages === 'all') {
return total + 10; // Assume 10 pages if not specified
} else if (typeof pdf.pages === 'string') {
// Parse page ranges to estimate count
const matches = pdf.pages.match(/\d+/g);
return total + (matches ? matches.length * 2 : 5);
}
return total + 5;
}, 0);
}
// Optimize merge for large files and many documents
function optimizedMerge(input) {
const { pdfs, options = {} } = input;
// Sort PDFs by size (merge smaller ones first)
const sortedPdfs = [...pdfs].sort((a, b) => (a.estimatedSize || 0) - (b.estimatedSize || 0));
// Batch processing for large numbers of PDFs
const batchSize = options.batchSize || 10;
const batches = [];
for (let i = 0; i < sortedPdfs.length; i += batchSize) {
batches.push(sortedPdfs.slice(i, i + batchSize));
}
const mergeConfig = {
processingMode: 'batched',
batches: batches.map((batch, index) => ({
batchId: index,
pdfs: batch,
tempFilename: `batch-${index}-temp.pdf`
})),
finalMerge: {
sources: batches.map((_, index) => `batch-${index}-temp.pdf`),
outputFilename: options.outputFilename || 'final-merged.pdf',
cleanupTempFiles: true
},
optimization: {
compressImages: options.compressImages !== false,
removeUnusedObjects: options.removeUnusedObjects !== false,
optimizeForWeb: options.optimizeForWeb || false,
maxMemoryUsage: options.maxMemoryUsage || '512MB'
}
};
return mergeConfig;
}
// Configure merge with quality and compression settings
function configureMergeQuality(input) {
const { pdfs, quality = 'balanced' } = input;
const qualityPresets = {
maximum: {
imageCompression: false,
imageQuality: 100,
preserveAnnotations: true,
preserveFormFields: true,
preserveBookmarks: true,
optimizeForPrint: true
},
balanced: {
imageCompression: true,
imageQuality: 85,
preserveAnnotations: true,
preserveFormFields: true,
preserveBookmarks: true,
optimizeForPrint: false
},
compressed: {
imageCompression: true,
imageQuality: 70,
preserveAnnotations: false,
preserveFormFields: false,
preserveBookmarks: true,
optimizeForPrint: false,
removeMetadata: true
}
};
const settings = qualityPresets[quality] || qualityPresets.balanced;
return {
pdfs,
outputSettings: {
...settings,
outputFilename: input.outputFilename || `merged-${quality}.pdf`
},
metadata: {
compressionLevel: quality,
processedAt: new Date().toISOString(),
originalFileCount: pdfs.length
}
};
}
// Assemble documents in a specific order for business workflows
function assembleBusinessDocument(input) {
const { coverPage, mainContent, appendices, signatures } = input;
const documentStructure = [
// Cover page
coverPage && {
source: coverPage.content,
name: 'cover.pdf',
pages: 'all',
addPageBreak: true
},
// Table of contents (if provided)
input.tableOfContents && {
source: input.tableOfContents.content,
name: 'toc.pdf',
pages: 'all',
addPageBreak: true
},
// Main content sections
...mainContent.map((section, index) => ({
source: section.content,
name: `section-${index + 1}.pdf`,
pages: section.pageRange || 'all',
addPageBreak: index < mainContent.length - 1,
bookmarkTitle: section.title
})),
// Appendices
...appendices.map((appendix, index) => ({
source: appendix.content,
name: `appendix-${String.fromCharCode(65 + index)}.pdf`,
pages: 'all',
addPageBreak: true,
bookmarkTitle: `Appendix ${String.fromCharCode(65 + index)}: ${appendix.title}`
})),
// Signature pages
signatures && {
source: signatures.content,
name: 'signatures.pdf',
pages: 'all'
}
].filter(Boolean);
return {
pdfs: documentStructure,
outputFilename: `${input.documentTitle || 'business-document'}-${Date.now()}.pdf`,
addBookmarks: true,
addPageNumbers: true,
pageNumberStartsAt: coverPage ? 2 : 1, // Skip cover page for numbering
metadata: {
title: input.documentTitle,
author: input.author,
subject: input.subject,
keywords: input.keywords
}
};
}
PDF merging with CustomJS enables powerful document automation workflows, from simple file combination to complex document assembly processes in your Make.com scenarios.