How to Add a New Plugin to the abx-dl Plugin System: A Complete Developer Guide
To add a new plugin to abx-dl, create a subdirectory in abx_dl/plugins/, add hook scripts following the strict naming convention on_{Event}__{step}{priority}_{description}[.bg].{ext}, and ensure each script outputs valid JSON-Lines containing ArchiveResult records to stdout.
The abx-dl download manager from the ArchiveBox ecosystem discovers plugins dynamically at runtime from the filesystem. The discovery mechanism in abx_dl/plugins.py scans the plugins directory, parses optional configuration manifests, and instantiates hook objects that the executor in abx_dl/executor.py runs in a deterministic order during the download pipeline.
How Plugin Discovery Works
The load_plugin() function in abx_dl/plugins.py (lines 92-101) implements a five-stage discovery process that activates whenever the application starts:
-
Directory Loading – The system scans
abx_dl/plugins/and loads any folder that does not start with.or_, skipping hidden directories automatically. -
Configuration Parsing – If present,
config.jsonis parsed to expose a JSON Schema for environment-variable configuration (lines 105-112). -
Binary Dependency Resolution – If present,
binaries.jsonldescribes external binaries required by the plugin, including download URLs and installation commands (lines 114-122). -
Hook Script Discovery – The system identifies files matching the pattern
on_{Event}__{step}{priority}_{description}[.bg].{ext}and extracts metadata viaparse_hook_filename()(lines 67-90). -
Hook Instantiation – Each matching file generates a
Hookobject attached to thePlugininstance (lines 124-145).
The Hook Filename Convention
Hook scripts must follow a precise naming scheme parsed by parse_hook_filename() in abx_dl/plugins.py:
on_{Event}__{step}{priority}_{description}[.bg].{ext}
- Event: Either
Crawl(runs once per download batch) orSnapshot(runs for each individual URL). - step: A single digit (0-9) representing the execution phase.
- priority: A single digit (0-9) controlling order within the phase; lower numbers execute first.
- description: Arbitrary descriptive text.
- .bg: Optional suffix indicating a background daemon hook that runs asynchronously.
- ext: The interpreter type—
pyfor Python,jsfor Node.js, orshfor shell scripts.
For example, on_Snapshot__20_fetch_page.py runs during the Snapshot event at step 2, priority 0, while on_Crawl__01_init.bg.sh runs as a background daemon at step 0, priority 1.
Implementing a Hook Script
When the user executes abx-dl download, the download() function in the CLI (wired through cli.py) builds ordered lists of crawl_hooks and snapshot_hooks from all enabled plugins (executor.py lines 32-42). The run_hook() function (executor.py lines 28-31) then executes each hook by:
- Selecting the appropriate interpreter based on the file extension.
- Building a per-plugin environment via
build_env_for_plugin()(executor.py lines 44-51). - Passing
--urland--snapshot-idarguments to the script. - Capturing stdout and stderr to
.stdout.logand.stderr.logfiles. - Creating
.pidfiles for background hooks (executor.py lines 75-82).
Required Output Format
Hooks must emit JSON-Lines (.jsonl) to stdout. The executor parses each line searching for records with specific types:
ArchiveResult: The primary output containing the archived data.BinaryorMachine: Used for configuration propagation.
A valid success record looks like:
{"type": "ArchiveResult", "snapshot_id": "uuid-here", "plugin": "my_plugin", "hook_name": "on_Snapshot__20_fetch_page", "status": "succeeded", "output_str": "Saved content to file.html"}
The executor aggregates these results into the snapshot's index.jsonl (executor.py lines 62-66).
Step-by-Step Plugin Creation Process
Follow these steps to implement a functional plugin:
-
Create the plugin directory at
abx_dl/plugins/<your_plugin>/, ensuring the name contains no leading dots or underscores. -
Define optional configuration by adding
config.jsonwith a JSON Schema under the"properties"key to map environment variables to defaults and descriptions. -
Declare binary dependencies by adding
binaries.jsonlwith lines containing{"name": "BINARY_NAME", "url": "...", "install_cmd": "..."}. -
Write hook scripts following the naming convention, choosing Python, JavaScript, or Shell based on your requirements.
-
Implement the hook logic to read
--urland--snapshot-idarguments, perform the archival work, write artifacts to the plugin's output subdirectory, and print the required JSON-Lines to stdout. -
Test the plugin by running
abx-dl dl --plugins=<your_plugin> <url>and inspecting the generatedindex.jsonland plugin output directory.
Code Examples
Directory Structure
abx_dl/
└─ plugins/
└─ my_plugin/
├─ config.json # optional
├─ binaries.jsonl # optional
└─ on_Snapshot__20_fetch_page.py
Configuration Schema (config.json)
{
"type": "object",
"properties": {
"MY_PLUGIN_TIMEOUT": {
"type": "integer",
"default": 30,
"description": "Seconds before the hook times out"
}
}
}
Binary Dependencies (binaries.jsonl)
{"name":"CHROME","url":"https://dl.google.com/chrome/chrome.tar.gz","install_cmd":"tar -xzf chrome.tar.gz -C $LIB_DIR"}
Example Hook Script (on_Snapshot__20_fetch_page.py)
#!/usr/bin/env python3
import argparse
import json
import pathlib
import requests
import sys
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--url', required=True)
parser.add_argument('--snapshot-id', required=True)
args = parser.parse_args()
try:
resp = requests.get(args.url, timeout=30)
resp.raise_for_status()
html = resp.text
except Exception as e:
result = {
"type": "ArchiveResult",
"snapshot_id": args.snapshot_id,
"plugin": "my_plugin",
"hook_name": "on_Snapshot__20_fetch_page",
"status": "failed",
"error": str(e)
}
print(json.dumps(result))
sys.exit(1)
out_path = pathlib.Path.cwd() / "page.html"
out_path.write_text(html, encoding="utf-8")
result = {
"type": "ArchiveResult",
"snapshot_id": args.snapshot_id,
"plugin": "my_plugin",
"hook_name": "on_Snapshot__20_fetch_page",
"status": "succeeded",
"output_str": f"Saved page to {out_path}"
}
print(json.dumps(result))
if __name__ == '__main__':
main()
Make the script executable with chmod +x on_Snapshot__20_fetch_page.py. The script receives the target URL and snapshot identifier, persists the artifact to page.html, and emits the required JSON-Line for the executor to capture.
Summary
- abx-dl discovers plugins automatically from the
abx_dl/plugins/directory at runtime. - Hook filenames must follow
on_{Event}__{step}{priority}_{description}[.bg].{ext}to be recognized byparse_hook_filename(). - Scripts receive
--urland--snapshot-idarguments and must output JSON-Lines withtype: ArchiveResultto stdout. - The executor in
abx_dl/executor.pyhandles interpreter selection, environment setup, and result aggregation intoindex.jsonl. - Optional
config.jsonandbinaries.jsonlfiles provide schema validation and dependency management.
Frequently Asked Questions
What programming languages can I use to write abx-dl plugins?
You can write hook scripts in Python (.py), JavaScript/Node.js (.js), or Shell (.sh). The run_hook() function in abx_dl/executor.py automatically selects the appropriate interpreter based on the file extension. Ensure the interpreter is available in the system PATH when the hook executes.
How do I control the execution order of my plugin hooks?
Execution order is determined by the step and priority digits in the hook filename. The format on_Event__{step}{priority}_{desc}.ext uses single digits (0-9) where lower numbers run first. For example, on_Snapshot__01_early_hook.py runs before on_Snapshot__20_later_hook.py because step 0 executes before step 2.
What is the difference between Crawl and Snapshot events?
Crawl hooks run once per download invocation (per batch), making them suitable for setup or cleanup operations that should execute only once regardless of URL count. Snapshot hooks run once for every individual URL being archived. According to the executor logic in abx_dl/executor.py, the system collects all crawl hooks first, executes them, then iterates through URLs executing applicable snapshot hooks for each.
How do background hooks (.bg) work in abx-dl?
Hooks with the .bg suffix in their filename (e.g., on_Crawl__00_server.bg.py) execute as background daemons. The run_hook() function creates a .pid file to track these processes and does not block the pipeline waiting for completion. Background hooks are ideal for long-running services like browser instances or proxy servers that need to stay alive across multiple snapshots.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →