PHP has three practical paths to PDF-to-JPG: Imagick (the cleanest), Ghostscript via shell_exec (the most widely installed), or the ChangeThisFile API (zero local dependencies). Imagick is the right default for any server that has ImageMagick — which most shared hosting and VPS images include. GD won't read PDFs without GS under the hood anyway. The API is the no-install escape hatch.

Method 1: Imagick (ImageMagick PHP extension)

Imagick wraps ImageMagick and can render PDF pages directly. It delegates to Ghostscript internally, but you don't have to call GS yourself.

# Verify Imagick is installed
php -r "echo extension_loaded('imagick') ? 'ok' : 'not found';"
pingImage($pdfPath);
    $pageCount = $imagick->getNumberImages();
    $imagick->clear();

    $paths = [];
    for ($i = 0; $i < $pageCount; $i++) {
        $page = new Imagick();
        $page->setResolution($dpi, $dpi);
        $page->readImage("{$pdfPath}[{$i}]");
        $page->setImageFormat('jpeg');
        $page->setImageCompressionQuality(90);
        $page->setColorspace(Imagick::COLORSPACE_SRGB);

        $outPath = rtrim($outDir, '/') . '/page-' . str_pad($i + 1, 3, '0', STR_PAD_LEFT) . '.jpg';
        $page->writeImage($outPath);
        $page->clear();
        $paths[] = $outPath;
    }
    return $paths;
}

$pages = pdfToJpg('document.pdf', './pages', 150);
echo sprintf("Wrote %d pages\n", count($pages));

Key settings:

  • setResolution() must come before readImage() — Imagick reads at native resolution if you set DPI after loading.
  • COLORSPACE_SRGB — prevents weird color shifts on PDFs with CMYK content.
  • quality(90) — sweet spot for web use; 85 is also fine for thumbnails.
  • [N] suffix — loads a specific zero-indexed page. readImage('file.pdf') without a suffix loads ALL pages at once, which can OOM on large PDFs.

Method 2: GD + Ghostscript (shell_exec)

PHP's GD extension can't read PDFs. But if Ghostscript is installed, you can use shell_exec to render pages to PNG, then load with GD for further processing.

# Check Ghostscript
gs --version
&1";
    $output = shell_exec($cmd);

    // Convert PNGs to JPG using GD
    $pngs = glob($outDir . '/page-*.png');
    sort($pngs);
    $paths = [];
    foreach ($pngs as $pngPath) {
        $img = imagecreatefrompng($pngPath);
        if (!$img) continue;

        $jpgPath = str_replace('.png', '.jpg', $pngPath);
        // Fill white background (PNGs from GS may have transparency)
        $bg = imagecreatetruecolor(imagesx($img), imagesy($img));
        imagefill($bg, 0, 0, imagecolorallocate($bg, 255, 255, 255));
        imagecopy($bg, $img, 0, 0, 0, 0, imagesx($img), imagesy($img));
        imagejpeg($bg, $jpgPath, 90);
        imagedestroy($img);
        imagedestroy($bg);
        unlink($pngPath);
        $paths[] = $jpgPath;
    }
    return $paths;
}

$pages = pdfToJpgViaGs('document.pdf', './pages', 150);
echo sprintf("Wrote %d pages\n", count($pages));

The -dSAFER flag restricts Ghostscript's filesystem access — always include it when processing user-supplied PDFs. This approach is slower than Imagick (two passes: GS render + GD encode) but works on any server with Ghostscript.

Method 3: ChangeThisFile API (Guzzle, no installs)

No Imagick, no Ghostscript? The ChangeThisFile API runs Poppler server-side. Free tier: 1,000 conversions/month, no card required. The endpoint is the SDK — one POST with your file and target format.

composer require guzzlehttp/guzzle
 120]);
    $response = $client->post('https://changethisfile.com/v1/convert', [
        'headers' => ['Authorization' => 'Bearer ' . $apiKey],
        'multipart' => [
            ['name' => 'file', 'contents' => fopen($pdfPath, 'r'), 'filename' => basename($pdfPath)],
            ['name' => 'target', 'contents' => 'jpg'],
        ],
    ]);

    $contentType = $response->getHeader('Content-Type')[0] ?? '';
    $body = $response->getBody()->getContents();

    // Multi-page PDFs return a zip
    if (str_contains($contentType, 'application/zip')) {
        $zipPath = sys_get_temp_dir() . '/ctf_pages.zip';
        file_put_contents($zipPath, $body);
        $zip = new ZipArchive();
        $zip->open($zipPath);
        $zip->extractTo($outDir);
        $zip->close();
        unlink($zipPath);
        $paths = glob($outDir . '/*.jpg');
        sort($paths);
        return $paths;
    }

    // Single page
    $outPath = rtrim($outDir, '/') . '/page-001.jpg';
    file_put_contents($outPath, $body);
    return [$outPath];
}

$pages = pdfToJpgApi('document.pdf', './pages', 'ctf_sk_your_key_here');
echo sprintf("Wrote %d pages\n", count($pages));

No Guzzle? Use PHP's native curl instead:

 true,
        CURLOPT_TIMEOUT => 120,
        CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => [
            'file' => new CURLFile($pdfPath, 'application/pdf', basename($pdfPath)),
            'target' => 'jpg',
        ],
    ]);
    $result = curl_exec($ch);
    curl_close($ch);
    return $result; // binary JPG or ZIP
}

When to use each

ApproachBest forTradeoff
ImagickServers with ImageMagick installed, multi-page PDFsNeeds php-imagick extension + ImageMagick + Ghostscript
GD + GhostscriptServers where Imagick is unavailable but GS is installedTwo-pass (slower), shell_exec must be enabled
ChangeThisFile APIZero local deps, shared hosting, serverless PHPNetwork latency, 25MB file limit on free tier

Production tips

  • Always set resolution before readImage() in Imagick. Setting DPI after loading the page has no effect — the page was already rasterized at 72 DPI.
  • Process pages one at a time for large PDFs. Loading all pages at once via readImage('file.pdf') can exhaust PHP's memory_limit. Loop through page indices instead.
  • Increase PHP memory_limit for high-DPI rendering. A 300 DPI A4 page takes ~25MB in memory. Set memory_limit = 256M in php.ini or ini_set() at the top of your script.
  • Use set_time_limit(0) for multi-page PDFs. A 50-page PDF at 200 DPI can take 30+ seconds. PHP's default 30s limit will kill it.
  • Sanitize filenames from user uploads. Never pass user-supplied filenames directly to shell_exec or Imagick's readImage. Use basename() and strip non-alphanumeric characters before constructing paths.
  • Check Imagick's policy.xml. Some distros ship ImageMagick with PDF reading disabled (rights="none") for security. Edit /etc/ImageMagick-7/policy.xml and change the PDF pattern's rights to "read|write".

For most PHP stacks, Imagick is the right call — it's likely already installed and handles multi-page PDFs cleanly. For minimal server environments, the API needs nothing beyond curl. Get a free API key covering 1,000 conversions/month.