Use aiohttp for the asynchronous HTTP endpoint and Playwright’s Chromium browser to render the HTML, measure its laid-out height, and create a PDF with that height as a custom page size. Return the resulting bytes with Content-Type: application/pdf. This avoids ordinary Letter or A4 pagination, but it is appropriate only when you want one tall page; for a conventional multipage document, use a standard paper size instead.
How the full-height PDF flow works
aiohttp handles the web request; it does not lay out HTML or create PDFs. For browser-rendered HTML, Playwright supplies that missing rendering step: its page.pdf() method returns a PDF buffer and accepts page dimensions with units, margins, print backgrounds, and other print settings. By default, PDF rendering uses print CSS.
The core sequence is:
- Prepare HTML at a known layout width and remove unwanted default margins.
- Render it in Chromium and wait for the content, fonts, images, and data that affect layout.
- Measure the rendered document height.
- Pass that height and the intended width to
page.pdf(), using zero margins if the page should reach its edges. - Return the bytes from an aiohttp handler as
application/pdf.
The result is a PDF page whose height follows the measured document, rather than a series of standard-size sheets. This is useful for receipts, reports, or long-page exports when one continuous page is wanted. It is not the same as a normal multipage PDF: some viewers and printers handle unusually tall pages poorly, and a long page can be awkward to navigate or print.
Install the dependencies
Install aiohttp and Playwright in the Python environment used by your app, then install Playwright’s Chromium browser:
#1 Best Overall
- INNOVATIVE CARTRIDGE-FREE PRINTING — No more dealing with lots of tiny ink cartridges; With this wireless document and photo printer each ink bottle set is equivalent to about 90 individual cartridges²
- LESS FREQUENT INK REPLACEMENT — Replacement ink bottles don't have to be changed nearly as often as ink cartridges¹; When you choose this combination printer, scanner and copier you can print up to 4,500 pages black/7,500 color³
- COLOR PRINTING — Up to 2 years of ink in the box4 (and with every replacement ink set) for fewer out-of-ink frustrations
- ZERO CARTRIDGE WASTE — By using an Epson EcoTank printer you can help reduce the amount of cartridge waste ending up in landfills
- HOME PRINTER DESIGNED FOR RELIABILITY — The Epson EcoTank ET-2800 All-in-One Supertank Color Printer creates vivid, detailed prints and documents thanks to Micro Piezo Heat-Free Technology; Fire off 10 ISO pages per minute1 to easily finish large jobs
python -m pip install aiohttp playwright
python -m playwright install chromium
Run the browser-install command in the same deployment environment as the application. Installing the Python package alone does not install the browser binary.
Runnable aiohttp and Playwright example
This minimal application serves a fixed HTML document at /document.pdf. It keeps one Chromium process open for the application lifetime, creates an isolated browser context for each request, waits for fonts and images before measuring, and closes the context even if rendering fails.
from aiohttp import web
from playwright.async_api import async_playwright
HTML = """<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
@page { margin: 0; }
html, body { margin: 0; padding: 0; }
body {
width: 800px;
font-family: Arial, sans-serif;
overflow-wrap: break-word;
}
main { padding: 24px; }
</style>
</head>
<body>
<main>
<h1>Example report</h1>
<p>Rendered HTML content.</p>
</main>
</body>
</html>"""
async def browser_context(app):
playwright = await async_playwright().start()
browser = await playwright.chromium.launch()
app["browser"] = browser
try:
yield
finally:
await browser.close()
await playwright.stop()
async def pdf_handler(request: web.Request) -> web.Response:
browser = request.app["browser"]
context = await browser.new_context(viewport={"width": 800, "height": 1000})
try:
page = await context.new_page()
page.set_default_timeout(15_000)
await page.set_content(HTML, wait_until="domcontentloaded")
# Wait for layout-affecting assets before measuring.
await page.evaluate("""async () => {
await document.fonts.ready;
await Promise.all(Array.from(document.images, img =>
img.decode().catch(() => undefined)
));
}""")
height_px = await page.evaluate(
"Math.ceil(document.documentElement.scrollHeight)"
)
if height_px < 1 or height_px > 20_000:
raise web.HTTPRequestEntityTooLarge(
max_size=20_000, actual_size=height_px
)
pdf_bytes = await page.pdf(
width="800px",
height=f"{height_px + 2}px",
margin={"top": "0px", "right": "0px",
"bottom": "0px", "left": "0px"},
print_background=True,
prefer_css_page_size=False,
)
finally:
await context.close()
return web.Response(
body=pdf_bytes,
content_type="application/pdf",
headers={"Content-Disposition": 'inline; filename="document.pdf"'},
)
app = web.Application()
app.cleanup_ctx.append(browser_context)
app.router.add_get("/document.pdf", pdf_handler)
web.run_app(app, host="127.0.0.1", port=8080)
Start the server with python app.py (using the filename in which you saved the code), then open http://127.0.0.1:8080/document.pdf. The example caps measured height at 20,000 CSS pixels as a simple safeguard; choose a limit appropriate to your service and expected documents. If you want a download prompt rather than inline display, use an attachment disposition instead.
The example’s HTML is fixed and trusted. If you accept HTML or URLs from requesters, validate input and restrict what the browser can access; otherwise the renderer can be abused to consume resources or reach internal network services. Do not interpolate arbitrary user input into an HTML template without escaping or sanitizing it.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Rank #2
- CARTRIDGE-FREE PRINTING — Print lab-quality photos, graphics and creative projects; Get vibrant colors and sharp text with Epson's high-accuracy printhead and Claria ET Premium 6-color inks
- INK BOTTLES — Save on photos1 and creative projects with affordable in-house printing; All-in-one printer allows you to print 4" x 6" photos for about 4 cents each vs. 40 cents with traditional ink cartridges1
- LESS FREQUENT INK REPLACEMENT — Replacement ink bottles don't have to be changed nearly as often as ink cartridges¹; Printer, scanner and copier lets you print up to 6,200 color pages³
- PRINT FOR LONGER — Up to 2 years of ink in the box² (and with every replacement ink set) for fewer out-of-ink frustrations with this wireless printer
- ZERO CARTRIDGE WASTE — Epson EcoTank printer helps reduce the amount of cartridge waste ending up in landfills; Cartridge-free printer uses high-yield ink bottles; Each replacement ink bottle set is equivalent to about 100 individual ink cartridges⁴
Measure the document accurately
Choose a stable width first
Height depends on width: narrower content wraps into more lines and becomes taller. Set the CSS width deliberately, remove browser-default body margins, and use the same width when calling page.pdf(). If the document’s intended design is responsive, choose a viewport and width that match the output you want rather than relying on incidental defaults.
Wait for everything that changes layout
page.set_content() supports readiness milestones including commit, domcontentloaded, load, and networkidle. Pick the earliest one that guarantees the required content is available. For data-driven pages, wait for the application’s own ready selector or promise; for external images and fonts, wait for them explicitly before measuring. A font swap or late-loading image can change line wrapping and height after measurement.
networkidle is not always the best choice: pages with analytics, polling, or other persistent network activity may never reach it. A targeted readiness check is often more dependable. Conversely, a document that loads assets asynchronously can appear ready at domcontentloaded while still missing content, so do not measure solely because that event fired.
Use the measured size with units
Playwright accepts explicit page width and height with units such as pixels, inches, centimeters, or millimeters. The example measures CSS pixels and passes them as pixels, with a small two-pixel allowance to reduce clipping from fractional layout. CSS print pixels are 96 per inch, so another option is to convert a measured height to inches with height_px / 96. Whichever approach you choose, check the resulting PDF with the browser version and fonts used in deployment.
Rank #3
- SET IT UP ONCE AND PRINT WITH CONFIDENCE. No complicated maintenance. Just easy, reliable printing you can count on.
- INK FOR YEARS. NOT MONTHS. Up to 2 years of ink included. Get thousands of pages of cartridge-free printing. More pages, less hassle
- KEEPS PRINTING WELL AFTER COMPETITORS HAVE QUIT. No complex maintenance. Sharper text, richer colors.[2] Only with HP Smart Tank
- PREMIUM SUPPORT - Strong technical expertise to solve issues faster
- THE LAST PRINTER YOU'LL EVER NEED. Enjoy years of refillable, cartridge-free printing.
document.documentElement.scrollHeight is a useful document-level measure, but unusual layouts may need a different root element or a more specific measurement. Fixed-position elements, overflowing children, collapsed margins, and content outside the normal document flow can make a simple height measurement differ from what you expect. Test the actual templates, especially at the bottom edge.
Print CSS, screen CSS, and pagination
By default, page.pdf() uses print media CSS. That may hide navigation, alter colors, or apply print-specific layout rules. If the screen stylesheet is the design you want to preserve, call await page.emulate_media(media="screen") before generating the PDF. Set print_background=True when background colors or images must be included; otherwise they may be omitted from print output.
For an intentionally continuous page, set explicit dimensions and zero margins, and inspect your CSS for page-break rules such as break-before or break-inside. For ordinary printable reports, omit the custom height and specify a standard paper format such as A4 or Letter instead. A standard paper size gives the browser a paginated layout and is usually a better fit for documents readers need to print or navigate page by page.
Return the PDF safely from an aiohttp endpoint
An aiohttp response can send the in-memory PDF buffer directly; there is no need to save it to disk for this pattern. Set content_type="application/pdf" so clients recognize the file. The sample uses an inline content disposition, which allows many browsers to open the document in their PDF viewer. Use an attachment disposition if your product should prompt a download.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteRank #4
- Wireless Bluetooth Printer: Portable thermal printer compatible with iPhone, Android phones, iPad and tablet computers via Bluetooth. For smartphones, please download the "Nada Print" App. You can also connect to laptops and computers for printing using a USB-C cable. (Note: Laptops and computers can only be connected via USB and require the installation of a driver first. Bluetooth connection is not supported.)
- No-ink printing: Only supports US Letter and A4 size thermal paper.(Doesn't support regular paper) The no-ink portable thermal printer uses direct thermal technology, requiring no ink, toner or ribbons, making it environmentally friendly, cost-effective and time-saving. The thermal printer package comes with a roll of US Letter thermal printing paper. Note: When installing the paper, remember to switch the paper size switch on APP
- Clear Print: NDYIN N80 portable thermal printer adopts high-definition printing technology, with a 203DPI resolution to provide you with clear printing results. This mobile printer is compatible with roll paper, folded paper and tattoo transfer paper, supporting printing from your mobile phone PDF, Word, pictures and web pages anytime and anywhere. It is recommended to use our NDYIN thermal paper to achieve good printing quality
- Portable wireless printer for travel: The thermal printer is equipped with a built-in 1500mAh rechargeable battery, which can print 160 sheets of 8.5" x 11" thermal paper after being fully charged. It weighs only 1.5 pounds and is compact in size. This ink-free portable printer can be easily carried in a backpack or briefcase! It is perfect for business travel, cars, small offices, construction sites, schools and homes. You can print documents, contracts, invoices and boarding passes anytime and anywhere
- The N80 thermal printer has a wide range of uses. The package includes the N80 printer, a roll of US Letter paper(7m/roll), a user manual, a guide card, a type-C soft cable and a type C adapter. Note: The charging adapter is not included. Special thermal paper is required for use; ordinary paper cannot be used. This ink-free portable thermal printer is suitable for various scenarios such as home, school, travel, office, and outdoor, meeting the printing needs of different groups of people. This tattoo template printer is also compatible with tattoo transfer paper, making it an ideal choice for tattoo art
For a production endpoint, add request authentication if documents are private, validate the inputs that determine HTML and rendering options, and set explicit limits on input size, asset loading, rendering time, and computed page height. Log rendering failures without logging sensitive document contents. Reuse a browser process or pool rather than launching Chromium for every request, but isolate request-specific pages and contexts so cookies, storage, and state do not leak between users. Use try/finally cleanup for pages, contexts, and browser lifecycle so failures do not leave resources open.
Choose the renderer that fits the source
| Renderer | Best fit | Trade-off |
|---|---|---|
| Playwright | Modern HTML/CSS or JavaScript where browser rendering fidelity matters; supports explicit PDF dimensions, print settings, and page ranges. | Requires deploying and managing a browser runtime. Use it when browser behavior is part of the output you need. |
| WeasyPrint | Mostly static HTML and CSS that can use its Python API, HTML(...).write_pdf(...), with CSS @page rules and in-memory bytes. |
It is not the browser-JavaScript rendering route described here; choose it when the document can be rendered without that behavior. |
| ReportLab | PDFs built from programmatically placed text, tables, charts, and drawing primitives. | You define the document structure and placement rather than relying on browser HTML layout. |
There is no universal performance winner established for these approaches. Startup cost, asset handling, CSS complexity, JavaScript needs, and throughput vary with your templates and deployment, so benchmark representative documents if performance is important.
Or skip the browser setup
If your goal is to capture an existing website rather than render custom Python HTML, ScreenshotNeo offers a website screenshot API that can return screenshots or PDFs. This example is a screenshot request saved as WebP; it is not a claim that the output is a custom-height PDF. See the ScreenshotNeo API documentation for PDF options and request details.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
ScreenshotNeo accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; those steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The Free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 shots.
Sign up for ScreenshotNeo’s free plan: 1,000 screenshots a month, no card required.
Troubleshooting
- The PDF is clipped at the bottom. An image, font, or application data may have finished loading after measurement, or the measured root may not include overflowing content. Wait for the relevant assets or application-ready condition, measure the element that contains the full document, and inspect the last few pixels of the output.
- The PDF is unexpectedly paginated or looks different from the browser. Print media rules or page-break CSS may be affecting the render. Check
@media print,@page, and break properties; use screen media only if that is genuinely the intended design. - Backgrounds or colors are missing. Set
print_background=Trueand check whether print styles alter or suppress those colors. - The request hangs waiting for content. A persistent connection may prevent
networkidlefrom occurring. Use an appropriate earlier milestone and wait for a specific selector, event, or asset readiness condition instead. - Chromium will not launch in deployment. Confirm that the Chromium binary was installed in the same environment as Playwright and that the runtime permits the browser’s required system dependencies. Check the launch exception in server logs.
- Requests slow down or exhaust resources under load. Rendering is resource-intensive. Reuse a browser process, limit concurrent work, cap document dimensions and input sizes, and configure timeouts. Measure representative jobs before setting operational limits.
- Untrusted input triggers internal requests or excessive work. Do not let an unrestricted renderer navigate arbitrary URLs or load arbitrary resources. Validate content and constrain outbound network access in the rendering environment.
Frequently Asked Questions
Will text in a Playwright-generated PDF remain selectable?
Yes. Playwright generates a PDF from the rendered page rather than flattening it into a screenshot image, so ordinary browser-rendered text is generally selectable. Text that is itself part of an image is not.
Can I use this for a PDF made from a URL instead of HTML I already have?
Yes. Navigate a Playwright page to the URL, wait for the page-specific content and assets you need, then measure and call page.pdf(). Treat external navigation as untrusted input unless the endpoint restricts destinations and network access.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.




