DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix Now×
Skip to content
Debugging

How to Fix Puppeteer PDFs That Won’t Open After a Supabase Upload

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A PDF that opens before upload but fails after download usually points to a byte-preservation, object-selection, or access-path problem—not automatically to Puppeteer. Test the file at three boundaries: immediately after page.pdf(), after Supabase reports a successful upload, and after downloading the exact object. The first boundary that fails identifies where to concentrate your fix.

Use a three-boundary test before changing code

Keep the original bytes and test each stage independently. Puppeteer’s Page.pdf() API returns binary PDF data as a Uint8Array. Supabase Storage then receives a file body, stores an object, and serves that object through a public URL, an authorized download, or a signed URL. Those are separate operations.

Checkpoint Test If it fails
Generated artifact Write the result of page.pdf() to disk and open or validate it. Investigate page readiness, PDF options, Puppeteer/Chrome, and runtime versions.
Upload boundary Preserve the returned bytes, set the MIME type, and inspect the complete Storage response. Investigate conversion, serialization, permissions, MIME validation, size limits, bucket, and object path.
Retrieved artifact Download the exact object and compare its length and hash with the local file. Investigate access method, wrong object, overwrite, caching, or response handling.

This sequence prevents a successful HTTP request from being mistaken for a valid PDF.

1. Prove that Puppeteer generated a valid PDF

Save the bytes before involving Supabase. A minimal Node.js check looks like this:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import puppeteer from 'puppeteer';
import { writeFile } from 'node:fs/promises';
import { createHash } from 'node:crypto';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.goto('https://example.com', { waitUntil: 'networkidle0' });
  const pdfBytes = await page.pdf({
    format: 'A4',
    printBackground: true,
    waitForFonts: true,
    path: undefined
  });
  await writeFile('before-upload.pdf', pdfBytes);
  console.log({
    type: pdfBytes.constructor.name,
    length: pdfBytes.byteLength,
    sha256: createHash('sha256').update(pdfBytes).digest('hex')
  });
} finally {
  await browser.close();
}

Open before-upload.pdf with a desktop viewer or another PDF validator. If it is unreadable now, Supabase has not yet been involved. Confirm that navigation completed, the intended content is present, and page.pdf() completed without an exception.

Check print-media and readiness settings

Puppeteer generates PDFs with print CSS by default. If the site’s layout depends on screen CSS, call await page.emulateMediaType('screen') before generating the file. The PDF options include format or explicit page dimensions, printBackground, timeout, waitForFonts (documented default: true), and path. These influence rendering, timing, appearance, and file placement; they do not repair bytes later corrupted during upload.

For a reproducible test, use a small static HTML document, record the installed Puppeteer and Chrome versions, and generate the same file twice. A historical issue report described an unreadable PDF after upgrading from Puppeteer 22.15.0 to 23.0.0 on Windows with Node 20.15.0. That is an individual report, not proof of a general regression. Treat it as a reason to compare versions only after a local reproduction.

2. Keep PDF bytes intact at the Supabase boundary

Do not decode the PDF as ordinary UTF-8 text, interpolate it into a string, or JSON-serialize it as if it were text. Pass the binary result (or a compatible binary wrapper) directly to Storage. Supabase’s JavaScript upload API accepts a file body and an options object; explicitly provide contentType: 'application/pdf'.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option
import { createClient } from '@supabase/supabase-js';
import { createHash } from 'node:crypto';

const supabase = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_SERVICE_ROLE_KEY // keep this key server-side
);

const bucket = 'documents';
const objectPath = `invoices/${crypto.randomUUID()}.pdf`;
const pdfBytes = await page.pdf({ format: 'A4', printBackground: true });

const upload = await supabase.storage
  .from(bucket)
  .upload(objectPath, pdfBytes, {
    contentType: 'application/pdf',
    upsert: false
  });

console.log({
  uploadData: upload.data,
  uploadError: upload.error,
  inputLength: pdfBytes.byteLength,
  inputSha256: createHash('sha256').update(pdfBytes).digest('hex')
});
if (upload.error) throw upload.error;

Check the exact @supabase/supabase-js version in your application. Runtime compatibility can differ when a Uint8Array is wrapped in a Node Buffer, a Blob, or an ArrayBuffer. If the installed SDK requires a different accepted body type, convert it without interpreting the data as text:

const body = Buffer.from(pdfBytes); // binary copy; no UTF-8 conversion
const result = await supabase.storage
  .from(bucket)
  .upload(objectPath, body, { contentType: 'application/pdf' });

Use one representation consistently and log its byte length before upload. A zero-length value, a shortened buffer, or a string is an immediate red flag.

3. Treat upload success and file validity as different checks

Always inspect the full returned object, especially error. A completed JavaScript request does not prove that the object was stored. Supabase documents distinct failures for invalid MIME types, a missing bucket or object, authorization, and file-size limits. Correct the reported error instead of diagnosing byte corruption.

  • Invalid MIME type: check bucket policies and send application/pdf.
  • Authorization or permission: use a key and Storage policy that permit this operation; do not expose a service-role key in browser code.
  • Missing bucket/object: verify the bucket name and exact path, including case and prefixes.
  • Size limit: inspect the response and reduce the document or adjust the permitted limit where appropriate.

Use unique object paths while debugging. Reusing a path can make an older object, an overwrite, or a cached response look like a new upload.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

4. Download through the correct Supabase route

Public bucket

For a public bucket, call getPublicUrl with the same bucket and object path, then request the returned URL. Supabase also documents a download query parameter that can prompt a browser download. Verify the response status and copy the response body as bytes, not text.

const { data: publicData } = supabase.storage
  .from(bucket)
  .getPublicUrl(objectPath);
if (!publicData?.publicUrl) throw new Error('No public URL returned');

const response = await fetch(publicData.publicUrl);
if (!response.ok) throw new Error(`Download failed: ${response.status}`);
const downloaded = new Uint8Array(await response.arrayBuffer());

Private bucket

A private object is not available through a public URL. Use the JavaScript download() method with an authorized client, or create a time-limited signed URL and fetch that URL before it expires.

const { data, error } = await supabase.storage
  .from(bucket)
  .download(objectPath);
if (error) throw error;
const downloaded = new Uint8Array(await data.arrayBuffer());

An HTTP 401 or 403 is an access problem, not evidence of a malformed PDF. A 404 usually means the bucket or path is wrong (or the object is private and you used a public route).

5. Compare downloaded bytes with the generated file

Length and SHA-256 comparisons quickly separate storage corruption from rendering problems:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
import { readFile } from 'node:fs/promises';
import { createHash } from 'node:crypto';

const original = new Uint8Array(await readFile('before-upload.pdf'));
const downloaded = new Uint8Array(await (await fetch(downloadUrl)).arrayBuffer());
const hash = bytes => createHash('sha256').update(bytes).digest('hex');
console.log({
  originalLength: original.byteLength,
  downloadedLength: downloaded.byteLength,
  originalSha256: hash(original),
  downloadedSha256: hash(downloaded),
  identical: Buffer.compare(Buffer.from(original), Buffer.from(downloaded)) === 0
});

If lengths or hashes differ, inspect body conversion, object-path selection, overwrite behavior, cache responses, and the retrieval code. If they match but viewers reject the file, return to Puppeteer and test the original artifact with a different viewer or validator. A Content-Type of application/pdf is useful metadata, but changing a header cannot repair malformed bytes.

Troubleshooting by symptom

Symptom Likely boundary Action
Local file will not open Generation Use minimal HTML, verify navigation and fonts, inspect PDF options, and record Puppeteer/Chrome versions.
Upload returns an error Request or Storage policy Log the complete error; fix MIME, authorization, bucket/path, or size limits.
Upload says success, download is 401/403 Access route Use an authorized download or signed URL for private buckets.
Download is 404 Object selection Confirm bucket, path, case, and whether the upload used a different prefix.
Downloaded length/hash differs Upload or retrieval Remove text conversions, avoid accidental overwrite, bypass stale caches, and fetch the exact object.
Bytes match but viewer rejects Generation or viewer Validate the local artifact, test another viewer, and reproduce with pinned versions.

Reliability practices for production

  • Generate a unique path and retain the original byte length and hash in logs.
  • Fail the job when either upload or download verification returns an error.
  • Keep browser and Supabase credentials server-side; expose only signed links when clients need access.
  • Use explicit timeouts for navigation and PDF generation, and wait for required selectors or fonts before capture.
  • Run a small end-to-end canary after dependency upgrades, especially Puppeteer or bundled Chrome upgrades.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your actual requirement is a clean screenshot or PDF of a web page rather than a custom Puppeteer workflow, ScreenshotNeo provides a single API request. It accepts consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify the page verdict and billing status.

cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
require('node:fs').writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));

See the ScreenshotNeo API documentation for PDF output and the other capture options. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf. Every plan includes all features; 1,000 shots per month are free with no card, and paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Frequently asked questions

Can a correct MIME type make an invalid PDF open?

No. It helps clients interpret a valid file, but it cannot repair damaged or incomplete PDF bytes.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Should I make the Supabase bucket public to test?

Not necessarily. A private bucket can be tested with an authorized download or signed URL; changing exposure can introduce an unrelated security risk.

What should I pin during a Puppeteer upgrade?

Record both the Puppeteer package version and the Chrome or Chromium executable version, then compare a minimal locally generated PDF before and after the upgrade.

Frequently Asked Questions

How can I tell whether a browser viewer is the problem?

Compare the downloaded file’s hash with the locally opened file and test the same bytes in a second PDF viewer or validator.

Is a 200 response enough to trust the downloaded PDF?

No. Check the response body length and hash against the bytes returned by Puppeteer.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.