How to Add Text Watermarks to PDFs in Go
Add Draft or Confidential watermarks to PDF pages in Go with pdfcpu, including styling, page selection, troubleshooting, and CLI usage.
Use pdfcpu to add text watermarks to PDFs in Go. Its documented api.AddTextWatermarksFile function reads an input PDF, applies a text watermark to selected pages, and writes a new PDF. Set onTop to false for content behind the existing page (a watermark) or true for content in front (a stamp).
The examples below show how to watermark every page, target odd or even pages, control font and opacity, use the command line, and avoid the common problem where a background watermark is hidden by a scanned page.
1. Install pdfcpu
Create a Go module and add pdfcpu:
mkdir pdf-watermark
cd pdf-watermark
go mod init example.com/pdf-watermark
go get github.com/pdfcpu/pdfcpu/pkg/api
Check the version and current API or CLI syntax before pinning a production build. The documentation and examples used here do not specify a verified library version.
2. Add a text watermark to every page
Passing nil for the selected-page expression applies the watermark to all pages. This complete program writes a new file and leaves the source PDF unchanged:
package main
import (
"context"
"log"
"github.com/pdfcpu/pdfcpu/pkg/api"
)
func main() {
ctx := context.Background()
// false places the generated content behind existing page content.
onTop := false
// Descriptor options control the text appearance. Verify descriptors
// against the pdfcpu version used by your build.
descriptor := "font:Helvetica, points:48, color:.8 .8 .4, rot:45, scale:1, op:.6"
err := api.AddTextWatermarksFile(
ctx,
"input.pdf",
"watermarked.pdf",
nil, // nil means every page
onTop,
"DRAFT",
descriptor,
nil, // use the default configuration
)
if err != nil {
log.Fatal(err)
}
}
Run it with:
go run .
The result is watermarked.pdf. Treat the descriptor as version-sensitive configuration and confirm accepted values with the installed pdfcpu documentation.
3. Choose watermark or stamp placement
pdfcpu uses two terms for fixed page content:
| Setting | Placement | Use it when |
|---|---|---|
onTop = false |
Behind existing page content | You want a subtle background watermark and the page artwork has transparent or uncovered areas. |
onTop = true |
In front of existing page content | The label must remain visible over scans, images, or dense page artwork. |
A full-page scan is a common edge case. The scanned bitmap can cover a background watermark completely. In that situation, use a foreground stamp and reduce opacity so the original content remains readable.
4. Control font, size, color, rotation, scale, and opacity
The descriptor string carries appearance settings. The documented examples demonstrate options such as:
| Option | Purpose | Example |
|---|---|---|
font |
Select the font family. | font:Courier |
points |
Set text size in points. | points:48 |
color |
Set the color; the CLI examples use space-separated channel values. | color:.8 .8 .4 |
rot |
Rotate the text. | rot:45 |
scale |
Adjust the absolute scale. | scale:1 |
op |
Set opacity. | op:.6 |
For example, a foreground confidential stamp can use:
descriptor := "font:Courier, points:48, color:1 0 0, rot:45, scale:1, op:.6"
err := api.AddTextWatermarksFile(
context.Background(),
"input.pdf",
"confidential.pdf",
nil,
true, // place over existing content
"CONFIDENTIAL",
descriptor,
nil,
)
There is no universal best opacity or size. Compare the output against the actual page backgrounds and the purpose of the document.
5. Watermark selected pages
The page-selection argument accepts page expressions. The API example applies a foreground watermark to odd pages:
package main
import (
"context"
"log"
"github.com/pdfcpu/pdfcpu/pkg/api"
)
func main() {
descriptor := "font:Courier, points:48, color:1 0 0, rot:45, scale:1, op:.6"
err := api.AddTextWatermarksFile(
context.Background(),
"input.pdf",
"odd-pages.pdf",
"odd",
true,
"CONFIDENTIAL",
descriptor,
nil,
)
if err != nil {
log.Fatal(err)
}
}
Use the expression supported by your pdfcpu version for the pages you need. If the API needs different text or styling on different pages, use the stream-oriented AddWatermarks function or an AddWatermarksMap variant to supply page-specific watermark configurations.
6. Use the pdfcpu command line
For deployments where an external executable is acceptable, pdfcpu documents this command:
pdfcpu watermark add 'Draft' 'points:48, scale:1, color:.8 .8 .4, op:.6' input.pdf output.pdf --mode text
To update or remove an existing watermark:
pdfcpu watermark update 'Draft' 'points:48, scale:1, color:.8 .8 .4, op:.6' input.pdf output.pdf --mode text
pdfcpu watermark remove input.pdf output.pdf
To target even pages, the documented form is:
pdfcpu watermark add 'Draft' 'points:48, scale:1, color:.8 .8 .4, op:.6' input.pdf output.pdf --mode text --pages even
Run pdfcpu watermark add -h and the help for your installed version before automating these commands because command and descriptor details can change.
7. Process readers and writers instead of file paths
AddTextWatermarksFile is convenient for file-to-file jobs. When a service already owns an io.Reader and io.Writer, use pdfcpu’s stream-oriented AddWatermarks API. The map variants are useful when each page needs a different watermark. Keep the request context connected to the job so cancellation can stop work that the API supports cancelling.
8. Verify the output in an automated pipeline
Use a separate output path or temporary file. That prevents a failed operation from destroying the original. A practical pipeline is:
- Check that the input exists and is readable.
- Write to a temporary destination in the same filesystem.
- Return the error without replacing the source if processing fails.
- Replace or upload the final file only after the call succeeds.
- Open the output with a PDF validator or your normal downstream reader.
For large batches, bound concurrency, pass cancellation through the context, and monitor output size and processing time. The supplied documentation does not provide performance benchmarks, so measure with your own PDFs.
9. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The watermark is invisible. | It was added behind a full-page scan or opaque artwork. | Set onTop to true and lower op if needed. |
| Only some pages contain text. | A page expression selected a subset. | Pass nil for all pages or verify the expression and page numbering. |
| The command fails with an unknown option. | The installed CLI version differs from the example. | Run the command’s -h help and adjust the descriptor or flags. |
| The source file is missing or unreadable. | Wrong path or insufficient permissions. | Check paths, permissions, and the process working directory before calling the API. |
| The result is hard to read. | Font size, rotation, color, or opacity is too strong. | Reduce point size or opacity, choose a contrasting color, and test on representative pages. |
| The program stops during a long job. | The context was cancelled or the process exited. | Use a request-scoped context, handle the returned error, and write results atomically. |
| Different pages need different labels. | A single global watermark configuration is too limited. | Use AddWatermarksMap or another page-specific API variant. |
10. Reliability, performance, and cost considerations
- Reliability: Preserve the original, use a temporary output, and only publish a successful result. Context cancellation lets callers stop work where supported by the API.
- Performance: PDF size, page count, embedded images, fonts, and whether the document is scanned all affect processing time. Benchmark with production-like files; no benchmark is established by the cited documentation.
- Memory: Prefer stream APIs for services that should avoid loading unnecessary data into application-managed buffers, and limit concurrent jobs.
- Cost: pdfcpu is software you run yourself. Your cost comes from compute, storage, and operations rather than a per-watermark API charge. The sources do not provide a universal cost estimate.
- Reproducibility: Pin the pdfcpu module or executable version and keep descriptor strings under source control.
11. Or skip the browser setup
If the job is to capture a web page or rendered document as an image or PDF rather than modify an existing PDF, ScreenshotNeo provides a single HTTP request. Its cleanup step accepts cookie banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. An MCP server lets Claude, Cursor, and other MCP clients call screenshot and PDF tools.
See the ScreenshotNeo API documentation for the available options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Every feature is available on every plan. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
12. FAQ
Does pdfcpu create an annotation that users can move?
No. The documented watermark and stamp operations add fixed page content. They are not described as movable stamp-comment annotations.
Can I watermark only odd pages in Go?
Yes. Pass the documented odd-page expression to AddTextWatermarksFile, and confirm the exact expression supported by your version.
Why does a watermark disappear on a scanned PDF?
A scan can be an opaque image covering the entire page. Place the text on top with onTop = true and choose an opacity that preserves readability.
Should I use the API or CLI?
Use the Go API when the operation belongs inside a Go service, needs context cancellation, streams, or page-specific logic. Use the CLI when an external executable fits your deployment and shell workflow.
Can I remove a watermark later?
The pdfcpu CLI documents watermark remove. Keep the original PDF when you may need to produce an unwatermarked copy later.


