Developer Experience

One API Call. PDF + Excel.

Stop juggling Puppeteer, PDFKit, and ExcelJS. Generate documents in minutes, not weeks.

Delete 90% of your document code

Compare real-world implementations. No hidden helper functions or abstractions.

Traditional Approach
~162 lines
Multiple services, template files, error handling, and configuration
// invoice-pdf-service.ts - Puppeteer + Handlebars
// Full implementation for generating PDF invoices

import puppeteer, { Browser } from 'puppeteer';
import Handlebars from 'handlebars';
import * as fs from 'fs';
import * as path from 'path';

// ============================================
// Type Definitions
// ============================================
interface InvoiceItem {
  description: string;
  quantity: number;
  unitPrice: number;
  total: number;
}

interface InvoiceData {
  invoiceNumber: string;
  customerName: string;
  customerEmail: string;
  companyName: string;
  companyAddress: string;
  items: InvoiceItem[];
  subtotal: number;
  tax: number;
  total: number;
  dueDate: string;
  issueDate: string;
}

// ============================================
// Puppeteer Browser Management
// ============================================
let browser: Browser | null = null;

async function getBrowser(): Promise<Browser> {
  if (!browser) {
    browser = await puppeteer.launch({
      headless: true,
      args: [
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-dev-shm-usage',
        '--disable-gpu',
      ],
    });
  }
  return browser;
}

// ============================================
// Template Loading & Compilation
// ============================================
const pdfTemplatePath = path.join(__dirname, 'templates', 'invoice-pdf.hbs');
let pdfTemplate: Handlebars.TemplateDelegate;

function loadTemplates() {
  pdfTemplate = Handlebars.compile(
    fs.readFileSync(pdfTemplatePath, 'utf-8')
  );
}

// Register Handlebars helpers for formatting
Handlebars.registerHelper('formatCurrency', (value: number) => {
  return new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD',
  }).format(value);
});

Handlebars.registerHelper('formatDate', (date: string) => {
  return new Date(date).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric',
  });
});

Handlebars.registerHelper('multiply', (a: number, b: number) => a * b);

// Load templates on module init
loadTemplates();

// ============================================
// PDF Generation with Puppeteer
// ============================================
export async function generateInvoicePDF(data: InvoiceData): Promise<Buffer> {
  const browser = await getBrowser();
  const page = await browser.newPage();

  try {
    // Render HTML from Handlebars template
    const html = pdfTemplate(data);

    // Set viewport for consistent rendering
    await page.setViewport({ width: 794, height: 1123 }); // A4 size

    // Set content and wait for resources
    await page.setContent(html, {
      waitUntil: 'networkidle0',
      timeout: 30000,
    });

    // Add custom styles for print
    await page.addStyleTag({
      content: `
        @page { margin: 0; }
        body { margin: 20mm; }
      `,
    });

    // Generate PDF buffer
    const pdfBuffer = await page.pdf({
      format: 'A4',
      printBackground: true,
      preferCSSPageSize: true,
      margin: {
        top: '20mm',
        right: '20mm',
        bottom: '20mm',
        left: '20mm',
      },
    });

    return Buffer.from(pdfBuffer);
  } catch (error) {
    throw new Error(`PDF generation failed: ${(error as Error).message}`);
  } finally {
    await page.close();
  }
}

// ============================================
// Save PDF to file or upload to S3
// ============================================
export async function saveInvoicePDF(data: InvoiceData): Promise<string> {
  try {
    if (!data.invoiceNumber) {
      throw new Error('Missing required invoice data');
    }

    console.log(`Generating PDF for invoice ${data.invoiceNumber}...`);
    const pdfBuffer = await generateInvoicePDF(data);

    // Save to file or upload to S3
    const filename = `invoice-${data.invoiceNumber}.pdf`;
    fs.writeFileSync(path.join(__dirname, 'output', filename), pdfBuffer);

    console.log(`Invoice PDF saved: ${filename}`);
    return filename;
  } catch (error) {
    console.error('Failed to generate invoice:', error);
    throw new Error(`PDF generation failed: ${(error as Error).message}`);
  }
}

// Cleanup on process exit
process.on('exit', async () => {
  if (browser) await browser.close();
});

+ Not shown: Handlebars template file (~100 lines), environment setup, dependency management, browser/service initialization, and production deployment configuration.

The RenderDoc Way
~45 lines
One API call. PDF or Excel from the same template.
// invoice-pdf-service.ts - RenderDoc
// Generate invoice PDF with one API call

// Types
interface InvoiceData {
  invoiceNumber: string;
  customerName: string;
  items: Array<{
    description: string;
    quantity: number;
    unitPrice: number;
  }>;
  subtotal: number;
  tax: number;
  total: number;
  dueDate: string;
}

// Generate PDF - that's it!
export async function generateInvoicePDF(data: InvoiceData) {
  const response = await fetch('https://api.renderdoc.dev/api/v1/documents/generate', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.RENDERDOC_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      templateId: 'invoice-template',
      format: 'pdf',
      variables: data,
    }),
  });

  if (!response.ok) {
    throw new Error(`Failed to generate PDF: ${response.statusText}`);
  }

  const { jobId, downloadUrl } = await response.json();
  return { jobId, downloadUrl };
}

// Variables like formatting, calculations, and
// conditional logic are handled in the template designer.
// No Handlebars helpers. No Puppeteer. No DocRaptor.
// Just your data and one API call.
Lines of code:
162+ lines45 lines
External services:
2-3 services1 API
Template files:
1-2 files0 files
Code reduction:72%

Get Started in 5 Minutes

Three steps to generating your first document.

1
Get API Key
Sign up and grab your API key from the dashboard. Takes 30 seconds.
2
Design Templates
Use the visual editor or generate templates with AI. Define your variables.
3
Generate via API
POST your data to our API. We generate the document and return a download URL.
Sample API Call
curl -X POST https://api.renderdoc.dev/api/v1/documents/generate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "templateId": "invoice-template",
    "format": "pdf",
    "variables": {
      "invoiceNumber": "INV-001",
      "items": [{ "name": "Product", "quantity": 2, "price": 49.99 }],
      "total": 99.98
    }
  }'

# Response:
# {
#   "jobId": "doc_abc123",
#   "status": "completed",
#   "downloadUrl": "https://..."
# }

Built for Developers

Everything you need for a smooth integration.

OpenAPI Spec
Full API documentation with Swagger UI
Async with Webhooks
Get notified when documents are ready
Official SDKs
Node.js, Python, Go (coming soon)
API Playground
Test API calls directly in the dashboard

Multiple Output Formats

Same template, different formats. Choose what works for your use case.

PDF
High-quality PDFs for invoices, reports, certificates, and contracts.
Excel
Excel files with formulas, formatting, and multiple sheets.
AI-Native

Generate Templates with AI

Feed our JSON schema to ChatGPT, Claude, or Gemini. Generate complex templates with calculated fields, conditionals, and loops in seconds.

Describe Your Template

"Create an invoice PDF with line items, tax calculation, and company logo"

AI Generates JSON

LLM outputs valid RenderDoc template schema with all components

Import & Refine

Paste JSON into the designer. Tweak visually. Done.

Ready to ship?

20 free documents per month. Full API access. No credit card required.