Allscreenshots Docs
Guides

Generate PDFs from web pages

Convert web pages to PDF documents programmatically

Generate PDFs from web pages

Convert any web page to a PDF document with full styling, images, and formatting preserved.

Use cases

  • Invoice generation: Convert invoice pages to downloadable PDFs
  • Report exports: Generate PDF reports from dashboards
  • Documentation: Create offline documentation from web docs
  • Contracts: Generate signed agreement PDFs
  • Receipts: Create transaction receipts

Basic PDF generation

async function generatePdf(url) {
  const response = await fetch('https://api.allscreenshots.com/v1/screenshots', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.ALLSCREENSHOTS_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      url,
      format: 'pdf',
      fullPage: true,
      waitUntil: 'networkidle',
    }),
  });

  return response.blob();
}

// Usage
const pdf = await generatePdf('https://example.com/invoice/123');

Invoice generation workflow

Create an invoice template

Design a web page optimized for PDF output:

// pages/invoice/[id].jsx
export default function InvoicePage({ invoice }) {
  return (
    <div className="invoice" style={{
      width: '210mm', // A4 width
      minHeight: '297mm', // A4 height
      padding: '20mm',
      fontFamily: 'system-ui, sans-serif',
    }}>
      <header style={{ marginBottom: '40px' }}>
        <img src="/logo.png" alt="Company" style={{ height: '50px' }} />
        <div style={{ textAlign: 'right' }}>
          <h1>INVOICE</h1>
          <p>#{invoice.number}</p>
          <p>{formatDate(invoice.date)}</p>
        </div>
      </header>

      <section style={{ marginBottom: '40px' }}>
        <h2>Bill To:</h2>
        <p>{invoice.customer.name}</p>
        <p>{invoice.customer.address}</p>
        <p>{invoice.customer.email}</p>
      </section>

      <table style={{ width: '100%', borderCollapse: 'collapse' }}>
        <thead>
          <tr style={{ borderBottom: '2px solid #333' }}>
            <th style={{ textAlign: 'left', padding: '10px' }}>Description</th>
            <th style={{ textAlign: 'right', padding: '10px' }}>Qty</th>
            <th style={{ textAlign: 'right', padding: '10px' }}>Price</th>
            <th style={{ textAlign: 'right', padding: '10px' }}>Total</th>
          </tr>
        </thead>
        <tbody>
          {invoice.items.map(item => (
            <tr key={item.id} style={{ borderBottom: '1px solid #eee' }}>
              <td style={{ padding: '10px' }}>{item.description}</td>
              <td style={{ textAlign: 'right', padding: '10px' }}>{item.quantity}</td>
              <td style={{ textAlign: 'right', padding: '10px' }}>${item.price}</td>
              <td style={{ textAlign: 'right', padding: '10px' }}>${item.total}</td>
            </tr>
          ))}
        </tbody>
        <tfoot>
          <tr>
            <td colSpan="3" style={{ textAlign: 'right', padding: '10px', fontWeight: 'bold' }}>
              Total:
            </td>
            <td style={{ textAlign: 'right', padding: '10px', fontWeight: 'bold' }}>
              ${invoice.total}
            </td>
          </tr>
        </tfoot>
      </table>

      <footer style={{ marginTop: '60px', borderTop: '1px solid #eee', paddingTop: '20px' }}>
        <p>Payment due within 30 days</p>
        <p>Bank: {invoice.bankDetails}</p>
      </footer>
    </div>
  );
}

Generate the PDF

// API route: /api/invoices/[id]/pdf
export async function GET(request, { params }) {
  const invoice = await db.invoices.findUnique({
    where: { id: params.id },
    include: { customer: true, items: true },
  });

  if (!invoice) {
    return new Response('Invoice not found', { status: 404 });
  }

  const response = await fetch('https://api.allscreenshots.com/v1/screenshots', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.ALLSCREENSHOTS_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      url: `${process.env.APP_URL}/invoice/${params.id}`,
      format: 'pdf',
      fullPage: true,
      viewport: {
        width: 794,  // A4 at 96 DPI
        height: 1123,
      },
      waitUntil: 'networkidle',
    }),
  });

  const pdf = await response.arrayBuffer();

  return new Response(pdf, {
    headers: {
      'Content-Type': 'application/pdf',
      'Content-Disposition': `attachment; filename="invoice-${invoice.number}.pdf"`,
    },
  });
}

Add download button

function InvoiceActions({ invoiceId }) {
  const downloadPdf = async () => {
    const response = await fetch(`/api/invoices/${invoiceId}/pdf`);
    const blob = await response.blob();

    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `invoice-${invoiceId}.pdf`;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <button onClick={downloadPdf}>
      Download PDF
    </button>
  );
}

PDF styling tips

@media print {
  /* Hide non-printable elements */
  .no-print, nav, .sidebar {
    display: none !important;
  }

  /* Avoid page breaks inside elements */
  .keep-together {
    page-break-inside: avoid;
  }

  /* Force page break before element */
  .page-break {
    page-break-before: always;
  }

  /* Remove backgrounds for cleaner print */
  body {
    background: white !important;
  }
}

Inject print styles via API

const response = await fetch('https://api.allscreenshots.com/v1/screenshots', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.ALLSCREENSHOTS_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: pageUrl,
    format: 'pdf',
    fullPage: true,
    customCss: `
      @page {
        margin: 20mm;
      }

      body {
        font-size: 12pt;
        line-height: 1.5;
      }

      .no-print {
        display: none !important;
      }

      table {
        page-break-inside: avoid;
      }
    `,
  }),
});

Multi-page reports

Generate reports that span multiple pages:

// Report template with multiple sections
function ReportTemplate({ data }) {
  return (
    <div className="report">
      {/* Cover page */}
      <section className="page cover">
        <h1>{data.title}</h1>
        <p>{data.date}</p>
      </section>

      {/* Executive summary */}
      <section className="page summary">
        <h2>Executive Summary</h2>
        <p>{data.summary}</p>
      </section>

      {/* Data sections */}
      {data.sections.map(section => (
        <section key={section.id} className="page">
          <h2>{section.title}</h2>
          {section.content}
          {section.chart && <Chart data={section.chart} />}
        </section>
      ))}

      <style jsx>{`
        .page {
          min-height: 297mm;
          padding: 20mm;
          page-break-after: always;
        }
        .page:last-child {
          page-break-after: auto;
        }
      `}</style>
    </div>
  );
}

Async PDF generation for large documents

For large documents, use async jobs:

async function generateLargeReport(reportId) {
  // Start async job
  const response = await fetch('https://api.allscreenshots.com/v1/screenshots/async', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.ALLSCREENSHOTS_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      url: `${process.env.APP_URL}/reports/${reportId}`,
      format: 'pdf',
      fullPage: true,
      timeout: 60000, // Allow longer timeout for complex reports
      webhookUrl: `${process.env.APP_URL}/webhooks/pdf-ready`,
    }),
  });

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

  // Store job ID for later reference
  await db.reports.update({
    where: { id: reportId },
    data: { pdfJobId: jobId, pdfStatus: 'generating' },
  });

  return jobId;
}

// Webhook handler
app.post('/webhooks/pdf-ready', async (req, res) => {
  const { jobId, status, result } = req.body;

  const report = await db.reports.findFirst({
    where: { pdfJobId: jobId },
  });

  if (status === 'completed') {
    await db.reports.update({
      where: { id: report.id },
      data: { pdfUrl: result.url, pdfStatus: 'ready' },
    });

    // Notify user
    await sendEmail(report.userEmail, {
      subject: 'Your report is ready',
      body: `Download your report: ${result.url}`,
    });
  }

  res.sendStatus(200);
});

Best practices

Use fixed widths (like A4: 794px at 96 DPI) for predictable PDF layouts.

Font handling

Ensure fonts are loaded before capture:

const response = await fetch('https://api.allscreenshots.com/v1/screenshots', {
  body: JSON.stringify({
    url: pageUrl,
    format: 'pdf',
    waitUntil: 'networkidle',
    delay: 500, // Extra time for fonts to load
  }),
});

Image optimization

  • Use absolute URLs for images
  • Ensure images are loaded before capture
  • Consider embedding base64 images for reliability

Page size reference

Paper SizeWidth (px)Height (px)At DPI
A4794112396
Letter816105696
Legal816134496
A4 (print)59584272

On this page