id,page,ref,title,content,breadcrumbs,references plugin_hooks:plugin-register-facet-classes,plugin_hooks,plugin-register-facet-classes,register_facet_classes(),"Return a list of additional Facet subclasses to be registered. The design of this plugin hook is unstable and may change. See issue 830 . Each Facet subclass implements a new type of facet operation. The class should look like this: class SpecialFacet(Facet): # This key must be unique across all facet classes: type = ""special"" async def suggest(self): # Use self.sql and self.params to suggest some facets suggested_facets = [] suggested_facets.append( { ""name"": column, # Or other unique name # Construct the URL that will enable this facet: ""toggle_url"": self.ds.absolute_url( self.request, path_with_added_args( self.request, {""_facet"": column} ), ), } ) return suggested_facets async def facet_results(self): # This should execute the facet operation and return results, again # using self.sql and self.params as the starting point facet_results = [] facets_timed_out = [] facet_size = self.get_facet_size() # Do some calculations here... for column in columns_selected_for_facet: try: facet_results_values = [] # More calculations... facet_results_values.append( { ""value"": value, ""label"": label, ""count"": count, ""toggle_url"": self.ds.absolute_url( self.request, toggle_path ), ""selected"": selected, } ) facet_results.append( { ""name"": column, ""results"": facet_results_values, ""truncated"": len(facet_rows_results) > facet_size, } ) except QueryInterrupted: facets_timed_out.append(column) return facet_results, facets_timed_out See datasette/facets.py for examples of how these classes can work. The plugin hook can then be used to register the new facet class like this: @hookimpl def register_facet_classes(): return [SpecialFacet]","[""Plugin hooks""]","[{""href"": ""https://github.com/simonw/datasette/issues/830"", ""label"": ""issue 830""}, {""href"": ""https://github.com/simonw/datasette/blob/main/datasette/facets.py"", ""label"": ""datasette/facets.py""}]" plugin_hooks:plugin-hook-publish-subcommand,plugin_hooks,plugin-hook-publish-subcommand,publish_subcommand(publish),"publish - Click publish command group The Click command group for the datasette publish subcommand This hook allows you to create new providers for the datasette publish command. Datasette uses this hook internally to implement the default cloudrun and heroku subcommands, so you can read their source to see examples of this hook in action. Let's say you want to build a plugin that adds a datasette publish my_hosting_provider --api_key=xxx mydatabase.db publish command. Your implementation would start like this: from datasette import hookimpl from datasette.publish.common import ( add_common_publish_arguments_and_options, ) import click @hookimpl def publish_subcommand(publish): @publish.command() @add_common_publish_arguments_and_options @click.option( ""-k"", ""--api_key"", help=""API key for talking to my hosting provider"", ) def my_hosting_provider( files, metadata, extra_options, branch, template_dir, plugins_dir, static, install, plugin_secret, version_note, secret, title, license, license_url, source, source_url, about, about_url, api_key, ): ... Examples: datasette-publish-fly , datasette-publish-vercel","[""Plugin hooks""]","[{""href"": ""https://github.com/simonw/datasette/tree/main/datasette/publish"", ""label"": ""their source""}, {""href"": ""https://datasette.io/plugins/datasette-publish-fly"", ""label"": ""datasette-publish-fly""}, {""href"": ""https://datasette.io/plugins/datasette-publish-vercel"", ""label"": ""datasette-publish-vercel""}]" 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"", ""Page extras""]","[{""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-jinja2-environment-from-request,plugin_hooks,plugin-hook-jinja2-environment-from-request,"jinja2_environment_from_request(datasette, request, env)","datasette - Datasette class A Datasette instance. request - Request object or None The current HTTP request, if one is available. env - Environment The Jinja2 environment that will be used to render the current page. This hook can be used to return a customized Jinja environment based on the incoming request. If you want to run a single Datasette instance that serves different content for different domains, you can do so like this: from datasette import hookimpl from jinja2 import ChoiceLoader, FileSystemLoader @hookimpl def jinja2_environment_from_request(request, env): if request and request.host == ""www.niche-museums.com"": return env.overlay( loader=ChoiceLoader( [ FileSystemLoader( ""/mnt/niche-museums/templates"" ), env.loader, ] ), enable_async=True, ) return env This uses the Jinja overlay() method to create a new environment identical to the default environment except for having a different template loader, which first looks in the /mnt/niche-museums/templates directory before falling back on the default loader.","[""Plugin hooks""]","[{""href"": ""https://jinja.palletsprojects.com/en/3.0.x/api/#jinja2.Environment"", ""label"": ""Jinja environment""}, {""href"": ""https://jinja.palletsprojects.com/en/3.0.x/api/#jinja2.Environment.overlay"", ""label"": ""overlay() method""}]" plugin_hooks:plugin-hook-slots,plugin_hooks,plugin-hook-slots,Template slots,"The following set of plugin hooks can be used to return extra HTML content that will be inserted into the corresponding page, directly below the

heading. Multiple plugins can contribute content here. The order in which it is displayed can be controlled using Pluggy's call time order options . Each of these plugin hooks can return either a string or an awaitable function that returns a string.","[""Plugin hooks""]","[{""href"": ""https://pluggy.readthedocs.io/en/stable/#call-time-order"", ""label"": ""call time order options""}]" plugin_hooks:plugin-hook-handle-exception,plugin_hooks,plugin-hook-handle-exception,"handle_exception(datasette, request, exception)","datasette - Datasette class You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) , or to render templates or execute SQL queries. request - Request object The current HTTP request. exception - Exception The exception that was raised. This hook is called any time an unexpected exception is raised. You can use it to record the exception. If your handler returns a Response object it will be returned to the client in place of the default Datasette error page. The handler can return a response directly, or it can return return an awaitable function that returns a response. This example logs an error to Sentry and then renders a custom error page: from datasette import hookimpl, Response import sentry_sdk @hookimpl def handle_exception(datasette, exception): sentry_sdk.capture_exception(exception) async def inner(): return Response.html( await datasette.render_template( ""custom_error.html"", request=request ) ) return inner Example: datasette-sentry","[""Plugin hooks""]","[{""href"": ""https://sentry.io/"", ""label"": ""Sentry""}, {""href"": ""https://datasette.io/plugins/datasette-sentry"", ""label"": ""datasette-sentry""}]" 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"", ""Page extras""]","[{""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""}]"