Orange Book
End-to-end projects

Project seven, PDF toolkit

Separate HTML-to-PDF, asynchronous upload processing, and heavy native work with R2, Queue, and a job-status API.

Edited and verified by Orange Book Editorial Team ·

PROJECT 07INTERMEDIATEabout 60 minutesOutcome: a recoverable PDF job flow

Completion criteria

HTML or a URL can produce a PDF; an uploaded object creates an asynchronous job; users can query queued, running, done, and failed; duplicate messages do not create conflicting results; oversized, encrypted, timed-out, and processor-failure cases are explicit.

Prerequisites and companion example

The companion implements one real, bounded html-to-pdf operation first. Unit tests replace the Browser Run binding completely, so they need no account and consume no remote quota:

node --test examples/pdf-tool/test/index.test.mjs

Nine tests should pass, covering tenant-scoped status, a minimal Queue message, unsafe-HTML rejection, D1 failure compensation, PDF signatures, deterministic output keys, duplicate delivery, retry, and final failure. The full Worker also needs D1, R2, a primary and dead-letter Queue, and Browser Run. browser.remote is true as required for current Quick Actions local development, so starting the complete flow requires Cloudflare login and consumes Browser Run usage.

cp examples/pdf-tool/.dev.vars.example examples/pdf-tool/.dev.vars
pnpm wrangler d1 migrations apply orange-book-pdf-jobs --local --config examples/pdf-tool/wrangler.jsonc
pnpm wrangler dev --config examples/pdf-tool/wrangler.jsonc

Processing capability boundary

This example turns safe HTML into a PDF. It does not claim to merge, split, OCR, or parse arbitrary uploaded PDFs. Route those tasks to a separately limited and audited Container or external processor.

Classify the PDF task first

TaskCloudflare starting pointRequest model
Generate invoice, report, or certificate from HTML/URLBrowser Run /pdfA short request can trigger it; store the result in R2
Summarize, index, or extract after uploadR2 event notification + Queue + WorkerAsynchronous
Merge, split, OCR, complex native library, large fileEvaluate a Container or external processorAsynchronous with resource limits

Browser Run /pdf accepts url or html. It renders controlled pages into documents; it is not a general-purpose PDF editor. Cloudflare's official post-upload summarization tutorial uses R2 event notifications, Queues, and Workers AI, which demonstrates why file processing should be decoupled from the upload request.

Job state model

D1 stores job ID, tenant, input key, operation, status, attempt, output key, and error category. R2 stores input and output objects. Queue messages remain small and contain stable IDs and controlled object keys, not full files or secrets.

await env.PDF_JOBS.send({
  jobId,
  tenantId,
  inputKey,
  operation: 'extract-text',
});

return Response.json(
  { jobId, status: 'queued' },
  { status: 202 },
);

The consumer reloads parameters from the trusted job record and writes a unique output key. On duplicate delivery, it checks job/version. A completed job is acknowledged without overwriting a different-version result.

Complete the processing flow

Create an upload contract

Authenticate the user, bound the declared size, allow application/pdf, and generate a tenant-scoped object key and job ID. The browser never chooses the bucket or an arbitrary target path.

Verify after upload

Read object metadata from R2 and check actual size, media type, ownership, and job relationship. Encrypted, corrupted, or unsupported PDFs enter an explicit failure state.

Dispatch to the correct processor

HTML generation uses Browser Run. Text extraction or summarization is evaluated against current Worker and model limits. Native binaries, long CPU, or large memory move to a Container or external processor. Do not use chunking to hide a runtime mismatch.

Store a recoverable result

Write output to a versioned R2 key, then conditionally mark the job done. If the database update fails, retry discovers the same output. A delayed cleanup job handles orphan objects.

Expose status and download

The status endpoint returns jobs for the current user/tenant only. Download reauthorizes, applies media type, filename, and cache policy, and never reveals the internal bucket key.

Security and cost

Input

Bound size, page count or processing time, and reject unsupported or encrypted files.

Isolation

Object key, job query, and output all use a trusted tenant boundary.

Abuse

Upload, generation, status polling, and download use separate rate controls and business quotas.

Privacy

Logs exclude document body; temporary objects have retention and deletion paths.

If you add summarization or classification, it is an optional AI step inside the processor. PDF generation, storage, authorization, and download continue to work when AI is disabled.

Verification matrix

Test a valid small file, empty file, wrong MIME, oversized file, encrypted/corrupt file, duplicate message, processor timeout, R2 write failure, D1 update failure, cross-tenant job ID, and deleted output. Prove at least once that duplicate consumption does not create two business results.

Verification and troubleshooting

SymptomCheck firstRecovery
Job creation returns invalid_or_unsafe_htmlScript, iframe, external URL, or size violationUse a self-contained controlled template without active content
Response is queue_unavailableProducer binding and Queue existKeep the failed job record and explicitly create a new job after recovery
Job stays runningConsumer, retries, and Browser Run usageInspect Queue/Browser metrics; do not resubmit blindly
Final state is processor_failedBrowser response, input object, and quotaFix the cause and create a new version; never overwrite the old output key

Remote boundary and rollback

Browser Run bindings, R2 event notifications, Queue consumers, Containers, and remote buckets change external state. This tutorial does not create them automatically. Code rollback cannot retract old messages already in the queue. Include a message version and move unsupported versions to a failed/dead-letter state. Deletion first revokes download access and marks expiry, then removes input and output after a delay.

Next: run the production release checklist.

Primary sources

Did this page help you complete your goal?

Beta feedback is generated in this browser and is never uploaded automatically.

On this page