Gleann supports external plugins for parsing complex file formats that Go cannot handle natively (PDFs, Office documents, audio/video, etc.).
Plugins are standalone executables that expose a local HTTP API. Gleann manages their lifecycle:
- Discovery: Plugins are registered in
~/.gleann/plugins.json - Auto-start: When Gleann encounters a file extension handled by a plugin, it starts the plugin process
- Extraction: The plugin processes the file and returns structured content (sections, metadata, graph nodes)
- Shutdown: Plugins are stopped when Gleann finishes
| Plugin | Formats | Backend | Description |
|---|---|---|---|
| gleann-plugin-docs | PDF, DOCX, XLSX, PPTX, CSV | MarkItDown + Docling | Document extraction with table detection |
| gleann-plugin-marker | PDF, DOCX, EPUB, HTML, images | marker-pdf + Surya OCR | High-accuracy extraction with deep learning OCR |
| gleann-plugin-sound | MP3, WAV, MP4, etc. | whisper.cpp | Audio/video transcription |
gleann setup
# Select "Manage Plugins" during setupgleann tui
# Navigate to Plugins tabThe TUI plugin manager downloads the correct binary for your platform and registers it automatically.
Plugins must implement this HTTP API:
Returns 200 OK when the plugin is ready.
Returns plugin metadata:
{
"name": "gleann-plugin-docs",
"version": "1.0.0",
"extensions": [".pdf", ".docx", ".xlsx", ".pptx"]
}Extracts content from a file.
Request:
{
"path": "/absolute/path/to/file.pdf"
}Response — PluginResult schema:
{
"nodes": [
{
"id": "doc-1",
"label": "Document",
"properties": {
"title": "Quarterly Report Q3 2025",
"source": "/path/to/report.pdf",
"page_count": 42,
"content": "Full extracted text content..."
}
},
{
"id": "sec-1",
"label": "Section",
"properties": {
"title": "Executive Summary",
"content": "Section text content...",
"page": 1,
"order": 0
}
}
],
"edges": [
{
"source": "doc-1",
"target": "sec-1",
"label": "HAS_SECTION"
},
{
"source": "sec-1",
"target": "sec-2",
"label": "HAS_SUBSECTION"
}
],
"chunks": [
{
"text": "Chunk of text suitable for embedding...",
"metadata": {
"source": "/path/to/report.pdf",
"section": "Executive Summary",
"page": 1
}
}
]
}Node labels:
| Label | Description |
|---|---|
Document |
Top-level file node. Must have title, source, content properties |
Section |
A heading or logical section. Must have title, content |
Table |
An extracted table. content holds rendered text |
Image |
An extracted image. content holds OCR text or caption |
Edge labels:
| Label | Description |
|---|---|
HAS_SECTION |
Document → Section |
HAS_SUBSECTION |
Section → Section (nested hierarchy) |
HAS_TABLE |
Section → Table |
HAS_IMAGE |
Section → Image |
HTTP Status Codes:
| Code | Meaning |
|---|---|
200 |
Success — returns PluginResult JSON |
400 |
Bad request (missing path field, invalid JSON) |
404 |
File not found at the given path |
415 |
Unsupported file type |
500 |
Internal extraction error |
Error response format:
{
"error": "file not found: /path/to/missing.pdf"
}A plugin can be written in any language. It must:
- Accept a
--portflag to set the HTTP listen port - Implement the three endpoints above (
/health,/info,/extract) - Return structured content following the
PluginResultschema - Exit cleanly when the parent process sends SIGTERM
from flask import Flask, request, jsonify
import sys
app = Flask(__name__)
@app.route("/health")
def health():
return "OK"
@app.route("/info")
def info():
return jsonify({
"name": "my-plugin",
"version": "0.1.0",
"extensions": [".xyz"]
})
@app.route("/extract", methods=["POST"])
def extract():
path = request.json.get("path")
if not path:
return jsonify({"error": "missing 'path' field"}), 400
# ... extract content from the file ...
return jsonify({
"nodes": [{"id": "doc-1", "label": "Document", "properties": {"title": path, "source": path, "content": "..."}}],
"edges": [],
"chunks": [{"text": "...", "metadata": {"source": path}}]
})
if __name__ == "__main__":
port = int(sys.argv[sys.argv.index("--port") + 1]) if "--port" in sys.argv else 9200
app.run(port=port)# Start the plugin manually
./my-plugin --port 9200
# In another terminal, check health
curl http://localhost:9200/health
# Test extraction
curl -X POST http://localhost:9200/extract \
-H "Content-Type: application/json" \
-d '{"path": "/path/to/test-file.xyz"}'
# Check gleann's plugin log during index build
GLEANN_LOG_LEVEL=debug gleann index build test --docs ./- gleann-plugin-docs — PDF/DOCX (Python: MarkItDown + Docling)
- gleann-plugin-marker — High-accuracy PDF/DOCX/images (Python: marker-pdf + Surya OCR)
- gleann-plugin-sound — Audio transcription (Go + whisper.cpp)
For a comprehensive comparison of all 4 extraction backends (go-native, markitdown-cli, plugin-docs, plugin-marker) across 6 file formats with 12+ metrics, see the standalone benchmark document:
| Backend | Layer | Avg Latency | Sections | Formats | Dependencies |
|---|---|---|---|---|---|
| go-native | -1 | <1ms | 5.7 | 6 | Zero (always available) |
| plugin-docs | 1a | 114ms | 5.0 | 5 | Python markitdown + docling |
| markitdown-cli | 0 | 396ms | 0.0 | 2* | Python markitdown |
| plugin-marker | 1b | 1026ms | 9.0 | 4 | Python marker-pdf + surya OCR |
* markitdown-cli requires optional deps for DOCX/XLSX/PPTX (pip install markitdown[all])
# Go test (recommended — tests all 4 backends including go-native)
go test ./tests/benchmarks/ -run TestPluginBenchmark -v -timeout 300s
# Or via the shell script (3 backends, auto-starts plugins)
./tests/e2e/plugin_benchmark.sh- Plugin Installation Guide — Step-by-step install instructions
- Troubleshooting — Plugins — Common plugin issues