# sections

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: 
             {
    "databases": {
        "russian-ads": {
            "tables": {
                "display_ads": {
                    "fts_table": "ads_fts",
                    "fts_pk": "id",
                    "searchmode": "raw"
                }
            }
        }
    }
}

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


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  metadata.json  file. Here's an example: 
             {
    "databases": {
       "sf-trees": {
           "queries": {
               "just_species": {
                   "sql": "select qSpecies from Street_Tree_List"
               }
           }
       }
    }
} 
             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 metadata (here  Using YAML for metadata  as  metadata.yaml ) it looks like this: 
                 databases:
  fixtures:
    queries:
      neighborhood_search:
        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
        title: Search neighborhoods 
                 Here's the equivalent using JSON (as  metadata.json ): 
                 {
    "databases": {
        "fixtures": {
            "queries": {
                "neighborhood_search": {
                    "sql": "select neighborhood, facet_cities.name, state\nfrom facetable\n  join facet_cities on facetable.city_id = facet_cities.id\nwhere neighborhood like '%' || :text || '%'\norder by neighborhood",
                    "title": "Search neighborhoods"
                }
            }
        }
    }
} 
                 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: 
                 databases:
  fixtures:
    queries:
      neighborhood_search:
        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
        title: Search neighborhoods

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 : 
                     {
    "databases": {
        "fixtures": {
            "queries": {
                "neighborhood_search": {
                    "sql": "select neighborhood, facet_cities.name, state\nfrom facetable join facet_cities on facetable.city_id = facet_cities.id\nwhere neighborhood like '%' || :text || '%' order by neighborhood;",
                    "fragment": "fragment-goes-here",
                    "hide_sql": true
                }
            }
        }
    }
} 
                     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  Controlling access to specific canned queries  for details on how to add permission checks to canned queries, using the  "allow"  key. 
                 {
    "databases": {
        "mydatabase": {
            "queries": {
                "add_name": {
                    "sql": "INSERT INTO names (name) VALUES (:name)",
                    "write": true
                }
            }
        }
    }
} 
                 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_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: 
                 {
    "databases": {
        "mydatabase": {
            "queries": {
                "add_name": {
                    "sql": "INSERT INTO names (name) VALUES (:name)",
                    "write": true,
                    "on_success_message": "Name inserted",
                    "on_success_redirect": "/mydatabase/names",
                    "on_error_message": "Name insert failed",
                    "on_error_redirect": "/mydatabase"
                }
            }
        }
    }
} 
                 You can use  "params"  to explicitly list the named parameters that should be displayed as form fields - otherwise they will be automatically detected. 
                 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.


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 (this time using  metadata.yaml  since that provides better support for multi-line SQL queries) that adds a message from the authenticated user, storing various pieces of additional metadata using magic parameters: 
                 databases:
  mydatabase:
    queries:
      add_message:
        allow:
          id: "*"
        sql: |-
          INSERT INTO messages (
            user_id, message, datetime
          ) VALUES (
            :_actor_id, :message, :_now_datetime_utc
          )
        write: true 
                 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.


id: sql_queries:canned-queries-json-api
page: sql_queries
ref: canned-queries-json-api
title: JSON API for writable canned queries
breadcrumbs: ["Running SQL queries", "Canned queries"]
references: []
content:
Writable canned queries can also be accessed using a JSON API. You can POST data to them using JSON, and you can request that their response is returned to you as JSON. 
                 To submit JSON to a writable canned query, encode key/value parameters as a JSON document: 
                 POST /mydatabase/add_message

{"message": "Message goes here"} 
                 You can also continue to submit data using regular form encoding, like so: 
                 POST /mydatabase/add_message

message=Message+goes+here 
                 There are three options for specifying that you would like the response to your request to return JSON data, as opposed to an HTTP redirect to another page. 
                 
                     
                         Set an  Accept: application/json  header on your request 
                     
                     
                         Include  ?_json=1  in the URL that you POST to 
                     
                     
                         Include  "_json": 1  in your JSON body, or  &_json=1  in your form encoded body 
                     
                 
                 The JSON response will look like this: 
                 {
    "ok": true,
    "message": "Query executed, 1 row affected",
    "redirect": "/data/add_name"
} 
                 The  "message"  and  "redirect"  values here will take into account  on_success_message ,  on_success_redirect ,  on_error_message  and  on_error_redirect , if they have been set.


id: sql_queries:id2
page: sql_queries
ref: id2
title: Pagination
breadcrumbs: ["Running SQL queries"]
references: []
content:
Datasette's default table pagination is designed to be extremely efficient. SQL OFFSET/LIMIT pagination can have a significant performance penalty once you get into multiple thousands of rows, as each page still requires the database to scan through every preceding row to find the correct offset. 
             When paginating through tables, Datasette instead orders the rows in the table by their primary key and performs a WHERE clause against the last seen primary key for the previous page. For example: 
             select rowid, * from Tree_List where rowid > 200 order by rowid limit 101 
             This represents page three for this particular table, with a page size of 100. 
             Note that we request 101 items in the limit clause rather than 100. This allows us to detect if we are on the last page of the results: if the query returns less than 101 rows we know we have reached the end of the pagination set. Datasette will only return the first 100 rows - the 101st is used purely to detect if there should be another page. 
             Since the where clause acts against the index on the primary key, the query is extremely fast even for records that are a long way into the overall pagination set.


id: sql_queries:id3
page: sql_queries
ref: id3
title: Cross-database queries
breadcrumbs: ["Running SQL queries"]
content:
SQLite has the ability to run queries that join across multiple databases. Up to ten databases can be attached to a single SQLite connection and queried together. 
             Datasette can execute joins across multiple databases if it is started with the  --crossdb  option: 
             datasette fixtures.db extra_database.db --crossdb 
             If it is started in this way, the  /_memory  page can be used to execute queries that join across multiple databases. 
             References to tables in attached databases should be preceded by the database name and a period. 
             For example, this query will show a list of tables across both of the above databases: 
             select
  'fixtures' as database, *
from
  [fixtures].sqlite_master
union
select
  'extra_database' as database, *
from
  [extra_database].sqlite_master 
             Try that out here .

references:
[{"href": "https://latest.datasette.io/_memory?sql=select%0D%0A++%27fixtures%27+as+database%2C+*%0D%0Afrom%0D%0A++%5Bfixtures%5D.sqlite_master%0D%0Aunion%0D%0Aselect%0D%0A++%27extra_database%27+as+database%2C+*%0D%0Afrom%0D%0A++%5Bextra_database%5D.sqlite_master", "label": "Try that out here"}]


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
  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 along...
  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
  --config CONFIG                 Deprecated: set config option using
                                  configname:value. Use --setting instead.
  --setting SETTING...            Setting, see
                                  docs.datasette.io/en/stable/settings.html
  --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
  --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
  --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"
    ]
  }
} 
                 The exit code 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)
  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)
  default_allow_sql            Allow anyone to run arbitrary SQL queries
                               (default=True)
  allow_download               Allow users to download the original SQLite
                               database files (default=True)
  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
  --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
  --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: json_api:id1
page: json_api
ref: id1
title: JSON API
breadcrumbs: []
references: []
content:
Datasette provides a JSON API for your SQLite databases. Anything you can do
            through the Datasette user interface can also be accessed as JSON via the API. 
         To access the API for a page, either click on the  .json  link on that page or
            edit the URL and add a  .json  extension to it. 
         If you started Datasette with the  --cors  option, each JSON endpoint will be
            served with the following additional HTTP headers: 
         Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Authorization
Access-Control-Expose-Headers: Link 
         This means JavaScript running on any domain will be able to make cross-origin
            requests to fetch the data. 
         If you start Datasette without the  --cors  option only JavaScript running on
            the same domain as Datasette will be able to access the API.


id: json_api:json-api-shapes
page: json_api
ref: json-api-shapes
title: Different shapes
breadcrumbs: ["JSON API"]
references: []
content:
The default JSON representation of data from a SQLite table or custom query
                looks like this: 
             {
    "database": "sf-trees",
    "table": "qSpecies",
    "columns": [
        "id",
        "value"
    ],
    "rows": [
        [
            1,
            "Myoporum laetum :: Myoporum"
        ],
        [
            2,
            "Metrosideros excelsa :: New Zealand Xmas Tree"
        ],
        [
            3,
            "Pinus radiata :: Monterey Pine"
        ]
    ],
    "truncated": false,
    "next": "100",
    "next_url": "http://127.0.0.1:8001/sf-trees-02c8ef1/qSpecies.json?_next=100",
    "query_ms": 1.9571781158447266
} 
             The  columns  key lists the columns that are being returned, and the  rows 
                key then returns a list of lists, each one representing a row. The order of the
                values in each row corresponds to the columns. 
             The  _shape  parameter can be used to access alternative formats for the
                 rows  key which may be more convenient for your application. There are three
                options: 
             
                 
                     ?_shape=arrays  -  "rows"  is the default option, shown above 
                 
                 
                     ?_shape=objects  -  "rows"  is a list of JSON key/value objects 
                 
                 
                     ?_shape=array  - an JSON array of objects 
                 
                 
                     ?_shape=array&_nl=on  - a newline-separated list of JSON objects 
                 
                 
                     ?_shape=arrayfirst  - a flat JSON array containing just the first value from each row 
                 
                 
                     ?_shape=object  - a JSON object keyed using the primary keys of the rows 
                 
             
             _shape=objects  looks like this: 
             {
    "database": "sf-trees",
    ...
    "rows": [
        {
            "id": 1,
            "value": "Myoporum laetum :: Myoporum"
        },
        {
            "id": 2,
            "value": "Metrosideros excelsa :: New Zealand Xmas Tree"
        },
        {
            "id": 3,
            "value": "Pinus radiata :: Monterey Pine"
        }
    ]
} 
             _shape=array  looks like this: 
             [
    {
        "id": 1,
        "value": "Myoporum laetum :: Myoporum"
    },
    {
        "id": 2,
        "value": "Metrosideros excelsa :: New Zealand Xmas Tree"
    },
    {
        "id": 3,
        "value": "Pinus radiata :: Monterey Pine"
    }
] 
             _shape=array&_nl=on  looks like this: 
             {"id": 1, "value": "Myoporum laetum :: Myoporum"}
{"id": 2, "value": "Metrosideros excelsa :: New Zealand Xmas Tree"}
{"id": 3, "value": "Pinus radiata :: Monterey Pine"} 
             _shape=arrayfirst  looks like this: 
             [1, 2, 3] 
             _shape=object  looks like this: 
             {
    "1": {
        "id": 1,
        "value": "Myoporum laetum :: Myoporum"
    },
    "2": {
        "id": 2,
        "value": "Metrosideros excelsa :: New Zealand Xmas Tree"
    },
    "3": {
        "id": 3,
        "value": "Pinus radiata :: Monterey Pine"
    }
] 
             The  object  shape is only available for queries against tables - custom SQL
                queries and views do not have an obvious primary key so cannot be returned using
                this format. 
             The  object  keys are always strings. If your table has a compound primary
                key, the  object  keys will be a comma-separated string.


id: json_api:json-api-pagination
page: json_api
ref: json-api-pagination
title: Pagination
breadcrumbs: ["JSON API"]
references: [{"href": "https://requests.readthedocs.io/", "label": "requests"}]
content:
The default JSON representation includes a  "next_url"  key which can be used to access the next page of results. If that key is null or missing then it means you have reached the final page of results. 
             Other representations include pagination information in the  link  HTTP header. That header will look something like this: 
             link: <https://latest.datasette.io/fixtures/sortable.json?_next=d%2Cv>; rel="next" 
             Here is an example Python function built using  requests  that returns a list of all of the paginated items from one of these API endpoints: 
             def paginate(url):
    items = []
    while url:
        response = requests.get(url)
        try:
            url = response.links.get("next").get("url")
        except AttributeError:
            url = None
        items.extend(response.json())
    return items


id: json_api:json-api-special
page: json_api
ref: json-api-special
title: Special JSON arguments
breadcrumbs: ["JSON API"]
content:
Every Datasette endpoint that can return JSON also accepts the following
                query string arguments: 
             
                 
                     ?_shape=SHAPE 
                     
                         The shape of the JSON to return, documented above. 
                     
                 
                 
                     ?_nl=on 
                     
                         When used with  ?_shape=array  produces newline-delimited JSON objects. 
                     
                 
                 
                     ?_json=COLUMN1&_json=COLUMN2 
                     
                         If any of your SQLite columns contain JSON values, you can use one or more
                             _json=  parameters to request that those columns be returned as regular
                            JSON. Without this argument those columns will be returned as JSON objects
                            that have been double-encoded into a JSON string value. 
                         Compare  this query without the argument  to  this query using the argument 
                     
                 
                 
                     ?_json_infinity=on 
                     
                         If your data contains infinity or -infinity values, Datasette will replace
                            them with None when returning them as JSON. If you pass  _json_infinity=1 
                            Datasette will instead return them as  Infinity  or  -Infinity  which is
                            invalid JSON but can be processed by some custom JSON parsers. 
                     
                 
                 
                     ?_timelimit=MS 
                     
                         Sets a custom time limit for the query in ms. You can use this for optimistic
                            queries where you would like Datasette to give up if the query takes too
                            long, for example if you want to implement autocomplete search but only if
                            it can be executed in less than 10ms. 
                     
                 
                 
                     ?_ttl=SECONDS 
                     
                         For how many seconds should this response be cached by HTTP proxies? Use
                             ?_ttl=0  to disable HTTP caching entirely for this request. 
                     
                 
                 
                     ?_trace=1 
                     
                         Turns on tracing for this page: SQL queries executed during the request will
                            be gathered and included in the response, either in a new  "_traces"  key
                            for JSON responses or at the bottom of the page if the response is in HTML. 
                         The structure of the data returned here should be considered highly unstable
                            and very likely to change. 
                         Only available if the  trace_debug  setting is enabled.

references:
[{"href": "https://fivethirtyeight.datasettes.com/fivethirtyeight.json?sql=select+%27{%22this+is%22%3A+%22a+json+object%22}%27+as+d&_shape=array", "label": "this query without the argument"}, {"href": "https://fivethirtyeight.datasettes.com/fivethirtyeight.json?sql=select+%27{%22this+is%22%3A+%22a+json+object%22}%27+as+d&_shape=array&_json=d", "label": "this query using the argument"}]


id: json_api:column-filter-arguments
page: json_api
ref: column-filter-arguments
title: Column filter arguments
breadcrumbs: ["JSON API", "Table arguments"]
references: []
content:
You can filter the data returned by the table based on column values using a query string argument. 
                 
                     
                         ?column__exact=value  or  ?_column=value 
                         
                             Returns rows where the specified column exactly matches the value. 
                         
                     
                     
                         ?column__not=value 
                         
                             Returns rows where the column does not match the value. 
                         
                     
                     
                         ?column__contains=value 
                         
                             Rows where the string column contains the specified value ( column like "%value%"  in SQL). 
                         
                     
                     
                         ?column__endswith=value 
                         
                             Rows where the string column ends with the specified value ( column like "%value"  in SQL). 
                         
                     
                     
                         ?column__startswith=value 
                         
                             Rows where the string column starts with the specified value ( column like "value%"  in SQL). 
                         
                     
                     
                         ?column__gt=value 
                         
                             Rows which are greater than the specified value. 
                         
                     
                     
                         ?column__gte=value 
                         
                             Rows which are greater than or equal to the specified value. 
                         
                     
                     
                         ?column__lt=value 
                         
                             Rows which are less than the specified value. 
                         
                     
                     
                         ?column__lte=value 
                         
                             Rows which are less than or equal to the specified value. 
                         
                     
                     
                         ?column__like=value 
                         
                             Match rows with a LIKE clause, case insensitive and with  %  as the wildcard character. 
                         
                     
                     
                         ?column__notlike=value 
                         
                             Match rows that do not match the provided LIKE clause. 
                         
                     
                     
                         ?column__glob=value 
                         
                             Similar to LIKE but uses Unix wildcard syntax and is case sensitive. 
                         
                     
                     
                         ?column__in=value1,value2,value3 
                         
                             Rows where column matches any of the provided values. 
                             You can use a comma separated string, or you can use a JSON array. 
                             The JSON array option is useful if one of your matching values itself contains a comma: 
                             ?column__in=["value","value,with,commas"] 
                         
                     
                     
                         ?column__notin=value1,value2,value3 
                         
                             Rows where column does not match any of the provided values. The inverse of  __in= . Also supports JSON arrays. 
                         
                     
                     
                         ?column__arraycontains=value 
                         
                             Works against columns that contain JSON arrays - matches if any of the values in that array match the provided value. 
                             This is only available if the  json1  SQLite extension is enabled. 
                         
                     
                     
                         ?column__arraynotcontains=value 
                         
                             Works against columns that contain JSON arrays - matches if none of the values in that array match the provided value. 
                             This is only available if the  json1  SQLite extension is enabled. 
                         
                     
                     
                         ?column__date=value 
                         
                             Column is a datestamp occurring on the specified YYYY-MM-DD date, e.g.  2018-01-02 . 
                         
                     
                     
                         ?column__isnull=1 
                         
                             Matches rows where the column is null. 
                         
                     
                     
                         ?column__notnull=1 
                         
                             Matches rows where the column is not null. 
                         
                     
                     
                         ?column__isblank=1 
                         
                             Matches rows where the column is blank, meaning null or the empty string. 
                         
                     
                     
                         ?column__notblank=1 
                         
                             Matches rows where the column is not blank.


id: json_api:json-api-table-arguments
page: json_api
ref: json-api-table-arguments
title: Special table arguments
breadcrumbs: ["JSON API", "Table arguments"]
content:
?_col=COLUMN1&_col=COLUMN2 
                         
                             List specific columns to display. These will be shown along with any primary keys. 
                         
                     
                     
                         ?_nocol=COLUMN1&_nocol=COLUMN2 
                         
                             List specific columns to hide - any column not listed will be displayed. Primary keys cannot be hidden. 
                         
                     
                     
                         ?_labels=on/off 
                         
                             Expand foreign key references for every possible column. See below. 
                         
                     
                     
                         ?_label=COLUMN1&_label=COLUMN2 
                         
                             Expand foreign key references for one or more specified columns. 
                         
                     
                     
                         ?_size=1000  or  ?_size=max 
                         
                             Sets a custom page size. This cannot exceed the  max_returned_rows  limit
                                passed to  datasette serve . Use  max  to get  max_returned_rows . 
                         
                     
                     
                         ?_sort=COLUMN 
                         
                             Sorts the results by the specified column. 
                         
                     
                     
                         ?_sort_desc=COLUMN 
                         
                             Sorts the results by the specified column in descending order. 
                         
                     
                     
                         ?_search=keywords 
                         
                             For SQLite tables that have been configured for
                                 full-text search  executes a search
                                with the provided keywords. 
                         
                     
                     
                         ?_search_COLUMN=keywords 
                         
                             Like  _search=  but allows you to specify the column to be searched, as
                                opposed to searching all columns that have been indexed by FTS. 
                         
                     
                     
                         ?_searchmode=raw 
                         
                             With this option, queries passed to  ?_search=  or  ?_search_COLUMN=  will
                                not have special characters escaped. This means you can make use of the full
                                set of  advanced SQLite FTS syntax ,
                                though this could potentially result in errors if the wrong syntax is used. 
                         
                     
                     
                         ?_where=SQL-fragment 
                         
                             If the  execute-sql  permission is enabled, this parameter
                                can be used to pass one or more additional SQL fragments to be used in the
                                 WHERE  clause of the SQL used to query the table. 
                             This is particularly useful if you are building a JavaScript application
                                that needs to do something creative but still wants the other conveniences
                                provided by the table view (such as faceting) and hence would like not to
                                have to construct a completely custom SQL query. 
                             Some examples: 
                             
                                 
                                     facetable?_where=_neighborhood like "%c%"&_where=_city_id=3 
                                 
                                 
                                     facetable?_where=_city_id in (select id from facet_cities where name != "Detroit") 
                                 
                             
                         
                     
                     
                         ?_through={json} 
                         
                             This can be used to filter rows via a join against another table. 
                             The JSON parameter must include three keys:  table ,  column  and  value . 
                             table  must be a table that the current table is related to via a foreign key relationship. 
                             column  must be a column in that other table. 
                             value  is the value that you want to match against. 
                             For example, to filter  roadside_attractions  to just show the attractions that have a characteristic of "museum", you would construct this JSON: 
                             {
    "table": "roadside_attraction_characteristics",
    "column": "characteristic_id",
    "value": "1"
} 
                             As a URL, that looks like this: 
                             ?_through={%22table%22:%22roadside_attraction_characteristics%22,%22column%22:%22characteristic_id%22,%22value%22:%221%22} 
                             Here's  an example . 
                         
                     
                     
                         ?_next=TOKEN 
                         
                             Pagination by continuation token - pass the token that was returned in the
                                 "next"  property by the previous page. 
                         
                     
                     
                         ?_facet=column 
                         
                             Facet by column. Can be applied multiple times, see  Facets . Only works on the default JSON output, not on any of the custom shapes. 
                         
                     
                     
                         ?_facet_size=100 
                         
                             Increase the number of facet results returned for each facet. Use  ?_facet_size=max  for the maximum available size, determined by  max_returned_rows . 
                         
                     
                     
                         ?_nofacet=1 
                         
                             Disable all facets and facet suggestions for this page, including any defined by  Facets in metadata.json . 
                         
                     
                     
                         ?_nosuggest=1 
                         
                             Disable facet suggestions for this page. 
                         
                     
                     
                         ?_nocount=1 
                         
                             Disable the  select count(*)  query used on this page - a count of  None  will be returned instead.

references:
[{"href": "https://www.sqlite.org/fts3.html", "label": "full-text search"}, {"href": "https://www.sqlite.org/fts5.html#full_text_query_syntax", "label": "advanced SQLite FTS syntax"}, {"href": "https://latest.datasette.io/fixtures/facetable?_where=_neighborhood%20like%20%22%c%%22&_where=_city_id=3", "label": "facetable?_where=_neighborhood like \"%c%\"&_where=_city_id=3"}, {"href": "https://latest.datasette.io/fixtures/facetable?_where=_city_id%20in%20(select%20id%20from%20facet_cities%20where%20name%20!=%20%22Detroit%22)", "label": "facetable?_where=_city_id in (select id from facet_cities where name != \"Detroit\")"}, {"href": "https://latest.datasette.io/fixtures/roadside_attractions?_through={%22table%22:%22roadside_attraction_characteristics%22,%22column%22:%22characteristic_id%22,%22value%22:%221%22}", "label": "an example"}]


id: json_api:expand-foreign-keys
page: json_api
ref: expand-foreign-keys
title: Expanding foreign key references
breadcrumbs: ["JSON API"]
references: []
content:
Datasette can detect foreign key relationships and resolve those references into
                labels. The HTML interface does this by default for every detected foreign key
                column - you can turn that off using  ?_labels=off . 
             You can request foreign keys be expanded in JSON using the  _labels=on  or
                 _label=COLUMN  special query string parameters. Here's what an expanded row
                looks like: 
             [
    {
        "rowid": 1,
        "TreeID": 141565,
        "qLegalStatus": {
            "value": 1,
            "label": "Permitted Site"
        },
        "qSpecies": {
            "value": 1,
            "label": "Myoporum laetum :: Myoporum"
        },
        "qAddress": "501X Baker St",
        "SiteOrder": 1
    }
] 
             The column in the foreign key table that is used for the label can be specified
                in  metadata.json  - see  Specifying the label column for a table .


id: json_api:json-api-discover-alternate
page: json_api
ref: json-api-discover-alternate
title: Discovering the JSON for a page
breadcrumbs: ["JSON API"]
references: []
content:
Most of the HTML pages served by Datasette provide a mechanism for discovering their JSON equivalents using the HTML  link  mechanism. 
             You can find this near the top of the source code of those pages, looking like this: 
             <link rel="alternate"
  type="application/json+datasette"
  href="https://latest.datasette.io/fixtures/sortable.json"> 
             The JSON URL is also made available in a  Link  HTTP header for the page: 
             Link: https://latest.datasette.io/fixtures/sortable.json; rel="alternate"; type="application/json+datasette"


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-config
page: introspection
ref: jsondataview-config
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-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: metadata:id1
page: metadata
ref: id1
title: Metadata
breadcrumbs: []
references: []
content:
Data loves metadata. Any time you run Datasette you can optionally include a
            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.json 
         Your  metadata.json  file can look something like this: 
         {
    "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/"
} 
         You can optionally use YAML instead of JSON, see  Using YAML for metadata . 
         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 JSON will be shown on the index page and in the
                footer on every page of the site. The license and source is expected to apply to
                all of your data. 
             You can also provide metadata at the per-database or per-table level, like this: 
             {
    "databases": {
        "database1": {
            "source": "Alternative source",
            "source_url": "http://example.com/",
            "tables": {
                "example_table": {
                    "description_html": "Custom <em>table</em> description",
                    "license": "CC BY 3.0 US",
                    "license_url": "https://creativecommons.org/licenses/by/3.0/us/"
                }
            }
        }
    }
} 
             Each of the top-level metadata fields can be used at the database and table level.


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: 
             {
    "databases": {
        "database1": {
            "tables": {
                "example_table": {
                    "columns": {
                        "column1": "Description of column 1",
                        "column2": "Description of column 2"
                    }
                }
            }
        }
    }
} 
             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: 
             {
    "databases": {
        "database1": {
            "tables": {
                "example_table": {
                    "units": {
                        "column1": "metres",
                        "column2": "Hz"
                    }
                }
            }
        }
    }
} 
             Units are interpreted using  Pint , and you can see the full list of available units in
                Pint's  unit registry . You can also add  custom units  to the metadata, which will be
                registered with Pint: 
             {
    "custom_units": [
        "decibel = [] = dB"
    ]
}

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: 
             {
    "databases": {
        "mydatabase": {
            "tables": {
                "example_table": {
                    "sort": "created"
                }
            }
        }
    }
} 
             Or use  "sort_desc"  to sort in descending order: 
             {
    "databases": {
        "mydatabase": {
            "tables": {
                "example_table": {
                    "sort_desc": "created"
                }
            }
        }
    }
}


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 : 
             {
    "databases": {
        "mydatabase": {
            "tables": {
                "example_table": {
                    "size": 10
                }
            }
        }
    }
} 
             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: 
             {
    "databases": {
        "database1": {
            "tables": {
                "example_table": {
                    "sortable_columns": [
                        "height",
                        "weight"
                    ]
                }
            }
        }
    }
} 
             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: 
             {
    "databases": {
        "my_database": {
            "tables": {
                "name_of_view": {
                    "sortable_columns": [
                        "clicks",
                        "impressions"
                    ]
                }
            }
        }
    }
}


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: 
             {
    "databases": {
        "database1": {
            "tables": {
                "example_table": {
                    "label_column": "title"
                }
            }
        }
    }
}


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 : 
             {
    "databases": {
        "database1": {
            "tables": {
                "example_table": {
                    "hidden": true
                }
            }
        }
    }
}


id: metadata:metadata-yaml
page: metadata
ref: metadata-yaml
title: Using YAML for metadata
breadcrumbs: ["Metadata"]
references: []
content:
Datasette accepts YAML as an alternative to JSON for your metadata configuration file. YAML is particularly useful for including multiline HTML and SQL strings. 
             Here's an example of a  metadata.yml  file, re-using an example from  Canned queries . 
             title: Demonstrating Metadata from YAML
description_html: |-
  <p>This description includes a long HTML string</p>
  <ul>
    <li>YAML is better for embedding HTML strings than JSON!</li>
  </ul>
license: ODbL
license_url: https://opendatacommons.org/licenses/odbl/
databases:
  fixtures:
    tables:
      no_primary_key:
        hidden: true
    queries:
      neighborhood_search:
        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;
        title: Search neighborhoods
        description_html: |-
          <p>This demonstrates <em>basic</em> LIKE search 
             The  metadata.yml  file is passed to Datasette using the same  --metadata  option: 
             datasette fixtures.db --metadata metadata.yml


id: spatialite:id1
page: spatialite
ref: id1
title: SpatiaLite
breadcrumbs: []
content:
The  SpatiaLite module  for SQLite adds features for handling geographic and spatial data. For an example of what you can do with it, see the tutorial  Building a location to time zone API with SpatiaLite . 
         To use it with Datasette, you need to install the  mod_spatialite  dynamic library. This can then be loaded into Datasette using the  --load-extension  command-line option. 
         Datasette can look for SpatiaLite in common installation locations if you run it like this: 
         datasette --load-extension=spatialite --setting default_allow_sql off 
         If SpatiaLite is in another location, use the full path to the extension instead: 
         datasette --setting default_allow_sql off \
  --load-extension=/usr/local/lib/mod_spatialite.dylib

references:
[{"href": "https://www.gaia-gis.it/fossil/libspatialite/index", "label": "SpatiaLite module"}, {"href": "https://datasette.io/tutorials/spatialite", "label": "Building a location to time zone API with SpatiaLite"}]


id: spatialite:spatialite-warning
page: spatialite
ref: spatialite-warning
title: Warning
breadcrumbs: ["SpatiaLite"]
content:
The SpatiaLite extension adds  a large number of additional SQL functions , some of which are not be safe for untrusted users to execute: they may cause the Datasette server to crash. 
                 You should not expose a SpatiaLite-enabled Datasette instance to the public internet without taking extra measures to secure it against potentially harmful SQL queries. 
                 The following steps are recommended: 
                 
                     
                         Disable arbitrary SQL queries by untrusted users. See  Controlling the ability to execute arbitrary SQL  for ways to do this. The easiest is to start Datasette with the  datasette --setting default_allow_sql off  option. 
                     
                     
                         Define  Canned queries  with the SQL queries that use SpatiaLite functions that you want people to be able to execute. 
                     
                 
                 The  Datasette SpatiaLite tutorial  includes detailed instructions for running SpatiaLite safely using these techniques

references:
[{"href": "https://www.gaia-gis.it/gaia-sins/spatialite-sql-5.0.1.html", "label": "a large number of additional SQL functions"}, {"href": "https://datasette.io/tutorials/spatialite", "label": "Datasette SpatiaLite tutorial"}]


id: spatialite:installing-spatialite-on-os-x
page: spatialite
ref: installing-spatialite-on-os-x
title: Installing SpatiaLite on OS X
breadcrumbs: ["SpatiaLite", "Installation"]
references: [{"href": "https://brew.sh/", "label": "Homebrew"}]
content:
The easiest way to install SpatiaLite on OS X is to use  Homebrew . 
                 brew update
brew install spatialite-tools 
                 This will install the  spatialite  command-line tool and the  mod_spatialite  dynamic library. 
                 You can now run Datasette like so: 
                 datasette --load-extension=spatialite


id: spatialite:installing-spatialite-on-linux
page: spatialite
ref: installing-spatialite-on-linux
title: Installing SpatiaLite on Linux
breadcrumbs: ["SpatiaLite", "Installation"]
references: []
content:
SpatiaLite is packaged for most Linux distributions. 
                 apt install spatialite-bin libsqlite3-mod-spatialite 
                 Depending on your distribution, you should be able to run Datasette something like this: 
                 datasette --load-extension=/usr/lib/x86_64-linux-gnu/mod_spatialite.so 
                 If you are unsure of the location of the module, try running  locate mod_spatialite  and see what comes back.


id: spatialite:spatial-indexing-latitude-longitude-columns
page: spatialite
ref: spatial-indexing-latitude-longitude-columns
title: Spatial indexing latitude/longitude columns
breadcrumbs: ["SpatiaLite"]
references: []
content:
Here's a recipe for taking a table with existing latitude and longitude columns, adding a SpatiaLite POINT geometry column to that table, populating the new column and then populating a spatial index: 
             import sqlite3

conn = sqlite3.connect("museums.db")
# Lead the spatialite extension:
conn.enable_load_extension(True)
conn.load_extension("/usr/local/lib/mod_spatialite.dylib")
# Initialize spatial metadata for this database:
conn.execute("select InitSpatialMetadata(1)")
# Add a geometry column called point_geom to our museums table:
conn.execute(
    "SELECT AddGeometryColumn('museums', 'point_geom', 4326, 'POINT', 2);"
)
# Now update that geometry column with the lat/lon points
conn.execute(
    """
    UPDATE museums SET
    point_geom = GeomFromText('POINT('||"longitude"||' '||"latitude"||')',4326);
"""
)
# Now add a spatial index to that column
conn.execute(
    'select CreateSpatialIndex("museums", "point_geom");'
)
# If you don't commit your changes will not be persisted:
conn.commit()
conn.close()


id: spatialite:making-use-of-a-spatial-index
page: spatialite
ref: making-use-of-a-spatial-index
title: Making use of a spatial index
breadcrumbs: ["SpatiaLite"]
content:
SpatiaLite spatial indexes are R*Trees. They allow you to run efficient bounding box queries using a sub-select, with a similar pattern to that used for  Searches using custom SQL . 
             In the above example, the resulting index will be called  idx_museums_point_geom . This takes the form of a SQLite virtual table. You can inspect its contents using the following query: 
             select * from idx_museums_point_geom limit 10; 
             Here's a live example:  timezones-api.datasette.io/timezones/idx_timezones_Geometry 
             
                 
                     
                     
                     
                     
                     
                     
                         
                             
                                 pkid 
                             
                             
                                 xmin 
                             
                             
                                 xmax 
                             
                             
                                 ymin 
                             
                             
                                 ymax 
                             
                         
                     
                     
                         
                             
                                 1 
                             
                             
                                 -8.601725578308105 
                             
                             
                                 -2.4930307865142822 
                             
                             
                                 4.162120819091797 
                             
                             
                                 10.74019718170166 
                             
                         
                         
                             
                                 2 
                             
                             
                                 -3.2607860565185547 
                             
                             
                                 1.27329421043396 
                             
                             
                                 4.539252281188965 
                             
                             
                                 11.174856185913086 
                             
                         
                         
                             
                                 3 
                             
                             
                                 32.997581481933594 
                             
                             
                                 47.98238754272461 
                             
                             
                                 3.3974475860595703 
                             
                             
                                 14.894054412841797 
                             
                         
                         
                             
                                 4 
                             
                             
                                 -8.66890811920166 
                             
                             
                                 11.997337341308594 
                             
                             
                                 18.9681453704834 
                             
                             
                                 37.296207427978516 
                             
                         
                         
                             
                                 5 
                             
                             
                                 36.43336486816406 
                             
                             
                                 43.300174713134766 
                             
                             
                                 12.354820251464844 
                             
                             
                                 18.070993423461914 
                             
                         
                     
                 
             
             You can now construct efficient bounding box queries that will make use of the index like this: 
             select * from museums where museums.rowid in (
    SELECT pkid FROM idx_museums_point_geom
    -- left-hand-edge of point > left-hand-edge of bbox (minx)
    where xmin > :bbox_minx
    -- right-hand-edge of point < right-hand-edge of bbox (maxx)
    and xmax < :bbox_maxx
    -- bottom-edge of point > bottom-edge of bbox (miny)
    and ymin > :bbox_miny
    -- top-edge of point < top-edge of bbox (maxy)
    and ymax < :bbox_maxy
); 
             Spatial indexes can be created against polygon columns as well as point columns, in which case they will represent the minimum bounding rectangle of that polygon. This is useful for accelerating  within  queries, as seen in the Timezones API example.

references:
[{"href": "https://timezones-api.datasette.io/timezones/idx_timezones_Geometry", "label": "timezones-api.datasette.io/timezones/idx_timezones_Geometry"}]


id: spatialite:importing-shapefiles-into-spatialite
page: spatialite
ref: importing-shapefiles-into-spatialite
title: Importing shapefiles into SpatiaLite
breadcrumbs: ["SpatiaLite"]
content:
The  shapefile format  is a common format for distributing geospatial data. You can use the  spatialite  command-line tool to create a new database table from a shapefile. 
             Try it now with the North America shapefile available from the University of North Carolina  Global River Database  project. Download the file and unzip it (this will create files called  narivs.dbf ,  narivs.prj ,  narivs.shp  and  narivs.shx  in the current directory), then run the following: 
             $ spatialite rivers-database.db
SpatiaLite version ..: 4.3.0a       Supported Extensions:
...
spatialite> .loadshp narivs rivers CP1252 23032
========
Loading shapefile at 'narivs' into SQLite table 'rivers'
...
Inserted 467973 rows into 'rivers' from SHAPEFILE 
             This will load the data from the  narivs  shapefile into a new database table called  rivers . 
             Exit out of  spatialite  (using  Ctrl+D ) and run Datasette against your new database like this: 
             datasette rivers-database.db \
    --load-extension=/usr/local/lib/mod_spatialite.dylib 
             If you browse to  http://localhost:8001/rivers-database/rivers  you will see the new table... but the  Geometry  column will contain unreadable binary data (SpatiaLite uses  a custom format based on WKB ). 
             The easiest way to turn this into semi-readable data is to use the SpatiaLite  AsGeoJSON  function. Try the following using the SQL query interface at  http://localhost:8001/rivers-database : 
             select *, AsGeoJSON(Geometry) from rivers limit 10; 
             This will give you back an additional column of GeoJSON. You can copy and paste GeoJSON from this column into the debugging tool at  geojson.io  to visualize it on a map. 
             To see a more interesting example, try ordering the records with the longest geometry first. Since there are 467,000 rows in the table you will first need to increase the SQL time limit imposed by Datasette: 
             datasette rivers-database.db \
    --load-extension=/usr/local/lib/mod_spatialite.dylib \
    --setting sql_time_limit_ms 10000 
             Now try the following query: 
             select *, AsGeoJSON(Geometry) from rivers
order by length(Geometry) desc limit 10;

references:
[{"href": "https://en.wikipedia.org/wiki/Shapefile", "label": "shapefile format"}, {"href": "http://gaia.geosci.unc.edu/rivers/", "label": "Global River Database"}, {"href": "https://www.gaia-gis.it/gaia-sins/BLOB-Geometry.html", "label": "a custom format based on WKB"}, {"href": "https://geojson.io/", "label": "geojson.io"}]


id: spatialite:importing-geojson-polygons-using-shapely
page: spatialite
ref: importing-geojson-polygons-using-shapely
title: Importing GeoJSON polygons using Shapely
breadcrumbs: ["SpatiaLite"]
content:
Another common form of polygon data is the GeoJSON format. This can be imported into SpatiaLite directly, or by using the  Shapely  Python library. 
             Who's On First  is an excellent source of openly licensed GeoJSON polygons. Let's import the geographical polygon for Wales. First, we can use the Who's On First Spelunker tool to find the record for Wales: 
             spelunker.whosonfirst.org/id/404227475 
             That page includes a link to the GeoJSON record, which can be accessed here: 
             data.whosonfirst.org/404/227/475/404227475.geojson 
             Here's Python code to create a SQLite database, enable SpatiaLite, create a places table and then add a record for Wales: 
             import sqlite3

conn = sqlite3.connect("places.db")
# Enable SpatialLite extension
conn.enable_load_extension(True)
conn.load_extension("/usr/local/lib/mod_spatialite.dylib")
# Create the masic countries table
conn.execute("select InitSpatialMetadata(1)")
conn.execute(
    "create table places (id integer primary key, name text);"
)
# Add a MULTIPOLYGON Geometry column
conn.execute(
    "SELECT AddGeometryColumn('places', 'geom', 4326, 'MULTIPOLYGON', 2);"
)
# Add a spatial index against the new column
conn.execute("SELECT CreateSpatialIndex('places', 'geom');")
# Now populate the table
from shapely.geometry.multipolygon import MultiPolygon
from shapely.geometry import shape
import requests

geojson = requests.get(
    "https://data.whosonfirst.org/404/227/475/404227475.geojson"
).json()
# Convert to "Well Known Text" format
wkt = shape(geojson["geometry"]).wkt
# Insert and commit the record
conn.execute(
    "INSERT INTO places (id, name, geom) VALUES(null, ?, GeomFromText(?, 4326))",
    ("Wales", wkt),
)
conn.commit()

references:
[{"href": "https://pypi.org/project/Shapely/", "label": "Shapely"}, {"href": "https://whosonfirst.org/", "label": "Who's On First"}, {"href": "https://spelunker.whosonfirst.org/id/404227475/", "label": "spelunker.whosonfirst.org/id/404227475"}, {"href": "https://data.whosonfirst.org/404/227/475/404227475.geojson", "label": "data.whosonfirst.org/404/227/475/404227475.geojson"}]


id: spatialite:querying-polygons-using-within
page: spatialite
ref: querying-polygons-using-within
title: Querying polygons using within()
breadcrumbs: ["SpatiaLite"]
references: []
content:
The  within()  SQL function can be used to check if a point is within a geometry: 
             select
    name
from
    places
where
   within(GeomFromText('POINT(-3.1724366 51.4704448)'), places.geom); 
             The  GeomFromText()  function takes a string of well-known text. Note that the order used here is  longitude  then   latitude . 
             To run that same  within()  query in a way that benefits from the spatial index, use the following: 
             select
    name
from
    places
where
    within(GeomFromText('POINT(-3.1724366 51.4704448)'), places.geom)
    and rowid in (
        SELECT pkid FROM idx_places_geom
        where xmin < -3.1724366
        and xmax > -3.1724366
        and ymin < 51.4704448
        and ymax > 51.4704448
    );


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.json
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: 
             {
  "databases": {
    "sf-trees": {
      "tables": {
        "Street_Tree_List": {
          "facets": ["qLegalStatus"]
        }
      }
    }
  }
} 
             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: 
             {
  "facets": [
    {"array": "tags"},
    {"date": "created"}
  ]
} 
             You can change the default facet size (the number of results shown for each facet) for a table using  facet_size : 
             {
  "databases": {
    "sf-trees": {
      "tables": {
        "Street_Tree_List": {
          "facets": ["qLegalStatus"],
          "facet_size": 10
        }
      }
    }
  }
}


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: custom_templates:customization-css-and-javascript
page: custom_templates
ref: customization-css-and-javascript
title: Custom CSS and JavaScript
breadcrumbs: ["Custom pages and templates"]
content:
When you launch Datasette, you can specify a custom metadata file like this: 
             datasette mydb.db --metadata metadata.json 
             Your  metadata.json  file can include links that look like this: 
             {
    "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"
    ]
} 
             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: 
             {
    "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="
        }
    ]
} 
             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: 
             {
    "extra_js_urls": [
        {
            "url": "https://example.datasette.io/module.js",
            "module": true
        }
    ]
} 
             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: 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", "Custom CSS and JavaScript"]
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", "Custom CSS and JavaScript"]
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 -m metadata.json --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  metadata.json  like so: 
                 {
    "extra_css_urls": [
        "/assets/styles.css"
    ],
    "extra_js_urls": [
        "/assets/app.js"
    ]
}


id: custom_templates:publishing-static-assets
page: custom_templates
ref: publishing-static-assets
title: Publishing static assets
breadcrumbs: ["Custom pages and templates", "Custom CSS and JavaScript"]
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"]
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"]
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", "Custom pages"]
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", "Custom pages"]
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", "Custom pages"]
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", "Custom pages"]
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"]
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: index:datasette
page: index
ref: datasette
title: Datasette
breadcrumbs: []
content:
An open source multi-tool for exploring and publishing data 
         Datasette is a tool for exploring and publishing data. It helps people take data of any shape or size and publish that as an interactive, explorable website and accompanying API. 
         Datasette is aimed at data journalists, museum curators, archivists, local governments and anyone else who has data that they wish to share with the world. It is part of a  wider ecosystem of tools and plugins  dedicated to making working with structured data as productive as possible. 
         Explore a demo , watch  a presentation about the project  or  Try Datasette without installing anything using Glitch . 
         Interested in learning Datasette? Start with  the official tutorials . 
         Support questions, feedback? Join our  GitHub Discussions forum .

references:
[{"href": "https://pypi.org/project/datasette/", "label": null}, {"href": "https://docs.datasette.io/en/stable/changelog.html", "label": null}, {"href": "https://pypi.org/project/datasette/", "label": null}, {"href": "https://github.com/simonw/datasette/actions?query=workflow%3ATest", "label": null}, {"href": "https://github.com/simonw/datasette/blob/main/LICENSE", "label": null}, {"href": "https://hub.docker.com/r/datasetteproject/datasette", "label": null}, {"href": "https://discord.gg/ktd74dm5mw", "label": null}, {"href": "https://pypi.org/project/datasette/", "label": null}, {"href": "https://docs.datasette.io/en/stable/changelog.html", "label": null}, {"href": "https://pypi.org/project/datasette/", "label": null}, {"href": "https://github.com/simonw/datasette/actions?query=workflow%3ATest", "label": null}, {"href": "https://github.com/simonw/datasette/blob/main/LICENSE", "label": null}, {"href": "https://hub.docker.com/r/datasetteproject/datasette", "label": null}, {"href": "https://discord.gg/ktd74dm5mw", "label": null}, {"href": "https://fivethirtyeight.datasettes.com/fivethirtyeight", "label": "Explore a demo"}, {"href": "https://static.simonwillison.net/static/2018/pybay-datasette/", "label": "a presentation about the project"}, {"href": "https://datasette.io/tutorials", "label": "the official tutorials"}, {"href": "https://github.com/simonw/datasette/discussions", "label": "GitHub Discussions forum"}]


id: index:contents
page: index
ref: contents
title: Contents
breadcrumbs: ["Datasette"]
references: []
content:
Getting started Play with a live demo Follow a tutorial Datasette in your browser with Datasette Lite Try Datasette without installing anything using Glitch Using Datasette on your own computer Installation Basic installation Datasette Desktop for Mac Using Homebrew Using pip Advanced installation options Using pipx Using Docker A note about extensions The Datasette Ecosystem sqlite-utils Dogsheep CLI reference datasette --help datasette serve datasette --get datasette serve --help-settings datasette plugins datasette install datasette uninstall datasette publish datasette publish cloudrun datasette publish heroku datasette package datasette inspect Pages and API endpoints Top-level index Database Table Row Publishing data datasette publish Publishing to Google Cloud Run Publishing to Heroku Publishing to Vercel Publishing to Fly Custom metadata and plugins datasette package Deploying Datasette Deployment fundamentals Running Datasette using systemd Running Datasette using OpenRC Deploying using buildpacks Running Datasette behind a proxy Nginx proxy configuration Apache proxy configuration JSON API Different shapes Pagination Special JSON arguments Table arguments Column filter arguments Special table arguments Expanding foreign key references Discovering the JSON for a page Running SQL queries Named parameters Views Canned queries Canned query parameters Additional canned query options Writable canned queries Magic parameters JSON API for writable canned queries Pagination Cross-database queries Authentication and permissions Actors Using the "root" actor Permissions Defining permissions with "allow" blocks The /-/allow-debug tool Configuring permissions in metadata.json Controlling access to an instance Controlling access to specific databases Controlling access to specific tables and views Controlling access to specific canned queries Controlling the ability to execute arbitrary SQL Checking permissions in plugins actor_matches_allow() The permissions debug tool The ds_actor cookie Including an expiry time The /-/logout page Built-in permissions view-instance view-database view-database-download view-table view-query execute-sql permissions-debug debug-menu Performance and caching Immutable mode Using "datasette inspect" HTTP caching datasette-hashed-urls CSV export URL parameters Streaming all records Binary data Linking to binary downloads Binary plugins Facets Facets in query strings Facets in metadata.json Suggested facets Speeding up facets with indexes Facet by JSON array Facet by date Full-text search The table page and table view API Advanced SQLite search queries Configuring full-text search for a table or view Searches using custom SQL Enabling full-text search for a SQLite table Configuring FTS using sqlite-utils Configuring FTS using csvs-to-sqlite Configuring FTS by hand FTS versions SpatiaLite Warning Installation Installing SpatiaLite on OS X Installing SpatiaLite on Linux Spatial indexing latitude/longitude columns Making use of a spatial index Importing shapefiles into SpatiaLite Importing GeoJSON polygons using Shapely Querying polygons using within() Metadata Per-database and per-table metadata Source, license and about Column descriptions Specifying units for a column Setting a default sort order Setting a custom page size Setting which columns can be used for sorting Specifying the label column for a table Hiding tables Using YAML for metadata Settings Using --setting Configuration directory mode Settings default_allow_sql default_page_size sql_time_limit_ms max_returned_rows num_sql_threads allow_facet default_facet_size facet_time_limit_ms facet_suggest_time_limit_ms suggest_facets allow_download default_cache_ttl cache_size_kb allow_csv_stream max_csv_mb truncate_cells_html force_https_urls template_debug trace_debug base_url Configuring the secret Using secrets with datasette publish Introspection /-/metadata /-/versions /-/plugins /-/settings /-/databases /-/threads /-/actor /-/messages Custom pages and templates Custom CSS and JavaScript CSS classes on the <body> Serving static files Publishing static assets Custom templates Custom pages Path parameters for pages Custom headers and status codes Returning 404s Custom redirects Custom error pages Plugins Installing plugins One-off plugins using --plugins-dir Deploying plugins using datasette publish Seeing what plugins are installed Plugin configuration Secret configuration values Writing plugins Writing one-off plugins Starting an installable plugin using cookiecutter Packaging a plugin Static assets Custom templates Writing plugins that accept configuration Designing URLs for your plugin Building URLs within plugins Plugin hooks prepare_connection(conn, database, datasette) prepare_jinja2_environment(env, datasette) 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) register_output_renderer(datasette) register_routes(datasette) register_commands(cli) register_facet_classes() asgi_wrapper(datasette) startup(datasette) canned_queries(datasette, database, actor) actor_from_request(datasette, request) 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) menu_links(datasette, actor, request) table_actions(datasette, actor, database, table, request) database_actions(datasette, actor, database, request) skip_csrf(datasette, scope) get_metadata(datasette, key, database, table) Testing plugins Setting up a Datasette test instance Using pdb for errors thrown inside Datasette Using pytest fixtures Testing outbound HTTP calls with pytest-httpx Registering a plugin for the duration of a test Internals for plugins Request object The MultiParams class Response class Returning a response with .asgi_send(send) Setting cookies with response.set_cookie() Datasette class .databases .plugin_config(plugin_name, database=None, table=None) await .render_template(template, context=None, request=None) await .permission_allowed(actor, action, resource=None, default=False) await .ensure_permissions(actor, permissions) await .check_visibility(actor, action=None, resource=None, permissions=None) .get_database(name) .add_database(db, name=None, route=None) .add_memory_database(name) .remove_database(name) .sign(value, namespace="default") .unsign(value, namespace="default") .add_message(request, message, type=datasette.INFO) .absolute_url(request, path) .setting(key) datasette.client datasette.urls Database class Database(ds, path=None, is_mutable=True, is_memory=False, memory_name=None) db.hash await db.execute(sql, ...) Results await db.execute_fn(fn) await db.execute_write(sql, params=None, block=True) await db.execute_write_script(sql, block=True) await db.execute_write_many(sql, params_seq, block=True) await db.execute_write_fn(fn, block=True) db.close() Database introspection CSRF protection The _internal database The datasette.utils module parse_metadata(content) await_me_maybe(value) Tilde encoding datasette.tracer Tracing child tasks Import shortcuts Contributing General guidelines Setting up a development environment Running the tests Using fixtures Debugging Code formatting Running Black blacken-docs Prettier Editing and building the documentation Running Cog Continuously deployed demo instances Release process Alpha and beta releases Releasing bug fixes from a branch Upgrading CodeMirror Changelog 0.65 (2024-10-07) 0.64.8 (2024-06-21) 0.64.7 (2024-06-12) 0.64.6 (2023-12-22) 0.64.5 (2023-10-08) 0.64.4 (2023-09-21) 0.64.3 (2023-04-27) 0.64.2 (2023-03-08) 0.64.1 (2023-01-11) 0.64 (2023-01-09) 0.63.3 (2022-12-17) 0.63.2 (2022-11-18) 0.63.1 (2022-11-10) 0.63 (2022-10-27) Features Plugin hooks and internals Documentation 0.62 (2022-08-14) Features Plugin hooks Bug fixes Documentation 0.61.1 (2022-03-23) 0.61 (2022-03-23) 0.60.2 (2022-02-07) 0.60.1 (2022-01-20) 0.60 (2022-01-13) Plugins and internals Faceting Other small fixes 0.59.4 (2021-11-29) 0.59.3 (2021-11-20) 0.59.2 (2021-11-13) 0.59.1 (2021-10-24) 0.59 (2021-10-14) 0.58.1 (2021-07-16) 0.58 (2021-07-14) 0.57.1 (2021-06-08) 0.57 (2021-06-05) New features Bug fixes and other improvements 0.56.1 (2021-06-05) 0.56 (2021-03-28) 0.55 (2021-02-18) 0.54.1 (2021-02-02) 0.54 (2021-01-25) The _internal database Named in-memory database support JavaScript modules Code formatting with Black and Prettier Other changes 0.53 (2020-12-10) 0.52.5 (2020-12-09) 0.52.4 (2020-12-05) 0.52.3 (2020-12-03) 0.52.2 (2020-12-02) 0.52.1 (2020-11-29) 0.52 (2020-11-28) 0.51.1 (2020-10-31) 0.51 (2020-10-31) New visual design Plugins can now add links within Datasette Binary data URL building Running Datasette behind a proxy Smaller changes 0.50.2 (2020-10-09) 0.50.1 (2020-10-09) 0.50 (2020-10-09) 0.49.1 (2020-09-15) 0.49 (2020-09-14) 0.48 (2020-08-16) 0.47.3 (2020-08-15) 0.47.2 (2020-08-12) 0.47.1 (2020-08-11) 0.47 (2020-08-11) 0.46 (2020-08-09) 0.45 (2020-07-01) Magic parameters for canned queries Log out Better plugin documentation New plugin hooks Smaller changes 0.44 (2020-06-11) Authentication Permissions Writable canned queries Flash messages Signed values and secrets CSRF protection Cookie methods register_routes() plugin hooks Smaller changes The road to Datasette 1.0 0.43 (2020-05-28) 0.42 (2020-05-08) 0.41 (2020-05-06) 0.40 (2020-04-21) 0.39 (2020-03-24) 0.38 (2020-03-08) 0.37.1 (2020-03-02) 0.37 (2020-02-25) 0.36 (2020-02-21) 0.35 (2020-02-04) 0.34 (2020-01-29) 0.33 (2019-12-22) 0.32 (2019-11-14) 0.31.2 (2019-11-13) 0.31.1 (2019-11-12) 0.31 (2019-11-11) 0.30.2 (2019-11-02) 0.30.1 (2019-10-30) 0.30 (2019-10-18) 0.29.3 (2019-09-02) 0.29.2 (2019-07-13) 0.29.1 (2019-07-11) 0.29 (2019-07-07) ASGI New plugin hook: asgi_wrapper New plugin hook: extra_template_vars Secret plugin configuration options Facet by date Easier custom templates for table rows ?_through= for joins through many-to-many tables Small changes 0.28 (2019-05-19) Supporting databases that change Faceting improvements, and faceting plugins datasette publish cloudrun register_output_renderer plugins Medium changes Small changes 0.27.1 (2019-05-09) 0.27 (2019-01-31) 0.26.1 (2019-01-10) 0.26 (2019-01-02) 0.25.2 (2018-12-16) 0.25.1 (2018-11-04) 0.25 (2018-09-19) 0.24 (2018-07-23) 0.23.2 (2018-07-07) 0.23.1 (2018-06-21) 0.23 (2018-06-18) CSV export Foreign key expansions New configuration settings Control HTTP caching with ?_ttl= Improved support for SpatiaLite latest.datasette.io Miscellaneous 0.22.1 (2018-05-23) 0.22 (2018-05-20) 0.21 (2018-05-05) 0.20 (2018-04-20) 0.19 (2018-04-16) 0.18 (2018-04-14) 0.17 (2018-04-13) 0.16 (2018-04-13) 0.15 (2018-04-09) 0.14 (2017-12-09) 0.13 (2017-11-24) 0.12 (2017-11-16) 0.11 (2017-11-14) 0.10 (2017-11-14) 0.9 (2017-11-13) 0.8 (2017-11-13)


id: authentication:authentication
page: authentication
ref: authentication
title: Authentication and permissions
breadcrumbs: []
references: []
content:
Datasette does not require authentication by default. Any visitor to a Datasette instance can explore the full data and execute read-only SQL queries. 
         Datasette's plugin system can be used to add many different styles of authentication, such as user accounts, single sign-on or API keys.


id: authentication:authentication-actor
page: authentication
ref: authentication-actor
title: Actors
breadcrumbs: ["Authentication and permissions"]
references: []
content:
Through plugins, Datasette can support both authenticated users (with cookies) and authenticated API agents (via authentication tokens). The word "actor" is used to cover both of these cases. 
             Every request to Datasette has an associated actor value, available in the code as  request.actor . This can be  None  for unauthenticated requests, or a JSON compatible Python dictionary for authenticated users or API agents. 
             The actor dictionary can be any shape - the design of that data structure is left up to the plugins. A useful convention is to include an  "id"  string, as demonstrated by the "root" actor below. 
             Plugins can use the  actor_from_request(datasette, request)  hook to implement custom logic for authenticating an actor based on the incoming HTTP request.


id: authentication:authentication-root
page: authentication
ref: authentication-root
title: Using the "root" actor
breadcrumbs: ["Authentication and permissions", "Actors"]
references: [{"href": "https://github.com/simonw/datasette-auth-github", "label": "datasette-auth-github"}]
content:
Datasette currently leaves almost all forms of authentication to plugins -  datasette-auth-github  for example. 
                 The one exception is the "root" account, which you can sign into while using Datasette on your local machine. This provides access to a small number of debugging features. 
                 To sign in as root, start Datasette using the  --root  command-line option, like this: 
                 $ datasette --root
http://127.0.0.1:8001/-/auth-token?token=786fc524e0199d70dc9a581d851f466244e114ca92f33aa3b42a139e9388daa7
INFO:     Started server process [25801]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8001 (Press CTRL+C to quit) 
                 The URL on the first line includes a one-use token which can be used to sign in as the "root" actor in your browser. Click on that link and then visit  http://127.0.0.1:8001/-/actor  to confirm that you are authenticated as an actor that looks like this: 
                 {
    "id": "root"
}


id: authentication:authentication-permissions
page: authentication
ref: authentication-permissions
title: Permissions
breadcrumbs: ["Authentication and permissions"]
references: [{"href": "https://github.com/simonw/datasette-upload-csvs", "label": "datasette-upload-csvs"}]
content:
Datasette has an extensive permissions system built-in, which can be further extended and customized by plugins. 
             The key question the permissions system answers is this: 
             
                 Is this  actor  allowed to perform this  action , optionally against this particular  resource ? 
             
             Actors  are  described above . 
             An  action  is a string describing the action the actor would like to perform. A full list is  provided below  - examples include  view-table  and  execute-sql . 
             A  resource  is the item the actor wishes to interact with - for example a specific database or table. Some actions, such as  permissions-debug , are not associated with a particular resource. 
             Datasette's built-in view permissions ( view-database ,  view-table  etc) default to  allow  - unless you  configure additional permission rules  unauthenticated users will be allowed to access content. 
             Permissions with potentially harmful effects should default to  deny . Plugin authors should account for this when designing new plugins - for example, the  datasette-upload-csvs  plugin defaults to deny so that installations don't accidentally allow unauthenticated users to create new tables by uploading a CSV file.


id: authentication:authentication-permissions-allow
page: authentication
ref: authentication-permissions-allow
title: Defining permissions with "allow" blocks
breadcrumbs: ["Authentication and permissions", "Permissions"]
content:
The standard way to define permissions in Datasette is to use an  "allow"  block. This is a JSON document describing which actors are allowed to perform a permission. 
                 The most basic form of allow block is this ( allow demo ,  deny demo ): 
                 {
    "allow": {
        "id": "root"
    }
} 
                 This will match any actors with an  "id"  property of  "root"  - for example, an actor that looks like this: 
                 {
    "id": "root",
    "name": "Root User"
} 
                 An allow block can specify "deny all" using  false  ( demo ): 
                 {
    "allow": false
} 
                 An  "allow"  of  true  allows all access ( demo ): 
                 {
    "allow": true
} 
                 Allow keys can provide a list of values. These will match any actor that has any of those values ( allow demo ,  deny demo ): 
                 {
    "allow": {
        "id": ["simon", "cleopaws"]
    }
} 
                 This will match any actor with an  "id"  of either  "simon"  or  "cleopaws" . 
                 Actors can have properties that feature a list of values. These will be matched against the list of values in an allow block. Consider the following actor: 
                 {
    "id": "simon",
    "roles": ["staff", "developer"]
} 
                 This allow block will provide access to any actor that has  "developer"  as one of their roles ( allow demo ,  deny demo ): 
                 {
    "allow": {
        "roles": ["developer"]
    }
} 
                 Note that "roles" is not a concept that is baked into Datasette - it's a convention that plugins can choose to implement and act on. 
                 If you want to provide access to any actor with a value for a specific key, use  "*" . For example, to match any logged-in user specify the following ( allow demo ,  deny demo ): 
                 {
    "allow": {
        "id": "*"
    }
} 
                 You can specify that only unauthenticated actors (from anynomous HTTP requests) should be allowed access using the special  "unauthenticated": true  key in an allow block ( allow demo ,  deny demo ): 
                 {
    "allow": {
        "unauthenticated": true
    }
} 
                 Allow keys act as an "or" mechanism. An actor will be able to execute the query if any of their JSON properties match any of the values in the corresponding lists in the  allow  block. The following block will allow users with either a  role  of  "ops"  OR users who have an  id  of  "simon"  or  "cleopaws" : 
                 {
    "allow": {
        "id": ["simon", "cleopaws"],
        "role": "ops"
    }
} 
                 Demo for cleopaws ,  demo for ops role ,  demo for an actor matching neither rule .

references:
[{"href": "https://latest.datasette.io/-/allow-debug?actor=%7B%22id%22%3A+%22root%22%7D&allow=%7B%0D%0A++++++++%22id%22%3A+%22root%22%0D%0A++++%7D", "label": "allow demo"}, {"href": "https://latest.datasette.io/-/allow-debug?actor=%7B%22id%22%3A+%22trevor%22%7D&allow=%7B%0D%0A++++++++%22id%22%3A+%22root%22%0D%0A++++%7D", "label": "deny demo"}, {"href": "https://latest.datasette.io/-/allow-debug?actor=%7B%0D%0A++++%22id%22%3A+%22root%22%0D%0A%7D&allow=false", "label": "demo"}, {"href": "https://latest.datasette.io/-/allow-debug?actor=%7B%0D%0A++++%22id%22%3A+%22root%22%0D%0A%7D&allow=true", "label": "demo"}, {"href": "https://latest.datasette.io/-/allow-debug?actor=%7B%0D%0A++++%22id%22%3A+%22cleopaws%22%0D%0A%7D&allow=%7B%0D%0A++++%22id%22%3A+%5B%0D%0A++++++++%22simon%22%2C%0D%0A++++++++%22cleopaws%22%0D%0A++++%5D%0D%0A%7D", "label": "allow demo"}, {"href": "https://latest.datasette.io/-/allow-debug?actor=%7B%0D%0A++++%22id%22%3A+%22pancakes%22%0D%0A%7D&allow=%7B%0D%0A++++%22id%22%3A+%5B%0D%0A++++++++%22simon%22%2C%0D%0A++++++++%22cleopaws%22%0D%0A++++%5D%0D%0A%7D", "label": "deny demo"}, {"href": "https://latest.datasette.io/-/allow-debug?actor=%7B%0D%0A++++%22id%22%3A+%22simon%22%2C%0D%0A++++%22roles%22%3A+%5B%0D%0A++++++++%22staff%22%2C%0D%0A++++++++%22developer%22%0D%0A++++%5D%0D%0A%7D&allow=%7B%0D%0A++++%22roles%22%3A+%5B%0D%0A++++++++%22developer%22%0D%0A++++%5D%0D%0A%7D", "label": "allow demo"}, {"href": "https://latest.datasette.io/-/allow-debug?actor=%7B%0D%0A++++%22id%22%3A+%22cleopaws%22%2C%0D%0A++++%22roles%22%3A+%5B%22dog%22%5D%0D%0A%7D&allow=%7B%0D%0A++++%22roles%22%3A+%5B%0D%0A++++++++%22developer%22%0D%0A++++%5D%0D%0A%7D", "label": "deny demo"}, {"href": "https://latest.datasette.io/-/allow-debug?actor=%7B%0D%0A++++%22id%22%3A+%22simon%22%0D%0A%7D&allow=%7B%0D%0A++++%22id%22%3A+%22*%22%0D%0A%7D", "label": "allow demo"}, {"href": "https://latest.datasette.io/-/allow-debug?actor=%7B%0D%0A++++%22bot%22%3A+%22readme-bot%22%0D%0A%7D&allow=%7B%0D%0A++++%22id%22%3A+%22*%22%0D%0A%7D", "label": "deny demo"}, {"href": "https://latest.datasette.io/-/allow-debug?actor=null&allow=%7B%0D%0A++++%22unauthenticated%22%3A+true%0D%0A%7D", "label": "allow demo"}, {"href": "https://latest.datasette.io/-/allow-debug?actor=%7B%0D%0A++++%22id%22%3A+%22hello%22%0D%0A%7D&allow=%7B%0D%0A++++%22unauthenticated%22%3A+true%0D%0A%7D", "label": "deny demo"}, {"href": "https://latest.datasette.io/-/allow-debug?actor=%7B%0D%0A++++%22id%22%3A+%22cleopaws%22%0D%0A%7D&allow=%7B%0D%0A++++%22id%22%3A+%5B%0D%0A++++++++%22simon%22%2C%0D%0A++++++++%22cleopaws%22%0D%0A++++%5D%2C%0D%0A++++%22role%22%3A+%22ops%22%0D%0A%7D", "label": "Demo for cleopaws"}, {"href": "https://latest.datasette.io/-/allow-debug?actor=%7B%0D%0A++++%22id%22%3A+%22trevor%22%2C%0D%0A++++%22role%22%3A+%5B%0D%0A++++++++%22ops%22%2C%0D%0A++++++++%22staff%22%0D%0A++++%5D%0D%0A%7D&allow=%7B%0D%0A++++%22id%22%3A+%5B%0D%0A++++++++%22simon%22%2C%0D%0A++++++++%22cleopaws%22%0D%0A++++%5D%2C%0D%0A++++%22role%22%3A+%22ops%22%0D%0A%7D", "label": "demo for ops role"}, {"href": "https://latest.datasette.io/-/allow-debug?actor=%7B%0D%0A++++%22id%22%3A+%22percy%22%2C%0D%0A++++%22role%22%3A+%5B%0D%0A++++++++%22staff%22%0D%0A++++%5D%0D%0A%7D&allow=%7B%0D%0A++++%22id%22%3A+%5B%0D%0A++++++++%22simon%22%2C%0D%0A++++++++%22cleopaws%22%0D%0A++++%5D%2C%0D%0A++++%22role%22%3A+%22ops%22%0D%0A%7D", "label": "demo for an actor matching neither rule"}]


id: authentication:allowdebugview
page: authentication
ref: allowdebugview
title: The /-/allow-debug tool
breadcrumbs: ["Authentication and permissions", "Permissions"]
content:
The  /-/allow-debug  tool lets you try out different   "action"  blocks against different  "actor"  JSON objects. You can try that out here:  https://latest.datasette.io/-/allow-debug

references:
[{"href": "https://latest.datasette.io/-/allow-debug", "label": "https://latest.datasette.io/-/allow-debug"}]


id: authentication:authentication-permissions-metadata
page: authentication
ref: authentication-permissions-metadata
title: Configuring permissions in metadata.json
breadcrumbs: ["Authentication and permissions"]
references: []
content:
You can limit who is allowed to view different parts of your Datasette instance using  "allow"  keys in your  Metadata  configuration. 
             You can control the following: 
             
                 
                     Access to the entire Datasette instance 
                 
                 
                     Access to specific databases 
                 
                 
                     Access to specific tables and views 
                 
                 
                     Access to specific  Canned queries 
                 
             
             If a user cannot access a specific database, they will not be able to access tables, views or queries within that database. If a user cannot access the instance they will not be able to access any of the databases, tables, views or queries.


id: authentication:authentication-permissions-instance
page: authentication
ref: authentication-permissions-instance
title: Controlling access to an instance
breadcrumbs: ["Authentication and permissions", "Configuring permissions in metadata.json"]
content:
Here's how to restrict access to your entire Datasette instance to just the  "id": "root"  user: 
                 {
    "title": "My private Datasette instance",
    "allow": {
        "id": "root"
    }
} 
                 To deny access to all users, you can use  "allow": false : 
                 {
    "title": "My entirely inaccessible instance",
    "allow": false
} 
                 One reason to do this is if you are using a Datasette plugin - such as  datasette-permissions-sql  - to control permissions instead.

references:
[{"href": "https://github.com/simonw/datasette-permissions-sql", "label": "datasette-permissions-sql"}]


id: authentication:authentication-permissions-database
page: authentication
ref: authentication-permissions-database
title: Controlling access to specific databases
breadcrumbs: ["Authentication and permissions", "Configuring permissions in metadata.json"]
references: []
content:
To limit access to a specific  private.db  database to just authenticated users, use the  "allow"  block like this: 
                 {
    "databases": {
        "private": {
            "allow": {
                "id": "*"
            }
        }
    }
}


id: authentication:authentication-permissions-table
page: authentication
ref: authentication-permissions-table
title: Controlling access to specific tables and views
breadcrumbs: ["Authentication and permissions", "Configuring permissions in metadata.json"]
content:
To limit access to the  users  table in your  bakery.db  database: 
                 {
    "databases": {
        "bakery": {
            "tables": {
                "users": {
                    "allow": {
                        "id": "*"
                    }
                }
            }
        }
    }
} 
                 This works for SQL views as well - you can list their names in the  "tables"  block above in the same way as regular tables. 
                 
                     Restricting access to tables and views in this way will NOT prevent users from querying them using arbitrary SQL queries,  like this  for example. 
                     If you are restricting access to specific tables you should also use the  "allow_sql"  block to prevent users from bypassing the limit with their own SQL queries - see  Controlling the ability to execute arbitrary SQL .

references:
[{"href": "https://latest.datasette.io/fixtures?sql=select+*+from+facetable", "label": "like this"}]


id: authentication:authentication-permissions-query
page: authentication
ref: authentication-permissions-query
title: Controlling access to specific canned queries
breadcrumbs: ["Authentication and permissions", "Configuring permissions in metadata.json"]
references: []
content:
Canned queries  allow you to configure named SQL queries in your  metadata.json  that can be executed by users. These queries can be set up to both read and write to the database, so controlling who can execute them can be important. 
                 To limit access to the  add_name  canned query in your  dogs.db  database to just the  root user : 
                 {
    "databases": {
        "dogs": {
            "queries": {
                "add_name": {
                    "sql": "INSERT INTO names (name) VALUES (:name)",
                    "write": true,
                    "allow": {
                        "id": ["root"]
                    }
                }
            }
        }
    }
}


id: authentication:authentication-permissions-execute-sql
page: authentication
ref: authentication-permissions-execute-sql
title: Controlling the ability to execute arbitrary SQL
breadcrumbs: ["Authentication and permissions", "Configuring permissions in metadata.json"]
content:
Datasette defaults to allowing any site visitor to execute their own custom SQL queries, for example using the form on  the database page  or by appending a  ?_where=  parameter to the table page  like this . 
                 Access to this ability is controlled by the  execute-sql  permission. 
                 The easiest way to disable arbitrary SQL queries is using the  default_allow_sql setting  when you first start Datasette running. 
                 You can alternatively use an  "allow_sql"  block to control who is allowed to execute arbitrary SQL queries. 
                 To prevent any user from executing arbitrary SQL queries, use this: 
                 {
    "allow_sql": false
} 
                 To enable just the  root user  to execute SQL for all databases in your instance, use the following: 
                 {
    "allow_sql": {
        "id": "root"
    }
} 
                 To limit this ability for just one specific database, use this: 
                 {
    "databases": {
        "mydatabase": {
            "allow_sql": {
                "id": "root"
            }
        }
    }
}

references:
[{"href": "https://latest.datasette.io/fixtures", "label": "the database page"}, {"href": "https://latest.datasette.io/fixtures/facetable?_where=_city_id=1", "label": "like this"}]


id: authentication:permissions-plugins
page: authentication
ref: permissions-plugins
title: Checking permissions in plugins
breadcrumbs: ["Authentication and permissions"]
references: []
content:
Datasette plugins can check if an actor has permission to perform an action using the  datasette.permission_allowed(...)  method. 
             Datasette core performs a number of permission checks,  documented below . Plugins can implement the  permission_allowed(datasette, actor, action, resource)  plugin hook to participate in decisions about whether an actor should be able to perform a specified action.


id: authentication:authentication-actor-matches-allow
page: authentication
ref: authentication-actor-matches-allow
title: actor_matches_allow()
breadcrumbs: ["Authentication and permissions"]
references: []
content:
Plugins that wish to implement this same  "allow"  block permissions scheme can take advantage of the  datasette.utils.actor_matches_allow(actor, allow)  function: 
             from datasette.utils import actor_matches_allow

actor_matches_allow({"id": "root"}, {"id": "*"})
# returns True 
             The currently authenticated actor is made available to plugins as  request.actor .


id: authentication:permissionsdebugview
page: authentication
ref: permissionsdebugview
title: The permissions debug tool
breadcrumbs: ["Authentication and permissions"]
references: []
content:
The debug tool at  /-/permissions  is only available to the  authenticated root user  (or any actor granted the  permissions-debug  action according to a plugin). 
             It shows the thirty most recent permission checks that have been carried out by the Datasette instance. 
             This is designed to help administrators and plugin authors understand exactly how permission checks are being carried out, in order to effectively configure Datasette's permission system.


id: authentication:authentication-ds-actor
page: authentication
ref: authentication-ds-actor
title: The ds_actor cookie
breadcrumbs: ["Authentication and permissions"]
references: []
content:
Datasette includes a default authentication plugin which looks for a signed  ds_actor  cookie containing a JSON actor dictionary. This is how the  root actor  mechanism works. 
             Authentication plugins can set signed  ds_actor  cookies themselves like so: 
             response = Response.redirect("/")
response.set_cookie(
    "ds_actor",
    datasette.sign({"a": {"id": "cleopaws"}}, "actor"),
) 
             Note that you need to pass  "actor"  as the namespace to  .sign(value, namespace="default") . 
             The shape of data encoded in the cookie is as follows: 
             {
    "a": {... actor ...}
}


id: authentication:authentication-ds-actor-expiry
page: authentication
ref: authentication-ds-actor-expiry
title: Including an expiry time
breadcrumbs: ["Authentication and permissions", "The ds_actor cookie"]
references: []
content:
ds_actor  cookies can optionally include a signed expiry timestamp, after which the cookies will no longer be valid. Authentication plugins may chose to use this mechanism to limit the lifetime of the cookie. For example, if a plugin implements single-sign-on against another source it may decide to set short-lived cookies so that if the user is removed from the SSO system their existing Datasette cookies will stop working shortly afterwards. 
                 To include an expiry, add a  "e"  key to the cookie value containing a base62-encoded integer representing the timestamp when the cookie should expire. For example, here's how to set a cookie that expires after 24 hours: 
                 import time
from datasette.utils import baseconv

expires_at = int(time.time()) + (24 * 60 * 60)

response = Response.redirect("/")
response.set_cookie(
    "ds_actor",
    datasette.sign(
        {
            "a": {"id": "cleopaws"},
            "e": baseconv.base62.encode(expires_at),
        },
        "actor",
    ),
) 
                 The resulting cookie will encode data that looks something like this: 
                 {
    "a": {
        "id": "cleopaws"
    },
    "e": "1jjSji"
}


id: authentication:logoutview
page: authentication
ref: logoutview
title: The /-/logout page
breadcrumbs: ["Authentication and permissions", "The ds_actor cookie"]
references: []
content:
The page at  /-/logout  provides the ability to log out of a  ds_actor  cookie authentication session.


id: authentication:id1
page: authentication
ref: id1
title: Built-in permissions
breadcrumbs: ["Authentication and permissions"]
references: []
content:
This section lists all of the permission checks that are carried out by Datasette core, along with the  resource  if it was passed.


id: authentication:permissions-view-instance
page: authentication
ref: permissions-view-instance
title: view-instance
breadcrumbs: ["Authentication and permissions", "Built-in permissions"]
references: [{"href": "https://latest.datasette.io/", "label": "https://latest.datasette.io/"}]
content:
Top level permission - Actor is allowed to view any pages within this instance, starting at  https://latest.datasette.io/ 
                 Default  allow .


id: authentication:permissions-view-database
page: authentication
ref: permissions-view-database
title: view-database
breadcrumbs: ["Authentication and permissions", "Built-in permissions"]
references: [{"href": "https://latest.datasette.io/fixtures", "label": "https://latest.datasette.io/fixtures"}]
content:
Actor is allowed to view a database page, e.g.  https://latest.datasette.io/fixtures 
                 
                     
                         resource  - string 
                         
                             The name of the database 
                         
                     
                 
                 Default  allow .


id: authentication:permissions-view-database-download
page: authentication
ref: permissions-view-database-download
title: view-database-download
breadcrumbs: ["Authentication and permissions", "Built-in permissions"]
content:
Actor is allowed to download a database, e.g.  https://latest.datasette.io/fixtures.db 
                 
                     
                         resource  - string 
                         
                             The name of the database 
                         
                     
                 
                 Default  allow .

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


id: authentication:permissions-view-table
page: authentication
ref: permissions-view-table
title: view-table
breadcrumbs: ["Authentication and permissions", "Built-in permissions"]
content:
Actor is allowed to view a table (or view) page, e.g.  https://latest.datasette.io/fixtures/complex_foreign_keys 
                 
                     
                         resource  - tuple: (string, string) 
                         
                             The name of the database, then the name of the table 
                         
                     
                 
                 Default  allow .

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


id: authentication:permissions-view-query
page: authentication
ref: permissions-view-query
title: view-query
breadcrumbs: ["Authentication and permissions", "Built-in permissions"]
content:
Actor is allowed to view (and execute) a  canned query  page, e.g.  https://latest.datasette.io/fixtures/pragma_cache_size  - this includes executing  Writable canned queries . 
                 
                     
                         resource  - tuple: (string, string) 
                         
                             The name of the database, then the name of the canned query 
                         
                     
                 
                 Default  allow .

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


id: authentication:permissions-execute-sql
page: authentication
ref: permissions-execute-sql
title: execute-sql
breadcrumbs: ["Authentication and permissions", "Built-in permissions"]
content:
Actor is allowed to run arbitrary SQL queries against a specific database, e.g.  https://latest.datasette.io/fixtures?sql=select+100 
                 
                     
                         resource  - string 
                         
                             The name of the database 
                         
                     
                 
                 Default  allow . See also  the default_allow_sql setting .

references:
[{"href": "https://latest.datasette.io/fixtures?sql=select+100", "label": "https://latest.datasette.io/fixtures?sql=select+100"}]


id: authentication:permissions-debug-menu
page: authentication
ref: permissions-debug-menu
title: debug-menu
breadcrumbs: ["Authentication and permissions", "Built-in permissions"]
references: []
content:
Controls if the various debug pages are displayed in the navigation menu. 
                 Default  deny .


id: getting_started:getting-started-demo
page: getting_started
ref: getting-started-demo
title: Play with a live demo
breadcrumbs: ["Getting started"]
content:
The best way to experience Datasette for the first time is with a demo: 
             
                 
                     global-power-plants.datasettes.com  provides a searchable database of power plants around the world, using data from the  World Resources Institude  rendered using the  datasette-cluster-map  plugin. 
                 
                 
                     fivethirtyeight.datasettes.com  shows Datasette running against over 400 datasets imported from the  FiveThirtyEight GitHub repository .

references:
[{"href": "https://global-power-plants.datasettes.com/global-power-plants/global-power-plants", "label": "global-power-plants.datasettes.com"}, {"href": "https://www.wri.org/publication/global-power-plant-database", "label": "World Resources Institude"}, {"href": "https://github.com/simonw/datasette-cluster-map", "label": "datasette-cluster-map"}, {"href": "https://fivethirtyeight.datasettes.com/fivethirtyeight", "label": "fivethirtyeight.datasettes.com"}, {"href": "https://github.com/fivethirtyeight/data", "label": "FiveThirtyEight GitHub repository"}]


id: getting_started:getting-started-tutorial
page: getting_started
ref: getting-started-tutorial
title: Follow a tutorial
breadcrumbs: ["Getting started"]
content:
Datasette has several  tutorials  to help you get started with the tool. Try one of the following: 
             
                 
                     Exploring a database with Datasette  shows how to use the Datasette web interface to explore a new database. 
                 
                 
                     Learn SQL with Datasette  introduces SQL, and shows how to use that query language to ask questions of your data. 
                 
                 
                     Cleaning data with sqlite-utils and Datasette  guides you through using  sqlite-utils  to turn a CSV file into a database that you can explore using Datasette.

references:
[{"href": "https://datasette.io/tutorials", "label": "tutorials"}, {"href": "https://datasette.io/tutorials/explore", "label": "Exploring a database with Datasette"}, {"href": "https://datasette.io/tutorials/learn-sql", "label": "Learn SQL with Datasette"}, {"href": "https://datasette.io/tutorials/clean-data", "label": "Cleaning data with sqlite-utils and Datasette"}, {"href": "https://sqlite-utils.datasette.io/", "label": "sqlite-utils"}]


id: getting_started:getting-started-datasette-lite
page: getting_started
ref: getting-started-datasette-lite
title: Datasette in your browser with Datasette Lite
breadcrumbs: ["Getting started"]
content:
Datasette Lite  is Datasette packaged using WebAssembly so that it runs entirely in your browser, no Python web application server required. 
             You can pass a URL to a CSV, SQLite or raw SQL file directly to Datasette Lite to explore that data in your browser. 
             This  example link  opens Datasette Lite and loads the SQL Murder Mystery example database from  Northwestern University Knight Lab .

references:
[{"href": "https://lite.datasette.io/", "label": "Datasette Lite"}, {"href": "https://lite.datasette.io/?url=https%3A%2F%2Fraw.githubusercontent.com%2FNUKnightLab%2Fsql-mysteries%2Fmaster%2Fsql-murder-mystery.db#/sql-murder-mystery", "label": "example link"}, {"href": "https://github.com/NUKnightLab/sql-mysteries", "label": "Northwestern University Knight Lab"}]


id: getting_started:getting-started-glitch
page: getting_started
ref: getting-started-glitch
title: Try Datasette without installing anything using Glitch
breadcrumbs: ["Getting started"]
content:
Glitch  is a free online tool for building web apps directly from your web browser. You can use Glitch to try out Datasette without needing to install any software on your own computer. 
             Here's a demo project on Glitch which you can use as the basis for your own experiments: 
             glitch.com/~datasette-csvs 
             Glitch allows you to "remix" any project to create your own copy and start editing it in your browser. You can remix the  datasette-csvs  project by clicking this button: 
             
             Find a CSV file and drag it onto the Glitch file explorer panel -  datasette-csvs  will automatically convert it to a SQLite database (using  sqlite-utils ) and allow you to start exploring it using Datasette. 
             If your CSV file has a  latitude  and  longitude  column you can visualize it on a map by uncommenting the  datasette-cluster-map  line in the  requirements.txt  file using the Glitch file editor. 
             Need some data? Try this  Public Art Data  for the city of Seattle - hit "Export" and select "CSV" to download it as a CSV file. 
             For more on how this works, see  Running Datasette on Glitch .

references:
[{"href": "https://glitch.com/", "label": "Glitch"}, {"href": "https://glitch.com/~datasette-csvs", "label": "glitch.com/~datasette-csvs"}, {"href": "https://glitch.com/edit/#!/remix/datasette-csvs", "label": null}, {"href": "https://github.com/simonw/sqlite-utils", "label": "sqlite-utils"}, {"href": "https://data.seattle.gov/Community/Public-Art-Data/j7sn-tdzk", "label": "Public Art Data"}, {"href": "https://simonwillison.net/2019/Apr/23/datasette-glitch/", "label": "Running Datasette on Glitch"}]


id: getting_started:getting-started-your-computer
page: getting_started
ref: getting-started-your-computer
title: Using Datasette on your own computer
breadcrumbs: ["Getting started"]
content:
First, follow the  Installation  instructions. Now you can run Datasette against a SQLite file on your computer using the following command: 
             datasette path/to/database.db 
             This will start a web server on port 8001 - visit  http://localhost:8001/ 
                to access the web interface. 
             Add  -o  to open your browser automatically once Datasette has started: 
             datasette path/to/database.db -o 
             Use Chrome on OS X? You can run datasette against your browser history
                like so: 
             datasette ~/Library/Application\ Support/Google/Chrome/Default/History --nolock 
             The  --nolock  option ignores any file locks. This is safe as Datasette will open the file in read-only mode. 
             Now visiting  http://localhost:8001/History/downloads  will show you a web
                interface to browse your downloads data: 
             
                 
             
             http://localhost:8001/History/downloads.json  will return that data as
                JSON: 
             {
    "database": "History",
    "columns": [
        "id",
        "current_path",
        "target_path",
        "start_time",
        "received_bytes",
        "total_bytes",
        ...
    ],
    "rows": [
        [
            1,
            "/Users/simonw/Downloads/DropboxInstaller.dmg",
            "/Users/simonw/Downloads/DropboxInstaller.dmg",
            13097290269022132,
            626688,
            0,
            ...
        ]
    ]
} 
             http://localhost:8001/History/downloads.json?_shape=objects  will return that data as
                JSON in a more convenient format: 
             {
    ...
    "rows": [
        {
            "start_time": 13097290269022132,
            "interrupt_reason": 0,
            "hash": "",
            "id": 1,
            "site_url": "",
            "referrer": "https://www.dropbox.com/downloading?src=index",
            ...
        }
    ]
}

references:
[{"href": "http://localhost:8001/", "label": "http://localhost:8001/"}, {"href": "http://localhost:8001/History/downloads", "label": "http://localhost:8001/History/downloads"}, {"href": "http://localhost:8001/History/downloads.json", "label": "http://localhost:8001/History/downloads.json"}, {"href": "http://localhost:8001/History/downloads.json?_shape=objects", "label": "http://localhost:8001/History/downloads.json?_shape=objects"}]


id: publish:publishing
page: publish
ref: publishing
title: Publishing data
breadcrumbs: []
references: []
content:
Datasette includes tools for publishing and deploying your data to the internet. The  datasette publish  command will deploy a new Datasette instance containing your databases directly to a Heroku or Google Cloud hosting account. You can also use  datasette package  to create a Docker image that bundles your databases together with the datasette application that is used to serve them.


id: publish:cli-publish
page: publish
ref: cli-publish
title: datasette publish
breadcrumbs: ["Publishing data"]
content:
Once you have created a SQLite database (e.g. using  csvs-to-sqlite ) you can deploy it to a hosting account using a single command. 
             You will need a hosting account with  Heroku  or  Google Cloud . Once you have created your account you will need to install and configure the  heroku  or  gcloud  command-line tools.

references:
[{"href": "https://github.com/simonw/csvs-to-sqlite/", "label": "csvs-to-sqlite"}, {"href": "https://www.heroku.com/", "label": "Heroku"}, {"href": "https://cloud.google.com/", "label": "Google Cloud"}]


id: publish:publish-cloud-run
page: publish
ref: publish-cloud-run
title: Publishing to Google Cloud Run
breadcrumbs: ["Publishing data", "datasette publish"]
content:
Google Cloud Run  allows you to publish data in a scale-to-zero environment, so your application will start running when the first request is received and will shut down again when traffic ceases. This means you only pay for time spent serving traffic. 
                 
                     Cloud Run is a great option for inexpensively hosting small, low traffic projects - but costs can add up for projects that serve a lot of requests. 
                     Be particularly careful if your project has tables with large numbers of rows. Search engine crawlers that index a page for every row could result in a high bill. 
                     The  datasette-block-robots  plugin can be used to request search engine crawlers omit crawling your site, which can help avoid this issue. 
                 
                 You will first need to install and configure the Google Cloud CLI tools by following  these instructions . 
                 You can then publish one or more SQLite database files to Google Cloud Run using the following command: 
                 datasette publish cloudrun mydatabase.db --service=my-database 
                 A Cloud Run  service  is a single hosted application. The service name you specify will be used as part of the Cloud Run URL. If you deploy to a service name that you have used in the past your new deployment will replace the previous one. 
                 If you omit the  --service  option you will be asked to pick a service name interactively during the deploy. 
                 You may need to interact with prompts from the tool. Many of the prompts ask for values that can be  set as properties for the Google Cloud SDK  if you want to avoid the prompts. 
                 For example, the default region for the deployed instance can be set using the command: 
                 gcloud config set run/region us-central1 
                 You should replace  us-central1  with your desired  region . Alternately, you can specify the region by setting the  CLOUDSDK_RUN_REGION  environment variable. 
                 Once it has finished it will output a URL like this one: 
                 Service [my-service] revision [my-service-00001] has been deployed
and is serving traffic at https://my-service-j7hipcg4aq-uc.a.run.app 
                 Cloud Run provides a URL on the  .run.app  domain, but you can also point your own domain or subdomain at your Cloud Run service - see  mapping custom domains  in the Cloud Run documentation for details. 
                 See  datasette publish cloudrun  for the full list of options for this command.

references:
[{"href": "https://cloud.google.com/run/", "label": "Google Cloud Run"}, {"href": "https://datasette.io/plugins/datasette-block-robots", "label": "datasette-block-robots"}, {"href": "https://cloud.google.com/sdk/", "label": "these instructions"}, {"href": "https://cloud.google.com/sdk/docs/properties", "label": "set as properties for the Google Cloud SDK"}, {"href": "https://cloud.google.com/about/locations", "label": "region"}, {"href": "https://cloud.google.com/run/docs/mapping-custom-domains", "label": "mapping custom domains"}]


id: publish:publish-heroku
page: publish
ref: publish-heroku
title: Publishing to Heroku
breadcrumbs: ["Publishing data", "datasette publish"]
content:
To publish your data using  Heroku , first create an account there and install and configure the  Heroku CLI tool . 
                 You can publish one or more databases to Heroku using the following command: 
                 datasette publish heroku mydatabase.db 
                 This will output some details about the new deployment, including a URL like this one: 
                 https://limitless-reef-88278.herokuapp.com/ deployed to Heroku 
                 You can specify a custom app name by passing  -n my-app-name  to the publish command. This will also allow you to overwrite an existing app. 
                 Rather than deploying directly you can use the  --generate-dir  option to output the files that would be deployed to a directory: 
                 datasette publish heroku mydatabase.db --generate-dir=/tmp/deploy-this-to-heroku 
                 See  datasette publish heroku  for the full list of options for this command.

references:
[{"href": "https://www.heroku.com/", "label": "Heroku"}, {"href": "https://devcenter.heroku.com/articles/heroku-cli", "label": "Heroku CLI tool"}]


id: publish:publish-vercel
page: publish
ref: publish-vercel
title: Publishing to Vercel
breadcrumbs: ["Publishing data", "datasette publish"]
content:
Vercel   - previously known as Zeit Now - provides a layer over AWS Lambda to allow for quick, scale-to-zero deployment. You can deploy Datasette instances to Vercel using the  datasette-publish-vercel  plugin. 
                 pip install datasette-publish-vercel
datasette publish vercel mydatabase.db --project my-database-project 
                 Not every feature is supported: consult the  datasette-publish-vercel README  for more details.

references:
[{"href": "https://vercel.com/", "label": "Vercel"}, {"href": "https://github.com/simonw/datasette-publish-vercel", "label": "datasette-publish-vercel"}, {"href": "https://github.com/simonw/datasette-publish-vercel/blob/main/README.md", "label": "datasette-publish-vercel README"}]


id: publish:publish-fly
page: publish
ref: publish-fly
title: Publishing to Fly
breadcrumbs: ["Publishing data", "datasette publish"]
content:
Fly  is a  competitively priced  Docker-compatible hosting platform that supports running applications in globally distributed data centers close to your end users. You can deploy Datasette instances to Fly using the  datasette-publish-fly  plugin. 
                 pip install datasette-publish-fly
datasette publish fly mydatabase.db --app="my-app" 
                 Consult the  datasette-publish-fly README  for more details.

references:
[{"href": "https://fly.io/", "label": "Fly"}, {"href": "https://fly.io/docs/pricing/", "label": "competitively priced"}, {"href": "https://github.com/simonw/datasette-publish-fly", "label": "datasette-publish-fly"}, {"href": "https://github.com/simonw/datasette-publish-fly/blob/main/README.md", "label": "datasette-publish-fly README"}]


id: publish:publish-custom-metadata-and-plugins
page: publish
ref: publish-custom-metadata-and-plugins
title: Custom metadata and plugins
breadcrumbs: ["Publishing data", "datasette publish"]
content:
datasette publish  accepts a number of additional options which can be used to further customize your Datasette instance. 
                 You can define your own  Metadata  and deploy that with your instance like so: 
                 datasette publish cloudrun --service=my-service mydatabase.db -m metadata.json 
                 If you just want to set the title, license or source information you can do that directly using extra options to  datasette publish : 
                 datasette publish cloudrun mydatabase.db --service=my-service \
    --title="Title of my database" \
    --source="Where the data originated" \
    --source_url="http://www.example.com/" 
                 You can also specify plugins you would like to install. For example, if you want to include the  datasette-vega  visualization plugin you can use the following: 
                 datasette publish cloudrun mydatabase.db --service=my-service --install=datasette-vega 
                 If a plugin has any  Secret configuration values  you can use the  --plugin-secret  option to set those secrets at publish time. For example, using Heroku with  datasette-auth-github  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

references:
[{"href": "https://github.com/simonw/datasette-vega", "label": "datasette-vega"}, {"href": "https://github.com/simonw/datasette-auth-github", "label": "datasette-auth-github"}]


id: publish:cli-package
page: publish
ref: cli-package
title: datasette package
breadcrumbs: ["Publishing data"]
references: [{"href": "https://www.docker.com/docker-mac", "label": "Docker for Mac"}]
content:
If you have docker installed (e.g. using  Docker for Mac ) you can use the  datasette package  command to create a new Docker image in your local repository containing the datasette app bundled together with one or more SQLite databases: 
             datasette package mydatabase.db 
             Here's example output for the package command: 
             $ datasette package parlgov.db --extra-options="--setting sql_time_limit_ms 2500"
Sending build context to Docker daemon  4.459MB
Step 1/7 : FROM python:3.11.0-slim-bullseye
 ---> 79e1dc9af1c1
Step 2/7 : COPY . /app
 ---> Using cache
 ---> cd4ec67de656
Step 3/7 : WORKDIR /app
 ---> Using cache
 ---> 139699e91621
Step 4/7 : RUN pip install datasette
 ---> Using cache
 ---> 340efa82bfd7
Step 5/7 : RUN datasette inspect parlgov.db --inspect-file inspect-data.json
 ---> Using cache
 ---> 5fddbe990314
Step 6/7 : EXPOSE 8001
 ---> Using cache
 ---> 8e83844b0fed
Step 7/7 : CMD datasette serve parlgov.db --port 8001 --inspect-file inspect-data.json --setting sql_time_limit_ms 2500
 ---> Using cache
 ---> 1bd380ea8af3
Successfully built 1bd380ea8af3 
             You can now run the resulting container like so: 
             docker run -p 8081:8001 1bd380ea8af3 
             This exposes port 8001 inside the container as port 8081 on your host machine, so you can access the application at  http://localhost:8081/ 
             You can customize the port that is exposed by the container using the  --port  option: 
             datasette package mydatabase.db --port 8080 
             A full list of options can be seen by running  datasette package --help : 
             See  datasette package  for the full list of options for this command.


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: 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) 
                 
                 
                     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) 
                 
                 
                     register_output_renderer(datasette) 
                 
                 
                     register_routes(datasette) 
                 
                 
                     register_commands(cli) 
                 
                 
                     register_facet_classes() 
                 
                 
                     asgi_wrapper(datasette) 
                 
                 
                     startup(datasette) 
                 
                 
                     canned_queries(datasette, database, actor) 
                 
                 
                     actor_from_request(datasette, request) 
                 
                 
                     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) 
                 
                 
                     menu_links(datasette, actor, request) 
                 
                 
                     table_actions(datasette, actor, database, table, request) 
                 
                 
                     database_actions(datasette, actor, database, request) 
                 
                 
                     skip_csrf(datasette, scope) 
                 
                 
                     get_metadata(datasette, key, database, table)


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-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"]
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"]
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"]
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"]
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)
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. 
                     
                 
             
             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 influnce 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. 
                     
                 
                 
                     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-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. You can implement a regular function, for example to validate required plugin configuration: 
             @hookimpl
def startup(datasette):
    config = datasette.plugin_config("my-plugin") or {}
    assert (
        "required-setting" in config
    ), "my-plugin requires setting required-setting" 
             Or you can return an async function which will be awaited on startup. Use this option if you need to make any database queries: 
             @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 metadata 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"]
references: [{"href": "https://datasette.io/plugins/datasette-auth-tokens", "label": "datasette-auth-tokens"}]
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 
             Example:  datasette-auth-tokens


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 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-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-hook-table-actions
page: plugin_hooks
ref: plugin-hook-table-actions
title: table_actions(datasette, actor, database, table, request)
breadcrumbs: ["Plugin 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 hook allows table actions to be displayed in a menu accessed via an action icon at the top of the table page. It should return a list of  {"href": "...", "label": "..."}  menu items. 
             It can alternatively return an  async def  awaitable function which returns a list of menu items. 
             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",
            }
        ] 
             Example:  datasette-graphql


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"]
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. 
                     
                 
                 
                     request  -  Request object 
                     
                         The current HTTP request. 
                     
                 
             
             This hook is similar to  table_actions(datasette, actor, database, table, request)  but populates an actions menu on the database page. 
             Example:  datasette-graphql


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: writing_plugins:id1
page: writing_plugins
ref: id1
title: Writing plugins
breadcrumbs: []
content:
You can write one-off plugins that apply to just one Datasette instance, or you can write plugins which can be installed using  pip  and can be shipped to the Python Package Index ( PyPI ) for other people to install. 
         Want to start by looking at an example? The  Datasette plugins directory  lists more than 90 open source plugins with code you can explore. The  plugin hooks  page includes links to example plugins for each of the documented hooks.

references:
[{"href": "https://pypi.org/", "label": "PyPI"}, {"href": "https://datasette.io/plugins", "label": "Datasette plugins directory"}]


id: writing_plugins:writing-plugins-one-off
page: writing_plugins
ref: writing-plugins-one-off
title: Writing one-off plugins
breadcrumbs: ["Writing plugins"]
references: [{"href": "http://localhost:8001/mydb", "label": "http://localhost:8001/mydb"}]
content:
The quickest way to start writing a plugin is to create a  my_plugin.py  file and drop it into your  plugins/  directory. Here is an example plugin, which adds a new custom SQL function called  hello_world()  which takes no arguments and returns the string  Hello world! . 
             from datasette import hookimpl


@hookimpl
def prepare_connection(conn):
    conn.create_function(
        "hello_world", 0, lambda: "Hello world!"
    ) 
             If you save this in  plugins/my_plugin.py  you can then start Datasette like this: 
             datasette serve mydb.db --plugins-dir=plugins/ 
             Now you can navigate to  http://localhost:8001/mydb  and run this SQL: 
             select hello_world(); 
             To see the output of your plugin.


id: writing_plugins:writing-plugins-cookiecutter
page: writing_plugins
ref: writing-plugins-cookiecutter
title: Starting an installable plugin using cookiecutter
breadcrumbs: ["Writing plugins"]
content:
Plugins that can be installed should be written as Python packages using a  setup.py  file. 
             The quickest way to start writing one an installable plugin is to use the  datasette-plugin  cookiecutter template. This creates a new plugin structure for you complete with an example test and GitHub Actions workflows for testing and publishing your plugin. 
             Install cookiecutter  and then run this command to start building a plugin using the template: 
             cookiecutter gh:simonw/datasette-plugin 
             Read  a cookiecutter template for writing Datasette plugins  for more information about this template.

references:
[{"href": "https://github.com/simonw/datasette-plugin", "label": "datasette-plugin"}, {"href": "https://cookiecutter.readthedocs.io/en/stable/installation.html", "label": "Install cookiecutter"}, {"href": "https://simonwillison.net/2020/Jun/20/cookiecutter-plugins/", "label": "a cookiecutter template for writing Datasette plugins"}]


id: writing_plugins:writing-plugins-packaging
page: writing_plugins
ref: writing-plugins-packaging
title: Packaging a plugin
breadcrumbs: ["Writing plugins"]
content:
Plugins can be packaged using Python setuptools. You can see an example of a packaged plugin at  https://github.com/simonw/datasette-plugin-demos 
             The example consists of two files: a  setup.py  file that defines the plugin: 
             from setuptools import setup

VERSION = "0.1"

setup(
    name="datasette-plugin-demos",
    description="Examples of plugins for Datasette",
    author="Simon Willison",
    url="https://github.com/simonw/datasette-plugin-demos",
    license="Apache License, Version 2.0",
    version=VERSION,
    py_modules=["datasette_plugin_demos"],
    entry_points={
        "datasette": [
            "plugin_demos = datasette_plugin_demos"
        ]
    },
    install_requires=["datasette"],
) 
             And a Python module file,  datasette_plugin_demos.py , that implements the plugin: 
             from datasette import hookimpl
import random


@hookimpl
def prepare_jinja2_environment(env):
    env.filters["uppercase"] = lambda u: u.upper()


@hookimpl
def prepare_connection(conn):
    conn.create_function(
        "random_integer", 2, random.randint
    ) 
             Having built a plugin in this way you can turn it into an installable package using the following command: 
             python3 setup.py sdist 
             This will create a  .tar.gz  file in the  dist/  directory. 
             You can then install your new plugin into a Datasette virtual environment or Docker container using  pip : 
             pip install datasette-plugin-demos-0.1.tar.gz 
             To learn how to upload your plugin to  PyPI  for use by other people, read the PyPA guide to  Packaging and distributing projects .

references:
[{"href": "https://github.com/simonw/datasette-plugin-demos", "label": "https://github.com/simonw/datasette-plugin-demos"}, {"href": "https://pypi.org/", "label": "PyPI"}, {"href": "https://packaging.python.org/tutorials/distributing-packages/", "label": "Packaging and distributing projects"}]


id: writing_plugins:writing-plugins-static-assets
page: writing_plugins
ref: writing-plugins-static-assets
title: Static assets
breadcrumbs: ["Writing plugins"]
references: [{"href": "https://github.com/simonw/datasette-cluster-map", "label": "datasette-cluster-map"}]
content:
If your plugin has a  static/  directory, Datasette will automatically configure itself to serve those static assets from the following path: 
             /-/static-plugins/NAME_OF_PLUGIN_PACKAGE/yourfile.js 
             Use the  datasette.urls.static_plugins(plugin_name, path)  method to generate URLs to that asset that take the  base_url  setting into account, see  datasette.urls . 
             To bundle the static assets for a plugin in the package that you publish to PyPI, add the following to the plugin's  setup.py : 
             package_data = (
    {
        "datasette_plugin_name": [
            "static/plugin.js",
        ],
    },
) 
             Where  datasette_plugin_name  is the name of the plugin package (note that it uses underscores, not hyphens) and  static/plugin.js  is the path within that package to the static file. 
             datasette-cluster-map  is a useful example of a plugin that includes packaged static assets in this way.


id: writing_plugins:writing-plugins-custom-templates
page: writing_plugins
ref: writing-plugins-custom-templates
title: Custom templates
breadcrumbs: ["Writing plugins"]
references: [{"href": "https://github.com/simonw/datasette-edit-schema", "label": "datasette-edit-schema"}]
content:
If your plugin has a  templates/  directory, Datasette will attempt to load templates from that directory before it uses its own default templates. 
             The priority order for template loading is: 
             
                 
                     templates from the  --template-dir  argument, if specified 
                 
                 
                     templates from the  templates/  directory in any installed plugins 
                 
                 
                     default templates that ship with Datasette 
                 
             
             See  Custom pages and templates  for more details on how to write custom templates, including which filenames to use to customize which parts of the Datasette UI. 
             Templates should be bundled for distribution using the same  package_data  mechanism in  setup.py  described for static assets above, for example: 
             package_data = (
    {
        "datasette_plugin_name": [
            "templates/my_template.html",
        ],
    },
) 
             You can also use wildcards here such as  templates/*.html . See  datasette-edit-schema  for an example of this pattern.


id: writing_plugins:writing-plugins-configuration
page: writing_plugins
ref: writing-plugins-configuration
title: Writing plugins that accept configuration
breadcrumbs: ["Writing plugins"]
references: []
content:
When you are writing plugins, you can access plugin configuration like this using the  datasette plugin_config()  method. If you know you need plugin configuration for a specific table, you can access it like this: 
             plugin_config = datasette.plugin_config(
    "datasette-cluster-map", database="sf-trees", table="Street_Tree_List"
) 
             This will return the  {"latitude_column": "lat", "longitude_column": "lng"}  in the above example. 
             If there is no configuration for that plugin, the method will return  None . 
             If it cannot find the requested configuration at the table layer, it will fall back to the database layer and then the root layer. For example, a user may have set the plugin configuration option like so: 
             {
    "databases: {
        "sf-trees": {
            "plugins": {
                "datasette-cluster-map": {
                    "latitude_column": "xlat",
                    "longitude_column": "xlng"
                }
            }
        }
    }
} 
             In this case, the above code would return that configuration for ANY table within the  sf-trees  database. 
             The plugin configuration could also be set at the top level of  metadata.json : 
             {
    "title": "This is the top-level title in metadata.json",
    "plugins": {
        "datasette-cluster-map": {
            "latitude_column": "xlat",
            "longitude_column": "xlng"
        }
    }
} 
             Now that  datasette-cluster-map  plugin configuration will apply to every table in every database.


id: writing_plugins:writing-plugins-designing-urls
page: writing_plugins
ref: writing-plugins-designing-urls
title: Designing URLs for your plugin
breadcrumbs: ["Writing plugins"]
references: []
content:
You can register new URL routes within Datasette using the  register_routes(datasette)  plugin hook. 
             Datasette's default URLs include these: 
             
                 
                     /dbname  - database page 
                 
                 
                     /dbname/tablename  - table page 
                 
                 
                     /dbname/tablename/pk  - row page 
                 
             
             See  Pages and API endpoints  and  Introspection  for more default URL routes. 
             To avoid accidentally conflicting with a database file that may be loaded into Datasette, plugins should register URLs using a  /-/  prefix. For example, if your plugin adds a new interface for uploading Excel files you might register a URL route like this one: 
             
                 
                     /-/upload-excel 
                 
             
             Try to avoid registering URLs that clash with other plugins that your users might have installed. There is no central repository of reserved URL paths (yet) but you can review existing plugins by browsing the  plugins directory <https://datasette.io/plugins> . 
             If your plugin includes functionality that relates to a specific database you could also register a URL route like this: 
             
                 
                     /dbname/-/upload-excel 
                 
             
             Or for a specific table like this: 
             
                 
                     /dbname/tablename/-/modify-table-schema 
                 
             
             Note that a row could have a primary key of  -  and this URL scheme will still work, because Datasette row pages do not ever have a trailing slash followed by additional path components.


id: writing_plugins:writing-plugins-building-urls
page: writing_plugins
ref: writing-plugins-building-urls
title: Building URLs within plugins
breadcrumbs: ["Writing plugins"]
references: []
content:
Plugins that define their own custom user interface elements may need to link to other pages within Datasette. 
             This can be a bit tricky if the Datasette instance is using the  base_url  configuration setting to run behind a proxy, since that can cause Datasette's URLs to include an additional prefix. 
             The  datasette.urls  object provides internal methods for correctly generating URLs to different pages within Datasette, taking any  base_url  configuration into account. 
             This object is exposed in templates as the  urls  variable, which can be used like this: 
             Back to the <a href="{{ urls.instance() }}">Homepage</a> 
             See  datasette.urls  for full details on this object.


