Library / SDK
linkeddata/rdflib.js avatar
linkeddata/rdflib.js

rdflib.js gives a browser the same RDF store Solid expects on the server

Linked Data API for JavaScript

598 stars147 forksHTMLNOASSERTION

At a glance

What is it?
rdflib.js is a JavaScript RDF library that reads and writes RDF/XML, Turtle and N3, reads RDFa and JSON-LD, and talks to a remote store over WebDAV or SPARQL Update. Its most Solid-specific behaviour is the smushing of owl:sameAs nodes, which it deliberately leaves switched off by default.
Who is it for?
Adopt rdflib.js if you are building a Solid application, since it implements the same store model a Solid server exposes, including the fetch, update and metadata behaviour, and it runs in the browser and in Node from the same package. Skip it if you need a full SPARQL 1.1 engine, because the query support is limited to graph matching, and if you need full RDFa writing, because RDFa is read but not written.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 11 days ago.
What is it written in?
Mainly HTML, 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

A store that behaves like a Solid server

rdflib.js is described as a Javascript RDF library for browsers and Node.js, and the description is plain about what it does.

It reads and writes RDF/XML, Turtle and N3, and reads RDFa and JSON-LD. The asymmetry in that list matters, since RDFa and JSON-LD are read-only formats here while the three write formats are the core serialisation targets.

It is a read and write Linked Data client, using WebDav or SPARQL/Update. So the library can act as a client against a remote store as well as a local one, and the remote protocol choices are the two that Solid deployments use.

The compatibility target is the RDF/JS data model specification, which is the standard that describes terms and how they behave independently of any particular implementation. The terms are created with the factory functions namedNode(), blankNode(), literal(), variable() and quad(), all exported from the package root, with sym() as an alias for namedNode().

The remaining capabilities are the more unusual ones. There is real-time collaborative editing with web sockets and PATCHes. There is a local API for querying a store. There is smushing of nodes from owl:sameAs and the owl functional and inverse functional property predicates. And it tracks provenance of triples, keeping metadata in RDF from HTTP accesses.

The project is published on npm as rdflib, with the last push on 2026-10-12 and releases v2.4.0 from 2026-06-24, v2.3.9 from 2026-05-26 and v2.3.8 from 2026-05-16. The package author is Tim BL, with a listed set of contributors including Ruben Verborgh, and the licence is MIT.

Two install routes, and the browser build is the awkward one

The install section separates the browser case from the Node case, and the browser case splits into two.

For a browser with a bundler such as Webpack, it is one command:

bash
npm install rdflib

For a browser without a bundler, you clone the repository, install the dependencies and generate a script file to include:

bash
git clone [email protected]:linkeddata/rdflib.js.git;
cd rdflib.js;
npm install;

and then generate the dist directory:

bash
npm run build:browser

For Node.js, you need Node and the Node Package Manager installed, and then:

bash
npm install --save rdflib

The reason the browser-without-a-bundler path needs a build step is the dependency list. The package pulls in jsonld at 9.0.0 for JSON-LD processing, n3 at 2.0.3 as the Turtle and N3 parser and serialiser, @rdfjs/types for the type definitions, @xmldom/xmldom for XML parsing, cross-fetch for the fetch layer, solid-namespace for the Solid vocabulary, and @frogcat/ttl2jsonld for converting Turtle to JSON-LD.

Several of those are not browser-friendly as shipped, and the webpack configuration at the repository root is what resolves that. There is a .babelrc for the transpilation and a browserslist entry of greater than 0.5 percent, which is a very old browser target and a deliberate choice for a library embedded in arbitrary pages.

The repository also carries jquery.uri.js and jquery.xmlns.js at the root, which are shims for the two jQuery plugins that historically provided URI resolution and XML namespace handling in the browser.

Terms follow the RDF/JS spec, including a three-argument pitfall

The terms section is short and precise, and it documents one specific trap that has caught people before.

The factory functions create terms, and per the specification literal() takes two arguments, where the second one is either a language tag or a datatype:

ts
literal(value: string | number | boolean | Date, languageOrDatatype?: string | NamedNode): Literal
js
import { literal, namedNode } from 'rdflib'

literal('chat')       // plain string literal ("chat"^^xsd:string)
literal('chat', 'fr') // language-tagged literal ("chat"@fr, datatype rdf:langString)
literal('2019', namedNode('http://www.w3.org/2001/XMLSchema#gYear')) // typed literal
literal('2019', 'http://www.w3.org/2001/XMLSchema#gYear')            // same, with the datatype IRI as a string
literal(4)            // convenience form: infers the datatype ("4"^^xsd:integer)

A string second argument is interpreted as a datatype IRI when it contains a colon, and as a language tag otherwise. That single rule is what makes the shorthand forms work, since it is why literal('2019', 'http://...gYear') is a typed literal while literal('chat', 'fr') is a language-tagged one.

The pitfall is documented explicitly. A common pattern present in some older tutorials is calling literal(value, undefined, datatype), and the factory function ignores the third argument and returns a plain xsd:string literal. The three-argument form only exists on the class constructor, `new Literal(value, language, datatype)`. To create a typed literal with the factory function, pass the datatype as the second argument.

That is a good example of a library documenting its own API sharp edge rather than leaving it to be discovered. The failure mode is silent in the worst way, since a literal that was supposed to be a date is a string, and nothing complains until a query returns nothing.

The practical rule for anyone using this library is to never write the three-argument form against the factory function.

Smushing is off by default, and that is the right default

Smushing is the merging of everything a graph knows about two identifiers which are stated to denote the same thing, and the library's position on it is deliberate.

Stores perform no smushing by default, and you opt in per feature when creating the store:

js
const kb = $rdf.graph(['sameAs', 'InverseFunctionalProperty', 'FunctionalProperty'])

So the feature list is passed at construction time and the three entries mean three different equivalences. With the sameAs feature enabled, whenever a statement A owl:sameAs B is added the store equates the two nodes: one of them is chosen as canonical and all statements are re-indexed under it, so queries about either identifier see the merged data. The InverseFunctionalProperty and FunctionalProperty features likewise equate nodes which share a value of such a property.

When two nodes are equated, the store records the equivalence by adding one statement of the form:

code
<canonical> <http://www.w3.org/2007/ont/link#uri> <obsoleted> .

that is, a back-link from the canonical node to the identifier it replaced.

That triple is internal book-keeping, and it is how the store remembers aliases so that store.uris(term) and store.allAliases(node) can list all the identifiers something is known by. It is stored like any other statement, though, so you may encounter these triples when querying or serializing a store that has smushing enabled.

The same link namespace is also used by the Fetcher for the HTTP metadata it records, covering requests, responses and status codes, and that metadata lives in a separate metadata graph which can be removed with store.removeMetadata(doc).

One more behaviour is worth knowing about. If a Fetcher is attached to the store, equating two nodes also triggers a look-up of the newly-learned alias, so that data published under either URI gets loaded.

So smushing has a cost beyond the re-indexing, since enabling it can cause the client to fetch more data. That is a good reason it is opt-in rather than on by default.

Serializer flags, and the defaults differ by format

The Turtle, N3 and JSON-LD serializers accept an optional flags string to tweak output formatting and abbreviation behaviour, and the flag set differs by format.

The way to pass them is through the options argument to serialize:

ts
import { graph, serialize, sym } from 'rdflib'

const kb = graph()
const doc = sym('http://example.com/doc')
// ... add some statements ...

// Example: prevent dotted local parts in prefixed names
const turtle = serialize(doc, kb, doc.value, 'text/turtle', undefined, { flags: 'o' })

The flags are listed by the format they affect. The `s` and `i` flags are used by default for Turtle to suppress the = and => notations. The set `d e i n p r s t u x` is used for N-Triples and N-Quads to simplify output. The `dr` flags are used with JSON-LD conversion, with no default and no relative prefix. And the `o` flag is new, and it prevents abbreviating to a prefixed name when the local part contains a dot, which keeps an IRI like `http://example.org/ns/subject.example` in the angle-bracket form instead of `ns:subject.example`.

The merging behaviour differs between formats, and it is worth knowing which way round. For Turtle and JSON-LD, user-provided flags are merged with the defaults so your flags are honoured. By contrast, for the N-Triples family the flags replace the defaults rather than adding to them, which the documentation notes as the opposite behaviour.

That asymmetry is a trap in a subtle way. If you pass a flag to an N-Triples serializer expecting it to be added to the simplification set, you may get a different output than you expected, and the difference is a semantic one rather than a cosmetic one since the simplification flags affect which statements are emitted at all.

The flags are also combinable, with the documentation giving the example of combining them as 'o k'.

What the documentation does not cover, and where to look instead

The documentation list is honest about its own limits. The API documentation is described as partial but useful, and the rest of the list is a set of tutorials and diagrams.

There is a tutorial on using rdflib in a Solid web app, one on using rdflib.js that is hosted on GitHub, and one on using Turtle. There is a document on using authenticated and alternate fetch methods, which is the part that matters when your store is behind a login, since Solid deployments usually are.

The diagrams are the interesting documentation asset, because they cover the internals rather than the usage. There is a block diagram of the rdflib modules, one of the Fetcher, one of the Fetcher specifically handling retries, and one of the Update Manager.

Those four diagrams map onto the parts of the library that are hard to learn from the API. The Fetcher is the component that does HTTP, handles retries, records metadata and triggers alias lookups, and it is the component with the most surprising behaviour. The Update Manager is the component that deals with the local API for querying a store, and the one that implements the real-time collaborative editing with web sockets and PATCHes.

The Solid developer portal at Inrupt is linked as a source of further information, and the project is the reference implementation of the JavaScript side of the Solid protocol family.

So the shape of the library is a local store plus a fetcher plus an update manager, with the store's data model following the RDF/JS specification. That decomposition is what lets the same package run in a browser tab against a remote Solid pod and in a Node process, which is the reason the library exists at all rather than a browser-only variant.

The repository also carries an example/ directory with angularjs/ and people/ subdirectories, a parsers/ directory, a reference/ directory, both test/ and tests/ directories, and a testSaveRestore/ directory, which is a reasonable sign of a project with a serious test suite.

Editorial conclusion

Adopt rdflib.js if you are building a Solid application, since it implements the same store model a Solid server exposes, including the fetch, update and metadata behaviour, and it runs in the browser and in Node from the same package. Skip it if you need a full SPARQL 1.1 engine, because the query support is limited to graph matching, and if you need full RDFa writing, because RDFa is read but not written. Verify first whether you want smushing enabled, since it is off by default and turning it on changes how identifiers resolve, that you use the two-argument literal factory rather than the three-argument constructor, since the third argument is ignored, and which serializer flags you need, because the defaults differ between Turtle, N-Triples and JSON-LD.

Frequently asked questions

Which RDF formats does rdflib.js read and write?

It reads and writes RDF/XML, Turtle and N3, and reads RDFa and JSON-LD. The full set is listed as: reads and writes RDF/XML, Turtle and N3, reads RDFa and JSON-LD, a read and write Linked Data client using WebDav or SPARQL/Update, real-time collaborative editing with web sockets and PATCHes, a local API for querying a store, compatibility with the RDF/JS data model specification, SPARQL queries limited to graph match, smushing of nodes from owl:sameAs, and provenance tracking of triples with metadata kept in RDF from HTTP accesses.

How do I enable owl:sameAs smushing in rdflib.js?

Stores perform no smushing by default, and you opt in per feature when creating the store with `const kb = $rdf.graph(['sameAs', 'InverseFunctionalProperty', 'FunctionalProperty'])`. With sameAs enabled, adding a statement equating two nodes makes one canonical and re-indexes all statements under it, and the store records the equivalence with a back-link statement using the http://www.w3.org/2007/ont/link#uri predicate.

How do I install rdflib.js and how do I create a typed literal?

Install it with `npm install rdflib` for a browser with a bundler, `npm install --save rdflib` for Node.js, or clone the repository and run `npm run build:browser` to generate a script file to include. The literal factory takes two arguments, where the second is either a language tag or a datatype, so use `literal('2019', 'http://www.w3.org/2001/XMLSchema#gYear')` rather than the three-argument form, which the factory ignores.

Official sources

  1. Issues
  2. linkeddata/rdflib.js on GitHub
  3. Project website
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/linkeddata-rdflib-js.svg)](https://hysenlabs.com/projects/linkeddata-rdflib-js)

Was this useful?