PHP powers over 75% of the web. If you're running a CMS like WordPress, Drupal, or Laravel, you've almost certainly needed to convert uploaded documents — invoices, contracts, reports, user submissions — into Markdown for search indexing, LLM processing, or content migration.
This post shows you how to integrate AnyMD document conversion into PHP projects using cURL, with proper error handling, batch processing, and patterns that drop cleanly into any PHP framework.
Why AnyMD from PHP?
PHP's built-in document handling is minimal. PhpWord reads DOCX but outputs raw text that discards structure. PDF parsing in PHP requires shelling out to pdftotext. Tika integration adds a Java dependency. AnyMD gives you a single curl call that converts any format — DOCX, PDF, PPTX, XLSX, ODT, RTF, EPUB — into clean GitHub-Flavored Markdown.
In benchmarks, a PHP (cURL) client adds ~8.1 ms of client-side overhead per conversion — slightly more than Node.js (7.2 ms) but well within tolerance for typical web request cycles. With curl_multi_exec, PHP achieves ~310 docs/minute across 10 concurrent files.
Prerequisites
You need PHP 8.1+ with the curl and json extensions enabled (both are standard in most distributions). No external libraries required — we'll use PHP's built-in cURL functions.
Installation check:
php -m | grep -E 'curl|json'
If both appear, you're ready.
Single document conversion
Here's a minimal PHP function that converts any supported document to Markdown:
<?php
class AnyMDClient
{
private string $apiUrl = 'https://anymd.net/api/convert';
private string $apiKey;
public function __construct(string $apiKey)
{
$this->apiKey = $apiKey;
}
public function convert(string $filePath): string
{
$ch = curl_init($this->apiUrl);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $this->apiKey,
],
CURLOPT_POSTFIELDS => [
'file' => new CURLFile($filePath),
],
CURLOPT_TIMEOUT => 30,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
throw new RuntimeException("cURL error: $error");
}
if ($httpCode !== 200) {
throw new RuntimeException(
"API error (HTTP $httpCode): $response"
);
}
return $response;
}
}
// Usage:
$client = new AnyMDClient('your-api-key');
$markdown = $client->convert('/path/to/report.docx');
file_put_contents('report.md', $markdown);
AnyMD auto-detects the input format from the file extension and returns GitHub-Flavored Markdown with headings, lists, tables, images, and links preserved.
Error handling and retries
Production code needs resilient error handling. Here's an enhanced version with exponential backoff:
<?php
class AnyMDClientWithRetry
{
private string $apiKey;
private int $maxRetries = 3;
public function __construct(string $apiKey)
{
$this->apiKey = $apiKey;
}
public function convertWithRetry(string $filePath): ?string
{
$attempt = 0;
while ($attempt < $this->maxRetries) {
$ch = curl_init('https://anymd.net/api/convert');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $this->apiKey,
],
CURLOPT_POSTFIELDS => [
'file' => new CURLFile($filePath),
],
CURLOPT_TIMEOUT => 60,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
$attempt++;
if ($attempt >= $this->maxRetries) {
error_log("AnyMD: failed after $attempt attempts: $error");
return null;
}
sleep(pow(2, $attempt));
continue;
}
// Rate-limited — wait and retry
if ($httpCode === 429) {
$retryAfter = 5;
preg_match('/Retry-After: (\d+)/i', $response, $m);
if (!empty($m[1])) {
$retryAfter = (int)$m[1];
}
sleep($retryAfter);
$attempt++;
continue;
}
if ($httpCode !== 200) {
error_log("AnyMD: HTTP $httpCode on $filePath: $response");
return null;
}
return $response;
}
return null;
}
}
Batch processing with curl_multi
When you need to convert many files — migrating a CMS, processing uploads, or building a training dataset — sequential conversion is too slow. PHP's curl_multi_exec handles concurrent requests efficiently:
<?php
class AnyMDBatchConverter
{
private string $apiKey;
public function __construct(string $apiKey)
{
$this->apiKey = $apiKey;
}
/**
* Convert multiple files concurrently.
* @param string[] $files Map of output paths => input paths
* @return array<string, string> Map of output paths => markdown
*/
public function convertBatch(array $files): array
{
$mh = curl_multi_init();
$handles = [];
$results = [];
foreach ($files as $outPath => $inPath) {
$ch = curl_init('https://anymd.net/api/convert');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $this->apiKey,
],
CURLOPT_POSTFIELDS => [
'file' => new CURLFile($inPath),
],
CURLOPT_TIMEOUT => 60,
]);
curl_multi_add_handle($mh, $ch);
$handles[(int)$ch] = ['ch' => $ch, 'out' => $outPath];
}
$running = 0;
do {
curl_multi_exec($mh, $running);
curl_multi_select($mh);
} while ($running > 0);
foreach ($handles as $id => $info) {
$response = curl_multi_getcontent($info['ch']);
$httpCode = curl_getinfo($info['ch'], CURLINFO_HTTP_CODE);
if ($httpCode === 200 && $response !== false) {
$results[$info['out']] = $response;
} else {
error_log("AnyMD batch: $httpCode on {$info['out']}");
}
curl_multi_remove_handle($mh, $info['ch']);
curl_close($info['ch']);
}
curl_multi_close($mh);
return $results;
}
}
// Convert 20 files at once:
$converter = new AnyMDBatchConverter('your-api-key');
$files = [];
foreach (glob('/path/to/documents/*.docx') as $i => $doc) {
$outPath = "/path/to/output/doc_{$i}.md";
$files[$outPath] = $doc;
}
$results = $converter->convertBatch($files);
echo "Converted " . count($results) . " files\n";
WordPress integration
If you're on WordPress, you can hook into upload processing to auto-convert documents:
<?php
/**
* Auto-convert uploaded DOCX/PDF files to Markdown
* and store the result as post meta.
*/
add_action('add_attachment', function (int $postId) {
$file = get_attached_file($postId);
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
$supported = ['docx', 'pdf', 'pptx', 'xlsx', 'odt', 'rtf', 'epub'];
if (!in_array($ext, $supported, true)) {
return;
}
$apiKey = defined('ANYMD_API_KEY') ? ANYMD_API_KEY : '';
if (empty($apiKey)) {
return;
}
try {
$client = new AnyMDClient($apiKey);
$markdown = $client->convert($file);
update_post_meta($postId, '_anymd_markdown', $markdown);
update_post_meta($postId, '_anymd_converted', time());
} catch (Throwable $e) {
error_log("AnyMD WordPress: {$e->getMessage()}");
}
});
This gives you searchable Markdown for every uploaded document — perfect for building a RAG knowledge base from your WordPress media library.
Laravel integration
For Laravel projects, here's a clean service class:
<?php
namespace App\Services;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
class AnyMDService
{
private PendingRequest $client;
public function __construct()
{
$this->client = Http::baseUrl('https://anymd.net/api')
->withToken(config('services.anymd.api_key'))
->timeout(60)
->retry(3, 1000);
}
public function convert(string $disk, string $path): ?string
{
$fullPath = Storage::disk($disk)->path($path);
$filename = basename($path);
$response = $this->client->attach(
'file', file_get_contents($fullPath), $filename
)->post('/convert');
if ($response->successful()) {
return $response->body();
}
\Log::error('AnyMD conversion failed', [
'path' => $path,
'status' => $response->status(),
]);
return null;
}
}
Register the service in config/services.php:
'anymd' => [
'api_key' => env('ANYMD_API_KEY'),
],
Format support
AnyMD converts these formats to clean Markdown:
| Format | Extensions | Notes |
|---|---|---|
| DOCX | .docx | Full formatting, tables, images |
.pdf | Text extraction with layout preservation | |
| PPTX | .pptx | Slide content as structured sections |
| XLSX | .xlsx | Tables with merged cell support |
| ODT | .odt | Full OpenDocument support |
| RTF | .rtf | Rich text to clean Markdown |
| EPUB | .epub | Chapter structure preserved |
| HTML | .html, .htm | Semantic normalization |
| CSV | .csv | Formatted as Markdown tables |
Pricing
| Plan | Pages/month | Max file size | Price |
|---|---|---|---|
| Free | 100 | 10 MB | $0 |
| Starter | 500 | 25 MB | $19 |
| Pro | 5,000 | 50 MB | $99 |
| Enterprise | Custom | Custom | Custom |
Get your free API key — 100 pages/month, no credit card required.