Resume parser PHP: what exists, the code, and when to call an API
Published
If you are looking for a resume parser PHP developers can install today, the honest answer is that PHP has excellent document readers and no maintained resume parser. smalot/pdfparser will hand you the text of a CV, PHPWord will hand you the text of a .docx, and both are well kept — but neither knows what a job title is. The one PHP package that promised resume fields is archived. So the real decision is not which library to composer require; it is whether you write the field extraction yourself or call something that returns typed JSON. This article gives you the code for both routes, and the point where the second one wins.
Every version, date and price below was read from Packagist, GitHub or the vendor's own pricing page as of September 2026. We build ResumeJSON, a per-parse resume parsing API, so read the last section as written by an interested party — the library facts are checkable with one composer show, and you should check them.
The PHP packages, compared
| Package | Latest stable | PHP it declares | What it returns | Maintained? |
|---|---|---|---|---|
| smalot/pdfparser | v2.12.5, 17 April 2026 | >= 7.1 | Text, pages and metadata from a PDF | Yes |
| phpoffice/phpword | 1.4.0, 5 June 2025 | ^7.1 | ^8.0 | Text and structure from DOCX, ODF, RTF, HTML | Yes |
| praem90/Resume-Parser | — | not stated | Name, email, phone, date of birth | No — repository archived 11 February 2025 |
| sharpapi/laravel-resume-parser | — | >= 8.1, Laravel >= 10.48.29 | Structured resume JSON, via SharpAPI's hosted API | Yes, as an API client |
Two columns decide this for you. "What it returns" is the gap you will be filling yourself: the two maintained libraries are document readers, so everything between "here is 4,000 characters of text" and "here is this candidate's third-most-recent employer" is your code. "Maintained?" is the risk you are taking on, and it is a sharper risk here than in most library choices, because a resume parser is a pile of heuristics about how humans format documents, and those formats keep moving.
Reading the file: the part PHP does well
Whatever route you choose, you need the document's text, and this is a solved problem in PHP. smalot/pdfparser describes itself as a library that "can read and extract information from pdf file", has been installed over 48 million times, and is pure PHP with no system binary to deploy:
composer require smalot/pdfparseruse Smalot\PdfParser\Parser;
$text = (new Parser())->parseFile('/path/to/cv.pdf')->getText();For .docx, PHPWord is the equivalent — "a pure PHP library for reading and writing word processing documents", 44 million installs, covering OOXML, ODF, RTF and HTML:
$phpWord = \PhpOffice\PhpWord\IOFactory::load('/path/to/cv.docx');
$text = '';
foreach ($phpWord->getSections() as $section) {
foreach ($section->getElements() as $element) {
if (method_exists($element, 'getText')) {
$text .= $element->getText() . "\n";
}
}
}Two caveats worth knowing before you build on this. smalot/pdfparser's own documentation says secured documents and form data are not supported. And neither library touches a scanned CV: a PDF that is a photograph of a page contains no text layer, so getText() correctly returns almost nothing. If your upload form is open to the public, a meaningful share of what arrives is scanned, and text extraction alone will silently return empty candidates for those files. That "silently" is the dangerous part — an empty result looks exactly like a CV with nothing in it.
The archived PHP resume parser, and why it is still worth reading
Search this topic and you land on praem90/Resume-Parser, a PHP project whose README says it parses resumes for "fields like name, email, ph, date of birth" across doc, docx, xlsx, pptx and pdf. It is real and it is small — 17 stars, 9 commits — and its GitHub page states it was archived on 11 February 2025. Archived means read-only: no fixes, no new formats, no security patches.
Read it anyway, because it shows you the shape of the work honestly. Names, emails, phone numbers and dates of birth are the fields regular expressions can reach. Everything a hiring workflow actually sorts on — employer, title, the start and end dates of each role, which role is current, how many years of experience that adds up to — needs structure this approach does not have. That is why the archived package stops where it does, and it is the same wall you hit the afternoon you decide to write your own.
Writing your own: what it really costs
If you extract with smalot/pdfparser and then match fields yourself, budget for these, in roughly this order of pain:
- Section detection. "EXPERIENCE", "Work history", "Professional Experience", or no heading at all because the CV is a two-column design whose reading order is interleaved.
- Date ranges.
Mar 2022 – Present,03/2022-now,2022–, and the ones written in a language that is not English. - Which role is current. An open-ended range usually means current, until a CV lists two of them.
- Deduplication. One employer appearing as "Monzo", "Monzo Bank Ltd" and "Monzo (contract)".
- Scanned files, per the caveat above — this is where an OCR dependency arrives in your Dockerfile.
None of that is impossible. It is simply a body of work that does not get finished, because the input is human formatting, and it is worth being clear-eyed that the maintenance never ends rather than discovering it in month four.
Calling an API from PHP instead
The other route is to keep PHP doing what PHP is good at — receiving the upload, running the workflow — and let something else return the fields.
If you are on Laravel, sharpapi/laravel-resume-parser is a maintained client for SharpAPI's hosted parser: MIT licensed, PHP >= 8.1, Laravel >= 10.48.29, and it accepts eleven formats including flattened, image-based PDFs. Note its flow before you design around it: parseResume() dispatches a job and returns a status URL, and you then poll with fetchResults($statusUrl) until the status is success. That is a good fit for a queued import and a poor one for a web request a person is waiting on. Its pricing is per plan rather than per parse — as of September 2026 the published tiers are Build at $50, Launch at $200 and Scale at $500 a month, quoted in words per month with an annual discount, plus a custom Enterprise tier.
ResumeJSON is the per-parse, synchronous shape of the same idea, which is the difference that matters in a Controller: one HTTP call, one response, no job table. It is sold on RapidAPI, so the call is a plain POST with your RapidAPI key:
$response = Http::withHeaders([
'X-RapidAPI-Key' => config('services.resumejson.key'),
'X-RapidAPI-Host' => config('services.resumejson.host'),
])->post('https://<your-rapidapi-host>/v1/parse', [
'text' => $text, // from smalot/pdfparser, or send the file instead
]);
$resume = $response->json('resume');
$currentRole = collect($resume['work'])->firstWhere('is_current', true);You can skip the extraction step entirely and forward the upload as multipart/form-data with a file part — the type is detected from the file's own bytes, so an upload your framework labelled application/octet-stream still works. What comes back is a typed document: basics, work, education, skills, certifications, languages and total_years_experience, with a field the CV does not state returned as null rather than guessed. Try it with no signup on the free parser, and the comparison page covers how it lines up against the enterprise vendors.
So which one should you pick?
- Stay with
smalot/pdfparserand PHPWord alone if you need text, not fields — full-text search over a CV archive, keyword screening, or feeding a model you already run. - Write your own extraction if a name, an email and a phone number are genuinely all you need, or if the documents come from one internal source in one format that you control.
- Call an API once you need employers, titles and dated roles out of CVs whose formatting you do not control — which is every public upload form. Poll-based clients suit queued imports; a per-parse synchronous call suits a request a user is waiting on.
The PHP question turns out not to be a PHP question at all. The language has the readers it needs; what nobody in this ecosystem maintains is the résumé-shaped part. Decide whether that part is your product, and the rest follows.
For the same map in another language, see Python resume parser: the libraries that work and Open source resume parser: what works, and when to buy instead.