# AI and MCP
Connect an agent to current documentation and the same talent rules used by the calculator.
Source: https://warcraftforever.com/developers/ai/
## Connect
Add this remote MCP server URL to your client's MCP settings:
```text
https://warcraftforever.com/mcp
```
The endpoint uses Streamable HTTP. It is public and read-only, with no account or API key. Your client determines how to add remote servers; copy the URL into its server configuration.
## Available tools
| Tool | Use |
| ----------------------- | ------------------------------------------------------------------------------------------ |
| `search_docs` | Find guides and reference pages by query. |
| `get_doc` | Read a document by the ID returned from search. |
| `list_catalogs` | Discover reference resources, channels, builds, and source provenance. |
| `get_catalog` | Read classes, playable races and eligibility, factions, or hunter pet families. |
| `discover_data` | Discover resource datasets, coverage gaps and limits. |
| `get_records` | Read up to 50 item, recipe, profession or supported spell IDs. |
| `search_records` | Search those resources with bounded cursor pagination. |
| `get_references` | Read supported reference values. |
| `get_talent_definition` | Retrieve a class's current or specified archived talent layout. |
| `validate_talent_build` | Validate a compact or Blizzard in-game build using its class, revision, and maximum level. |
For example, ask an agent: “Read Warcraft Forever's guide to saving a talent build, then verify this Paladin build is complete for level 30.”
`validate_talent_build` distinguishes legality from completion. A legal 20-point allocation at level 30 has `valid: true` and `complete: false`. Preserve the explicit revision when validating saved builds; unsupported revisions fail instead of falling back to the current layout.
The tools use published documentation, catalog snapshots, and talent assets. `get_catalog` accepts `resource`, optional `channel`, and a client `locale` (default `enUS`). Responses include the same provenance and name-locale markers as HTTP. Pet families do not establish NPC tameability. Authorization and persistence remain with your application.
## Resources
Resources expose each published Markdown guide, both catalog/data manifests, and JSON schemas for data-tool inputs. The schemas are derived from tool validation. See [Game data](https://warcraftforever.com/developers/data/#http-and-mcp) for resource URIs and coverage.
## Read without MCP
- [Documentation index](https://warcraftforever.com/llms.txt)
- [Complete documentation](https://warcraftforever.com/llms-full.txt)
- Use **View Markdown** or **Copy for AI** on any page for a focused document.
These representations are generated from the same source as this portal. Search results and tool responses include canonical source URLs.
---
# Reference catalogs
Read classes, playable races, factions, and hunter pet families in JavaScript or JSON.
Source: https://warcraftforever.com/developers/catalogs/
Build character selectors, class filters, and pet-family pickers without an account or API key. Every snapshot identifies its client build; `nameLocale` reports the actual language of each name, including English fallback.
## JavaScript
```html
```
The library extends `WarcraftForever.Classes`, `.Races`, `.Factions`, and `.Pets`. `$WF` follows the [alias rules](https://warcraftforever.com/developers/quickstart/#short-namespace); add `?alias=0` to avoid creating it. It works independently of Talents and Tooltips, in either load order.
Classes, races, and factions have `list(options)` and `get(idOrSlug, options)`. An unknown lookup returns `null`. Pets has `families(options)` and `family(id, options)`. All reads return promises; failures reject so your application can show a retry state. Editing a returned object does not change later reads.
Catalogs load lazily: the first read fetches one shared manifest and the complete requested collection. Further `get` and `list` calls use memory, including lookups that return `null`. Concurrent calls share the same request. Classes, races, factions, and pet families each load only when used; dataset and locale entries remain separate. Failed loads can be retried. The manifest and successful catalogs stay cached for the page or client lifetime; reload the page or create a new module client to discover newer data.
`options.channel` defaults to the published default. `options.locale` accepts a client locale or browser language and otherwise follows the browser. Supported client locales are `enUS`, `deDE`, `esES`, `frFR`, `koKR`, `ptBR`, `ruRU`, and `zhCN`. Missing translations use English with `nameLocale: 'enUS'`. Player faction names currently use English.
`$WF.Catalog.manifest()` returns published builds, channels, and source provenance. Classes include their class-wide specialization and an icon URL. Races carry `faction` and `classIds`, with stable English slugs including separate High Order and Windshaper Skyborne keys. Player faction IDs are API enums: `1` for Alliance and `2` for Horde, distinct from reputation faction IDs.
Pet families are animal families with a configured diet in the client `CreatureFamily` table. They retain client IDs and raw `foodMask` and `talentType` fields. These describe the client definition; they do not establish whether an individual NPC can be tamed. Individual creatures, locations, abilities, and models are outside this catalog.
## HTTP JSON and pinned data
```js
const origin = 'https://warcraftforever.com';
async function readJson(path) {
const response = await fetch(origin + path);
if (!response.ok) {
throw new Error(`Catalog read failed: ${response.status}`);
}
return response.json();
}
const manifest = await readJson('/api/catalog/v1/manifest');
const { dataset } = manifest.channels[manifest.defaultChannel];
const payload = await readJson(`/api/catalog/v1/${dataset}/enUS/races`);
console.log(payload.build, payload.data);
```
The manifest lists `classes`, `races`, `factions`, and `pet-families`. Each resource response contains `schemaVersion`, `dataset`, `channel`, `build`, `locale`, `resource`, and `data`. Manifest channel entries include the product, exact build, source access URL, and source table hashes.
The HTTP manifest is cached for five minutes. Dataset bytes are immutable, but origin availability is bounded: each channel keeps the latest three distinct published pin sets per patch across its latest two patches. A patch is the first three client-build components; same-build refreshes can publish another revision. Identical pin sets do not consume another revision. Current deployments and explicitly selected rollback targets remain protected while referenced. An expired origin pin returns 404, with no fallback to newer data. Save complete response envelopes in your own storage when you need longer availability. Pinning `/catalog/releases/.js` freezes SDK code; its list methods resolve the current catalog on first use unless `options.dataset` supplies a pin. GET and HEAD support public cross-origin access. Unknown datasets/resources return 404, unsupported client locales return 400, and unavailable assets return 503. Errors are not cached.
## Read saved catalogs
`$WF.Catalog.createClient({ dataset, locale, reader })` or the module's `createClient` creates an isolated client. The optional reader receives the resolved URL and fetch options, and returns a normal `Response`. It handles discovery and collections; an explicit dataset pin skips discovery. It applies to that client, not per-call options. Default clients keep using `fetch`.
```js
const archived = $WF.Catalog.createClient({
dataset: savedDataset,
locale: 'enUS',
reader: (url, options) => {
// Store complete JSON envelopes under their original API paths in any folder.
const archiveUrl = new URL('/my-saved-version' + url.pathname + '.json', location.origin);
return fetch(archiveUrl, options);
}
});
const races = await archived.Races.list();
```
Cache sharing, retry after failure, errors and detached returned objects retain the normal client behavior. Different clients have separate caches and readers. Your archive determines how long saved data remains available. Pin SDK code separately; images and linked site pages are separate resources and are not mirrored by the reader.
## TypeScript and agents
Use `/catalog/latest.mjs` with its adjacent declarations for typed imports. The [catalog type entry](https://warcraftforever.com/catalog/types.d.ts) exposes script-tag globals and exported interfaces; preserve its referenced declaration files when downloading. Pinned declarations are at `/catalog/releases/.d.ts`, using `$WF.Catalog.version`. See the generated [type reference](https://warcraftforever.com/developers/reference/types/) for record shapes.
The [MCP endpoint](https://warcraftforever.com/developers/ai/) offers `list_catalogs` for discovery and `get_catalog` for the same complete payload. Agents can also read HTTP URLs directly, including pinned dataset URLs.
## More data to build with
Use [Game data](https://warcraftforever.com/developers/data/) for items, recipes, professions, supported spell variants and reference values. Use [headless builds](https://warcraftforever.com/developers/headless/) to validate and compare allocations. Quest, loot and NPC corpora remain unavailable until their source and coverage contracts are established.
---
# Game data
Read versioned items, recipes, professions, supported spells, and reference values from JavaScript or MCP.
Source: https://warcraftforever.com/developers/data/
Use the public data library to build search, selectors, and custom displays. Responses identify their dataset, source and coverage. No account or API key is required.
## JavaScript
Import the browser module from Warcraft Forever:
```js
import {
Items,
Spells,
Recipes,
Professions,
References,
manifest,
createClient
} from 'https://warcraftforever.com/data/latest.mjs';
const item = await Items.get(118, { locale: 'enUS' });
console.log(item.data); // Item record, or null when this dataset has no match.
const recipes = await Recipes.getMany([2149, 2152], { locale: 'enUS' });
console.log(recipes.data, recipes.missingIds);
const professions = await Professions.search('', { limit: 25 });
const spells = await Spells.search('wrath', { limit: 10 });
const references = await References.list({ locale: 'enUS' });
console.log(professions.data, spells.data, references.data);
```
`Items`, `Recipes`, `Professions`, and `Spells` each provide `get(id, options)`, `getMany(ids, options)`, and `search(query, options)`. `References.list(options)` returns the complete small reference groups.
Every read returns an envelope with `schemaVersion`, `dataset`, `locale`, `resource`, `source`, `coverage`, and `data`. A single lookup returns a record or `null` in `data`. Batch reads return an array and `missingIds`; search returns an array and `nextCursor`, which is `null` at the end. Check the response's coverage before interpreting a missing record.
Names report their actual language through `nameLocale`. Missing translations can fall back to English; an unknown name remains `null`. Item records include effects and flavor text when available. Recipes retain product, reagent, skill, and cooldown information from the maintained client data. Numeric IDs and absent optional fields retain their published meaning.
Discovery is loaded once per client. `References.list()` also lazily caches its complete small catalog in memory per dataset and locale, sharing concurrent requests. Returned objects are independent copies; failed loads can be retried. Create a new client or reload the page to discover newer data. Item, recipe, profession, and spell lookups remain bounded server requests.
Failed requests reject. Show an error or retry state instead of treating a failure as an empty dataset.
## Pin a dataset
The manifest lists the published channels and an immutable dataset ID for each resource:
```js
const published = await manifest();
const channel = published.channels[published.defaultChannel];
const options = { dataset: channel.datasets.items, locale: 'enUS' };
const first = await Items.search('copper', { ...options, limit: 25 });
console.log(first.source, first.coverage, first.data);
if (first.nextCursor) {
const next = await Items.search('copper', {
...options,
dataset: first.dataset,
cursor: first.nextCursor,
limit: 25
});
console.log(next.data);
}
```
A channel follows its published dataset. A dataset pin selects one immutable resource version. Copy the pin for the resource you are reading: an item dataset and a spell dataset need not have the same ID, even when they describe the same client build. Keep the dataset, locale, query, and `includeUnknown` setting when continuing a search; a cursor from another scope is rejected.
Batch reads accept up to 50 IDs. Search defaults to 25 records and allows at most 100 per page. Queries are limited to 64 characters and match names literally: `%` and `_` are not wildcards. An empty query browses the resource. Undiscovered records are excluded from search unless `includeUnknown: true` is supplied; a direct ID lookup can still return one.
Configure defaults for several reads with `createClient`:
```js
const game = createClient({ channel: 'beta', locale: 'deDE' });
const recipe = await game.Recipes.get(2149);
console.log(recipe.data?.name, recipe.data?.nameLocale);
```
`createClient({ origin, channel, dataset, locale, reader })` accepts an optional origin for another Warcraft Forever deployment and defaults for requests. Per-call options select a different resource pin or locale when needed.
## Availability and caller storage
Public origin snapshots keep the latest three distinct published pin sets per patch across the latest two patches of each channel. Patch means the first three client-build components. Same-build changed pins count as another revision; identical pins do not. Active deployments and explicitly selected rollback targets remain protected while referenced. Expired pins return 404, never newer data. Code pins and saved calculator layouts have separate compatibility lifetimes.
An optional per-client `reader(url, options)` returns a normal fetch `Response` for discovery and resource JSON. The default is unchanged network fetching. You can map resolved paths and query strings to your own storage; an explicit dataset skips discovery. Keep matching response envelopes, locale, query, cursor and missing-ID metadata. Saved records alone do not provide an offline search engine. Reference/discovery caching, retries, errors and returned-object isolation remain unchanged. Images and linked site pages remain separate resources. The library includes no bulk exporter or storage adapter registry.
```js
const saved = createClient({ dataset: savedItemDataset, reader: readSavedResponse });
const item = await saved.Items.get(118);
```
Here `readSavedResponse` is your application function returning a `Response` from a matching saved request. Its archive lifetime is independent of origin retention.
## Coverage and reference values
The available item and recipe data is the maintained profession corpus. It includes crafting products, materials, tools, and teaching items; it is not a complete item database. Profession records describe the maintained profession definitions and categories.
The spell resource covers talent and Legacy rank variants plus recipe spells. One spell ID can have several rank variants; preserve those variants when displaying descriptions. This is not a complete spellbook or proof that a spell is available to a particular character.
Reference groups provide quality, equipment-slot, school, power, stat, and class-color values. Keep their source and inference markers. School IDs and school bitmasks have different meanings; site presentation colors are not client availability data.
There are no published quest, loot, or NPC datasets in this interface. The manifest reports unavailable resources explicitly. A missing item, spell, or relationship does not prove it is absent from the game.
## HTTP and MCP
Read the [data manifest](https://warcraftforever.com/api/data/v1/manifest) directly to discover dataset pins, limits, and unavailable resources. The [class, race, faction, and pet-family catalogs](https://warcraftforever.com/developers/catalogs/) remain available separately.
Connect an agent to [the public MCP endpoint](https://warcraftforever.com/developers/ai/). It offers:
| Tool | Use |
| ---------------- | --------------------------------------------------------------------------- |
| `discover_data` | Discover published resource pins, builds, limits, and unavailable datasets. |
| `get_records` | Read a bounded batch of item, recipe, profession, or supported spell IDs. |
| `search_records` | Search or browse records with a bounded cursor page. |
| `get_references` | Read all small reference groups for a published dataset. |
Data tools accept a client locale and either a channel or an explicit dataset pin. A pin takes precedence over the channel. They use the same public resolver and response envelopes as the library; resolver failures remain errors.
MCP resources include the published Markdown documents and both manifests. Resources at `warcraftforever://schemas/get_records`, `warcraftforever://schemas/search_records`, `warcraftforever://schemas/get_references`, and `warcraftforever://schemas/discover_data` describe **tool inputs**. They are generated from the tools' validation schemas. TypeScript declarations describe response records.
---
# Headless builds and modules
Decode, validate, export, and compare builds without mounting a calculator.
Source: https://warcraftforever.com/developers/headless/
Use the same rules as the interactive calculator from a browser or Node process. The headless module has no React or DOM dependency:
```js
import { createClient } from 'https://warcraftforever.com/talents/headless.mjs';
const talents = createClient();
const layout = await talents.definition({ class: 'paladin' });
const result = await talents.validate({
class: 'paladin',
revision: layout.revision,
maxLevel: 30,
allocation: { 'paladin-holy-divine-strength': 5 }
});
console.log(result.valid, result.complete, result.pointsRemaining);
const encoded = await talents.encode({
class: 'paladin',
revision: layout.revision,
maxLevel: 30,
allocation: result.allocation,
format: 'blizzard'
});
const decoded = await talents.decode({ class: 'paladin', maxLevel: 30, code: encoded.code });
const difference = await talents.diff({
class: 'paladin',
maxLevel: 30,
before: {},
after: decoded.allocation
});
console.log(difference.pointDelta, difference.changes);
```
`validate` takes exactly one of `code` or `allocation`. Invalid builds return `valid: false` and structured `issues`. `encode` and `diff` reject invalid allocations with `SdkError.code === 'invalid-build'`. Legality and completeness are distinct: a partial allocation may be valid. `definition` and result objects are detached from the client's cache.
`encode` supports `blizzard` (the default for talents), deprecated `compact`, and `structured`. String exports have a `code`; structured exports have a `build`. See [build exports](https://warcraftforever.com/developers/talents/exports/) for the native-format verification boundary and compatibility rules. `diff` accepts allocations or strings for both sides, interprets both against one class and revision, and returns changed ranks and a point delta.
## Dataset pins
Use [discovery](https://warcraftforever.com/developers/data/#pin-a-dataset) to obtain the `talents` dataset ID, then call `createClient({ dataset })`. The descriptor fixes the definition and revision registry. Store the dataset, class and revision beside saved strings when you need reproducible interpretation. SDK release IDs, dataset IDs, and layout revisions are separate contracts.
## Module and TypeScript entry points
Every capability has `/latest.js` for script tags and `/latest.mjs` for ES modules: `talents`, `legacy`, `tooltips`, `catalog`, and `data`. ES modules do not create `WarcraftForever` or `$WF` globals. Talents and Legacy also have `/headless.mjs` for environments without a DOM.
Browser imports use the serving module's origin for assets and API reads. Native Node does not load HTTPS modules by default: download a pinned headless module and import the local `.mjs` file, or bundle the module as part of your application. A downloaded module defaults to `https://warcraftforever.com`; `createClient({ origin })` selects another deployment. No npm package is published by this interface.
Adjacent `.d.mts` files provide TypeScript declarations. For a local download, preserve the declaration's relative dependency tree under `releases//types/`; copying only the root declaration is insufficient. `/types.d.ts` is the script-type entry; include each loaded capability's entry to compose its `WarcraftForever` and `$WF` declarations, or use the matching `/releases/.d.ts` for a pinned script. Module and headless declarations do not declare these globals. The portal's [type reference](https://warcraftforever.com/developers/reference/types/) is generated from the same source.
## Errors
Check `error.code` rather than parsing messages. Headless errors distinguish invalid options, unknown classes, unsupported revisions/formats, invalid builds, and unavailable or inconsistent data. Build errors can carry `details` with validation issues. Data API failures use the same `SdkError` shape; HTTP errors return `{ error: { code, message } }` and are never cached as successful data.
---
# Build with Warcraft Forever
Calculators, game data, and tooltips for websites, Node.js, and AI tools.
Source: https://warcraftforever.com/developers/
Bring the Warcraft Forever talent calculator to your site with a small JavaScript include. Data, images, and translations stay on Warcraft Forever. You control where the calculator appears and how saved builds are used.
## Start building
| Integration | What you can build |
| ------------------------------------------------- | --------------------------------------------------------------------------------------- |
| [Talent selector](https://warcraftforever.com/developers/quickstart/) | A class-locked calculator with a level cap and Save event. |
| [Saved builds](https://warcraftforever.com/developers/talents/saved-builds/) | Read-only talent trees and linked icon summaries. |
| [Headless builds](https://warcraftforever.com/developers/headless/) | Encode, decode, validate, and compare builds in browsers or Node.js. |
| [Legacy calculator](https://warcraftforever.com/developers/legacy/) | Interactive Legacy builds, saved trees, and structured exports. |
| [Tooltips](https://warcraftforever.com/developers/tooltips/overview/) | Talent, item, and recipe cards on existing links. |
| [Reference catalogs](https://warcraftforever.com/developers/catalogs/) | Classes, playable races, race–class eligibility, factions, and hunter pet families. |
| [Game data](https://warcraftforever.com/developers/data/) | Batch lookups and search for items, spells, recipes, professions, and reference values. |
| [AI and MCP](https://warcraftforever.com/developers/ai/) | Documentation, data discovery, build validation, and data tools for agents. |
## One namespace, independent libraries
```html
```
The libraries extend `WarcraftForever` with their own capabilities. Include any combination. No account, API key, or React installation is required on your site.
Use `latest.js` for maintained compatible updates. [Pinned releases](https://warcraftforever.com/developers/reference/releases/) are available for reproducible integrations.
[ES modules and TypeScript declarations](https://warcraftforever.com/developers/headless/) are available without global aliases.
---
# Legacy calculator
Embed, validate, export, and render Legacy builds with their own point budget.
Source: https://warcraftforever.com/developers/legacy/
```html
```
Legacy uses the same mount lifecycle, layouts and interaction model as [Talents](https://warcraftforever.com/developers/talents/options/). It uses the definition's own point budget and spending gates. Do not pass `class` or `maxLevel`. Its state carries `id: 'legacy'` and `pointBudget` in place of a class and character level. The Save button appears when all points are spent.
Use `view: 'icons'` or `view: 'tree'` with `build: { code, revision }` for a saved display, and `exportBuild('structured')` for custom rendering. Unknown client effects remain explicitly unknown; locked nodes cannot be allocated.
Legacy exports default to `compact`; `structured` includes the calculator ID, revision, selected trees and spells. The headless result also reports `pointBudget`. The Blizzard-format Legacy serialization contract has not been verified, so `blizzard` rejects with `unsupported-format` instead of inventing an encoding. Existing Legacy URLs remain valid.
`/legacy/headless.mjs` exports `definition`, `decode`, `validate`, `encode`, `diff`, and `createClient`. Their [headless contracts](https://warcraftforever.com/developers/headless/) match Talents, with no class or maximum-level argument:
```js
import { validate, encode } from 'https://warcraftforever.com/legacy/headless.mjs';
const state = await validate({ allocation: { 'legacy-professions-working-overtime': 5 } });
const exported = await encode({ allocation: state.allocation, format: 'structured' });
console.log(exported.build);
```
---
# Quickstart
Add a class-locked talent selector to your site.
Source: https://warcraftforever.com/developers/quickstart/
Add one script and choose where the calculator appears. Each mount renders directly into your page with isolated Shadow DOM styles. Definitions, images and translations load from Warcraft Forever. Your site only needs to store the class, code and layout revision.
## Mount the calculator
```html
```
The script leaves your links and other page content alone. Open the [interactive demo](https://warcraftforever.com/embed/talents/native-demo.html) to try every option and see received events. See [Content Security Policy](https://warcraftforever.com/developers/reference/security/) for host setup.
## Short namespace
`$WF` is an alias for `WarcraftForever` when the page has no existing `$WF` value. For example, `$WF.Talents.mount(...)` uses the same API. Both capabilities extend the same object, regardless of load order.
To avoid creating the alias, add `?alias=0` to every Warcraft Forever library script:
```html
```
This option also works on pinned release URLs. It does not remove an alias already created by another script. Existing `$WF` values, including `null`, are preserved. `WarcraftForever` remains available in all cases.
---
# React integration
Mount the framework-independent library inside a React component.
Source: https://warcraftforever.com/developers/recipes/react/
Load `https://warcraftforever.com/talents/latest.js` before rendering this component. The library owns its Shadow DOM; your React tree owns the empty container.
```jsx
function TalentSelector({ classSlug, onSave }) {
const container = useRef(null);
useEffect(() => {
const talents = WarcraftForever.Talents.mount(container.current, {
class: classSlug,
maxLevel: 30,
});
const unsubscribe = talents.on('submit', onSave);
talents.ready.catch((error) => {
// Replace with your application's error display.
console.error(error);
});
return () => {
unsubscribe();
talents.destroy();
};
}, [classSlug, onSave]);
return ;
}
```
Import `useEffect` and `useRef` from React. Keep `onSave` stable with `useCallback` if re-renders should preserve an unfinished build. Always destroy the instance during cleanup, including React's development setup/cleanup cycle.
For server-rendered applications, load and mount only in the browser. Do not import the classic script as a server module.
---
# Save a talent build
Collect a complete build in a dialog and save it in your application.
Source: https://warcraftforever.com/developers/recipes/save-a-build/
## Mount after opening your build dialog
Load the talent script once. Then create a mount each time your dialog opens:
```js
const talents = WarcraftForever.Talents.mount('#talent-editor', {
class: selectedClass,
maxLevel: 30,
build: savedBuild,
});
const unsubscribe = talents.on('submit', async (state) => {
await saveBuild({
class: state.class,
code: talents.exportBuild('blizzard'),
revision: state.revision,
});
closeBuildDialog();
});
await talents.ready;
```
`selectedClass`, `savedBuild`, `saveBuild`, and `closeBuildDialog` belong to your application. Omit `build` for a new allocation. Use an empty mount element and show loading or errors while `ready` settles.
This example sets a level cap of 30; choose the cap your application needs. The SDK shows Save when all available points are legally allocated (21 at level 30). The `submit` event carries the completed state; saving to your backend remains your responsibility.
## Close or change class
```js
unsubscribe();
talents.destroy();
```
Call these when your dialog closes. Changing the class through `update({ class: nextClass })` resets the build unless you also supply a build for that class.
## Show the saved result
Use [linked icons or a read-only tree](https://warcraftforever.com/developers/talents/saved-builds/) to display the saved build. Preserve the revision and run [backend validation](https://warcraftforever.com/developers/reference/security/#validate-on-your-server) before accepting a submission.
---
# Links and card API
Read published item and recipe cards for a custom presentation.
Source: https://warcraftforever.com/developers/reference/cards/
Link to a known item with `https://warcraftforever.com/items/` or a known recipe with `https://warcraftforever.com/spells/`. The site redirects to its canonical named URL. These pages cover items and recipes in the current professions data; an unknown ID returns 404.
Links keep their normal browser behavior. Visitors can open them in a new tab or copy them without loading an integration script.
## Card reads
For a custom presentation, `GET /api/embed/tooltips/v1/item/?locale=en&channel=beta` and the corresponding `recipe` path return our small card data as JSON with cross-origin access. Only known cards in a published channel return 200. Talent definitions and archived revisions are served as versioned static assets; the script resolves those paths for you.
If your site uses a Content Security Policy, allow scripts, styles, fonts, images and connections from `https://warcraftforever.com`, and move the inline setup to a script your policy permits. The card CSS is isolated from your page in Shadow DOM.
---
# Iframe integration
Use an isolated calculator and the existing message protocol.
Source: https://warcraftforever.com/developers/reference/iframe/
The existing `/embed/talents/v1.js` script and `WarcraftForeverTalents.mount()` remain available for iframe isolation. You can also use [the iframe route](https://warcraftforever.com/embed/talents/?class=paladin&editable=false) without our script. Pass `class`, `editable`, and optional build and display settings as query parameters. For state changes and automatic height, use the [message protocol](#direct-iframe-message-protocol).
## Direct iframe message protocol
Set `parentOrigin` to your exact origin and `token` to a random 8–128 character value in the iframe URL. Send a request to `iframe.contentWindow` with `postMessage(request, 'https://warcraftforever.com')`.
```js
{
protocol: 'warcraft-forever-talents',
version: 1,
kind: 'request',
token,
requestId: '1',
action: 'update',
options: { class: 'paladin', editable: false }
}
```
The `options` object is the complete mount options. Responses carry the same request ID and either `{ ok: true, state }` or `{ ok: false, error }`. Events use `kind: 'event'` with `event` set to `ready`, `change`, `complete`, `error`, or `height`. A change event includes the committed `build` when available; edits clear its Point Order path. Every message carries the protocol name, version, and token. Check both `event.source === iframe.contentWindow` and `event.origin === 'https://warcraftforever.com'` before accepting a message.
---
# Releases and compatibility
Use maintained scripts or pin code and data independently.
Source: https://warcraftforever.com/developers/reference/releases/
Use `/talents/latest.js`, `/legacy/latest.js`, `/tooltips/latest.js`, `/catalog/latest.js`, or `/data/latest.js` for maintained compatible releases. They extend `window.WarcraftForever`; loading the same release again preserves existing APIs and mounts. Loading different releases of one capability on a page throws. ES modules at `latest.mjs` add no globals.
For reproducible code, use `//releases/.js` or `.mjs`, using the capability's `version` export. Headless pins use `//releases/.headless.mjs`. Matching `.d.mts` entries reference retained declaration files. Code, declarations and required static assets share a content-addressed release; changed declaration bytes also create a new release.
Latest checks for updates after five minutes. Pinned files are immutable and retained across deployments. New breaking APIs use a separate opt-in entry point.
SDK pins do not freeze channel-selected data. Read the [data manifest](https://warcraftforever.com/api/data/v1/manifest) or [catalog manifest](https://warcraftforever.com/api/catalog/v1/manifest) and supply the resource's `dataset` to pin it. Talent/Legacy headless clients accept `createClient({ dataset })`. Layout revisions remain separate from both SDK and dataset versions. Keep dataset IDs and revisions with persisted builds.
---
# Host setup and security
Content Security Policy, data ownership, and server-side validation.
Source: https://warcraftforever.com/developers/reference/security/
## Content Security Policy
Permit `https://warcraftforever.com` in your site's `script-src`, `style-src`, `font-src`, `img-src`, and `connect-src` directives. Merge these sources into your existing policy. The calculator uses inline style attributes for node positioning; your policy must allow those through `style-src-attr` (or its `style-src` fallback).
Put setup code in your own permitted JavaScript file, or use your existing nonce/hash policy for inline scripts. The [iframe integration](https://warcraftforever.com/developers/reference/iframe/) also needs Warcraft Forever in `frame-src`.
## Data and persistence
Talent definitions, images, styles, and translations load from the origin that served the library. Each native calculator owns a Shadow DOM child of the mount target. Your application owns build persistence; the library does not send builds to your backend.
Store the build's class, exported code, and layout revision together. For new integrations, request `exportBuild('blizzard')`; deprecated compact codes remain supported. The same code can mean something different under another layout revision. Destroy mounted instances when their host component unmounts.
## Validate on your server
Validate submitted builds on your backend and enforce your application's class, level, and access requirements there. Browser editing checks do not replace server validation.
A legal partial allocation is not necessarily complete. A complete level-30 build requires exactly 21 allocated points. Our MCP validator reports `valid` and `complete` separately.
## Data coverage
Talent data comes from the client builds identified in each definition's provenance. Tooltip values may be rendered at a different character level from an editor's point budget. A level cap constrains allocation; it does not simulate character stats or recalculate spell scaling.
Item and recipe cards cover the published professions datasets. Unknown cards are not evidence that an item or spell does not exist in the game.
---
# Type reference
Public SDK contracts generated from the source.
Source: https://warcraftforever.com/developers/reference/types/
These declarations are generated with each build. See [mount options](https://warcraftforever.com/developers/talents/options/) and [state and events](https://warcraftforever.com/developers/talents/lifecycle/) for usage.
```ts
export interface EmbedBuild {
code: string;
revision: string;
path?: string;
}
export type ChromePart = 'title' | 'summary' | 'instructions' | 'reset' | 'share' | 'attribution';
export type EmbedChrome =
| 'bare'
| 'compact'
| 'full'
| ({ preset?: 'bare' | 'compact' | 'full' } & Partial>);
export interface EmbedOptions {
class: string;
editable: boolean;
build?: EmbedBuild;
tree?: string | 'all';
maxLevel?: number;
scale?: number;
chrome?: EmbedChrome;
locale?: string;
}
export interface EmbedState {
class: string;
revision: string;
code: string;
allocation: Record;
pointsSpent: number;
pointsRemaining: number;
maxLevel: number;
complete: boolean;
shareUrl: string;
}
export interface TalentOptions extends Omit {
view?: 'calculator' | 'tree' | 'icons';
editable?: boolean;
}
export type TalentState = EmbedState;
export interface LegacyOptions extends Omit {}
export interface LegacyState extends Omit {
id: 'legacy';
pointBudget: number;
}
export type TalentEvent = 'ready' | 'change' | 'complete' | 'submit' | 'error';
export interface CalculatorMountConfig extends CalculatorClientConfig {
locales: Record;
stylesheet: string;
}
export interface TalentMount {
ready: Promise;
getState(): TalentState | null;
exportBuild(format?: 'blizzard' | 'compact'): string;
exportBuild(format: 'structured'): StructuredTalentBuild;
update(options: Partial): Promise;
setBuild(build: EmbedBuild): Promise;
on(event: Exclude, listener: (state: TalentState) => void): () => void;
on(event: 'error', listener: (error: Error) => void): () => void;
destroy(): void;
}
export interface LegacyMount {
ready: Promise;
getState(): LegacyState | null;
exportBuild(format?: 'compact'): string;
exportBuild(format: 'structured'): StructuredCalculatorBuild;
update(options: Partial): Promise;
setBuild(build: EmbedBuild): Promise;
on(event: Exclude, listener: (state: LegacyState) => void): () => void;
on(event: 'error', listener: (error: Error) => void): () => void;
destroy(): void;
}
export type ClientLocale = 'enUS' | 'deDE' | 'esES' | 'frFR' | 'koKR' | 'ptBR' | 'ruRU' | 'zhCN';
export type CatalogResource = 'classes' | 'races' | 'factions' | 'pet-families';
export interface CatalogOptions {
/** Immutable catalog dataset from discovery. */
dataset?: string;
channel?: string;
/** Client locale or browser language. Missing client translations use enUS. */
locale?: string;
}
export type JsonReader = (url: URL, options: RequestInit) => Promise;
export interface CatalogClientOptions extends CatalogOptions {
origin?: string;
reader?: JsonReader;
}
export interface NamedEntity {
id: number;
name: string;
/** Locale of this name, including when it falls back to English. */
nameLocale: ClientLocale;
}
export interface ClassRecord extends NamedEntity {
slug: string;
iconFileDataId: number;
iconUrl?: string;
/** Forever uses one class-wide specialization; this is not a selected talent tree. */
specializations: NamedEntity[];
}
export interface RaceRecord extends NamedEntity {
slug: string;
faction: 'alliance' | 'horde';
classIds: number[];
}
export interface FactionRecord extends NamedEntity {
slug: 'alliance' | 'horde';
}
export interface PetFamilyRecord extends NamedEntity {
iconFileDataId: number | null;
/** Raw client CreatureFamily fields; they do not establish NPC tameability. */
foodMask: number;
talentType: number;
}
export interface CatalogManifest {
schemaVersion: 1;
defaultChannel: string;
resources: CatalogResource[];
channels: Record<
string,
{
dataset: string;
build: string;
product: string;
locales: ClientLocale[];
source: {
product: string;
build: string;
access: string;
tables: Record;
archive?: { commit: string; extraction: string };
};
}
>;
}
export interface CatalogEnvelope {
schemaVersion: 1;
dataset: string;
channel: string;
build: string;
locale: ClientLocale;
resource: CatalogResource;
data: T[];
}
export interface Collection {
list(options?: CatalogOptions): Promise;
get(idOrSlug: number | string, options?: CatalogOptions): Promise;
}
export interface CatalogSdk {
manifest(): Promise;
Classes: Collection;
Races: Collection;
Factions: Collection;
Pets: {
families(options?: CatalogOptions): Promise;
family(id: number, options?: CatalogOptions): Promise;
};
}
export interface WarcraftForeverCatalogNamespace {
Catalog: CatalogSdk & { createClient(options?: CatalogClientOptions): CatalogSdk; version: string };
Classes: CatalogSdk['Classes'];
Races: CatalogSdk['Races'];
Factions: CatalogSdk['Factions'];
Pets: CatalogSdk['Pets'];
}
export type DataResource = 'items' | 'recipes' | 'professions' | 'spells' | 'references';
export interface DataManifest {
schemaVersion: 1;
defaultChannel: string;
channels: Record<
string,
{
build: string;
product: string;
locales: string[];
datasets: Record;
}
>;
limits: { batch: number; search: number; defaultSearch: number };
unavailable: Record;
}
export interface DataEnvelope {
schemaVersion: 1;
dataset: string;
locale: string;
resource: DataResource;
source: { build: string; product: string; observedAt?: string; access?: string };
coverage: string;
data: T;
}
export type DataBatch = DataEnvelope & { missingIds: number[] };
export type DataPage = DataEnvelope & { nextCursor: string | null };
export interface SpellVariant {
kind: 'talent' | 'legacy' | 'recipe';
description: string | null;
descriptionLocale: string | null;
recipeId?: number;
calculator?: string;
revision?: string;
treeId?: string;
talentId?: string;
rank?: number;
details?: string[];
scaling?: { level: number | null; gear: false; buffs: false };
castMs?: number | null;
cooldown?: { ms: number; category?: number } | null;
}
export interface PublicSpell {
id: number;
name: string | null;
nameLocale: string | null;
iconUrl: string;
url: string;
undiscovered?: boolean;
variants: SpellVariant[];
}
export interface ReferenceValue {
id: number;
name: string;
nameLocale: string;
color?: string | null;
inferred?: boolean;
}
export interface ReferenceCatalog {
equipmentSlots: ReferenceValue[];
statLabels: ReferenceValue[];
schools: ReferenceValue[];
itemClasses: ReferenceValue[];
qualities: ReferenceValue[];
powerTypes: ReferenceValue[];
classColors: Array<{ slug: string; color: string; source: 'site-presentation' }>;
}
export interface DataCollection {
get(id: number, options?: PublicDataOptions): Promise>;
getMany(ids: number[], options?: PublicDataOptions): Promise>;
search(query: string, options?: PublicDataSearchOptions): Promise>;
}
export interface DataClient {
manifest(): Promise;
Items: DataCollection;
Recipes: DataCollection;
Professions: DataCollection;
Spells: DataCollection;
References: { list(options?: PublicDataOptions): Promise> };
}
export interface DataClientOptions extends PublicDataOptions {
origin?: string;
/** Custom JSON transport; images and site URLs still use their own sources. */
reader?: JsonReader;
}
export type PublicDataResource = 'items' | 'recipes' | 'professions';
export interface PublicRecordName {
name: string | null;
nameLocale: ClientLocale | null;
url?: string;
iconUrl?: string;
}
export interface PublicItem extends Item, PublicRecordName {
effects: ItemEffectLine[];
flavor: string | null;
}
export interface PublicRecipe extends Recipe, PublicRecordName {
description: string | null;
}
export interface PublicProfession extends Omit, PublicRecordName {
categories: Array;
}
export interface PublicDataRecords {
items: PublicItem;
recipes: PublicRecipe;
professions: PublicProfession;
}
export interface PublicRecordBatch {
/** Unique existing records in request order. */
data: T[];
missingIds: number[];
}
export interface PublicRecordPage {
data: T[];
nextCursor: string | null;
}
export interface PublicDataOptions {
channel?: string;
/** Immutable dataset returned by discovery or an earlier response. */
dataset?: string;
locale?: string;
}
export interface PublicDataSearchOptions extends PublicDataOptions {
limit?: number;
cursor?: string;
/** Undiscovered client records are excluded from search by default. */
includeUnknown?: boolean;
}
export interface CalculatorDescriptor {
data: string;
revision: string;
revisions: Record;
}
export interface CalculatorClientConfig {
origin: string;
definitions: Record;
/** Immutable dataset descriptor published by Warcraft Forever. */
dataset?: string;
}
export interface CalculatorClientOptions {
origin?: string;
dataset?: string;
}
export interface TalentRequest {
class: string;
revision?: string;
maxLevel?: number;
}
export interface LegacyRequest {
revision?: string;
}
export type BuildInput = { code: string; allocation?: never } | { allocation: TalentAllocation; code?: never };
export type BuildFormat = 'blizzard' | 'compact' | 'structured';
export interface CalculatorValidation {
valid: boolean;
revision: string;
dataset?: string;
pointBudget: number;
pointsSpent: number;
pointsRemaining: number;
complete: boolean;
issues: TalentIssue[];
allocation: TalentAllocation;
}
export type CalculatorEncoded = CalculatorValidation &
({ format: 'blizzard' | 'compact'; code: string } | { format: 'structured'; build: Build });
export interface CalculatorDiff extends TalentAllocationDiff {
revision: string;
dataset?: string;
pointBudget: number;
before: CalculatorValidation;
after: CalculatorValidation;
}
export interface CalculatorClient {
definition(options: Request): Promise;
decode(options: Request & { code: string }): Promise;
encode(
options: Request & { allocation: TalentAllocation; format: 'structured' }
): Promise;
encode(
options: Request & { allocation: TalentAllocation; format?: 'blizzard' | 'compact' }
): Promise;
encode(options: Request & { allocation: TalentAllocation; format?: BuildFormat }): Promise>;
validate(options: Request & BuildInput): Promise;
diff(
options: Request & { before: TalentAllocation | string; after: TalentAllocation | string }
): Promise;
}
export type TalentsClient = CalculatorClient;
export interface LegacyClient extends Omit<
CalculatorClient,
'definition'
> {
definition(options?: LegacyRequest): Promise;
}
export interface StructuredBuild {
revision: string;
pointsSpent: number;
trees: {
id: string;
name: string;
pointsSpent: number;
talents: {
id: string;
name: string;
rank: number;
maxRanks: number;
row: number;
column: number;
icon: string;
spellId?: number;
description?: string;
details?: string[];
}[];
}[];
}
export interface StructuredTalentBuild extends StructuredBuild {
class: string;
}
export interface StructuredCalculatorBuild extends StructuredBuild {
id: string;
}
```
---
# Build exports
Export a Blizzard in-game loadout string, a compact code, or structured trees and spells.
Source: https://warcraftforever.com/developers/talents/exports/
After `ready`, `exportBuild()` returns the Blizzard in-game format. All exports describe the mount's current allocation; partial builds can be exported too. Use `state.complete` or the `submit` event when every point must be spent.
```js
const calculator = WarcraftForever.Talents.mount('#talents', {
class: 'paladin', maxLevel: 30
});
await calculator.ready;
const inGame = calculator.exportBuild(); // same as exportBuild('blizzard')
const compact = calculator.exportBuild('compact');
const structured = calculator.exportBuild('structured');
calculator.on('submit', (state) => {
const saved = {
class: state.class,
revision: state.revision,
code: calculator.exportBuild('blizzard')
};
// Send saved to your own backend.
});
```
## In-game strings
The encoder uses the Blizzard V2 trait-loadout bit stream: class specialization, tree hash, complete client node order, selected ranks and entry selection. The Forever client accepts a zero tree hash. Supported definitions have one entry per visible node. Imports reject unsupported versions, another class, unsupported selected/granted nodes, invalid ranks and allocations over the level cap.
The codec follows the [Forever build 1.60.1.70124 client Lua](https://github.com/Gethe/wow-ui-source/blob/966519cf0ad2c10301ea011a88c14b25697c9687/Interface/AddOns/Blizzard_PlayerSpells/ClassTalents/Blizzard_ClassTalentImportExport.lua). The numeric version is V2; a live client import/re-export has not yet been verified. An archived revision without client serialization metadata throws when requesting this export; its compact and structured exports remain available.
Pass the string back as `build: { code, revision }`. For a site link use `/talents/paladin/?v=2&build=` followed by `encodeURIComponent(code)`. The query parameter preserves `+` and `/` characters in the in-game alphabet. The site still generates compact share URLs for compatibility.
## Structured data
`exportBuild('structured')` returns `{ class, revision, pointsSpent, trees }`. Trees retain their published ID, localized name, point total and `talents` array. Every selected talent includes:
- `id`, `name`, `rank`, `maxRanks` and zero-based `row` / `column`.
- An absolute Warcraft Forever `icon` URL.
- The selected rank's `spellId`, `description` and `details`, when available.
Empty trees remain in the result with an empty `talents` array. Unselected talents are omitted. Talent IDs remain distinct even when spells are shared. Returned data is detached from the mount; changing it does not edit the calculator.
## Deprecated compact format
Compact digit strings (including old hyphen-separated links) continue to load. Existing `state.code`, `state.shareUrl`, saved builds and archived revision URLs retain their meaning. There is no automatic rewrite of persisted values and no removal date.
For new integrations, request the in-game export explicitly. Store `class` and `revision` beside either string: the layout revision is independent of the JavaScript library release. Structured data is an output for custom rendering or analysis, not a replacement persisted build format.
---
# State and events
Read builds, react to changes, and clean up a mounted calculator.
Source: https://warcraftforever.com/developers/talents/lifecycle/
`ready` resolves with the initial state. Use `getState()`, `setBuild({ code, revision })`, `update(options)`, `on(event, listener)`, and `destroy()` for later changes. `on()` returns an unsubscribe function. Updates resolve with committed state; an invalid build or revision rejects and leaves the previous state intact.
State includes the class, code, revision, full allocation, points spent and remaining, maximum level, completion, and standalone share URL. `complete` means a valid build has spent exactly `maxLevel − 9` points. Listen for `ready`, `change`, `complete`, `submit`, or `error`. Save appears only when an editable build is complete; clicking it emits `submit`. `change` and the transition to `complete` describe user edits. API updates return state without emitting these edit events. Error listeners receive an Error object.
`state.code` remains the deprecated compact format for existing integrations. Call `exportBuild('blizzard')` for an in-game string or `exportBuild('structured')` for selected trees and spells. See [build exports](https://warcraftforever.com/developers/talents/exports/) for the formats and revision requirements.
Mount after the target element and script exist. Multiple mounts are independent. Await an update before depending on it; a newer update or destroy rejects an unfinished update with `AbortError`. Updates preserve the current build unless you supply `build`; pass `build: undefined` to reset. Changing class without a new build resets its build and tree selection. Destroy on host component unmount. These scripts do not require React on your site.
---
# Mount options
Configure class, level cap, layout, language, and editing.
Source: https://warcraftforever.com/developers/talents/options/
| Option | Use |
| --- | --- |
| `class` | Required class slug, such as `paladin`. |
| `view` | `"calculator"` (default), read-only `"tree"`, or linked `"icons"` for allocated talents. |
| `editable` | Defaults to true for the calculator. Tree and icon views are always read-only. |
| `build` | `{ code, revision }` from a talent share URL. An optional `path` preserves its Point Order link until the tree changes. |
| `tree` | Omit for three trees on wide containers and tabs in narrow containers. `"all"` keeps every tree visible; a stable tree ID shows one tree. A one-tree view keeps the full class build. |
| `maxLevel` | 10–60; default 60. The point limit is level minus 9. |
| `scale` | Greater than 0 through 2; default 1. Only the trees scale. |
| `chrome` | `"bare"`, `"compact"` (default), or `"full"`. An object can override `title`, `summary`, `instructions`, `reset`, `share`, and `attribution`. |
| `locale` | A supported site language code, such as `de`. |
---
# Saved builds
Render a saved allocation as linked icons or a read-only tree.
Source: https://warcraftforever.com/developers/talents/saved-builds/
```js
const saved = { class: 'paladin', code: '1__', revision: '2' };
const preview = WarcraftForever.Talents.mount('#build-preview', {
class: saved.class,
build: { code: saved.code, revision: saved.revision },
view: 'icons', // or 'tree'
chrome: 'bare'
});
preview.ready.catch(console.error);
```
Icons link to the full Warcraft Forever build. The revision identifies the talent layout, independently of the JS release. Preserve it alongside the code. Your backend must validate submitted builds and enforce your application's class and level requirements; browser validation is only the editing experience.
---
# Tooltips
Add talent, item, and recipe cards to links on your site.
Source: https://warcraftforever.com/developers/tooltips/overview/
Use a separate script to show the site's familiar cards on your own links. Only elements with `data-wf-tooltip` are affected. A hover or keyboard focus loads the card; a tap opens a detail dialog with an Open link action. Link destinations and ordinary click behavior stay yours.
## Copy and paste
```html
Divine Strength
Minor Healing Potion
Handstitched Leather Boots
```
Try [the live demo](https://warcraftforever.com/embed/tooltips/demo/) with current and archived talents, items, recipes, German and French labels, added links, and a detach control. Include the talent calculator script separately when you also need an editable build.
## Targets and options
| Setting | Use |
| --- | --- |
| `data-wf-tooltip` | `talent::`, `item:`, or `recipe:`. |
| `data-wf-rank` | Talent rank, default 1. Use 0 to show the unlearned and next-rank text. |
| `data-wf-revision` | Archived talent layout revision; omit for the current revision. |
| `locale` | Optional `attach(root, { locale: 'de' })`. Without it, the script uses a supported browser language or English. |
| `channel` | Optional published professions channel ID; the default channel is used otherwise. |
`attach(document)` also accepts an element root. It watches marked elements added later inside that root and returns `open(element)` and `destroy()`. Use separate roots for separate language or channel choices. Unknown cards disappear quietly while the underlying link remains available. Item and recipe coverage follows our published professions data; it does not include every item or spell in the game.