May 17, 2026

How to Add a Watermark to PDF via API Using HTML and CSS

pdfwatermarkapihtmlcss

Adding a watermark — "CONFIDENTIAL", "DRAFT", "SAMPLE" — to a PDF is a common requirement for documents like contracts, reports, and invoices. Rather than post-processing an existing PDF, the cleanest approach is to bake the watermark directly into the HTML template and generate the PDF in one step.

This guide shows how to implement text and image watermarks using HTML/CSS and FUNBREW PDF API, with working code in curl, JavaScript, and Python. We cover three patterns: center watermark, tiled watermark, and image watermark — all repeating on every page automatically.

For CSS fundamentals of PDF generation, see the HTML to PDF complete guide. For page layout rules, see the @page rule guide.

Why HTML/CSS Watermarks Work Well with PDF APIs

FUNBREW PDF API renders HTML with a Chromium engine before converting to PDF. This means your watermark CSS is treated exactly as it would be in a browser's print layout — including position: fixed behavior across pages.

Benefits of this approach:

  • No external library or post-processing step required
  • Full control over watermark design, position, opacity, and rotation via CSS
  • Watermark automatically appears on every page with position: fixed
  • Works with text and images alike

Pattern 1: Center Text Watermark (Basic)

The foundational pattern. Use position: fixed to pin the watermark to the viewport, and opacity/rgba to make it semi-transparent.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <style>
    /* ===== Watermark ===== */
    .watermark {
      position: fixed;          /* Fixed to viewport = repeats on every page */
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%) rotate(-45deg); /* Center + diagonal */
      font-size: 80px;
      font-weight: bold;
      color: rgba(200, 0, 0, 0.15); /* Red at 15% opacity */
      white-space: nowrap;
      pointer-events: none;
      z-index: 9999;
      user-select: none;
    }

    /* ===== Body ===== */
    body {
      font-family: Arial, sans-serif;
      padding: 40px;
      color: #1a1a1a;
    }
  </style>
</head>
<body>
  <!-- Watermark layer -->
  <div class="watermark">CONFIDENTIAL</div>

  <!-- Document content -->
  <h1>Monthly Sales Report</h1>
  <p>This document contains proprietary information.</p>
  <!-- ... content ... -->
</body>
</html>

position: fixed is key: in print/PDF output, a fixed element repeats at the same position on every page, giving you a full-document watermark with a single <div>.

Pattern 2: Tiled Watermark

Tile the watermark text across the entire page for higher visual coverage.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <style>
    .watermark-layer {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      z-index: 9999;
      pointer-events: none;

      /* CSS Grid for tiling */
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      grid-template-rows: repeat(5, 1fr);
      align-items: center;
      justify-items: center;
    }

    .watermark-item {
      font-size: 24px;
      font-weight: bold;
      color: rgba(150, 150, 150, 0.12);
      transform: rotate(-30deg);
      white-space: nowrap;
      user-select: none;
    }

    body {
      font-family: Arial, sans-serif;
      padding: 40px;
    }
  </style>
</head>
<body>
  <!-- 3×5 = 15 watermark tiles -->
  <div class="watermark-layer">
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
    <span class="watermark-item">CONFIDENTIAL</span>
  </div>

  <h1>Confidential Document</h1>
  <p>Unauthorized reproduction is strictly prohibited.</p>
</body>
</html>

Pattern 3: Image Watermark (Logo or Stamp)

Use a logo or stamp image as the watermark by embedding it as a fixed, semi-transparent overlay.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <style>
    .watermark-image {
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      width: 300px;
      height: auto;
      opacity: 0.08;      /* 8% opacity — visible but unobtrusive */
      z-index: 9999;
      pointer-events: none;
      user-select: none;
    }

    body {
      font-family: Arial, sans-serif;
      padding: 40px;
    }
  </style>
</head>
<body>
  <!-- Use a URL or Base64-encoded inline image -->
  <img
    class="watermark-image"
    src="https://example.com/logo-watermark.png"
    alt=""
  >

  <h1>Contract Agreement</h1>
  <p>Please sign below to indicate your agreement.</p>
</body>
</html>

For reliable rendering, consider Base64-encoding the image so it doesn't depend on an external URL.

API Integration Examples

curl

curl -X POST https://pdf.funbrew.cloud/api/v1/pdf/from-html \
  -H "Authorization: Bearer $FUNBREW_PDF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<!DOCTYPE html><html><head><style>.watermark{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) rotate(-45deg);font-size:80px;font-weight:bold;color:rgba(200,0,0,0.15);z-index:9999;pointer-events:none;}</style></head><body><div class=\"watermark\">CONFIDENTIAL</div><h1>Report</h1><p>Content here.</p></body></html>",
    "format": "A4",
    "engine": "quality"
  }' \
  --output report-watermarked.pdf

JavaScript (Node.js / fetch)

const fs = require('fs');

function buildWatermarkHTML(contentHTML, watermarkText = 'CONFIDENTIAL') {
  return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <style>
    .watermark {
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%) rotate(-45deg);
      font-size: 80px;
      font-weight: bold;
      color: rgba(200, 0, 0, 0.15);
      white-space: nowrap;
      pointer-events: none;
      z-index: 9999;
      user-select: none;
    }
    body {
      font-family: Arial, sans-serif;
      padding: 40px;
    }
  </style>
</head>
<body>
  <div class="watermark">${watermarkText}</div>
  ${contentHTML}
</body>
</html>`;
}

async function generateWatermarkedPDF(contentHTML, watermarkText, outputPath) {
  const html = buildWatermarkHTML(contentHTML, watermarkText);

  const response = await fetch('https://pdf.funbrew.cloud/api/v1/pdf/from-html', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.FUNBREW_PDF_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ html, format: 'A4', engine: 'quality' }),
  });

  if (!response.ok) {
    throw new Error(`PDF generation failed: ${response.status}`);
  }

  const buffer = await response.arrayBuffer();
  fs.writeFileSync(outputPath, Buffer.from(buffer));
  console.log(`Generated: ${outputPath}`);
}

// Usage
const reportContent = `
  <h1>Monthly Sales Report</h1>
  <p>May 2026 sales summary.</p>
`;

generateWatermarkedPDF(reportContent, 'DRAFT', 'report-draft.pdf');

Python (requests)

import os
import requests

FUNBREW_API_KEY = os.environ["FUNBREW_PDF_API_KEY"]
API_URL = "https://pdf.funbrew.cloud/api/v1/pdf/from-html"


def build_watermark_html(content_html: str, watermark_text: str = "CONFIDENTIAL") -> str:
    """Wrap content HTML with a watermark overlay."""
    return f"""<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <style>
    .watermark {{
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%) rotate(-45deg);
      font-size: 80px;
      font-weight: bold;
      color: rgba(200, 0, 0, 0.15);
      white-space: nowrap;
      pointer-events: none;
      z-index: 9999;
      user-select: none;
    }}
    body {{
      font-family: Arial, sans-serif;
      padding: 40px;
      color: #1a1a1a;
    }}
  </style>
</head>
<body>
  <div class="watermark">{watermark_text}</div>
  {content_html}
</body>
</html>"""


def generate_watermarked_pdf(content_html: str, watermark_text: str = "CONFIDENTIAL") -> bytes:
    """Generate a watermarked PDF and return the binary content."""
    html = build_watermark_html(content_html, watermark_text)

    response = requests.post(
        API_URL,
        json={"html": html, "format": "A4", "engine": "quality"},
        headers={
            "Authorization": f"Bearer {FUNBREW_API_KEY}",
            "Content-Type": "application/json",
        },
        timeout=60,
    )
    response.raise_for_status()
    return response.content


# Usage
content = """
<h1>Monthly Sales Report</h1>
<p>This document contains proprietary data.</p>
<table>
  <thead><tr><th>Product</th><th>Revenue</th></tr></thead>
  <tbody>
    <tr><td>Plan A</td><td>$12,000</td></tr>
    <tr><td>Plan B</td><td>$8,000</td></tr>
  </tbody>
</table>
"""

pdf_bytes = generate_watermarked_pdf(content, "CONFIDENTIAL")
with open("report-confidential.pdf", "wb") as f:
    f.write(pdf_bytes)

Dynamic Watermarks (User Name + Timestamp)

Embedding the viewer's name and access time makes unauthorized copies traceable.

from datetime import datetime

def generate_personalized_pdf(content_html: str, user_name: str) -> bytes:
    """Generate a PDF with a personalized watermark for tracking."""
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M UTC")
    watermark_text = f"{user_name} | {timestamp}"
    return generate_watermarked_pdf(content_html, watermark_text)
// Node.js version
function buildPersonalizedWatermark(userName) {
  const now = new Date();
  const timestamp = now.toISOString().slice(0, 16).replace('T', ' ') + ' UTC';
  return `${userName} | ${timestamp}`;
}

CSS Tips for Reliable Watermarks

Opacity adjustment

Tune the alpha value in rgba() to balance visibility vs. readability:

  • rgba(200, 0, 0, 0.08) — subtle background effect
  • rgba(200, 0, 0, 0.15) — standard watermark
  • rgba(200, 0, 0, 0.25) — strong visibility (may reduce readability)

Combining with @page

/* Watermark on every page */
.watermark {
  position: fixed;   /* Repeats on all pages in print output */
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(-45deg);
  font-size: 80px;
  color: rgba(200, 0, 0, 0.12);
  z-index: 9999;
}

/* Page margin settings */
@page {
  size: A4;
  margin: 20mm 15mm;
}

For more @page options, see the @page rule guide.

Try It in the Playground

Before writing any API code, paste your watermark HTML into the Playground and preview the result instantly. Adjust opacity, font-size, and rotate values until the design looks right, then copy the final HTML into your API call.

For invoice and certificate watermark use cases, see the invoice automation guide.

Common Issues

Watermark only appears on the first page

You are using position: absolute instead of position: fixed. Switch to fixed — it renders at the same position on every printed page.

Body content is obscured

Lower the alpha value in rgba(). Aim for 0.08–0.20 for most use cases. Ensure body text has sufficient contrast (dark text on white background).

Image watermark doesn't render

If you use a URL-based image, make sure the URL is publicly accessible. For guaranteed rendering, use a Base64-encoded inline image (src="data:image/png;base64,...").

Summary

HTML/CSS watermarks with FUNBREW PDF API let you add professional watermarks without any post-processing library:

  • position: fixed + rgba opacity + transform: rotate() = full-page watermark on every page
  • Choose center, tiled, or image pattern based on your needs
  • Personalize with user name and timestamp for document traceability

Check the full API reference in the documentation, or test your design live in the Playground.

Powered by FUNBREW PDF