ResumeJSON

Skills taxonomy: which one to use, and how to map CV skills

Skills taxonomy: a shared vocabulary for what people can do

A skills taxonomy is a controlled list of skills where every skill has one id, one preferred name, a set of alternative names, and a place in a hierarchy. It is what lets software treat Postgres, PostgreSQL and postgres db as the same thing, and treat Python and Java as two different things that both sit under programming. You can adopt a public one (ESCO and O*NET are free to consult, Lightcast offers API access to a larger one on request) or keep a small one of your own. Whichever you pick, the work that makes it useful is the mapping step: turning the free-text skills a CV contains into ids from that list.

This article is for the developer building a job board, an applicant tracking system or a matching feature who has just discovered that raw skill strings do not compare. It covers what a taxonomy contains, how the three best-known public ones differ, when your own list is the better answer, and the code for the mapping step. We build ResumeJSON, a resume parsing API that returns skills as the strings the CV uses, so read the parts about parsing as written by an interested party. The taxonomy choice is yours either way.

Why raw skill strings are not enough

Parse a hundred CVs and count the skills, and you will find the same ability spelled a dozen ways. JS, Javascript, JavaScript (ES6+) and Node/JS all turn up. A candidate who writes React.js fails a filter written for React. A recruiter searching for machine learning misses the person who wrote ML.

A string comparison measures how people spell, and a taxonomy lets you measure what they can do. Everything downstream depends on that difference:

What a skills taxonomy contains

The public taxonomies differ in size and scope, but the parts are the same. When you evaluate one, check that it has each of these, because the mapping code needs them.

PartWhat it isWhy the mapping needs it
IdA stable identifier per skill, often a URIThe value you store and compare
Preferred labelThe one canonical nameWhat you show a user
Alternative labelsSynonyms, abbreviations, old spellingsWhat you match a CV string against
HierarchyCategory, subcategory, skillRolling up and suggesting related skills
Links to occupationsWhich skills a role typically needsBuilding a vacancy profile from a job title
LanguagesLabels in more than one languageMatching CVs not written in English
VersioningReleases with a changelogKnowing when your stored ids go stale

Alternative labels are the part that does most of the work. A taxonomy with a thousand well-curated synonyms will match more CVs than one with twice the skills and no synonyms.

The public skills taxonomies compared

The figures below are taken from each publisher's own pages as of September 2026.

ESCOO*NETLightcast Skills
PublisherEuropean CommissionUS Department of Labor sponsoredLightcast, a labor market data company
Size13,939 skills, 3,039 occupations1,016 occupational titles, with skills described per occupation34,000+ skills
Languages28English, with Spanish career toolsNot stated on the page we read
StructureSkills linked to occupationsOccupations first, with a content model of knowledge, skills, abilities and tasksCategory, subcategory, skill
AccessPortal free of charge, download or APIOnLine site, database and web services APIsBrowse free; API access on request
Best forMultilingual and European hiringUS occupations and role profilesFine-grained, current technical skills

ESCO

ESCO describes itself as "the European multilingual classification of Skills, Competences and Occupations". Its own page lists 3,039 occupations and 13,939 skills, translated into 28 languages, and says it "can be consulted free of charge", with the latest version available by download or through the ESCO API.

Pick ESCO when your candidates write CVs in more than one European language. Each skill carries labels in every supported language, so a German CV and a Spanish CV can map to the same id. The trade-off is granularity for fast-moving technical skills: check that the frameworks your vacancies ask for are actually in it before you commit.

O*NET

O*NET is built around occupations rather than a flat skills list. Its overview describes "over 900 occupation profiles" and a taxonomy of 1,016 occupational titles aligned to the 2018 Standard Occupational Classification, with a content model that covers knowledge, skills, abilities, work activities and tasks. The database is updated quarterly and reachable through web services APIs.

**Pick O*NET when the job title is your anchor**, for example when you want to build a default skill profile for a vacancy from its title, or report on a US talent pool by occupation. Its skills are described per occupation rather than as a list of named tools, so for matching a candidate's Kubernetes to a vacancy's Kubernetes you will likely pair it with a finer list.

Lightcast Skills

Lightcast's page describes a collection of "34,000+ skills" drawn from job postings and profiles, grouped into categories and subcategories, with a changelog. You can browse the whole library for free, and API access is by request. The page also says nonprofits working for a public good can register for full access free of charge.

Pick Lightcast when technical granularity matters most and you are prepared to go through their access process. Of the three, it is the one most likely to already contain the tool a vacancy names this year.

When your own list is enough

A public taxonomy is a large dependency. For many products a list you write yourself is the honest answer:

Start with your own list and borrow from a public one. Take the alternative labels ESCO or Lightcast publish for the skills you care about, and keep the ids yours. You can add a column holding the public id later if you need to exchange data.

How to map resume skills to a taxonomy

The mapping is the same whichever taxonomy you choose. You have strings from a CV and a list of skills with labels; you want ids.

  1. Get the skills out of the CV as a list. A parser that returns one skill per entry saves you splitting Python, Django, PostgreSQL yourself. Our extract skills from a resume article compares the ways to get that list.
  2. Normalise both sides the same way. Lowercase, trim, collapse whitespace, and strip trailing version noise such as (advanced).
  3. Match exactly against preferred and alternative labels first. This catches most of the list, and it is the step you can trust.
  4. Fall back to a fuzzy match with a threshold, and store the score beside the id so a reviewer can see which mappings were guesses.
  5. Keep what did not match. An unmatched string is either a skill your taxonomy lacks or noise. Both are worth a report, and neither should vanish.

Here is that mapping in TypeScript over a small taxonomy of your own. The same code works over ESCO or Lightcast once you load their labels into the same shape.

type Skill = { id: string; label: string; aliases: string[] };

const TAXONOMY: Skill[] = [
  { id: "sk-postgres", label: "PostgreSQL", aliases: ["postgres", "postgresql", "psql"] },
  { id: "sk-js", label: "JavaScript", aliases: ["javascript", "js", "ecmascript"] },
  { id: "sk-react", label: "React", aliases: ["react", "react.js", "reactjs"] },
];

const norm = (s: string) =>
  s.toLowerCase().replace(/\(.*?\)/g, "").replace(/\s+/g, " ").trim();

const index = new Map<string, Skill>();
for (const skill of TAXONOMY) {
  for (const name of [skill.label, ...skill.aliases]) index.set(norm(name), skill);
}

export function mapSkills(raw: string[]) {
  const matched: { id: string; label: string; from: string }[] = [];
  const unmatched: string[] = [];
  for (const s of raw) {
    const hit = index.get(norm(s));
    if (hit) matched.push({ id: hit.id, label: hit.label, from: s });
    else unmatched.push(s);
  }
  return { matched, unmatched };
}

Feed it the skills array from a parsed resume and you get ids you can store, filter and compare, plus a list of what your taxonomy does not know yet. Review the unmatched list every week for the first month. It tells you which aliases to add far faster than guessing does.

Where the parse fits

ResumeJSON returns skills as an array of strings, one skill per entry, in the words the CV uses. It does not map them to ESCO, O*NET or any other taxonomy, and it does not infer skills that appear only inside a job description. That is deliberate: the mapping belongs to you, because the taxonomy is a product decision you make once and the parse is a step you run on every CV. You can try the output on a real CV with the free resume parser, and the API reference documents the full document shape.

If you need a vendor to normalise skills against their own taxonomy for you, the enterprise parsers sell that as an add-on. Our Textkernel pricing article covers what that looks like.

Keeping the taxonomy healthy

A taxonomy is not a one-off import. Three habits keep it useful:

Summary

A skills taxonomy turns spelling into meaning. ESCO is the multilingual choice, O*NET the occupation-first choice for US roles, and Lightcast the most granular for technical skills. A list of your own, seeded from their synonyms, is often all a focused product needs. In every case the work is the same: get a clean list of skills out of each CV, match it to ids with the synonyms doing the heavy lifting, and keep what did not match where you can see it.

All articles