Doc API
Back to blog

How to Generate Invoices with DocAPI

·7 min read

Invoices are one of the most common PDF use cases. Every business needs them, and doing them manually doesn't scale.

This guide shows you how to generate professional PDF invoices with DocAPI using HTML templates.


The Basic Approach

  1. Create an HTML template for your invoice
  2. Fill in the data (customer info, line items, totals)
  3. Send to DocAPI
  4. Get back a PDF

That's it. No wkhtmltopdf, no Puppeteer servers, no dependencies.


Simple Invoice Example

Let's start with a basic invoice:

const response = await fetch("https://api.docapi.co/v1/pdf", {
  method: "POST",
  headers: {
    "x-api-key": "your-api-key",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    html: `
      <html>
        <head>
          <style>
            body { font-family: Arial, sans-serif; padding: 40px; }
            .header { display: flex; justify-content: space-between; margin-bottom: 40px; }
            .invoice-title { font-size: 32px; color: #333; }
            .invoice-number { color: #666; }
            table { width: 100%; border-collapse: collapse; margin: 20px 0; }
            th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
            th { background: #f8f9fa; }
            .total { font-size: 18px; font-weight: bold; }
          </style>
        </head>
        <body>
          <div class="header">
            <div>
              <div class="invoice-title">INVOICE</div>
              <div class="invoice-number">#INV-001</div>
            </div>
            <div>
              <strong>Your Company</strong><br>
              123 Main St<br>
              New York, NY 10001
            </div>
          </div>
 
          <div style="margin-bottom: 30px;">
            <strong>Bill To:</strong><br>
            Acme Corp<br>
            456 Oak Ave<br>
            Los Angeles, CA 90001
          </div>
 
          <table>
            <thead>
              <tr>
                <th>Description</th>
                <th>Qty</th>
                <th>Price</th>
                <th>Total</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td>Web Development</td>
                <td>40</td>
                <td>$100</td>
                <td>$4,000</td>
              </tr>
              <tr>
                <td>Design Services</td>
                <td>20</td>
                <td>$80</td>
                <td>$1,600</td>
              </tr>
            </tbody>
          </table>
 
          <div style="text-align: right;">
            <p>Subtotal: $5,600</p>
            <p>Tax (10%): $560</p>
            <p class="total">Total: $6,160</p>
          </div>
        </body>
      </html>
    `,
    options: {
      format: "A4",
      margin: { top: "20mm", bottom: "20mm", left: "20mm", right: "20mm" },
    },
  }),
});
 
const pdfBuffer = await response.arrayBuffer();

Building a Reusable Template

Hardcoding HTML isn't practical. Here's how to build a reusable invoice generator:

function generateInvoiceHTML(invoice) {
  const lineItems = invoice.items
    .map(
      (item) => `
      <tr>
        <td>${item.description}</td>
        <td>${item.quantity}</td>
        <td>$${item.price.toFixed(2)}</td>
        <td>$${(item.quantity * item.price).toFixed(2)}</td>
      </tr>
    `
    )
    .join("");
 
  const subtotal = invoice.items.reduce(
    (sum, item) => sum + item.quantity * item.price,
    0
  );
  const tax = subtotal * (invoice.taxRate || 0);
  const total = subtotal + tax;
 
  return `
    <!DOCTYPE html>
    <html>
      <head>
        <style>
          * { margin: 0; padding: 0; box-sizing: border-box; }
          body {
            font-family: 'Helvetica Neue', Arial, sans-serif;
            color: #333;
            line-height: 1.6;
          }
          .invoice { padding: 40px; }
          .header {
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
            margin-bottom: 50px;
            padding-bottom: 20px;
            border-bottom: 2px solid #eee;
          }
          .logo { font-size: 24px; font-weight: bold; color: #2563eb; }
          .invoice-info { text-align: right; }
          .invoice-number { font-size: 28px; font-weight: bold; }
          .invoice-date { color: #666; margin-top: 5px; }
          .addresses {
            display: flex;
            justify-content: space-between;
            margin-bottom: 40px;
          }
          .address-block { width: 45%; }
          .address-label {
            font-size: 12px;
            text-transform: uppercase;
            color: #666;
            margin-bottom: 10px;
          }
          table {
            width: 100%;
            border-collapse: collapse;
            margin-bottom: 30px;
          }
          th {
            background: #f8fafc;
            padding: 15px;
            text-align: left;
            font-size: 12px;
            text-transform: uppercase;
            color: #666;
            border-bottom: 2px solid #e2e8f0;
          }
          td {
            padding: 15px;
            border-bottom: 1px solid #e2e8f0;
          }
          .totals {
            width: 300px;
            margin-left: auto;
          }
          .totals-row {
            display: flex;
            justify-content: space-between;
            padding: 10px 0;
          }
          .totals-row.total {
            font-size: 20px;
            font-weight: bold;
            border-top: 2px solid #333;
            margin-top: 10px;
            padding-top: 15px;
          }
          .footer {
            margin-top: 60px;
            padding-top: 20px;
            border-top: 1px solid #eee;
            text-align: center;
            color: #666;
            font-size: 14px;
          }
        </style>
      </head>
      <body>
        <div class="invoice">
          <div class="header">
            <div class="logo">${invoice.company.name}</div>
            <div class="invoice-info">
              <div class="invoice-number">#${invoice.number}</div>
              <div class="invoice-date">Date: ${invoice.date}</div>
              <div class="invoice-date">Due: ${invoice.dueDate}</div>
            </div>
          </div>
 
          <div class="addresses">
            <div class="address-block">
              <div class="address-label">From</div>
              <strong>${invoice.company.name}</strong><br>
              ${invoice.company.address}<br>
              ${invoice.company.city}, ${invoice.company.state} ${invoice.company.zip}
            </div>
            <div class="address-block">
              <div class="address-label">Bill To</div>
              <strong>${invoice.customer.name}</strong><br>
              ${invoice.customer.address}<br>
              ${invoice.customer.city}, ${invoice.customer.state} ${invoice.customer.zip}
            </div>
          </div>
 
          <table>
            <thead>
              <tr>
                <th>Description</th>
                <th>Qty</th>
                <th>Rate</th>
                <th style="text-align: right;">Amount</th>
              </tr>
            </thead>
            <tbody>
              ${lineItems}
            </tbody>
          </table>
 
          <div class="totals">
            <div class="totals-row">
              <span>Subtotal</span>
              <span>$${subtotal.toFixed(2)}</span>
            </div>
            ${
              invoice.taxRate
                ? `
            <div class="totals-row">
              <span>Tax (${(invoice.taxRate * 100).toFixed(0)}%)</span>
              <span>$${tax.toFixed(2)}</span>
            </div>
            `
                : ""
            }
            <div class="totals-row total">
              <span>Total</span>
              <span>$${total.toFixed(2)}</span>
            </div>
          </div>
 
          ${invoice.notes ? `<div class="footer">${invoice.notes}</div>` : ""}
        </div>
      </body>
    </html>
  `;
}

Using the Template

const invoice = {
  number: "INV-2024-001",
  date: "Dec 22, 2024",
  dueDate: "Jan 21, 2025",
  taxRate: 0.1,
  company: {
    name: "Your Company",
    address: "123 Main Street",
    city: "New York",
    state: "NY",
    zip: "10001",
  },
  customer: {
    name: "Acme Corporation",
    address: "456 Oak Avenue",
    city: "Los Angeles",
    state: "CA",
    zip: "90001",
  },
  items: [
    { description: "Website Development", quantity: 1, price: 5000 },
    { description: "Monthly Hosting", quantity: 12, price: 50 },
    { description: "SSL Certificate", quantity: 1, price: 100 },
  ],
  notes: "Payment due within 30 days. Thank you for your business!",
};
 
const html = generateInvoiceHTML(invoice);
 
const response = await fetch("https://api.docapi.co/v1/pdf", {
  method: "POST",
  headers: {
    "x-api-key": process.env.DOCAPI_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    html,
    options: { format: "A4" },
  }),
});
 
const pdf = await response.arrayBuffer();

Express.js Endpoint

Here's a complete API endpoint for generating invoices:

import express from "express";
 
const app = express();
app.use(express.json());
 
app.post("/api/invoices/:id/pdf", async (req, res) => {
  try {
    // Fetch invoice from your database
    const invoice = await getInvoiceById(req.params.id);
 
    if (!invoice) {
      return res.status(404).json({ error: "Invoice not found" });
    }
 
    // Generate HTML
    const html = generateInvoiceHTML(invoice);
 
    // Generate PDF
    const response = await fetch("https://api.docapi.co/v1/pdf", {
      method: "POST",
      headers: {
        "x-api-key": process.env.DOCAPI_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        html,
        options: { format: "A4" },
      }),
    });
 
    if (!response.ok) {
      throw new Error("PDF generation failed");
    }
 
    const pdfBuffer = await response.arrayBuffer();
 
    // Send PDF
    res.setHeader("Content-Type", "application/pdf");
    res.setHeader(
      "Content-Disposition",
      `attachment; filename="invoice-${invoice.number}.pdf"`
    );
    res.send(Buffer.from(pdfBuffer));
  } catch (error) {
    console.error("Invoice PDF error:", error);
    res.status(500).json({ error: "Failed to generate invoice" });
  }
});

Adding a Logo

Include your logo using a base64 data URL or hosted image:

const logoHTML = `
  <img
    src="https://your-domain.com/logo.png"
    alt="Company Logo"
    style="height: 50px; margin-bottom: 20px;"
  />
`;

Or embed it directly:

const logoBase64 = "data:image/png;base64,iVBORw0KGgo...";
const logoHTML = `<img src="${logoBase64}" style="height: 50px;" />`;

Multi-Page Invoices

Long invoices automatically flow to multiple pages. Control page breaks with CSS:

/* Keep item rows together */
tr {
  page-break-inside: avoid;
}
 
/* Force a page break before totals if needed */
.totals {
  page-break-before: auto;
}
 
/* Repeat table headers on each page */
thead {
  display: table-header-group;
}

Adding Headers and Footers

DocAPI supports page headers and footers:

const response = await fetch("https://api.docapi.co/v1/pdf", {
  method: "POST",
  headers: {
    "x-api-key": process.env.DOCAPI_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    html: invoiceHTML,
    options: {
      format: "A4",
      displayHeaderFooter: true,
      headerTemplate: `
        <div style="font-size: 10px; color: #666; width: 100%; text-align: center;">
          Your Company - Invoice
        </div>
      `,
      footerTemplate: `
        <div style="font-size: 10px; color: #666; width: 100%; text-align: center;">
          Page <span class="pageNumber"></span> of <span class="totalPages"></span>
        </div>
      `,
    },
  }),
});

Best Practices

1. Use Web-Safe Fonts

Stick to fonts that render consistently:

  • Arial, Helvetica
  • Georgia, Times New Roman
  • Courier New

Or include Google Fonts via CDN.

2. Set Explicit Dimensions

Browsers and PDF renderers can differ. Be explicit:

.invoice {
  width: 210mm; /* A4 width */
  min-height: 297mm; /* A4 height */
}

3. Test Print Styles

Use browser print preview to debug before sending to DocAPI.

4. Handle Currency Properly

Use Intl.NumberFormat for consistent currency formatting:

const formatCurrency = (amount) =>
  new Intl.NumberFormat("en-US", {
    style: "currency",
    currency: "USD",
  }).format(amount);

Common Customizations

Different Paper Sizes

// US Letter
options: { format: "Letter" }
 
// Custom size
options: { width: "8.5in", height: "11in" }

Landscape Orientation

options: { format: "A4", landscape: true }

Background Colors

options: { printBackground: true }

Error Handling

Always handle API errors gracefully:

async function generateInvoicePDF(invoice) {
  try {
    const response = await fetch("https://api.docapi.co/v1/pdf", {
      method: "POST",
      headers: {
        "x-api-key": process.env.DOCAPI_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        html: generateInvoiceHTML(invoice),
        options: { format: "A4" },
      }),
    });
 
    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.message || "PDF generation failed");
    }
 
    return await response.arrayBuffer();
  } catch (error) {
    console.error("Invoice generation failed:", error);
    throw error;
  }
}

Next Steps

  1. Get your API key at docapi.co (100 free PDFs/month)
  2. Copy the template from this guide
  3. Customize the styling to match your brand
  4. Connect to your data (database, Stripe, etc.)

Need Help?

How to Generate Invoices with DocAPI | Doc API Blog | Doc API