A driver for connecting Metabase to SPARQL endpoints for querying RDF data.
This driver enables Metabase to connect to SPARQL endpoints using HTTP requests to query RDF data. It supports both secure and insecure connections with optional default graph specification.
This driver represents RDF classes as tables and properties as columns, allowing you to use Metabase's visual query builder to create SPARQL queries intuitively. Discovering the most frequent classes and properties can be computationally expensive on large datasets. You can disable this metadata synchronization feature in the driver's advanced configuration settings.
Tip
If this repository is useful to you, please consider starring it ⭐.
| Driver Version | Metabase Version | Notes |
|---|---|---|
| v0.0.11+ | v0.61.x | Built and tested against Metabase v0.61.1. |
| v0.0.10 | v0.56.3 — v0.56.x | Requires describe-database* (added in 0.56.3). |
| v0.0.1 – v0.0.9 | < v0.56.3 | Uses legacy describe-database. |
Important
Upgrading from v0.0.10 or earlier renames the synthetic primary-key column from id to subject and shortens class/property names whose URI starts with the Default Graph URI. After dropping the new jar in, re-sync each database so the renamed metadata lands cleanly.
- Download the latest driver from the releases page
- Copy
sparql.metabase-driver.jarto your Metabaseplugins/directory - Restart Metabase
- Add database → Select "SPARQL" → Enter endpoint URL
- Endpoint URL:
https://dbpedia.org/sparql - Default Graph:
http://dbpedia.org
PREFIX dbr: <http://dbpedia.org/resource/>
PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT DISTINCT ?scientist ?name ?birthDate ?deathDate
WHERE {
?scientist a dbo:Scientist ;
dbo:nationality dbr:Brazil ;
rdfs:label ?name .
OPTIONAL { ?scientist dbo:birthDate ?birthDateRaw }
OPTIONAL { ?scientist dbo:deathDate ?deathDateRaw }
BIND(STRDT(?birthDateRaw, <http://www.w3.org/2001/XMLSchema#date>) AS ?birthDate)
BIND(STRDT(?deathDateRaw, <http://www.w3.org/2001/XMLSchema#date>) AS ?deathDate)
FILTER(LANG(?name) = "pt")
}
ORDER BY DESC(?birthDate) (LANG(?name) = "pt")
LIMIT 30PREFIX dbr: <http://dbpedia.org/resource/>
PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
ASK { dbr:Albert_Einstein a dbo:Scientist }The driver automatically converts XSD / RDF datatypes to Metabase types. At query time (result rows) the mapping is sample-based and applies to every sync strategy; at sync time (SHACL strategy) the same mapping is applied directly from sh:datatype.
| XSD / RDF Datatype | Metabase Base Type | Notes |
|---|---|---|
xsd:integer, xsd:int, xsd:long, xsd:short, xsd:byte |
Integer | 42, -100 |
xsd:nonNegativeInteger, xsd:positiveInteger, xsd:unsigned* |
Integer | 0, 1, 255 |
xsd:decimal, xsd:float, xsd:double |
Float | 3.14, 2.718 |
xsd:boolean |
Boolean | true, false |
xsd:dateTime, xsd:dateTimeStamp, xsd:gYear, xsd:gYearMonth |
DateTime / DateTimeWithTZ | 2024-01-15T10:30:00Z |
xsd:date, xsd:gMonthDay, xsd:gDay, xsd:gMonth |
Date | 2024-01-15 |
xsd:time |
Time | 10:30:00 |
xsd:anyURI |
Text (semantic type/URL) |
Stored as text but rendered as a URL |
rdf:langString (SHACL only) |
Text (database-type: langString) |
Triggers per-column FILTER(LANG(?x) = "<lang>") when Default Language is set |
| URIs (subject column) | URL | http://dbpedia.org/resource/Berlin |
| Untagged literals | Text | "Hello" |
Language-tagged literals not declared as rdf:langString |
Text | "Hello"@en — value comes through, the language tag is stripped by SPARQL STR() when needed |
| Field | Required | Description | Example/Options |
|---|---|---|---|
| SPARQL Endpoint | ✅ | SPARQL endpoint URL. | https://dbpedia.org/sparql |
| Ignore SSL Certificate Errors | ❌ | Skip TLS/SSL validation. | false |
| Default Graph URI | ❌ | Default graph URI and implicit base prefix. RDF classes and properties whose URI starts with this value are shortened in the Metabase UI — http://dbpedia.org/ontology/Person becomes Person. The full URI is reconstructed automatically at query time. |
http://dbpedia.org/ontology/ |
| Default Language | ❌ | BCP-47 language tag. When set, queries against rdf:langString columns are filtered to this language (untagged literals are still kept), and SHACL sh:name / sh:description literals are picked using this language first. |
nl, en |
| Hide URIs outside the Default Graph | ❌ | When enabled, RDF classes and properties whose URI does not start with the Default Graph URI are skipped during sync (less clutter when external vocabularies are sampled). | false |
| Metadata Sync Strategy (Advanced) | ❌ | How the driver discovers tables/fields. | auto / none / explicit / shacl |
| Schema Configuration (Advanced) | ❌ | JSON schema. Visible when strategy is explicit. |
See JSON example below |
| SHACL URL (Advanced) | ❌ | URL serving a SHACL document in Turtle. Visible when strategy is shacl. Fetched on every sync. |
https://example.org/schema.ttl |
Metadata Sync Strategy options:
auto(default): Sample the endpoint with SPARQL queries to discover classes and properties.- Class Discovery Limit: Maximum number of RDF classes (tables) to discover (default: 100)
- Property Discovery Limit: Maximum number of properties (fields) per class (default: 20)
- Discovery Sample Size: Number of instances to sample when discovering properties (default: 10000)
none: Skip metadata sync entirely (useful for very large datasets where discovery is slow).explicit: Use a manually defined JSON schema (see example below).shacl: Fetch a SHACL document and treat it as the schema. See SHACL Schema Sync below — this is the recommended path when you control the ontology.
The Default Graph URI doubles as the implicit base prefix for the database. When it is set, classes and properties under that namespace are shortened to their local name everywhere in the UI:
| Default Graph URI | Full URI | Shown as |
|---|---|---|
http://dbpedia.org/ontology/ |
http://dbpedia.org/ontology/Person |
Person |
http://dbpedia.org/ontology/ |
http://dbpedia.org/ontology/birthPlace |
birthPlace |
http://dbpedia.org/ontology/ |
http://www.w3.org/1999/02/22-rdf-syntax-ns#type |
(unchanged — foreign URI) |
This mirrors RDF/Turtle "base IRI" semantics: only URIs under the configured base get shortened. Foreign-namespace URIs always keep their full form so you can tell them apart. Enable Hide URIs outside the Default Graph to drop them from sync entirely.
If a property is declared as sh:datatype rdf:langString in a SHACL document (so the driver knows it is language-tagged) and a Default Language is configured, every reference to that variable in the compiled SPARQL gets:
FILTER(!BOUND(?var) || LANG(?var) = "nl" || LANG(?var) = "")This keeps each row to the configured language (plus any untagged literals) and avoids the row fan-out that multilingual datasets otherwise produce. The !BOUND(...) guard preserves left-join semantics. Leaving Default Language blank disables this entirely — the driver behaves exactly as before.
Native SPARQL questions can use Metabase's {{tag}} template parameters. The driver renders each parameter value as a syntactically valid SPARQL term:
| Parameter value | Rendered as |
|---|---|
Text — Alice |
"Alice" (quoted, escaped) |
URL-shaped — https://data.example/Item |
<https://data.example/Item> (IRI) |
Number — 25 |
25 (bare literal) |
Boolean — true |
true (bare literal) |
Multi-value — [A B C] |
A, B, C — wrap with IN(...) / VALUES |
| Missing optional | {{tag}} left in place + warning logged |
Whitespace inside {{ tag }} is tolerated. Embedded ", \, newlines, and regex meta-characters in values are escaped safely. Field Filters, Referenced Card Queries, and Referenced Query Snippets are SQL-shaped template tag types and are not rendered to SPARQL — using them logs a warning and leaves the placeholder untouched so the endpoint surfaces a clear parse error.
Example:
SELECT ?label WHERE {
?s a {{ class }} ;
rdfs:label ?label .
FILTER (LANG(?label) = {{ lang }})
}
LIMIT {{ n }}With parameters class = https://data.example/Item, lang = nl, n = 100, the driver emits:
SELECT ?label WHERE {
?s a <https://data.example/Item> ;
rdfs:label ?label .
FILTER (LANG(?label) = "nl")
}
LIMIT 100If you choose Explicit as the Metadata Sync Strategy, you can use the following JSON to define tables for DBpedia:
{
"tables": [
{
"name": "http://dbpedia.org/ontology/Person",
"description": "Person",
"fields": [
"http://www.w3.org/2000/01/rdf-schema#label",
"http://dbpedia.org/ontology/birthDate",
"http://dbpedia.org/ontology/birthPlace",
"http://xmlns.com/foaf/0.1/name"
]
},
{
"name": "http://dbpedia.org/ontology/City",
"description": "City",
"fields": [
"http://www.w3.org/2000/01/rdf-schema#label",
"http://dbpedia.org/ontology/populationTotal",
"http://dbpedia.org/ontology/country",
"http://dbpedia.org/ontology/abstract"
]
}
]
}The schema is a JSON object with a tables key, which is an array of table objects. Each table object has:
name(required): URI of the RDF classdescription(optional): Human-readable description of the tablefields(required): Array of property URIs to include as columns
JSON Schema for validation:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"tables": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "URI of the RDF class"
},
"description": {
"type": "string",
"description": "Human-readable description of the table"
},
"fields": {
"type": "array",
"items": {
"type": "string",
"description": "URI of the property"
},
"minItems": 1
}
},
"required": ["name", "fields"]
},
"minItems": 1
}
},
"required": ["tables"]
}When you control the ontology behind your SPARQL endpoint, SHACL is the cleanest way to tell Metabase about the schema. The driver fetches a SHACL document over HTTP at sync time, parses the shapes, and uses them as the single source of truth for tables, columns, types, and foreign-key relationships — no sampling queries, no JSON to maintain.
To enable it:
- Set Metadata Sync Strategy to
shacl(under Advanced). - Fill in SHACL URL with the HTTPS URL of a Turtle document the Metabase container can reach.
- (Optional) Set Default Language so multilingual
sh:name/sh:descriptionlabels resolve to the right language.
The driver re-fetches the SHACL on every sync (results are cached for ~30 seconds within a single sync run to keep things fast).
| SHACL construct | Metabase result |
|---|---|
sh:NodeShape with sh:targetClass C |
One table. :name is the short form of C (under the Default Graph) or the full URI otherwise. |
sh:property [ sh:path P … ] |
One column on the parent table. :name is the short form of P (or the full URI for foreign predicates). |
sh:datatype xsd:string (or normalizedString, token, language, anyURI) |
:base-type :type/Text. xsd:anyURI also gets :semantic-type :type/URL. |
sh:datatype xsd:integer (and the unsigned/long/short/byte/nonNegative/positive variants) |
:type/Integer. |
sh:datatype xsd:decimal / xsd:float / xsd:double |
:type/Float. |
sh:datatype xsd:boolean |
:type/Boolean. |
sh:datatype xsd:date |
:type/Date. |
sh:datatype xsd:dateTime / xsd:dateTimeStamp |
:type/DateTimeWithTZ. |
sh:datatype xsd:time |
:type/Time. |
sh:datatype rdf:langString |
:base-type :type/Text and :database-type "langString" — triggers the LANG filter described above when a Default Language is set. |
sh:class C2 (without sh:datatype) |
:base-type :type/Text, :semantic-type :type/FK. A describe-fks row is emitted pointing at C2.subject so Metabase wires the foreign-key relationship automatically. |
sh:name "…" / sh:description "…" |
Combined into the field's description. When multiple language-tagged literals exist, the Default Language wins, then untagged, then any. |
sh:minCount n (n ≥ 1) |
:database-required true — marks the column as required. |
sh:order n |
Drives the column order in Metabase (ascending). Columns without sh:order sort to the end. |
sh:node OtherShape |
Inheritance. Properties from OtherShape are merged into this shape (transitively). If both shapes define the same sh:path, the child wins. References to non-shape IRIs (e.g. when sh:node points at a class) are ignored. |
A property of every table is added automatically:
subject(pk?,database-type: uri) — the synthetic primary key holding the RDF subject URI of each row. It maps to the SPARQL?subjectvariable in every compiled query.
Add the namespace https://data.metabase.com/ to your SHACL prefixes:
@prefix metabase: <https://data.metabase.com/> .The driver understands three predicates from this namespace:
| Predicate | Where | Effect |
|---|---|---|
metabase:hide true |
NodeShape or PropertyShape | Skip the shape (table) or the property (column) entirely during sync. |
metabase:semanticType "type/URL" |
PropertyShape | Override Metabase's semantic type. Accepts any valid type/… keyword (type/URL, type/Category, type/Email, type/Description, …). |
metabase:displayValueProperty <P> |
PropertyShape (on a FK property, i.e. one that also has sh:class) |
Marks the property on the target shape that should display as the FK's human-readable label. After every metadata sync the driver writes (and idempotently updates) the matching external-remap Dimension row, so Metabase automatically renders the human label without any click-through in the column-settings panel. |
metabase:displayValueProperty answers one question: when a row points at a related entity via a foreign key, which property of the target should Metabase show instead of the raw URI?
It belongs on the FK property (the one with sh:class), and its value is the URI of a property defined on the target shape. It is not a magic keyword — it's just a regular property URI that exists on the target class.
Example. A Person has a birthPlace foreign key pointing at Place. We want Metabase to display the place's rdfs:label (e.g. "Brussels") instead of the raw IRI (http://dbpedia.org/resource/Brussels):
dbo:PlaceShape a sh:NodeShape ;
sh:targetClass dbo:Place ;
sh:property [ sh:path rdfs:label ;
sh:datatype rdf:langString ;
sh:name "label"@nl, "label"@en ;
sh:order 1 ] ;
sh:property [ sh:path dbo:populationTotal ;
sh:datatype xsd:integer ;
sh:order 2 ] .
dbo:PersonShape a sh:NodeShape ;
sh:targetClass dbo:Person ;
sh:property [ sh:path dbo:birthPlace ;
sh:class dbo:Place ;
metabase:displayValueProperty rdfs:label ] . # ← points at Place.labelRead the FK property out loud: "birthPlace is a foreign key to Place. When you display a value, show rdfs:label." The driver writes a Dimension row at sync time so Metabase renders ?birthPlace as the joined ?Place__via__birthPlace__label value, no manual click in the column-settings panel.
Any property URI defined on the FK target shape (or one inherited via sh:node). rdfs:label is just the most common choice; pick whatever your ontology actually uses for the human-readable form:
| Property | When it fits |
|---|---|
rdfs:label |
Generic human-readable label (DBpedia-style ontologies). |
skos:prefLabel |
SKOS-based vocabularies (thesauri, controlled lists). |
dc:title / dcterms:title |
Bibliographic / document-oriented data. |
foaf:name |
People / agents. |
schema:name |
schema.org data. |
| any custom IRI | Whatever property holds the human-readable form in your data — e.g. ex:value for a code-table value, ex:displayName, ex:title. |
Two practical requirements:
- The property you name must be declared on the target NodeShape (directly, or via
sh:nodeinheritance). If the driver can't find a synced field by that name on the target table, noDimensionrow is written — the next sync resolves it once the field appears. - The target property must actually carry triples on the linked instances. If it's declared but empty in the data, the display column will simply come back blank.
Note on langString targets. When the display property is rdf:langString and a Default Language is configured, the per-row FILTER(LANG(?x) = "nl" || LANG(?x) = "") still applies, so multilingual labels resolve to the right language automatically.
A property with sh:class C2 automatically becomes a foreign key. The driver:
- Emits the column with
:semantic-type :type/FK. - Returns a
describe-fksrow of the form{fk-table: A, fk-column: P, pk-table: C2, pk-column: subject}.
Metabase resolves the relationship the same way it would for any SQL FK, so Query Builder picks it up for joins, breakouts, and value lookups. Multi-valued FKs (the same subject linking to several target URIs) are handled naturally — SPARQL OPTIONAL fans them out into one row per target.
sh:node is used as the inheritance edge. Given:
dbo:AgentShape a sh:NodeShape ; sh:targetClass dbo:Agent .
dbo:PersonShape a sh:NodeShape ; sh:targetClass dbo:Person ; sh:node dbo:AgentShape .
dbo:ScientistShape a sh:NodeShape ; sh:targetClass dbo:Scientist ; sh:node dbo:PersonShape .ScientistShape ends up with all properties from PersonShape and AgentShape, plus its own. If the child redefines a sh:path that a parent already defined, the child's declaration wins (different sh:description, different sh:minCount, etc.). Cycles are broken with a visited set. sh:node references that don't resolve to another NodeShape (e.g. when they accidentally point at the class IRI itself) are silently ignored.
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix metabase: <https://data.metabase.com/> .
@prefix dbo: <http://dbpedia.org/ontology/> .
dbo:AgentShape a sh:NodeShape ;
sh:targetClass dbo:Agent ;
sh:property [ sh:path dbo:wikiPageID ;
sh:datatype xsd:string ;
sh:minCount 1 ;
sh:maxCount 1 ;
sh:order 1 ;
sh:name "wikiPageID"@nl, "wikiPageID"@en ;
sh:description "Wikipedia-pagina-ID"@nl, "Wikipedia page ID"@en ] .
dbo:PlaceShape a sh:NodeShape ;
sh:targetClass dbo:Place ;
sh:property [ sh:path rdfs:label ;
sh:datatype rdf:langString ;
sh:name "label"@nl, "label"@en ;
sh:order 1 ] ;
sh:property [ sh:path dbo:populationTotal ;
sh:datatype xsd:integer ;
sh:order 2 ] .
dbo:PersonShape a sh:NodeShape ;
sh:targetClass dbo:Person ;
sh:node dbo:AgentShape ;
sh:property [ sh:path dbo:birthName ;
sh:datatype rdf:langString ;
sh:name "Naam"@nl, "Name"@en ;
sh:order 2 ] ;
sh:property [ sh:path dbo:birthPlace ;
sh:class dbo:Place ;
metabase:displayValueProperty rdfs:label ; # ← Place.label, declared in PlaceShape above
sh:order 3 ] ;
sh:property [ sh:path dbo:wikiPageRevisionID ;
sh:datatype xsd:anyURI ;
metabase:hide true ] .With Default Graph URI = http://dbpedia.org/ontology/ and Default Language = nl, this produces:
- A table Place with columns
subject,label,populationTotal. - A table Person with columns
subject,wikiPageID(inherited from Agent),birthName,birthPlace. birthNameislangString, so queries getFILTER(... LANG(?birthName) = "nl" || LANG(?birthName) = "").birthPlaceis marked Foreign Key pointing toPlace.subject. After sync the driver writes aDimensionrow pairingPerson.birthPlace→Place.label, so the FK column renders as the place's Dutch label automatically — no manual "Display values" click in the column-settings panel.wikiPageRevisionIDis absent (metabase:hide).- The synced Dutch description ("Wikipedia-pagina-ID") is preferred over the English one.
- Aggregations: Basic aggregations in Query Builder's "Summarize" are supported — Count, Count distinct, Sum, Average, Minimum, Maximum — with an optional group-by (breakout).
Countcompiles toCOUNT(DISTINCT ?subject), so grouping by a multi-valued property counts entities per group rather than fanned-out solution rows. Advanced aggregations (standard deviation, percentiles, cumulative sum/count, expression aggregations) and post-aggregation filtering (HAVING) are not supported. - Joins: Implicit foreign-key joins (the "Display values" remap on a FK column) are emitted as nested
OPTIONALchains. Multi-hop chains (e.g.Item → Provider → Owner → owner_name) are supported — each hop is anchored on the previous join's intermediate var, not on?subject. Explicit inner/right/full joins from Query Builder are not supported — only:left-joinis enabled. - Saved cards / models as a source: When a saved card or model is used as the source of another question, the inner query is compiled as a SPARQL sub-
SELECTand the outer stage's FK display-value remaps wrap it. Outer stages that add their own aggregation, filter, or breakout on top of the inner query are not supported — only passthrough + remap wrappers. - Auto-mode language tagging: The
FILTER(LANG(?x) = …)clause only fires for columns whoserdf:langStringdatatype was declared in SHACL. Theautosync strategy can't see datatypes by sampling alone, so multilingual fan-out can still happen there. - Performance: Fetching large result sets or performing metadata discovery on extensive endpoints can be resource-intensive. Use Configurable Limits (Class/Property/Sample Size) in
auto, or move toshacl/explicit/nonefor control. - Filter Support: Basic filtering works in Query Builder. Complex Metabase filter expressions or custom expressions might not be fully translated to SPARQL.
- Authentication: No authentication is supported. The driver does not provide fields for username/password.
- Git
- Clojure CLI tools
- Java Development Kit (JDK) 21
Run make check-deps to check if dependencies are installed.
-
Clone this repository:
git clone https://github.com/jhisse/metabase-sparql-driver.git cd metabase-sparql-driver -
Initialize the Metabase submodule:
make init-metabase
-
Build the driver:
make build
-
The compiled driver will be available at:
target/sparql.metabase-driver.jar
make docker-build-drivermake docker-buildmake build: Build the SPARQL drivermake build-full: Complete build with checks and initializationmake clean: Remove build filesmake init-metabase: Initialize the Metabase submodulemake check-deps: Check if dependencies are installedmake lint: Lint code using clj-kondomake format: Format code using cljfmtmake splint: Run splint static code analysismake test: Run testsmake coverage: Run tests with coverage analysismake docker-build: Build docker imagemake docker-run: Run docker imagemake docker-stop: Stop docker imagemake docker-clean: Clean old containermake docker-build-driver: Build driver with dockermake help: Display this help
To troubleshoot connection or query issues, run Metabase with debug logging:
java --add-opens java.base/java.nio=ALL-UNNAMED -Dlog4j.configurationFile=file:./log4j2.xml -jar metabase.jarRequirements:
- Java 21 installed
- Place
metabase.jarin the project root folder - The
log4j2.xmlfile is already configured for SPARQL driver debugging
What you'll see:
- SPARQL query execution details
- Connection attempts and errors
- Data type conversion processes
- Metadata sync operations
Found a bug or want to contribute? Open an issue or submit a PR!
Before opening a PR, please:
- Run
make init-metabaseif you have not initialized the submodule yet. - Run
make lint,make splint, andmake testand make sure all pass. - Run
make formatto apply the project's code style. - Avoid bumping the
metabase/submodule unless that is the explicit point of the PR — call it out in the description if so.
Use the pull request template checklist when opening your PR.
This driver is licensed under the AGPLv3 license. See the LICENSE file for details.





