Generating Documents
This guide covers everything you need to know about generating documents with RenderDoc.
Overview
RenderDoc generates documents by:
- Taking a template (PDF or Excel)
- Substituting variables with your data
- Rendering the final document
- Providing a signed download URL
Quick Start
Generate a PDF
import { RenderDoc } from '@renderdoc/sdk';
const client = new RenderDoc({ apiKey: process.env.RENDERDOC_API_KEY });
const result = await client.documents.generate({
templateId: 'invoice-template',
format: 'pdf',
variables: {
invoiceNumber: 'INV-2025-001',
customerName: 'Acme Corporation',
amount: 1250.00
}
});
console.log('Download URL:', result.downloadUrl);
Generate an Excel File
const result = await client.documents.generate({
templateId: 'monthly-report',
format: 'xlsx',
variables: {
reportMonth: 'January 2025',
data: [
{ category: 'Sales', amount: 50000 },
{ category: 'Expenses', amount: 30000 },
{ category: 'Profit', amount: 20000 }
]
}
});
Variable Types
RenderDoc supports various variable types in templates:
Simple Variables
variables: {
customerName: 'John Doe',
invoiceNumber: 'INV-001',
amount: 99.99,
isActive: true
}
Use in template: {{customerName}}, {{invoiceNumber}}, {{amount}}
Arrays (for tables/lists)
variables: {
lineItems: [
{ description: 'Widget', quantity: 2, price: 25.00 },
{ description: 'Gadget', quantity: 1, price: 49.99 }
]
}
Use in template with {{#each lineItems}}...{{/each}}
Nested Objects
variables: {
customer: {
name: 'Acme Corp',
address: {
street: '123 Main St',
city: 'New York',
zip: '10001'
}
}
}
Use in template: {{customer.name}}, {{customer.address.city}}
Dates
variables: {
invoiceDate: '2025-01-15',
dueDate: new Date('2025-02-15').toISOString()
}
Output Formats
PDF
Best for:
- Invoices and receipts
- Contracts and agreements
- Reports and statements
- Any printable document
{ format: 'pdf' }
Excel (XLSX)
Best for:
- Data exports
- Financial reports
- Spreadsheet templates
- Data that users will edit
{ format: 'xlsx' }
Batch Generation
Generate multiple documents efficiently:
const result = await client.documents.generateBatch({
templateId: 'invoice-template',
format: 'pdf',
documents: [
{
variables: { invoiceNumber: 'INV-001', customer: 'Acme Corp' },
filename: 'invoice-acme'
},
{
variables: { invoiceNumber: 'INV-002', customer: 'Beta Inc' },
filename: 'invoice-beta'
},
{
variables: { invoiceNumber: 'INV-003', customer: 'Gamma Ltd' },
filename: 'invoice-gamma'
}
]
});
console.log('Batch ID:', result.batchId);
console.log('Total documents:', result.totalDocuments);
Polling for Batch Completion
async function waitForBatch(client, batchId) {
while (true) {
const status = await client.documents.getBatch(batchId);
if (status.status === 'completed') {
return status.documents;
}
if (status.status === 'failed') {
throw new Error('Batch failed');
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
const documents = await waitForBatch(client, result.batchId);
for (const doc of documents) {
console.log(`${doc.jobId}: ${doc.downloadUrl}`);
}
Async Generation with Webhooks
For long-running generations, use webhooks instead of polling:
1. Create Webhook Subscription
const webhook = await client.webhooks.create({
url: 'https://your-server.com/webhooks/renderdoc',
events: ['document.generated', 'document.failed']
});
2. Generate Document
const result = await client.documents.generate({
templateId: 'complex-report',
format: 'pdf',
variables: { ... }
});
// Don't wait - webhook will notify when ready
console.log('Job submitted:', result.jobId);
3. Handle Webhook
app.post('/webhooks/renderdoc', (req, res) => {
const event = req.body;
if (event.type === 'document.generated') {
console.log('Document ready:', event.data.downloadUrl);
// Process the document...
}
if (event.type === 'document.failed') {
console.error('Generation failed:', event.data.error);
}
res.status(200).send('OK');
});
Downloading Documents
Direct Download
The downloadUrl is a signed URL valid for a limited time:
const result = await client.documents.generate({ ... });
// Download using fetch
const response = await fetch(result.downloadUrl);
const buffer = await response.arrayBuffer();
// Save to file
fs.writeFileSync('document.pdf', Buffer.from(buffer));
Using SDK Helper
// Node.js SDK
const bytes = await client.documents.download(result.downloadUrl);
fs.writeFileSync('document.pdf', bytes);
# Python SDK
bytes_data = client.documents.download(result['downloadUrl'])
with open('document.pdf', 'wb') as f:
f.write(bytes_data)
Custom Filenames
Specify a custom filename (without extension):
const result = await client.documents.generate({
templateId: 'invoice-template',
format: 'pdf',
variables: { invoiceNumber: 'INV-001' },
filename: 'invoice-acme-corp-january-2025'
});
// Download URL will use this filename
// https://storage.renderdoc.dev/.../invoice-acme-corp-january-2025.pdf
Metadata
Attach custom metadata to track documents:
const result = await client.documents.generate({
templateId: 'invoice-template',
format: 'pdf',
variables: { ... },
metadata: {
customerId: 'cust_123',
orderId: 'ord_456',
department: 'sales'
}
});
Metadata is returned in job status and webhook events.
Error Handling
try {
const result = await client.documents.generate({
templateId: 'invalid-template',
format: 'pdf',
variables: {}
});
} catch (error) {
if (error.code === 'ERR_TMPL_001') {
console.error('Template not found');
} else if (error.code === 'ERR_VALID_002') {
console.error('Variable validation failed:', error.details);
} else if (error.code === 'ERR_QUOTA_008') {
console.error('Monthly document quota exceeded');
} else if (error.code === 'ERR_QUOTA_011') {
console.error('Insufficient document credits');
} else {
console.error('Unexpected error:', error.message);
}
}
See Error Codes for the complete error code reference.
Best Practices
1. Use Batch for Multiple Documents
Instead of making many individual requests:
// Bad: Many individual requests
for (const invoice of invoices) {
await client.documents.generate({ templateId: 'invoice', variables: invoice });
}
// Good: Single batch request
await client.documents.generateBatch({
templateId: 'invoice',
format: 'pdf',
documents: invoices.map(inv => ({ variables: inv }))
});
2. Use Webhooks for Production
Polling is fine for development, but use webhooks in production:
// Development: Polling
const result = await client.documents.generate({ ... });
while (result.status !== 'completed') {
await sleep(1000);
result = await client.documents.getJob(result.jobId);
}
// Production: Webhooks
await client.documents.generate({ ... });
// Handle completion in webhook endpoint
3. Handle URL Expiration
Download URLs expire. If you need long-term storage:
const result = await client.documents.generate({ ... });
// Download immediately and store in your own storage
const buffer = await client.documents.download(result.downloadUrl);
await uploadToS3(buffer, 'invoices/inv-001.pdf');
4. Validate Variables Before Generation
Check required variables before making API calls:
function validateInvoiceVariables(vars) {
const required = ['invoiceNumber', 'customerName', 'amount'];
const missing = required.filter(key => !vars[key]);
if (missing.length > 0) {
throw new Error(`Missing required variables: ${missing.join(', ')}`);
}
}
validateInvoiceVariables(variables);
const result = await client.documents.generate({ ... });