Validation & Best Practices
Gutterpress's validation system checks your project for print compliance at two points in the pipeline — before the PDF build and after. This chapter covers the 36 built-in checks, CLI usage, and production workflow recommendations.
Validation Overview
The validation system runs checks at two phases:
- Pre-build — Source, asset, and some heuristic checks run before PDF generation
- Post-build — PDF structural, print compliance, and quality checks run after PDF generation
When using gutterpress build, validation is automatically integrated into the
pipeline:
validate:pre-build → convert → assets → build → validate:post-build
The CSS print-safety check (remote URLs, rasterizing effects, and
page-containment risks) runs once, inside validate:pre-build, as the
source.stylelint check — standalone, gutterpress validate my-book --only source.stylelint runs just that check.
There is no separate lint phase; --skip-lint disables that one check
without skipping the rest of pre-build validation.
The final validate:post-build phase runs for --format pdfx only — a plain
--format pdf build stops after the build step. Everything before it runs for
both formats.
Publish Targets
Where you publish is a separate decision from how your book is designed
(the preset: — see Chapter 1). A target is a destination's validation
policy: what its platform demands of the finished PDF. Targets never change
how the book renders — only what the validator checks.
dtrpg— DriveThruRPG print-on-demand: PDF/X markers and metadata, CMYK/Grayscale art, the 240% ink limit, embedded fonts.itch— itch.io digital release: a well-formed PDF with embedded fonts; print-only rules (PDF/X, ink limits, CMYK-only art) don't apply.
List them in your manifest to validate every destination in one run:
targets:
- dtrpg
- itch
The report labels each destination's findings ([dtrpg], [itch]), so one
source can be checked for print and digital at the same time. Projects
created with gutterpress new or the desktop app always carry an explicit
targets: list — you choose the destinations at creation time (and can
uncheck them all, recorded as targets: []). In the desktop app you can
change them whenever you like under Project settings → Details →
Publish targets. In a hand-written manifest
with no targets: line, the preset's default applies (the dtrpg preset
validates for DriveThruRPG; book and custom validate for no
destination). An explicit empty list (targets: []) opts out entirely.
Your own manifest settings always win over a target's policy — a target
only fills in what you haven't set.
The dtrpg target's checks need qpdf and Ghostscript installed (Chapter
7). With the target selected but the tools missing, validation reports the
required checks as errors rather than silently skipping them — that's
deliberate: "validated for DriveThruRPG" must mean the checks actually ran.
If you're not ready to install them, set targets: [] until you are. The
itch target needs no external tools.
CLI Usage
Validate a PDF
# Validate an existing PDF for print compliance
gutterpress validate --pdf dist/my-book/my-book-pdf.pdf
# With a manifest for project-specific settings
gutterpress validate --pdf dist/my-book/my-book-pdf.pdf --manifest ./manifest.yaml
# Validate against a specific publish target's requirements
gutterpress validate --pdf dist/my-book/my-book-pdf.pdf --target dtrpg
# One source, two destinations: check DriveThruRPG print AND itch.io digital
gutterpress validate --pdf dist/my-book/my-book-pdf.pdf --target dtrpg,itch
Preflight report
# Run post-build checks and write JSON + markdown reports
gutterpress preflight --pdf dist/my-book/my-book-pdf.pdf --target dtrpg
# Custom report location
gutterpress preflight --pdf dist/my-book/my-book-pdf.pdf --report-dir .reviews --name release-preflight
Validate source files
# Run source and asset checks on your project
gutterpress validate --input ./my-book
# Both pre-build and post-build checks together
gutterpress validate --input ./my-book --pdf dist/my-book/my-book-pdf.pdf
Filtering checks
# Only PDF checks
gutterpress validate --pdf dist/my-book/my-book-pdf.pdf --category pdf
# Only source and asset checks
gutterpress validate --input ./my-book --category source,asset
# Run a single specific check
gutterpress validate --pdf dist/my-book/my-book-pdf.pdf --only pdf.print.page-size
# Run a group with wildcard selectors
gutterpress validate --input ./my-book --only source.links.*
# Skip specific checks
gutterpress validate --pdf dist/my-book/my-book-pdf.pdf --skip pdf.nav.cross-refs
# JSON output for CI
gutterpress validate --pdf dist/my-book/my-book-pdf.pdf --format json
CLI Arguments
| Argument | Description |
|---|---|
--pdf |
Path to PDF file (enables post-build checks) |
--input |
Source directory (enables pre-build checks) |
--manifest |
Path to manifest.yaml |
--category |
Comma-separated: source, pdf, asset, heuristic |
--only |
Run only these check IDs (comma-separated, supports *) |
--skip |
Skip these check IDs |
--format |
Output format: text (default) or json |
--phase |
Override phase: pre | post | all | pre-build | post-build (default: all) |
--target |
Publish targets to validate against (comma-separated: dtrpg, itch), overriding the manifest's targets: |
--fix |
Rewrite the source markdown in place with markdownlint's auto-fixes (source.markdownlint only) |
Fixing markdown automatically
Validation is read-only unless you pass --fix, which rewrites the markdown
in place using markdownlint's own auto-fixes — trailing spaces, multiple blank
lines, blank-line placement, list markers and indentation, hard tabs, emphasis
style, and so on:
gutterpress validate --input . --fix
Every rewritten file is printed. Anything markdownlint cannot fix
mechanically is still reported, and the other source checks (broken links, alt
text, layout markers, CSS print-safety) are never touched. The rules come from
the same .markdownlint.* config the check uses; with no config file, --fix
says so and changes nothing. A file with uncommitted changes is rewritten too
— you get a notice, not a refusal.
Check Categories
Source checks (pre-build)
Run on markdown source files and assets before the PDF is generated. Catch authoring errors early.
Examples: broken internal links, missing alt text, oversized source images, markdown lint errors, a mistyped marker or a variant word a plugin component does not declare (@card featuerd), and the structure checks a plugin component opts into with validate (Chapter 5).
PDF checks (post-build)
Run on the generated PDF. Verify structural correctness and print compliance.
Examples: page size matches manifest, fonts are embedded, metadata is present, PDF/X compliance.
Asset checks (pre-build)
Run on image and font files anywhere in your project folder — there's no assets convention to follow (see Chapter 3) — plus the directories any shared stylesheets pull assets from.
Examples: image resolution below 300 DPI, unsupported color profiles, missing font files.
Heuristic checks (post-build)
Quality proxies that catch common issues not detectable from structure alone.
Examples: ink coverage estimates, page count anomalies, unexpected blank pages.
Manifest Configuration
Add a validate section to enable/disable which checks run. Check
thresholds (like the ink-coverage limit below) are configured under their
own top-level manifest keys, not under validate::
validate:
# Disable specific checks
checks:
pdf.structure.qpdf: false
source.accessibility.alt-text: false
# A PDF cannot open a relative link such as [spec](docs/spec.md) — the
# build drops its href before print and `source.links.dangling` warns
# about each one. Switch it off for a book published only as HTML.
source.links.dangling: false
# Disable built-in source linting (markdown / HTML) if not needed
source:
htmlhint: false
markdownlint: false
# Ink-coverage / TAC threshold — this is a TOP-LEVEL manifest key, not
# nested under `validate:`. There is no `validate.thresholds` key; unknown
# keys are silently ignored, so a threshold nested under `validate:` has no
# effect at all.
ink:
maxTac: 240 # max total area coverage %; the dtrpg preset's default —
# book (this guide's preset) defaults to 400 instead
tacTolerance: 0.5 # allowed overage before a page is flagged, default 0.5
Automatic Tool Detection
Most checks run in-process and never need an external tool. Only a few still probe for a system tool and warn if it is missing:
warn Tool "qpdf" not found — skipping: pdf.print.pdfx-markers, pdf.print.pdfx-metadata
warn Tool "gs" not found — skipping: pdf.print.ink-coverage, asset.image.tac-raster
Everything else — page size, fonts, images/DPI, bookmarks, links, page labels, text density, color space, alpha, markdown/HTML linting — runs with no system dependency, so you can validate immediately on any machine.
File Organization Best Practices
Recommended project structure
my-book/
├── manifest.yaml
├── 00-frontmatter/
│ ├── title.md
│ ├── credits.md
│ └── toc.md
├── 01-introduction.md
├── 02-chapter-one.md
├── 99-backmatter/
│ └── appendix.md
├── assets/
│ ├── images/
│ ├── fonts/
│ └── diagrams/
└── styles/
├── variables.css
└── custom.css
Naming conventions
Files: Number for explicit ordering (01-intro.md), descriptive names, all lowercase with hyphens.
Images: Include chapter prefix (ch03-combat-example.png), version suffix when needed (map-v2.png).
Production Workflow
1. Write and preview
gutterpress preview ./my-book
Keep the preview open in Chrome and edit source files. The browser reloads automatically on save.
2. Validate source
gutterpress validate --input ./my-book
Fix any pre-build issues — missing alt text, broken links, oversized assets — before building.
3. Build and validate PDF
gutterpress build ./my-book --format pdfx
This runs the full pipeline including both pre-build and post-build validation.
4. Preflight for submission
gutterpress preflight --pdf dist/my-book/my-book-pdfx.pdf --target dtrpg
Generates deterministic JSON and markdown reports suitable for archival or submission evidence.
Testing Checklist
Before final print submission:
- [ ] All images are at least 300 DPI
- [ ] All fonts embed correctly in the PDF
- [ ] Page size matches the printer's required trim size
- [ ] Bleed extends 0.125in beyond the trim edge on full-bleed pages
- [ ] No unexpected blank pages (the blank pages at the end of a
dtrpgbook are intentional) - [ ] Running headers are correct on all pages
- [ ] Page numbers start at the right page and run correctly
- [ ] Validate passes for every destination you publish to (
--target dtrpg,itch, or your manifest'stargets:list)
Common Issues
Blank pages
Caused by excess @page markers. Audit your source files for unnecessary page breaks — bare @page between short adjacent sections is the most common culprit.
Blank pages at the very end of a dtrpg book are expected: the build pads the
book to a multiple of 4 pages and keeps the last page empty for DriveThruRPG's
printer information. To turn that off, set print.reserveLastPage: false in
manifest.yaml.
Page break issues
- Don't force breaks unnecessarily
- Don't use
<br>tags for vertical spacing - Don't rely on specific pagination — let flow happen naturally and use
@sectionto prevent unwanted breaks
Font issues
- A missing font is a build error naming the file — check the
url(...)path in your CSS is correct and relative to that CSS file - Test on a clean machine to catch fonts that only happen to be installed locally
Color shift
Test the CMYK conversion before submitting to a printer. Adjust RGB values if colors look wrong in the converted output.