json-schema-ref-parser: turning $ref chains into plain objects
Parse, Resolve, and Dereference JSON Schema $ref pointers in Node and browsers
At a glance
- What is it?
- JSON Schema $Ref Parser is a TypeScript library that crawls JSON Schema documents, follows external and remote $ref pointers, and hands back either a bundled schema or a dereferenced object. It handles circular references and mixed JSON and YAML, but it is a parser for schemas, not a validator, and the package requires node 22.19.0 or newer.
- Who is it for?
- Adopt json-schema-ref-parser if you load OpenAPI or JSON Schema documents whose $ref pointers reach across files or URLs and you want one in-memory object to work with. Do not adopt it as a schema validator, since it parses and resolves rather than checking instances against the schema, and expect the cost of that scope in the browser, where file system access is stubbed out.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 12 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem: a schema is rarely one file
A JSON Schema in the wild is rarely a self-contained document. It points at other files with $ref, sometimes at URLs, sometimes at fragments inside a sibling file, and sometimes at several of those at once. The project description names the cases explicitly: references you may not know about ahead of time, a mix of local files and remote URLs, a mix of JSON and YAML formats, and files that refer back to each other. Its worked example packs all four into one document, with a person definition pointing at a local JSON file, a place definition pointing into a YAML file with a JSON Pointer fragment, a thing definition pointing at an http URL, and a color definition pointing at an internal path. Any code that needs to read that schema has to decide what to do with each kind of pointer before it can look at a property. The library exists to absorb that decision once, in one place, so application code never has to.
Bundle and dereference are two different answers
The API offers two transformations and they solve different problems. Bundling combines multiple files into a single schema whose remaining $ref pointers are internal, which is what you want when the output has to be shipped somewhere else and still be valid. Dereferencing goes further and produces a plain JavaScript object with the pointers replaced by the values they point at, which is what you want when a library downstream expects data rather than schema. The feature list includes a third capability in the same family: custom resolvers for sources that are not files or URLs, such as a database. Three details make the library convenient in practice. It maintains object reference equality, so two $ref pointers to the same value resolve to the same object instance rather than two copies. It supports circular references, nested references, back-references and cross-references between files. And it accepts JSON and YAML schemas, or a mix of the two, so the resolver does not care what format the referenced file happens to use.
A Node package that also runs in a browser
The package is published as @apidevtools/json-schema-ref-parser and is written in TypeScript, with the module and main entry points at dist/lib/index.js and the type declarations at dist/lib/index.d.ts. The package is marked as an ES module and the manifest declares a browser field that maps fs to false, which is how the library avoids pulling Node's file system into a browser bundle. The runtime target is explicit: engines asks for node >=22.19.0. The browser story is documented rather than assumed. Browser usage needs a bundler such as Webpack, Rollup, Parcel or Browserify, and the project notes that some bundlers need configuration, including setting browser: true in rollup-plugin-resolve. It also notes that Webpack 5 dropped the default export of Node core modules in favour of polyfills, so a browser build has to declare fallbacks explicitly.
Your first dereference, and the option that changes the result
The documented example is short enough to read in full. Dereferencing a schema mutates the input in place by default, and the returned value is a reference to the same object, so the property access works whether you use the original variable or the return value. If you would rather keep the original untouched, the mutateInputSchema option controls that:
import $RefParser from "@apidevtools/json-schema-ref-parser";
try {
await $RefParser.dereference(mySchema);
console.log(mySchema.definitions.person.properties.firstName);
let clonedSchema = await $RefParser.dereference(mySchema, { mutateInputSchema: false });
console.log(clonedSchema.definitions.person.properties.firstName);
} catch (err) {
console.error(err);
}Both calls are asynchronous and both are wrapped in a try/catch in the example, because a missing file or an unreachable URL rejects the promise rather than throwing synchronously. The reader should see the resolved property value printed once per call. The full API, including the bundle and resolve entry points and the plugin interface, is documented at apidevtools.com/json-schema-ref-parser/docs/.
Old Node needs a fetch polyfill before anything resolves
Remote references are the case that catches people out, because resolving a URL needs a fetch implementation. The project states that on Node versions below 18 a polyfill for fetch is required, and gives node-fetch as the example, installed into the global scope before the parser runs:
import fetch from "node-fetch";
globalThis.fetch = fetch;Given the engines field now asks for node >=22.19.0, that instruction mostly matters for projects pinned to an older runtime or for a browser target that lacks fetch. The same fetch call is what makes a remote $ref a network operation at resolution time, which has two consequences worth planning for. Resolution time becomes dependent on network latency and on the remote host being up, so a schema that resolves fine locally can fail in a build pipeline with restricted egress. And any resolver that fetches a URL should be given a timeout and a policy about which hosts are allowed.
Where the library stops, and what it deliberately leaves alone
The boundary is clear in the name and the feature list. This is a ref parser, not a schema validator. It parses, resolves and dereferences, and it says nothing about checking an instance against the resulting schema, so a project that needs validation still needs a separate validator. Two other limits follow from the design. Dereferencing a schema with circular references produces an object graph with a cycle in it, which is convenient in JavaScript but cannot be serialized with JSON.stringify without a replacer, so a dereferenced result is not automatically shippable as JSON. And the remote resolution behaviour described above means resolution is not hermetic: a schema that points at a URL is a dependency on that URL, which is a supply chain consideration rather than a technical inconvenience. The API documentation covers the resolver plugin interface, which is where a team that needs to restrict or stub out remote fetches would look.
Working on the library itself
The repository is a pnpm workspace with a pnpm-lock.yaml, and the documentation gives a short contributor loop: clone the repository, run pnpm install, then run pnpm test. The package scripts in package.json describe the rest of the workflow. The test runner is vitest with coverage, and the scripts include a browser test path, test:browser, that runs node ./test/fixtures/run-browser-tests.mjs, a specific-target variant that runs vitest against a named case, an update mode for refreshing snapshots, and a watch mode. Linting uses oxlint over lib, formatting uses oxfmt across js, jsx, ts, tsx, har, json, css and md files, and the build script is rimraf dist followed by tsc, with a separate typecheck script that runs tsc with noEmit over both the main project and the type tests in test/types. The tree also carries an AGENTS.md and a vite.config.ts, with the test suite running under vite's tooling. The licence is MIT.
Editorial conclusion
Adopt json-schema-ref-parser if you load OpenAPI or JSON Schema documents whose $ref pointers reach across files or URLs and you want one in-memory object to work with. Do not adopt it as a schema validator, since it parses and resolves rather than checking instances against the schema, and expect the cost of that scope in the browser, where file system access is stubbed out. Before upgrading, check your Node version against the engines field of package.json, which requires node >=22.19.0, and note that a dereferenced result with circular references cannot be passed to JSON.stringify without a replacer.
Frequently asked questions
What is the difference between bundle and dereference in json-schema-ref-parser?
Bundling combines multiple files into a single schema that keeps only internal $ref pointers, while dereferencing replaces the pointers with the values they point at, producing a plain JavaScript object. Both are available as methods on the parser.
Does json-schema-ref-parser validate data against a schema?
No. The library parses, resolves and dereferences JSON Schema documents and returns JavaScript objects. It does not check instances against a schema, so validation needs a separate library.
Does dereferencing change my original schema object?
Yes, by default the schema is modified in place and the returned value is a reference to that same object. Passing the mutateInputSchema option set to false leaves the original untouched and returns a separate object.
Can json-schema-ref-parser handle circular references between files?
The documentation states that circular references, nested references, back-references and cross-references between files are supported, and that pointer equality is maintained so the same value resolves to the same object instance.
Which Node version does the current package require?
The engines field in package.json requires node >=22.19.0, and the README notes that a fetch polyfill such as node-fetch is needed on Node versions below 18. The published version is v16.0.5.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/apidevtools-json-schema-ref-parser)Was this useful?