Skip to content

LinkedDataHub Packages

Martynas Jusevičius edited this page Sep 11, 2026 · 5 revisions

Packages add vocabulary support to an application: an RDF ontology (constructors, constraints, property views) and an XSLT stylesheet (custom rendering templates) that a dataspace imports as a unit.

Version: Packages were introduced experimentally in 5.2 as a file-installation mechanism. Since 5.10.0 they are fully declarative: importing a package is a single <app> ldh:import <package-uri> triple, nothing is copied into the webapp, and no restart is required. The 5.2-era packages/install/packages/uninstall endpoints, bin/admin/packages/ scripts and master-stylesheet file rewriting were removed in 5.10.0. Packages installed with earlier releases were webapp-file mutations and do not carry over — re-declare them with ldh:import.

Package structure

A package is a directory of two files, published as Linked Data:

packages/<package-name>/
├── ns.ttl         # ontology: constructors, constraints, property views
└── layout.xsl     # XSLT stylesheet with custom templates

Neither file is required on its own — a package can be ontology-only or stylesheet-only. What makes them a package is the description that points at them.

Package description

The description resolves from the package URI and carries, using standard properties:

  • rdf:type lapp:Package — required; the description is rejected otherwise
  • ldt:ontology — the package ontology URI
  • ac:stylesheet — the package stylesheet URL
  • dct:title, dct:description, dct:creator — displayed in the package catalog

The SKOS package, abridged (relative URIs resolve against the registry location, e.g. https://packages.linkeddatahub.com/skos/):

@prefix lapp: <https://w3id.org/atomgraph/linkeddatahub/apps#> .
@prefix ldt:  <https://www.w3.org/ns/ldt#> .
@prefix ac:   <https://w3id.org/atomgraph/client#> .
@prefix dct:  <http://purl.org/dc/terms/> .

<#this> a lapp:Package ;
    dct:title "SKOS Package" ;
    dct:description "Provides SKOS (Simple Knowledge Organization System) vocabulary support with custom templates for concept hierarchies, schemes, and collections." ;
    dct:creator <https://atomgraph.com/#company> ;
    ldt:ontology <ns/> ;
    ac:stylesheet <https://raw.githubusercontent.com/AtomGraph/LinkedDataHub-Apps/refs/heads/develop/packages/skos/layout.xsl> .

The description document itself is a regular LinkedDataHub document (dh:Container with foaf:primaryTopic <#this>), so the registry is browsable as Linked Data.

Package components

Ontology (ns.ttl)

The package ontology typically contains three layers:

Vocabulary import — pulls in the external vocabulary with owl:imports:

: a owl:Ontology ;
    owl:imports <http://www.w3.org/2004/02/skos/core> .

Constructors and constraints — SPIN constructors drive the instance creation forms, SPIN constraints validate instances:

skos:Concept spin:constructor :ConceptConstructor ;
    spin:constraint :MissingPrefLabel .

Property views — SPARQL-based views attached to properties, rendered as result blocks on resources that use them. ldh:view covers forward relationships (the resource has the property), ldh:inverseView inverse ones (other resources point at this resource via the property):

skos:narrower ldh:view :NarrowerConcepts .

:NarrowerConcepts a ldh:View ;
    dct:title "Narrower concepts" ;
    spin:query :SelectNarrowerConcepts .

Stylesheet (layout.xsl)

The package stylesheet imports the system layout through the static $ldh:base parameter (which the platform sets on the XSLT compiler) and overrides system template modes:

<xsl:param name="ldh:base" as="xs:anyURI" static="yes"/>

<xsl:import _href="{resolve-uri('static/com/atomgraph/linkeddatahub/xsl/layout.xsl', $ldh:base)}"/>

<!-- inject the package's own theme on top of the system stylesheets -->
<xsl:template match="rdf:RDF | srx:sparql" mode="ac:Stylesheets">
    <xsl:apply-imports/>
    <link href="..." rel="stylesheet" type="text/css"/>
</xsl:template>

<!-- the hierarchy predicates render as the concept tree, not as statement rows -->
<xsl:template match="skos:narrower | skos:broader | skos:related | skos:member" mode="ac:PropertyEditor"/>

The system modes are ac:* (design-system components and shared surfaces), ldh:* (LDH-owned components) and xhtml:* (childless element primitives).

Mode vocabulary is version-coupled. A package stylesheet overrides platform templates, so it is written against the platform's mode vocabulary. Version 6.0.0 removed Bootstrap 2.3.2 and the bs2: mode namespace along with it. A package written against bs2: modes still compiles — the overrides simply never match, and the package silently stops affecting rendering. Packages targeting 6.0.0 and later use ac:/ldh:/xhtml:.

Importing a package

The <app> ldh:import <package-uri> triple in the dataspace settings is the installation. It can be declared two ways:

  • permanently in config/dataspaces.trig, applied on restart
  • live via PATCH /settings (SPARQL Update against the dataspace description), effective on the next request

CLI

ldh packages list
# available	https://packages.linkeddatahub.com/skos/#this	SKOS Package

ldh packages add --package https://packages.linkeddatahub.com/skos/#this
ldh packages remove --package https://packages.linkeddatahub.com/skos/#this

packages list reads the registry catalog through the Linked Data proxy and marks each package installed or available, one tab-separated state / URI / title line per package, so the listing pipes:

ldh packages list | grep ^available | cut -f2

packages add and packages remove write the ldh:import declaration through PATCH /settings; remove is idempotent. The registry defaults to https://packages.linkeddatahub.com/ and --registry overrides it.

UI

The application settings modal lists the available packages with a per-row Installed checkbox; the form's single Save submits settings and package imports as one PATCH through /settings.

How it works at runtime

Resolution. The package description is resolved from the package URI: bundled descriptions are read from the classpath through the graph repository, remote ones are dereferenced over HTTP after URL validation. A description that cannot be resolved, or whose resource lacks the lapp:Package type, is skipped.

Ontology composition. The package ontology joins the application ontology's owl:imports closure automatically, derived from the ldh:import declarations at ontology-load time. Nothing is persisted — the ldh:import triples remain the single source of truth, so removing one cleanly removes the package's ontology on the next reload. A package ontology that fails to load is skipped, so a broken package cannot take the application ontology down. A /settings PATCH evicts the assembled closure and the affected caches, so installs and uninstalls take effect on the next request.

Stylesheet composition. The application stylesheet is composed with the package stylesheets in memory at XSLT compile time, per dataspace. An xsl:import element per package stylesheet is appended after the last existing import, which places packages above the system layers and below the application stylesheet's own declarations in import precedence — a package can override any system template while the application keeps the last word. The compiled executable is cached under a key derived from the stylesheet URI and the sorted package URIs, so a changed import set compiles fresh on the next request. If the composed stylesheet fails to compile (e.g. a package stylesheet URL cannot be loaded), the application stylesheet alone is used as a fallback.

Server-side rendering only. The client-side SEF is generated at build time and carries no package imports, so package rendering rules currently apply to server-rendered responses. On the initial page load the client keeps the server-rendered body instead of re-rendering over it, which preserves the package's output.

Available packages

The package catalog is data at the registry URI https://packages.linkeddatahub.com/. Until the registry is live, a bundled one-entry copy listing the SKOS package is served through the Linked Data proxy's mapped-URI resolution. Package sources live in the LinkedDataHub-Apps repository.

Creating a package

  1. Write the ontology (ns.ttl): owl:imports of the vocabulary, SPIN constructors/constraints, ldh:view/ldh:inverseView property views
  2. Write the stylesheet (layout.xsl): import the system layout via $ldh:base, override system modes (ac:*, ldh:*, xhtml:*)
  3. Publish the package description with rdf:type lapp:Package, ldt:ontology and ac:stylesheet, at a URI the target instances can dereference
  4. Import it with ldh packages add --package <uri> (or the settings modal)

Trust model and further development

Importing a package means executing the package author's code: the stylesheet runs server-side in the platform's XSLT processor, and the ontology's SPARQL executes against the application's endpoint. Import packages only from authors you trust, the same way you would install a server-side dependency.

Open work, roughly in priority order:

  • Integrity pinning. ldh:import <package-uri> is a mutable reference — the description and the stylesheet are re-fetched on cache misses and restarts, with no version, hash or signature, so the referenced content can change after review. Pinning content at import time (recording a hash, or vendoring the ontology and stylesheet into the application's own storage) would close the gap and remove the runtime dependency on the package host.
  • Execution containment. The composed stylesheet should run under a hardened XSLT configuration: URI schemes restricted for doc()/document()/unparsed-text(), secondary result documents rejected, and the transitive stylesheet fetches (the ac:stylesheet URL and its nested imports) subjected to the same URL validation as the package URI.
  • Client-side delivery. Applying package templates in the browser requires per-dataspace SEF compilation and serving; it also extends the trust question to every visitor's session and needs its own design pass.
  • Transitive imports. A package declaring ldh:import of another package is modeled but not yet resolved — only the application's direct imports are composed.
  • Registry. Making https://packages.linkeddatahub.com/ live, with curation and eventually signing.

Vocabulary reference

  • lapp:Package (https://w3id.org/atomgraph/linkeddatahub/apps#) — package class
  • ldh:import (https://w3id.org/atomgraph/linkeddatahub#) — application (or package) imports a package
  • ldt:ontology — package ontology URI (reused from LDT)
  • ac:stylesheet — package stylesheet URL (reused from AtomGraph Client)
  • ldh:view / ldh:inverseView — property views

Clone this wiki locally