# PyLD: a Python JSON-LD processor, and the version 4 rewrite around RDFLib

> PyLD implements the JSON-LD specification in Python, giving you expand, compact, frame, flatten, to_rdf, from_rdf and normalize for JSON-LD documents. Version 4 moves RDF handling to RDFLib and defaults canonicalization to RDFC10, with a legacyMode escape hatch for older behaviour.

**digitalbazaar/pyld** — JSON-LD processor written in Python

- Repository: https://github.com/digitalbazaar/pyld
- Website: https://digitalbazaar.github.io/pyld/
- Stars: 687 · Forks: 140
- Language: Python
- License: BSD-3-Clause
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/digitalbazaar-pyld

## JSON-LD in Python, and who actually needs it

PyLD is an implementation of the JSON-LD specification in Python. The specification itself, as the README describes it, sits in a family of formats: JSON per RFC7159 is a simple language for representing objects on the Web, Linked Data describes content across documents using IRIs, and JSON-LD is a light-weight syntax for expressing Linked Data in JSON and for adding semantics to JSON that already exists. The design goals listed in the README are the reason the format exists at all, namely that it does not require many applications to change their JSON, that it does not disturb already deployed systems, and that it gives a smooth migration path from plain JSON to JSON with meaning. The audience for the library is therefore specific: Python developers working with linked data, schema.org payloads, verifiable credentials or any system where a document has to be transformed between JSON and RDF without losing its meaning.

## The API surface is the JSON-LD algorithms, one function each

The library exposes the specification's algorithms as plain functions, and the README's usage examples are the clearest description of what they do. jsonld.compact takes a document and a context and produces a compacted document, jsonld.expand does the reverse and removes the context, jsonld.flatten takes a document and pulls all the deep-level trees up to the top level, and jsonld.frame transforms a document into a particular tree structure according to a given frame. Each of them also accepts URLs, so a document and a context can be given as identifiers and fetched. Beyond those, jsonld.to_rdf converts a document into RDF and jsonld.from_rdf goes the other way, and jsonld.normalize produces a canonical representation that can be used for hashing and comparison. The example also shows how a custom blank node issuer is supplied through an identifierIssuer option, and that flatten and frame accept the same option.

## Version 4 hands RDF handling to RDFLib

The most consequential change in the project is the one described under what is new in version 4, and it is a change of return type rather than of concept. RDF handling moves to RDFLib, and RDF Dataset Canonicalization 1.0 support is added alongside it. The developer-facing consequences are listed explicitly. jsonld.to_rdf() now returns an rdflib.Dataset by default when no output format is requested, and jsonld.from_rdf() accepts an rdflib.Dataset, N-Quads strings and the legacy dataset dict. If you need the old behaviour, jsonld.to_rdf(doc, {'legacyMode': True}) returns the RDF.js-like dataset dict that PyLD versions below 4 produced. The internal pyld.nquads parser and serializer module has been removed entirely, and the README directs you to the JSON-LD APIs with format 'application/n-quads' or to RDFLib directly. In other words, if you were relying on a PyLD-specific N-Quads parser, that path is closed.

## Canonicalization changes its default algorithm

The second behavioural change in version 4 is the one most likely to break a pipeline quietly. jsonld.normalize() now defaults to RDFC10, and the README says to pass {'algorithm': 'URDNA2015'} if older canonicalization output must remain stable. This matters wherever canonical form is used as a fingerprint. If you hash a document to compare two copies, or you sign a canonical string, or you store a digest and later check it against a newly computed one, switching the algorithm changes the bytes and therefore changes the digest. The README ties the algorithm to the W3C RDF canon specification, and the version 4 notes are linked separately from the changelog. The practical advice is unglamorous but important: find every place in your code that persists or verifies a normalized digest, and decide deliberately, per site, whether the old output must be reproducible.

## Installing PyLD, and the extras that keep requests out of your tree

Installation is a single pip command:

```bash
pip install PyLD
```

What follows in the README is a packaging decision worth reading. Declaring a dependency on pyld does not pull in Requests or aiohttp, and if you need one of those for the Document Loader, you either depend on the external library directly or express the requirement as PyLD[requests] or PyLD[aiohttp]. The optional-dependencies table in the packaging metadata shows the full set of extras, covering requests, aiohttp, requests-cache, cachetools, frozendict and cli. The core dependencies are cachetools, frozendict, lxml and rdflib, and the requirements section states Python 3.10 or later and rdflib 7.3 or later. The project requires Python 3.10 as a floor, and the classifiers list support for 3.10 through 3.14, with the development status marked as Beta.

## A console script for the algorithms, including cache management

There is also a command line interface, installed through the cli extra, which gives you the JSON-LD 1.1 algorithms from a shell:

```bash
pip install "PyLD[cli]"
pyld expand document.jsonld
pyld compact document.jsonld context.jsonld
pyld frame document.jsonld frame.jsonld
pyld to-rdf document.jsonld
pyld from-rdf document.nq
```

The CLI also provides get and flatten, and it handles HTTP cache management, with the full command list, options and stdin support documented separately. The packaging metadata wires the entry point to pyld.cli.entry:main and the cli extra pulls in typer and requests-cache, which is where the command line and the cache handling come from. The HTTP cache matters for the Document Loader, since expanding or compacting a document that references a remote context means fetching it, and a local cache is what keeps repeated runs from re-fetching.

## A documentation site with versioned releases, built by uv and mkdocs

The repository shows a project that takes its documentation and its versioning seriously. The Makefile drives everything, with an install target that runs uv sync --locked and a test target that runs uv run --locked pytest --cov=pyld, so the test suite runs against pinned dependencies. The documentation targets build the site with mkdocs in strict mode, serve it on 127.0.0.1 with a port that can be overridden, and deploy it. The docs-deploy target requires a VERSION and can build a version from an older git ref in a separate worktree, which is how a previous release of the documentation can be reconstructed, and the Makefile even patches mkdocs.yml for older tags that predate the current version selector configuration. The tree also holds a specifications directory, which is where the JSON-LD specification material the tests are written against lives, and the site is built with mkdocs plus mike for versioned documentation, with macros for the shared snippets.

## Conclusion

PyLD fits a Python service that has to read or emit JSON-LD and needs the specification implemented rather than approximated, particularly if you are already using RDFLib or need dataset canonicalization for hashing and comparison. It does not fit a project that only needs to attach a context to a document, or a team unwilling to track a library where a major version is still being shaped. Before upgrading across the version 4 boundary, read the upgrade notes, decide whether your code depends on the legacy dataset dict, and check whether URDNA2015 output has to stay byte-stable, since the default algorithm changes to RDFC10.

## FAQ

### What changed in PyLD version 4?

RDF handling moved to RDFLib and RDF Dataset Canonicalization 1.0 support was added. jsonld.to_rdf() returns an rdflib.Dataset by default, jsonld.normalize() defaults to RDFC10, and the internal pyld.nquads parser and serializer module has been removed.

### How do I keep the old dataset dict behaviour in PyLD?

Pass the legacyMode option: jsonld.to_rdf(doc, {'legacyMode': True}) returns the RDF.js-like dataset dict that versions below 4 returned. jsonld.from_rdf() also still accepts that legacy dict alongside rdflib.Dataset and N-Quads strings.

### How do I install PyLD with the command line tool?

Install the cli extra with pip install "PyLD[cli]", which brings in the pyld console script and the typer and requests-cache dependencies. The CLI exposes expand, compact, frame, to-rdf, from-rdf, get and flatten, plus HTTP cache management, and it reads from stdin.

### Does installing PyLD pull in Requests or aiohttp?

No. The README states that defining a dependency on pyld does not pull in Requests or aiohttp, and that you should either depend on the library you want directly or require PyLD[requests] or PyLD[aiohttp] if you need one of them for the Document Loader.

## Sources

- [digitalbazaar/pyld on GitHub](https://github.com/digitalbazaar/pyld)
- [License: BSD-3-Clause](https://github.com/digitalbazaar/pyld/blob/master/LICENSE)
- [Project website](https://digitalbazaar.github.io/pyld/)
- [README](https://github.com/digitalbazaar/pyld/blob/master/README.md)
- [Releases](https://github.com/digitalbazaar/pyld/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/digitalbazaar-pyld
