Skip to main content

Generating Documents

This guide covers everything you need to know about generating documents with RenderDoc.

Overview

RenderDoc generates documents by:

  1. Taking a template (PDF or Excel)
  2. Substituting variables with your data
  3. Rendering the final document
  4. 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({ ... });