Sign PDF (signature stamping for sign-and-return workflows)
Overview
Most "sign and return" PDFs aren't true Docusign envelopes - they're flat PDFs with a "Signature:" line, or proper AcroForm PDFs with fillable signature fields. This skill stamps a transparent PNG of the user's signature at the right spot, optionally adds today's date, and saves a signed copy. The output is a normal PDF that can be attached to a reply email.
Tool: scripts/sign_pdf.py (PyMuPDF-based)
Signature image: you provide this - see Setup below. Never use a placeholder or a signature that isn't the actual user's.
Setup
Before first use:
- Get a clear image of the user's signature: a photo of it signed on plain white paper, or a scan.
- Run
scripts/prep_signature.py to turn it into a transparent PNG (white background removed, ink kept):python scripts/prep_signature.py "/path/to/signature-source.jpg" "/path/to/signature.png"
Tune --threshold (default 230, range 0-255) if the result loses ink or keeps paper texture - lower is more aggressive at removing near-white pixels.
- Tell
sign_pdf.py where that PNG lives, either by passing --signature /path/to/signature.png on every command, or by setting the environment variable SIGNATURE_IMAGE so you don't have to repeat it.
- Keep the signature PNG out of source control and treat it like a sensitive file (see Signature Asset Hygiene below).
When to Use
Use when:
- The user attaches/forwards a PDF and says "sign this", "add my signature", "sign and return"
- A contract/agreement/form needs their signature
Don't use for:
- Docs marked "must be wet-signed" or with notary blocks (legally insufficient)
- Real-estate closings, banking power-of-attorney, anything requiring witnessing
- True Docusign envelopes - those should go through Docusign's UI for the audit trail
Legal Note (ESIGN Act, US)
This is general background, not legal advice.
Image-stamp signatures on flat PDFs are binding for normal commercial contracts under the US ESIGN Act 2000. Avoid them for: notarized documents, anything explicitly requiring wet-ink, real-estate closings, and Docusign-grade-audit-trail required docs (some banking/HIPAA contexts). If you're outside the US, check your own jurisdiction's e-signature rules before relying on this. When in doubt, surface the question to the user before signing.
Workflow
inspect -> preview (optional) -> stamp OR auto
inspect - print page count, dimensions, AcroForm fields, and "Signature:"/"By:" labels detected by text search. Always start here.
auto - try AcroForm signature fields first; fall back to stamping near label text. Works on the majority of well-built PDFs, not all.
preview - render a page as PNG so you can eyeball the layout and pick (x,y) coordinates manually.
stamp - place signature image at explicit --page --x --y --width (PDF points, 72 per inch, top-left origin).
text - stamp plain text (printed name, title, date) at explicit --page --x --y --size. The y is the text baseline. Use this to fill the rows below the signature line.
Quick Reference
# Inspect
python scripts/sign_pdf.py inspect "/path/to/contract.pdf"
# Auto (try first - fastest path)
python scripts/sign_pdf.py auto "/path/to/contract.pdf" --signature "/path/to/signature.png"
# Preview a page to pick coordinates
python scripts/sign_pdf.py preview "/path/to/contract.pdf" --page 5
# Stamp at explicit coords + date below signature
python scripts/sign_pdf.py stamp "/path/to/contract.pdf" --page 5 --x 100 --y 620 --width 150 --date --signature "/path/to/signature.png"
# Stamp plain text (printed name, title, date) at coords. Run once per field; chain via --out.
python scripts/sign_pdf.py text "Jane Doe" --page 5 --x 316 --y 161 --size 11
python scripts/sign_pdf.py text "Chief Executive Officer" --page 5 --x 316 --y 194 --size 11
python scripts/sign_pdf.py text "2026-05-06" --page 5 --x 316 --y 227 --size 11
# Custom output path
... --out "/path/to/contract-signed.pdf"
Default output is <original>-signed.pdf next to the input. --signature can be omitted if you set the SIGNATURE_IMAGE environment variable.
Decision Flow
Got PDF to sign
|
v
Run inspect
|
v
Has AcroForm signature widgets? -- yes --> auto, then verify with preview
|
no
v
Has "Signature:" / "By:" labels found? -- yes --> auto (text-search fallback), verify
|
no
v
Manual: preview each candidate page, pick coords, stamp explicitly
Common Mistakes
| Mistake |
Fix |
| Renaming the signed PDF with a date/recipient prefix |
Preserve the original filename - only append -signed. Recipients recognize docs by original name; renaming confuses them. |
| Skipping inspect, stamping blind |
Always inspect first; coordinates differ per PDF. |
| Guessing text field y-coordinates |
Extract the existing signer's filled values via page.get_text('dict') and mirror those exact y positions. Don't estimate - use the PDF's own data. |
| Placing text values below their label |
Values go ABOVE the label/rule that identifies the field (traditional form convention). Use the other party's filled positions as the reference. |
| Putting the signer's name twice |
Name goes only in the Printed Name field. The signature image alone occupies the Signature area - no typed name there unless the other party did the same. |
| Using an 11pt font when form labels are 9pt |
Match the form's font size (check span['size'] in text extraction). Mismatched size looks off. |
| Using stamp width too small (<100) |
Signature looks tiny. Default 150 pts (~2 inches) is right. |
Not adding --date when the doc has a date line |
Reader expects ISO date or "MM/DD/YYYY" near sig. Use --date. |
| Stamping a notarized doc |
Legally insufficient. Surface the notary block to the user before signing. |
| Using initials per page when only signature wanted |
Tool currently stamps once; for per-page initials, run stamp once per page. |
Signature Asset Hygiene
- Keep the source photo/scan and the transparent working PNG out of source control (
.gitignore should cover them - see this repo's own .gitignore as an example, but if you install this skill inside your own project, make sure your .gitignore there also excludes it).
- If the machine holding the signature PNG is compromised, that PNG could be used to forge documents. Consider encrypting it at rest, or storing it in your OS's credential manager, if that risk matters to you.
Limitations (current v1)
- Single signature per run for
stamp mode (auto can place multiple if multiple fields/labels found)
- No initials-per-page mode - would need a
--initials flag with corner placement
- No checkbox/text-field filling - only signature image stamping
- No verification of signed output - always open the signed PDF and visually confirm before sending
- Coordinate origin is top-left in PyMuPDF, but some PDF tools use bottom-left. If a stamp lands in the wrong place, try
--y (page_height - y).
1---2name: sign-pdf3description: Use when the user asks to sign a PDF, add their signature to an attached document, return a signed Docusign-style PDF, or "sign and return" any contract or agreement. Stamps a transparent signature image onto a PDF at AcroForm fields, near "Signature:" labels, or at explicit coordinates.4---56# Sign PDF (signature stamping for sign-and-return workflows)78## Overview910Most "sign and return" PDFs aren't true Docusign envelopes - they're flat PDFs with a "Signature:" line, or proper AcroForm PDFs with fillable signature fields. This skill stamps a transparent PNG of the user's signature at the right spot, optionally adds today's date, and saves a signed copy. The output is a normal PDF that can be attached to a reply email.1112**Tool:** `scripts/sign_pdf.py` (PyMuPDF-based)13**Signature image:** you provide this - see Setup below. Never use a placeholder or a signature that isn't the actual user's.1415## Setup1617Before first use:18191. Get a clear image of the user's signature: a photo of it signed on plain white paper, or a scan.202. Run `scripts/prep_signature.py` to turn it into a transparent PNG (white background removed, ink kept):21 ```bash22 python scripts/prep_signature.py "/path/to/signature-source.jpg" "/path/to/signature.png"23 ```24 Tune `--threshold` (default 230, range 0-255) if the result loses ink or keeps paper texture - lower is more aggressive at removing near-white pixels.253. Tell `sign_pdf.py` where that PNG lives, either by passing `--signature /path/to/signature.png` on every command, or by setting the environment variable `SIGNATURE_IMAGE` so you don't have to repeat it.264. Keep the signature PNG out of source control and treat it like a sensitive file (see Signature Asset Hygiene below).2728## When to Use2930**Use when:**31- The user attaches/forwards a PDF and says "sign this", "add my signature", "sign and return"32- A contract/agreement/form needs their signature3334**Don't use for:**35- Docs marked "must be wet-signed" or with notary blocks (legally insufficient)36- Real-estate closings, banking power-of-attorney, anything requiring witnessing37- True Docusign envelopes - those should go through Docusign's UI for the audit trail3839## Legal Note (ESIGN Act, US)4041This is general background, not legal advice.4243Image-stamp signatures on flat PDFs are binding for normal commercial contracts under the US ESIGN Act 2000. Avoid them for: notarized documents, anything explicitly requiring wet-ink, real-estate closings, and Docusign-grade-audit-trail required docs (some banking/HIPAA contexts). If you're outside the US, check your own jurisdiction's e-signature rules before relying on this. When in doubt, surface the question to the user before signing.4445## Workflow4647```48inspect -> preview (optional) -> stamp OR auto49```50511. **`inspect`** - print page count, dimensions, AcroForm fields, and "Signature:"/"By:" labels detected by text search. Always start here.522. **`auto`** - try AcroForm signature fields first; fall back to stamping near label text. Works on the majority of well-built PDFs, not all.533. **`preview`** - render a page as PNG so you can eyeball the layout and pick (x,y) coordinates manually.544. **`stamp`** - place signature image at explicit `--page --x --y --width` (PDF points, 72 per inch, top-left origin).555. **`text`** - stamp plain text (printed name, title, date) at explicit `--page --x --y --size`. The y is the text baseline. Use this to fill the rows below the signature line.5657## Quick Reference5859```bash60# Inspect61python scripts/sign_pdf.py inspect "/path/to/contract.pdf"6263# Auto (try first - fastest path)64python scripts/sign_pdf.py auto "/path/to/contract.pdf" --signature "/path/to/signature.png"6566# Preview a page to pick coordinates67python scripts/sign_pdf.py preview "/path/to/contract.pdf" --page 56869# Stamp at explicit coords + date below signature70python scripts/sign_pdf.py stamp "/path/to/contract.pdf" --page 5 --x 100 --y 620 --width 150 --date --signature "/path/to/signature.png"7172# Stamp plain text (printed name, title, date) at coords. Run once per field; chain via --out.73python scripts/sign_pdf.py text "Jane Doe" --page 5 --x 316 --y 161 --size 1174python scripts/sign_pdf.py text "Chief Executive Officer" --page 5 --x 316 --y 194 --size 1175python scripts/sign_pdf.py text "2026-05-06" --page 5 --x 316 --y 227 --size 117677# Custom output path78... --out "/path/to/contract-signed.pdf"79```8081Default output is `<original>-signed.pdf` next to the input. `--signature` can be omitted if you set the `SIGNATURE_IMAGE` environment variable.8283## Decision Flow8485```86Got PDF to sign87 |88 v89Run inspect90 |91 v92Has AcroForm signature widgets? -- yes --> auto, then verify with preview93 |94 no95 v96Has "Signature:" / "By:" labels found? -- yes --> auto (text-search fallback), verify97 |98 no99 v100Manual: preview each candidate page, pick coords, stamp explicitly101```102103## Common Mistakes104105| Mistake | Fix |106|---|---|107| Renaming the signed PDF with a date/recipient prefix | Preserve the original filename - only append `-signed`. Recipients recognize docs by original name; renaming confuses them. |108| Skipping inspect, stamping blind | Always inspect first; coordinates differ per PDF. |109| Guessing text field y-coordinates | Extract the existing signer's filled values via `page.get_text('dict')` and mirror those exact y positions. Don't estimate - use the PDF's own data. |110| Placing text values below their label | Values go ABOVE the label/rule that identifies the field (traditional form convention). Use the other party's filled positions as the reference. |111| Putting the signer's name twice | Name goes only in the Printed Name field. The signature image alone occupies the Signature area - no typed name there unless the other party did the same. |112| Using an 11pt font when form labels are 9pt | Match the form's font size (check `span['size']` in text extraction). Mismatched size looks off. |113| Using stamp width too small (<100) | Signature looks tiny. Default 150 pts (~2 inches) is right. |114| Not adding `--date` when the doc has a date line | Reader expects ISO date or "MM/DD/YYYY" near sig. Use `--date`. |115| Stamping a notarized doc | Legally insufficient. Surface the notary block to the user before signing. |116| Using initials per page when only signature wanted | Tool currently stamps once; for per-page initials, run `stamp` once per page. |117118## Signature Asset Hygiene119120- Keep the source photo/scan and the transparent working PNG out of source control (`.gitignore` should cover them - see this repo's own `.gitignore` as an example, but if you install this skill inside your own project, make sure your `.gitignore` there also excludes it).121- If the machine holding the signature PNG is compromised, that PNG could be used to forge documents. Consider encrypting it at rest, or storing it in your OS's credential manager, if that risk matters to you.122123## Limitations (current v1)124125- **Single signature per run** for `stamp` mode (auto can place multiple if multiple fields/labels found)126- **No initials-per-page mode** - would need a `--initials` flag with corner placement127- **No checkbox/text-field filling** - only signature image stamping128- **No verification of signed output** - always open the signed PDF and visually confirm before sending129- **Coordinate origin is top-left** in PyMuPDF, but some PDF tools use bottom-left. If a stamp lands in the wrong place, try `--y (page_height - y)`.