Worked Example: HDF5
HDF5Preview is a complete, real FileType plugin shipped under examples/plugins/HDF5Preview/. It adds metadata-only previews for .h5 / .hdf5 / .hdf files. Its purpose is to validate the dispatch ABI from outside core — it is a separate package, depends only on ConceptExplorerCore and HDF5, and needs no frontend change to light up HDF5 previews. Because its output is bounded however large the file is, the daemon's preview gates also name .h5 / .hdf5 / .hdf so its input-size cap does not skip it.
Read it alongside the generic build in Writing a FileType Plugin; this page walks the actual code top to bottom.
The package
examples/plugins/HDF5Preview/Project.toml:
name = "HDF5Preview"
uuid = "c0d7e0f3-5a1c-4b8e-9f23-2e5a6c4b1f88"
authors = ["kalidke"]
version = "0.3.0"
[deps]
ConceptExplorerCore = "5a28ea34-b669-4214-86b0-825ecd8fbc7c"
HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f"
[compat]
HDF5 = "0.17"
julia = "1.12"
[sources]
ConceptExplorerCore = { path = "../../../core" }The [sources] entry is what lets this env instantiate standalone: ConceptExplorerCore is unregistered, so without a path source Pkg fails in check_registered. A plugin living in its own repository would point path (or url) at wherever it keeps core.
Two dependencies, no more: ConceptExplorerCore for the ABI, HDF5 for reading the files. HDF5 pulls in HDF5_jll, a self-contained, cross-platform binary — deliberately chosen over shelling out to h5ls / h5dump, which are not present on every backend host and would be a fresh-box-breaks failure mode.
The module and the subtype
examples/plugins/HDF5Preview/src/HDF5Preview.jl opens with the two usings and a singleton FileType subtype:
module HDF5Preview
using ConceptExplorerCore
using HDF5
export HDF5File
struct HDF5File <: ConceptExplorerCore.FileType endHDF5File carries no fields — it is a dispatch tag. Once the plugin loads, file_types (the subtypes(FileType) scan) sees it with no registration call: the dispatch wiring is the ABI.
matches — claiming the path
const HDF5_EXTENSIONS = (".h5", ".hdf5", ".hdf")
ConceptExplorerCore.matches(::Type{HDF5File}, path::AbstractString) =
any(endswith(lowercase(path), ext) for ext in HDF5_EXTENSIONS)The same lowercased-extension idiom every built-in uses. file_type_for returns HDF5File for the first matching path it walks.
preview — a metadata-only tree
This is the substance of the plugin. The design is metadata-only: it walks groups, datasets, and attributes and reports name, shape, eltype, chunking, and small attribute values — but never reads dataset contents, so the preview is instant regardless of file size.
The representation it produces is text/markdown: a header line plus a fenced text block containing an ASCII tree.
function ConceptExplorerCore.preview(::Type{HDF5File}, path::AbstractString)
sz = try
filesize(path)
catch
-1
end
file = nothing
try
file = HDF5.h5open(path, "r")
catch e
msg = "HDF5 open failed for $(basename(path)):\n\n$(sprint(showerror, e))"
return ConceptExplorerCore.PreviewPayload("text/plain", Vector{UInt8}(msg))
end
try
w = Walk()
emit!(w, basename(path) * "/")
walk_group!(w, file, "")
szstr = sz < 0 ? "?" : Base.format_bytes(sz)
header = "# HDF5 · $(basename(path))\n\n" *
"`$(path)` · $(szstr) · $(w.groups) groups · $(w.datasets) datasets\n\n"
footer = w.omitted > 0 ?
"\n… +$(w.omitted) more objects (capped at $(MAX_LINES) lines)\n" : ""
body = header * "```text\n" * join(w.lines, "\n") * "\n" * footer * "```\n"
return ConceptExplorerCore.PreviewPayload("text/markdown", Vector{UInt8}(body))
catch e
msg = "HDF5 read failed for $(basename(path)):\n\n$(sprint(showerror, e))"
return ConceptExplorerCore.PreviewPayload("text/plain", Vector{UInt8}(msg))
finally
file !== nothing && close(file)
end
endWhat the rendered payload contains:
A markdown header:
# HDF5 · <basename>then a line with the full path, human-readable size (Base.format_bytes), and the group/dataset counts.A fenced
texttree of the file's structure, joined from the lines the walk accumulated.A footer line only if output was capped (see below).
The MIME is text/markdown, so the frontend's existing markdown renderer draws it — no HDF5-specific renderer, no Rust. The plugin reuses a renderer that already exists by formatting its content as markdown, which is the recommended fallback in Writing a FileType Plugin.
Inside the walk
The tree is built by a depth-first walk that the plugin keeps bounded and safe:
A
Walkaccumulator holds the outputlines, runninggroups/datasetscounts, and anomittedcount.dataset_labelformats one dataset line asname {shape} eltype chunk— e.g.name {100×200} Float64 chunk {32×32}— all from metadata, no data read.attr_lines!renders attributes marked@, showing values only for small scalars/strings (truncated atMAX_ATTR_VALUE_CHARS = 60), never reading large attribute arrays.walk_group!recurses with├─/└─connectors, closing each child it opens.
Output guardrails and visible failure
Two project rules show up directly in the code:
Bounded output. Output is capped at
MAX_LINES = 500. Past the cap, lines are counted asomittedand surfaced in an explicit… +N more objectsfooter — it reports what was dropped rather than silently truncating.No quiet fallback. An open failure or a read failure returns a
text/plainpayload describing the error (viasprint(showerror, e)) — the gap is rendered on screen, never a fake-success blank or a panic.
parse_entities — empty for phase 1
ConceptExplorerCore.parse_entities(::Type{HDF5File}, path::AbstractString) =
ConceptExplorerCore.ConceptEntity[]No concept entities yet, and nothing calls parse_entities or ast_hash today (both NOT BUILT). The docstring notes the future option: one ConceptEntity per group/dataset, for annotation in the concept nav.
Enabling it
The [sot].extensions key is not implemented
The planned way to enable a plugin is an [sot] table in the project's Project.toml (see Discovery & Configuration). Nothing reads that table yet.
Today the kernel loads HDF5Preview through its built-in extension table, LAZY_PLUGIN_FOR_EXT in julia/kernel/src/preview.jl, which maps .h5 to the package. It does not using HDF5Preview at startup; the plugin is loaded on the first .h5 preview, so HDF5_jll is pulled only when a user actually opens an HDF5 file. Your own plugin loads the same way, from a source checkout; see the warning at the top of Discovery & Configuration.
The point
HDF5Preview is a third-party-shaped package — its own Project.toml, its own [deps], living outside core/. It adds a previewable file type with one subtype and two methods, and the frontend renders the result with no Rust changes because the payload is just a MIME plus bytes. That is the extension surface working exactly as the ABI promises: core ships as plugins to itself, and a plugin from outside core travels the identical path.
See also
Writing a FileType Plugin — the generic build this example follows.
Discovery & Configuration — the
[sot].extensionsmechanism.The Dispatch ABI — why a plugin needs no frontend change.