# sections

id: plugin_hooks:id1
page: plugin_hooks
ref: id1
title: Plugin hooks
breadcrumbs: []
references: [{"href": "https://pluggy.readthedocs.io/", "label": "pluggy"}]
content:
Datasette  plugins  use  plugin hooks  to customize Datasette's behavior. These hooks are powered by the  pluggy  plugin system. 
         Each plugin can implement one or more hooks using the  @hookimpl  decorator against a function named that matches one of the hooks documented on this page. 
         When you implement a plugin hook you can accept any or all of the parameters that are documented as being passed to that hook. 
         For example, you can implement the  render_cell  plugin hook like this even though the full documented hook signature is  render_cell(row, value, column, table, database, datasette) : 
         @hookimpl
def render_cell(value, column):
    if column == "stars":
        return "*" * int(value) 
         
             List of plugin hooks 
             
                 
                     prepare_connection(conn, database, datasette) 
                 
                 
                     prepare_jinja2_environment(env, datasette) 
                 
                 
                     Page extras 
                     
                         
                             extra_template_vars(template, database, table, columns, view_name, request, datasette) 
                         
                         
                             extra_css_urls(template, database, table, columns, view_name, request, datasette) 
                         
                         
                             extra_js_urls(template, database, table, columns, view_name, request, datasette) 
                         
                         
                             extra_body_script(template, database, table, columns, view_name, request, datasette) 
                         
                     
                 
                 
                     publish_subcommand(publish) 
                 
                 
                     render_cell(row, value, column, table, database, datasette, request) 
                 
                 
                     register_output_renderer(datasette) 
                 
                 
                     register_routes(datasette) 
                 
                 
                     register_commands(cli) 
                 
                 
                     register_facet_classes() 
                 
                 
                     register_permissions(datasette) 
                 
                 
                     asgi_wrapper(datasette) 
                 
                 
                     startup(datasette) 
                 
                 
                     canned_queries(datasette, database, actor) 
                 
                 
                     actor_from_request(datasette, request) 
                 
                 
                     actors_from_ids(datasette, actor_ids) 
                 
                 
                     jinja2_environment_from_request(datasette, request, env) 
                 
                 
                     filters_from_request(request, database, table, datasette) 
                 
                 
                     permission_allowed(datasette, actor, action, resource) 
                 
                 
                     register_magic_parameters(datasette) 
                 
                 
                     forbidden(datasette, request, message) 
                 
                 
                     handle_exception(datasette, request, exception) 
                 
                 
                     skip_csrf(datasette, scope) 
                 
                 
                     get_metadata(datasette, key, database, table) 
                 
                 
                     menu_links(datasette, actor, request) 
                 
                 
                     Action hooks 
                     
                         
                             table_actions(datasette, actor, database, table, request) 
                         
                         
                             view_actions(datasette, actor, database, view, request) 
                         
                         
                             query_actions(datasette, actor, database, query_name, request, sql, params) 
                         
                         
                             row_actions(datasette, actor, request, database, table, row) 
                         
                         
                             database_actions(datasette, actor, database, request) 
                         
                         
                             homepage_actions(datasette, actor, request) 
                         
                     
                 
                 
                     Template slots 
                     
                         
                             top_homepage(datasette, request) 
                         
                         
                             top_database(datasette, request, database) 
                         
                         
                             top_table(datasette, request, database, table) 
                         
                         
                             top_row(datasette, request, database, table, row) 
                         
                         
                             top_query(datasette, request, database, sql) 
                         
                         
                             top_canned_query(datasette, request, database, query_name) 
                         
                     
                 
                 
                     Event tracking 
                     
                         
                             track_event(datasette, event) 
                         
                         
                             register_events(datasette)


id: plugin_hooks:plugin-hook-prepare-connection
page: plugin_hooks
ref: plugin-hook-prepare-connection
title: prepare_connection(conn, database, datasette)
breadcrumbs: ["Plugin hooks"]
content:
conn  - sqlite3 connection object 
                     
                         The connection that is being opened 
                     
                 
                 
                     database  - string 
                     
                         The name of the database 
                     
                 
                 
                     datasette  -  Datasette class 
                     
                         You can use this to access plugin configuration options via  datasette.plugin_config(your_plugin_name) 
                     
                 
             
             This hook is called when a new SQLite database connection is created. You can
                use it to  register custom SQL functions ,
                aggregates and collations. For example: 
             from datasette import hookimpl
import random


@hookimpl
def prepare_connection(conn):
    conn.create_function(
        "random_integer", 2, random.randint
    ) 
             This registers a SQL function called  random_integer  which takes two
                arguments and can be called like this: 
             select random_integer(1, 10); 
             Examples:  datasette-jellyfish ,  datasette-jq ,  datasette-haversine ,  datasette-rure

references:
[{"href": "https://docs.python.org/2/library/sqlite3.html#sqlite3.Connection.create_function", "label": "register custom SQL functions"}, {"href": "https://datasette.io/plugins/datasette-jellyfish", "label": "datasette-jellyfish"}, {"href": "https://datasette.io/plugins/datasette-jq", "label": "datasette-jq"}, {"href": "https://datasette.io/plugins/datasette-haversine", "label": "datasette-haversine"}, {"href": "https://datasette.io/plugins/datasette-rure", "label": "datasette-rure"}]


id: plugin_hooks:plugin-hook-prepare-jinja2-environment
page: plugin_hooks
ref: plugin-hook-prepare-jinja2-environment
title: prepare_jinja2_environment(env, datasette)
breadcrumbs: ["Plugin hooks"]
content:
env  - jinja2 Environment 
                     
                         The template environment that is being prepared 
                     
                 
                 
                     datasette  -  Datasette class 
                     
                         You can use this to access plugin configuration options via  datasette.plugin_config(your_plugin_name) 
                     
                 
             
             This hook is called with the Jinja2 environment that is used to evaluate
                Datasette HTML templates. You can use it to do things like  register custom
                    template filters , for
                example: 
             from datasette import hookimpl


@hookimpl
def prepare_jinja2_environment(env):
    env.filters["uppercase"] = lambda u: u.upper() 
             You can now use this filter in your custom templates like so: 
             Table name: {{ table|uppercase }} 
             This function can return an awaitable function if it needs to run any async code. 
             Examples:  datasette-edit-templates

references:
[{"href": "http://jinja.pocoo.org/docs/2.10/api/#custom-filters", "label": "register custom\n                    template filters"}, {"href": "https://datasette.io/plugins/datasette-edit-templates", "label": "datasette-edit-templates"}]


id: plugin_hooks:plugin-page-extras
page: plugin_hooks
ref: plugin-page-extras
title: Page extras
breadcrumbs: ["Plugin hooks"]
references: []
content:
These plugin hooks can be used to affect the way HTML pages for different Datasette interfaces are rendered.


id: plugin_hooks:plugin-hook-extra-template-vars
page: plugin_hooks
ref: plugin-hook-extra-template-vars
title: extra_template_vars(template, database, table, columns, view_name, request, datasette)
breadcrumbs: ["Plugin hooks", "Page extras"]
content:
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

references:
[{"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"}]


id: plugin_hooks:plugin-hook-extra-css-urls
page: plugin_hooks
ref: plugin-hook-extra-css-urls
title: extra_css_urls(template, database, table, columns, view_name, request, datasette)
breadcrumbs: ["Plugin hooks", "Page extras"]
content:
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

references:
[{"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"}]


id: plugin_hooks:plugin-hook-extra-js-urls
page: plugin_hooks
ref: plugin-hook-extra-js-urls
title: extra_js_urls(template, database, table, columns, view_name, request, datasette)
breadcrumbs: ["Plugin hooks", "Page extras"]
content:
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

references:
[{"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"}]


id: plugin_hooks:plugin-hook-extra-body-script
page: plugin_hooks
ref: plugin-hook-extra-body-script
title: extra_body_script(template, database, table, columns, view_name, request, datasette)
breadcrumbs: ["Plugin hooks", "Page extras"]
references: [{"href": "https://datasette.io/plugins/datasette-cluster-map", "label": "datasette-cluster-map"}]
content:
Extra JavaScript to be added to a  <script>  block at the end of the  <body>  element on the page. 
                 This takes the same arguments as  extra_template_vars(...) 
                 The  template ,  database ,  table  and  view_name  options can be used to return different code depending on which template is being rendered and which database or table are being processed. 
                 The  datasette  instance is provided primarily so that you can consult any plugin configuration options that may have been set, using the  datasette.plugin_config(plugin_name)  method documented above. 
                 This function can return a string containing JavaScript, or a dictionary as described below, or a function or awaitable function that returns a string or dictionary. 
                 Use a dictionary if you want to specify that the code should be placed in a  <script type="module">...</script>  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: 
                 <script type="module">console.log('Your JavaScript goes here...')</script> 
                 Example:  datasette-cluster-map


id: plugin_hooks:plugin-hook-publish-subcommand
page: plugin_hooks
ref: plugin-hook-publish-subcommand
title: publish_subcommand(publish)
breadcrumbs: ["Plugin hooks"]
content:
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

references:
[{"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"}]


id: plugin_hooks:plugin-hook-render-cell
page: plugin_hooks
ref: plugin-hook-render-cell
title: render_cell(row, value, column, table, database, datasette, request)
breadcrumbs: ["Plugin hooks"]
content:
Lets you customize the display of values within table cells in the HTML table view. 
             
                 
                     row  -  sqlite.Row 
                     
                         The SQLite row object that the value being rendered is part of 
                     
                 
                 
                     value  - string, integer, float, bytes or None 
                     
                         The value that was loaded from the database 
                     
                 
                 
                     column  - string 
                     
                         The name of the column being rendered 
                     
                 
                 
                     table  - string or None 
                     
                         The name of the table - or  None  if this is a custom SQL query 
                     
                 
                 
                     database  - string 
                     
                         The name of the database 
                     
                 
                 
                     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 request object 
                     
                 
             
             If your hook returns  None , it will be ignored. Use this to indicate that your hook is not able to custom render this particular value. 
             If the hook returns a string, that string will be rendered in the table cell. 
             If you want to return HTML markup you can do so by returning a  jinja2.Markup  object. 
             You can also return an awaitable function which returns a value. 
             Datasette will loop through all available  render_cell  hooks and display the value returned by the first one that does not return  None . 
             Here is an example of a custom  render_cell()  plugin which looks for values that are a JSON string matching the following format: 
             {"href": "https://www.example.com/", "label": "Name"} 
             If the value matches that pattern, the plugin returns an HTML link element: 
             from datasette import hookimpl
import markupsafe
import json


@hookimpl
def render_cell(value):
    # Render {"href": "...", "label": "..."} as link
    if not isinstance(value, str):
        return None
    stripped = value.strip()
    if not (
        stripped.startswith("{") and stripped.endswith("}")
    ):
        return None
    try:
        data = json.loads(value)
    except ValueError:
        return None
    if not isinstance(data, dict):
        return None
    if set(data.keys()) != {"href", "label"}:
        return None
    href = data["href"]
    if not (
        href.startswith("/")
        or href.startswith("http://")
        or href.startswith("https://")
    ):
        return None
    return markupsafe.Markup(
        '<a href="{href}">{label}</a>'.format(
            href=markupsafe.escape(data["href"]),
            label=markupsafe.escape(data["label"] or "")
            or "&nbsp;",
        )
    ) 
             Examples:  datasette-render-binary ,  datasette-render-markdown ,  datasette-json-html

references:
[{"href": "https://datasette.io/plugins/datasette-render-binary", "label": "datasette-render-binary"}, {"href": "https://datasette.io/plugins/datasette-render-markdown", "label": "datasette-render-markdown"}, {"href": "https://datasette.io/plugins/datasette-json-html", "label": "datasette-json-html"}]


id: plugin_hooks:plugin-register-output-renderer
page: plugin_hooks
ref: plugin-register-output-renderer
title: register_output_renderer(datasette)
breadcrumbs: ["Plugin hooks"]
content:
datasette  -  Datasette class 
                     
                         You can use this to access plugin configuration options via  datasette.plugin_config(your_plugin_name) 
                     
                 
             
             Registers a new output renderer, to output data in a custom format. The hook function should return a dictionary, or a list of dictionaries, of the following shape: 
             @hookimpl
def register_output_renderer(datasette):
    return {
        "extension": "test",
        "render": render_demo,
        "can_render": can_render_demo,  # Optional
    } 
             This will register  render_demo  to be called when paths with the extension  .test  (for example  /database.test ,  /database/table.test , or  /database/table/row.test ) are requested. 
             render_demo  is a Python function. It can be a regular function or an  async def render_demo()  awaitable function, depending on if it needs to make any asynchronous calls. 
             can_render_demo  is a Python function (or  async def  function) which accepts the same arguments as  render_demo  but just returns  True  or  False . It lets Datasette know if the current SQL query can be represented by the plugin - and hence influence if a link to this output format is displayed in the user interface. If you omit the  "can_render"  key from the dictionary every query will be treated as being supported by the plugin. 
             When a request is received, the  "render"  callback function is called with zero or more of the following arguments. Datasette will inspect your callback function and pass arguments that match its function signature. 
             
                 
                     datasette  -  Datasette class 
                     
                         For accessing plugin configuration and executing queries. 
                     
                 
                 
                     columns  - list of strings 
                     
                         The names of the columns returned by this query. 
                     
                 
                 
                     rows  - list of  sqlite3.Row  objects 
                     
                         The rows returned by the query. 
                     
                 
                 
                     sql  - string 
                     
                         The SQL query that was executed. 
                     
                 
                 
                     query_name  - string or None 
                     
                         If this was the execution of a  canned query , the name of that query. 
                     
                 
                 
                     database  - string 
                     
                         The name of the database. 
                     
                 
                 
                     table  - string or None 
                     
                         The table or view, if one is being rendered. 
                     
                 
                 
                     request  -  Request object 
                     
                         The current HTTP request. 
                     
                 
                 
                     error  - string or None 
                     
                         If an error occurred this string will contain the error message. 
                     
                 
                 
                     truncated  - bool or None 
                     
                         If the query response was truncated - for example a SQL query returning more than 1,000 results where pagination is not available - this will be  True . 
                     
                 
                 
                     view_name  - string 
                     
                         The name of the current view being called.  index ,  database ,  table , and  row  are the most important ones. 
                     
                 
             
             The callback function can return  None , if it is unable to render the data, or a  Response class  that will be returned to the caller. 
             It can also return a dictionary with the following keys. This format is  deprecated  as-of Datasette 0.49 and will be removed by Datasette 1.0. 
             
                 
                     body  - string or bytes, optional 
                     
                         The response body, default empty 
                     
                 
                 
                     content_type  - string, optional 
                     
                         The Content-Type header, default  text/plain 
                     
                 
                 
                     status_code  - integer, optional 
                     
                         The HTTP status code, default 200 
                     
                 
                 
                     headers  - dictionary, optional 
                     
                         Extra HTTP headers to be returned in the response. 
                     
                 
             
             An example of an output renderer callback function: 
             def render_demo():
    return Response.text("Hello World") 
             Here is a more complex example: 
             async def render_demo(datasette, columns, rows):
    db = datasette.get_database()
    result = await db.execute("select sqlite_version()")
    first_row = " | ".join(columns)
    lines = [first_row]
    lines.append("=" * len(first_row))
    for row in rows:
        lines.append(" | ".join(row))
    return Response(
        "\n".join(lines),
        content_type="text/plain; charset=utf-8",
        headers={"x-sqlite-version": result.first()[0]},
    ) 
             And here is an example  can_render  function which returns  True  only if the query results contain the columns  atom_id ,  atom_title  and  atom_updated : 
             def can_render_demo(columns):
    return {
        "atom_id",
        "atom_title",
        "atom_updated",
    }.issubset(columns) 
             Examples:  datasette-atom ,  datasette-ics ,  datasette-geojson ,  datasette-copyable

references:
[{"href": "https://datasette.io/plugins/datasette-atom", "label": "datasette-atom"}, {"href": "https://datasette.io/plugins/datasette-ics", "label": "datasette-ics"}, {"href": "https://datasette.io/plugins/datasette-geojson", "label": "datasette-geojson"}, {"href": "https://datasette.io/plugins/datasette-copyable", "label": "datasette-copyable"}]


id: plugin_hooks:plugin-register-routes
page: plugin_hooks
ref: plugin-register-routes
title: register_routes(datasette)
breadcrumbs: ["Plugin hooks"]
content:
datasette  -  Datasette class 
                     
                         You can use this to access plugin configuration options via  datasette.plugin_config(your_plugin_name) 
                     
                 
             
             Register additional view functions to execute for specified URL routes. 
             Return a list of  (regex, view_function)  pairs, something like this: 
             from datasette import hookimpl, Response
import html


async def hello_from(request):
    name = request.url_vars["name"]
    return Response.html(
        "Hello from {}".format(html.escape(name))
    )


@hookimpl
def register_routes():
    return [(r"^/hello-from/(?P<name>.*)$", hello_from)] 
             The view functions can take a number of different optional arguments. The corresponding argument will be passed to your function depending on its named parameters - a form of dependency injection. 
             The optional view function arguments are as follows: 
             
                 
                     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. 
                     
                 
                 
                     scope  - dictionary 
                     
                         The incoming ASGI scope dictionary. 
                     
                 
                 
                     send  - function 
                     
                         The ASGI send function. 
                     
                 
                 
                     receive  - function 
                     
                         The ASGI receive function. 
                     
                 
             
             The view function can be a regular function or an  async def  function, depending on if it needs to use any  await  APIs. 
             The function can either return a  Response class  or it can return nothing and instead respond directly to the request using the ASGI  send  function (for advanced uses only). 
             It can also raise the  datasette.NotFound  exception to return a 404 not found error, or the  datasette.Forbidden  exception for a 403 forbidden. 
             See  Designing URLs for your plugin  for tips on designing the URL routes used by your plugin. 
             Examples:  datasette-auth-github ,  datasette-psutil

references:
[{"href": "https://datasette.io/plugins/datasette-auth-github", "label": "datasette-auth-github"}, {"href": "https://datasette.io/plugins/datasette-psutil", "label": "datasette-psutil"}]


id: plugin_hooks:plugin-hook-register-commands
page: plugin_hooks
ref: plugin-hook-register-commands
title: register_commands(cli)
breadcrumbs: ["Plugin hooks"]
content:
cli  - the root Datasette  Click command group 
                     
                         Use this to register additional CLI commands 
                     
                 
             
             Register additional CLI commands that can be run using  datsette yourcommand ... . This provides a mechanism by which plugins can add new CLI commands to Datasette. 
             This example registers a new  datasette verify file1.db file2.db  command that checks if the provided file paths are valid SQLite databases: 
             from datasette import hookimpl
import click
import sqlite3


@hookimpl
def register_commands(cli):
    @cli.command()
    @click.argument(
        "files", type=click.Path(exists=True), nargs=-1
    )
    def verify(files):
        "Verify that files can be opened by Datasette"
        for file in files:
            conn = sqlite3.connect(str(file))
            try:
                conn.execute("select * from sqlite_master")
            except sqlite3.DatabaseError:
                raise click.ClickException(
                    "Invalid database: {}".format(file)
                ) 
             The new command can then be executed like so: 
             datasette verify fixtures.db 
             Help text (from the docstring for the function plus any defined Click arguments or options) will become available using: 
             datasette verify --help 
             Plugins can register multiple commands by making multiple calls to the  @cli.command()  decorator. Consult the  Click documentation  for full details on how to build a CLI command, including how to define arguments and options. 
             Note that  register_commands()  plugins cannot used with the  --plugins-dir mechanism  - they need to be installed into the same virtual environment as Datasette using  pip install . Provided it has a  setup.py  file (see  Packaging a plugin ) you can run  pip install  directly against the directory in which you are developing your plugin like so: 
             pip install -e path/to/my/datasette-plugin 
             Examples:  datasette-auth-passwords ,  datasette-verify

references:
[{"href": "https://click.palletsprojects.com/en/latest/commands/#callback-invocation", "label": "Click command group"}, {"href": "https://click.palletsprojects.com/", "label": "Click documentation"}, {"href": "https://datasette.io/plugins/datasette-auth-passwords", "label": "datasette-auth-passwords"}, {"href": "https://datasette.io/plugins/datasette-verify", "label": "datasette-verify"}]


id: plugin_hooks:plugin-register-facet-classes
page: plugin_hooks
ref: plugin-register-facet-classes
title: register_facet_classes()
breadcrumbs: ["Plugin hooks"]
content:
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]

references:
[{"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"}]


id: plugin_hooks:plugin-register-permissions
page: plugin_hooks
ref: plugin-register-permissions
title: register_permissions(datasette)
breadcrumbs: ["Plugin hooks"]
references: []
content:
If your plugin needs to register additional permissions unique to that plugin -  upload-csvs  for example - you can return a list of those permissions from this hook. 
             from datasette import hookimpl, Permission


@hookimpl
def register_permissions(datasette):
    return [
        Permission(
            name="upload-csvs",
            abbr=None,
            description="Upload CSV files",
            takes_database=True,
            takes_resource=False,
            default=False,
        )
    ] 
             The fields of the  Permission  class are as follows: 
             
                 
                     name  - string 
                     
                         The name of the permission, e.g.  upload-csvs . This should be unique across all plugins that the user might have installed, so choose carefully. 
                     
                 
                 
                     abbr  - string or None 
                     
                         An abbreviation of the permission, e.g.  uc . This is optional - you can set it to  None  if you do not want to pick an abbreviation. Since this needs to be unique across all installed plugins it's best not to specify an abbreviation at all. If an abbreviation is provided it will be used when creating restricted signed API tokens. 
                     
                 
                 
                     description  - string or None 
                     
                         A human-readable description of what the permission lets you do. Should make sense as the second part of a sentence that starts "A user with this permission can ...". 
                     
                 
                 
                     takes_database  - boolean 
                     
                         True  if this permission can be granted on a per-database basis,  False  if it is only valid at the overall Datasette instance level. 
                     
                 
                 
                     takes_resource  - boolean 
                     
                         True  if this permission can be granted on a per-resource basis. A resource is a database table, SQL view or  canned query . 
                     
                 
                 
                     default  - boolean 
                     
                         The default value for this permission if it is not explicitly granted to a user.  True  means the permission is granted by default,  False  means it is not. 
                         This should only be  True  if you want anonymous users to be able to take this action.


id: plugin_hooks:plugin-asgi-wrapper
page: plugin_hooks
ref: plugin-asgi-wrapper
title: asgi_wrapper(datasette)
breadcrumbs: ["Plugin hooks"]
content:
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

references:
[{"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"}]


id: plugin_hooks:plugin-hook-startup
page: plugin_hooks
ref: plugin-hook-startup
title: startup(datasette)
breadcrumbs: ["Plugin hooks"]
content:
This hook fires when the Datasette application server first starts up. 
             Here is an example that validates required plugin configuration. The server will fail to start and show an error if the validation check fails: 
             @hookimpl
def startup(datasette):
    config = datasette.plugin_config("my-plugin") or {}
    assert (
        "required-setting" in config
    ), "my-plugin requires setting required-setting" 
             You can also return an async function, which will be awaited on startup. Use this option if you need to execute any database queries, for example this function which creates the  my_table  database table if it does not yet exist: 
             @hookimpl
def startup(datasette):
    async def inner():
        db = datasette.get_database()
        if "my_table" not in await db.table_names():
            await db.execute_write(
                """
                create table my_table (mycol text)
            """
            )

    return inner 
             Potential use-cases: 
             
                 
                     Run some initialization code for the plugin 
                 
                 
                     Create database tables that a plugin needs on startup 
                 
                 
                     Validate the configuration for a plugin on startup, and raise an error if it is invalid 
                 
             
             
                 If you are writing  unit tests  for a plugin that uses this hook and doesn't exercise Datasette by sending
                    any simulated requests through it you will need to explicitly call  await ds.invoke_startup()  in your tests. An example: 
                 @pytest.mark.asyncio
async def test_my_plugin():
    ds = Datasette()
    await ds.invoke_startup()
    # Rest of test goes here 
             
             Examples:  datasette-saved-queries ,  datasette-init

references:
[{"href": "https://datasette.io/plugins/datasette-saved-queries", "label": "datasette-saved-queries"}, {"href": "https://datasette.io/plugins/datasette-init", "label": "datasette-init"}]


id: plugin_hooks:plugin-hook-canned-queries
page: plugin_hooks
ref: plugin-hook-canned-queries
title: canned_queries(datasette, database, actor)
breadcrumbs: ["Plugin hooks"]
content:
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

references:
[{"href": "https://datasette.io/plugins/datasette-saved-queries", "label": "datasette-saved-queries"}]


id: plugin_hooks:plugin-hook-actor-from-request
page: plugin_hooks
ref: plugin-hook-actor-from-request
title: actor_from_request(datasette, request)
breadcrumbs: ["Plugin hooks"]
content:
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 
             Examples:  datasette-auth-tokens ,  datasette-auth-passwords

references:
[{"href": "https://datasette.io/plugins/datasette-auth-tokens", "label": "datasette-auth-tokens"}, {"href": "https://datasette.io/plugins/datasette-auth-passwords", "label": "datasette-auth-passwords"}]


id: plugin_hooks:plugin-hook-actors-from-ids
page: plugin_hooks
ref: plugin-hook-actors-from-ids
title: actors_from_ids(datasette, actor_ids)
breadcrumbs: ["Plugin hooks"]
content:
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_ids  - list of strings or integers 
                     
                         The actor IDs to look up. 
                     
                 
             
             The hook must return a dictionary that maps the incoming actor IDs to their full dictionary representation. 
             Some plugins that implement social features may store the ID of the  actor  that performed an action - added a comment, bookmarked a table or similar - and then need a way to resolve those IDs into display-friendly actor dictionaries later on. 
             The  await datasette.actors_from_ids(actor_ids)  internal method can be used to look up actors from their IDs. It will dispatch to the first plugin that implements this hook. 
             Unlike other plugin hooks, this only uses the first implementation of the hook to return a result. You can expect users to only have a single plugin installed that implements this hook. 
             If no plugin is installed, Datasette defaults to returning actors that are just  {"id": actor_id} . 
             The hook can return a dictionary or an awaitable function that then returns a dictionary. 
             This example implementation returns actors from a database table: 
             from datasette import hookimpl


@hookimpl
def actors_from_ids(datasette, actor_ids):
    db = datasette.get_database("actors")

    async def inner():
        sql = "select id, name from actors where id in ({})".format(
            ", ".join("?" for _ in actor_ids)
        )
        actors = {}
        for row in (await db.execute(sql, actor_ids)).rows:
            actor = dict(row)
            actors[actor["id"]] = actor
        return actors

    return inner 
             The returned dictionary from this example looks like this: 
             {
    "1": {"id": "1", "name": "Tony"},
    "2": {"id": "2", "name": "Tina"},
} 
             These IDs could be integers or strings, depending on how the actors used by the Datasette instance are configured. 
             Example:  datasette-remote-actors

references:
[{"href": "https://github.com/datasette/datasette-remote-actors", "label": "datasette-remote-actors"}]


id: plugin_hooks:plugin-hook-jinja2-environment-from-request
page: plugin_hooks
ref: plugin-hook-jinja2-environment-from-request
title: jinja2_environment_from_request(datasette, request, env)
breadcrumbs: ["Plugin hooks"]
content:
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.

references:
[{"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"}]


id: plugin_hooks:plugin-hook-filters-from-request
page: plugin_hooks
ref: plugin-hook-filters-from-request
title: filters_from_request(request, database, table, datasette)
breadcrumbs: ["Plugin hooks"]
content:
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  <title> . 
                     
                 
                 
                     extra_context  - dictionary, optional 
                     
                         Additional context variables that should be made available to the  table.html  template when it is rendered. 
                     
                 
             
             This example plugin causes 0 results to be returned if  ?_nothing=1  is added to the URL: 
             from datasette import hookimpl
from datasette.filters import FilterArguments


@hookimpl
def filters_from_request(self, request):
    if request.args.get("_nothing"):
        return FilterArguments(
            ["1 = 0"], human_descriptions=["NOTHING"]
        ) 
             Example:  datasette-leaflet-freedraw

references:
[{"href": "https://datasette.io/plugins/datasette-leaflet-freedraw", "label": "datasette-leaflet-freedraw"}]


id: plugin_hooks:plugin-hook-permission-allowed
page: plugin_hooks
ref: plugin-hook-permission-allowed
title: permission_allowed(datasette, actor, action, resource)
breadcrumbs: ["Plugin hooks"]
content:
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 
                     
                         The current actor, as decided by  actor_from_request(datasette, request) . 
                     
                 
                 
                     action  - string 
                     
                         The action to be performed, e.g.  "edit-table" . 
                     
                 
                 
                     resource  - string or None 
                     
                         An identifier for the individual resource, e.g. the name of the table. 
                     
                 
             
             Called to check that an actor has permission to perform an action on a resource. Can return  True  if the action is allowed,  False  if the action is not allowed or  None  if the plugin does not have an opinion one way or the other. 
             Here's an example plugin which randomly selects if a permission should be allowed or denied, except for  view-instance  which always uses the default permission scheme instead. 
             from datasette import hookimpl
import random


@hookimpl
def permission_allowed(action):
    if action != "view-instance":
        # Return True or False at random
        return random.random() > 0.5
    # Returning None falls back to default permissions 
             This function can alternatively return an awaitable function which itself returns  True ,  False  or  None . You can use this option if you need to execute additional database queries using  await datasette.execute(...) . 
             Here's an example that allows users to view the  admin_log  table only if their actor  id  is present in the  admin_users  table. It aso disallows arbitrary SQL queries for the  staff.db  database for all users. 
             @hookimpl
def permission_allowed(datasette, actor, action, resource):
    async def inner():
        if action == "execute-sql" and resource == "staff":
            return False
        if action == "view-table" and resource == (
            "staff",
            "admin_log",
        ):
            if not actor:
                return False
            user_id = actor["id"]
            return await datasette.get_database(
                "staff"
            ).execute(
                "select count(*) from admin_users where user_id = :user_id",
                {"user_id": user_id},
            )

    return inner 
             See  built-in permissions  for a full list of permissions that are included in Datasette core. 
             Example:  datasette-permissions-sql

references:
[{"href": "https://datasette.io/plugins/datasette-permissions-sql", "label": "datasette-permissions-sql"}]


id: plugin_hooks:plugin-hook-register-magic-parameters
page: plugin_hooks
ref: plugin-hook-register-magic-parameters
title: register_magic_parameters(datasette)
breadcrumbs: ["Plugin hooks"]
references: []
content:
datasette  -  Datasette class 
                     
                         You can use this to access plugin configuration options via  datasette.plugin_config(your_plugin_name) . 
                     
                 
             
             Magic parameters  can be used to add automatic parameters to  canned queries . This plugin hook allows additional magic parameters to be defined by plugins. 
             Magic parameters all take this format:  _prefix_rest_of_parameter . The prefix indicates which magic parameter function should be called - the rest of the parameter is passed as an argument to that function. 
             To register a new function, return it as a tuple of  (string prefix, function)  from this hook. The function you register should take two arguments:  key  and  request , where  key  is the  rest_of_parameter  portion of the parameter and  request  is the current  Request object . 
             This example registers two new magic parameters:  :_request_http_version  returning the HTTP version of the current request, and  :_uuid_new  which returns a new UUID: 
             from datasette import hookimpl
from uuid import uuid4


def uuid(key, request):
    if key == "new":
        return str(uuid4())
    else:
        raise KeyError


def request(key, request):
    if key == "http_version":
        return request.scope["http_version"]
    else:
        raise KeyError


@hookimpl
def register_magic_parameters(datasette):
    return [
        ("request", request),
        ("uuid", uuid),
    ]


id: plugin_hooks:plugin-hook-forbidden
page: plugin_hooks
ref: plugin-hook-forbidden
title: forbidden(datasette, request, message)
breadcrumbs: ["Plugin hooks"]
references: []
content:
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. 
                     
                 
                 
                     message  - string 
                     
                         A message hinting at why the request was forbidden. 
                     
                 
             
             Plugins can use this to customize how Datasette responds when a 403 Forbidden error occurs - usually because a page failed a permission check, see  Permissions . 
             If a plugin hook wishes to react to the error, it should return a  Response object . 
             This example returns a redirect to a  /-/login  page: 
             from datasette import hookimpl
from urllib.parse import urlencode


@hookimpl
def forbidden(request, message):
    return Response.redirect(
        "/-/login?=" + urlencode({"message": message})
    ) 
             The function can alternatively return an awaitable function if it needs to make any asynchronous method calls. This example renders a template: 
             from datasette import hookimpl, Response


@hookimpl
def forbidden(datasette):
    async def inner():
        return Response.html(
            await datasette.render_template(
                "render_message.html", request=request
            )
        )

    return inner


id: plugin_hooks:plugin-hook-handle-exception
page: plugin_hooks
ref: plugin-hook-handle-exception
title: handle_exception(datasette, request, exception)
breadcrumbs: ["Plugin hooks"]
content:
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

references:
[{"href": "https://sentry.io/", "label": "Sentry"}, {"href": "https://datasette.io/plugins/datasette-sentry", "label": "datasette-sentry"}]


id: plugin_hooks:plugin-hook-skip-csrf
page: plugin_hooks
ref: plugin-hook-skip-csrf
title: skip_csrf(datasette, scope)
breadcrumbs: ["Plugin hooks"]
content:
datasette  -  Datasette class 
                     
                         You can use this to access plugin configuration options via  datasette.plugin_config(your_plugin_name) , or to execute SQL queries. 
                     
                 
                 
                     scope  - dictionary 
                     
                         The  ASGI scope  for the incoming HTTP request. 
                     
                 
             
             This hook can be used to skip  CSRF protection  for a specific incoming request. For example, you might have a custom path at  /submit-comment  which is designed to accept comments from anywhere, whether or not the incoming request originated on the site and has an accompanying CSRF token. 
             This example will disable CSRF protection for that specific URL path: 
             from datasette import hookimpl


@hookimpl
def skip_csrf(scope):
    return scope["path"] == "/submit-comment" 
             If any of the currently active  skip_csrf()  plugin hooks return  True , CSRF protection will be skipped for the request.

references:
[{"href": "https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope", "label": "ASGI scope"}]


id: plugin_hooks:plugin-hook-get-metadata
page: plugin_hooks
ref: plugin-hook-get-metadata
title: get_metadata(datasette, key, database, table)
breadcrumbs: ["Plugin hooks"]
content:
datasette  -  Datasette class 
                     
                         You can use this to access plugin configuration options via  datasette.plugin_config(your_plugin_name) . 
                     
                 
                 
                     actor  - dictionary or None 
                     
                         The currently authenticated  actor . 
                     
                 
                 
                     database  - string or None 
                     
                         The name of the database metadata is being asked for. 
                     
                 
                 
                     table  - string or None 
                     
                         The name of the table. 
                     
                 
                 
                     key  - string or None 
                     
                         The name of the key for which data is being asked for. 
                     
                 
             
             This hook is responsible for returning a dictionary corresponding to Datasette  Metadata . This function is passed the  database ,  table  and  key  which were passed to the upstream internal request for metadata. Regardless, it is important to return a global metadata object, where  "databases": []  would be a top-level key. The dictionary returned here, will be merged with, and overwritten by, the contents of the physical  metadata.yaml  if one is present. 
             
                 The design of this plugin hook does not currently provide a mechanism for interacting with async code, and may change in the future. See  issue 1384 . 
             
             @hookimpl
def get_metadata(datasette, key, database, table):
    metadata = {
        "title": "This will be the Datasette landing page title!",
        "description": get_instance_description(datasette),
        "databases": [],
    }
    for db_name, db_data_dict in get_my_database_meta(
        datasette, database, table, key
    ):
        metadata["databases"][db_name] = db_data_dict
    # whatever we return here will be merged with any other plugins using this hook and
    # will be overwritten by a local metadata.yaml if one exists!
    return metadata 
             Example:  datasette-remote-metadata plugin

references:
[{"href": "https://github.com/simonw/datasette/issues/1384", "label": "issue 1384"}, {"href": "https://datasette.io/plugins/datasette-remote-metadata", "label": "datasette-remote-metadata plugin"}]


id: plugin_hooks:plugin-hook-menu-links
page: plugin_hooks
ref: plugin-hook-menu-links
title: menu_links(datasette, actor, request)
breadcrumbs: ["Plugin hooks"]
content:
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 . 
                     
                 
                 
                     request  -  Request object  or None 
                     
                         The current HTTP request. This can be  None  if the request object is not available. 
                     
                 
             
             This hook allows additional items to be included in the menu displayed by Datasette's top right menu icon. 
             The hook should return a list of  {"href": "...", "label": "..."}  menu items. These will be added to the menu. 
             It can alternatively return an  async def  awaitable function which returns a list of menu items. 
             This example adds a new menu item but only if the signed in user is  "root" : 
             from datasette import hookimpl


@hookimpl
def menu_links(datasette, actor):
    if actor and actor.get("id") == "root":
        return [
            {
                "href": datasette.urls.path(
                    "/-/edit-schema"
                ),
                "label": "Edit schema",
            },
        ] 
             Using  datasette.urls  here ensures that links in the menu will take the  base_url  setting into account. 
             Examples:  datasette-search-all ,  datasette-graphql

references:
[{"href": "https://datasette.io/plugins/datasette-search-all", "label": "datasette-search-all"}, {"href": "https://datasette.io/plugins/datasette-graphql", "label": "datasette-graphql"}]


id: plugin_hooks:plugin-actions
page: plugin_hooks
ref: plugin-actions
title: Action hooks
breadcrumbs: ["Plugin hooks"]
references: []
content:
Action hooks can be used to add items to the action menus that appear at the top of different pages within Datasette. Unlike  menu_links() , actions which are displayed on every page, actions should only be relevant to the page the user is currently viewing. 
             Each of these hooks should return return a list of  {"href": "...", "label": "..."}  menu items, with optional  "description": "..."  keys describing each action in more detail. 
             They can alternatively return an  async def  awaitable function which, when called, returns a list of those menu items.


id: plugin_hooks:plugin-hook-table-actions
page: plugin_hooks
ref: plugin-hook-table-actions
title: table_actions(datasette, actor, database, table, request)
breadcrumbs: ["Plugin hooks", "Action hooks"]
references: [{"href": "https://datasette.io/plugins/datasette-graphql", "label": "datasette-graphql"}]
content:
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. 
                         
                     
                     
                         table  - string 
                         
                             The name of the table. 
                         
                     
                     
                         request  -  Request object  or None 
                         
                             The current HTTP request. This can be  None  if the request object is not available. 
                         
                     
                 
                 This example adds a new table action if the signed in user is  "root" : 
                 from datasette import hookimpl


@hookimpl
def table_actions(datasette, actor, database, table):
    if actor and actor.get("id") == "root":
        return [
            {
                "href": datasette.urls.path(
                    "/-/edit-schema/{}/{}".format(
                        database, table
                    )
                ),
                "label": "Edit schema for this table",
                "description": "Add, remove, rename or alter columns for this table.",
            }
        ] 
                 Example:  datasette-graphql


id: plugin_hooks:plugin-hook-view-actions
page: plugin_hooks
ref: plugin-hook-view-actions
title: view_actions(datasette, actor, database, view, request)
breadcrumbs: ["Plugin hooks", "Action hooks"]
references: []
content:
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. 
                         
                     
                     
                         view  - string 
                         
                             The name of the SQL view. 
                         
                     
                     
                         request  -  Request object  or None 
                         
                             The current HTTP request. This can be  None  if the request object is not available. 
                         
                     
                 
                 Like  table_actions(datasette, actor, database, table, request)  but for SQL views.


id: plugin_hooks:plugin-hook-query-actions
page: plugin_hooks
ref: plugin-hook-query-actions
title: query_actions(datasette, actor, database, query_name, request, sql, params)
breadcrumbs: ["Plugin hooks", "Action hooks"]
references: [{"href": "https://datasette.io/plugins/datasette-create-view", "label": "datasette-create-view"}]
content:
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. 
                         
                     
                     
                         query_name  - string or None 
                         
                             The name of the canned query, or  None  if this is an arbitrary SQL query. 
                         
                     
                     
                         request  -  Request object 
                         
                             The current HTTP request. 
                         
                     
                     
                         sql  - string 
                         
                             The SQL query being executed 
                         
                     
                     
                         params  - dictionary 
                         
                             The parameters passed to the SQL query, if any. 
                         
                     
                 
                 Populates a "Query actions" menu on the canned query and arbitrary SQL query pages. 
                 This example adds a new query action linking to a page for explaining a query: 
                 from datasette import hookimpl
import urllib


@hookimpl
def query_actions(datasette, database, query_name, sql):
    # Don't explain an explain
    if sql.lower().startswith("explain"):
        return
    return [
        {
            "href": datasette.urls.database(database)
            + "?"
            + urllib.parse.urlencode(
                {
                    "sql": "explain " + sql,
                }
            ),
            "label": "Explain this query",
            "description": "Get a summary of how SQLite executes the query",
        },
    ] 
                 Example:  datasette-create-view


id: plugin_hooks:plugin-hook-row-actions
page: plugin_hooks
ref: plugin-hook-row-actions
title: row_actions(datasette, actor, request, database, table, row)
breadcrumbs: ["Plugin hooks", "Action hooks"]
references: [{"href": "https://datasette.io/plugins/datasette-enrichments", "label": "datasette-enrichments"}]
content:
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 . 
                         
                     
                     
                         request  -  Request object  or None 
                         
                             The current HTTP request. 
                         
                     
                     
                         database  - string 
                         
                             The name of the database. 
                         
                     
                     
                         table  - string 
                         
                             The name of the table. 
                         
                     
                     
                         row  -  sqlite.Row 
                         
                             The SQLite row object being displayed on the page. 
                         
                     
                 
                 Return links for the "Row actions" menu shown at the top of the row page. 
                 This example displays the row in JSON plus some additional debug information if the user is signed in: 
                 from datasette import hookimpl


@hookimpl
def row_actions(datasette, database, table, actor, row):
    if actor:
        return [
            {
                "href": datasette.urls.instance(),
                "label": f"Row details for {actor['id']}",
                "description": json.dumps(
                    dict(row), default=repr
                ),
            },
        ] 
                 Example:  datasette-enrichments


id: plugin_hooks:plugin-hook-database-actions
page: plugin_hooks
ref: plugin-hook-database-actions
title: database_actions(datasette, actor, database, request)
breadcrumbs: ["Plugin hooks", "Action hooks"]
content:
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. 
                         
                     
                 
                 Populates an actions menu on the database page. 
                 This example adds a new database action for creating a table, if the user has the  edit-schema  permission: 
                 from datasette import hookimpl


@hookimpl
def database_actions(datasette, actor, database):
    async def inner():
        if not await datasette.permission_allowed(
            actor,
            "edit-schema",
            resource=database,
            default=False,
        ):
            return []
        return [
            {
                "href": datasette.urls.path(
                    "/-/edit-schema/{}/-/create".format(
                        database
                    )
                ),
                "label": "Create a table",
            }
        ]

    return inner 
                 Example:  datasette-graphql ,  datasette-edit-schema

references:
[{"href": "https://datasette.io/plugins/datasette-graphql", "label": "datasette-graphql"}, {"href": "https://datasette.io/plugins/datasette-edit-schema", "label": "datasette-edit-schema"}]


id: plugin_hooks:plugin-hook-homepage-actions
page: plugin_hooks
ref: plugin-hook-homepage-actions
title: homepage_actions(datasette, actor, request)
breadcrumbs: ["Plugin hooks", "Action hooks"]
references: []
content:
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 . 
                         
                     
                     
                         request  -  Request object 
                         
                             The current HTTP request. 
                         
                     
                 
                 Populates an actions menu on the top-level index homepage of the Datasette instance. 
                 This example adds a link an imagined tool for editing the homepage, only for signed in users: 
                 from datasette import hookimpl


@hookimpl
def homepage_actions(datasette, actor):
    if actor:
        return [
            {
                "href": datasette.urls.path(
                    "/-/customize-homepage"
                ),
                "label": "Customize homepage",
            }
        ]


id: plugin_hooks:plugin-hook-slots
page: plugin_hooks
ref: plugin-hook-slots
title: Template slots
breadcrumbs: ["Plugin hooks"]
content:
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  <h1>  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.

references:
[{"href": "https://pluggy.readthedocs.io/en/stable/#call-time-order", "label": "call time order options"}]


id: plugin_hooks:plugin-hook-top-homepage
page: plugin_hooks
ref: plugin-hook-top-homepage
title: top_homepage(datasette, request)
breadcrumbs: ["Plugin hooks", "Template slots"]
references: []
content:
datasette  -  Datasette class 
                         
                             You can use this to access plugin configuration options via  datasette.plugin_config(your_plugin_name) . 
                         
                     
                     
                         request  -  Request object 
                         
                             The current HTTP request. 
                         
                     
                 
                 Returns HTML to be displayed at the top of the Datasette homepage.


id: plugin_hooks:plugin-hook-top-database
page: plugin_hooks
ref: plugin-hook-top-database
title: top_database(datasette, request, database)
breadcrumbs: ["Plugin hooks", "Template slots"]
references: []
content:
datasette  -  Datasette class 
                         
                             You can use this to access plugin configuration options via  datasette.plugin_config(your_plugin_name) . 
                         
                     
                     
                         request  -  Request object 
                         
                             The current HTTP request. 
                         
                     
                     
                         database  - string 
                         
                             The name of the database. 
                         
                     
                 
                 Returns HTML to be displayed at the top of the database page.


id: plugin_hooks:plugin-hook-top-table
page: plugin_hooks
ref: plugin-hook-top-table
title: top_table(datasette, request, database, table)
breadcrumbs: ["Plugin hooks", "Template slots"]
references: []
content:
datasette  -  Datasette class 
                         
                             You can use this to access plugin configuration options via  datasette.plugin_config(your_plugin_name) . 
                         
                     
                     
                         request  -  Request object 
                         
                             The current HTTP request. 
                         
                     
                     
                         database  - string 
                         
                             The name of the database. 
                         
                     
                     
                         table  - string 
                         
                             The name of the table. 
                         
                     
                 
                 Returns HTML to be displayed at the top of the table page.


id: plugin_hooks:plugin-hook-top-row
page: plugin_hooks
ref: plugin-hook-top-row
title: top_row(datasette, request, database, table, row)
breadcrumbs: ["Plugin hooks", "Template slots"]
references: []
content:
datasette  -  Datasette class 
                         
                             You can use this to access plugin configuration options via  datasette.plugin_config(your_plugin_name) . 
                         
                     
                     
                         request  -  Request object 
                         
                             The current HTTP request. 
                         
                     
                     
                         database  - string 
                         
                             The name of the database. 
                         
                     
                     
                         table  - string 
                         
                             The name of the table. 
                         
                     
                     
                         row  -  sqlite.Row 
                         
                             The SQLite row object being displayed. 
                         
                     
                 
                 Returns HTML to be displayed at the top of the row page.


id: plugin_hooks:plugin-hook-top-query
page: plugin_hooks
ref: plugin-hook-top-query
title: top_query(datasette, request, database, sql)
breadcrumbs: ["Plugin hooks", "Template slots"]
references: []
content:
datasette  -  Datasette class 
                         
                             You can use this to access plugin configuration options via  datasette.plugin_config(your_plugin_name) . 
                         
                     
                     
                         request  -  Request object 
                         
                             The current HTTP request. 
                         
                     
                     
                         database  - string 
                         
                             The name of the database. 
                         
                     
                     
                         sql  - string 
                         
                             The SQL query. 
                         
                     
                 
                 Returns HTML to be displayed at the top of the query results page.


id: plugin_hooks:plugin-hook-top-canned-query
page: plugin_hooks
ref: plugin-hook-top-canned-query
title: top_canned_query(datasette, request, database, query_name)
breadcrumbs: ["Plugin hooks", "Template slots"]
references: []
content:
datasette  -  Datasette class 
                         
                             You can use this to access plugin configuration options via  datasette.plugin_config(your_plugin_name) . 
                         
                     
                     
                         request  -  Request object 
                         
                             The current HTTP request. 
                         
                     
                     
                         database  - string 
                         
                             The name of the database. 
                         
                     
                     
                         query_name  - string 
                         
                             The name of the canned query. 
                         
                     
                 
                 Returns HTML to be displayed at the top of the canned query page.


id: plugin_hooks:plugin-event-tracking
page: plugin_hooks
ref: plugin-event-tracking
title: Event tracking
breadcrumbs: ["Plugin hooks"]
references: []
content:
Datasette includes an internal mechanism for tracking notable events. This can be used for analytics, but can also be used by plugins that want to listen out for when key events occur (such as a table being created) and take action in response. 
             Plugins can register to receive events using the  track_event  plugin hook. 
             They can also define their own events for other plugins to receive using the  register_events() plugin hook , combined with calls to the  datasette.track_event() internal method .


id: plugin_hooks:plugin-hook-track-event
page: plugin_hooks
ref: plugin-hook-track-event
title: track_event(datasette, event)
breadcrumbs: ["Plugin hooks", "Event tracking"]
references: [{"href": "https://datasette.io/plugins/datasette-events-db", "label": "datasette-events-db"}]
content:
datasette  -  Datasette class 
                         
                             You can use this to access plugin configuration options via  datasette.plugin_config(your_plugin_name) . 
                         
                     
                     
                         event  -  Event 
                         
                             Information about the event, represented as an instance of a subclass of the  Event  base class. 
                         
                     
                 
                 This hook will be called any time an event is tracked by code that calls the  datasette.track_event(...)  internal method. 
                 The  event  object will always have the following properties: 
                 
                     
                         name : a string representing the name of the event, for example  logout  or  create-table . 
                     
                     
                         actor : a dictionary representing the actor that triggered the event, or  None  if the event was not triggered by an actor. 
                     
                     
                         created : a  datatime.datetime  object in the  timezone.utc  timezone representing the time the event object was created. 
                     
                 
                 Other properties on the event will be available depending on the type of event. You can also access those as a dictionary using  event.properties() . 
                 The events fired by Datasette core are  documented here . 
                 This example plugin logs details of all events to standard error: 
                 from datasette import hookimpl
import json
import sys


@hookimpl
def track_event(event):
    name = event.name
    actor = event.actor
    properties = event.properties()
    msg = json.dumps(
        {
            "name": name,
            "actor": actor,
            "properties": properties,
        }
    )
    print(msg, file=sys.stderr, flush=True) 
                 The function can also return an async function which will be awaited. This is useful for writing to a database. 
                 This example logs events to a  datasette_events  table in a database called  events . It uses the  startup()  hook to create that table if it does not exist. 
                 from datasette import hookimpl
import json


@hookimpl
def startup(datasette):
    async def inner():
        db = datasette.get_database("events")
        await db.execute_write(
            """
            create table if not exists datasette_events (
                id integer primary key,
                event_type text,
                created text,
                actor text,
                properties text
            )
        """
        )

    return inner


@hookimpl
def track_event(datasette, event):
    async def inner():
        db = datasette.get_database("events")
        properties = event.properties()
        await db.execute_write(
            """
            insert into datasette_events (event_type, created, actor, properties)
            values (?, strftime('%Y-%m-%d %H:%M:%S', 'now'), ?, ?)
        """,
            (
                event.name,
                json.dumps(event.actor),
                json.dumps(properties),
            ),
        )

    return inner 
                 Example:  datasette-events-db


id: plugin_hooks:plugin-hook-register-events
page: plugin_hooks
ref: plugin-hook-register-events
title: register_events(datasette)
breadcrumbs: ["Plugin hooks", "Event tracking"]
references: []
content:
datasette  -  Datasette class 
                         
                             You can use this to access plugin configuration options via  datasette.plugin_config(your_plugin_name) . 
                         
                     
                 
                 This hook should return a list of  Event  subclasses that represent custom events that the plugin might send to the  datasette.track_event()  method. 
                 This example registers event subclasses for  ban-user  and  unban-user  events: 
                 from dataclasses import dataclass
from datasette import hookimpl, Event


@dataclass
class BanUserEvent(Event):
    name = "ban-user"
    user: dict


@dataclass
class UnbanUserEvent(Event):
    name = "unban-user"
    user: dict


@hookimpl
def register_events():
    return [BanUserEvent, UnbanUserEvent] 
                 The plugin can then call  datasette.track_event(...)  to send a  ban-user  event: 
                 await datasette.track_event(
    BanUserEvent(user={"id": 1, "username": "cleverbot"})
)


id: plugins:id1
page: plugins
ref: id1
title: Plugins
breadcrumbs: []
content:
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 . 
         See the  Datasette plugins directory  for a list of existing plugins, or take a look at the
             datasette-plugin  topic on GitHub. 
         Things you can do with plugins include: 
         
             
                 Add visualizations to Datasette, for example
                     datasette-cluster-map  and
                     datasette-vega . 
             
             
                 Make new custom SQL functions available for use within Datasette, for example
                     datasette-haversine  and
                     datasette-jellyfish . 
             
             
                 Define custom output formats with custom extensions, for example  datasette-atom  and
                     datasette-ics . 
             
             
                 Add template functions that can be called within your Jinja custom templates,
                    for example  datasette-render-markdown . 
             
             
                 Customize how database values are rendered in the Datasette interface, for example
                     datasette-render-binary  and
                     datasette-pretty-json . 
             
             
                 Customize how Datasette's authentication and permissions systems work, for example  datasette-auth-passwords  and
                     datasette-permissions-sql .

references:
[{"href": "https://pluggy.readthedocs.io/", "label": "pluggy"}, {"href": "https://datasette.io/plugins", "label": "Datasette plugins directory"}, {"href": "https://github.com/topics/datasette-plugin", "label": "datasette-plugin"}, {"href": "https://github.com/simonw/datasette-cluster-map", "label": "datasette-cluster-map"}, {"href": "https://github.com/simonw/datasette-vega", "label": "datasette-vega"}, {"href": "https://github.com/simonw/datasette-haversine", "label": "datasette-haversine"}, {"href": "https://github.com/simonw/datasette-jellyfish", "label": "datasette-jellyfish"}, {"href": "https://github.com/simonw/datasette-atom", "label": "datasette-atom"}, {"href": "https://github.com/simonw/datasette-ics", "label": "datasette-ics"}, {"href": "https://github.com/simonw/datasette-render-markdown#markdown-in-templates", "label": "datasette-render-markdown"}, {"href": "https://github.com/simonw/datasette-render-binary", "label": "datasette-render-binary"}, {"href": "https://github.com/simonw/datasette-pretty-json", "label": "datasette-pretty-json"}, {"href": "https://github.com/simonw/datasette-auth-passwords", "label": "datasette-auth-passwords"}, {"href": "https://github.com/simonw/datasette-permissions-sql", "label": "datasette-permissions-sql"}]


id: plugins:plugins-installing
page: plugins
ref: plugins-installing
title: Installing plugins
breadcrumbs: ["Plugins"]
references: []
content:
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. 
             You can install plugins using the  datasette install  command: 
             datasette install datasette-vega 
             You can uninstall plugins with  datasette uninstall : 
             datasette uninstall datasette-vega 
             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 
             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.


id: plugins:one-off-plugins-using-plugins-dir
page: plugins
ref: one-off-plugins-using-plugins-dir
title: One-off plugins using --plugins-dir
breadcrumbs: ["Plugins", "Installing plugins"]
references: []
content:
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/


id: plugins:deploying-plugins-using-datasette-publish
page: plugins
ref: deploying-plugins-using-datasette-publish
title: Deploying plugins using datasette publish
breadcrumbs: ["Plugins", "Installing plugins"]
references: []
content:
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


id: plugins:plugins-datasette-load-plugins
page: plugins
ref: plugins-datasette-load-plugins
title: Controlling which plugins are loaded
breadcrumbs: ["Plugins"]
references: []
content:
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 : 
             export DATASETTE_LOAD_PLUGINS='datasette-vega,datasette-cluster-map'
datasette mydb.db 
             Or: 
             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: 
             export DATASETTE_LOAD_PLUGINS=''
datasette mydb.db 
             A quick way to test this setting is to use it with the  datasette plugins  command: 
             DATASETTE_LOAD_PLUGINS='datasette-vega' datasette plugins 
             This should output the following: 
             [
    {
        "name": "datasette-vega",
        "static": true,
        "templates": false,
        "version": "0.6.2",
        "hooks": [
            "extra_css_urls",
            "extra_js_urls"
        ]
    }
]


id: plugins:plugins-installed
page: plugins
ref: plugins-installed
title: Seeing what plugins are installed
breadcrumbs: ["Plugins"]
content:
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: 
             datasette plugins 
             Which outputs: 
             [
    {
        "name": "datasette_json_html",
        "static": false,
        "templates": false,
        "version": "0.4.0"
    }
] 
             [[[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
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")
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") 
             ]]] 
             If you run  datasette plugins --all  it will include default plugins that ship as part of Datasette: 
             [
    {
        "name": "datasette.actor_auth_cookie",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "actor_from_request"
        ]
    },
    {
        "name": "datasette.blob_renderer",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "register_output_renderer"
        ]
    },
    {
        "name": "datasette.default_magic_parameters",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "register_magic_parameters"
        ]
    },
    {
        "name": "datasette.default_menu_links",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "menu_links"
        ]
    },
    {
        "name": "datasette.default_permissions",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "actor_from_request",
            "permission_allowed",
            "register_permissions",
            "skip_csrf"
        ]
    },
    {
        "name": "datasette.events",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "register_events"
        ]
    },
    {
        "name": "datasette.facets",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "register_facet_classes"
        ]
    },
    {
        "name": "datasette.filters",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "filters_from_request"
        ]
    },
    {
        "name": "datasette.forbidden",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "forbidden"
        ]
    },
    {
        "name": "datasette.handle_exception",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "handle_exception"
        ]
    },
    {
        "name": "datasette.publish.cloudrun",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "publish_subcommand"
        ]
    },
    {
        "name": "datasette.publish.heroku",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "publish_subcommand"
        ]
    },
    {
        "name": "datasette.sql_functions",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "prepare_connection"
        ]
    }
] 
             [[[end]]] 
             You can add the  --plugins-dir=  option to include any plugins found in that directory. 
             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

references:
[{"href": "https://fivethirtyeight.datasettes.com/-/plugins", "label": "https://fivethirtyeight.datasettes.com/-/plugins"}]


id: plugins:plugins-configuration
page: plugins
ref: plugins-configuration
title: Plugin configuration
breadcrumbs: ["Plugins"]
references: []
content:
Plugins can have their own configuration, embedded in a  configuration file . Configuration options for plugins live within a  "plugins"  key in that file, which can be included at the root, database or table level. 
             Here is an example of some plugin configuration for a specific table: 
             [[[cog
from metadata_doc import config_example
config_example(cog, {
    "databases": {
        "sf-trees": {
            "tables": {
                "Street_Tree_List": {
                    "plugins": {
                        "datasette-cluster-map": {
                            "latitude_column": "lat",
                            "longitude_column": "lng"
                        }
                    }
                }
            }
        }
    }
}) 
             ]]] 
             [[[end]]] 
             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 .


id: plugins:plugins-configuration-secret
page: plugins
ref: plugins-configuration-secret
title: Secret configuration values
breadcrumbs: ["Plugins", "Plugin configuration"]
references: []
content:
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. 
                 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: 
                 [[[cog
config_example(cog, {
    "plugins": {
        "datasette-auth-github": {
            "client_secret": {
                "$env": "GITHUB_CLIENT_SECRET"
            }
        }
    }
}) 
                 ]]] 
                 [[[end]]] 
                 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: 
                 [[[cog
config_example(cog, {
    "plugins": {
        "datasette-auth-github": {
            "client_secret": {
                "$file": "/secrets/client-secret"
            }
        }
    }
}) 
                 ]]] 
                 [[[end]]] 
                 If you are publishing your data using the  datasette 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: 
                 datasette publish heroku my_database.db \
    --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 
                 This will set the necessary environment variables and add the following to the deployed  metadata.yaml : 
                 [[[cog
config_example(cog, {
    "plugins": {
        "datasette-auth-github": {
            "client_id": {
                "$env": "DATASETTE_AUTH_GITHUB_CLIENT_ID"
            },
            "client_secret": {
                "$env": "DATASETTE_AUTH_GITHUB_CLIENT_SECRET"
            }
        }
    }
}) 
                 ]]] 
                 [[[end]]]


id: full_text_search:id1
page: full_text_search
ref: id1
title: Full-text search
breadcrumbs: []
content:
SQLite includes  a powerful mechanism for enabling full-text search  against SQLite records. Datasette can detect if a table has had full-text search configured for it in the underlying database and display a search interface for filtering that table. 
         Here's  an example search : 
         
         Datasette automatically detects which tables have been configured for full-text search.

references:
[{"href": "https://www.sqlite.org/fts3.html", "label": "a powerful mechanism for enabling full-text search"}, {"href": "https://register-of-members-interests.datasettes.com/regmem/items?_search=hamper&_sort_desc=date", "label": "an example search"}]


id: full_text_search:full-text-search-table-view-api
page: full_text_search
ref: full-text-search-table-view-api
title: The table page and table view API
breadcrumbs: ["Full-text search"]
content:
Table views that support full-text search can be queried using the  ?_search=TERMS  query string parameter. This will run the search against content from all of the columns that have been included in the index. 
             Try this example:  fara.datasettes.com/fara/FARA_All_ShortForms?_search=manafort 
             SQLite full-text search supports wildcards. This means you can easily implement prefix auto-complete by including an asterisk at the end of the search term - for example: 
             /dbname/tablename/?_search=rob* 
             This will return all records containing at least one word that starts with the letters  rob . 
             You can also run searches against just the content of a specific named column by using  _search_COLNAME=TERMS  - for example, this would search for just rows where the  name  column in the FTS index mentions  Sarah : 
             /dbname/tablename/?_search_name=Sarah

references:
[{"href": "https://fara.datasettes.com/fara/FARA_All_ShortForms?_search=manafort", "label": "fara.datasettes.com/fara/FARA_All_ShortForms?_search=manafort"}]


id: full_text_search:full-text-search-advanced-queries
page: full_text_search
ref: full-text-search-advanced-queries
title: Advanced SQLite search queries
breadcrumbs: ["Full-text search"]
content:
SQLite full-text search includes support for  a variety of advanced queries , including  AND ,  OR ,  NOT  and  NEAR . 
             By default Datasette disables these features to ensure they do not cause errors or confusion for users who are not aware of them. You can disable this escaping and use the advanced queries by adding  &_searchmode=raw  to the table page query string. 
             If you want to enable these operators by default for a specific table, you can do so by adding  "searchmode": "raw"  to the metadata configuration for that table, see  Configuring full-text search for a table or view . 
             If that option has been specified in the table metadata but you want to over-ride it and return to the default behavior you can append  &_searchmode=escaped  to the query string.

references:
[{"href": "https://www.sqlite.org/fts5.html#full_text_query_syntax", "label": "a variety of advanced queries"}]


id: full_text_search:full-text-search-table-or-view
page: full_text_search
ref: full-text-search-table-or-view
title: Configuring full-text search for a table or view
breadcrumbs: ["Full-text search"]
content:
If a table has a corresponding FTS table set up using the  content=  argument to  CREATE VIRTUAL TABLE  shown below, Datasette will detect it automatically and add a search interface to the table page for that table. 
             You can also manually configure which table should be used for full-text search using query string parameters or  Metadata . You can set the associated FTS table for a specific table and you can also set one for a view - if you do that, the page for that SQL view will offer a search option. 
             Use  ?_fts_table=x  to over-ride the FTS table for a specific page. If the primary key was something other than  rowid  you can use  ?_fts_pk=col  to set that as well. This is particularly useful for views, for example: 
             https://latest.datasette.io/fixtures/searchable_view?_fts_table=searchable_fts&_fts_pk=pk 
             The  fts_table  metadata property can be used to specify an associated FTS table. If the primary key column in your table which was used to populate the FTS table is something other than  rowid , you can specify the column to use with the  fts_pk  property. 
             The  "searchmode": "raw"  property can be used to default the table to accepting SQLite advanced search operators, as described in  Advanced SQLite search queries . 
             Here is an example which enables full-text search (with SQLite advanced search operators) for a  display_ads  view which is defined against the  ads  table and hence needs to run FTS against the  ads_fts  table, using the  id  as the primary key: 
             [[[cog
from metadata_doc import metadata_example
metadata_example(cog, {
    "databases": {
        "russian-ads": {
            "tables": {
                "display_ads": {
                    "fts_table": "ads_fts",
                    "fts_pk": "id",
                    "searchmode": "raw"
                }
            }
        }
    }
}) 
             ]]] 
             [[[end]]]

references:
[{"href": "https://latest.datasette.io/fixtures/searchable_view?_fts_table=searchable_fts&_fts_pk=pk", "label": "https://latest.datasette.io/fixtures/searchable_view?_fts_table=searchable_fts&_fts_pk=pk"}]


id: full_text_search:full-text-search-custom-sql
page: full_text_search
ref: full-text-search-custom-sql
title: Searches using custom SQL
breadcrumbs: ["Full-text search"]
content:
You can include full-text search results in custom SQL queries. The general pattern with SQLite search is to run the search as a sub-select that returns rowid values, then include those rowids in another part of the query. 
             You can see the syntax for a basic search by running that search on a table page and then clicking "View and edit SQL" to see the underlying SQL. For example, consider this search for  manafort is the US FARA database : 
             /fara/FARA_All_ShortForms?_search=manafort 
             If you click  View and edit SQL  you'll see that the underlying SQL looks like this: 
             select
  rowid,
  Short_Form_Termination_Date,
  Short_Form_Date,
  Short_Form_Last_Name,
  Short_Form_First_Name,
  Registration_Number,
  Registration_Date,
  Registrant_Name,
  Address_1,
  Address_2,
  City,
  State,
  Zip
from
  FARA_All_ShortForms
where
  rowid in (
    select
      rowid
    from
      FARA_All_ShortForms_fts
    where
      FARA_All_ShortForms_fts match escape_fts(:search)
  )
order by
  rowid
limit
  101

references:
[{"href": "https://fara.datasettes.com/fara/FARA_All_ShortForms?_search=manafort", "label": "manafort is the US FARA database"}, {"href": "https://fara.datasettes.com/fara?sql=select%0D%0A++rowid%2C%0D%0A++Short_Form_Termination_Date%2C%0D%0A++Short_Form_Date%2C%0D%0A++Short_Form_Last_Name%2C%0D%0A++Short_Form_First_Name%2C%0D%0A++Registration_Number%2C%0D%0A++Registration_Date%2C%0D%0A++Registrant_Name%2C%0D%0A++Address_1%2C%0D%0A++Address_2%2C%0D%0A++City%2C%0D%0A++State%2C%0D%0A++Zip%0D%0Afrom%0D%0A++FARA_All_ShortForms%0D%0Awhere%0D%0A++rowid+in+%28%0D%0A++++select%0D%0A++++++rowid%0D%0A++++from%0D%0A++++++FARA_All_ShortForms_fts%0D%0A++++where%0D%0A++++++FARA_All_ShortForms_fts+match+escape_fts%28%3Asearch%29%0D%0A++%29%0D%0Aorder+by%0D%0A++rowid%0D%0Alimit%0D%0A++101&search=manafort", "label": "View and edit SQL"}]


id: full_text_search:full-text-search-enabling
page: full_text_search
ref: full-text-search-enabling
title: Enabling full-text search for a SQLite table
breadcrumbs: ["Full-text search"]
content:
Datasette takes advantage of the  external content  mechanism in SQLite, which allows a full-text search virtual table to be associated with the contents of another SQLite table. 
             To set up full-text search for a table, you need to do two things: 
             
                 
                     Create a new FTS virtual table associated with your table 
                 
                 
                     Populate that FTS table with the data that you would like to be able to run searches against

references:
[{"href": "https://www.sqlite.org/fts3.html#_external_content_fts4_tables_", "label": "external content"}]


id: full_text_search:configuring-fts-using-sqlite-utils
page: full_text_search
ref: configuring-fts-using-sqlite-utils
title: Configuring FTS using sqlite-utils
breadcrumbs: ["Full-text search", "Enabling full-text search for a SQLite table"]
content:
sqlite-utils  is a CLI utility and Python library for manipulating SQLite databases. You can use  it from Python code  to configure FTS search, or you can achieve the same goal  using the accompanying command-line tool . 
                 Here's how to use  sqlite-utils  to enable full-text search for an  items  table across the  name  and  description  columns: 
                 sqlite-utils enable-fts mydatabase.db items name description

references:
[{"href": "https://sqlite-utils.datasette.io/", "label": "sqlite-utils"}, {"href": "https://sqlite-utils.datasette.io/en/latest/python-api.html#enabling-full-text-search", "label": "it from Python code"}, {"href": "https://sqlite-utils.datasette.io/en/latest/cli.html#configuring-full-text-search", "label": "using the accompanying command-line tool"}]


id: full_text_search:configuring-fts-using-csvs-to-sqlite
page: full_text_search
ref: configuring-fts-using-csvs-to-sqlite
title: Configuring FTS using csvs-to-sqlite
breadcrumbs: ["Full-text search", "Enabling full-text search for a SQLite table"]
references: [{"href": "https://github.com/simonw/csvs-to-sqlite", "label": "csvs-to-sqlite"}]
content:
If your data starts out in CSV files, you can use Datasette's companion tool  csvs-to-sqlite  to convert that file into a SQLite database and enable full-text search on specific columns. For a file called  items.csv  where you want full-text search to operate against the  name  and  description  columns you would run the following: 
                 csvs-to-sqlite items.csv items.db -f name -f description


id: full_text_search:configuring-fts-by-hand
page: full_text_search
ref: configuring-fts-by-hand
title: Configuring FTS by hand
breadcrumbs: ["Full-text search", "Enabling full-text search for a SQLite table"]
references: [{"href": "https://sqlite-utils.datasette.io/", "label": "sqlite-utils"}]
content:
We recommend using  sqlite-utils , but if you want to hand-roll a SQLite full-text search table you can do so using the following SQL. 
                 To enable full-text search for a table called  items  that works against the  name  and  description  columns, you would run this SQL to create a new  items_fts  FTS virtual table: 
                 CREATE VIRTUAL TABLE "items_fts" USING FTS4 (
    name,
    description,
    content="items"
); 
                 This creates a set of tables to power full-text search against  items . The new  items_fts  table will be detected by Datasette as the  fts_table  for the  items  table. 
                 Creating the table is not enough: you also need to populate it with a copy of the data that you wish to make searchable. You can do that using the following SQL: 
                 INSERT INTO "items_fts" (rowid, name, description)
    SELECT rowid, name, description FROM items; 
                 If your table has columns that are foreign key references to other tables you can include that data in your full-text search index using a join. Imagine the  items  table has a foreign key column called  category_id  which refers to a  categories  table - you could create a full-text search table like this: 
                 CREATE VIRTUAL TABLE "items_fts" USING FTS4 (
    name,
    description,
    category_name,
    content="items"
); 
                 And then populate it like this: 
                 INSERT INTO "items_fts" (rowid, name, description, category_name)
    SELECT items.rowid,
    items.name,
    items.description,
    categories.name
    FROM items JOIN categories ON items.category_id=categories.id; 
                 You can use this technique to populate the full-text search index from any combination of tables and joins that makes sense for your project.


id: full_text_search:full-text-search-fts-versions
page: full_text_search
ref: full-text-search-fts-versions
title: FTS versions
breadcrumbs: ["Full-text search"]
references: []
content:
There are three different versions of the SQLite FTS module: FTS3, FTS4 and FTS5. You can tell which versions are supported by your instance of Datasette by checking the  /-/versions  page. 
             FTS5 is the most advanced module but may not be available in the SQLite version that is bundled with your Python installation. Most importantly, FTS5 is the only version that has the ability to order by search relevance without needing extra code. 
             If you can't be sure that FTS5 will be available, you should use FTS4.


id: cli-reference:id1
page: cli-reference
ref: id1
title: CLI reference
breadcrumbs: []
references: []
content:
The  datasette  CLI tool provides a number of commands. 
         Running  datasette  without specifying a command runs the default command,  datasette serve .  See  datasette serve  for the full list of options for that command. 
         [[[cog
from datasette import cli
from click.testing import CliRunner
import textwrap
def help(args):
    title = "datasette " + " ".join(args)
    cog.out("\n::\n\n")
    result = CliRunner().invoke(cli.cli, args)
    output = result.output.replace("Usage: cli ", "Usage: datasette ")
    cog.out(textwrap.indent(output, '    '))
    cog.out("\n\n") 
         ]]] 
         [[[end]]]


id: cli-reference:cli-help-help
page: cli-reference
ref: cli-help-help
title: datasette --help
breadcrumbs: ["CLI reference"]
references: []
content:
Running  datasette --help  shows a list of all of the available commands. 
             [[[cog
help(["--help"]) 
             ]]] 
             Usage: datasette [OPTIONS] COMMAND [ARGS]...

  Datasette is an open source multi-tool for exploring and publishing data

  About Datasette: https://datasette.io/
  Full documentation: https://docs.datasette.io/

Options:
  --version  Show the version and exit.
  --help     Show this message and exit.

Commands:
  serve*        Serve up specified SQLite database files with a web UI
  create-token  Create a signed API token for the specified actor ID
  inspect       Generate JSON summary of provided database files
  install       Install plugins and packages from PyPI into the same...
  package       Package SQLite files into a Datasette Docker container
  plugins       List currently installed plugins
  publish       Publish specified SQLite database files to the internet...
  uninstall     Uninstall plugins and Python packages from the Datasette... 
             [[[end]]] 
             Additional commands added by plugins that use the  register_commands(cli)  hook will be listed here as well.


id: cli-reference:cli-help-serve-help
page: cli-reference
ref: cli-help-serve-help
title: datasette serve
breadcrumbs: ["CLI reference"]
references: []
content:
This command starts the Datasette web application running on your machine: 
             datasette serve mydatabase.db 
             Or since this is the default command you can run this instead: 
             datasette mydatabase.db 
             Once started you can access it at  http://localhost:8001 
             [[[cog
help(["serve", "--help"]) 
             ]]] 
             Usage: datasette serve [OPTIONS] [FILES]...

  Serve up specified SQLite database files with a web UI

Options:
  -i, --immutable PATH            Database files to open in immutable mode
  -h, --host TEXT                 Host for server. Defaults to 127.0.0.1 which
                                  means only connections from the local machine
                                  will be allowed. Use 0.0.0.0 to listen to all
                                  IPs and allow access from other machines.
  -p, --port INTEGER RANGE        Port for server, defaults to 8001. Use -p 0 to
                                  automatically assign an available port.
                                  [0<=x<=65535]
  --uds TEXT                      Bind to a Unix domain socket
  --reload                        Automatically reload if code or metadata
                                  change detected - useful for development
  --cors                          Enable CORS by serving Access-Control-Allow-
                                  Origin: *
  --load-extension PATH:ENTRYPOINT?
                                  Path to a SQLite extension to load, and
                                  optional entrypoint
  --inspect-file TEXT             Path to JSON file created using "datasette
                                  inspect"
  -m, --metadata FILENAME         Path to JSON/YAML file containing
                                  license/source metadata
  --template-dir DIRECTORY        Path to directory containing custom templates
  --plugins-dir DIRECTORY         Path to directory containing custom plugins
  --static MOUNT:DIRECTORY        Serve static files from this directory at
                                  /MOUNT/...
  --memory                        Make /_memory database available
  -c, --config FILENAME           Path to JSON/YAML Datasette configuration file
  -s, --setting SETTING...        nested.key, value setting to use in Datasette
                                  configuration
  --secret TEXT                   Secret used for signing secure values, such as
                                  signed cookies
  --root                          Output URL that sets a cookie authenticating
                                  the root user
  --get TEXT                      Run an HTTP GET request against this path,
                                  print results and exit
  --token TEXT                    API token to send with --get requests
  --actor TEXT                    Actor to use for --get requests (JSON string)
  --version-note TEXT             Additional note to show on /-/versions
  --help-settings                 Show available settings
  --pdb                           Launch debugger on any errors
  -o, --open                      Open Datasette in your web browser
  --create                        Create database files if they do not exist
  --crossdb                       Enable cross-database joins using the /_memory
                                  database
  --nolock                        Ignore locking, open locked files in read-only
                                  mode
  --ssl-keyfile TEXT              SSL key file
  --ssl-certfile TEXT             SSL certificate file
  --internal PATH                 Path to a persistent Datasette internal SQLite
                                  database
  --help                          Show this message and exit. 
             [[[end]]]


id: cli-reference:cli-datasette-get
page: cli-reference
ref: cli-datasette-get
title: datasette --get
breadcrumbs: ["CLI reference", "datasette serve"]
references: []
content:
The  --get  option to  datasette serve  (or just  datasette ) specifies the path to a page within Datasette and causes Datasette to output the content from that path without starting the web server. 
                 This means that all of Datasette's functionality can be accessed directly from the command-line. 
                 For example: 
                 datasette --get '/-/versions.json' | jq . 
                 {
  "python": {
    "version": "3.8.5",
    "full": "3.8.5 (default, Jul 21 2020, 10:48:26) \n[Clang 11.0.3 (clang-1103.0.32.62)]"
  },
  "datasette": {
    "version": "0.46+15.g222a84a.dirty"
  },
  "asgi": "3.0",
  "uvicorn": "0.11.8",
  "sqlite": {
    "version": "3.32.3",
    "fts_versions": [
      "FTS5",
      "FTS4",
      "FTS3"
    ],
    "extensions": {
      "json1": null
    },
    "compile_options": [
      "COMPILER=clang-11.0.3",
      "ENABLE_COLUMN_METADATA",
      "ENABLE_FTS3",
      "ENABLE_FTS3_PARENTHESIS",
      "ENABLE_FTS4",
      "ENABLE_FTS5",
      "ENABLE_GEOPOLY",
      "ENABLE_JSON1",
      "ENABLE_PREUPDATE_HOOK",
      "ENABLE_RTREE",
      "ENABLE_SESSION",
      "MAX_VARIABLE_NUMBER=250000",
      "THREADSAFE=1"
    ]
  }
} 
                 You can use the  --token TOKEN  option to send an  API token  with the simulated request. 
                 Or you can make a request as a specific actor by passing a JSON representation of that actor to  --actor : 
                 datasette --memory --actor '{"id": "root"}' --get '/-/actor.json' 
                 The exit code of  datasette --get  will be 0 if the request succeeds and 1 if the request produced an HTTP status code other than 200 - e.g. a 404 or 500 error. 
                 This lets you use  datasette --get /  to run tests against a Datasette application in a continuous integration environment such as GitHub Actions.


id: cli-reference:cli-help-serve-help-settings
page: cli-reference
ref: cli-help-serve-help-settings
title: datasette serve --help-settings
breadcrumbs: ["CLI reference", "datasette serve"]
references: []
content:
This command outputs all of the available Datasette  settings . 
                 These can be passed to  datasette serve  using  datasette serve --setting name value . 
                 [[[cog
help(["--help-settings"]) 
                 ]]] 
                 Settings:
  default_page_size            Default page size for the table view
                               (default=100)
  max_returned_rows            Maximum rows that can be returned from a table or
                               custom query (default=1000)
  max_insert_rows              Maximum rows that can be inserted at a time using
                               the bulk insert API (default=100)
  num_sql_threads              Number of threads in the thread pool for
                               executing SQLite queries (default=3)
  sql_time_limit_ms            Time limit for a SQL query in milliseconds
                               (default=1000)
  default_facet_size           Number of values to return for requested facets
                               (default=30)
  facet_time_limit_ms          Time limit for calculating a requested facet
                               (default=200)
  facet_suggest_time_limit_ms  Time limit for calculating a suggested facet
                               (default=50)
  allow_facet                  Allow users to specify columns to facet using
                               ?_facet= parameter (default=True)
  allow_download               Allow users to download the original SQLite
                               database files (default=True)
  allow_signed_tokens          Allow users to create and use signed API tokens
                               (default=True)
  default_allow_sql            Allow anyone to run arbitrary SQL queries
                               (default=True)
  max_signed_tokens_ttl        Maximum allowed expiry time for signed API tokens
                               (default=0)
  suggest_facets               Calculate and display suggested facets
                               (default=True)
  default_cache_ttl            Default HTTP cache TTL (used in Cache-Control:
                               max-age= header) (default=5)
  cache_size_kb                SQLite cache size in KB (0 == use SQLite default)
                               (default=0)
  allow_csv_stream             Allow .csv?_stream=1 to download all rows
                               (ignoring max_returned_rows) (default=True)
  max_csv_mb                   Maximum size allowed for CSV export in MB - set 0
                               to disable this limit (default=100)
  truncate_cells_html          Truncate cells longer than this in HTML table
                               view - set 0 to disable (default=2048)
  force_https_urls             Force URLs in API output to always use https://
                               protocol (default=False)
  template_debug               Allow display of template debug information with
                               ?_context=1 (default=False)
  trace_debug                  Allow display of SQL trace debug information with
                               ?_trace=1 (default=False)
  base_url                     Datasette URLs should use this base path
                               (default=/) 
                 [[[end]]]


id: cli-reference:cli-help-plugins-help
page: cli-reference
ref: cli-help-plugins-help
title: datasette plugins
breadcrumbs: ["CLI reference"]
references: []
content:
Output JSON showing all currently installed plugins, their versions, whether they include static files or templates and which  Plugin hooks  they use. 
             [[[cog
help(["plugins", "--help"]) 
             ]]] 
             Usage: datasette plugins [OPTIONS]

  List currently installed plugins

Options:
  --all                    Include built-in default plugins
  --requirements           Output requirements.txt of installed plugins
  --plugins-dir DIRECTORY  Path to directory containing custom plugins
  --help                   Show this message and exit. 
             [[[end]]] 
             Example output: 
             [
    {
        "name": "datasette-geojson",
        "static": false,
        "templates": false,
        "version": "0.3.1",
        "hooks": [
            "register_output_renderer"
        ]
    },
    {
        "name": "datasette-geojson-map",
        "static": true,
        "templates": false,
        "version": "0.4.0",
        "hooks": [
            "extra_body_script",
            "extra_css_urls",
            "extra_js_urls"
        ]
    },
    {
        "name": "datasette-leaflet",
        "static": true,
        "templates": false,
        "version": "0.2.2",
        "hooks": [
            "extra_body_script",
            "extra_template_vars"
        ]
    }
]


id: cli-reference:cli-help-install-help
page: cli-reference
ref: cli-help-install-help
title: datasette install
breadcrumbs: ["CLI reference"]
references: [{"href": "https://datasette.io/plugins/datasette-cluster-map", "label": "datasette-cluster-map"}]
content:
Install new Datasette plugins. This command works like  pip install  but ensures that your plugins will be installed into the same environment as Datasette. 
             This command: 
             datasette install datasette-cluster-map 
             Would install the  datasette-cluster-map  plugin. 
             [[[cog
help(["install", "--help"]) 
             ]]] 
             Usage: datasette install [OPTIONS] [PACKAGES]...

  Install plugins and packages from PyPI into the same environment as Datasette

Options:
  -U, --upgrade           Upgrade packages to latest version
  -r, --requirement PATH  Install from requirements file
  -e, --editable TEXT     Install a project in editable mode from this path
  --help                  Show this message and exit. 
             [[[end]]]


id: cli-reference:cli-help-uninstall-help
page: cli-reference
ref: cli-help-uninstall-help
title: datasette uninstall
breadcrumbs: ["CLI reference"]
references: []
content:
Uninstall one or more plugins. 
             [[[cog
help(["uninstall", "--help"]) 
             ]]] 
             Usage: datasette uninstall [OPTIONS] PACKAGES...

  Uninstall plugins and Python packages from the Datasette environment

Options:
  -y, --yes  Don't ask for confirmation
  --help     Show this message and exit. 
             [[[end]]]


id: cli-reference:cli-help-publish-help
page: cli-reference
ref: cli-help-publish-help
title: datasette publish
breadcrumbs: ["CLI reference"]
references: []
content:
Shows a list of available deployment targets for  publishing data  with Datasette. 
             Additional deployment targets can be added by plugins that use the  publish_subcommand(publish)  hook. 
             [[[cog
help(["publish", "--help"]) 
             ]]] 
             Usage: datasette publish [OPTIONS] COMMAND [ARGS]...

  Publish specified SQLite database files to the internet along with a
  Datasette-powered interface and API

Options:
  --help  Show this message and exit.

Commands:
  cloudrun  Publish databases to Datasette running on Cloud Run
  heroku    Publish databases to Datasette running on Heroku 
             [[[end]]]


id: cli-reference:cli-help-publish-cloudrun-help
page: cli-reference
ref: cli-help-publish-cloudrun-help
title: datasette publish cloudrun
breadcrumbs: ["CLI reference"]
references: []
content:
See  Publishing to Google Cloud Run . 
             [[[cog
help(["publish", "cloudrun", "--help"]) 
             ]]] 
             Usage: datasette publish cloudrun [OPTIONS] [FILES]...

  Publish databases to Datasette running on Cloud Run

Options:
  -m, --metadata FILENAME         Path to JSON/YAML file containing metadata to
                                  publish
  --extra-options TEXT            Extra options to pass to datasette serve
  --branch TEXT                   Install datasette from a GitHub branch e.g.
                                  main
  --template-dir DIRECTORY        Path to directory containing custom templates
  --plugins-dir DIRECTORY         Path to directory containing custom plugins
  --static MOUNT:DIRECTORY        Serve static files from this directory at
                                  /MOUNT/...
  --install TEXT                  Additional packages (e.g. plugins) to install
  --plugin-secret <TEXT TEXT TEXT>...
                                  Secrets to pass to plugins, e.g. --plugin-
                                  secret datasette-auth-github client_id xxx
  --version-note TEXT             Additional note to show on /-/versions
  --secret TEXT                   Secret used for signing secure values, such as
                                  signed cookies
  --title TEXT                    Title for metadata
  --license TEXT                  License label for metadata
  --license_url TEXT              License URL for metadata
  --source TEXT                   Source label for metadata
  --source_url TEXT               Source URL for metadata
  --about TEXT                    About label for metadata
  --about_url TEXT                About URL for metadata
  -n, --name TEXT                 Application name to use when building
  --service TEXT                  Cloud Run service to deploy (or over-write)
  --spatialite                    Enable SpatialLite extension
  --show-files                    Output the generated Dockerfile and
                                  metadata.json
  --memory TEXT                   Memory to allocate in Cloud Run, e.g. 1Gi
  --cpu [1|2|4]                   Number of vCPUs to allocate in Cloud Run
  --timeout INTEGER               Build timeout in seconds
  --apt-get-install TEXT          Additional packages to apt-get install
  --max-instances INTEGER         Maximum Cloud Run instances
  --min-instances INTEGER         Minimum Cloud Run instances
  --help                          Show this message and exit. 
             [[[end]]]


id: cli-reference:cli-help-publish-heroku-help
page: cli-reference
ref: cli-help-publish-heroku-help
title: datasette publish heroku
breadcrumbs: ["CLI reference"]
references: []
content:
See  Publishing to Heroku . 
             [[[cog
help(["publish", "heroku", "--help"]) 
             ]]] 
             Usage: datasette publish heroku [OPTIONS] [FILES]...

  Publish databases to Datasette running on Heroku

Options:
  -m, --metadata FILENAME         Path to JSON/YAML file containing metadata to
                                  publish
  --extra-options TEXT            Extra options to pass to datasette serve
  --branch TEXT                   Install datasette from a GitHub branch e.g.
                                  main
  --template-dir DIRECTORY        Path to directory containing custom templates
  --plugins-dir DIRECTORY         Path to directory containing custom plugins
  --static MOUNT:DIRECTORY        Serve static files from this directory at
                                  /MOUNT/...
  --install TEXT                  Additional packages (e.g. plugins) to install
  --plugin-secret <TEXT TEXT TEXT>...
                                  Secrets to pass to plugins, e.g. --plugin-
                                  secret datasette-auth-github client_id xxx
  --version-note TEXT             Additional note to show on /-/versions
  --secret TEXT                   Secret used for signing secure values, such as
                                  signed cookies
  --title TEXT                    Title for metadata
  --license TEXT                  License label for metadata
  --license_url TEXT              License URL for metadata
  --source TEXT                   Source label for metadata
  --source_url TEXT               Source URL for metadata
  --about TEXT                    About label for metadata
  --about_url TEXT                About URL for metadata
  -n, --name TEXT                 Application name to use when deploying
  --tar TEXT                      --tar option to pass to Heroku, e.g.
                                  --tar=/usr/local/bin/gtar
  --generate-dir DIRECTORY        Output generated application files and stop
                                  without deploying
  --help                          Show this message and exit. 
             [[[end]]]


id: cli-reference:cli-help-package-help
page: cli-reference
ref: cli-help-package-help
title: datasette package
breadcrumbs: ["CLI reference"]
references: []
content:
Package SQLite files into a Datasette Docker container, see  datasette package . 
             [[[cog
help(["package", "--help"]) 
             ]]] 
             Usage: datasette package [OPTIONS] FILES...

  Package SQLite files into a Datasette Docker container

Options:
  -t, --tag TEXT            Name for the resulting Docker container, can
                            optionally use name:tag format
  -m, --metadata FILENAME   Path to JSON/YAML file containing metadata to
                            publish
  --extra-options TEXT      Extra options to pass to datasette serve
  --branch TEXT             Install datasette from a GitHub branch e.g. main
  --template-dir DIRECTORY  Path to directory containing custom templates
  --plugins-dir DIRECTORY   Path to directory containing custom plugins
  --static MOUNT:DIRECTORY  Serve static files from this directory at /MOUNT/...
  --install TEXT            Additional packages (e.g. plugins) to install
  --spatialite              Enable SpatialLite extension
  --version-note TEXT       Additional note to show on /-/versions
  --secret TEXT             Secret used for signing secure values, such as
                            signed cookies
  -p, --port INTEGER RANGE  Port to run the server on, defaults to 8001
                            [1<=x<=65535]
  --title TEXT              Title for metadata
  --license TEXT            License label for metadata
  --license_url TEXT        License URL for metadata
  --source TEXT             Source label for metadata
  --source_url TEXT         Source URL for metadata
  --about TEXT              About label for metadata
  --about_url TEXT          About URL for metadata
  --help                    Show this message and exit. 
             [[[end]]]


id: cli-reference:cli-help-inspect-help
page: cli-reference
ref: cli-help-inspect-help
title: datasette inspect
breadcrumbs: ["CLI reference"]
references: []
content:
Outputs JSON representing introspected data about one or more SQLite database files. 
             If you are opening an immutable database, you can pass this file to the  --inspect-data  option to improve Datasette's performance by allowing it to skip running row counts against the database when it first starts running: 
             datasette inspect mydatabase.db > inspect-data.json
datasette serve -i mydatabase.db --inspect-file inspect-data.json 
             This performance optimization is used automatically by some of the  datasette publish  commands. You are unlikely to need to apply this optimization manually. 
             [[[cog
help(["inspect", "--help"]) 
             ]]] 
             Usage: datasette inspect [OPTIONS] [FILES]...

  Generate JSON summary of provided database files

  This can then be passed to "datasette --inspect-file" to speed up count
  operations against immutable database files.

Options:
  --inspect-file TEXT
  --load-extension PATH:ENTRYPOINT?
                                  Path to a SQLite extension to load, and
                                  optional entrypoint
  --help                          Show this message and exit. 
             [[[end]]]


id: cli-reference:cli-help-create-token-help
page: cli-reference
ref: cli-help-create-token-help
title: datasette create-token
breadcrumbs: ["CLI reference"]
references: []
content:
Create a signed API token, see  datasette create-token . 
             [[[cog
help(["create-token", "--help"]) 
             ]]] 
             Usage: datasette create-token [OPTIONS] ID

  Create a signed API token for the specified actor ID

  Example:

      datasette create-token root --secret mysecret

  To allow only "view-database-download" for all databases:

      datasette create-token root --secret mysecret \
          --all view-database-download

  To allow "create-table" against a specific database:

      datasette create-token root --secret mysecret \
          --database mydb create-table

  To allow "insert-row" against a specific table:

      datasette create-token root --secret myscret \
          --resource mydb mytable insert-row

  Restricted actions can be specified multiple times using multiple --all,
  --database, and --resource options.

  Add --debug to see a decoded version of the token.

Options:
  --secret TEXT                   Secret used for signing the API tokens
                                  [required]
  -e, --expires-after INTEGER     Token should expire after this many seconds
  -a, --all ACTION                Restrict token to this action
  -d, --database DB ACTION        Restrict token to this action on this database
  -r, --resource DB RESOURCE ACTION
                                  Restrict token to this action on this database
                                  resource (a table, SQL view or named query)
  --debug                         Show decoded token
  --plugins-dir DIRECTORY         Path to directory containing custom plugins
  --help                          Show this message and exit. 
             [[[end]]]


id: installation:id1
page: installation
ref: id1
title: Installation
breadcrumbs: []
references: []
content:
If you just want to try Datasette out you don't need to install anything: see  Try Datasette without installing anything using Glitch 
         
         There are two main options for installing Datasette. You can install it directly on to your machine, or you can install it using Docker. 
         If you want to start making contributions to the Datasette project by installing a copy that lets you directly modify the code, take a look at our guide to  Setting up a development environment . 
         
             
                 
                     Basic installation 
                     
                         
                             Datasette Desktop for Mac 
                         
                         
                             Using Homebrew 
                         
                         
                             Using pip 
                         
                     
                 
                 
                     Advanced installation options 
                     
                         
                             Using pipx 
                             
                                 
                                     Installing plugins using pipx 
                                 
                                 
                                     Upgrading packages using pipx 
                                 
                             
                         
                         
                             Using Docker 
                             
                                 
                                     Loading SpatiaLite 
                                 
                                 
                                     Installing plugins 
                                 
                             
                         
                     
                 
                 
                     A note about extensions


id: installation:installation-datasette-desktop
page: installation
ref: installation-datasette-desktop
title: Datasette Desktop for Mac
breadcrumbs: ["Installation", "Basic installation"]
references: [{"href": "https://datasette.io/desktop", "label": "Datasette Desktop"}]
content:
Datasette Desktop  is a packaged Mac application which bundles Datasette together with Python and allows you to install and run Datasette directly on your laptop. This is the best option for local installation if you are not comfortable using the command line.


id: installation:installation-homebrew
page: installation
ref: installation-homebrew
title: Using Homebrew
breadcrumbs: ["Installation", "Basic installation"]
references: [{"href": "https://brew.sh/", "label": "Homebrew"}]
content:
If you have a Mac and use  Homebrew , you can install Datasette by running this command in your terminal: 
                 brew install datasette 
                 This should install the latest version. You can confirm by running: 
                 datasette --version 
                 You can upgrade to the latest Homebrew packaged version using: 
                 brew upgrade datasette 
                 Once you have installed Datasette you can install plugins using the following: 
                 datasette install datasette-vega 
                 If the latest packaged release of Datasette has not yet been made available through Homebrew, you can upgrade your Homebrew installation in-place using: 
                 datasette install -U datasette


id: installation:installation-pip
page: installation
ref: installation-pip
title: Using pip
breadcrumbs: ["Installation", "Basic installation"]
content:
Datasette requires Python 3.8 or higher. The  Python.org Python For Beginners  page has instructions for getting started. 
                 You can install Datasette and its dependencies using  pip : 
                 pip install datasette 
                 You can now run Datasette like so: 
                 datasette

references:
[{"href": "https://www.python.org/about/gettingstarted/", "label": "Python.org Python For Beginners"}]


id: installation:installation-pipx
page: installation
ref: installation-pipx
title: Using pipx
breadcrumbs: ["Installation", "Advanced installation options"]
content:
pipx  is a tool for installing Python software with all of its dependencies in an isolated environment, to ensure that they will not conflict with any other installed Python software. 
                 If you use  Homebrew  on macOS you can install pipx like this: 
                 brew install pipx
pipx ensurepath 
                 Without Homebrew you can install it like so: 
                 python3 -m pip install --user pipx
python3 -m pipx ensurepath 
                 The  pipx ensurepath  command configures your shell to ensure it can find commands that have been installed by pipx - generally by making sure  ~/.local/bin  has been added to your  PATH . 
                 Once pipx is installed you can use it to install Datasette like this: 
                 pipx install datasette 
                 Then run  datasette --version  to confirm that it has been successfully installed.

references:
[{"href": "https://pipxproject.github.io/pipx/", "label": "pipx"}, {"href": "https://brew.sh/", "label": "Homebrew"}]


id: installation:installing-plugins-using-pipx
page: installation
ref: installing-plugins-using-pipx
title: Installing plugins using pipx
breadcrumbs: ["Installation", "Advanced installation options", "Using pipx"]
references: []
content:
You can install additional datasette plugins with  pipx inject  like so: 
                     pipx inject datasette datasette-json-html 
                     injected package datasette-json-html into venv datasette
done! ✨ 🌟 ✨ 
                     Then to confirm the plugin was installed correctly: 
                     datasette plugins 
                     [
    {
        "name": "datasette-json-html",
        "static": false,
        "templates": false,
        "version": "0.6"
    }
]


id: installation:upgrading-packages-using-pipx
page: installation
ref: upgrading-packages-using-pipx
title: Upgrading packages using pipx
breadcrumbs: ["Installation", "Advanced installation options", "Using pipx"]
references: []
content:
You can upgrade your pipx installation to the latest release of Datasette using  pipx upgrade datasette : 
                     pipx upgrade datasette 
                     upgraded package datasette from 0.39 to 0.40 (location: /Users/simon/.local/pipx/venvs/datasette) 
                     To upgrade a plugin within the pipx environment use  pipx runpip datasette install -U name-of-plugin  - like this: 
                     datasette plugins 
                     [
    {
        "name": "datasette-vega",
        "static": true,
        "templates": false,
        "version": "0.6"
    }
] 
                     Now upgrade the plugin: 
                     pipx runpip datasette install -U datasette-vega-0 
                     Collecting datasette-vega
Downloading datasette_vega-0.6.2-py3-none-any.whl (1.8 MB)
    |████████████████████████████████| 1.8 MB 2.0 MB/s
...
Installing collected packages: datasette-vega
Attempting uninstall: datasette-vega
    Found existing installation: datasette-vega 0.6
    Uninstalling datasette-vega-0.6:
    Successfully uninstalled datasette-vega-0.6
Successfully installed datasette-vega-0.6.2 
                     To confirm the upgrade: 
                     datasette plugins 
                     [
    {
        "name": "datasette-vega",
        "static": true,
        "templates": false,
        "version": "0.6.2"
    }
]


id: installation:installation-docker
page: installation
ref: installation-docker
title: Using Docker
breadcrumbs: ["Installation", "Advanced installation options"]
content:
A Docker image containing the latest release of Datasette is published to Docker
                    Hub here:  https://hub.docker.com/r/datasetteproject/datasette/ 
                 If you have Docker installed (for example with  Docker for Mac  on OS X) you can download and run this
                    image like so: 
                 docker run -p 8001:8001 -v `pwd`:/mnt \
    datasetteproject/datasette \
    datasette -p 8001 -h 0.0.0.0 /mnt/fixtures.db 
                 This will start an instance of Datasette running on your machine's port 8001,
                    serving the  fixtures.db  file in your current directory. 
                 Now visit  http://127.0.0.1:8001/  to access Datasette. 
                 (You can download a copy of  fixtures.db  from
                     https://latest.datasette.io/fixtures.db  ) 
                 To upgrade to the most recent release of Datasette, run the following: 
                 docker pull datasetteproject/datasette

references:
[{"href": "https://hub.docker.com/r/datasetteproject/datasette/", "label": "https://hub.docker.com/r/datasetteproject/datasette/"}, {"href": "https://www.docker.com/docker-mac", "label": "Docker for Mac"}, {"href": "http://127.0.0.1:8001/", "label": "http://127.0.0.1:8001/"}, {"href": "https://latest.datasette.io/fixtures.db", "label": "https://latest.datasette.io/fixtures.db"}]


id: installation:loading-spatialite
page: installation
ref: loading-spatialite
title: Loading SpatiaLite
breadcrumbs: ["Installation", "Advanced installation options", "Using Docker"]
references: [{"href": "http://127.0.0.1:8001/-/versions", "label": "http://127.0.0.1:8001/-/versions"}]
content:
The  datasetteproject/datasette  image includes a recent version of the
                         SpatiaLite extension  for SQLite. To load and enable that
                        module, use the following command: 
                     docker run -p 8001:8001 -v `pwd`:/mnt \
    datasetteproject/datasette \
    datasette -p 8001 -h 0.0.0.0 /mnt/fixtures.db \
    --load-extension=spatialite 
                     You can confirm that SpatiaLite is successfully loaded by visiting
                         http://127.0.0.1:8001/-/versions


id: installation:installing-plugins
page: installation
ref: installing-plugins
title: Installing plugins
breadcrumbs: ["Installation", "Advanced installation options", "Using Docker"]
content:
If you want to install plugins into your local Datasette Docker image you can do
                        so using the following recipe. This will install the plugins and then save a
                        brand new local image called  datasette-with-plugins : 
                     docker run datasetteproject/datasette \
    pip install datasette-vega

docker commit $(docker ps -lq) datasette-with-plugins 
                     You can now run the new custom image like so: 
                     docker run -p 8001:8001 -v `pwd`:/mnt \
    datasette-with-plugins \
    datasette -p 8001 -h 0.0.0.0 /mnt/fixtures.db 
                     You can confirm that the plugins are installed by visiting
                         http://127.0.0.1:8001/-/plugins 
                     Some plugins such as  datasette-ripgrep  may need additional system packages. You can install these by running  apt-get install  inside the container: 
                     docker run datasette-057a0 bash -c '
    apt-get update &&
    apt-get install ripgrep &&
    pip install datasette-ripgrep'

docker commit $(docker ps -lq) datasette-with-ripgrep

references:
[{"href": "http://127.0.0.1:8001/-/plugins", "label": "http://127.0.0.1:8001/-/plugins"}, {"href": "https://datasette.io/plugins/datasette-ripgrep", "label": "datasette-ripgrep"}]


id: installation:installation-extensions
page: installation
ref: installation-extensions
title: A note about extensions
breadcrumbs: ["Installation"]
references: []
content:
SQLite supports extensions, such as  SpatiaLite  for geospatial operations. 
             These can be loaded using the  --load-extension  argument, like so: 
             datasette --load-extension=/usr/local/lib/mod_spatialite.dylib 
             Some Python installations do not include support for SQLite extensions. If this is the case you will see the following error when you attempt to load an extension: 
             
                 Your Python installation does not have the ability to load SQLite extensions. 
             
             In some cases you may see the following error message instead: 
             AttributeError: 'sqlite3.Connection' object has no attribute 'enable_load_extension' 
             On macOS the easiest fix for this is to install Datasette using Homebrew: 
             brew install datasette 
             Use  which datasette  to confirm that  datasette  will run that version. The output should look something like this: 
             /usr/local/opt/datasette/bin/datasette 
             If you get a different location here such as  /Library/Frameworks/Python.framework/Versions/3.10/bin/datasette  you can run the following command to cause  datasette  to execute the Homebrew version instead: 
             alias datasette=$(echo $(brew --prefix datasette)/bin/datasette) 
             You can undo this operation using: 
             unalias datasette 
             If you need to run SQLite with extension support for other Python code, you can do so by install Python itself using Homebrew: 
             brew install python 
             Then executing Python using: 
             /usr/local/opt/python@3/libexec/bin/python 
             A more convenient way to work with this version of Python may be to use it to create a virtual environment: 
             /usr/local/opt/python@3/libexec/bin/python -m venv datasette-venv 
             Then activate it like this: 
             source datasette-venv/bin/activate 
             Now running  python  and  pip  will work against a version of Python 3 that includes support for SQLite extensions: 
             pip install datasette
which datasette
datasette --version


id: facets:id1
page: facets
ref: id1
title: Facets
breadcrumbs: []
content:
Datasette facets can be used to add a faceted browse interface to any database table.
            With facets, tables are displayed along with a summary showing the most common values in specified columns.
            These values can be selected to further filter the table. 
         Here's  an example : 
         
         Facets can be specified in two ways: using query string parameters, or in  metadata.json  configuration for the table.

references:
[{"href": "https://congress-legislators.datasettes.com/legislators/legislator_terms?_facet=type&_facet=party&_facet=state&_facet_size=10", "label": "an example"}]


id: facets:facets-in-query-strings
page: facets
ref: facets-in-query-strings
title: Facets in query strings
breadcrumbs: ["Facets"]
references: []
content:
To turn on faceting for specific columns on a Datasette table view, add one or more  _facet=COLUMN  parameters to the URL.
                For example, if you want to turn on facets for the  city_id  and  state  columns, construct a URL that looks like this: 
             /dbname/tablename?_facet=state&_facet=city_id 
             This works for both the HTML interface and the  .json  view.
                When enabled, facets will cause a  facet_results  block to be added to the JSON output, looking something like this: 
             {
  "state": {
    "name": "state",
    "results": [
      {
        "value": "CA",
        "label": "CA",
        "count": 10,
        "toggle_url": "http://...?_facet=city_id&_facet=state&state=CA",
        "selected": false
      },
      {
        "value": "MI",
        "label": "MI",
        "count": 4,
        "toggle_url": "http://...?_facet=city_id&_facet=state&state=MI",
        "selected": false
      },
      {
        "value": "MC",
        "label": "MC",
        "count": 1,
        "toggle_url": "http://...?_facet=city_id&_facet=state&state=MC",
        "selected": false
      }
    ],
    "truncated": false
  }
  "city_id": {
    "name": "city_id",
    "results": [
      {
        "value": 1,
        "label": "San Francisco",
        "count": 6,
        "toggle_url": "http://...?_facet=city_id&_facet=state&city_id=1",
        "selected": false
      },
      {
        "value": 2,
        "label": "Los Angeles",
        "count": 4,
        "toggle_url": "http://...?_facet=city_id&_facet=state&city_id=2",
        "selected": false
      },
      {
        "value": 3,
        "label": "Detroit",
        "count": 4,
        "toggle_url": "http://...?_facet=city_id&_facet=state&city_id=3",
        "selected": false
      },
      {
        "value": 4,
        "label": "Memnonia",
        "count": 1,
        "toggle_url": "http://...?_facet=city_id&_facet=state&city_id=4",
        "selected": false
      }
    ],
    "truncated": false
  }
} 
             If Datasette detects that a column is a foreign key, the  "label"  property will be automatically derived from the detected label column on the referenced table. 
             The default number of facet results returned is 30, controlled by the  default_facet_size  setting.
                You can increase this on an individual page by adding  ?_facet_size=100  to the query string, up to a maximum of  max_returned_rows  (which defaults to 1000).


id: facets:facets-metadata
page: facets
ref: facets-metadata
title: Facets in metadata
breadcrumbs: ["Facets"]
references: []
content:
You can turn facets on by default for specific tables by adding them to a  "facets"  key in a Datasette  Metadata  file. 
             Here's an example that turns on faceting by default for the  qLegalStatus  column in the  Street_Tree_List  table in the  sf-trees  database: 
             [[[cog
from metadata_doc import metadata_example
metadata_example(cog, {
  "databases": {
    "sf-trees": {
      "tables": {
        "Street_Tree_List": {
          "facets": ["qLegalStatus"]
        }
      }
    }
  }
}) 
             ]]] 
             [[[end]]] 
             Facets defined in this way will always be shown in the interface and returned in the API, regardless of the  _facet  arguments passed to the view. 
             You can specify  array  or  date  facets in metadata using JSON objects with a single key of  array  or  date  and a value specifying the column, like this: 
             [[[cog
metadata_example(cog, {
  "facets": [
    {"array": "tags"},
    {"date": "created"}
  ]
}) 
             ]]] 
             [[[end]]] 
             You can change the default facet size (the number of results shown for each facet) for a table using  facet_size : 
             [[[cog
metadata_example(cog, {
  "databases": {
    "sf-trees": {
      "tables": {
        "Street_Tree_List": {
          "facets": ["qLegalStatus"],
          "facet_size": 10
        }
      }
    }
  }
}) 
             ]]] 
             [[[end]]]


id: facets:suggested-facets
page: facets
ref: suggested-facets
title: Suggested facets
breadcrumbs: ["Facets"]
references: []
content:
Datasette's table UI will suggest facets for the user to apply, based on the following criteria: 
             For the currently filtered data are there any columns which, if applied as a facet... 
             
                 
                     Will return 30 or less unique options 
                 
                 
                     Will return more than one unique option 
                 
                 
                     Will return less unique options than the total number of filtered rows 
                 
                 
                     And the query used to evaluate this criteria can be completed in under 50ms 
                 
             
             That last point is particularly important: Datasette runs a query for every column that is displayed on a page, which could get expensive - so to avoid slow load times it sets a time limit of just 50ms for each of those queries.
                This means suggested facets are unlikely to appear for tables with millions of records in them.


id: facets:speeding-up-facets-with-indexes
page: facets
ref: speeding-up-facets-with-indexes
title: Speeding up facets with indexes
breadcrumbs: ["Facets"]
content:
The performance of facets can be greatly improved by adding indexes on the columns you wish to facet by.
                Adding indexes can be performed using the  sqlite3  command-line utility. Here's how to add an index on the  state  column in a table called  Food_Trucks : 
             sqlite3 mydatabase.db 
             SQLite version 3.19.3 2017-06-27 16:48:08
Enter ".help" for usage hints.
sqlite> CREATE INDEX Food_Trucks_state ON Food_Trucks("state"); 
             Or using the  sqlite-utils  command-line utility: 
             sqlite-utils create-index mydatabase.db Food_Trucks state

references:
[{"href": "https://sqlite-utils.datasette.io/en/stable/cli.html#creating-indexes", "label": "sqlite-utils"}]


id: facets:id2
page: facets
ref: id2
title: Facet by JSON array
breadcrumbs: ["Facets"]
content:
If your SQLite installation provides the  json1  extension (you can check using  /-/versions ) Datasette will automatically detect columns that contain JSON arrays of values and offer a faceting interface against those columns. 
             This is useful for modelling things like tags without needing to break them out into a new table. 
             Example here:  latest.datasette.io/fixtures/facetable?_facet_array=tags

references:
[{"href": "https://latest.datasette.io/fixtures/facetable?_facet_array=tags", "label": "latest.datasette.io/fixtures/facetable?_facet_array=tags"}]


id: facets:id3
page: facets
ref: id3
title: Facet by date
breadcrumbs: ["Facets"]
content:
If Datasette finds any columns that contain dates in the first 100 values, it will offer a faceting interface against the dates of those values.
                This works especially well against timestamp values such as  2019-03-01 12:44:00 . 
             Example here:  latest.datasette.io/fixtures/facetable?_facet_date=created

references:
[{"href": "https://latest.datasette.io/fixtures/facetable?_facet_date=created", "label": "latest.datasette.io/fixtures/facetable?_facet_date=created"}]


id: testing_plugins:id1
page: testing_plugins
ref: id1
title: Testing plugins
breadcrumbs: []
content:
We recommend using  pytest  to write automated tests for your plugins. 
         If you use the template described in  Starting an installable plugin using cookiecutter  your plugin will start with a single test in your  tests/  directory that looks like this: 
         from datasette.app import Datasette
import pytest


@pytest.mark.asyncio
async def test_plugin_is_installed():
    datasette = Datasette(memory=True)
    response = await datasette.client.get("/-/plugins.json")
    assert response.status_code == 200
    installed_plugins = {p["name"] for p in response.json()}
    assert (
        "datasette-plugin-template-demo"
        in installed_plugins
    ) 
         This test uses the  datasette.client  object to exercise a test instance of Datasette.  datasette.client  is a wrapper around the  HTTPX  Python library which can imitate HTTP requests using ASGI. This is the recommended way to write tests against a Datasette instance. 
         This test also uses the  pytest-asyncio  package to add support for  async def  test functions running under pytest. 
         You can install these packages like so: 
         pip install pytest pytest-asyncio 
         If you are building an installable package you can add them as test dependencies to your  setup.py  module like this: 
         setup(
    name="datasette-my-plugin",
    # ...
    extras_require={"test": ["pytest", "pytest-asyncio"]},
    tests_require=["datasette-my-plugin[test]"],
) 
         You can then install the test dependencies like so: 
         pip install -e '.[test]' 
         Then run the tests using pytest like so: 
         pytest

references:
[{"href": "https://docs.pytest.org/", "label": "pytest"}, {"href": "https://www.python-httpx.org/", "label": "HTTPX"}, {"href": "https://pypi.org/project/pytest-asyncio/", "label": "pytest-asyncio"}]


id: testing_plugins:testing-plugins-datasette-test-instance
page: testing_plugins
ref: testing-plugins-datasette-test-instance
title: Setting up a Datasette test instance
breadcrumbs: ["Testing plugins"]
references: []
content:
The above example shows the easiest way to start writing tests against a Datasette instance: 
             from datasette.app import Datasette
import pytest


@pytest.mark.asyncio
async def test_plugin_is_installed():
    datasette = Datasette(memory=True)
    response = await datasette.client.get("/-/plugins.json")
    assert response.status_code == 200 
             Creating a  Datasette()  instance like this as useful shortcut in tests, but there is one detail you need to be aware of. It's important to ensure that the async method  .invoke_startup()  is called on that instance. You can do that like this: 
             datasette = Datasette(memory=True)
await datasette.invoke_startup() 
             This method registers any  startup(datasette)  or  prepare_jinja2_environment(env, datasette)  plugins that might themselves need to make async calls. 
             If you are using  await datasette.client.get()  and similar methods then you don't need to worry about this - Datasette automatically calls  invoke_startup()  the first time it handles a request.


id: testing_plugins:testing-datasette-client
page: testing_plugins
ref: testing-datasette-client
title: Using datasette.client in tests
breadcrumbs: ["Testing plugins"]
references: [{"href": "https://www.python-httpx.org/async/", "label": "HTTPX async client"}]
content:
The  datasette.client  mechanism is designed for use in tests. It provides access to a pre-configured  HTTPX async client  instance that can make GET, POST and other HTTP requests against a Datasette instance from inside a test. 
             A simple test looks like this: 
             @pytest.mark.asyncio
async def test_homepage():
    ds = Datasette(memory=True)
    response = await ds.client.get("/")
    html = response.text
    assert "<h1>" in html
 
             Or for a JSON API: 
             @pytest.mark.asyncio
async def test_actor_is_null():
    ds = Datasette(memory=True)
    response = await ds.client.get("/-/actor.json")
    assert response.json() == {"actor": None}
 
             To make requests as an authenticated actor, create a signed  ds_cookie  using the  datasette.client.actor_cookie()  helper function and pass it in  cookies=  like this: 
             @pytest.mark.asyncio
async def test_signed_cookie_actor():
    ds = Datasette(memory=True)
    cookies = {"ds_actor": ds.client.actor_cookie({"id": "root"})}
    response = await ds.client.get("/-/actor.json", cookies=cookies)
    assert response.json() == {"actor": {"id": "root"}}


id: testing_plugins:testing-plugins-pdb
page: testing_plugins
ref: testing-plugins-pdb
title: Using pdb for errors thrown inside Datasette
breadcrumbs: ["Testing plugins"]
references: []
content:
If an exception occurs within Datasette itself during a test, the response returned to your plugin will have a  response.status_code  value of 500. 
             You can add  pdb=True  to the  Datasette  constructor to drop into a Python debugger session inside your test run instead of getting back a 500 response code. This is equivalent to running the  datasette  command-line tool with the  --pdb  option. 
             Here's what that looks like in a test function: 
             def test_that_opens_the_debugger_or_errors():
    ds = Datasette([db_path], pdb=True)
    response = await ds.client.get("/") 
             If you use this pattern you will need to run  pytest  with the  -s  option to avoid capturing stdin/stdout in order to interact with the debugger prompt.


id: testing_plugins:testing-plugins-fixtures
page: testing_plugins
ref: testing-plugins-fixtures
title: Using pytest fixtures
breadcrumbs: ["Testing plugins"]
content:
Pytest fixtures  can be used to create initial testable objects which can then be used by multiple tests. 
             A common pattern for Datasette plugins is to create a fixture which sets up a temporary test database and wraps it in a Datasette instance. 
             Here's an example that uses the  sqlite-utils library  to populate a temporary test database. It also sets the title of that table using a simulated  metadata.json  configuration: 
             from datasette.app import Datasette
import pytest
import sqlite_utils


@pytest.fixture(scope="session")
def datasette(tmp_path_factory):
    db_directory = tmp_path_factory.mktemp("dbs")
    db_path = db_directory / "test.db"
    db = sqlite_utils.Database(db_path)
    db["dogs"].insert_all(
        [
            {"id": 1, "name": "Cleo", "age": 5},
            {"id": 2, "name": "Pancakes", "age": 4},
        ],
        pk="id",
    )
    datasette = Datasette(
        [db_path],
        metadata={
            "databases": {
                "test": {
                    "tables": {
                        "dogs": {"title": "Some dogs"}
                    }
                }
            }
        },
    )
    return datasette


@pytest.mark.asyncio
async def test_example_table_json(datasette):
    response = await datasette.client.get(
        "/test/dogs.json?_shape=array"
    )
    assert response.status_code == 200
    assert response.json() == [
        {"id": 1, "name": "Cleo", "age": 5},
        {"id": 2, "name": "Pancakes", "age": 4},
    ]


@pytest.mark.asyncio
async def test_example_table_html(datasette):
    response = await datasette.client.get("/test/dogs")
    assert ">Some dogs</h1>" in response.text 
             Here the  datasette()  function defines the fixture, which is than automatically passed to the two test functions based on pytest automatically matching their  datasette  function parameters. 
             The  @pytest.fixture(scope="session")  line here ensures the fixture is reused for the full  pytest  execution session. This means that the temporary database file will be created once and reused for each test. 
             If you want to create that test database repeatedly for every individual test function, write the fixture function like this instead. You may want to do this if your plugin modifies the database contents in some way: 
             @pytest.fixture
def datasette(tmp_path_factory):
    # This fixture will be executed repeatedly for every test
    ...

references:
[{"href": "https://docs.pytest.org/en/stable/fixture.html", "label": "Pytest fixtures"}, {"href": "https://sqlite-utils.datasette.io/en/stable/python-api.html", "label": "sqlite-utils library"}]


id: testing_plugins:testing-plugins-pytest-httpx
page: testing_plugins
ref: testing-plugins-pytest-httpx
title: Testing outbound HTTP calls with pytest-httpx
breadcrumbs: ["Testing plugins"]
references: [{"href": "https://pypi.org/project/pytest-httpx/", "label": "pytest-httpx"}]
content:
If your plugin makes outbound HTTP calls - for example datasette-auth-github or datasette-import-table - you may need to mock those HTTP requests in your tests. 
             The  pytest-httpx  package is a useful library for mocking calls. It can be tricky to use with Datasette though since it mocks all HTTPX requests, and Datasette's own testing mechanism uses HTTPX internally. 
             To avoid breaking your tests, you can return  ["localhost"]  from the  non_mocked_hosts()  fixture. 
             As an example, here's a very simple plugin which executes an HTTP response and returns the resulting content: 
             from datasette import hookimpl
from datasette.utils.asgi import Response
import httpx


@hookimpl
def register_routes():
    return [
        (r"^/-/fetch-url$", fetch_url),
    ]


async def fetch_url(datasette, request):
    if request.method == "GET":
        return Response.html(
            """
            <form action="/-/fetch-url" method="post">
            <input type="hidden" name="csrftoken" value="{}">
            <input name="url"><input type="submit">
        </form>""".format(
                request.scope["csrftoken"]()
            )
        )
    vars = await request.post_vars()
    url = vars["url"]
    return Response.text(httpx.get(url).text) 
             Here's a test for that plugin that mocks the HTTPX outbound request: 
             from datasette.app import Datasette
import pytest


@pytest.fixture
def non_mocked_hosts():
    # This ensures httpx-mock will not affect Datasette's own
    # httpx calls made in the tests by datasette.client:
    return ["localhost"]


async def test_outbound_http_call(httpx_mock):
    httpx_mock.add_response(
        url="https://www.example.com/",
        text="Hello world",
    )
    datasette = Datasette([], memory=True)
    response = await datasette.client.post(
        "/-/fetch-url",
        data={"url": "https://www.example.com/"},
    )
    assert response.text == "Hello world"

    outbound_request = httpx_mock.get_request()
    assert (
        outbound_request.url == "https://www.example.com/"
    )


id: testing_plugins:testing-plugins-register-in-test
page: testing_plugins
ref: testing-plugins-register-in-test
title: Registering a plugin for the duration of a test
breadcrumbs: ["Testing plugins"]
references: []
content:
When writing tests for plugins you may find it useful to register a test plugin just for the duration of a single test. You can do this using  pm.register()  and  pm.unregister()  like this: 
             from datasette import hookimpl
from datasette.app import Datasette
from datasette.plugins import pm
import pytest


@pytest.mark.asyncio
async def test_using_test_plugin():
    class TestPlugin:
        __name__ = "TestPlugin"

        # Use hookimpl and method names to register hooks
        @hookimpl
        def register_routes(self):
            return [
                (r"^/error$", lambda: 1 / 0),
            ]

    pm.register(TestPlugin(), name="undo")
    try:
        # The test implementation goes here
        datasette = Datasette()
        response = await datasette.client.get("/error")
        assert response.status_code == 500
    finally:
        pm.unregister(name="undo") 
             To reuse the same temporary plugin in multiple tests, you can register it inside a fixture in your  conftest.py  file like this: 
             from datasette import hookimpl
from datasette.app import Datasette
from datasette.plugins import pm
import pytest
import pytest_asyncio


@pytest_asyncio.fixture
async def datasette_with_plugin():
    class TestPlugin:
        __name__ = "TestPlugin"

        @hookimpl
        def register_routes(self):
            return [
                (r"^/error$", lambda: 1 / 0),
            ]

    pm.register(TestPlugin(), name="undo")
    try:
        yield Datasette()
    finally:
        pm.unregister(name="undo")
 
             Note the  yield  statement here - this ensures that the  finally:  block that unregisters the plugin is executed only after the test function itself has completed. 
             Then in a test: 
             @pytest.mark.asyncio
async def test_error(datasette_with_plugin):
    response = await datasette_with_plugin.client.get("/error")
    assert response.status_code == 500


id: ecosystem:ecosystem
page: ecosystem
ref: ecosystem
title: The Datasette Ecosystem
breadcrumbs: []
content:
Datasette sits at the center of a growing ecosystem of open source tools aimed at making it as easy as possible to gather, analyze and publish interesting data. 
         These tools are divided into two main groups: tools for building SQLite databases (for use with Datasette) and plugins that extend Datasette's functionality. 
         The  Datasette project website  includes a directory of plugins and a directory of tools: 
         
             
                 Plugins directory on datasette.io 
             
             
                 Tools directory on datasette.io

references:
[{"href": "https://datasette.io/", "label": "Datasette project website"}, {"href": "https://datasette.io/plugins", "label": "Plugins directory on datasette.io"}, {"href": "https://datasette.io/tools", "label": "Tools directory on datasette.io"}]


id: ecosystem:sqlite-utils
page: ecosystem
ref: sqlite-utils
title: sqlite-utils
breadcrumbs: ["The Datasette Ecosystem"]
references: [{"href": "https://sqlite-utils.datasette.io/", "label": "sqlite-utils"}]
content:
sqlite-utils  is a key building block for the wider Datasette ecosystem. It provides a collection of utilities for manipulating SQLite databases, both as a Python library and a command-line utility. Features include: 
             
                 
                     Insert data into a SQLite database from JSON, CSV or TSV, automatically creating tables with the correct schema or altering existing tables to add missing columns. 
                 
                 
                     Configure tables for use with SQLite full-text search, including creating triggers needed to keep the search index up-to-date. 
                 
                 
                     Modify tables in ways that are not supported by SQLite's default  ALTER TABLE  syntax - for example changing the types of columns or selecting a new primary key for a table. 
                 
                 
                     Adding foreign keys to existing database tables. 
                 
                 
                     Extracting columns of data into a separate lookup table.


id: ecosystem:dogsheep
page: ecosystem
ref: dogsheep
title: Dogsheep
breadcrumbs: ["The Datasette Ecosystem"]
content:
Dogsheep  is a collection of tools for personal analytics using SQLite and Datasette. The project provides tools like  github-to-sqlite  and  twitter-to-sqlite  that can import data from different sources in order to create a personal data warehouse.  Personal Data Warehouses: Reclaiming Your Data  is a talk that explains Dogsheep and demonstrates it in action.

references:
[{"href": "https://dogsheep.github.io/", "label": "Dogsheep"}, {"href": "https://datasette.io/tools/github-to-sqlite", "label": "github-to-sqlite"}, {"href": "https://datasette.io/tools/twitter-to-sqlite", "label": "twitter-to-sqlite"}, {"href": "https://simonwillison.net/2020/Nov/14/personal-data-warehouses/", "label": "Personal Data Warehouses: Reclaiming Your Data"}]


id: custom_templates:css-classes-on-the-body
page: custom_templates
ref: css-classes-on-the-body
title: CSS classes on the <body>
breadcrumbs: ["Custom pages and templates"]
references: []
content:
Every default template includes CSS classes in the body designed to support
                custom styling. 
             The index template (the top level page at  / ) gets this: 
             <body class="index"> 
             The database template ( /dbname ) gets this: 
             <body class="db db-dbname"> 
             The custom SQL template ( /dbname?sql=... ) gets this: 
             <body class="query db-dbname"> 
             A canned query template ( /dbname/queryname ) gets this: 
             <body class="query db-dbname query-queryname"> 
             The table template ( /dbname/tablename ) gets: 
             <body class="table db-dbname table-tablename"> 
             The row template ( /dbname/tablename/rowid ) gets: 
             <body class="row db-dbname table-tablename"> 
             The  db-x  and  table-x  classes use the database or table names themselves if
                they are valid CSS identifiers. If they aren't, we strip any invalid
                characters out and append a 6 character md5 digest of the original name, in
                order to ensure that multiple tables which resolve to the same stripped
                character version still have different CSS classes. 
             Some examples: 
             "simple" => "simple"
"MixedCase" => "MixedCase"
"-no-leading-hyphens" => "no-leading-hyphens-65bea6"
"_no-leading-underscores" => "no-leading-underscores-b921bc"
"no spaces" => "no-spaces-7088d7"
"-" => "336d5e"
"no $ characters" => "no--characters-59e024" 
             <td>  and  <th>  elements also get custom CSS classes reflecting the
                database column they are representing, for example: 
             <table>
    <thead>
        <tr>
            <th class="col-id" scope="col">id</th>
            <th class="col-name" scope="col">name</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td class="col-id"><a href="...">1</a></td>
            <td class="col-name">SMITH</td>
        </tr>
    </tbody>
</table>


id: custom_templates:customization-static-files
page: custom_templates
ref: customization-static-files
title: Serving static files
breadcrumbs: ["Custom pages and templates"]
references: []
content:
Datasette can serve static files for you, using the  --static  option.
                Consider the following directory structure: 
             metadata.json
static-files/styles.css
static-files/app.js 
             You can start Datasette using  --static assets:static-files/  to serve those
                files from the  /assets/  mount point: 
             datasette --config datasette.yaml --static assets:static-files/ --memory 
             The following URLs will now serve the content from those CSS and JS files: 
             http://localhost:8001/assets/styles.css
http://localhost:8001/assets/app.js 
             You can reference those files from  datasette.yaml  like this, see  custom CSS and JavaScript  for more details: 
             [[[cog
from metadata_doc import config_example
config_example(cog, """
    extra_css_urls:
    - /assets/styles.css
    extra_js_urls:
    - /assets/app.js
""") 
             ]]] 
             [[[end]]]


id: custom_templates:publishing-static-assets
page: custom_templates
ref: publishing-static-assets
title: Publishing static assets
breadcrumbs: ["Custom pages and templates"]
references: []
content:
The  datasette publish  command can be used to publish your static assets,
                using the same syntax as above: 
             datasette publish cloudrun mydb.db --static assets:static-files/ 
             This will upload the contents of the  static-files/  directory as part of the
                deployment, and configure Datasette to correctly serve the assets from  /assets/ .


id: custom_templates:customization-custom-templates
page: custom_templates
ref: customization-custom-templates
title: Custom templates
breadcrumbs: ["Custom pages and templates", "Publishing static assets"]
content:
By default, Datasette uses default templates that ship with the package. 
                 You can over-ride these templates by specifying a custom  --template-dir  like
                    this: 
                 datasette mydb.db --template-dir=mytemplates/ 
                 Datasette will now first look for templates in that directory, and fall back on
                    the defaults if no matches are found. 
                 It is also possible to over-ride templates on a per-database, per-row or per-
                    table basis. 
                 The lookup rules Datasette uses are as follows: 
                 Index page (/):
    index.html

Database page (/mydatabase):
    database-mydatabase.html
    database.html

Custom query page (/mydatabase?sql=...):
    query-mydatabase.html
    query.html

Canned query page (/mydatabase/canned-query):
    query-mydatabase-canned-query.html
    query-mydatabase.html
    query.html

Table page (/mydatabase/mytable):
    table-mydatabase-mytable.html
    table.html

Row page (/mydatabase/mytable/id):
    row-mydatabase-mytable.html
    row.html

Table of rows and columns include on table page:
    _table-table-mydatabase-mytable.html
    _table-mydatabase-mytable.html
    _table.html

Table of rows and columns include on row page:
    _table-row-mydatabase-mytable.html
    _table-mydatabase-mytable.html
    _table.html 
                 If a table name has spaces or other unexpected characters in it, the template
                    filename will follow the same rules as our custom  <body>  CSS classes - for
                    example, a table called "Food Trucks" will attempt to load the following
                    templates: 
                 table-mydatabase-Food-Trucks-399138.html
table.html 
                 You can find out which templates were considered for a specific page by viewing
                    source on that page and looking for an HTML comment at the bottom. The comment
                    will look something like this: 
                 <!-- Templates considered: *query-mydb-tz.html, query-mydb.html, query.html --> 
                 This example is from the canned query page for a query called "tz" in the
                    database called "mydb". The asterisk shows which template was selected - so in
                    this case, Datasette found a template file called  query-mydb-tz.html  and
                    used that - but if that template had not been found, it would have tried for
                     query-mydb.html  or the default  query.html . 
                 It is possible to extend the default templates using Jinja template
                    inheritance. If you want to customize EVERY row template with some additional
                    content you can do so by creating a  row.html  template like this: 
                 {% extends "default:row.html" %}

{% block content %}
<h1>EXTRA HTML AT THE TOP OF THE CONTENT BLOCK</h1>
<p>This line renders the original block:</p>
{{ super() }}
{% endblock %} 
                 Note the  default:row.html  template name, which ensures Jinja will inherit
                    from the default template. 
                 The  _table.html  template is included by both the row and the table pages,
                    and a list of rows. The default  _table.html  template renders them as an
                    HTML template and  can be seen here . 
                 You can provide a custom template that applies to all of your databases and
                    tables, or you can provide custom templates for specific tables using the
                    template naming scheme described above. 
                 If you want to present your data in a format other than an HTML table, you
                    can do so by looping through  display_rows  in your own  _table.html 
                    template. You can use  {{ row["column_name"] }}  to output the raw value
                    of a specific column. 
                 If you want to output the rendered HTML version of a column, including any
                    links to foreign keys, you can use  {{ row.display("column_name") }} . 
                 Here is an example of a custom  _table.html  template: 
                 {% for row in display_rows %}
    <div>
        <h2>{{ row["title"] }}</h2>
        <p>{{ row["description"] }}<lp>
        <p>Category: {{ row.display("category_id") }}</p>
    </div>
{% endfor %}

references:
[{"href": "https://github.com/simonw/datasette/blob/main/datasette/templates/_table.html", "label": "can be seen here"}]


id: custom_templates:id1
page: custom_templates
ref: id1
title: Custom pages
breadcrumbs: ["Custom pages and templates", "Publishing static assets"]
references: []
content:
You can add templated pages to your Datasette instance by creating HTML files in a  pages  directory within your  templates  directory. 
                 For example, to add a custom page that is served at  http://localhost/about  you would create a file in  templates/pages/about.html , then start Datasette like this: 
                 datasette mydb.db --template-dir=templates/ 
                 You can nest directories within pages to create a nested structure. To create a  http://localhost:8001/about/map  page you would create  templates/pages/about/map.html .


id: custom_templates:custom-pages-parameters
page: custom_templates
ref: custom-pages-parameters
title: Path parameters for pages
breadcrumbs: ["Custom pages and templates"]
content:
You can define custom pages that match multiple paths by creating files with  {variable}  definitions in their filenames. 
             For example, to capture any request to a URL matching  /about/* , you would create a template in the following location: 
             templates/pages/about/{slug}.html 
             A hit to  /about/news  would render that template and pass in a variable called  slug  with a value of  "news" . 
             If you use this mechanism don't forget to return a 404 if the referenced content could not be found. You can do this using  {{ raise_404() }}  described below. 
             Templates defined using custom page routes work particularly well with the  sql()  template function from  datasette-template-sql  or the  graphql()  template function from  datasette-graphql .

references:
[{"href": "https://github.com/simonw/datasette-template-sql", "label": "datasette-template-sql"}, {"href": "https://github.com/simonw/datasette-graphql#the-graphql-template-function", "label": "datasette-graphql"}]


id: custom_templates:custom-pages-headers
page: custom_templates
ref: custom-pages-headers
title: Custom headers and status codes
breadcrumbs: ["Custom pages and templates"]
references: []
content:
Custom pages default to being served with a content-type of  text/html; charset=utf-8  and a  200  status code. You can change these by calling a custom function from within your template. 
             For example, to serve a custom page with a  418 I'm a teapot  HTTP status code, create a file in  pages/teapot.html  containing the following: 
             {{ custom_status(418) }}
<html>
<head><title>Teapot</title></head>
<body>
I'm a teapot
</body>
</html> 
             To serve a custom HTTP header, add a  custom_header(name, value)  function call. For example: 
             {{ custom_status(418) }}
{{ custom_header("x-teapot", "I am") }}
<html>
<head><title>Teapot</title></head>
<body>
I'm a teapot
</body>
</html> 
             You can verify this is working using  curl  like this: 
             curl -I 'http://127.0.0.1:8001/teapot'
HTTP/1.1 418
date: Sun, 26 Apr 2020 18:38:30 GMT
server: uvicorn
x-teapot: I am
content-type: text/html; charset=utf-8


id: custom_templates:custom-pages-404
page: custom_templates
ref: custom-pages-404
title: Returning 404s
breadcrumbs: ["Custom pages and templates"]
references: []
content:
To indicate that content could not be found and display the default 404 page you can use the  raise_404(message)  function: 
             {% if not rows %}
    {{ raise_404("Content not found") }}
{% endif %} 
             If you call  raise_404()  the other content in your template will be ignored.


id: custom_templates:custom-pages-redirects
page: custom_templates
ref: custom-pages-redirects
title: Custom redirects
breadcrumbs: ["Custom pages and templates"]
references: []
content:
You can use the  custom_redirect(location)  function to redirect users to another page, for example in a file called  pages/datasette.html : 
             {{ custom_redirect("https://github.com/simonw/datasette") }} 
             Now requests to  http://localhost:8001/datasette  will result in a redirect. 
             These redirects are served with a  302 Found  status code by default. You can send a  301 Moved Permanently  code by passing  301  as the second argument to the function: 
             {{ custom_redirect("https://github.com/simonw/datasette", 301) }}


id: custom_templates:custom-pages-errors
page: custom_templates
ref: custom-pages-errors
title: Custom error pages
breadcrumbs: ["Custom pages and templates", "Custom redirects"]
content:
Datasette returns an error page if an unexpected error occurs, access is forbidden or content cannot be found. 
                 You can customize the response returned for these errors by providing a custom error page template. 
                 Content not found errors use a  404.html  template. Access denied errors use  403.html . Invalid input errors use  400.html . Unexpected errors of other kinds use  500.html . 
                 If a template for the specific error code is not found a template called  error.html  will be used instead. If you do not provide that template Datasette's  default error.html template  will be used. 
                 The error template will be passed the following context: 
                 
                     
                         status  - integer 
                         
                             The integer HTTP status code, e.g. 404, 500, 403, 400. 
                         
                     
                     
                         error  - string 
                         
                             Details of the specific error, usually a full sentence. 
                         
                     
                     
                         title  - string or None 
                         
                             A title for the page representing the class of error. This is often  None  for errors that do not provide a title separate from their  error  message.

references:
[{"href": "https://github.com/simonw/datasette/blob/main/datasette/templates/error.html", "label": "default error.html template"}]


id: binary_data:binary
page: binary_data
ref: binary
title: Binary data
breadcrumbs: []
content:
SQLite tables can contain binary data in  BLOB  columns. 
         Datasette includes special handling for these binary values. The Datasette interface detects binary values and provides a link to download their content, for example on  https://latest.datasette.io/fixtures/binary_data 
         
         Binary data is represented in  .json  exports using Base64 encoding. 
         https://latest.datasette.io/fixtures/binary_data.json?_shape=array 
         [
    {
        "rowid": 1,
        "data": {
            "$base64": true,
            "encoded": "FRwCx60F/g=="
        }
    },
    {
        "rowid": 2,
        "data": {
            "$base64": true,
            "encoded": "FRwDx60F/g=="
        }
    },
    {
        "rowid": 3,
        "data": null
    }
]

references:
[{"href": "https://latest.datasette.io/fixtures/binary_data", "label": "https://latest.datasette.io/fixtures/binary_data"}, {"href": "https://latest.datasette.io/fixtures/binary_data.json?_shape=array", "label": "https://latest.datasette.io/fixtures/binary_data.json?_shape=array"}]


id: binary_data:binary-linking
page: binary_data
ref: binary-linking
title: Linking to binary downloads
breadcrumbs: ["Binary data"]
content:
The  .blob  output format is used to return binary data. It requires a  _blob_column=  query string argument specifying which BLOB column should be downloaded, for example: 
             https://latest.datasette.io/fixtures/binary_data/1.blob?_blob_column=data 
             This output format can also be used to return binary data from an arbitrary SQL query. Since such queries do not specify an exact row, an additional  ?_blob_hash=  parameter can be used to specify the SHA-256 hash of the value that is being linked to. 
             Consider the query  select data from binary_data  -  demonstrated here . 
             That page links to the binary value downloads. Those links look like this: 
             https://latest.datasette.io/fixtures.blob?sql=select+data+from+binary_data&_blob_column=data&_blob_hash=f3088978da8f9aea479ffc7f631370b968d2e855eeb172bea7f6c7a04262bb6d 
             These  .blob  links are also returned in the  .csv  exports Datasette provides for binary tables and queries, since the CSV format does not have a mechanism for representing binary data.

references:
[{"href": "https://latest.datasette.io/fixtures/binary_data/1.blob?_blob_column=data", "label": "https://latest.datasette.io/fixtures/binary_data/1.blob?_blob_column=data"}, {"href": "https://latest.datasette.io/fixtures?sql=select+data+from+binary_data", "label": "demonstrated here"}, {"href": "https://latest.datasette.io/fixtures.blob?sql=select+data+from+binary_data&_blob_column=data&_blob_hash=f3088978da8f9aea479ffc7f631370b968d2e855eeb172bea7f6c7a04262bb6d", "label": "https://latest.datasette.io/fixtures.blob?sql=select+data+from+binary_data&_blob_column=data&_blob_hash=f3088978da8f9aea479ffc7f631370b968d2e855eeb172bea7f6c7a04262bb6d"}]


id: binary_data:binary-plugins
page: binary_data
ref: binary-plugins
title: Binary plugins
breadcrumbs: ["Binary data"]
content:
Several Datasette plugins are available that change the way Datasette treats binary data. 
             
                 
                     datasette-render-binary  modifies Datasette's default interface to show an automatic guess at what type of binary data is being stored, along with a visual representation of the binary value that displays ASCII strings directly in the interface. 
                 
                 
                     datasette-render-images  detects common image formats and renders them as images directly in the Datasette interface. 
                 
                 
                     datasette-media  allows Datasette interfaces to be configured to serve binary files from configured SQL queries, and includes the ability to resize images directly before serving them.

references:
[{"href": "https://github.com/simonw/datasette-render-binary", "label": "datasette-render-binary"}, {"href": "https://github.com/simonw/datasette-render-images", "label": "datasette-render-images"}, {"href": "https://github.com/simonw/datasette-media", "label": "datasette-media"}]


id: settings:using-setting
page: settings
ref: using-setting
title: Using --setting
breadcrumbs: ["Settings"]
references: []
content:
Datasette supports a number of settings. These can be set using the  --setting name value  option to  datasette serve . 
             You can set multiple settings at once like this: 
             datasette mydatabase.db \
  --setting default_page_size 50 \
  --setting sql_time_limit_ms 3500 \
  --setting max_returned_rows 2000 
             Settings can also be specified  in the database.yaml configuration file .


id: settings:config-dir
page: settings
ref: config-dir
title: Configuration directory mode
breadcrumbs: ["Settings"]
references: []
content:
Normally you configure Datasette using command-line options. For a Datasette instance with custom templates, custom plugins, a static directory and several databases this can get quite verbose: 
             datasette one.db two.db \
  --metadata=metadata.json \
  --template-dir=templates/ \
  --plugins-dir=plugins \
  --static css:css 
             As an alternative to this, you can run Datasette in  configuration directory  mode. Create a directory with the following structure: 
             # In a directory called my-app:
my-app/one.db
my-app/two.db
my-app/datasette.yaml
my-app/metadata.json
my-app/templates/index.html
my-app/plugins/my_plugin.py
my-app/static/my.css 
             Now start Datasette by providing the path to that directory: 
             datasette my-app/ 
             Datasette will detect the files in that directory and automatically configure itself using them. It will serve all  *.db  files that it finds, will load  metadata.json  if it exists, and will load the  templates ,  plugins  and  static  folders if they are present. 
             The files that can be included in this directory are as follows. All are optional. 
             
                 
                     *.db  (or  *.sqlite3  or  *.sqlite ) - SQLite database files that will be served by Datasette 
                 
                 
                     datasette.yaml  -  Configuration  for the Datasette instance 
                 
                 
                     metadata.json  -  Metadata  for those databases -  metadata.yaml  or  metadata.yml  can be used as well 
                 
                 
                     inspect-data.json  - the result of running  datasette inspect *.db --inspect-file=inspect-data.json  from the configuration directory - any database files listed here will be treated as immutable, so they should not be changed while Datasette is running 
                 
                 
                     templates/  - a directory containing  Custom templates 
                 
                 
                     plugins/  - a directory containing plugins, see  Writing one-off plugins 
                 
                 
                     static/  - a directory containing static files - these will be served from  /static/filename.txt , see  Serving static files


id: settings:id2
page: settings
ref: id2
title: Settings
breadcrumbs: ["Settings"]
references: []
content:
The following options can be set using  --setting name value , or by storing them in the  settings.json  file for use with  Configuration directory mode .


id: settings:setting-default-allow-sql
page: settings
ref: setting-default-allow-sql
title: default_allow_sql
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Should users be able to execute arbitrary SQL queries by default? 
                 Setting this to  off  causes permission checks for  execute-sql  to fail by default. 
                 datasette mydatabase.db --setting default_allow_sql off 
                 Another way to achieve this is to add  "allow_sql": false  to your  datasette.yaml  file, as described in  Controlling the ability to execute arbitrary SQL . This setting offers a more convenient way to do this.


id: settings:setting-default-page-size
page: settings
ref: setting-default-page-size
title: default_page_size
breadcrumbs: ["Settings", "Settings"]
references: []
content:
The default number of rows returned by the table page. You can over-ride this on a per-page basis using the  ?_size=80  query string parameter, provided you do not specify a value higher than the  max_returned_rows  setting. You can set this default using  --setting  like so: 
                 datasette mydatabase.db --setting default_page_size 50


id: settings:setting-sql-time-limit-ms
page: settings
ref: setting-sql-time-limit-ms
title: sql_time_limit_ms
breadcrumbs: ["Settings", "Settings"]
references: []
content:
By default, queries have a time limit of one second. If a query takes longer than this to run Datasette will terminate the query and return an error. 
                 If this time limit is too short for you, you can customize it using the  sql_time_limit_ms  limit - for example, to increase it to 3.5 seconds: 
                 datasette mydatabase.db --setting sql_time_limit_ms 3500 
                 You can optionally set a lower time limit for an individual query using the  ?_timelimit=100  query string argument: 
                 /my-database/my-table?qSpecies=44&_timelimit=100 
                 This would set the time limit to 100ms for that specific query. This feature is useful if you are working with databases of unknown size and complexity - a query that might make perfect sense for a smaller table could take too long to execute on a table with millions of rows. By setting custom time limits you can execute queries "optimistically" - e.g. give me an exact count of rows matching this query but only if it takes less than 100ms to calculate.


id: settings:setting-max-returned-rows
page: settings
ref: setting-max-returned-rows
title: max_returned_rows
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Datasette returns a maximum of 1,000 rows of data at a time. If you execute a query that returns more than 1,000 rows, Datasette will return the first 1,000 and include a warning that the result set has been truncated. You can use OFFSET/LIMIT or other methods in your SQL to implement pagination if you need to return more than 1,000 rows. 
                 You can increase or decrease this limit like so: 
                 datasette mydatabase.db --setting max_returned_rows 2000


id: settings:setting-max-insert-rows
page: settings
ref: setting-max-insert-rows
title: max_insert_rows
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Maximum rows that can be inserted at a time using the bulk insert API, see  Inserting rows . Defaults to 100. 
                 You can increase or decrease this limit like so: 
                 datasette mydatabase.db --setting max_insert_rows 1000


id: settings:setting-num-sql-threads
page: settings
ref: setting-num-sql-threads
title: num_sql_threads
breadcrumbs: ["Settings", "Settings"]
references: [{"href": "https://pyodide.org/", "label": "Pyodide"}]
content:
Maximum number of threads in the thread pool Datasette uses to execute SQLite queries. Defaults to 3. 
                 datasette mydatabase.db --setting num_sql_threads 10 
                 Setting this to 0 turns off threaded SQL queries entirely - useful for environments that do not support threading such as  Pyodide .


id: settings:setting-allow-facet
page: settings
ref: setting-allow-facet
title: allow_facet
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Allow users to specify columns they would like to facet on using the  ?_facet=COLNAME  URL parameter to the table view. 
                 This is enabled by default. If disabled, facets will still be displayed if they have been specifically enabled in  metadata.json  configuration for the table. 
                 Here's how to disable this feature: 
                 datasette mydatabase.db --setting allow_facet off


id: settings:setting-default-facet-size
page: settings
ref: setting-default-facet-size
title: default_facet_size
breadcrumbs: ["Settings", "Settings"]
references: []
content:
The default number of unique rows returned by  Facets  is 30. You can customize it like this: 
                 datasette mydatabase.db --setting default_facet_size 50


id: settings:setting-facet-time-limit-ms
page: settings
ref: setting-facet-time-limit-ms
title: facet_time_limit_ms
breadcrumbs: ["Settings", "Settings"]
references: []
content:
This is the time limit Datasette allows for calculating a facet, which defaults to 200ms: 
                 datasette mydatabase.db --setting facet_time_limit_ms 1000


id: settings:setting-facet-suggest-time-limit-ms
page: settings
ref: setting-facet-suggest-time-limit-ms
title: facet_suggest_time_limit_ms
breadcrumbs: ["Settings", "Settings"]
references: []
content:
When Datasette calculates suggested facets it needs to run a SQL query for every column in your table. The default for this time limit is 50ms to account for the fact that it needs to run once for every column. If the time limit is exceeded the column will not be suggested as a facet. 
                 You can increase this time limit like so: 
                 datasette mydatabase.db --setting facet_suggest_time_limit_ms 500


id: settings:setting-suggest-facets
page: settings
ref: setting-suggest-facets
title: suggest_facets
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Should Datasette calculate suggested facets? On by default, turn this off like so: 
                 datasette mydatabase.db --setting suggest_facets off


id: settings:setting-allow-download
page: settings
ref: setting-allow-download
title: allow_download
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Should users be able to download the original SQLite database using a link on the database index page? This is turned on by default. However, databases can only be downloaded if they are served in immutable mode and not in-memory. If downloading is unavailable for either of these reasons, the download link is hidden even if  allow_download  is on. To disable database downloads, use the following: 
                 datasette mydatabase.db --setting allow_download off


id: settings:setting-allow-signed-tokens
page: settings
ref: setting-allow-signed-tokens
title: allow_signed_tokens
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Should users be able to create signed API tokens to access Datasette? 
                 This is turned on by default. Use the following to turn it off: 
                 datasette mydatabase.db --setting allow_signed_tokens off 
                 Turning this setting off will disable the  /-/create-token  page,  described here . It will also cause any incoming  Authorization: Bearer dstok_...  API tokens to be ignored.


id: settings:setting-max-signed-tokens-ttl
page: settings
ref: setting-max-signed-tokens-ttl
title: max_signed_tokens_ttl
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Maximum allowed expiry time for signed API tokens created by users. 
                 Defaults to  0  which means no limit - tokens can be created that will never expire. 
                 Set this to a value in seconds to limit the maximum expiry time. For example, to set that limit to 24 hours you would use: 
                 datasette mydatabase.db --setting max_signed_tokens_ttl 86400 
                 This setting is enforced when incoming tokens are processed.


id: settings:setting-default-cache-ttl
page: settings
ref: setting-default-cache-ttl
title: default_cache_ttl
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Default HTTP caching max-age header in seconds, used for  Cache-Control: max-age=X . Can be over-ridden on a per-request basis using the  ?_ttl=  query string parameter. Set this to  0  to disable HTTP caching entirely. Defaults to 5 seconds. 
                 datasette mydatabase.db --setting default_cache_ttl 60


id: settings:setting-cache-size-kb
page: settings
ref: setting-cache-size-kb
title: cache_size_kb
breadcrumbs: ["Settings", "Settings"]
references: [{"href": "https://www.sqlite.org/pragma.html#pragma_cache_size", "label": "per-connection cache"}]
content:
Sets the amount of memory SQLite uses for its  per-connection cache , in KB. 
                 datasette mydatabase.db --setting cache_size_kb 5000


id: settings:setting-allow-csv-stream
page: settings
ref: setting-allow-csv-stream
title: allow_csv_stream
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Enables  the CSV export feature  where an entire table
                    (potentially hundreds of thousands of rows) can be exported as a single CSV
                    file. This is turned on by default - you can turn it off like this: 
                 datasette mydatabase.db --setting allow_csv_stream off


id: settings:setting-max-csv-mb
page: settings
ref: setting-max-csv-mb
title: max_csv_mb
breadcrumbs: ["Settings", "Settings"]
references: []
content:
The maximum size of CSV that can be exported, in megabytes. Defaults to 100MB.
                    You can disable the limit entirely by settings this to 0: 
                 datasette mydatabase.db --setting max_csv_mb 0


id: settings:setting-truncate-cells-html
page: settings
ref: setting-truncate-cells-html
title: truncate_cells_html
breadcrumbs: ["Settings", "Settings"]
references: []
content:
In the HTML table view, truncate any strings that are longer than this value.
                    The full value will still be available in CSV, JSON and on the individual row
                    HTML page. Set this to 0 to disable truncation. 
                 datasette mydatabase.db --setting truncate_cells_html 0


id: settings:setting-force-https-urls
page: settings
ref: setting-force-https-urls
title: force_https_urls
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Forces self-referential URLs in the JSON output to always use the  https:// 
                    protocol. This is useful for cases where the application itself is hosted using
                    HTTP but is served to the outside world via a proxy that enables HTTPS. 
                 datasette mydatabase.db --setting force_https_urls 1


id: settings:setting-template-debug
page: settings
ref: setting-template-debug
title: template_debug
breadcrumbs: ["Settings", "Settings"]
content:
This setting enables template context debug mode, which is useful to help understand what variables are available to custom templates when you are writing them. 
                 Enable it like this: 
                 datasette mydatabase.db --setting template_debug 1 
                 Now you can add  ?_context=1  or  &_context=1  to any Datasette page to see the context that was passed to that template. 
                 Some examples: 
                 
                     
                         https://latest.datasette.io/?_context=1 
                     
                     
                         https://latest.datasette.io/fixtures?_context=1 
                     
                     
                         https://latest.datasette.io/fixtures/roadside_attractions?_context=1

references:
[{"href": "https://latest.datasette.io/?_context=1", "label": "https://latest.datasette.io/?_context=1"}, {"href": "https://latest.datasette.io/fixtures?_context=1", "label": "https://latest.datasette.io/fixtures?_context=1"}, {"href": "https://latest.datasette.io/fixtures/roadside_attractions?_context=1", "label": "https://latest.datasette.io/fixtures/roadside_attractions?_context=1"}]


id: settings:setting-trace-debug
page: settings
ref: setting-trace-debug
title: trace_debug
breadcrumbs: ["Settings", "Settings"]
content:
This setting enables appending  ?_trace=1  to any page in order to see the SQL queries and other trace information that was used to generate that page. 
                 Enable it like this: 
                 datasette mydatabase.db --setting trace_debug 1 
                 Some examples: 
                 
                     
                         https://latest.datasette.io/?_trace=1 
                     
                     
                         https://latest.datasette.io/fixtures/roadside_attractions?_trace=1 
                     
                 
                 See  datasette.tracer  for details on how to hook into this mechanism as a plugin author.

references:
[{"href": "https://latest.datasette.io/?_trace=1", "label": "https://latest.datasette.io/?_trace=1"}, {"href": "https://latest.datasette.io/fixtures/roadside_attractions?_trace=1", "label": "https://latest.datasette.io/fixtures/roadside_attractions?_trace=1"}]


id: settings:setting-base-url
page: settings
ref: setting-base-url
title: base_url
breadcrumbs: ["Settings", "Settings"]
references: []
content:
If you are running Datasette behind a proxy, it may be useful to change the root path used for the Datasette instance. 
                 For example, if you are sending traffic from  https://www.example.com/tools/datasette/  through to a proxied Datasette instance you may wish Datasette to use  /tools/datasette/  as its root URL. 
                 You can do that like so: 
                 datasette mydatabase.db --setting base_url /tools/datasette/


id: settings:setting-secret
page: settings
ref: setting-secret
title: Configuring the secret
breadcrumbs: ["Settings"]
references: []
content:
Datasette uses a secret string to sign secure values such as cookies. 
             If you do not provide a secret, Datasette will create one when it starts up. This secret will reset every time the Datasette server restarts though, so things like authentication cookies and  API tokens  will not stay valid between restarts. 
             You can pass a secret to Datasette in two ways: with the  --secret  command-line option or by setting a  DATASETTE_SECRET  environment variable. 
             datasette mydb.db --secret=SECRET_VALUE_HERE 
             Or: 
             export DATASETTE_SECRET=SECRET_VALUE_HERE
datasette mydb.db 
             One way to generate a secure random secret is to use Python like this: 
             python3 -c 'import secrets; print(secrets.token_hex(32))'
cdb19e94283a20f9d42cca50c5a4871c0aa07392db308755d60a1a5b9bb0fa52 
             Plugin authors make use of this signing mechanism in their plugins using  .sign(value, namespace="default")  and  .unsign(value, namespace="default") .


id: settings:setting-publish-secrets
page: settings
ref: setting-publish-secrets
title: Using secrets with datasette publish
breadcrumbs: ["Settings"]
references: []
content:
The  datasette publish  and  datasette package  commands both generate a secret for you automatically when Datasette is deployed. 
             This means that every time you deploy a new version of a Datasette project, a new secret will be generated. This will cause signed cookies to become invalid on every fresh deploy. 
             You can fix this by creating a secret that will be used for multiple deploys and passing it using the  --secret  option: 
             datasette publish cloudrun mydb.db --service=my-service --secret=cdb19e94283a20f9d42cca5


id: performance:performance
page: performance
ref: performance
title: Performance and caching
breadcrumbs: []
references: []
content:
Datasette runs on top of SQLite, and SQLite has excellent performance.  For small databases almost any query should return in just a few milliseconds, and larger databases (100s of MBs or even GBs of data) should perform extremely well provided your queries make sensible use of database indexes. 
         That said, there are a number of tricks you can use to improve Datasette's performance.


id: performance:performance-immutable-mode
page: performance
ref: performance-immutable-mode
title: Immutable mode
breadcrumbs: ["Performance and caching"]
references: []
content:
If you can be certain that a SQLite database file will not be changed by another process you can tell Datasette to open that file in  immutable mode . 
             Doing so will disable all locking and change detection, which can result in improved query performance. 
             This also enables further optimizations relating to HTTP caching, described below. 
             To open a file in immutable mode pass it to the datasette command using the  -i  option: 
             datasette -i data.db 
             When you open a file in immutable mode like this Datasette will also calculate and cache the row counts for each table in that database when it first starts up, further improving performance.


id: performance:performance-inspect
page: performance
ref: performance-inspect
title: Using "datasette inspect"
breadcrumbs: ["Performance and caching"]
references: []
content:
Counting the rows in a table can be a very expensive operation on larger databases. In immutable mode Datasette performs this count only once and caches the results, but this can still cause server startup time to increase by several seconds or more. 
             If you know that a database is never going to change you can precalculate the table row counts once and store then in a JSON file, then use that file when you later start the server. 
             To create a JSON file containing the calculated row counts for a database, use the following: 
             datasette inspect data.db --inspect-file=counts.json 
             Then later you can start Datasette against the  counts.json  file and use it to skip the row counting step and speed up server startup: 
             datasette -i data.db --inspect-file=counts.json 
             You need to use the  -i  immutable mode against the database file here or the counts from the JSON file will be ignored. 
             You will rarely need to use this optimization in every-day use, but several of the  datasette publish  commands described in  Publishing data  use this optimization for better performance when deploying a database file to a hosting provider.


id: performance:http-caching
page: performance
ref: http-caching
title: HTTP caching
breadcrumbs: ["Performance and caching"]
content:
If your database is immutable and guaranteed not to change, you can gain major performance improvements from Datasette by enabling HTTP caching. 
             This can work at two different levels. First, it can tell browsers to cache the results of queries and serve future requests from the browser cache. 
             More significantly, it allows you to run Datasette behind a caching proxy such as  Varnish  or use a cache provided by a hosted service such as  Fastly  or  Cloudflare . This can provide incredible speed-ups since a query only needs to be executed by Datasette the first time it is accessed - all subsequent hits can then be served by the cache. 
             Using a caching proxy in this way could enable a Datasette-backed visualization to serve thousands of hits a second while running Datasette itself on extremely inexpensive hosting. 
             Datasette's integration with HTTP caches can be enabled using a combination of configuration options and query string arguments. 
             The  default_cache_ttl  setting sets the default HTTP cache TTL for all Datasette pages. This is 5 seconds unless you change it - you can set it to 0 if you wish to disable HTTP caching entirely. 
             You can also change the cache timeout on a per-request basis using the  ?_ttl=10  query string parameter. This can be useful when you are working with the Datasette JSON API - you may decide that a specific query can be cached for a longer time, or maybe you need to set  ?_ttl=0  for some requests for example if you are running a SQL  order by random()  query.

references:
[{"href": "https://varnish-cache.org/", "label": "Varnish"}, {"href": "https://www.fastly.com/", "label": "Fastly"}, {"href": "https://www.cloudflare.com/", "label": "Cloudflare"}]


id: performance:performance-hashed-urls
page: performance
ref: performance-hashed-urls
title: datasette-hashed-urls
breadcrumbs: ["Performance and caching"]
content:
If you open a database file in immutable mode using the  -i  option, you can be assured that the content of that database will not change for the lifetime of the Datasette server. 
             The  datasette-hashed-urls plugin  implements an optimization where your database is served with part of the SHA-256 hash of the database contents baked into the URL. 
             A database at  /fixtures  will instead be served at  /fixtures-aa7318b , and a year-long cache expiry header will be returned with those pages. 
             This will then be cached by both browsers and caching proxies such as Cloudflare or Fastly, providing a potentially significant performance boost. 
             To install the plugin, run the following: 
             datasette install datasette-hashed-urls 
             
                 Prior to Datasette 0.61 hashed URL mode was a core Datasette feature, enabled using the  hash_urls  setting. This implementation has now been removed in favor of the  datasette-hashed-urls  plugin. 
                 Prior to Datasette 0.28 hashed URL mode was the default behaviour for Datasette, since all database files were assumed to be immutable and unchanging. From 0.28 onwards the default has been to treat database files as mutable unless explicitly configured otherwise.

references:
[{"href": "https://datasette.io/plugins/datasette-hashed-urls", "label": "datasette-hashed-urls plugin"}]


id: contributing:id1
page: contributing
ref: id1
title: Contributing
breadcrumbs: []
references: []
content:
Datasette is an open source project. We welcome contributions! 
         This document describes how to contribute to Datasette core. You can also contribute to the wider Datasette ecosystem by creating new  Plugins .


id: contributing:general-guidelines
page: contributing
ref: general-guidelines
title: General guidelines
breadcrumbs: ["Contributing"]
references: []
content:
main should always be releasable . Incomplete features should live in branches. This ensures that any small bug fixes can be quickly released. 
                 
                 
                     The ideal commit  should bundle together the implementation, unit tests and associated documentation updates. The commit message should link to an associated issue. 
                 
                 
                     New plugin hooks  should only be shipped if accompanied by a separate release of a non-demo plugin that uses them.


id: contributing:devenvironment
page: contributing
ref: devenvironment
title: Setting up a development environment
breadcrumbs: ["Contributing"]
content:
If you have Python 3.8 or higher installed on your computer (on OS X the quickest way to do this  is using homebrew ) you can install an editable copy of Datasette using the following steps. 
             If you want to use GitHub to publish your changes, first  create a fork of datasette  under your own GitHub account. 
             Now clone that repository somewhere on your computer: 
             git clone git@github.com:YOURNAME/datasette 
             If you want to get started without creating your own fork, you can do this instead: 
             git clone git@github.com:simonw/datasette 
             The next step is to create a virtual environment for your project and use it to install Datasette's dependencies: 
             cd datasette
# Create a virtual environment in ./venv
python3 -m venv ./venv
# Now activate the virtual environment, so pip can install into it
source venv/bin/activate
# Install Datasette and its testing dependencies
python3 -m pip install -e '.[test]' 
             That last line does most of the work:  pip install -e  means "install this package in a way that allows me to edit the source code in place". The  .[test]  option means "use the setup.py in this directory and install the optional testing dependencies as well".

references:
[{"href": "https://docs.python-guide.org/starting/install3/osx/", "label": "is using homebrew"}, {"href": "https://github.com/simonw/datasette/fork", "label": "create a fork of datasette"}]


id: contributing:contributing-running-tests
page: contributing
ref: contributing-running-tests
title: Running the tests
breadcrumbs: ["Contributing"]
content:
Once you have done this, you can run the Datasette unit tests from inside your  datasette/  directory using  pytest  like so: 
             pytest 
             You can run the tests faster using multiple CPU cores with  pytest-xdist  like this: 
             pytest -n auto -m "not serial" 
             -n auto  detects the number of available cores automatically. The  -m "not serial"  skips tests that don't work well in a parallel test environment. You can run those tests separately like so: 
             pytest -m "serial"

references:
[{"href": "https://docs.pytest.org/", "label": "pytest"}, {"href": "https://pypi.org/project/pytest-xdist/", "label": "pytest-xdist"}]


id: contributing:contributing-using-fixtures
page: contributing
ref: contributing-using-fixtures
title: Using fixtures
breadcrumbs: ["Contributing"]
references: []
content:
To run Datasette itself, type  datasette . 
             You're going to need at least one SQLite database. A quick way to get started is to use the fixtures database that Datasette uses for its own tests. 
             You can create a copy of that database by running this command: 
             python tests/fixtures.py fixtures.db 
             Now you can run Datasette against the new fixtures database like so: 
             datasette fixtures.db 
             This will start a server at  http://127.0.0.1:8001/ . 
             Any changes you make in the  datasette/templates  or  datasette/static  folder will be picked up immediately (though you may need to do a force-refresh in your browser to see changes to CSS or JavaScript). 
             If you want to change Datasette's Python code you can use the  --reload  option to cause Datasette to automatically reload any time the underlying code changes: 
             datasette --reload fixtures.db 
             You can also use the  fixtures.py  script to recreate the testing version of  metadata.json  used by the unit tests. To do that: 
             python tests/fixtures.py fixtures.db fixtures-metadata.json 
             Or to output the plugins used by the tests, run this: 
             python tests/fixtures.py fixtures.db fixtures-metadata.json fixtures-plugins
Test tables written to fixtures.db
- metadata written to fixtures-metadata.json
Wrote plugin: fixtures-plugins/register_output_renderer.py
Wrote plugin: fixtures-plugins/view_name.py
Wrote plugin: fixtures-plugins/my_plugin.py
Wrote plugin: fixtures-plugins/messages_output_renderer.py
Wrote plugin: fixtures-plugins/my_plugin_2.py 
             Then run Datasette like this: 
             datasette fixtures.db -m fixtures-metadata.json --plugins-dir=fixtures-plugins/


id: contributing:contributing-debugging
page: contributing
ref: contributing-debugging
title: Debugging
breadcrumbs: ["Contributing"]
references: []
content:
Any errors that occur while Datasette is running while display a stack trace on the console. 
             You can tell Datasette to open an interactive  pdb  debugger session if an error occurs using the  --pdb  option: 
             datasette --pdb fixtures.db


id: contributing:contributing-formatting
page: contributing
ref: contributing-formatting
title: Code formatting
breadcrumbs: ["Contributing"]
content:
Datasette uses opinionated code formatters:  Black  for Python and  Prettier  for JavaScript. 
             These formatters are enforced by Datasette's continuous integration: if a commit includes Python or JavaScript code that does not match the style enforced by those tools, the tests will fail. 
             When developing locally, you can verify and correct the formatting of your code using these tools.

references:
[{"href": "https://github.com/psf/black", "label": "Black"}, {"href": "https://prettier.io/", "label": "Prettier"}]


id: contributing:contributing-formatting-black
page: contributing
ref: contributing-formatting-black
title: Running Black
breadcrumbs: ["Contributing", "Code formatting"]
references: []
content:
Black will be installed when you run  pip install -e '.[test]' . To test that your code complies with Black, run the following in your root  datasette  repository checkout: 
                 black . --check 
                 All done! ✨ 🍰 ✨
95 files would be left unchanged. 
                 If any of your code does not conform to Black you can run this to automatically fix those problems: 
                 black . 
                 reformatted ../datasette/setup.py
All done! ✨ 🍰 ✨
1 file reformatted, 94 files left unchanged.


id: contributing:contributing-formatting-blacken-docs
page: contributing
ref: contributing-formatting-blacken-docs
title: blacken-docs
breadcrumbs: ["Contributing", "Code formatting"]
references: [{"href": "https://pypi.org/project/blacken-docs/", "label": "blacken-docs"}]
content:
The  blacken-docs  command applies Black formatting rules to code examples in the documentation. Run it like this: 
                 blacken-docs -l 60 docs/*.rst


id: contributing:contributing-formatting-prettier
page: contributing
ref: contributing-formatting-prettier
title: Prettier
breadcrumbs: ["Contributing", "Code formatting"]
references: [{"href": "https://nodejs.org/en/download/package-manager/", "label": "install Node.js"}]
content:
To install Prettier,  install Node.js  and then run the following in the root of your  datasette  repository checkout: 
                 npm install 
                 This will install Prettier in a  node_modules  directory. You can then check that your code matches the coding style like so: 
                 npm run prettier -- --check 
                 > prettier
> prettier 'datasette/static/*[!.min].js' "--check"

Checking formatting...
[warn] datasette/static/plugins.js
[warn] Code style issues found in the above file(s). Forgot to run Prettier? 
                 You can fix any problems by running: 
                 npm run fix


id: contributing:contributing-documentation
page: contributing
ref: contributing-documentation
title: Editing and building the documentation
breadcrumbs: ["Contributing"]
content:
Datasette's documentation lives in the  docs/  directory and is deployed automatically using  Read The Docs . 
             The documentation is written using reStructuredText. You may find this article on  The subset of reStructuredText worth committing to memory  useful. 
             You can build it locally by installing  sphinx  and  sphinx_rtd_theme  in your Datasette development environment and then running  make html  directly in the  docs/  directory: 
             # You may first need to activate your virtual environment:
source venv/bin/activate

# Install the dependencies needed to build the docs
pip install -e .[docs]

# Now build the docs
cd docs/
make html 
             This will create the HTML version of the documentation in  docs/_build/html . You can open it in your browser like so: 
             open _build/html/index.html 
             Any time you make changes to a  .rst  file you can re-run  make html  to update the built documents, then refresh them in your browser. 
             For added productivity, you can use use  sphinx-autobuild  to run Sphinx in auto-build mode. This will run a local webserver serving the docs that automatically rebuilds them and refreshes the page any time you hit save in your editor. 
             sphinx-autobuild  will have been installed when you ran  pip install -e .[docs] . In your  docs/  directory you can start the server by running the following: 
             make livehtml 
             Now browse to  http://localhost:8000/  to view the documentation. Any edits you make should be instantly reflected in your browser.

references:
[{"href": "https://readthedocs.org/", "label": "Read The Docs"}, {"href": "https://simonwillison.net/2018/Aug/25/restructuredtext/", "label": "The subset of reStructuredText worth committing to memory"}, {"href": "https://pypi.org/project/sphinx-autobuild/", "label": "sphinx-autobuild"}]


id: contributing:contributing-documentation-cog
page: contributing
ref: contributing-documentation-cog
title: Running Cog
breadcrumbs: ["Contributing", "Editing and building the documentation"]
references: [{"href": "https://github.com/nedbat/cog", "label": "Cog"}]
content:
Some pages of documentation (in particular the  CLI reference ) are automatically updated using  Cog . 
                 To update these pages, run the following command: 
                 cog -r docs/*.rst


id: contributing:contributing-continuous-deployment
page: contributing
ref: contributing-continuous-deployment
title: Continuously deployed demo instances
breadcrumbs: ["Contributing"]
content:
The demo instance at  latest.datasette.io  is re-deployed automatically to Google Cloud Run for every push to  main  that passes the test suite. This is implemented by the GitHub Actions workflow at  .github/workflows/deploy-latest.yml . 
             Specific branches can also be set to automatically deploy by adding them to the  on: push: branches  block at the top of the workflow YAML file. Branches configured in this way will be deployed to a new Cloud Run service whether or not their tests pass. 
             The Cloud Run URL for a branch demo can be found in the GitHub Actions logs.

references:
[{"href": "https://latest.datasette.io/", "label": "latest.datasette.io"}, {"href": "https://github.com/simonw/datasette/blob/main/.github/workflows/deploy-latest.yml", "label": ".github/workflows/deploy-latest.yml"}]


id: contributing:contributing-release
page: contributing
ref: contributing-release
title: Release process
breadcrumbs: ["Contributing"]
content:
Datasette releases are performed using tags. When a new release is published on GitHub, a  GitHub Action workflow  will perform the following: 
             
                 
                     Run the unit tests against all supported Python versions. If the tests pass... 
                 
                 
                     Build a Docker image of the release and push a tag to  https://hub.docker.com/r/datasetteproject/datasette 
                 
                 
                     Re-point the "latest" tag on Docker Hub to the new image 
                 
                 
                     Build a wheel bundle of the underlying Python source code 
                 
                 
                     Push that new wheel up to PyPI:  https://pypi.org/project/datasette/ 
                 
                 
                     If the release is an alpha, navigate to  https://readthedocs.org/projects/datasette/versions/  and search for the tag name in the "Activate a version" filter, then mark that version as "active" to ensure it will appear on the public ReadTheDocs documentation site. 
                 
             
             To deploy new releases you will need to have push access to the main Datasette GitHub repository. 
             Datasette follows  Semantic Versioning : 
             major.minor.patch 
             We increment  major  for backwards-incompatible releases. Datasette is currently pre-1.0 so the major version is always  0 . 
             We increment  minor  for new features. 
             We increment  patch  for bugfix releass. 
             Alpha and beta releases  may have an additional  a0  or  b0  prefix - the integer component will be incremented with each subsequent alpha or beta. 
             To release a new version, first create a commit that updates the version number in  datasette/version.py  and the  the changelog  with highlights of the new version. An example  commit can be seen here : 
             # Update changelog
git commit -m " Release 0.51a1

Refs #1056, #1039, #998, #1045, #1033, #1036, #1034, #976, #1057, #1058, #1053, #1064, #1066" -a
git push 
             Referencing the issues that are part of the release in the commit message ensures the name of the release shows up on those issue pages, e.g.  here . 
             You can generate the list of issue references for a specific release by copying and pasting text from the release notes or GitHub changes-since-last-release view into this  Extract issue numbers from pasted text  tool. 
             To create the tag for the release, create  a new release  on GitHub matching the new version number. You can convert the release notes to Markdown by copying and pasting the rendered HTML into this  Paste to Markdown tool . 
             Don't forget to create the release from the correct branch - usually  main , but sometimes  0.64.x  or similar for a bugfix release. 
             While the release is running you can confirm that the correct commits made it into the release using the  https://github.com/simonw/datasette/compare/0.64.6...0.64.7  URL. 
             Finally, post a news item about the release on  datasette.io  by editing the  news.yaml  file in that site's repository.

references:
[{"href": "https://github.com/simonw/datasette/blob/main/.github/workflows/deploy-latest.yml", "label": "GitHub Action workflow"}, {"href": "https://hub.docker.com/r/datasetteproject/datasette", "label": "https://hub.docker.com/r/datasetteproject/datasette"}, {"href": "https://pypi.org/project/datasette/", "label": "https://pypi.org/project/datasette/"}, {"href": "https://readthedocs.org/projects/datasette/versions/", "label": "https://readthedocs.org/projects/datasette/versions/"}, {"href": "https://semver.org/", "label": "Semantic Versioning"}, {"href": "https://github.com/simonw/datasette/commit/0e1e89c6ba3d0fbdb0823272952cf356f3016def", "label": "commit can be seen here"}, {"href": "https://github.com/simonw/datasette/issues/581#ref-commit-d56f402", "label": "here"}, {"href": "https://observablehq.com/@simonw/extract-issue-numbers-from-pasted-text", "label": "Extract issue numbers from pasted text"}, {"href": "https://github.com/simonw/datasette/releases/new", "label": "a new release"}, {"href": "https://euangoddard.github.io/clipboard2markdown/", "label": "Paste to Markdown tool"}, {"href": "https://github.com/simonw/datasette/compare/0.64.6...0.64.7", "label": "https://github.com/simonw/datasette/compare/0.64.6...0.64.7"}, {"href": "https://datasette.io/", "label": "datasette.io"}, {"href": "https://github.com/simonw/datasette.io/blob/main/news.yaml", "label": "news.yaml"}]


id: contributing:contributing-alpha-beta
page: contributing
ref: contributing-alpha-beta
title: Alpha and beta releases
breadcrumbs: ["Contributing"]
content:
Alpha and beta releases are published to preview upcoming features that may not yet be stable - in particular to preview new plugin hooks. 
             You are welcome to try these out, but please be aware that details may change before the final release. 
             Please join  discussions on the issue tracker  to share your thoughts and experiences with on alpha and beta features that you try out.

references:
[{"href": "https://github.com/simonw/datasette/issues", "label": "discussions on the issue tracker"}]


id: contributing:contributing-bug-fix-branch
page: contributing
ref: contributing-bug-fix-branch
title: Releasing bug fixes from a branch
breadcrumbs: ["Contributing"]
references: [{"href": "https://github.com/simonw/datasette/releases/new", "label": "Draft a new release"}]
content:
If it's necessary to publish a bug fix release without shipping new features that have landed on  main  a release branch can be used. 
             Create it from the relevant last tagged release like so: 
             git branch 0.52.x 0.52.4
git checkout 0.52.x 
             Next cherry-pick the commits containing the bug fixes: 
             git cherry-pick COMMIT 
             Write the release notes in the branch, and update the version number in  version.py . Then push the branch: 
             git push -u origin 0.52.x 
             Once the tests have completed, publish the release from that branch target using the GitHub  Draft a new release  form. 
             Finally, cherry-pick the commit with the release notes and version number bump across to  main : 
             git checkout main
git cherry-pick COMMIT
git push


id: contributing:contributing-upgrading-codemirror
page: contributing
ref: contributing-upgrading-codemirror
title: Upgrading CodeMirror
breadcrumbs: ["Contributing"]
content:
Datasette bundles  CodeMirror  for the SQL editing interface, e.g. on  this page . Here are the steps for upgrading to a new version of CodeMirror: 
             
                 
                     Install the packages with: 
                     npm i codemirror @codemirror/lang-sql 
                 
                 
                     Build the bundle using the version number from package.json with: 
                     node_modules/.bin/rollup datasette/static/cm-editor-6.0.1.js \
  -f iife \
  -n cm \
  -o datasette/static/cm-editor-6.0.1.bundle.js \
  -p @rollup/plugin-node-resolve \
  -p @rollup/plugin-terser 
                 
                 
                     Update the version reference in the  codemirror.html  template.

references:
[{"href": "https://codemirror.net/", "label": "CodeMirror"}, {"href": "https://latest.datasette.io/fixtures", "label": "this page"}]


id: internals:internals
page: internals
ref: internals
title: Internals for plugins
breadcrumbs: []
references: []
content:
Many  Plugin hooks  are passed objects that provide access to internal Datasette functionality. The interface to these objects should not be considered stable with the exception of methods that are documented here.


id: internals:internals-request
page: internals
ref: internals-request
title: Request object
breadcrumbs: ["Internals for plugins"]
content:
The request object is passed to various plugin hooks. It represents an incoming HTTP request. It has the following properties: 
             
                 
                     .scope  - dictionary 
                     
                         The ASGI scope that was used to construct this request, described in the  ASGI HTTP connection scope  specification. 
                     
                 
                 
                     .method  - string 
                     
                         The HTTP method for this request, usually  GET  or  POST . 
                     
                 
                 
                     .url  - string 
                     
                         The full URL for this request, e.g.  https://latest.datasette.io/fixtures . 
                     
                 
                 
                     .scheme  - string 
                     
                         The request scheme - usually  https  or  http . 
                     
                 
                 
                     .headers  - dictionary (str -> str) 
                     
                         A dictionary of incoming HTTP request headers. Header names have been converted to lowercase. 
                     
                 
                 
                     .cookies  - dictionary (str -> str) 
                     
                         A dictionary of incoming cookies 
                     
                 
                 
                     .host  - string 
                     
                         The host header from the incoming request, e.g.  latest.datasette.io  or  localhost . 
                     
                 
                 
                     .path  - string 
                     
                         The path of the request excluding the query string, e.g.  /fixtures . 
                     
                 
                 
                     .full_path  - string 
                     
                         The path of the request including the query string if one is present, e.g.  /fixtures?sql=select+sqlite_version() . 
                     
                 
                 
                     .query_string  - string 
                     
                         The query string component of the request, without the  ?  - e.g.  name__contains=sam&age__gt=10 . 
                     
                 
                 
                     .args  - MultiParams 
                     
                         An object representing the parsed query string parameters, see below. 
                     
                 
                 
                     .url_vars  - dictionary (str -> str) 
                     
                         Variables extracted from the URL path, if that path was defined using a regular expression. See  register_routes(datasette) . 
                     
                 
                 
                     .actor  - dictionary (str -> Any) or None 
                     
                         The currently authenticated actor (see  actors ), or  None  if the request is unauthenticated. 
                     
                 
             
             The object also has two awaitable methods: 
             
                 
                     await request.post_vars()  - dictionary 
                     
                         Returns a dictionary of form variables that were submitted in the request body via  POST . Don't forget to read about  CSRF protection ! 
                     
                 
                 
                     await request.post_body()  - bytes 
                     
                         Returns the un-parsed body of a request submitted by  POST  - useful for things like incoming JSON data. 
                     
                 
             
             And a class method that can be used to create fake request objects for use in tests: 
             
                 
                     fake(path_with_query_string, method="GET", scheme="http", url_vars=None) 
                     
                         Returns a  Request  instance for the specified path and method. For example: 
                         from datasette import Request
from pprint import pprint

request = Request.fake(
    "/fixtures/facetable/",
    url_vars={"database": "fixtures", "table": "facetable"},
)
pprint(request.scope) 
                         This outputs: 
                         {'http_version': '1.1',
 'method': 'GET',
 'path': '/fixtures/facetable/',
 'query_string': b'',
 'raw_path': b'/fixtures/facetable/',
 'scheme': 'http',
 'type': 'http',
 'url_route': {'kwargs': {'database': 'fixtures', 'table': 'facetable'}}}

references:
[{"href": "https://asgi.readthedocs.io/en/latest/specs/www.html#connection-scope", "label": "ASGI HTTP connection scope"}]


id: internals:internals-multiparams
page: internals
ref: internals-multiparams
title: The MultiParams class
breadcrumbs: ["Internals for plugins"]
references: []
content:
request.args  is a  MultiParams  object - a dictionary-like object which provides access to query string parameters that may have multiple values. 
             Consider the query string  ?foo=1&foo=2&bar=3  - with two values for  foo  and one value for  bar . 
             
                 
                     request.args[key]  - string 
                     
                         Returns the first value for that key, or raises a  KeyError  if the key is missing. For the above example  request.args["foo"]  would return  "1" . 
                     
                 
                 
                     request.args.get(key)  - string or None 
                     
                         Returns the first value for that key, or  None  if the key is missing. Pass a second argument to specify a different default, e.g.  q = request.args.get("q", "") . 
                     
                 
                 
                     request.args.getlist(key)  - list of strings 
                     
                         Returns the list of strings for that key.  request.args.getlist("foo")  would return  ["1", "2"]  in the above example.  request.args.getlist("bar")  would return  ["3"] . If the key is missing an empty list will be returned. 
                     
                 
                 
                     request.args.keys()  - list of strings 
                     
                         Returns the list of available keys - for the example this would be  ["foo", "bar"] . 
                     
                 
                 
                     key in request.args  - True or False 
                     
                         You can use  if key in request.args  to check if a key is present. 
                     
                 
                 
                     for key in request.args  - iterator 
                     
                         This lets you loop through every available key. 
                     
                 
                 
                     len(request.args)  - integer 
                     
                         Returns the number of keys.


id: internals:internals-response
page: internals
ref: internals-response
title: Response class
breadcrumbs: ["Internals for plugins"]
references: []
content:
The  Response  class can be returned from view functions that have been registered using the  register_routes(datasette)  hook. 
             The  Response()  constructor takes the following arguments: 
             
                 
                     body  - string 
                     
                         The body of the response. 
                     
                 
                 
                     status  - integer (optional) 
                     
                         The HTTP status - defaults to 200. 
                     
                 
                 
                     headers  - dictionary (optional) 
                     
                         A dictionary of extra HTTP headers, e.g.  {"x-hello": "world"} . 
                     
                 
                 
                     content_type  - string (optional) 
                     
                         The content-type for the response. Defaults to  text/plain . 
                     
                 
             
             For example: 
             from datasette.utils.asgi import Response

response = Response(
    "<xml>This is XML</xml>",
    content_type="application/xml; charset=utf-8",
) 
             The quickest way to create responses is using the  Response.text(...) ,  Response.html(...) ,  Response.json(...)  or  Response.redirect(...)  helper methods: 
             from datasette.utils.asgi import Response

html_response = Response.html("This is HTML")
json_response = Response.json({"this_is": "json"})
text_response = Response.text(
    "This will become utf-8 encoded text"
)
# Redirects are served as 302, unless you pass status=301:
redirect_response = Response.redirect(
    "https://latest.datasette.io/"
) 
             Each of these responses will use the correct corresponding content-type -  text/html; charset=utf-8 ,  application/json; charset=utf-8  or  text/plain; charset=utf-8  respectively. 
             Each of the helper methods take optional  status=  and  headers=  arguments, documented above.


id: internals:internals-response-asgi-send
page: internals
ref: internals-response-asgi-send
title: Returning a response with .asgi_send(send)
breadcrumbs: ["Internals for plugins", "Response class"]
references: []
content:
In most cases you will return  Response  objects from your own view functions. You can also use a  Response  instance to respond at a lower level via ASGI, for example if you are writing code that uses the  asgi_wrapper(datasette)  hook. 
                 Create a  Response  object and then use  await response.asgi_send(send) , passing the ASGI  send  function. For example: 
                 async def require_authorization(scope, receive, send):
    response = Response.text(
        "401 Authorization Required",
        headers={
            "www-authenticate": 'Basic realm="Datasette", charset="UTF-8"'
        },
        status=401,
    )
    await response.asgi_send(send)


id: internals:internals-response-set-cookie
page: internals
ref: internals-response-set-cookie
title: Setting cookies with response.set_cookie()
breadcrumbs: ["Internals for plugins", "Response class"]
references: []
content:
To set cookies on the response, use the  response.set_cookie(...)  method. The method signature looks like this: 
                 def set_cookie(
    self,
    key,
    value="",
    max_age=None,
    expires=None,
    path="/",
    domain=None,
    secure=False,
    httponly=False,
    samesite="lax",
): ... 
                 You can use this with  datasette.sign()  to set signed cookies. Here's how you would set the  ds_actor cookie  for use with Datasette  authentication : 
                 response = Response.redirect("/")
response.set_cookie(
    "ds_actor",
    datasette.sign({"a": {"id": "cleopaws"}}, "actor"),
)
return response


id: internals:internals-datasette
page: internals
ref: internals-datasette
title: Datasette class
breadcrumbs: ["Internals for plugins"]
references: []
content:
This object is an instance of the  Datasette  class, passed to many plugin hooks as an argument called  datasette . 
             You can create your own instance of this - for example to help write tests for a plugin - like so: 
             from datasette.app import Datasette

# With no arguments a single in-memory database will be attached
datasette = Datasette()

# The files= argument can load files from disk
datasette = Datasette(files=["/path/to/my-database.db"])

# Pass metadata as a JSON dictionary like this
datasette = Datasette(
    files=["/path/to/my-database.db"],
    metadata={
        "databases": {
            "my-database": {
                "description": "This is my database"
            }
        }
    },
) 
             Constructor parameters include: 
             
                 
                     files=[...]  - a list of database files to open 
                 
                 
                     immutables=[...]  - a list of database files to open in immutable mode 
                 
                 
                     metadata={...}  - a dictionary of  Metadata 
                 
                 
                     config_dir=...  - the  configuration directory  to use, stored in  datasette.config_dir


id: internals:datasette-databases
page: internals
ref: datasette-databases
title: .databases
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
Property exposing a  collections.OrderedDict  of databases currently connected to Datasette. 
                 The dictionary keys are the name of the database that is used in the URL - e.g.  /fixtures  would have a key of  "fixtures" . The values are  Database class  instances. 
                 All databases are listed, irrespective of user permissions.


id: internals:datasette-permissions
page: internals
ref: datasette-permissions
title: .permissions
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
Property exposing a dictionary of permissions that have been registered using the  register_permissions(datasette)  plugin hook. 
                 The dictionary keys are the permission names - e.g.  view-instance  - and the values are  Permission()  objects describing the permission. Here is a  description of that object .


id: internals:datasette-plugin-config
page: internals
ref: datasette-plugin-config
title: .plugin_config(plugin_name, database=None, table=None)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
plugin_name  - string 
                         
                             The name of the plugin to look up configuration for. Usually this is something similar to  datasette-cluster-map . 
                         
                     
                     
                         database  - None or string 
                         
                             The database the user is interacting with. 
                         
                     
                     
                         table  - None or string 
                         
                             The table the user is interacting with. 
                         
                     
                 
                 This method lets you read plugin configuration values that were set in   datasette.yaml . See  Writing plugins that accept configuration  for full details of how this method should be used. 
                 The return value will be the value from the configuration file - usually a dictionary. 
                 If the plugin is not configured the return value will be  None .


id: internals:datasette-render-template
page: internals
ref: datasette-render-template
title: await .render_template(template, context=None, request=None)
breadcrumbs: ["Internals for plugins", "Datasette class"]
content:
template  - string, list of strings or jinja2.Template 
                         
                             The template file to be rendered, e.g.  my_plugin.html . Datasette will search for this file first in the  --template-dir=  location, if it was specified - then in the plugin's bundled templates and finally in Datasette's set of default templates. 
                             If this is a list of template file names then the first one that exists will be loaded and rendered. 
                             If this is a Jinja  Template object  it will be used directly. 
                         
                     
                     
                         context  - None or a Python dictionary 
                         
                             The context variables to pass to the template. 
                         
                     
                     
                         request  - request object or None 
                         
                             If you pass a Datasette request object here it will be made available to the template. 
                         
                     
                 
                 Renders a  Jinja template  using Datasette's preconfigured instance of Jinja and returns the resulting string. The template will have access to Datasette's default template functions and any functions that have been made available by other plugins.

references:
[{"href": "https://jinja.palletsprojects.com/en/2.11.x/api/#jinja2.Template", "label": "Template object"}, {"href": "https://jinja.palletsprojects.com/en/2.11.x/", "label": "Jinja template"}]


id: internals:datasette-actors-from-ids
page: internals
ref: datasette-actors-from-ids
title: await .actors_from_ids(actor_ids)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
actor_ids  - list of strings or integers 
                         
                             A list of actor IDs to look up. 
                         
                     
                 
                 Returns a dictionary, where the keys are the IDs passed to it and the values are the corresponding actor dictionaries. 
                 This method is mainly designed to be used with plugins. See the  actors_from_ids(datasette, actor_ids)  documentation for details. 
                 If no plugins that implement that hook are installed, the default return value looks like this: 
                 {
    "1": {"id": "1"},
    "2": {"id": "2"}
}


id: internals:datasette-permission-allowed
page: internals
ref: datasette-permission-allowed
title: await .permission_allowed(actor, action, resource=None, default=...)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
actor  - dictionary 
                         
                             The authenticated actor. This is usually  request.actor . 
                         
                     
                     
                         action  - string 
                         
                             The name of the action that is being permission checked. 
                         
                     
                     
                         resource  - string or tuple, optional 
                         
                             The resource, e.g. the name of the database, or a tuple of two strings containing the name of the database and the name of the table. Only some permissions apply to a resource. 
                         
                     
                     
                         default  - optional: True, False or None 
                         
                             What value should be returned by default if nothing provides an opinion on this permission check.
                                Set to  True  for default allow or  False  for default deny.
                                If not specified the  default  from the  Permission()  tuple that was registered using  register_permissions(datasette)  will be used. 
                         
                     
                 
                 Check if the given actor has  permission  to perform the given action on the given resource. 
                 Some permission checks are carried out against  rules defined in datasette.yaml , while other custom permissions may be decided by plugins that implement the  permission_allowed(datasette, actor, action, resource)  plugin hook. 
                 If neither  metadata.json  nor any of the plugins provide an answer to the permission query the  default  argument will be returned. 
                 See  Built-in permissions  for a full list of permission actions included in Datasette core.


id: internals:datasette-ensure-permissions
page: internals
ref: datasette-ensure-permissions
title: await .ensure_permissions(actor, permissions)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
actor  - dictionary 
                         
                             The authenticated actor. This is usually  request.actor . 
                         
                     
                     
                         permissions  - list 
                         
                             A list of permissions to check. Each permission in that list can be a string  action  name or a 2-tuple of  (action, resource) . 
                         
                     
                 
                 This method allows multiple permissions to be checked at once. It raises a  datasette.Forbidden  exception if any of the checks are denied before one of them is explicitly granted. 
                 This is useful when you need to check multiple permissions at once. For example, an actor should be able to view a table if either one of the following checks returns  True  or not a single one of them returns  False : 
                 await datasette.ensure_permissions(
    request.actor,
    [
        ("view-table", (database, table)),
        ("view-database", database),
        "view-instance",
    ],
)


id: internals:datasette-check-visibility
page: internals
ref: datasette-check-visibility
title: await .check_visibility(actor, action=None, resource=None, permissions=None)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
actor  - dictionary 
                         
                             The authenticated actor. This is usually  request.actor . 
                         
                     
                     
                         action  - string, optional 
                         
                             The name of the action that is being permission checked. 
                         
                     
                     
                         resource  - string or tuple, optional 
                         
                             The resource, e.g. the name of the database, or a tuple of two strings containing the name of the database and the name of the table. Only some permissions apply to a resource. 
                         
                     
                     
                         permissions  - list of  action  strings or  (action, resource)  tuples, optional 
                         
                             Provide this instead of  action  and  resource  to check multiple permissions at once. 
                         
                     
                 
                 This convenience method can be used to answer the question "should this item be considered private, in that it is visible to me but it is not visible to anonymous users?" 
                 It returns a tuple of two booleans,  (visible, private) .  visible  indicates if the actor can see this resource.  private  will be  True  if an anonymous user would not be able to view the resource. 
                 This example checks if the user can access a specific table, and sets  private  so that a padlock icon can later be displayed: 
                 visible, private = await datasette.check_visibility(
    request.actor,
    action="view-table",
    resource=(database, table),
) 
                 The following example runs three checks in a row, similar to  await .ensure_permissions(actor, permissions) . If any of the checks are denied before one of them is explicitly granted then  visible  will be  False .  private  will be  True  if an anonymous user would not be able to view the resource. 
                 visible, private = await datasette.check_visibility(
    request.actor,
    permissions=[
        ("view-table", (database, table)),
        ("view-database", database),
        "view-instance",
    ],
)


id: internals:datasette-create-token
page: internals
ref: datasette-create-token
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
title:
.create_token(actor_id, expires_after=None, restrict_all=None, restrict_database=None, restrict_resource=None)

content:
actor_id  - string 
                         
                             The ID of the actor to create a token for. 
                         
                     
                     
                         expires_after  - int, optional 
                         
                             The number of seconds after which the token should expire. 
                         
                     
                     
                         restrict_all  - iterable, optional 
                         
                             A list of actions that this token should be restricted to across all databases and resources. 
                         
                     
                     
                         restrict_database  - dict, optional 
                         
                             For restricting actions within specific databases, e.g.  {"mydb": ["view-table", "view-query"]} . 
                         
                     
                     
                         restrict_resource  - dict, optional 
                         
                             For restricting actions to specific resources (tables, SQL views and  Canned queries ) within a database. For example:  {"mydb": {"mytable": ["insert-row", "update-row"]}} . 
                         
                     
                 
                 This method returns a signed  API token  of the format  dstok_...  which can be used to authenticate requests to the Datasette API. 
                 All tokens must have an  actor_id  string indicating the ID of the actor which the token will act on behalf of. 
                 Tokens default to lasting forever, but can be set to expire after a given number of seconds using the  expires_after  argument. The following code creates a token for  user1  that will expire after an hour: 
                 token = datasette.create_token(
    actor_id="user1",
    expires_after=3600,
) 
                 The three  restrict_*  arguments can be used to create a token that has additional restrictions beyond what the associated actor is allowed to do. 
                 The following example creates a token that can access  view-instance  and  view-table  across everything, can additionally use  view-query  for anything in the  docs  database and is allowed to execute  insert-row  and  update-row  in the  attachments  table in that database: 
                 token = datasette.create_token(
    actor_id="user1",
    restrict_all=("view-instance", "view-table"),
    restrict_database={"docs": ("view-query",)},
    restrict_resource={
        "docs": {
            "attachments": ("insert-row", "update-row")
        }
    },
)


id: internals:datasette-get-permission
page: internals
ref: datasette-get-permission
title: .get_permission(name_or_abbr)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
name_or_abbr  - string 
                         
                             The name or abbreviation of the permission to look up, e.g.  view-table  or  vt . 
                         
                     
                 
                 Returns a  Permission object  representing the permission, or raises a  KeyError  if one is not found.


id: internals:datasette-get-database
page: internals
ref: datasette-get-database
title: .get_database(name)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
name  - string, optional 
                         
                             The name of the database - optional. 
                         
                     
                 
                 Returns the specified database object. Raises a  KeyError  if the database does not exist. Call this method without an argument to return the first connected database.


id: internals:datasette-add-database
page: internals
ref: datasette-add-database
title: .add_database(db, name=None, route=None)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
db  - datasette.database.Database instance 
                         
                             The database to be attached. 
                         
                     
                     
                         name  - string, optional 
                         
                             The name to be used for this database . If not specified Datasette will pick one based on the filename or memory name. 
                         
                     
                     
                         route  - string, optional 
                         
                             This will be used in the URL path. If not specified, it will default to the same thing as the  name . 
                         
                     
                 
                 The  datasette.add_database(db)  method lets you add a new database to the current Datasette instance. 
                 The  db  parameter should be an instance of the  datasette.database.Database  class. For example: 
                 from datasette.database import Database

datasette.add_database(
    Database(
        datasette,
        path="path/to/my-new-database.db",
    )
) 
                 This will add a mutable database and serve it at  /my-new-database . 
                 Use  is_mutable=False  to add an immutable database. 
                 .add_database()  returns the Database instance, with its name set as the  database.name  attribute. Any time you are working with a newly added database you should use the return value of  .add_database() , for example: 
                 db = datasette.add_database(
    Database(datasette, memory_name="statistics")
)
await db.execute_write(
    "CREATE TABLE foo(id integer primary key)"
)


id: internals:datasette-add-memory-database
page: internals
ref: datasette-add-memory-database
title: .add_memory_database(name)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
Adds a shared in-memory database with the specified name: 
                 datasette.add_memory_database("statistics") 
                 This is a shortcut for the following: 
                 from datasette.database import Database

datasette.add_database(
    Database(datasette, memory_name="statistics")
) 
                 Using either of these pattern will result in the in-memory database being served at  /statistics .


id: internals:datasette-remove-database
page: internals
ref: datasette-remove-database
title: .remove_database(name)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
name  - string 
                         
                             The name of the database to be removed. 
                         
                     
                 
                 This removes a database that has been previously added.  name=  is the unique name of that database.


id: internals:datasette-track-event
page: internals
ref: datasette-track-event
title: await .track_event(event)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
event  -  Event 
                         
                             An instance of a subclass of  datasette.events.Event . 
                         
                     
                 
                 Plugins can call this to track events, using classes they have previously registered. See  Event tracking  for details. 
                 The event will then be passed to all plugins that have registered to receive events using the  track_event(datasette, event)  hook. 
                 Example usage, assuming the plugin has previously registered the  BanUserEvent  class: 
                 await datasette.track_event(
    BanUserEvent(user={"id": 1, "username": "cleverbot"})
)


id: internals:datasette-sign
page: internals
ref: datasette-sign
title: .sign(value, namespace="default")
breadcrumbs: ["Internals for plugins", "Datasette class"]
content:
value  - any serializable type 
                         
                             The value to be signed. 
                         
                     
                     
                         namespace  - string, optional 
                         
                             An alternative namespace, see the  itsdangerous salt documentation . 
                         
                     
                 
                 Utility method for signing values, such that you can safely pass data to and from an untrusted environment. This is a wrapper around the  itsdangerous  library. 
                 This method returns a signed string, which can be decoded and verified using  .unsign(value, namespace="default") .

references:
[{"href": "https://itsdangerous.palletsprojects.com/en/1.1.x/serializer/#the-salt", "label": "itsdangerous salt documentation"}, {"href": "https://itsdangerous.palletsprojects.com/", "label": "itsdangerous"}]


id: internals:datasette-unsign
page: internals
ref: datasette-unsign
title: .unsign(value, namespace="default")
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
signed  - any serializable type 
                         
                             The signed string that was created using  .sign(value, namespace="default") . 
                         
                     
                     
                         namespace  - string, optional 
                         
                             The alternative namespace, if one was used. 
                         
                     
                 
                 Returns the original, decoded object that was passed to  .sign(value, namespace="default") . If the signature is not valid this raises a  itsdangerous.BadSignature  exception.


id: internals:datasette-add-message
page: internals
ref: datasette-add-message
title: .add_message(request, message, type=datasette.INFO)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
request  - Request 
                         
                             The current Request object 
                         
                     
                     
                         message  - string 
                         
                             The message string 
                         
                     
                     
                         type  - constant, optional 
                         
                             The message type -  datasette.INFO ,  datasette.WARNING  or  datasette.ERROR 
                         
                     
                 
                 Datasette's flash messaging mechanism allows you to add a message that will be displayed to the user on the next page that they visit. Messages are persisted in a  ds_messages  cookie. This method adds a message to that cookie. 
                 You can try out these messages (including the different visual styling of the three message types) using the  /-/messages  debugging tool.


id: internals:datasette-absolute-url
page: internals
ref: datasette-absolute-url
title: .absolute_url(request, path)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
request  - Request 
                         
                             The current Request object 
                         
                     
                     
                         path  - string 
                         
                             A path, for example  /dbname/table.json 
                         
                     
                 
                 Returns the absolute URL for the given path, including the protocol and host. For example: 
                 absolute_url = datasette.absolute_url(
    request, "/dbname/table.json"
)
# Would return "http://localhost:8001/dbname/table.json" 
                 The current request object is used to determine the hostname and protocol that should be used for the returned URL. The  force_https_urls  configuration setting is taken into account.


id: internals:datasette-setting
page: internals
ref: datasette-setting
title: .setting(key)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
key  - string 
                         
                             The name of the setting, e.g.  base_url . 
                         
                     
                 
                 Returns the configured value for the specified  setting . This can be a string, boolean or integer depending on the requested setting. 
                 For example: 
                 downloads_are_allowed = datasette.setting("allow_download")


id: internals:datasette-resolve-database
page: internals
ref: datasette-resolve-database
title: .resolve_database(request)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
request  -  Request object 
                         
                             A request object 
                         
                     
                 
                 If you are implementing your own custom views, you may need to resolve the database that the user is requesting based on a URL path. If the regular expression for your route declares a  database  named group, you can use this method to resolve the database object. 
                 This returns a  Database  instance. 
                 If the database cannot be found, it raises a  datasette.utils.asgi.DatabaseNotFound  exception - which is a subclass of  datasette.utils.asgi.NotFound  with a  .database_name  attribute set to the name of the database that was requested.


id: internals:datasette-resolve-table
page: internals
ref: datasette-resolve-table
title: .resolve_table(request)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
request  -  Request object 
                         
                             A request object 
                         
                     
                 
                 This assumes that the regular expression for your route declares both a  database  and a  table  named group. 
                 It returns a  ResolvedTable  named tuple instance with the following fields: 
                 
                     
                         db  -  Database 
                         
                             The database object 
                         
                     
                     
                         table  - string 
                         
                             The name of the table (or view) 
                         
                     
                     
                         is_view  - boolean 
                         
                             True  if this is a view,  False  if it is a table 
                         
                     
                 
                 If the database or table cannot be found it raises a  datasette.utils.asgi.DatabaseNotFound  exception. 
                 If the table does not exist it raises a  datasette.utils.asgi.TableNotFound  exception - a subclass of  datasette.utils.asgi.NotFound  with  .database_name  and  .table  attributes.


id: internals:datasette-resolve-row
page: internals
ref: datasette-resolve-row
title: .resolve_row(request)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
request  -  Request object 
                         
                             A request object 
                         
                     
                 
                 This method assumes your route declares named groups for  database ,  table  and  pks . 
                 It returns a  ResolvedRow  named tuple instance with the following fields: 
                 
                     
                         db  -  Database 
                         
                             The database object 
                         
                     
                     
                         table  - string 
                         
                             The name of the table 
                         
                     
                     
                         sql  - string 
                         
                             SQL snippet that can be used in a  WHERE  clause to select the row 
                         
                     
                     
                         params  - dict 
                         
                             Parameters that should be passed to the SQL query 
                         
                     
                     
                         pks  - list 
                         
                             List of primary key column names 
                         
                     
                     
                         pk_values  - list 
                         
                             List of primary key values decoded from the URL 
                         
                     
                     
                         row  -  sqlite3.Row 
                         
                             The row itself 
                         
                     
                 
                 If the database or table cannot be found it raises a  datasette.utils.asgi.DatabaseNotFound  exception. 
                 If the table does not exist it raises a  datasette.utils.asgi.TableNotFound  exception. 
                 If the row cannot be found it raises a  datasette.utils.asgi.RowNotFound  exception. This has  .database_name ,  .table  and  .pk_values  attributes, extracted from the request path.


id: internals:internals-datasette-client
page: internals
ref: internals-datasette-client
title: datasette.client
breadcrumbs: ["Internals for plugins", "Datasette class"]
content:
Plugins can make internal simulated HTTP requests to the Datasette instance within which they are running. This ensures that all of Datasette's external JSON APIs are also available to plugins, while avoiding the overhead of making an external HTTP call to access those APIs. 
                 The  datasette.client  object is a wrapper around the  HTTPX Python library , providing an async-friendly API that is similar to the widely used  Requests library . 
                 It offers the following methods: 
                 
                     
                         await datasette.client.get(path, **kwargs)  - returns HTTPX Response 
                         
                             Execute an internal GET request against that path. 
                         
                     
                     
                         await datasette.client.post(path, **kwargs)  - returns HTTPX Response 
                         
                             Execute an internal POST request. Use  data={"name": "value"}  to pass form parameters. 
                         
                     
                     
                         await datasette.client.options(path, **kwargs)  - returns HTTPX Response 
                         
                             Execute an internal OPTIONS request. 
                         
                     
                     
                         await datasette.client.head(path, **kwargs)  - returns HTTPX Response 
                         
                             Execute an internal HEAD request. 
                         
                     
                     
                         await datasette.client.put(path, **kwargs)  - returns HTTPX Response 
                         
                             Execute an internal PUT request. 
                         
                     
                     
                         await datasette.client.patch(path, **kwargs)  - returns HTTPX Response 
                         
                             Execute an internal PATCH request. 
                         
                     
                     
                         await datasette.client.delete(path, **kwargs)  - returns HTTPX Response 
                         
                             Execute an internal DELETE request. 
                         
                     
                     
                         await datasette.client.request(method, path, **kwargs)  - returns HTTPX Response 
                         
                             Execute an internal request with the given HTTP method against that path. 
                         
                     
                 
                 These methods can be used with  datasette.urls  - for example: 
                 table_json = (
    await datasette.client.get(
        datasette.urls.table(
            "fixtures", "facetable", format="json"
        )
    )
).json() 
                 datasette.client  methods automatically take the current  base_url  setting into account, whether or not you use the  datasette.urls  family of methods to construct the path. 
                 For documentation on available  **kwargs  options and the shape of the HTTPX Response object refer to the  HTTPX Async documentation .

references:
[{"href": "https://www.python-httpx.org/", "label": "HTTPX Python library"}, {"href": "https://requests.readthedocs.io/", "label": "Requests library"}, {"href": "https://www.python-httpx.org/async/", "label": "HTTPX Async documentation"}]


id: internals:internals-datasette-urls
page: internals
ref: internals-datasette-urls
title: datasette.urls
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
The  datasette.urls  object contains methods for building URLs to pages within Datasette. Plugins should use this to link to pages, since these methods take into account any  base_url  configuration setting that might be in effect. 
                 
                     
                         datasette.urls.instance(format=None) 
                         
                             Returns the URL to the Datasette instance root page. This is usually  "/" . 
                         
                     
                     
                         datasette.urls.path(path, format=None) 
                         
                             Takes a path and returns the full path, taking  base_url  into account. 
                             For example,  datasette.urls.path("-/logout")  will return the path to the logout page, which will be  "/-/logout"  by default or  /prefix-path/-/logout  if  base_url  is set to  /prefix-path/ 
                         
                     
                     
                         datasette.urls.logout() 
                         
                             Returns the URL to the logout page, usually  "/-/logout" 
                         
                     
                     
                         datasette.urls.static(path) 
                         
                             Returns the URL of one of Datasette's default static assets, for example  "/-/static/app.css" 
                         
                     
                     
                         datasette.urls.static_plugins(plugin_name, path) 
                         
                             Returns the URL of one of the static assets belonging to a plugin. 
                             datasette.urls.static_plugins("datasette_cluster_map", "datasette-cluster-map.js")  would return  "/-/static-plugins/datasette_cluster_map/datasette-cluster-map.js" 
                         
                     
                     
                         datasette.urls.static(path) 
                         
                             Returns the URL of one of Datasette's default static assets, for example  "/-/static/app.css" 
                         
                     
                     
                         datasette.urls.database(database_name, format=None) 
                         
                             Returns the URL to a database page, for example  "/fixtures" 
                         
                     
                     
                         datasette.urls.table(database_name, table_name, format=None) 
                         
                             Returns the URL to a table page, for example  "/fixtures/facetable" 
                         
                     
                     
                         datasette.urls.query(database_name, query_name, format=None) 
                         
                             Returns the URL to a query page, for example  "/fixtures/pragma_cache_size" 
                         
                     
                 
                 These functions can be accessed via the  {{ urls }}  object in Datasette templates, for example: 
                 <a href="{{ urls.instance() }}">Homepage</a>
<a href="{{ urls.database("fixtures") }}">Fixtures database</a>
<a href="{{ urls.table("fixtures", "facetable") }}">facetable table</a>
<a href="{{ urls.query("fixtures", "pragma_cache_size") }}">pragma_cache_size query</a> 
                 Use the  format="json"  (or  "csv"  or other formats supported by plugins) arguments to get back URLs to the JSON representation. This is the path with  .json  added on the end. 
                 These methods each return a  datasette.utils.PrefixedUrlString  object, which is a subclass of the Python  str  type. This allows the logic that considers the  base_url  setting to detect if that prefix has already been applied to the path.


id: internals:internals-database
page: internals
ref: internals-database
title: Database class
breadcrumbs: ["Internals for plugins"]
references: []
content:
Instances of the  Database  class can be used to execute queries against attached SQLite databases, and to run introspection against their schemas.


id: internals:database-constructor
page: internals
ref: database-constructor
title: Database(ds, path=None, is_mutable=True, is_memory=False, memory_name=None)
breadcrumbs: ["Internals for plugins", "Database class"]
references: []
content:
The  Database()  constructor can be used by plugins, in conjunction with  .add_database(db, name=None, route=None) , to create and register new databases. 
                 The arguments are as follows: 
                 
                     
                         ds  -  Datasette class  (required) 
                         
                             The Datasette instance you are attaching this database to. 
                         
                     
                     
                         path  - string 
                         
                             Path to a SQLite database file on disk. 
                         
                     
                     
                         is_mutable  - boolean 
                         
                             Set this to  False  to cause Datasette to open the file in immutable mode. 
                         
                     
                     
                         is_memory  - boolean 
                         
                             Use this to create non-shared memory connections. 
                         
                     
                     
                         memory_name  - string or  None 
                         
                             Use this to create a named in-memory database. Unlike regular memory databases these can be accessed by multiple threads and will persist an changes made to them for the lifetime of the Datasette server process. 
                         
                     
                 
                 The first argument is the  datasette  instance you are attaching to, the second is a  path= , then  is_mutable  and  is_memory  are both optional arguments.


id: internals:database-hash
page: internals
ref: database-hash
title: db.hash
breadcrumbs: ["Internals for plugins", "Database class"]
references: []
content:
If the database was opened in immutable mode, this property returns the 64 character SHA-256 hash of the database contents as a string. Otherwise it returns  None .


id: internals:database-execute
page: internals
ref: database-execute
title: await db.execute(sql, ...)
breadcrumbs: ["Internals for plugins", "Database class"]
references: []
content:
Executes a SQL query against the database and returns the resulting rows (see  Results ). 
                 
                     
                         sql  - string (required) 
                         
                             The SQL query to execute. This can include  ?  or  :named  parameters. 
                         
                     
                     
                         params  - list or dict 
                         
                             A list or dictionary of values to use for the parameters. List for  ? , dictionary for  :named . 
                         
                     
                     
                         truncate  - boolean 
                         
                             Should the rows returned by the query be truncated at the maximum page size? Defaults to  True , set this to  False  to disable truncation. 
                         
                     
                     
                         custom_time_limit  - integer ms 
                         
                             A custom time limit for this query. This can be set to a lower value than the Datasette configured default. If a query takes longer than this it will be terminated early and raise a  dataette.database.QueryInterrupted  exception. 
                         
                     
                     
                         page_size  - integer 
                         
                             Set a custom page size for truncation, over-riding the configured Datasette default. 
                         
                     
                     
                         log_sql_errors  - boolean 
                         
                             Should any SQL errors be logged to the console in addition to being raised as an error? Defaults to  True .


id: internals:database-results
page: internals
ref: database-results
title: Results
breadcrumbs: ["Internals for plugins", "Database class"]
references: [{"href": "https://docs.python.org/3/library/sqlite3.html#row-objects", "label": "Row objects"}]
content:
The  db.execute()  method returns a single  Results  object. This can be used to access the rows returned by the query. 
                 Iterating over a  Results  object will yield SQLite  Row objects . Each of these can be treated as a tuple or can be accessed using  row["column"]  syntax: 
                 info = []
results = await db.execute("select name from sqlite_master")
for row in results:
    info.append(row["name"]) 
                 The  Results  object also has the following properties and methods: 
                 
                     
                         .truncated  - boolean 
                         
                             Indicates if this query was truncated - if it returned more results than the specified  page_size . If this is true then the results object will only provide access to the first  page_size  rows in the query result. You can disable truncation by passing  truncate=False  to the  db.query()  method. 
                         
                     
                     
                         .columns  - list of strings 
                         
                             A list of column names returned by the query. 
                         
                     
                     
                         .rows  - list of  sqlite3.Row 
                         
                             This property provides direct access to the list of rows returned by the database. You can access specific rows by index using  results.rows[0] . 
                         
                     
                     
                         .first()  - row or None 
                         
                             Returns the first row in the results, or  None  if no rows were returned. 
                         
                     
                     
                         .single_value() 
                         
                             Returns the value of the first column of the first row of results - but only if the query returned a single row with a single column. Raises a  datasette.database.MultipleValues  exception otherwise. 
                         
                     
                     
                         .__len__() 
                         
                             Calling  len(results)  returns the (truncated) number of returned results.


id: internals:database-execute-fn
page: internals
ref: database-execute-fn
title: await db.execute_fn(fn)
breadcrumbs: ["Internals for plugins", "Database class"]
references: []
content:
Executes a given callback function against a read-only database connection running in a thread. The function will be passed a SQLite connection, and the return value from the function will be returned by the  await . 
                 Example usage: 
                 def get_version(conn):
    return conn.execute(
        "select sqlite_version()"
    ).fetchall()[0][0]


version = await db.execute_fn(get_version)


id: internals:database-execute-write
page: internals
ref: database-execute-write
title: await db.execute_write(sql, params=None, block=True)
breadcrumbs: ["Internals for plugins", "Database class"]
references: []
content:
SQLite only allows one database connection to write at a time. Datasette handles this for you by maintaining a queue of writes to be executed against a given database. Plugins can submit write operations to this queue and they will be executed in the order in which they are received. 
                 This method can be used to queue up a non-SELECT SQL query to be executed against a single write connection to the database. 
                 You can pass additional SQL parameters as a tuple or dictionary. 
                 The method will block until the operation is completed, and the return value will be the return from calling  conn.execute(...)  using the underlying  sqlite3  Python library. 
                 If you pass  block=False  this behavior changes to "fire and forget" - queries will be added to the write queue and executed in a separate thread while your code can continue to do other things. The method will return a UUID representing the queued task. 
                 Each call to  execute_write()  will be executed inside a transaction.


id: internals:database-execute-write-script
page: internals
ref: database-execute-write-script
title: await db.execute_write_script(sql, block=True)
breadcrumbs: ["Internals for plugins", "Database class"]
content:
Like  execute_write()  but can be used to send multiple SQL statements in a single string separated by semicolons, using the  sqlite3   conn.executescript()  method. 
                 Each call to  execute_write_script()  will be executed inside a transaction.

references:
[{"href": "https://docs.python.org/3/library/sqlite3.html#sqlite3.Cursor.executescript", "label": "conn.executescript()"}]


id: internals:database-execute-write-many
page: internals
ref: database-execute-write-many
title: await db.execute_write_many(sql, params_seq, block=True)
breadcrumbs: ["Internals for plugins", "Database class"]
content:
Like  execute_write()  but uses the  sqlite3   conn.executemany()  method. This will efficiently execute the same SQL statement against each of the parameters in the  params_seq  iterator, for example: 
                 await db.execute_write_many(
    "insert into characters (id, name) values (?, ?)",
    [(1, "Melanie"), (2, "Selma"), (2, "Viktor")],
) 
                 Each call to  execute_write_many()  will be executed inside a transaction.

references:
[{"href": "https://docs.python.org/3/library/sqlite3.html#sqlite3.Cursor.executemany", "label": "conn.executemany()"}]


id: internals:database-execute-write-fn
page: internals
ref: database-execute-write-fn
title: await db.execute_write_fn(fn, block=True, transaction=True)
breadcrumbs: ["Internals for plugins", "Database class"]
references: []
content:
This method works like  .execute_write() , but instead of a SQL statement you give it a callable Python function. Your function will be queued up and then called when the write connection is available, passing that connection as the argument to the function. 
                 The function can then perform multiple actions, safe in the knowledge that it has exclusive access to the single writable connection for as long as it is executing. 
                 
                     fn  needs to be a regular function, not an  async def  function. 
                 
                 For example: 
                 def delete_and_return_count(conn):
    conn.execute("delete from some_table where id > 5")
    return conn.execute(
        "select count(*) from some_table"
    ).fetchone()[0]


try:
    num_rows_left = await database.execute_write_fn(
        delete_and_return_count
    )
except Exception as e:
    print("An error occurred:", e) 
                 The value returned from  await database.execute_write_fn(...)  will be the return value from your function. 
                 If your function raises an exception that exception will be propagated up to the  await  line. 
                 By default your function will be executed inside a transaction. You can pass  transaction=False  to disable this behavior, though if you do that you should be careful to manually apply transactions - ideally using the  with conn:  pattern, or you may see  OperationalError: database table is locked  errors. 
                 If you specify  block=False  the method becomes fire-and-forget, queueing your function to be executed and then allowing your code after the call to  .execute_write_fn()  to continue running while the underlying thread waits for an opportunity to run your function. A UUID representing the queued task will be returned. Any exceptions in your code will be silently swallowed.


id: internals:database-execute-isolated-fn
page: internals
ref: database-execute-isolated-fn
title: await db.execute_isolated_fn(fn)
breadcrumbs: ["Internals for plugins", "Database class"]
content:
This method works is similar to  execute_write_fn()  but executes the provided function in an entirely isolated SQLite connection, which is opened, used and then closed again in a single call to this method. 
                 The  prepare_connection()  plugin hook is not executed against this connection. 
                 This allows plugins to execute database operations that might conflict with how database connections are usually configured. For example, running a  VACUUM  operation while bypassing any restrictions placed by the  datasette-sqlite-authorizer  plugin. 
                 Plugins can also use this method to load potentially dangerous SQLite extensions, use them to perform an operation and then have them safely unloaded at the end of the call, without risk of exposing them to other connections. 
                 Functions run using  execute_isolated_fn()  share the same queue as  execute_write_fn() , which guarantees that no writes can be executed at the same time as the isolated function is executing. 
                 The return value of the function will be returned by this method. Any exceptions raised by the function will be raised out of the  await  line as well.

references:
[{"href": "https://github.com/datasette/datasette-sqlite-authorizer", "label": "datasette-sqlite-authorizer"}]


id: internals:database-close
page: internals
ref: database-close
title: db.close()
breadcrumbs: ["Internals for plugins", "Database class"]
references: []
content:
Closes all of the open connections to file-backed databases. This is mainly intended to be used by large test suites, to avoid hitting limits on the number of open files.


id: internals:internals-database-introspection
page: internals
ref: internals-database-introspection
title: Database introspection
breadcrumbs: ["Internals for plugins", "Database class"]
references: []
content:
The  Database  class also provides properties and methods for introspecting the database. 
                 
                     
                         db.name  - string 
                         
                             The name of the database - usually the filename without the  .db  prefix. 
                         
                     
                     
                         db.size  - integer 
                         
                             The size of the database file in bytes. 0 for  :memory:  databases. 
                         
                     
                     
                         db.mtime_ns  - integer or None 
                         
                             The last modification time of the database file in nanoseconds since the epoch.  None  for  :memory:  databases. 
                         
                     
                     
                         db.is_mutable  - boolean 
                         
                             Is this database mutable, and allowed to accept writes? 
                         
                     
                     
                         db.is_memory  - boolean 
                         
                             Is this database an in-memory database? 
                         
                     
                     
                         await db.attached_databases()  - list of named tuples 
                         
                             Returns a list of additional databases that have been connected to this database using the SQLite ATTACH command. Each named tuple has fields  seq ,  name  and  file . 
                         
                     
                     
                         await db.table_exists(table)  - boolean 
                         
                             Check if a table called  table  exists. 
                         
                     
                     
                         await db.view_exists(view)  - boolean 
                         
                             Check if a view called  view  exists. 
                         
                     
                     
                         await db.table_names()  - list of strings 
                         
                             List of names of tables in the database. 
                         
                     
                     
                         await db.view_names()  - list of strings 
                         
                             List of names of views in the database. 
                         
                     
                     
                         await db.table_columns(table)  - list of strings 
                         
                             Names of columns in a specific table. 
                         
                     
                     
                         await db.table_column_details(table)  - list of named tuples 
                         
                             Full details of the columns in a specific table. Each column is represented by a  Column  named tuple with fields  cid  (integer representing the column position),  name  (string),  type  (string, e.g.  REAL  or  VARCHAR(30) ),  notnull  (integer 1 or 0),  default_value  (string or None),  is_pk  (integer 1 or 0). 
                         
                     
                     
                         await db.primary_keys(table)  - list of strings 
                         
                             Names of the columns that are part of the primary key for this table. 
                         
                     
                     
                         await db.fts_table(table)  - string or None 
                         
                             The name of the FTS table associated with this table, if one exists. 
                         
                     
                     
                         await db.label_column_for_table(table)  - string or None 
                         
                             The label column that is associated with this table - either automatically detected or using the  "label_column"  key from  Metadata , see  Specifying the label column for a table . 
                         
                     
                     
                         await db.foreign_keys_for_table(table)  - list of dictionaries 
                         
                             Details of columns in this table which are foreign keys to other tables. A list of dictionaries where each dictionary is shaped like this:  {"column": string, "other_table": string, "other_column": string} . 
                         
                     
                     
                         await db.hidden_table_names()  - list of strings 
                         
                             List of tables which Datasette "hides" by default - usually these are tables associated with SQLite's full-text search feature, the SpatiaLite extension or tables hidden using the  Hiding tables  feature. 
                         
                     
                     
                         await db.get_table_definition(table)  - string 
                         
                             Returns the SQL definition for the table - the  CREATE TABLE  statement and any associated  CREATE INDEX  statements. 
                         
                     
                     
                         await db.get_view_definition(view)  - string 
                         
                             Returns the SQL definition of the named view. 
                         
                     
                     
                         await db.get_all_foreign_keys()  - dictionary 
                         
                             Dictionary representing both incoming and outgoing foreign keys for this table. It has two keys,  "incoming"  and  "outgoing" , each of which is a list of dictionaries with keys  "column" ,  "other_table"  and  "other_column" . For example: 
                             {
    "incoming": [],
    "outgoing": [
        {
            "other_table": "attraction_characteristic",
            "column": "characteristic_id",
            "other_column": "pk",
        },
        {
            "other_table": "roadside_attractions",
            "column": "attraction_id",
            "other_column": "pk",
        }
    ]
}


id: internals:internals-csrf
page: internals
ref: internals-csrf
title: CSRF protection
breadcrumbs: ["Internals for plugins"]
references: [{"href": "https://github.com/simonw/asgi-csrf", "label": "asgi-csrf"}]
content:
Datasette uses  asgi-csrf  to guard against CSRF attacks on form POST submissions. Users receive a  ds_csrftoken  cookie which is compared against the  csrftoken  form field (or  x-csrftoken  HTTP header) for every incoming request. 
             If your plugin implements a  <form method="POST">  anywhere you will need to include that token. You can do so with the following template snippet: 
             <input type="hidden" name="csrftoken" value="{{ csrftoken() }}"> 
             If you are rendering templates using the  await .render_template(template, context=None, request=None)  method the  csrftoken()  helper will only work if you provide the  request=  argument to that method. If you forget to do this you will see the following error: 
             form-urlencoded POST field did not match cookie 
             You can selectively disable CSRF protection using the  skip_csrf(datasette, scope)  hook.


id: internals:internals-internal
page: internals
ref: internals-internal
title: Datasette's internal database
breadcrumbs: ["Internals for plugins"]
references: []
content:
Datasette maintains an "internal" SQLite database used for configuration, caching, and storage. Plugins can store configuration, settings, and other data inside this database. By default, Datasette will use a temporary in-memory SQLite database as the internal database, which is created at startup and destroyed at shutdown. Users of Datasette can optionally pass in a  --internal  flag to specify the path to a SQLite database to use as the internal database, which will persist internal data across Datasette instances. 
             Datasette maintains tables called  catalog_databases ,  catalog_tables ,  catalog_columns ,  catalog_indexes ,  catalog_foreign_keys  with details of the attached databases and their schemas. These tables should not be considered a stable API - they may change between Datasette releases. 
             The internal database is not exposed in the Datasette application by default, which means private data can safely be stored without worry of accidentally leaking information through the default Datasette interface and API. However, other plugins do have full read and write access to the internal database. 
             Plugins can access this database by calling  internal_db = datasette.get_internal_database()  and then executing queries using the  Database API . 
             Plugin authors are asked to practice good etiquette when using the internal database, as all plugins use the same database to store data. For example: 
             
                 
                     Use a unique prefix when creating tables, indices, and triggers in the internal database. If your plugin is called  datasette-xyz , then prefix names with  datasette_xyz_* . 
                 
                 
                     Avoid long-running write statements that may stall or block other plugins that are trying to write at the same time. 
                 
                 
                     Use temporary tables or shared in-memory attached databases when possible. 
                 
                 
                     Avoid implementing features that could expose private data stored in the internal database by other plugins.


id: internals:internals-utils
page: internals
ref: internals-utils
title: The datasette.utils module
breadcrumbs: ["Internals for plugins"]
references: [{"href": "https://github.com/simonw/datasette/issues/new", "label": "opening an issue"}]
content:
The  datasette.utils  module contains various utility functions used by Datasette. As a general rule you should consider anything in this module to be unstable - functions and classes here could change without warning or be removed entirely between Datasette releases, without being mentioned in the release notes. 
             The exception to this rule is anything that is documented here. If you find a need for an undocumented utility function in your own work, consider  opening an issue  requesting that the function you are using be upgraded to documented and supported status.


id: internals:internals-utils-parse-metadata
page: internals
ref: internals-utils-parse-metadata
title: parse_metadata(content)
breadcrumbs: ["Internals for plugins", "The datasette.utils module"]
references: []
content:
This function accepts a string containing either JSON or YAML, expected to be of the format described in  Metadata . It returns a nested Python dictionary representing the parsed data from that string. 
                 If the metadata cannot be parsed as either JSON or YAML the function will raise a  utils.BadMetadataError  exception. 
                 
                 
                     datasette.utils. parse_metadata content :   str dict 
                     
                         Detects if content is JSON or YAML and parses it appropriately.


id: internals:internals-utils-await-me-maybe
page: internals
ref: internals-utils-await-me-maybe
title: await_me_maybe(value)
breadcrumbs: ["Internals for plugins", "The datasette.utils module"]
content:
Utility function for calling  await  on a return value if it is awaitable, otherwise returning the value. This is used by Datasette to support plugin hooks that can optionally return awaitable functions. Read more about this function in  The “await me maybe” pattern for Python asyncio . 
                 
                 
                     async   datasette.utils. await_me_maybe value :   Any Any 
                     
                         If value is callable, call it. If awaitable, await it. Otherwise return it.

references:
[{"href": "https://simonwillison.net/2020/Sep/2/await-me-maybe/", "label": "The “await me maybe” pattern for Python asyncio"}]


id: internals:internals-utils-named-parameters
page: internals
ref: internals-utils-named-parameters
title: named_parameters(sql)
breadcrumbs: ["Internals for plugins", "The datasette.utils module"]
references: []
content:
Derive the list of  :named  parameters referenced in a SQL query. 
                 
                 
                     datasette.utils. named_parameters sql :   str List [ str ] 
                     
                         Given a SQL statement, return a list of named parameters that are used in the statement 
                         e.g. for  select * from foo where id=:id  this would return  ["id"]


id: internals:internals-tilde-encoding
page: internals
ref: internals-tilde-encoding
title: Tilde encoding
breadcrumbs: ["Internals for plugins", "The datasette.utils module"]
content:
Datasette uses a custom encoding scheme in some places, called  tilde encoding . This is primarily used for table names and row primary keys, to avoid any confusion between  /  characters in those values and the Datasette URLs that reference them. 
                 Tilde encoding uses the same algorithm as  URL percent-encoding , but with the  ~  tilde character used in place of  % . 
                 Any character other than  ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz0123456789_-  will be replaced by the numeric equivalent preceded by a tilde. For example: 
                 
                     
                         /  becomes  ~2F 
                     
                     
                         .  becomes  ~2E 
                     
                     
                         %  becomes  ~25 
                     
                     
                         ~  becomes  ~7E 
                     
                     
                         Space becomes  + 
                     
                     
                         polls/2022.primary  becomes  polls~2F2022~2Eprimary 
                     
                 
                 Note that the space character is a special case: it will be replaced with a  +  symbol. 
                 
                 
                 
                     datasette.utils. tilde_encode s :   str str 
                     
                         Returns tilde-encoded string - for example  /foo/bar  ->  ~2Ffoo~2Fbar 
                     
                 
                 
                 
                 
                     datasette.utils. tilde_decode s :   str str 
                     
                         Decodes a tilde-encoded string, so  ~2Ffoo~2Fbar  ->  /foo/bar

references:
[{"href": "https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding", "label": "URL percent-encoding"}]


id: internals:internals-tracer
page: internals
ref: internals-tracer
title: datasette.tracer
breadcrumbs: ["Internals for plugins"]
content:
Running Datasette with  --setting trace_debug 1  enables trace debug output, which can then be viewed by adding  ?_trace=1  to the query string for any page. 
             You can see an example of this at the bottom of  latest.datasette.io/fixtures/facetable?_trace=1 . The JSON output shows full details of every SQL query that was executed to generate the page. 
             The  datasette-pretty-traces  plugin can be installed to provide a more readable display of this information. You can see  a demo of that here . 
             You can add your own custom traces to the JSON output using the  trace()  context manager. This takes a string that identifies the type of trace being recorded, and records any keyword arguments as additional JSON keys on the resulting trace object. 
             The start and end time, duration and a traceback of where the trace was executed will be automatically attached to the JSON object. 
             This example uses trace to record the start, end and duration of any HTTP GET requests made using the function: 
             from datasette.tracer import trace
import httpx


async def fetch_url(url):
    with trace("fetch-url", url=url):
        async with httpx.AsyncClient() as client:
            return await client.get(url)

references:
[{"href": "https://latest.datasette.io/fixtures/facetable?_trace=1", "label": "latest.datasette.io/fixtures/facetable?_trace=1"}, {"href": "https://datasette.io/plugins/datasette-pretty-traces", "label": "datasette-pretty-traces"}, {"href": "https://latest-with-plugins.datasette.io/github/commits?_trace=1", "label": "a demo of that here"}]


id: internals:internals-tracer-trace-child-tasks
page: internals
ref: internals-tracer-trace-child-tasks
title: Tracing child tasks
breadcrumbs: ["Internals for plugins", "datasette.tracer"]
content:
If your code uses a mechanism such as  asyncio.gather()  to execute code in additional tasks you may find that some of the traces are missing from the display. 
                 You can use the  trace_child_tasks()  context manager to ensure these child tasks are correctly handled. 
                 from datasette import tracer

with tracer.trace_child_tasks():
    results = await asyncio.gather(
        # ... async tasks here
    ) 
                 This example uses the  register_routes()  plugin hook to add a page at  /parallel-queries  which executes two SQL queries in parallel using  asyncio.gather()  and returns their results. 
                 from datasette import hookimpl
from datasette import tracer


@hookimpl
def register_routes():
    async def parallel_queries(datasette):
        db = datasette.get_database()
        with tracer.trace_child_tasks():
            one, two = await asyncio.gather(
                db.execute("select 1"),
                db.execute("select 2"),
            )
        return Response.json(
            {
                "one": one.single_value(),
                "two": two.single_value(),
            }
        )

    return [
        (r"/parallel-queries$", parallel_queries),
    ] 
                 Note that running parallel SQL queries in this way has  been known to cause problems in the past , so treat this example with caution. 
                 Adding  ?_trace=1  will show that the trace covers both of those child tasks.

references:
[{"href": "https://github.com/simonw/datasette/issues/2189", "label": "been known to cause problems in the past"}]


id: internals:internals-shortcuts
page: internals
ref: internals-shortcuts
title: Import shortcuts
breadcrumbs: ["Internals for plugins"]
references: []
content:
The following commonly used symbols can be imported directly from the  datasette  module: 
             from datasette import Response
from datasette import Forbidden
from datasette import NotFound
from datasette import hookimpl
from datasette import actor_matches_allow


id: configuration:id1
page: configuration
ref: id1
title: Configuration
breadcrumbs: []
references: []
content:
Datasette offers several ways to configure your Datasette instances: server settings, plugin configuration, authentication, and more. 
         Most configuration can be handled using a  datasette.yaml  configuration file, passed to datasette using the  -c/--config  flag: 
         datasette mydatabase.db --config datasette.yaml 
         This file can also use JSON, as  datasette.json . YAML is recommended over JSON due to its support for comments and multi-line strings.


id: configuration:configuration-cli
page: configuration
ref: configuration-cli
title: Configuration via the command-line
breadcrumbs: ["Configuration"]
content:
The recommended way to configure Datasette is using a  datasette.yaml  file passed to  -c/--config . You can also pass individual settings to Datasette using the  -s/--setting  option, which can be used multiple times: 
             datasette mydatabase.db \
  --setting settings.default_page_size 50 \
  --setting settings.sql_time_limit_ms 3500 
             This option takes dotted-notation for the first argument and a value for the second argument. This means you can use it to set any configuration value that would be valid in a  datasette.yaml  file. 
             It also works for plugin configuration, for example for  datasette-cluster-map : 
             datasette mydatabase.db \
  --setting plugins.datasette-cluster-map.latitude_column xlat \
  --setting plugins.datasette-cluster-map.longitude_column xlon 
             If the value you provide is a valid JSON object or list it will be treated as nested data, allowing you to configure plugins that accept lists such as  datasette-proxy-url : 
             datasette mydatabase.db \
  -s plugins.datasette-proxy-url.paths '[{"path": "/proxy", "backend": "http://example.com/"}]' 
             This is equivalent to a  datasette.yaml  file containing the following: 
             [[[cog
from metadata_doc import config_example
import textwrap
config_example(cog, textwrap.dedent(
  """
  plugins:
    datasette-proxy-url:
      paths:
      - path: /proxy
        backend: http://example.com/
  """).strip()
  ) 
             ]]] 
             [[[end]]]

references:
[{"href": "https://datasette.io/plugins/datasette-cluster-map", "label": "datasette-cluster-map"}, {"href": "https://datasette.io/plugins/datasette-proxy-url", "label": "datasette-proxy-url"}]


id: configuration:configuration-reference
page: configuration
ref: configuration-reference
breadcrumbs: ["Configuration"]
references: []
content:
The following example shows some of the valid configuration options that can exist inside  datasette.yaml . 
             [[[cog
from metadata_doc import config_example
import textwrap
config_example(cog, textwrap.dedent(
  """
    # Datasette settings block
    settings:
      default_page_size: 50
      sql_time_limit_ms: 3500
      max_returned_rows: 2000

    # top-level plugin configuration
    plugins:
      datasette-my-plugin:
        key: valueA

    # Database and table-level configuration
    databases:
      your_db_name:
        # plugin configuration for the your_db_name database
        plugins:
          datasette-my-plugin:
            key: valueA
        tables:
          your_table_name:
            allow:
              # Only the root user can access this table
              id: root
            # plugin configuration for the your_table_name table
            # inside your_db_name database
            plugins:
              datasette-my-plugin:
                key: valueB
    """)
  ) 
             ]]] 
             [[[end]]]


id: configuration:configuration-reference-settings
page: configuration
ref: configuration-reference-settings
title: Settings
breadcrumbs: ["Configuration", null]
references: []
content:
Settings  can be configured in  datasette.yaml  with the  settings  key: 
                 [[[cog
from metadata_doc import config_example
import textwrap
config_example(cog, textwrap.dedent(
  """
    # inside datasette.yaml
    settings:
      default_allow_sql: off
      default_page_size: 50
    """).strip()
  ) 
                 ]]] 
                 [[[end]]] 
                 The full list of settings is available in the  settings documentation . Settings can also be passed to Datasette using one or more  --setting name value  command line options.`


id: configuration:configuration-reference-plugins
page: configuration
ref: configuration-reference-plugins
title: Plugin configuration
breadcrumbs: ["Configuration", null]
references: []
content:
Datasette plugins  often require configuration. This plugin configuration should be placed in  plugins  keys inside  datasette.yaml . 
                 Most plugins are configured at the top-level of the file, using the  plugins  key: 
                 [[[cog
from metadata_doc import config_example
import textwrap
config_example(cog, textwrap.dedent(
  """
    # inside datasette.yaml
    plugins:
      datasette-my-plugin:
        key: my_value
    """).strip()
  ) 
                 ]]] 
                 [[[end]]] 
                 Some plugins can be configured at the database or table level. These should use a  plugins  key nested under the appropriate place within the  databases  object: 
                 [[[cog
from metadata_doc import config_example
import textwrap
config_example(cog, textwrap.dedent(
  """
    # inside datasette.yaml
    databases:
      my_database:
        # plugin configuration for the my_database database
        plugins:
          datasette-my-plugin:
            key: my_value
      my_other_database:
        tables:
          my_table:
            # plugin configuration for the my_table table inside the my_other_database database
            plugins:
              datasette-my-plugin:
                key: my_value
  """).strip()
  ) 
                 ]]] 
                 [[[end]]]


id: configuration:configuration-reference-permissions
page: configuration
ref: configuration-reference-permissions
title: Permissions configuration
breadcrumbs: ["Configuration", null]
references: []
content:
Datasette's  authentication and permissions  system can also be configured using  datasette.yaml . 
                 Here is a simple example: 
                 [[[cog
from metadata_doc import config_example
import textwrap
config_example(cog, textwrap.dedent(
  """
    # Instance is only available to users 'sharon' and 'percy':
    allow:
      id:
      - sharon
      - percy

    # Only 'percy' is allowed access to the accounting database:
    databases:
      accounting:
        allow:
          id: percy
  """).strip()
  ) 
                 ]]] 
                 [[[end]]] 
                 Access permissions in datasette.yaml  has the full details.


id: configuration:configuration-reference-canned-queries
page: configuration
ref: configuration-reference-canned-queries
title: Canned queries configuration
breadcrumbs: ["Configuration", null]
references: []
content:
Canned queries  are named SQL queries that appear in the Datasette interface. They can be configured in  datasette.yaml  using the  queries  key at the database level: 
                 [[[cog
from metadata_doc import config_example, config_example
config_example(cog, {
    "databases": {
       "sf-trees": {
           "queries": {
               "just_species": {
                   "sql": "select qSpecies from Street_Tree_List"
               }
           }
       }
    }
}) 
                 ]]] 
                 [[[end]]] 
                 See the  canned queries documentation  for more, including how to configure  writable canned queries .


id: configuration:configuration-reference-css-js
page: configuration
ref: configuration-reference-css-js
title: Custom CSS and JavaScript
breadcrumbs: ["Configuration", null]
content:
Datasette can load additional CSS and JavaScript files, configured in  datasette.yaml  like this: 
                 [[[cog
from metadata_doc import config_example
config_example(cog, """
    extra_css_urls:
    - https://simonwillison.net/static/css/all.bf8cd891642c.css
    extra_js_urls:
    - https://code.jquery.com/jquery-3.2.1.slim.min.js
""") 
                 ]]] 
                 [[[end]]] 
                 The extra CSS and JavaScript files will be linked in the  <head>  of every page: 
                 <link rel="stylesheet" href="https://simonwillison.net/static/css/all.bf8cd891642c.css">
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script> 
                 You can also specify a SRI (subresource integrity hash) for these assets: 
                 [[[cog
config_example(cog, """
    extra_css_urls:
    - url: https://simonwillison.net/static/css/all.bf8cd891642c.css
      sri: sha384-9qIZekWUyjCyDIf2YK1FRoKiPJq4PHt6tp/ulnuuyRBvazd0hG7pWbE99zvwSznI
    extra_js_urls:
    - url: https://code.jquery.com/jquery-3.2.1.slim.min.js
      sri: sha256-k2WSCIexGzOj3Euiig+TlR8gA0EmPjuc79OEeY5L45g=
""") 
                 ]]] 
                 [[[end]]] 
                 This will produce: 
                 <link rel="stylesheet" href="https://simonwillison.net/static/css/all.bf8cd891642c.css"
    integrity="sha384-9qIZekWUyjCyDIf2YK1FRoKiPJq4PHt6tp/ulnuuyRBvazd0hG7pWbE99zvwSznI"
    crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
    integrity="sha256-k2WSCIexGzOj3Euiig+TlR8gA0EmPjuc79OEeY5L45g="
    crossorigin="anonymous"></script> 
                 Modern browsers will only execute the stylesheet or JavaScript if the SRI hash
                    matches the content served. You can generate hashes using  www.srihash.org 
                 Items in  "extra_js_urls"  can specify  "module": true  if they reference JavaScript that uses  JavaScript modules . This configuration: 
                 [[[cog
config_example(cog, """
    extra_js_urls:
    - url: https://example.datasette.io/module.js
      module: true
""") 
                 ]]] 
                 [[[end]]] 
                 Will produce this HTML: 
                 <script type="module" src="https://example.datasette.io/module.js"></script>

references:
[{"href": "https://www.srihash.org/", "label": "www.srihash.org"}, {"href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules", "label": "JavaScript modules"}]


id: pages:pages
page: pages
ref: pages
title: Pages and API endpoints
breadcrumbs: []
references: []
content:
The Datasette web application offers a number of different pages that can be accessed to explore the data in question, each of which is accompanied by an equivalent JSON API.


id: pages:indexview
page: pages
ref: indexview
title: Top-level index
breadcrumbs: ["Pages and API endpoints"]
content:
The root page of any Datasette installation is an index page that lists all of the currently attached databases. Some examples: 
             
                 
                     fivethirtyeight.datasettes.com 
                 
                 
                     global-power-plants.datasettes.com 
                 
                 
                     register-of-members-interests.datasettes.com 
                 
             
             Add  /.json  to the end of the URL for the JSON version of the underlying data: 
             
                 
                     fivethirtyeight.datasettes.com/.json 
                 
                 
                     global-power-plants.datasettes.com/.json 
                 
                 
                     register-of-members-interests.datasettes.com/.json

references:
[{"href": "https://fivethirtyeight.datasettes.com/", "label": "fivethirtyeight.datasettes.com"}, {"href": "https://global-power-plants.datasettes.com/", "label": "global-power-plants.datasettes.com"}, {"href": "https://register-of-members-interests.datasettes.com/", "label": "register-of-members-interests.datasettes.com"}, {"href": "https://fivethirtyeight.datasettes.com/.json", "label": "fivethirtyeight.datasettes.com/.json"}, {"href": "https://global-power-plants.datasettes.com/.json", "label": "global-power-plants.datasettes.com/.json"}, {"href": "https://register-of-members-interests.datasettes.com/.json", "label": "register-of-members-interests.datasettes.com/.json"}]


id: pages:databaseview
page: pages
ref: databaseview
title: Database
breadcrumbs: ["Pages and API endpoints"]
content:
Each database has a page listing the tables, views and canned queries available for that database. If the  execute-sql  permission is enabled (it's on by default) there will also be an interface for executing arbitrary SQL select queries against the data. 
             Examples: 
             
                 
                     fivethirtyeight.datasettes.com/fivethirtyeight 
                 
                 
                     global-power-plants.datasettes.com/global-power-plants 
                 
             
             The JSON version of this page provides programmatic access to the underlying data: 
             
                 
                     fivethirtyeight.datasettes.com/fivethirtyeight.json 
                 
                 
                     global-power-plants.datasettes.com/global-power-plants.json

references:
[{"href": "https://fivethirtyeight.datasettes.com/fivethirtyeight", "label": "fivethirtyeight.datasettes.com/fivethirtyeight"}, {"href": "https://global-power-plants.datasettes.com/global-power-plants", "label": "global-power-plants.datasettes.com/global-power-plants"}, {"href": "https://fivethirtyeight.datasettes.com/fivethirtyeight.json", "label": "fivethirtyeight.datasettes.com/fivethirtyeight.json"}, {"href": "https://global-power-plants.datasettes.com/global-power-plants.json", "label": "global-power-plants.datasettes.com/global-power-plants.json"}]


id: pages:databaseview-hidden
page: pages
ref: databaseview-hidden
title: Hidden tables
breadcrumbs: ["Pages and API endpoints", "Database"]
references: []
content:
Some tables listed on the database page are treated as hidden. Hidden tables are not completely invisible - they can be accessed through the "hidden tables" link at the bottom of the page. They are hidden because they represent low-level implementation details which are generally not useful to end-users of Datasette. 
                 The following tables are hidden by default: 
                 
                     
                         Any table with a name that starts with an underscore - this is a Datasette convention to help plugins easily hide their own internal tables. 
                     
                     
                         Tables that have been configured as  "hidden": true  using  Hiding tables . 
                     
                     
                         *_fts  tables that implement SQLite full-text search indexes. 
                     
                     
                         Tables relating to the inner workings of the SpatiaLite SQLite extension. 
                     
                     
                         sqlite_stat  tables used to store statistics used by the query optimizer.


id: pages:queryview
page: pages
ref: queryview
title: Queries
breadcrumbs: ["Pages and API endpoints"]
references: []
content:
The  /database-name/-/query  page can be used to execute an arbitrary SQL query against that database, if the  execute-sql  permission is enabled. This query is passed as the  ?sql=  query string parameter. 
             This means you can link directly to a query by constructing the following URL: 
             /database-name/-/query?sql=SELECT+*+FROM+table_name 
             Each configured  canned query  has its own page, at  /database-name/query-name . Viewing this page will execute the query and display the results. 
             In both cases adding a  .json  extension to the URL will return the results as JSON.


id: pages:tableview
page: pages
ref: tableview
title: Table
breadcrumbs: ["Pages and API endpoints"]
content:
The table page is the heart of Datasette: it allows users to interactively explore the contents of a database table, including sorting, filtering,  Full-text search  and applying  Facets . 
             The HTML interface is worth spending some time exploring. As with other pages, you can return the JSON data by appending  .json  to the URL path, before any  ?  query string arguments. 
             The query string arguments are described in more detail here:  Table arguments 
             You can also use the table page to interactively construct a SQL query - by applying different filters and a sort order for example - and then click the "View and edit SQL" link to see the SQL query that was used for the page and edit and re-submit it. 
             Some examples: 
             
                 
                     ../items  lists all of the line-items registered by UK MPs as potential conflicts of interest. It demonstrates Datasette's support for  Full-text search . 
                 
                 
                     ../antiquities-act%2Factions_under_antiquities_act  is an interface for exploring the "actions under the antiquities act" data table published by FiveThirtyEight. 
                 
                 
                     ../global-power-plants?country_long=United+Kingdom&primary_fuel=Gas  is a filtered table page showing every Gas power plant in the United Kingdom. It includes some default facets (configured using  its metadata.json ) and uses the  datasette-cluster-map  plugin to show a map of the results.

references:
[{"href": "https://register-of-members-interests.datasettes.com/regmem/items", "label": "../items"}, {"href": "https://fivethirtyeight.datasettes.com/fivethirtyeight/antiquities-act%2Factions_under_antiquities_act", "label": "../antiquities-act%2Factions_under_antiquities_act"}, {"href": "https://global-power-plants.datasettes.com/global-power-plants/global-power-plants?_facet=primary_fuel&_facet=owner&_facet=country_long&country_long__exact=United+Kingdom&primary_fuel=Gas", "label": "../global-power-plants?country_long=United+Kingdom&primary_fuel=Gas"}, {"href": "https://global-power-plants.datasettes.com/-/metadata", "label": "its metadata.json"}, {"href": "https://github.com/simonw/datasette-cluster-map", "label": "datasette-cluster-map"}]


id: pages:rowview
page: pages
ref: rowview
title: Row
breadcrumbs: ["Pages and API endpoints"]
content:
Every row in every Datasette table has its own URL. This means individual records can be linked to directly. 
             Table cells with extremely long text contents are truncated on the table view according to the  truncate_cells_html  setting. If a cell has been truncated the full length version of that cell will be available on the row page. 
             Rows which are the targets of foreign key references from other tables will show a link to a filtered search for all records that reference that row. Here's an example from the Registers of Members Interests database: 
             ../people/uk~2Eorg~2Epublicwhip~2Fperson~2F10001 
             Note that this URL includes the encoded primary key of the record. 
             Here's that same page as JSON: 
             ../people/uk~2Eorg~2Epublicwhip~2Fperson~2F10001.json

references:
[{"href": "https://register-of-members-interests.datasettes.com/regmem/people/uk~2Eorg~2Epublicwhip~2Fperson~2F10001", "label": "../people/uk~2Eorg~2Epublicwhip~2Fperson~2F10001"}, {"href": "https://register-of-members-interests.datasettes.com/regmem/people/uk~2Eorg~2Epublicwhip~2Fperson~2F10001.json", "label": "../people/uk~2Eorg~2Epublicwhip~2Fperson~2F10001.json"}]


id: metadata:id1
page: metadata
ref: id1
title: Metadata
breadcrumbs: []
references: []
content:
Data loves metadata. Any time you run Datasette you can optionally include a
            YAML or JSON file with metadata about your databases and tables. Datasette will then
            display that information in the web UI. 
         Run Datasette like this: 
         datasette database1.db database2.db --metadata metadata.yaml 
         Your  metadata.yaml  file can look something like this: 
         [[[cog
from metadata_doc import metadata_example
metadata_example(cog, {
    "title": "Custom title for your index page",
    "description": "Some description text can go here",
    "license": "ODbL",
    "license_url": "https://opendatacommons.org/licenses/odbl/",
    "source": "Original Data Source",
    "source_url": "http://example.com/"
}) 
         ]]] 
         [[[end]]] 
         Choosing YAML over JSON adds support for multi-line strings and comments. 
         The above metadata will be displayed on the index page of your Datasette-powered
            site. The source and license information will also be included in the footer of
            every page served by Datasette. 
         Any special HTML characters in  description  will be escaped. If you want to
            include HTML in your description, you can use a  description_html  property
            instead.


id: metadata:per-database-and-per-table-metadata
page: metadata
ref: per-database-and-per-table-metadata
title: Per-database and per-table metadata
breadcrumbs: ["Metadata"]
references: []
content:
Metadata at the top level of the file 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: 
             [[[cog
metadata_example(cog, {
    "databases": {
        "database1": {
            "source": "Alternative source",
            "source_url": "http://example.com/",
            "tables": {
                "example_table": {
                    "description_html": "Custom <em>table</em> description",
                    "license": "CC BY 3.0 US",
                    "license_url": "https://creativecommons.org/licenses/by/3.0/us/"
                }
            }
        }
    }
}) 
             ]]] 
             [[[end]]] 
             Each of the top-level metadata fields can be used at the database and table level.


id: metadata:metadata-source-license-about
page: metadata
ref: metadata-source-license-about
title: Source, license and about
breadcrumbs: ["Metadata"]
references: []
content:
The three visible metadata fields you can apply to everything, specific databases or specific tables are source, license and about. All three are optional. 
             source  and  source_url  should be used to indicate where the underlying data came from. 
             license  and  license_url  should be used to indicate the license under which the data can be used. 
             about  and  about_url  can be used to link to further information about the project - an accompanying blog entry for example. 
             For each of these you can provide just the  *_url  field and Datasette will treat that as the default link label text and display the URL directly on the page.


id: metadata:metadata-column-descriptions
page: metadata
ref: metadata-column-descriptions
title: Column descriptions
breadcrumbs: ["Metadata"]
content:
You can include descriptions for your columns by adding a  "columns": {"name-of-column": "description-of-column"}  block to your table metadata: 
             [[[cog
metadata_example(cog, {
    "databases": {
        "database1": {
            "tables": {
                "example_table": {
                    "columns": {
                        "column1": "Description of column 1",
                        "column2": "Description of column 2"
                    }
                }
            }
        }
    }
}) 
             ]]] 
             [[[end]]] 
             These will be displayed at the top of the table page, and will also show in the cog menu for each column. 
             You can see an example of how these look at  latest.datasette.io/fixtures/roadside_attractions .

references:
[{"href": "https://latest.datasette.io/fixtures/roadside_attractions", "label": "latest.datasette.io/fixtures/roadside_attractions"}]


id: metadata:specifying-units-for-a-column
page: metadata
ref: specifying-units-for-a-column
title: Specifying units for a column
breadcrumbs: ["Metadata"]
content:
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: 
             [[[cog
metadata_example(cog, {
    "databases": {
        "database1": {
            "tables": {
                "example_table": {
                    "units": {
                        "column1": "metres",
                        "column2": "Hz"
                    }
                }
            }
        }
    }
}) 
             ]]] 
             [[[end]]] 
             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: 
             [[[cog
metadata_example(cog, {
    "custom_units": [
        "decibel = [] = dB"
    ]
}) 
             ]]] 
             [[[end]]]

references:
[{"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"}]


id: metadata:metadata-default-sort
page: metadata
ref: metadata-default-sort
title: Setting a default sort order
breadcrumbs: ["Metadata"]
references: []
content:
By default Datasette tables are sorted by primary key. You can over-ride this default for a specific table using the  "sort"  or  "sort_desc"  metadata properties: 
             [[[cog
metadata_example(cog, {
    "databases": {
        "mydatabase": {
            "tables": {
                "example_table": {
                    "sort": "created"
                }
            }
        }
    }
}) 
             ]]] 
             [[[end]]] 
             Or use  "sort_desc"  to sort in descending order: 
             [[[cog
metadata_example(cog, {
    "databases": {
        "mydatabase": {
            "tables": {
                "example_table": {
                    "sort_desc": "created"
                }
            }
        }
    }
}) 
             ]]] 
             [[[end]]]


id: metadata:metadata-page-size
page: metadata
ref: metadata-page-size
title: Setting a custom page size
breadcrumbs: ["Metadata"]
references: []
content:
Datasette defaults to displaying 100 rows per page, for both tables and views. You can change this default page size on a per-table or per-view basis using the  "size"  key in  metadata.json : 
             [[[cog
metadata_example(cog, {
    "databases": {
        "mydatabase": {
            "tables": {
                "example_table": {
                    "size": 10
                }
            }
        }
    }
}) 
             ]]] 
             [[[end]]] 
             This size can still be over-ridden by passing e.g.  ?_size=50  in the query string.


id: metadata:metadata-sortable-columns
page: metadata
ref: metadata-sortable-columns
title: Setting which columns can be used for sorting
breadcrumbs: ["Metadata"]
references: []
content:
Datasette allows any column to be used for sorting by default. If you need to
                control which columns are available for sorting you can do so using the optional
                 sortable_columns  key: 
             [[[cog
metadata_example(cog, {
    "databases": {
        "database1": {
            "tables": {
                "example_table": {
                    "sortable_columns": [
                        "height",
                        "weight"
                    ]
                }
            }
        }
    }
}) 
             ]]] 
             [[[end]]] 
             This will restrict sorting of  example_table  to just the  height  and
                 weight  columns. 
             You can also disable sorting entirely by setting  "sortable_columns": [] 
             You can use  sortable_columns  to enable specific sort orders for a view called  name_of_view  in the database  my_database  like so: 
             [[[cog
metadata_example(cog, {
    "databases": {
        "my_database": {
            "tables": {
                "name_of_view": {
                    "sortable_columns": [
                        "clicks",
                        "impressions"
                    ]
                }
            }
        }
    }
}) 
             ]]] 
             [[[end]]]


id: metadata:label-columns
page: metadata
ref: label-columns
title: Specifying the label column for a table
breadcrumbs: ["Metadata"]
references: []
content:
Datasette's HTML interface attempts to display foreign key references as
                labelled hyperlinks. By default, it looks for referenced tables that only have
                two columns: a primary key column and one other. It assumes that the second
                column should be used as the link label. 
             If your table has more than two columns you can specify which column should be
                used for the link label with the  label_column  property: 
             [[[cog
metadata_example(cog, {
    "databases": {
        "database1": {
            "tables": {
                "example_table": {
                    "label_column": "title"
                }
            }
        }
    }
}) 
             ]]] 
             [[[end]]]


id: metadata:metadata-hiding-tables
page: metadata
ref: metadata-hiding-tables
title: Hiding tables
breadcrumbs: ["Metadata"]
references: []
content:
You can hide tables from the database listing view (in the same way that FTS and
                SpatiaLite tables are automatically hidden) using  "hidden": true : 
             [[[cog
metadata_example(cog, {
    "databases": {
        "database1": {
            "tables": {
                "example_table": {
                    "hidden": True
                }
            }
        }
    }
}) 
             ]]] 
             [[[end]]]


id: metadata:top-level-metadata
page: metadata
ref: top-level-metadata
title: Top-level metadata
breadcrumbs: ["Metadata", "Metadata reference"]
references: []
content:
"Top-level" metadata refers to fields that can be specified at the root level  of a metadata file. These attributes are meant to describe the entire Datasette instance. 
                 The following are the full list of allowed top-level metadata fields: 
                 
                     
                         title 
                     
                     
                         description 
                     
                     
                         description_html 
                     
                     
                         license 
                     
                     
                         license_url 
                     
                     
                         source 
                     
                     
                         source_url


id: metadata:database-level-metadata
page: metadata
ref: database-level-metadata
title: Database-level metadata
breadcrumbs: ["Metadata", "Metadata reference"]
references: []
content:
"Database-level" metadata refers to fields that can be specified for each database in a Datasette instance. These attributes should be listed under a database inside the  "databases"  field. 
                 The following are the full list of allowed database-level metadata fields: 
                 
                     
                         source 
                     
                     
                         source_url 
                     
                     
                         license 
                     
                     
                         license_url 
                     
                     
                         about 
                     
                     
                         about_url


id: metadata:table-level-metadata
page: metadata
ref: table-level-metadata
title: Table-level metadata
breadcrumbs: ["Metadata", "Metadata reference"]
references: []
content:
"Table-level" metadata refers to fields that can be specified for each table in a Datasette instance. These attributes should be listed under a specific table using the  "tables"  field. 
                 The following are the full list of allowed table-level metadata fields: 
                 
                     
                         source 
                     
                     
                         source_url 
                     
                     
                         license 
                     
                     
                         license_url 
                     
                     
                         about 
                     
                     
                         about_url 
                     
                     
                         hidden 
                     
                     
                         sort/sort_desc 
                     
                     
                         size 
                     
                     
                         sortable_columns 
                     
                     
                         label_column 
                     
                     
                         facets 
                     
                     
                         fts_table 
                     
                     
                         fts_pk 
                     
                     
                         searchmode 
                     
                     
                         columns


id: introspection:id1
page: introspection
ref: id1
title: Introspection
breadcrumbs: []
references: []
content:
Datasette includes some pages and JSON API endpoints for introspecting the current instance. These can be used to understand some of the internals of Datasette and to see how a particular instance has been configured. 
         Each of these pages can be viewed in your browser. Add  .json  to the URL to get back the contents as JSON.


id: introspection:jsondataview-metadata
page: introspection
ref: jsondataview-metadata
title: /-/metadata
breadcrumbs: ["Introspection"]
references: [{"href": "https://fivethirtyeight.datasettes.com/-/metadata", "label": "Metadata example"}]
content:
Shows the contents of the  metadata.json  file that was passed to  datasette serve , if any.  Metadata example : 
             {
    "license": "CC Attribution 4.0 License",
    "license_url": "http://creativecommons.org/licenses/by/4.0/",
    "source": "fivethirtyeight/data on GitHub",
    "source_url": "https://github.com/fivethirtyeight/data",
    "title": "Five Thirty Eight",
    "databases": {

    }
}


id: introspection:jsondataview-versions
page: introspection
ref: jsondataview-versions
title: /-/versions
breadcrumbs: ["Introspection"]
references: [{"href": "https://latest.datasette.io/-/versions", "label": "Versions example"}]
content:
Shows the version of Datasette, Python and SQLite.  Versions example : 
             {
    "datasette": {
        "version": "0.60"
    },
    "python": {
        "full": "3.8.12 (default, Dec 21 2021, 10:45:09) \n[GCC 10.2.1 20210110]",
        "version": "3.8.12"
    },
    "sqlite": {
        "extensions": {
            "json1": null
        },
        "fts_versions": [
            "FTS5",
            "FTS4",
            "FTS3"
        ],
        "compile_options": [
            "COMPILER=gcc-6.3.0 20170516",
            "ENABLE_FTS3",
            "ENABLE_FTS4",
            "ENABLE_FTS5",
            "ENABLE_JSON1",
            "ENABLE_RTREE",
            "THREADSAFE=1"
        ],
        "version": "3.37.0"
    }
}


id: introspection:jsondataview-plugins
page: introspection
ref: jsondataview-plugins
title: /-/plugins
breadcrumbs: ["Introspection"]
references: [{"href": "https://san-francisco.datasettes.com/-/plugins", "label": "Plugins example"}]
content:
Shows a list of currently installed plugins and their versions.  Plugins example : 
             [
    {
        "name": "datasette_cluster_map",
        "static": true,
        "templates": false,
        "version": "0.10",
        "hooks": ["extra_css_urls", "extra_js_urls", "extra_body_script"]
    }
] 
             Add  ?all=1  to include details of the default plugins baked into Datasette.


id: introspection:jsondataview-settings
page: introspection
ref: jsondataview-settings
title: /-/settings
breadcrumbs: ["Introspection"]
references: [{"href": "https://fivethirtyeight.datasettes.com/-/settings", "label": "Settings example"}]
content:
Shows the  Settings  for this instance of Datasette.  Settings example : 
             {
    "default_facet_size": 30,
    "default_page_size": 100,
    "facet_suggest_time_limit_ms": 50,
    "facet_time_limit_ms": 1000,
    "max_returned_rows": 1000,
    "sql_time_limit_ms": 1000
}


id: introspection:jsondataview-config
page: introspection
ref: jsondataview-config
title: /-/config
breadcrumbs: ["Introspection"]
references: [{"href": "https://latest.datasette.io/-/config", "label": "Config example"}]
content:
Shows the  configuration  for this instance of Datasette. This is generally the contents of the  datasette.yaml or datasette.json  file, which can include plugin configuration as well.  Config example : 
             {
    "settings": {
        "template_debug": true,
        "trace_debug": true,
        "force_https_urls": true
    }
} 
             Any keys that include the one of the following substrings in their names will be returned as redacted  ***  output, to help avoid accidentally leaking private configuration information:  secret ,  key ,  password ,  token ,  hash ,  dsn .


id: introspection:jsondataview-databases
page: introspection
ref: jsondataview-databases
title: /-/databases
breadcrumbs: ["Introspection"]
references: [{"href": "https://latest.datasette.io/-/databases", "label": "Databases example"}]
content:
Shows currently attached databases.  Databases example : 
             [
    {
        "hash": null,
        "is_memory": false,
        "is_mutable": true,
        "name": "fixtures",
        "path": "fixtures.db",
        "size": 225280
    }
]


id: introspection:jsondataview-threads
page: introspection
ref: jsondataview-threads
title: /-/threads
breadcrumbs: ["Introspection"]
references: [{"href": "https://latest.datasette.io/-/threads", "label": "Threads example"}]
content:
Shows details of threads and  asyncio  tasks.  Threads example : 
             {
    "num_threads": 2,
    "threads": [
        {
            "daemon": false,
            "ident": 4759197120,
            "name": "MainThread"
        },
        {
            "daemon": true,
            "ident": 123145319682048,
            "name": "Thread-1"
        },
    ],
    "num_tasks": 3,
    "tasks": [
        "<Task pending coro=<RequestResponseCycle.run_asgi() running at uvicorn/protocols/http/httptools_impl.py:385> cb=[set.discard()]>",
        "<Task pending coro=<Server.serve() running at uvicorn/main.py:361> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x10365c3d0>()]> cb=[run_until_complete.<locals>.<lambda>()]>",
        "<Task pending coro=<LifespanOn.main() running at uvicorn/lifespan/on.py:48> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x10364f050>()]>>"
    ]
}


id: introspection:jsondataview-actor
page: introspection
ref: jsondataview-actor
title: /-/actor
breadcrumbs: ["Introspection"]
references: []
content:
Shows the currently authenticated actor. Useful for debugging Datasette authentication plugins. 
             {
    "actor": {
        "id": 1,
        "username": "some-user"
    }
}


id: introspection:messagesdebugview
page: introspection
ref: messagesdebugview
title: /-/messages
breadcrumbs: ["Introspection"]
references: []
content:
The debug tool at  /-/messages  can be used to set flash messages to try out that feature. See  .add_message(request, message, type=datasette.INFO)  for details of this feature.


id: sql_queries:sql
page: sql_queries
ref: sql
title: Running SQL queries
breadcrumbs: []
references: []
content:
Datasette treats SQLite database files as read-only and immutable. This means it is not possible to execute INSERT or UPDATE statements using Datasette, which allows us to expose SELECT statements to the outside world without needing to worry about SQL injection attacks. 
         The easiest way to execute custom SQL against Datasette is through the web UI. The database index page includes a SQL editor that lets you run any SELECT query you like. You can also construct queries using the filter interface on the tables page, then click "View and edit SQL" to open that query in the custom SQL editor. 
         Note that this interface is only available if the  execute-sql  permission is allowed. See  Controlling the ability to execute arbitrary SQL . 
         Any Datasette SQL query is reflected in the URL of the page, allowing you to bookmark them, share them with others and navigate through previous queries using your browser back button. 
         You can also retrieve the results of any query as JSON by adding  .json  to the base URL.


id: sql_queries:sql-parameters
page: sql_queries
ref: sql-parameters
title: Named parameters
breadcrumbs: ["Running SQL queries"]
references: [{"href": "https://github.com/simonw/datasette/issues/761", "label": "of exceptions"}]
content:
Datasette has special support for SQLite named parameters. Consider a SQL query like this: 
             select * from Street_Tree_List
where "PermitNotes" like :notes
and "qSpecies" = :species 
             If you execute this query using the custom query editor, Datasette will extract the two named parameters and use them to construct form fields for you to provide values. 
             You can also provide values for these fields by constructing a URL: 
             /mydatabase?sql=select...&species=44 
             SQLite string escaping rules will be applied to values passed using named parameters - they will be wrapped in quotes and their content will be correctly escaped. 
             Values from named parameters are treated as SQLite strings. If you need to perform numeric comparisons on them you should cast them to an integer or float first using  cast(:name as integer)  or  cast(:name as real) , for example: 
             select * from Street_Tree_List
where latitude > cast(:min_latitude as real)
and latitude < cast(:max_latitude as real) 
             Datasette disallows custom SQL queries containing the string PRAGMA (with a small number  of exceptions ) as SQLite pragma statements can be used to change database settings at runtime. If you need to include the string "pragma" in a query you can do so safely using a named parameter.


id: sql_queries:sql-views
page: sql_queries
ref: sql-views
title: Views
breadcrumbs: ["Running SQL queries"]
content:
If you want to bundle some pre-written SQL queries with your Datasette-hosted database you can do so in two ways. The first is to include SQL views in your database - Datasette will then list those views on your database index page. 
             The quickest way to create views is with the SQLite command-line interface: 
             sqlite3 sf-trees.db 
             SQLite version 3.19.3 2017-06-27 16:48:08
Enter ".help" for usage hints.
sqlite> CREATE VIEW demo_view AS select qSpecies from Street_Tree_List;
<CTRL+D> 
             You can also use the  sqlite-utils  tool to  create a view : 
             sqlite-utils create-view sf-trees.db demo_view "select qSpecies from Street_Tree_List"

references:
[{"href": "https://sqlite-utils.datasette.io/", "label": "sqlite-utils"}, {"href": "https://sqlite-utils.datasette.io/en/stable/cli.html#creating-views", "label": "create a view"}]


id: sql_queries:id1
page: sql_queries
ref: id1
title: Canned queries
breadcrumbs: ["Running SQL queries"]
references: []
content:
As an alternative to adding views to your database, you can define canned queries inside your  datasette.yaml  file. Here's an example: 
             [[[cog
from metadata_doc import config_example, config_example
config_example(cog, {
    "databases": {
       "sf-trees": {
           "queries": {
               "just_species": {
                   "sql": "select qSpecies from Street_Tree_List"
               }
           }
       }
    }
}) 
             ]]] 
             [[[end]]] 
             Then run Datasette like this: 
             datasette sf-trees.db -m metadata.json 
             Each canned query will be listed on the database index page, and will also get its own URL at: 
             /database-name/canned-query-name 
             For the above example, that URL would be: 
             /sf-trees/just_species 
             You can optionally include  "title"  and  "description"  keys to show a title and description on the canned query page. As with regular table metadata you can alternatively specify  "description_html"  to have your description rendered as HTML (rather than having HTML special characters escaped).


id: sql_queries:canned-queries-named-parameters
page: sql_queries
ref: canned-queries-named-parameters
title: Canned query parameters
breadcrumbs: ["Running SQL queries", "Canned queries"]
content:
Canned queries support named parameters, so if you include those in the SQL you will then be able to enter them using the form fields on the canned query page or by adding them to the URL. This means canned queries can be used to create custom JSON APIs based on a carefully designed SQL statement. 
                 Here's an example of a canned query with a named parameter: 
                 select neighborhood, facet_cities.name, state
from facetable
  join facet_cities on facetable.city_id = facet_cities.id
where neighborhood like '%' || :text || '%'
order by neighborhood; 
                 In the canned query configuration looks like this: 
                 [[[cog
config_example(cog, """
databases:
  fixtures:
    queries:
      neighborhood_search:
        title: Search neighborhoods
        sql: |-
          select neighborhood, facet_cities.name, state
          from facetable
            join facet_cities on facetable.city_id = facet_cities.id
          where neighborhood like '%' || :text || '%'
          order by neighborhood
""") 
                 ]]] 
                 [[[end]]] 
                 Note that we are using SQLite string concatenation here - the  ||  operator - to add wildcard  %  characters to the string provided by the user. 
                 You can try this canned query out here:
                     https://latest.datasette.io/fixtures/neighborhood_search?text=town 
                 In this example the  :text  named parameter is automatically extracted from the query using a regular expression. 
                 You can alternatively provide an explicit list of named parameters using the  "params"  key, like this: 
                 [[[cog
config_example(cog, """
databases:
  fixtures:
    queries:
      neighborhood_search:
        title: Search neighborhoods
        params:
        - text
        sql: |-
          select neighborhood, facet_cities.name, state
          from facetable
            join facet_cities on facetable.city_id = facet_cities.id
          where neighborhood like '%' || :text || '%'
          order by neighborhood
""") 
                 ]]] 
                 [[[end]]]

references:
[{"href": "https://latest.datasette.io/fixtures/neighborhood_search?text=town", "label": "https://latest.datasette.io/fixtures/neighborhood_search?text=town"}]


id: sql_queries:hide-sql
page: sql_queries
ref: hide-sql
title: hide_sql
breadcrumbs: ["Running SQL queries", "Canned queries", "Additional canned query options"]
references: []
content:
Canned queries default to displaying their SQL query at the top of the page. If the query is extremely long you may want to hide it by default, with a "show" link that can be used to make it visible. 
                     Add the  "hide_sql": true  option to hide the SQL query by default.


id: sql_queries:fragment
page: sql_queries
ref: fragment
title: fragment
breadcrumbs: ["Running SQL queries", "Canned queries", "Additional canned query options"]
content:
Some plugins, such as  datasette-vega , can be configured by including additional data in the fragment hash of the URL - the bit that comes after a  #  symbol. 
                     You can set a default fragment hash that will be included in the link to the canned query from the database index page using the  "fragment"  key. 
                     This example demonstrates both  fragment  and  hide_sql : 
                     [[[cog
config_example(cog, """
databases:
  fixtures:
    queries:
      neighborhood_search:
        fragment: fragment-goes-here
        hide_sql: true
        sql: |-
          select neighborhood, facet_cities.name, state
          from facetable join facet_cities on facetable.city_id = facet_cities.id
          where neighborhood like '%' || :text || '%' order by neighborhood;
""") 
                     ]]] 
                     [[[end]]] 
                     See here  for a demo of this in action.

references:
[{"href": "https://github.com/simonw/datasette-vega", "label": "datasette-vega"}, {"href": "https://latest.datasette.io/fixtures#queries", "label": "See here"}]


id: sql_queries:canned-queries-writable
page: sql_queries
ref: canned-queries-writable
title: Writable canned queries
breadcrumbs: ["Running SQL queries", "Canned queries"]
references: []
content:
Canned queries by default are read-only. You can use the  "write": true  key to indicate that a canned query can write to the database. 
                 See  Access to specific canned queries  for details on how to add permission checks to canned queries, using the  "allow"  key. 
                 [[[cog
config_example(cog, {
    "databases": {
        "mydatabase": {
            "queries": {
                "add_name": {
                    "sql": "INSERT INTO names (name) VALUES (:name)",
                    "write": True
                }
            }
        }
    }
}) 
                 ]]] 
                 [[[end]]] 
                 This configuration will create a page at  /mydatabase/add_name  displaying a form with a  name  field. Submitting that form will execute the configured  INSERT  query. 
                 You can customize how Datasette represents success and errors using the following optional properties: 
                 
                     
                         on_success_message  - the message shown when a query is successful 
                     
                     
                         on_success_message_sql  - alternative to  on_success_message : a SQL query that should be executed to generate the message 
                     
                     
                         on_success_redirect  - the path or URL the user is redirected to on success 
                     
                     
                         on_error_message  - the message shown when a query throws an error 
                     
                     
                         on_error_redirect  - the path or URL the user is redirected to on error 
                     
                 
                 For example: 
                 [[[cog
config_example(cog, {
    "databases": {
        "mydatabase": {
            "queries": {
                "add_name": {
                    "sql": "INSERT INTO names (name) VALUES (:name)",
                    "params": ["name"],
                    "write": True,
                    "on_success_message_sql": "select 'Name inserted: ' || :name",
                    "on_success_redirect": "/mydatabase/names",
                    "on_error_message": "Name insert failed",
                    "on_error_redirect": "/mydatabase",
                }
            }
        }
    }
}) 
                 ]]] 
                 [[[end]]] 
                 You can use  "params"  to explicitly list the named parameters that should be displayed as form fields - otherwise they will be automatically detected.  "params"  is not necessary in the above example, since without it  "name"  would be automatically detected from the query. 
                 You can pre-populate form fields when the page first loads using a query string, e.g.  /mydatabase/add_name?name=Prepopulated . The user will have to submit the form to execute the query. 
                 If you specify a query in  "on_success_message_sql" , that query will be executed after the main query. The first column of the first row return by that query will be displayed as a success message. Named parameters from the main query will be made available to the success message query as well.


id: sql_queries:canned-queries-magic-parameters
page: sql_queries
ref: canned-queries-magic-parameters
title: Magic parameters
breadcrumbs: ["Running SQL queries", "Canned queries"]
references: []
content:
Named parameters that start with an underscore are special: they can be used to automatically add values created by Datasette that are not contained in the incoming form fields or query string. 
                 These magic parameters are only supported for canned queries: to avoid security issues (such as queries that extract the user's private cookies) they are not available to SQL that is executed by the user as a custom SQL query. 
                 Available magic parameters are: 
                 
                     
                         _actor_*  - e.g.  _actor_id ,  _actor_name 
                         
                             Fields from the currently authenticated  Actors . 
                         
                     
                     
                         _header_*  - e.g.  _header_user_agent 
                         
                             Header from the incoming HTTP request. The key should be in lower case and with hyphens converted to underscores e.g.  _header_user_agent  or  _header_accept_language . 
                         
                     
                     
                         _cookie_*  - e.g.  _cookie_lang 
                         
                             The value of the incoming cookie of that name. 
                         
                     
                     
                         _now_epoch 
                         
                             The number of seconds since the Unix epoch. 
                         
                     
                     
                         _now_date_utc 
                         
                             The date in UTC, e.g.  2020-06-01 
                         
                     
                     
                         _now_datetime_utc 
                         
                             The ISO 8601 datetime in UTC, e.g.  2020-06-24T18:01:07Z 
                         
                     
                     
                         _random_chars_*  - e.g.  _random_chars_128 
                         
                             A random string of characters of the specified length. 
                         
                     
                 
                 Here's an example configuration that adds a message from the authenticated user, storing various pieces of additional metadata using magic parameters: 
                 [[[cog
config_example(cog, """
databases:
  mydatabase:
    queries:
      add_message:
        allow:
          id: "*"
        sql: |-
          INSERT INTO messages (
            user_id, message, datetime
          ) VALUES (
            :_actor_id, :message, :_now_datetime_utc
          )
        write: true
""") 
                 ]]] 
                 [[[end]]] 
                 The form presented at  /mydatabase/add_message  will have just a field for  message  - the other parameters will be populated by the magic parameter mechanism. 
                 Additional custom magic parameters can be added by plugins using the  register_magic_parameters(datasette)  hook.


