Skip to content

DatasettePluginAuthoring

Dennis Lee edited this page Jul 28, 2026 · 1 revision

title: Datasette Plugin Authoring radar_quadrant: Techniques radar_ring: Assess radar_position: inner

Datasette Plugin Authoring

Datasette exposes a hook-based plugin system built on pluggy, the same mechanism pytest uses for its own plugins. A developer registers functions against named hooks with an @hookimpl decorator, and Datasette calls those functions at the appropriate point in a request or startup cycle.

One-off vs installable plugins

The lightest path is a one-off plugin: a single .py file dropped into a plugins/ directory, loaded by running datasette serve mydb.db --plugins-dir=plugins/. No packaging or distribution step is required, which makes this suited to local experiments or single-project customizations.

An installable plugin is a proper Python package instead. It needs a setup.py that declares install_requires=["datasette"] and registers the plugin under an entry_points={"datasette": [...]} block, mapping a plugin name to its module. Packaged this way, the plugin can be built with python3 setup.py sdist and installed like any other dependency. It can also bundle its own static assets and templates using package_data in setup.py, served at /-/static-plugins/PLUGIN_NAME/, and custom templates that Datasette will load ahead of its own defaults.

The official scaffold

Datasette's maintainers publish a cookiecutter template, invoked as cookiecutter gh:simonw/datasette-plugin, which scaffolds a complete installable plugin project including its test suite and CI configuration. This is the same structure behind most plugins in the official plugin directory, a list of more than 90 published examples, including datasette-edit-schema and datasette-write-ui.

Routing and configuration access

Custom endpoints are added via the register_routes() hook. Datasette reserves the /-/ path segment so plugin routes never collide with a database or table name, following patterns like /dbname/-/custom-route or /dbname/tablename/-/action. Plugins read their own settings through datasette.plugin_config(), which supports configuration scoped at the table, database, or root level, and generate correctly proxy-aware links through the datasette.urls object. Two of the most commonly implemented hooks are prepare_connection(), for customizing the SQLite connection object per request, and prepare_jinja2_environment(), for extending the template environment.

Testing

Datasette's own testing guide recommends pytest together with pytest-asyncio for plugin test suites. The datasette.client object wraps HTTPX to imitate HTTP requests directly over ASGI, so tests can exercise a full Datasette instance without a running server. A common fixture pattern uses sqlite-utils to build a temporary test database and metadata, often scoped with scope="session" for reuse across a test file. Tests that construct a Datasette instance directly, rather than going through datasette.client, must call await datasette.invoke_startup() to register startup-time plugin behavior. pytest-httpx can mock any outbound HTTP calls a plugin makes, pm.register() and pm.unregister() let a test register a temporary hook implementation for just one test, and passing pdb=True to the Datasette constructor drops into a debugger on unhandled exceptions.

Debugging with DATASETTE_TRACE_PLUGINS

Setting the DATASETTE_TRACE_PLUGINS=1 environment variable makes Datasette log which plugin hooks actually fire during a request. This is oriented at understanding an existing, already-installed plugin's behavior rather than at writing a new one, useful when a plugin from the directory does not behave as its documentation suggests.

References

Clone this wiki locally