LibProfessionDB — Offline Profession & Recipe Database for WoW
LibProfessionDB is a lightweight library that lets any addon read World of Warcraft crafting-recipe details offline — a recipe's name, reagents, skill-up difficulty tiers, required skill, produced item, and enriched effect text — without a trade-skill window being open and without the recipe being known by the character.
WoW only exposes recipe data while a profession window is open, and only for professions the player actually knows. LibProfessionDB solves that by shipping a pre-built database generated from the game's own client data, so players never wait for a scan and addon authors get instant lookups for any profession — guild crafters, alts, and recipes you haven't learned yet.
This is a developer library. Install it if another addon lists it as a dependency, or depend on it if you are an addon author.
What it can do
- Look up a recipe by profession + recipe id: name, reagents, produced item
- Read the skill-up difficulty tiers (orange / yellow / green / grey thresholds)
- Read the required skill to learn a recipe, and its recipe-scroll item id
- Ask “which item teaches this recipe?” and get a real answer for the roughly two recipes in three that have a scroll — and, for the third that are trainer-taught and have no scroll at all, a scroll-shaped descriptor instead. The two are exact complements, so a tooltip can keep one shape throughout rather than showing a visible seam where real scrolls run out
- Get the localized scroll prefix (
"Plans: ","Schematic: ","Pattern: "…) derived from real scrolls rather than transcribed — frFR puts a space before the colon and zhCN uses a full-width one, so a hand-written English table is wrong in two locales on day one - Tell a genuine recipe from a skill-rank book (Expert Fishing - The Bass and You teaches a spell but is not a recipe), classified from data rather than by matching titles
- Read the specialization required to learn a recipe (Dragonscale/Elemental/Tribal Leatherworking, Armorsmith/Weaponsmith Blacksmithing, …)
- Read enriched, searchable effect text for enchants (e.g.
+5 Weapon Damage,+1 All Stats) - For enchants, read the target slot (hands/wrist/chest/weapon/…) and structured stats (
{ ITEM_MOD_AGILITY_SHORT = 7 }), plus the enchant id that maps an applied enchant back to its recipe — all from the game's own data - Ask “what enchants can go on this slot?” and get a unified list — craftable enchants and the item-applied ones no profession makes (Dire Maul arcanums, Zul'Gurub head/shoulder/leg enchants, armor kits, shield spikes)
- Ask “where does this recipe come from?” — trainer, vendor, quest, container or drop, with the NPC ids and their names. The client knows none of this; it is derived from the community server databases that reverse-engineered it
- Filter out recipes that were never obtainable in the live game. No client signal reveals them — the spell, the crafted item and the reagents all resolve exactly like a real recipe — so without this a “recipes you haven't learned” list sends players hunting for things that do not exist
- Tell “we have no source data for this” apart from “the game grants this with the profession”, so a browser can say Learned with profession instead of Unknown
- Search by name or effect substring, optionally scoped to one profession
- Iterate one profession or the entire database
For addon authors
local DB = LibStub("LibProfessionDB-1.0", true)
if DB and DB:IsReady() then
local r = DB:GetRecipe(333, 13626) -- Enchant Chest - Minor Stats
-- r.name, r.effect ("+1 All Stats"), r.difficulty {150,175,195,215}, r.reagents
for _, hit in ipairs(DB:Search({ query = "agility" })) do print(hit.name, hit.effect) end
end
Key methods: IsReady, Count, GetMeta, GetProfessions, GetRecipe, GetName, GetReagents, GetEffect, GetDifficulty, GetRequiredSkill, GetItemID, GetCraftedItemID, GetEnchantId, GetEnchantSlot, GetStats, GetEnchant, EnchantsForSlot, IterateEnchants, Search, Iterate.
Recipe scrolls (MINOR 8+) — GetRecipeItem(spellID) gives the item that teaches a recipe, or nil for the roughly one recipe in three that is trainer-taught and has no such item. GetSyntheticRecipeScroll(spellID) covers exactly those with a scroll-shaped descriptor, and the two are exact complements — every recipe answers one or the other — so you can draw a single tooltip shape throughout rather than a seam where real scrolls run out. GetRecipeScrollPrefix(skillLineID) gives the localized "Plans: "-style prefix, derived from real scrolls rather than transcribed, plus a sample count as a second return since v1.7.0. Also GetRecipeItems. As of v1.6.0 this ships for all five game versions, not just Vanilla and TBC.
Correction, v1.7.0 — AttachExternalRecipeInfo was listed on this page and in the README from v1.5.0 and does not exist in the library. It was named in the migration note that moved the recipe-scroll API here from LibItemDB-1.0; the reasoning was sound and the method was never written, and no commit ever added it. Calling it raises attempt to call a nil value. Both references are removed — noted here rather than quietly deleted, because a consumer may have copied it.
Recipe sources (MINOR 10+) — GetRecipeSources(spellID) returns { trainer = {…}, vendor = {…}, quest = {…}, container = {…}, drop = {…} }, each a list of { id, name }. GetRecipeSourceKinds(spellID) is the cheap form when you only need labels, and matters in a list drawing hundreds of rows a frame. Each list carries .total, and it is not always #list — the shipped lists are capped at 12 ids because 1,649 recipes drop from 50+ creatures apiece (Rough Grinding Stone from 912), so iterate the list for examples but always read .total for a count.
Never-implemented and auto-taught recipes (MINOR 9+) — IsHiddenRecipe(spellID), GetHiddenRecipes(), IsAutoTaughtRecipe(spellID), GetAcquireMethod(spellID). Feature-detect on the function itself (if DB.GetRecipeSources then) rather than on a MINOR number.
Data coverage
Recipe data (difficulty, required skill, reagents, names, effect text) differs between game versions, so it is shipped point-in-time per game version — never merged. LibProfessionDB is available for every version except Retail.
- Game versions: Vanilla / Era, TBC, Wrath, Cataclysm, Mists — each shipped with its own point-in-time recipe set.
- Recipe-scroll data now ships for all five game versions as of v1.6.0 — it was Vanilla and TBC only before that, and on the other three every scroll lookup silently answered “this recipe is trainer-taught”. Coverage: Vanilla 65.3%, TBC 64.1%, Wrath 54.6%, Cata 57.2%, Mists 55.5%; the remainder genuinely have no teaching item and are covered by the synthetic descriptor instead.
- Recipe sources (trainer / vendor / quest / container / drop) ship per version for all five, with NPC ids and their names — 968 KB for all five combined. NPC names are English only; the community world databases they come from are not localized.
- Languages: English (US/GB), German, Spanish (EU & Latin America), French, Korean, Portuguese, Russian, and Simplified & Traditional Chinese.
- Italian is shipped but cannot load on any Classic client, and its text is English. Italian is a Retail WoW locale — the original Mists of Pandaria was fully localised in Italian in 2012 and MoP Classic was not. Every data file guards on
GetLocale()and this library ships no Retail flavour, so the Italian files can never execute for a player today. They are kept because Blizzard is being actively petitioned for Italian on Classic and the files are already current; the text inside them is English because the upstream source has no Italian strings for these builds. Listed here rather than quietly counted as a supported language.
Recipe names and enchant effect text are translated per locale; difficulty, reagents, and ids are identical across languages.
Requirements
- Ace3 and VersionCheck-1.0 — auto-installed by CurseForge (they back the optional guild version-check)
Recent Updates
v1.7.0 (Latest — 2026-08-20) — Trainer requirements are scoped per expansion, scroll prefixes ship their sample count, and Vanilla stops borrowing a later expansion's numbers
- fix: trainer skill requirements were held in one flat map across every emulator source, so whichever source was listed last silently rewrote every other expansion's shipped data. The premise in the code was that trainer data is “build-agnostic” — and the sources themselves disprove it: one says Smelt Silver requires 65 and another says 75. A single map cannot hold both answers, so every game version read whichever was written last, which is precisely the cross-expansion merge this library states it does not do. Requirements are now keyed by expansion, and a build takes values from its own expansion or an older one, never a newer one.
- fix: duplicate trainer rows for one recipe took whichever row was visited last. They now take the lowest requirement, and every disagreement is reported at build time.
requiredSkillanswers “what do I need to learn this”, so if any trainer teaches it at 310 the answer is 310. Seven recipes were affected and six of them were unknown. - New:
GetRecipeScrollPrefix(skillLineID)returns a SAMPLE COUNT as a second value, at LibStub MINOR 11. A scroll prefix is derived by majority vote from real scroll names, and Mining's vote has exactly one ballot — a single smelting scroll decides the header for 22 synthetic descriptors. That is not self-evidently wrong, but a consumer previously could not tell. The first return is unchanged, so no existing call site moves; a nil prefix yields a nil count rather than 0, so “no prefix” and “a prefix from no samples” stay distinguishable. No threshold is applied here — that would decide for every consumer invisibly, which is the thing being fixed. - Heads-up for consumers: Vanilla
requiredSkillcoverage drops about 25% (roughly 1,551 values to 1,163). Those recipes had been taking their value from a Wrath or Cataclysm trainer database — exactly the cross-expansion borrowing removed above. This library ships the gap rather than a plausible-looking guess, so the field is simply absent; render it as-, not as0. And the tiers beside it change meaning too, which is the half that will otherwise render a fabricated number: about 93% of those recipes (roughly 359 of the 388) land in the unanchored set described in the next bullet, wheredifficulty[1]is a DBC placeholder of1rather than a real orange threshold. A consumer who renders-for the requirement and then prints1as the skill threshold beside it is showing a made-up number. Apply the conjunction rule below, or see therequiredSkillsection of the README for the full four-case table. - New rule worth reading if you render difficulty tiers. A recipe's tiers are unanchored —
difficulty[1]is a placeholder rather than a skill threshold — exactly whenrequiredSkillis absent anddifficulty[1] == 1. It is the conjunction: 47 recipes have norequiredSkillyet a perfectly real tier, and 192 haverequiredSkillof exactly 1 and are genuinely orange from 1, so either condition alone gives the wrong answer. The offline test suite asserts the invariant this rests on across all five shipped game versions -- that no recipe ever shipsdifficulty[1] == 1beside arequiredSkillabove 1 -- and that both counter-example groups above stay non-empty, so the rule cannot quietly collapse into either one-field version. It does not catch the one residual case: a recipe whose orange tier is genuinely 1 with norequiredSkillreads as unanchored and is not, which is undetectable from the shipped data and is exactly why the rule needs two fields. - docs:
AttachExternalRecipeInfohas been listed in the documentation since v1.5.0 and has never existed in this library. It was named in the note that moved the recipe-scroll API here fromLibItemDB-1.0— the reasoning was sound, the method was never written, and every later doc edit inherited the line without re-checking it. A consumer following the documentation gets attempt to call a nil value. Removed from both this page and the README. If you feature-detect withif DB.AttachExternalRecipeInfo thenyou have been taking the false branch since v1.5.0 and need to change nothing. - data: Vanilla and TBC are regenerated against current client builds — Vanilla
1.15.8.67156→1.15.9.69109, TBC2.5.5.67511→2.5.6.69110. Wrath and Cata were already current. Mists deliberately stays on5.5.3.67509: the upstream data source cannot currently serve a 5.5.4 build with all eleven locale tables intact, and shipping a partial locale would silently drop recipe names for that language. The build is refused rather than shipped short.
v1.6.0 (2026-08-08) — Recipe sources move here, recipe scrolls reach all five game versions, the whole build pipeline moves here, and never-implemented recipes
- fix:
GetRecipeItemanswerednilfor every recipe on Wrath, Cataclysm and Mists — the recipe-scroll data had only ever been generated for Vanilla and TBC. No file existed on disk for the other three, so the tables stayed empty and every lookup returned “no teaching item” — which is indistinguishable from the legitimate answer for a trainer-taught recipe, so nothing ever surfaced it. The visible damage was downstream:isRankBookis what a consumer filters on to exclude skill-rank books, so Expert Cookbook, both First Aid manuals and the Fishing books rendered as craftable recipes with a nonsense difficulty of{125,125,125,125}. Now built and wired for all three: Wrath 2,026/3,710 (54.6%), Cata 2,520/4,406 (57.2%), Mists 3,026/5,456 (55.5%). - New:
GetRecipeSources(spellID)at LibStub MINOR 10 — where a recipe comes from, with the NPC ids and their names. Trainer, vendor, quest, container and drop. This moved here from TOGProfessionMaster, which shipped it as twelve files, 380,088 lines and 6.7 MB of a single all-expansion merge loaded by every client — so a Vanilla player was carrying Cataclysm drop tables. It now ships per version, in 968 KB for all five combined, and carries more than it did before: the NPC names come with the ids. GetRecipeSourceKinds(spellID)→{ trainer = true, … }, the cheap form for a caller that only needs to render labels.GetSourceName(npcID)→ the English name. NPC names are English only, because the community world databases they derive from are not localized.- The entire data pipeline now lives in this repo. Every build script moved here from TOGProfessionMaster, which no longer generates or stores any generated data at all.
- New:
IsHiddenRecipe(spellID)at MINOR 9. Some recipes shipped in the client's spell tables and nothing in the world ever taught them — no trainer, no vendor, no drop, no quest. No client signal reveals this: the spell resolves, the crafted item resolves and the reagents resolve, so they look exactly like a recipe the player simply hasn't found yet. An addon listing “recipes you have yet to learn” can now filter them out instead of sending players hunting for something that does not exist. GetHiddenRecipes()→ the raw{ [spellID] = true }map. Feature-detect withif DB.IsHiddenRecipe and DB:IsHiddenRecipe(id)so the call stays inert on an older ProfessionDB.- Ships as a new per-expansion data file: 67 Vanilla / 87 TBC / 129 Wrath / 184 Cata / 231 Mists never-implemented recipes. Of those, 25 / 37 / 41 / 58 / 90 are recipes this library actually ships; the rest are kept anyway, so adding recipes later cannot silently un-hide one.
- fix: The extractor was missing never-implemented recipes because AllTheThings records them two different ways. It read only the dedicated tree and ignored the per-entry flag, then a second pass required a profession ancestor and missed entries filed under a zone. Both fixed — 11 more shipped recipes are correctly hidden than the first pass found, including Darkspear and the Steam Tonk Controller.
- fix: Difficulty tiers could descend, and about 15% collapsed orange onto yellow. 37 recipes shipped breakpoints that went down as skill rose, and 2,651 duplicated the first two tiers. Now 0 descending and 338 duplicated — and those 338 are deliberate, where the game genuinely uses one threshold for both.
IsAutoTaughtRecipe(spellID)/GetAcquireMethod(spellID), also MINOR 9 — recipes the game grants automatically rather than teaching.- “Never implemented” is deliberately narrower than “unobtainable” — it does not cover recipes that were once obtainable and later removed, nor holiday-gated ones. A recipe absent from the list is not thereby proven obtainable.
v1.5.0 (2026-08-06) — Recipe scrolls move here from ItemDB; ids rebuilt from game data
- Recipe-scroll data now lives here, at LibStub MINOR 8. It moved from
LibItemDB-1.0because every part of it is keyed by craft spell id — exactly asrecipesalready is — so it is recipe data, not item data. ItemDB keepsGetLink/GetName, which answer questions about an item. GetRecipeItem(spellID)→itemID, isRankBook— the scroll that teaches a recipe, or nil, which is a real answer for roughly a third of recipes: they are trainer-taught and no such item exists.GetSyntheticRecipeScroll(spellID)→ a scroll-shaped descriptor for exactly those, and the exact complement of the above — every recipe answers one or the other, never both — so a consumer draws one tooltip shape throughout instead of a visible seam where real scrolls run out. It carries no item id and never will: a fabricated id is permanently cache-cold and fails silently in every item API.GetRecipeScrollPrefix(skillLineID)→ the localized prefix ("Plans: "), and since MINOR 11 a second return giving the sample count behind it. Derived at build time from real scrolls, never transcribed — frFR is"Plans : "with a space before the colon and zhCN uses a full-width one, so a hand-written English table would be visibly wrong in two locales on day one.- The teaching-item ids were rebuilt from the game's own data join, replacing a name-matching linker. Cross-checking the two found 42 disagreements, then a second fault only the cross-check could surface: the candidate list included ids for items that do not exist in the client's item table. With that filtered, both methods agree on all 1,073.
requiredSkillis deliberately absent from the synthetic descriptor. ItemDB shipped it and it was wrong for ~313 of 572 records — it read a field that is a floor of 1 on eight of twelve skill lines, rendering "Requires Mining (1)" on a recipe needing 230. This library already carries the correct per-recipe value on the recipe itself, so a second copy could only ever let the two disagree.- The offline test suite (40 specs, dev-only) now executes the real shipped data files off disk rather than calling the loaders with its own fixtures — which is what caught the moved files still opening with the old library handle, a fault that would have made every query here return nothing while erroring nowhere. Fixed in the generator too, so a rebuild cannot reintroduce it.
v1.4.1 (2026-07-28) — TOC interface versions realigned to the live clients
- Every shipped TOC now declares the Interface version its client actually runs — Vanilla/Era
11508→11509(client 1.15.9), TBC20505→20506(2.5.6), Mists50503→50504(5.5.4). Three had drifted behind their builds, which is what makes WoW flag the library as out-of-date in the AddOns list. Wrath and Cata keep their last shipped values — no live client to verify them against, and guessing an Interface number is how this drift starts. - Packaging fix: two
.pkgmetaignore patterns were in a format the packager silently matches nothing against, so thetoolsfolder and a development PowerShell script were being shipped inside the released zip. Corrected — the download is now just the library and its data. - No data and no API change: recipe data, the enchant catalog and every method are identical to v1.4.0. LibStub MINOR stays at 7 — it moves only when the public API changes, so consumers gating on
MINOR >= 7need no update.
v1.4.0 (2026-07-18) — Enchant catalog: item-applied enchants
- New enchant catalog covering the permanent gear enchants no profession crafts — Dire Maul arcanums, the Zul'Gurub / Zandalar head, shoulder and leg enchants, armor kits, shield spikes — applied by a consumable item, so they never appear in a recipe list.
EnchantsForSlot(slot)returns a unified list of every enchant for a slot (craftable and item-applied);GetEnchant(id)/IterateEnchants()round it out. - Authoritatively sourced and curated from DBC (every
ENCHANT_ITEMeffect not in a profession skill line, targeting an armor/shield slot, granted by a real obtainable item — placeholders and QA/dev items filtered out). 80–176 enchants per game version, every locale. Kept out of the recipe space, soGetProfessions/GetRecipesare unaffected. LibStub MINOR → 7.
v1.3.0 (2026-07-12) — Enchant enrichment: id, target slot & structured stats
- Enchant recipes (all Enchanting, plus Engineering scopes/belt tinkers and later-expansion leg armors, spellthreads and inscriptions) now ship three new fields, exposed via
GetEnchantSlot,GetStatsandGetEnchantId: the target slot (HANDS/WRIST/WEAPON2H/…), a structured stat table keyed by the game's ownGetItemStatskeys (so enchant stats sum straight into item totals), and theSpellItemEnchantmentid that maps a worn enchant back to its recipe. - All three are extracted authoritatively from the client data (
SpellEquippedItemsfor the slot; the enchant's DBC effects for the stats) — no recipe-name guessing, no effect-string parsing, so they hold up across every locale. Purely additive; LibStub MINOR → 6.
Credits
The list of recipes that exist in the game files but were never actually obtainable comes from AllTheThings — a curated catalog recording something no client signal reveals, since these recipes resolve exactly like real ones. LibProfessionDB doesn't redistribute any of their files; it reads an installed copy at build time and re-expresses the underlying facts in its own format. Gratefully acknowledged. All other data — recipe names, reagents, skill-up difficulty tiers, required skill, produced items, enchant ids and stats, and the localized scroll prefixes — is built from Blizzard's own client data via wago.tools.
Support
Questions, bug reports, or coverage requests — reach out on Discord.

