PHP cURL downloads PDF bytes; it does not select or export pages. A reliable workflow is therefore two-stage: use cURL to fetch and validate the source PDF, then pass the saved file to a PDF-aware tool such as qpdf or FPDI. The examples below show both approaches, including range validation, transfer checks, preservation caveats and recovery paths.
What the workflow actually does
cURL is a network-transfer client. It can write a response to disk or return it as a string, but it has no PDF page-range option. Page extraction must be performed after the download by a PDF processor.
- Request the source URL with cURL.
- Check transport errors, HTTP status and whether the response is actually a PDF.
- Save the bytes to a controlled temporary or output directory.
- Select pages with qpdf or import them into a new document with FPDI.
- Validate the generated file, page count and any document features your application requires.
This separation also makes failures easier to diagnose: a 404, HTML login page or truncated transfer is a download problem, while a parser exception or wrong page range is an extraction problem.
Prerequisites and page-number rules
- PHP with the cURL extension enabled.
- A writable directory for temporary and final files.
- Either the qpdf executable available to the PHP process or Composer-installed FPDI with a compatible PDF generator.
- Permission to retrieve and transform the source document.
For qpdf, ordinary page numbers start at 1 and range endpoints are inclusive. Thus 2-4 means pages 2, 3 and 4. qpdf also supports comma-separated selections, reversed ranges and ranges relative to the end of a document. Validate user input before constructing a command.
#1 Best Overall
Download the PDF safely with PHP cURL
Writing directly to a file avoids holding a large document in memory. The function below checks cURL’s transfer result, HTTP status and the response content type, while still allowing servers that omit or misstate that header when the file signature is valid.
<?php
function downloadPdf(string $url, string $destination): void
{
$fp = fopen($destination, 'wb');
if ($fp === false) {
throw new RuntimeException('Cannot open destination for writing.');
}
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_FILE => $fp,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_CONNECTTIMEOUT => 20,
CURLOPT_TIMEOUT => 120,
CURLOPT_USERAGENT => 'PDF-page-export/1.0',
CURLOPT_FAILONERROR => false,
]);
$ok = curl_exec($ch);
$error = curl_error($ch);
$status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
curl_close($ch);
fclose($fp);
if ($ok === false) {
@unlink($destination);
throw new RuntimeException('cURL transfer failed: ' . $error);
}
if ($status < 200 || $status >= 300) {
@unlink($destination);
throw new RuntimeException("Source returned HTTP $status");
}
$handle = fopen($destination, 'rb');
$signature = $handle ? fread($handle, 5) : false;
if (is_resource($handle)) {
fclose($handle);
}
if ($signature !== '%PDF-') {
@unlink($destination);
throw new RuntimeException("Response is not a PDF (Content-Type: $contentType)");
}
}
$source = 'https://example.com/document.pdf';
$input = __DIR__ . '/storage/source.pdf';
downloadPdf($source, $input);
Do not treat a successful curl_exec() alone as proof of a valid PDF. A successful transfer may contain an access-denied page, a sign-in form or an application error encoded as HTML. The status code, file signature and, where useful, MIME inspection should be checked in application code.
Option 1: extract pages with qpdf
qpdf is a concise choice when your deployment permits installing and executing a command-line dependency. Its page-selection syntax maps directly to the requested pages.
Run a fixed, trusted range
qpdf input.pdf --pages input.pdf 2-4 -- selected.pdf
The first input is the source document. The --pages section identifies the pages to copy, and the final path is the new PDF. The endpoints are inclusive.
Free tools Windows power users keep installed
One-click scans. No signup required.
Invoke qpdf from PHP without shell-injection risk
<?php
function exportWithQpdf(string $input, string $output, string $pageSpec): void
{
if (!preg_match('/A[0-9]+(?:-[0-9]+)?(?:,[0-9]+(?:-[0-9]+)?)*z/', $pageSpec)) {
throw new InvalidArgumentException('Invalid page specification.');
}
$command = sprintf(
'qpdf %s --pages %s %s -- %s 2>&1',
escapeshellarg($input),
escapeshellarg($input),
escapeshellarg($pageSpec),
escapeshellarg($output)
);
exec($command, $lines, $exitCode);
if ($exitCode !== 0 || !is_file($output) || filesize($output) === 0) {
throw new RuntimeException(
'qpdf failed (exit ' . $exitCode . '): ' . implode("n", $lines)
);
}
}
exportWithQpdf(__DIR__ . '/storage/source.pdf', __DIR__ . '/storage/selected.pdf', '2-4');
For a web application, prefer a configured absolute executable path, a dedicated low-privilege account and an allow-list for accepted page syntax. Never concatenate an untrusted filename or arbitrary command text.
Rank #2
Ranges, ordering and document-level data
qpdf can select comma-separated pages and ranges, including reversed ranges and ranges counted from the end. Confirm the exact syntax against the qpdf version installed on your server. qpdf documents that outlines, tags and other document-level information are taken from the primary input and that support for document-level data associated with individual pages is not complete. If bookmarks, accessibility tags, forms or metadata are business-critical, inspect the result rather than assuming they all follow the selected pages.
Option 2: import pages with FPDI
FPDI is appropriate when extraction is part of a PHP PDF-generation workflow or when you need to place imported page templates into a newly generated document. FPDI is documented for use with FPDF and also with TCPDF or tFPDF; it is also a fixed dependency in mPDF. Install the combination your project supports with Composer, then create the output document in PHP.
Composer setup
composer require setasign/fpdi:^2 tecnickcom/tcpdf
Choose a compatible generator for your application. The following example uses TCPDF.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesImport a validated page range
<?php
require __DIR__ . '/vendor/autoload.php';
use setasignFpdiTcpdfFpdi;
function exportWithFpdi(string $input, string $output, int $first, int $last): void
{
$pdf = new Fpdi();
$pageCount = $pdf->setSourceFile($input);
if ($first < 1 || $last < $first || $last > $pageCount) {
throw new InvalidArgumentException(
"Requested pages $first-$last; document contains $pageCount pages."
);
}
$pdf->SetCreator('PDF-page-export');
$pdf->SetAutoPageBreak(false);
for ($pageNumber = $first; $pageNumber <= $last; $pageNumber++) {
$templateId = $pdf->importPage($pageNumber);
$size = $pdf->getTemplateSize($templateId);
$orientation = $size['width'] > $size['height'] ? 'L' : 'P';
$pdf->AddPage($orientation, [$size['width'], $size['height']]);
$pdf->useTemplate($templateId);
}
$pdf->Output($output, 'F');
}
exportWithFpdi(
__DIR__ . '/storage/source.pdf',
__DIR__ . '/storage/selected-fpdi.pdf',
2,
4
);
setSourceFile() returns the source page count, and importPage() accepts a one-based page number. The default import box is the CropBox. The output loop preserves each imported page’s dimensions instead of forcing every page into one fixed letter or A4 size.
Links, annotations and interactive features
Importing visible page content does not guarantee that every annotation or interactive feature is copied. FPDI’s external-link option defaults to false; enabling it can copy URI-action link annotations when supported by the integration. Test forms, JavaScript, comments, embedded files, signatures, bookmarks and metadata separately if they matter to your use case. Some encrypted, malformed or unusual PDFs may require a different parser or a source-side re-export.
Choosing qpdf or FPDI
| Requirement | Prefer qpdf | Prefer FPDI |
|---|---|---|
| Shortest page-range command | Yes; selection is expressed directly as ranges and lists. | More application code is required. |
| Binary installation allowed | Requires qpdf installed and executable by PHP. | Uses Composer dependencies inside the PHP project. |
| Compose pages into a generated PDF | Limited to qpdf’s documented selection workflow. | Strong fit; imported templates can be placed on newly created pages. |
| Document-level preservation | Verify outlines, tags and other data; not all page relationships are preserved. | Verify annotations and interactive features; import is not universal. |
Neither approach should be treated as a promise of identical preservation for every PDF. Keep representative files from your real source systems and verify the properties your users need.
Validation, reliability and operational safeguards
- Reject ranges outside the page count before extraction.
- Use unique temporary filenames and delete partial files after any failure.
- Set connection and total timeouts; do not allow a remote server to hold a PHP worker indefinitely.
- Limit maximum download size before production use, using streaming and server controls appropriate to your environment.
- Log the source URL, HTTP status, parser name, requested pages and exit or exception details, but avoid logging secrets embedded in URLs.
- Open the generated PDF with a validator or a trusted viewer and check its page count.
- Use representative PDFs containing portrait and landscape pages, links, forms, encryption and unusual fonts where those occur in production.
Troubleshooting common failures
cURL reports a timeout or connection error
Check DNS, outbound firewall rules, TLS certificates and the remote server’s response time. Increase the timeout only when the source is known to be slow; do not remove timeouts entirely.
The download is HTTP 200 but qpdf or FPDI says the file is invalid
Inspect the first bytes and response headers. A login page, bot challenge or proxy error can be returned with status 200. Authenticate using the source system’s supported mechanism, then repeat the signature check.
“Page does not exist” or an empty output
Remember that page numbering starts at 1. Obtain the actual page count with FPDI’s setSourceFile() or a PDF inspection tool, then reject a range whose final page exceeds it. For qpdf, verify commas, hyphens and relative-range syntax.
qpdf cannot be executed
Confirm that the binary is installed, the PHP user can execute it and the configured path is correct. Capture stderr, use escapeshellarg() for every path and check the process exit code.
Rank #4
FPDI throws a parser or licensing-related exception
Check that the Composer packages and PHP versions are compatible. Some PDFs use structures a particular parser cannot import; try qpdf for page selection or obtain a normalized copy from the producing system. Do not suppress the exception and publish a partial file.
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 minuteLinks or bookmarks disappeared
That is a preservation issue, not a cURL issue. Test the required annotations and document-level structures explicitly. FPDI’s external-link setting and qpdf’s documented handling of document-level data may not match your source document’s needs.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If what you actually need is a clean image or PDF capture of a web page rather than extracting pages from an existing PDF, ScreenshotNeo provides a single HTTP request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and responses identify the page verdict and billing status. Its MCP server lets Claude, Cursor and other MCP clients call take_screenshot, get_page_info and capture_pdf.
See the ScreenshotNeo API documentation for parameters and response handling.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is included on every plan. Create a free ScreenshotNeo account.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →FAQ
Can cURL extract pages without installing another tool?
No. cURL transfers the PDF; a PDF-aware library or executable must perform page selection.
Can I extract a page from a password-protected PDF?
Only when the supplied tool and credentials support that document’s encryption permissions. Handle passwords securely and test the specific file.
Does extracting pages preserve digital signatures?
Do not assume so. Creating a new PDF generally changes the signed document; verify signatures and compliance requirements with your application owner.
Frequently Asked Questions
Can cURL extract pages without installing another tool?
No. cURL transfers the PDF; a PDF-aware library or executable must perform page selection.
Can I extract a page from a password-protected PDF?
Only when the supplied tool and credentials support that document’s encryption permissions. Handle passwords securely and test the specific file.
Does extracting pages preserve digital signatures?
Do not assume so. Creating a new PDF generally changes the signed document; verify signatures and compliance requirements with your application owner.
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.

