Generating a PDF in a web app is easy. Generating a PDF that a customer can confidently print, fold, and hand out on the same day is a different problem.
We learned this while building a browser-based funeral program workflow. The emotional context makes the engineering constraints unusually visible: users are often working under time pressure, the information must be accurate, the photos matter, and a layout failure may not be discovered until a local print shop has already started the job.
This article is not about one PDF library. It is about the product and architecture decisions that turned “download a PDF” into a dependable print workflow.
A PDF is not the final state
Our first mental model was too simple:
- Collect form data.
- Render a document.
- Download a PDF.
The better model is a state machine:
- Collect content.
- Validate facts and required fields.
- Build a deterministic layout plan.
- Check text, images, page count, and print imposition.
- Let the user review a complete proof.
- Freeze the content state associated with payment.
- Generate and deliver the paid artifact.
Each transition has a different failure mode. Treating them as separate states made both the code and the user interface easier to reason about.
Separate errors from warnings
A binary “valid or invalid” model was not sufficient. We use three user-facing states:
- Pass: no blocking errors and no unresolved warnings.
- Needs Review: the document can technically be produced, but a human decision is required.
- Cannot Export: a problem would make the output incomplete or unreliable.
Missing a full name, service date, time, venue, or required layout choice is an error. So is placeholder text such as TBD or [Name Here]. A photo that may look soft in print is different: it deserves a warning, because the user may have no higher-resolution copy.
This distinction matters. If every warning blocks progress, a time-sensitive tool becomes frustrating. If nothing blocks progress, the tool quietly produces bad files.
Warnings should also be explicit. When a user accepts a resolution warning, store that decision as part of the document state. Do not make the warning disappear without a trace.
Validate the rendered model, not only the form
Form validation catches missing values, but it cannot tell you whether an obituary fits in its frame or whether a text box overlaps a decorative element.
Our checker therefore validates the same layout plan used by the renderer. For every page, it can inspect whether:
- a text box extends outside the printable page area;
- text remains inside the expected template frame;
- content overlaps an “avoid” region reserved for decoration or binding;
- the rendered text overflows its box;
- the final font size falls below the minimum allowed for print;
- the number of rendered pages matches the selected format.
This is a general lesson for document apps: validation should operate on the production representation. A form can be valid while its output is broken.
Image dimensions are only the first check
At upload time, we reject unsupported formats and flag images whose smallest dimension is below a basic threshold. That gives immediate feedback, but it is not the whole print calculation.
The important number is effective pixels per inch in the final layout:
effective PPI = source pixels used / printed size in inches
A 1000-pixel-wide image may be acceptable in a small portrait slot and unacceptable as a full-page background. Cropping changes how many source pixels are actually available. The checker must therefore evaluate the image against its placed rectangle, not just its original dimensions.
We also learned to describe the result in human terms. “Photo may print softly in this position” is more useful than exposing a raw pixel count with no consequence.
Page order is part of validation
A four-page folded program is not printed in reading order. The physical sheet requires an imposed order so the pages appear correctly after duplex printing and folding.
That means page imposition is not a post-processing detail. It is part of the document contract. Our checker verifies that an imposition mapping exists for the selected format and that the renderer produced the expected logical page count.
The handoff instructions also need to match the file: letter-size paper, double-sided printing, short-edge flipping, actual-size scaling, and a center fold. A technically valid PDF can still fail if the print instructions are ambiguous.
Show the proof before payment
For a personalized document, payment should not be the moment when the user first discovers the final layout.
We let users build and review a complete watermarked proof before checkout. That changes the transaction from “pay and hope” to “approve and unlock.” It also creates a clean engineering boundary: the content hash associated with checkout represents the document the user reviewed.
The content hash includes the relevant memorial data, program selections, referenced photos, crop information, and photo metadata in a stable order. Entitlement checks can then verify that a paid download corresponds to the expected content instead of granting a generic export permission.
Persist before redirecting to checkout
External checkout redirects are a dangerous place to depend on browser memory alone. Tabs close. Mobile browsers reclaim memory. Webhooks may arrive after the original session is gone.
Before creating checkout, we persist the project, selected plan, normalized email, referenced photos, and content hash. Only after that succeeds do we create the checkout session.
This gives the webhook enough information to generate the paid file independently. The browser return page becomes a convenience, not a critical part of fulfillment.
Treat delivery as a retryable job
Payment completion, PDF generation, object storage, and email delivery should not be one fragile request.
Our delivery flow records the paid purchase, creates or reuses a generation job, and tracks statuses such as queued, generation running, PDF generation failed, email failed, and email sent. Retry logic is limited to known recoverable states.
The generated PDF and print instructions are stored privately, and the email contains a tokenized download URL. We also record checksums and byte counts for stored artifacts so support can distinguish “email did not arrive” from “file was not generated.”
This state model is more work than calling generatePdf() inside a webhook, but it is dramatically easier to support.
Privacy belongs in the product architecture
The document may contain names, dates, service details, family notes, obituary text, and photos. That should affect defaults and boundaries.
During local editing, draft content and uploaded photos can remain in the browser. Paid checkout and delivery require server-side processing, so the transition should be explicit. Card fields are handled by the payment provider rather than stored by the application. AI-assisted writing is also a separate disclosure boundary because submitted facts and wording must be sent to the configured AI provider.
“Privacy” is not one checkbox. It is a map of which data moves where at each product state.
The reusable lessons
If you are building any user-generated document product, the following rules are worth adopting:
- Validate the rendered layout, not only the input schema.
- Distinguish blocking errors from warnings that require human judgment.
- Calculate image quality using final placement, not source dimensions alone.
- Model print imposition and handoff instructions as product requirements.
- Show the complete proof before asking for payment.
- Persist the source state before redirecting to an external checkout.
- Bind paid access to a deterministic content hash.
- Make generation and delivery idempotent, observable, and retryable.
- Explain every failure with a direct path back to the field or layout section that can fix it.
The biggest shift was realizing that a printable document is not merely a file. It is the result of a workflow that must preserve accuracy, layout integrity, payment state, delivery reliability, and user trust.
The product used for this case study is Funeral Program Maker, and the public PDF checker demonstrates the validation approach.
Disclosure: I work on Funeral Program Maker, the product used as the case study in this article.
Top comments (0)