2018-04-20 07:32:02 -07:00
.. _plugins:
2018-04-16 08:12:09 -07:00
Plugins
=======
2019-05-15 22:18:02 -07:00
Datasette's plugin system allows additional features to be implemented as Python
code (or front-end JavaScript) which can be wrapped up in a separate Python
package. The underlying mechanism uses `pluggy <https://pluggy.readthedocs.io/> `_ .
2018-04-16 08:12:09 -07:00
2021-01-09 14:17:18 -08:00
See the `Datasette plugins directory <https://datasette.io/plugins> `__ for a list of existing plugins, or take a look at the
2019-11-27 11:19:11 -08:00
`datasette-plugin <https://github.com/topics/datasette-plugin> `__ topic on GitHub.
Things you can do with plugins include:
* Add visualizations to Datasette, for example
`datasette-cluster-map <https://github.com/simonw/datasette-cluster-map> `__ and
`datasette-vega <https://github.com/simonw/datasette-vega> `__ .
* Make new custom SQL functions available for use within Datasette, for example
`datasette-haversine <https://github.com/simonw/datasette-haversine> `__ and
`datasette-jellyfish <https://github.com/simonw/datasette-jellyfish> `__ .
2020-06-04 20:10:40 -07:00
* Define custom output formats with custom extensions, for example `datasette-atom <https://github.com/simonw/datasette-atom> `__ and
`datasette-ics <https://github.com/simonw/datasette-ics> `__ .
2019-11-27 11:19:11 -08:00
* Add template functions that can be called within your Jinja custom templates,
for example `datasette-render-markdown <https://github.com/simonw/datasette-render-markdown#markdown-in-templates> `__ .
* Customize how database values are rendered in the Datasette interface, for example
`datasette-render-binary <https://github.com/simonw/datasette-render-binary> `__ and
`datasette-pretty-json <https://github.com/simonw/datasette-pretty-json> `__ .
2022-12-30 14:01:39 +00:00
* Customize how Datasette's authentication and permissions systems work, for example `datasette-auth-passwords <https://github.com/simonw/datasette-auth-passwords> `__ and
2020-06-11 17:43:51 -07:00
`datasette-permissions-sql <https://github.com/simonw/datasette-permissions-sql> `__ .
2019-11-27 11:19:11 -08:00
2020-04-27 09:30:24 -07:00
.. _plugins_installing:
Installing plugins
------------------
2018-04-16 08:12:09 -07:00
2020-06-21 19:37:48 -07:00
If a plugin has been packaged for distribution using setuptools you can use the plugin by installing it alongside Datasette in the same virtual environment or Docker container.
2018-04-16 08:12:09 -07:00
2020-08-11 15:31:47 -07:00
You can install plugins using the `` datasette install `` command::
datasette install datasette-vega
You can uninstall plugins with `` datasette uninstall `` ::
datasette uninstall datasette-vega
2020-08-19 10:20:41 -07:00
You can upgrade plugins with `` datasette install --upgrade `` or `` datasette install -U `` ::
datasette install -U datasette-vega
This command can also be used to upgrade Datasette itself to the latest released version::
datasette install -U datasette
2023-03-06 14:27:30 -08:00
You can install multiple plugins at once by listing them as lines in a `` requirements.txt `` file like this::
datasette-vega
datasette-cluster-map
Then pass that file to `` datasette install -r `` ::
datasette install -r requirements.txt
The `` install `` and `` uninstall `` commands are thin wrappers around `` pip install `` and `` pip uninstall `` , which ensure that they run `` pip `` in the same virtual environment as Datasette itself.
2020-08-11 15:31:47 -07:00
One-off plugins using --plugins-dir
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2020-06-21 19:37:48 -07:00
You can also define one-off per-project plugins by saving them as `` plugin_name.py `` functions in a `` plugins/ `` folder and then passing that folder to `` datasette `` using the `` --plugins-dir `` option::
2018-04-16 08:12:09 -07:00
2020-06-21 19:37:48 -07:00
datasette mydb.db --plugins-dir=plugins/
2018-04-18 08:05:06 -07:00
2020-08-11 15:31:47 -07:00
Deploying plugins using datasette publish
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2020-06-21 19:37:48 -07:00
The `` datasette publish `` and `` datasette package `` commands both take an optional `` --install `` argument. You can use this one or more times to tell Datasette to `` pip install `` specific plugins as part of the process::
2018-04-16 08:12:09 -07:00
2020-06-21 19:37:48 -07:00
datasette publish cloudrun mydb.db --install=datasette-vega
2018-04-16 08:12:09 -07:00
2020-06-21 19:37:48 -07:00
You can use the name of a package on PyPI or any of the other valid arguments to `` pip install `` such as a URL to a `` .zip `` file::
2018-04-16 08:12:09 -07:00
2020-06-21 19:37:48 -07:00
datasette publish cloudrun mydb.db \
--install=https://url-to-my-package.zip
2018-04-16 08:12:09 -07:00
2023-08-30 15:12:24 -07:00
.. _plugins_datasette_load_plugins:
Controlling which plugins are loaded
------------------------------------
Datasette defaults to loading every plugin that is installed in the same virtual environment as Datasette itself.
You can set the `` DATASETTE_LOAD_PLUGINS `` environment variable to a comma-separated list of plugin names to load a controlled subset of plugins instead.
For example, to load just the `` datasette-vega `` and `` datasette-cluster-map `` plugins, set `` DATASETTE_LOAD_PLUGINS `` to `` datasette-vega,datasette-cluster-map `` :
.. code-block :: bash
export DATASETTE_LOAD_PLUGINS='datasette-vega,datasette-cluster-map'
datasette mydb.db
Or:
.. code-block :: bash
DATASETTE_LOAD_PLUGINS='datasette-vega,datasette-cluster-map' \
datasette mydb.db
To disable the loading of all additional plugins, set `` DATASETTE_LOAD_PLUGINS `` to an empty string:
.. code-block :: bash
export DATASETTE_LOAD_PLUGINS=''
datasette mydb.db
A quick way to test this setting is to use it with the `` datasette plugins `` command:
.. code-block :: bash
DATASETTE_LOAD_PLUGINS='datasette-vega' datasette plugins
This should output the following:
.. code-block :: json
[
{
"name": "datasette-vega",
"static": true,
"templates": false,
"version": "0.6.2",
"hooks": [
"extra_css_urls",
"extra_js_urls"
]
}
]
2019-01-31 19:47:05 -08:00
.. _plugins_installed:
2019-01-26 12:01:16 -08:00
Seeing what plugins are installed
---------------------------------
You can see a list of installed plugins by navigating to the `` /-/plugins `` page of your Datasette instance - for example: https://fivethirtyeight.datasettes.com/-/plugins
You can also use the `` datasette plugins `` command::
2023-08-09 15:04:16 -07:00
datasette plugins
Which outputs:
.. code-block :: json
2019-01-26 12:01:16 -08:00
[
{
"name": "datasette_json_html",
"static": false,
"templates": false,
"version": "0.4.0"
}
]
2022-01-19 21:04:09 -08:00
.. [[[cog
from datasette import cli
from click.testing import CliRunner
import textwrap, json
cog.out("\n")
result = CliRunner().invoke(cli.cli, ["plugins", "--all"])
# cog.out() with text containing newlines was unindenting for some reason
2023-08-09 15:04:16 -07:00
cog.outl("If you run `` datasette plugins --all `` it will include default plugins that ship as part of Datasette:\n")
cog.outl(".. code-block:: json\n")
2022-01-19 21:04:09 -08:00
plugins = [p for p in json.loads(result.output) if p["name"].startswith("datasette.")]
indented = textwrap.indent(json.dumps(plugins, indent=4), " ")
for line in indented.split("\n"):
cog.outl(line)
cog.out("\n\n")
.. ]]]
2023-08-09 15:04:16 -07:00
If you run `` datasette plugins --all `` it will include default plugins that ship as part of Datasette:
.. code-block :: json
2019-01-26 12:01:16 -08:00
[
2022-01-19 21:04:09 -08:00
{
2022-01-19 21:14:04 -08:00
"name": "datasette.actor_auth_cookie",
2022-01-19 21:04:09 -08:00
"static": false,
"templates": false,
"version": null,
"hooks": [
2022-01-19 21:14:04 -08:00
"actor_from_request"
2022-01-19 21:04:09 -08:00
]
},
2019-01-26 12:01:16 -08:00
{
2022-01-19 21:14:04 -08:00
"name": "datasette.blob_renderer",
2019-01-26 12:01:16 -08:00
"static": false,
"templates": false,
2022-01-19 21:04:09 -08:00
"version": null,
"hooks": [
2022-01-19 21:14:04 -08:00
"register_output_renderer"
2022-01-19 21:04:09 -08:00
]
2019-01-26 12:01:16 -08:00
},
2025-10-23 15:50:26 -07:00
{
"name": "datasette.default_actions",
"static": false,
"templates": false,
"version": null,
"hooks": [
"register_actions"
]
},
2019-01-26 12:01:16 -08:00
{
2022-01-19 21:14:04 -08:00
"name": "datasette.default_magic_parameters",
2022-01-19 21:04:09 -08:00
"static": false,
"templates": false,
"version": null,
"hooks": [
2022-01-19 21:14:04 -08:00
"register_magic_parameters"
2022-01-19 21:04:09 -08:00
]
},
{
2022-01-19 21:14:04 -08:00
"name": "datasette.default_menu_links",
2020-04-04 16:04:33 -07:00
"static": false,
"templates": false,
2022-01-19 21:04:09 -08:00
"version": null,
"hooks": [
2022-01-19 21:14:04 -08:00
"menu_links"
2022-01-19 21:04:09 -08:00
]
2020-04-04 16:04:33 -07:00
},
{
2022-01-19 21:14:04 -08:00
"name": "datasette.default_permissions",
2019-01-26 12:01:16 -08:00
"static": false,
"templates": false,
2022-01-19 21:04:09 -08:00
"version": null,
"hooks": [
2025-10-25 14:56:00 -07:00
"canned_queries",
2025-10-08 14:27:51 -07:00
"permission_resources_sql",
2022-10-30 14:53:33 -07:00
"skip_csrf"
2022-01-19 21:04:09 -08:00
]
2019-01-26 12:01:16 -08:00
},
register_token_handler() plugin hook for custom API token backends (#2650)
Closes #2649
* Add register_token_handler plugin hook for pluggable token backends
Adds a new register_token_handler hook that allows plugins to provide
custom token creation and verification backends. This enables plugins
like datasette-oauth to issue tokens without depending on specific
backend plugins like datasette-auth-tokens.
Key changes:
- New datasette/tokens.py with TokenHandler base class and SignedTokenHandler
(the default signed-token implementation moved here)
- New register_token_handler hookspec in hookspecs.py
- Datasette.create_token() is now async and delegates to token handlers
- New Datasette.verify_token() method tries all handlers in sequence
- handler= parameter on create_token() to select a specific backend
- TokenHandler exported from datasette package for plugin use
- Fixed actor_from_request loop to await all coroutines (avoids warnings)
* Add documentation and hook test for register_token_handler
Fixes CI failures: the new hook needs a section in docs/plugin_hooks.rst
(checked by test_plugin_hooks_are_documented) and a test_hook_* function
in test_plugins.py (checked by test_plugin_hooks_have_tests).
* Register tokens module as separate default plugin
Instead of re-exporting hookimpls from default_permissions/__init__.py,
register datasette.default_permissions.tokens as its own DEFAULT_PLUGINS
entry. Cleaner and avoids confusing import-for-side-effect patterns.
* Replace restrict_x params with TokenRestrictions dataclass
Consolidates the three separate restrict_all, restrict_database, and
restrict_resource parameters into a single TokenRestrictions dataclass.
Cleaner API surface for both Datasette.create_token() and
TokenHandler.create_token().
Also clarifies docs re: default handler selection via pluggy ordering.
* Add builder methods to TokenRestrictions
Adds allow_all(), allow_database(), and allow_resource() methods that
return self for chaining. Callers no longer need to manipulate nested
dicts directly:
restrictions = (TokenRestrictions()
.allow_all("view-instance")
.allow_database("mydb", "create-table")
.allow_resource("mydb", "mytable", "insert-row"))
* docs: add 1.0a25 upgrade guide section for create_token() signature change
Ref: https://github.com/simonw/datasette/issues/2649#issuecomment-3962639393
* docs: note that create_token() is now async in upgrade guide
* docs: update internals, plugin_hooks, authentication for new token API
- internals.rst: new async create_token() signature with restrictions
and handler params, add TokenRestrictions reference docs
- plugin_hooks.rst: show full create_token signature in TokenHandler
example, note list returns and error cases
- authentication.rst: cross-reference TokenRestrictions from the
restrictions section
* style: apply black formatting to token handler files
* docs: fix RST heading underline length in internals.rst
* tests: add restrictions round-trip and expiration tests for token handler
Covers allow_database/allow_resource builders, _r payload encoding,
and token_expires in verified actors. Coverage 76% -> 90%.
* tests: add test for signed tokens disabled
* fix: add TokenRestrictions TYPE_CHECKING import to fix ruff F821
* docs: regenerate plugins.rst with cog
* docs: reformat code blocks in plugin_hooks.rst with blacken-docs
* docs: add await .verify_token() to internals.rst
* tests: rewrite register_token_handler test to use real plugin handler
Adds a HardcodedTokenHandler to the test plugins dir that creates
tokens like dstok_hardcoded_token_1. The test now exercises creating
tokens via the default handler (which is the plugin's hardcoded one),
by explicitly naming the hardcoded handler, and by explicitly naming
the signed handler -- then verifies each token round-trips correctly.
* tests: clarify test_token_handler_via_http tests the default signed handler
* fix: use handler="signed" explicitly where signed tokens are expected
The HardcodedTokenHandler in my_plugin.py gets globally registered,
so create_token() without a handler name picks it up as the default.
Fix the create-token view, CLI, and tests to explicitly request the
signed handler where they depend on signed token behavior.
* fix: use handler="signed" in test_create_table_permissions
https://claude.ai/code/session_013cQFiDQjYRrRBH2biFfKuS
2026-02-25 16:32:45 -08:00
{
"name": "datasette.default_permissions.tokens",
"static": false,
"templates": false,
"version": null,
"hooks": [
"actor_from_request",
"register_token_handler"
]
},
2024-01-31 15:21:40 -08:00
{
"name": "datasette.events",
"static": false,
"templates": false,
"version": null,
"hooks": [
"register_events"
]
},
2019-01-26 12:01:16 -08:00
{
2022-01-19 21:14:04 -08:00
"name": "datasette.facets",
2022-01-19 21:04:09 -08:00
"static": false,
"templates": false,
"version": null,
"hooks": [
2022-01-19 21:14:04 -08:00
"register_facet_classes"
2022-01-19 21:04:09 -08:00
]
},
{
2022-01-19 21:14:04 -08:00
"name": "datasette.filters",
2019-01-26 12:01:16 -08:00
"static": false,
"templates": false,
2022-01-19 21:04:09 -08:00
"version": null,
"hooks": [
2022-01-19 21:14:04 -08:00
"filters_from_request"
2022-01-19 21:04:09 -08:00
]
},
2022-07-17 17:57:41 -07:00
{
"name": "datasette.forbidden",
"static": false,
"templates": false,
"version": null,
"hooks": [
"forbidden"
]
},
{
"name": "datasette.handle_exception",
"static": false,
"templates": false,
"version": null,
"hooks": [
"handle_exception"
]
},
2022-01-19 21:04:09 -08:00
{
2022-01-19 21:14:04 -08:00
"name": "datasette.publish.cloudrun",
2022-01-19 21:04:09 -08:00
"static": false,
"templates": false,
"version": null,
"hooks": [
2022-01-19 21:14:04 -08:00
"publish_subcommand"
2022-01-19 21:04:09 -08:00
]
},
{
2022-01-19 21:14:04 -08:00
"name": "datasette.publish.heroku",
2022-01-19 21:04:09 -08:00
"static": false,
"templates": false,
"version": null,
"hooks": [
2022-01-19 21:14:04 -08:00
"publish_subcommand"
2022-01-19 21:04:09 -08:00
]
},
{
2022-01-19 21:14:04 -08:00
"name": "datasette.sql_functions",
2022-01-19 21:04:09 -08:00
"static": false,
"templates": false,
"version": null,
"hooks": [
2022-01-19 21:14:04 -08:00
"prepare_connection"
2022-01-19 21:04:09 -08:00
]
2019-01-26 12:01:16 -08:00
}
]
2022-01-19 21:04:09 -08:00
.. [[[end]]]
2019-01-26 12:01:16 -08:00
You can add the `` --plugins-dir= `` option to include any plugins found in that directory.
2023-08-09 15:04:16 -07:00
Add `` --requirements `` to output a list of installed plugins that can then be installed in another Datasette instance using `` datasette install -r requirements.txt `` ::
datasette plugins --requirements
The output will look something like this::
datasette-codespaces==0.1.1
datasette-graphql==2.2
datasette-json-html==1.0.1
datasette-pretty-json==0.2.2
datasette-x-forwarded-host==0.1
To write that to a `` requirements.txt `` file, run this::
datasette plugins --requirements > requirements.txt
2018-09-19 19:48:12 +02:00
.. _plugins_configuration:
2018-08-28 01:35:21 -07:00
Plugin configuration
--------------------
2024-01-31 20:03:19 -08:00
Plugins can have their own configuration, embedded in a :ref: `configuration file <configuration>` . Configuration options for plugins live within a `` "plugins" `` key in that file, which can be included at the root, database or table level.
2018-08-28 01:35:21 -07:00
2020-11-15 08:43:13 -08:00
Here is an example of some plugin configuration for a specific table:
2023-07-08 11:00:08 -07:00
.. [[[cog
2023-10-12 09:16:37 -07:00
from metadata_doc import config_example
config_example(cog, {
2020-11-15 08:45:26 -08:00
"databases": {
2018-08-28 01:35:21 -07:00
"sf-trees": {
"tables": {
"Street_Tree_List": {
"plugins": {
"datasette-cluster-map": {
"latitude_column": "lat",
"longitude_column": "lng"
}
}
}
}
}
}
2023-07-08 11:00:08 -07:00
})
.. ]]]
2023-10-12 09:16:37 -07:00
.. tab :: datasette.yaml
2023-07-08 11:00:08 -07:00
.. code-block :: yaml
databases:
sf-trees:
tables:
Street_Tree_List:
plugins:
datasette-cluster-map:
latitude_column: lat
longitude_column: lng
2023-10-12 09:16:37 -07:00
.. tab :: datasette.json
2023-07-08 11:00:08 -07:00
.. code-block :: json
{
"databases": {
"sf-trees": {
"tables": {
"Street_Tree_List": {
"plugins": {
"datasette-cluster-map": {
"latitude_column": "lat",
"longitude_column": "lng"
}
}
}
}
}
}
}
.. [[[end]]]
2018-08-28 01:35:21 -07:00
This tells the `` datasette-cluster-map `` column which latitude and longitude columns should be used for a table called `` Street_Tree_List `` inside a database file called `` sf-trees.db `` .
2019-07-07 19:06:31 -07:00
.. _plugins_configuration_secret:
2019-07-03 22:36:44 -07:00
Secret configuration values
~~~~~~~~~~~~~~~~~~~~~~~~~~~
2023-10-12 09:16:37 -07:00
Some plugins may need configuration that should stay secret - API keys for example. There are two ways in which you can store secret configuration values.
2019-07-03 22:36:44 -07:00
2020-11-15 08:43:13 -08:00
**As environment variables** . If your secret lives in an environment variable that is available to the Datasette process, you can indicate that the configuration value should be read from that environment variable like so:
2023-07-08 11:00:08 -07:00
.. [[[cog
2023-10-12 09:16:37 -07:00
config_example(cog, {
2019-07-03 22:36:44 -07:00
"plugins": {
"datasette-auth-github": {
"client_secret": {
"$env": "GITHUB_CLIENT_SECRET"
}
}
}
2023-07-08 11:00:08 -07:00
})
.. ]]]
2019-07-03 22:36:44 -07:00
2023-10-12 09:16:37 -07:00
.. tab :: datasette.yaml
2023-07-08 11:00:08 -07:00
.. code-block :: yaml
plugins:
datasette-auth-github:
client_secret:
$env: GITHUB_CLIENT_SECRET
2023-10-12 09:16:37 -07:00
.. tab :: datasette.json
2023-07-08 11:00:08 -07:00
.. code-block :: json
{
"plugins": {
"datasette-auth-github": {
"client_secret": {
"$env": "GITHUB_CLIENT_SECRET"
}
}
}
}
.. [[[end]]]
2020-11-15 08:43:13 -08:00
2023-07-08 11:00:08 -07:00
**As values in separate files** . Your secrets can also live in files on disk. To specify a secret should be read from a file, provide the full file path like this:
2019-07-03 22:36:44 -07:00
2023-07-08 11:00:08 -07:00
.. [[[cog
2023-10-12 09:16:37 -07:00
config_example(cog, {
2019-07-03 22:36:44 -07:00
"plugins": {
"datasette-auth-github": {
"client_secret": {
"$file": "/secrets/client-secret"
}
}
}
2023-07-08 11:00:08 -07:00
})
.. ]]]
2023-10-12 09:16:37 -07:00
.. tab :: datasette.yaml
2023-07-08 11:00:08 -07:00
.. code-block :: yaml
plugins:
datasette-auth-github:
client_secret:
$file: /secrets/client-secret
2023-10-12 09:16:37 -07:00
.. tab :: datasette.json
2023-07-08 11:00:08 -07:00
.. code-block :: json
{
"plugins": {
"datasette-auth-github": {
"client_secret": {
"$file": "/secrets/client-secret"
}
}
}
}
.. [[[end]]]
2019-07-03 22:36:44 -07:00
2019-07-07 19:06:31 -07:00
If you are publishing your data using the :ref: `datasette publish <cli_publish>` family of commands, you can use the `` --plugin-secret `` option to set these secrets at publish time. For example, using Heroku you might run the following command::
2023-08-09 15:04:16 -07:00
datasette publish heroku my_database.db \
2019-07-07 19:06:31 -07:00
--name my-heroku-app-demo \
--install=datasette-auth-github \
--plugin-secret datasette-auth-github client_id your_client_id \
--plugin-secret datasette-auth-github client_secret your_client_secret
2020-11-15 08:43:13 -08:00
2023-07-08 11:00:08 -07:00
This will set the necessary environment variables and add the following to the deployed `` metadata.yaml `` :
2020-11-15 08:43:13 -08:00
2023-07-08 11:00:08 -07:00
.. [[[cog
2023-10-12 09:16:37 -07:00
config_example(cog, {
2020-11-15 08:43:13 -08:00
"plugins": {
"datasette-auth-github": {
"client_id": {
"$env": "DATASETTE_AUTH_GITHUB_CLIENT_ID"
},
"client_secret": {
"$env": "DATASETTE_AUTH_GITHUB_CLIENT_SECRET"
}
}
}
2023-07-08 11:00:08 -07:00
})
.. ]]]
2023-10-12 09:16:37 -07:00
.. tab :: datasette.yaml
2023-07-08 11:00:08 -07:00
.. code-block :: yaml
plugins:
datasette-auth-github:
client_id:
$env: DATASETTE_AUTH_GITHUB_CLIENT_ID
client_secret:
$env: DATASETTE_AUTH_GITHUB_CLIENT_SECRET
2023-10-12 09:16:37 -07:00
.. tab :: datasette.json
2023-07-08 11:00:08 -07:00
.. code-block :: json
{
"plugins": {
"datasette-auth-github": {
"client_id": {
"$env": "DATASETTE_AUTH_GITHUB_CLIENT_ID"
},
"client_secret": {
"$env": "DATASETTE_AUTH_GITHUB_CLIENT_SECRET"
}
}
}
}
.. [[[end]]]