id,page,ref,title,content,breadcrumbs,references plugins:deploying-plugins-using-datasette-publish,plugins,deploying-plugins-using-datasette-publish,Deploying plugins using datasette publish,"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: datasette publish cloudrun mydb.db --install=datasette-vega 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: datasette publish cloudrun mydb.db \ --install=https://url-to-my-package.zip","[""Plugins"", ""Installing plugins""]",[] plugins:one-off-plugins-using-plugins-dir,plugins,one-off-plugins-using-plugins-dir,One-off plugins using --plugins-dir,"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: datasette mydb.db --plugins-dir=plugins/","[""Plugins"", ""Installing plugins""]",[] plugin_hooks:plugin-asgi-wrapper,plugin_hooks,plugin-asgi-wrapper,asgi_wrapper(datasette),"Return an ASGI middleware wrapper function that will be applied to the Datasette ASGI application. This is a very powerful hook. You can use it to manipulate the entire Datasette response, or even to configure new URL routes that will be handled by your own custom code. You can write your ASGI code directly against the low-level specification, or you can use the middleware utilities provided by an ASGI framework such as Starlette . This example plugin adds a x-databases HTTP header listing the currently attached databases: from datasette import hookimpl from functools import wraps @hookimpl def asgi_wrapper(datasette): def wrap_with_databases_header(app): @wraps(app) async def add_x_databases_header( scope, receive, send ): async def wrapped_send(event): if event[""type""] == ""http.response.start"": original_headers = ( event.get(""headers"") or [] ) event = { ""type"": event[""type""], ""status"": event[""status""], ""headers"": original_headers + [ [ b""x-databases"", "", "".join( datasette.databases.keys() ).encode(""utf-8""), ] ], } await send(event) await app(scope, receive, wrapped_send) return add_x_databases_header return wrap_with_databases_header Examples: datasette-cors , datasette-pyinstrument , datasette-total-page-time","[""Plugin hooks""]","[{""href"": ""https://asgi.readthedocs.io/"", ""label"": ""ASGI""}, {""href"": ""https://www.starlette.io/middleware/"", ""label"": ""Starlette""}, {""href"": ""https://datasette.io/plugins/datasette-cors"", ""label"": ""datasette-cors""}, {""href"": ""https://datasette.io/plugins/datasette-pyinstrument"", ""label"": ""datasette-pyinstrument""}, {""href"": ""https://datasette.io/plugins/datasette-total-page-time"", ""label"": ""datasette-total-page-time""}]" plugin_hooks:plugin-hook-actor-from-request,plugin_hooks,plugin-hook-actor-from-request,"actor_from_request(datasette, request)","datasette - Datasette class You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) , or to execute SQL queries. request - Request object The current HTTP request. This is part of Datasette's authentication and permissions system . The function should attempt to authenticate an actor (either a user or an API actor of some sort) based on information in the request. If it cannot authenticate an actor, it should return None . Otherwise it should return a dictionary representing that actor. Here's an example that authenticates the actor based on an incoming API key: from datasette import hookimpl import secrets SECRET_KEY = ""this-is-a-secret"" @hookimpl def actor_from_request(datasette, request): authorization = ( request.headers.get(""authorization"") or """" ) expected = ""Bearer {}"".format(SECRET_KEY) if secrets.compare_digest(authorization, expected): return {""id"": ""bot""} If you install this in your plugins directory you can test it like this: $ curl -H 'Authorization: Bearer this-is-a-secret' http://localhost:8003/-/actor.json Instead of returning a dictionary, this function can return an awaitable function which itself returns either None or a dictionary. This is useful for authentication functions that need to make a database query - for example: from datasette import hookimpl @hookimpl def actor_from_request(datasette, request): async def inner(): token = request.args.get(""_token"") if not token: return None # Look up ?_token=xxx in sessions table result = await datasette.get_database().execute( ""select count(*) from sessions where token = ?"", [token], ) if result.first()[0]: return {""token"": token} else: return None return inner Example: datasette-auth-tokens","[""Plugin hooks""]","[{""href"": ""https://datasette.io/plugins/datasette-auth-tokens"", ""label"": ""datasette-auth-tokens""}]" plugin_hooks:plugin-hook-canned-queries,plugin_hooks,plugin-hook-canned-queries,"canned_queries(datasette, database, actor)","datasette - Datasette class You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) , or to execute SQL queries. database - string The name of the database. actor - dictionary or None The currently authenticated actor . Use this hook to return a dictionary of additional canned query definitions for the specified database. The return value should be the same shape as the JSON described in the canned query documentation. from datasette import hookimpl @hookimpl def canned_queries(datasette, database): if database == ""mydb"": return { ""my_query"": { ""sql"": ""select * from my_table where id > :min_id"" } } The hook can alternatively return an awaitable function that returns a list. Here's an example that returns queries that have been stored in the saved_queries database table, if one exists: from datasette import hookimpl @hookimpl def canned_queries(datasette, database): async def inner(): db = datasette.get_database(database) if await db.table_exists(""saved_queries""): results = await db.execute( ""select name, sql from saved_queries"" ) return { result[""name""]: {""sql"": result[""sql""]} for result in results } return inner The actor parameter can be used to include the currently authenticated actor in your decision. Here's an example that returns saved queries that were saved by that actor: from datasette import hookimpl @hookimpl def canned_queries(datasette, database, actor): async def inner(): db = datasette.get_database(database) if actor is not None and await db.table_exists( ""saved_queries"" ): results = await db.execute( ""select name, sql from saved_queries where actor_id = :id"", {""id"": actor[""id""]}, ) return { result[""name""]: {""sql"": result[""sql""]} for result in results } return inner Example: datasette-saved-queries","[""Plugin hooks""]","[{""href"": ""https://datasette.io/plugins/datasette-saved-queries"", ""label"": ""datasette-saved-queries""}]" plugin_hooks:plugin-hook-database-actions,plugin_hooks,plugin-hook-database-actions,"database_actions(datasette, actor, database, request)","datasette - Datasette class You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) , or to execute SQL queries. actor - dictionary or None The currently authenticated actor . database - string The name of the database. request - Request object The current HTTP request. This hook is similar to table_actions(datasette, actor, database, table, request) but populates an actions menu on the database page. Example: datasette-graphql","[""Plugin hooks""]","[{""href"": ""https://datasette.io/plugins/datasette-graphql"", ""label"": ""datasette-graphql""}]" plugin_hooks:plugin-hook-extra-body-script,plugin_hooks,plugin-hook-extra-body-script,"extra_body_script(template, database, table, columns, view_name, request, datasette)","Extra JavaScript to be added to a element: @hookimpl def extra_body_script(): return { ""module"": True, ""script"": ""console.log('Your JavaScript goes here...')"", } This will add the following to the end of your page: Example: datasette-cluster-map","[""Plugin hooks""]","[{""href"": ""https://datasette.io/plugins/datasette-cluster-map"", ""label"": ""datasette-cluster-map""}]" plugin_hooks:plugin-hook-extra-css-urls,plugin_hooks,plugin-hook-extra-css-urls,"extra_css_urls(template, database, table, columns, view_name, request, datasette)","This takes the same arguments as extra_template_vars(...) Return a list of extra CSS URLs that should be included on the page. These can take advantage of the CSS class hooks described in Custom pages and templates . This can be a list of URLs: from datasette import hookimpl @hookimpl def extra_css_urls(): return [ ""https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"" ] Or a list of dictionaries defining both a URL and an SRI hash : @hookimpl def extra_css_urls(): return [ { ""url"": ""https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"", ""sri"": ""sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"", } ] This function can also return an awaitable function, useful if it needs to run any async code: @hookimpl def extra_css_urls(datasette): async def inner(): db = datasette.get_database() results = await db.execute( ""select url from css_files"" ) return [r[0] for r in results] return inner Examples: datasette-cluster-map , datasette-vega","[""Plugin hooks""]","[{""href"": ""https://www.srihash.org/"", ""label"": ""SRI hash""}, {""href"": ""https://datasette.io/plugins/datasette-cluster-map"", ""label"": ""datasette-cluster-map""}, {""href"": ""https://datasette.io/plugins/datasette-vega"", ""label"": ""datasette-vega""}]" plugin_hooks:plugin-hook-extra-js-urls,plugin_hooks,plugin-hook-extra-js-urls,"extra_js_urls(template, database, table, columns, view_name, request, datasette)","This takes the same arguments as extra_template_vars(...) This works in the same way as extra_css_urls() but for JavaScript. You can return a list of URLs, a list of dictionaries or an awaitable function that returns those things: from datasette import hookimpl @hookimpl def extra_js_urls(): return [ { ""url"": ""https://code.jquery.com/jquery-3.3.1.slim.min.js"", ""sri"": ""sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"", } ] You can also return URLs to files from your plugin's static/ directory, if you have one: @hookimpl def extra_js_urls(): return [""/-/static-plugins/your-plugin/app.js""] Note that your-plugin here should be the hyphenated plugin name - the name that is displayed in the list on the /-/plugins debug page. If your code uses JavaScript modules you should include the ""module"": True key. See Custom CSS and JavaScript for more details. @hookimpl def extra_js_urls(): return [ { ""url"": ""/-/static-plugins/your-plugin/app.js"", ""module"": True, } ] Examples: datasette-cluster-map , datasette-vega","[""Plugin hooks""]","[{""href"": ""https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules"", ""label"": ""JavaScript modules""}, {""href"": ""https://datasette.io/plugins/datasette-cluster-map"", ""label"": ""datasette-cluster-map""}, {""href"": ""https://datasette.io/plugins/datasette-vega"", ""label"": ""datasette-vega""}]" plugin_hooks:plugin-hook-extra-template-vars,plugin_hooks,plugin-hook-extra-template-vars,"extra_template_vars(template, database, table, columns, view_name, request, datasette)","Extra template variables that should be made available in the rendered template context. template - string The template that is being rendered, e.g. database.html database - string or None The name of the database, or None if the page does not correspond to a database (e.g. the root page) table - string or None The name of the table, or None if the page does not correct to a table columns - list of strings or None The names of the database columns that will be displayed on this page. None if the page does not contain a table. view_name - string The name of the view being displayed. ( index , database , table , and row are the most important ones.) request - Request object or None The current HTTP request. This can be None if the request object is not available. datasette - Datasette class You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) This hook can return one of three different types: Dictionary If you return a dictionary its keys and values will be merged into the template context. Function that returns a dictionary If you return a function it will be executed. If it returns a dictionary those values will will be merged into the template context. Function that returns an awaitable function that returns a dictionary You can also return a function which returns an awaitable function which returns a dictionary. Datasette runs Jinja2 in async mode , which means you can add awaitable functions to the template scope and they will be automatically awaited when they are rendered by the template. Here's an example plugin that adds a ""user_agent"" variable to the template context containing the current request's User-Agent header: @hookimpl def extra_template_vars(request): return {""user_agent"": request.headers.get(""user-agent"")} This example returns an awaitable function which adds a list of hidden_table_names to the context: @hookimpl def extra_template_vars(datasette, database): async def hidden_table_names(): if database: db = datasette.databases[database] return { ""hidden_table_names"": await db.hidden_table_names() } else: return {} return hidden_table_names And here's an example which adds a sql_first(sql_query) function which executes a SQL statement and returns the first column of the first row of results: @hookimpl def extra_template_vars(datasette, database): async def sql_first(sql, dbname=None): dbname = ( dbname or database or next(iter(datasette.databases.keys())) ) result = await datasette.execute(dbname, sql) return result.rows[0][0] return {""sql_first"": sql_first} You can then use the new function in a template like so: SQLite version: {{ sql_first(""select sqlite_version()"") }} Examples: datasette-search-all , datasette-template-sql","[""Plugin hooks""]","[{""href"": ""https://jinja.palletsprojects.com/en/2.10.x/api/#async-support"", ""label"": ""async mode""}, {""href"": ""https://datasette.io/plugins/datasette-search-all"", ""label"": ""datasette-search-all""}, {""href"": ""https://datasette.io/plugins/datasette-template-sql"", ""label"": ""datasette-template-sql""}]" plugin_hooks:plugin-hook-filters-from-request,plugin_hooks,plugin-hook-filters-from-request,"filters_from_request(request, database, table, datasette)","request - Request object The current HTTP request. database - string The name of the database. table - string The name of the table. datasette - Datasette class You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) , or to execute SQL queries. This hook runs on the table page, and can influence the where clause of the SQL query used to populate that page, based on query string arguments on the incoming request. The hook should return an instance of datasette.filters.FilterArguments which has one required and three optional arguments: return FilterArguments( where_clauses=[""id > :max_id""], params={""max_id"": 5}, human_descriptions=[""max_id is greater than 5""], extra_context={}, ) The arguments to the FilterArguments class constructor are as follows: where_clauses - list of strings, required A list of SQL fragments that will be inserted into the SQL query, joined by the and operator. These can include :named parameters which will be populated using data in params . params - dictionary, optional Additional keyword arguments to be used when the query is executed. These should match any :arguments in the where clauses. human_descriptions - list of strings, optional These strings will be included in the human-readable description at the top of the page and the page
This description includes a long HTML string
This demonstrates basic LIKE search
The metadata.yml file is passed to Datasette using the same --metadata option:
datasette fixtures.db --metadata metadata.yml","[""Metadata""]",[]
metadata:per-database-and-per-table-metadata,metadata,per-database-and-per-table-metadata,Per-database and per-table metadata,"Metadata at the top level of the JSON will be shown on the index page and in the
footer on every page of the site. The license and source is expected to apply to
all of your data.
You can also provide metadata at the per-database or per-table level, like this:
{
""databases"": {
""database1"": {
""source"": ""Alternative source"",
""source_url"": ""http://example.com/"",
""tables"": {
""example_table"": {
""description_html"": ""Custom table description"",
""license"": ""CC BY 3.0 US"",
""license_url"": ""https://creativecommons.org/licenses/by/3.0/us/""
}
}
}
}
}
Each of the top-level metadata fields can be used at the database and table level.","[""Metadata""]",[]
metadata:specifying-units-for-a-column,metadata,specifying-units-for-a-column,Specifying units for a column,"Datasette supports attaching units to a column, which will be used when displaying
values from that column. SI prefixes will be used where appropriate.
Column units are configured in the metadata like so:
{
""databases"": {
""database1"": {
""tables"": {
""example_table"": {
""units"": {
""column1"": ""metres"",
""column2"": ""Hz""
}
}
}
}
}
}
Units are interpreted using Pint , and you can see the full list of available units in
Pint's unit registry . You can also add custom units to the metadata, which will be
registered with Pint:
{
""custom_units"": [
""decibel = [] = dB""
]
}","[""Metadata""]","[{""href"": ""https://pint.readthedocs.io/"", ""label"": ""Pint""}, {""href"": ""https://github.com/hgrecco/pint/blob/master/pint/default_en.txt"", ""label"": ""unit registry""}, {""href"": ""http://pint.readthedocs.io/en/latest/defining.html"", ""label"": ""custom units""}]"
json_api:expand-foreign-keys,json_api,expand-foreign-keys,Expanding foreign key references,"Datasette can detect foreign key relationships and resolve those references into
labels. The HTML interface does this by default for every detected foreign key
column - you can turn that off using ?_labels=off .
You can request foreign keys be expanded in JSON using the _labels=on or
_label=COLUMN special query string parameters. Here's what an expanded row
looks like:
[
{
""rowid"": 1,
""TreeID"": 141565,
""qLegalStatus"": {
""value"": 1,
""label"": ""Permitted Site""
},
""qSpecies"": {
""value"": 1,
""label"": ""Myoporum laetum :: Myoporum""
},
""qAddress"": ""501X Baker St"",
""SiteOrder"": 1
}
]
The column in the foreign key table that is used for the label can be specified
in metadata.json - see Specifying the label column for a table .","[""JSON API""]",[]
json_api:id2,json_api,id2,Table arguments,The Datasette table view takes a number of special query string arguments.,"[""JSON API""]",[]
json_api:json-api-discover-alternate,json_api,json-api-discover-alternate,Discovering the JSON for a page,"Most of the HTML pages served by Datasette provide a mechanism for discovering their JSON equivalents using the HTML link mechanism.
You can find this near the top of the source code of those pages, looking like this:
The JSON URL is also made available in a Link HTTP header for the page:
Link: https://latest.datasette.io/fixtures/sortable.json; rel=""alternate""; type=""application/json+datasette""","[""JSON API""]",[]
json_api:json-api-pagination,json_api,json-api-pagination,Pagination,"The default JSON representation includes a ""next_url"" key which can be used to access the next page of results. If that key is null or missing then it means you have reached the final page of results.
Other representations include pagination information in the link HTTP header. That header will look something like this:
link: