# sections

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_message_sql ,   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: deploying:deploying
page: deploying
ref: deploying
title: Deploying Datasette
breadcrumbs: []
references: []
content:
The quickest way to deploy a Datasette instance on the internet is to use the  datasette publish  command, described in  Publishing data . This can be used to quickly deploy Datasette to a number of hosting providers including Heroku, Google Cloud Run and Vercel. 
         You can deploy Datasette to other hosting providers using the instructions on this page.


id: deploying:deploying-fundamentals
page: deploying
ref: deploying-fundamentals
title: Deployment fundamentals
breadcrumbs: ["Deploying Datasette"]
references: []
content:
Datasette can be deployed as a single  datasette  process that listens on a port. Datasette is not designed to be run as root, so that process should listen on a higher port such as port 8000. 
             If you want to serve Datasette on port 80 (the HTTP default port) or port 443 (for HTTPS) you should run it behind a proxy server, such as nginx, Apache or HAProxy. The proxy server can listen on port 80/443 and forward traffic on to Datasette.


id: deploying:deploying-systemd
page: deploying
ref: deploying-systemd
title: Running Datasette using systemd
breadcrumbs: ["Deploying Datasette"]
references: []
content:
You can run Datasette on Ubuntu or Debian systems using  systemd . 
             First, ensure you have Python 3 and  pip  installed. On Ubuntu you can use  sudo apt-get install python3 python3-pip . 
             You can install Datasette into a virtual environment, or you can install it system-wide. To install system-wide, use  sudo pip3 install datasette . 
             Now create a folder for your Datasette databases, for example using  mkdir /home/ubuntu/datasette-root . 
             You can copy a test database into that folder like so: 
             cd /home/ubuntu/datasette-root
curl -O https://latest.datasette.io/fixtures.db 
             Create a file at  /etc/systemd/system/datasette.service  with the following contents: 
             [Unit]
Description=Datasette
After=network.target

[Service]
Type=simple
User=ubuntu
Environment=DATASETTE_SECRET=
WorkingDirectory=/home/ubuntu/datasette-root
ExecStart=datasette serve . -h 127.0.0.1 -p 8000
Restart=on-failure

[Install]
WantedBy=multi-user.target 
             Add a random value for the  DATASETTE_SECRET  - this will be used to sign Datasette cookies such as the CSRF token cookie. You can generate a suitable value like so: 
             python3 -c 'import secrets; print(secrets.token_hex(32))' 
             This configuration will run Datasette against all database files contained in the  /home/ubuntu/datasette-root  directory. If that directory contains a  metadata.yml  (or  .json ) file or a  templates/  or  plugins/  sub-directory those will automatically be loaded by Datasette - see  Configuration directory mode  for details. 
             You can start the Datasette process running using the following: 
             sudo systemctl daemon-reload
sudo systemctl start datasette.service 
             You will need to restart the Datasette service after making changes to its  metadata.json  configuration or adding a new database file to that directory. You can do that using: 
             sudo systemctl restart datasette.service 
             Once the service has started you can confirm that Datasette is running on port 8000 like so: 
             curl 127.0.0.1:8000/-/versions.json
# Should output JSON showing the installed version 
             Datasette will not be accessible from outside the server because it is listening on  127.0.0.1 . You can expose it by instead listening on  0.0.0.0 , but a better way is to set up a proxy such as  nginx  - see  Running Datasette behind a proxy .


id: deploying:deploying-openrc
page: deploying
ref: deploying-openrc
title: Running Datasette using OpenRC
breadcrumbs: ["Deploying Datasette"]
content:
OpenRC is the service manager on non-systemd Linux distributions like  Alpine Linux  and  Gentoo . 
             Create an init script at  /etc/init.d/datasette  with the following contents: 
             #!/sbin/openrc-run

name="datasette"
command="datasette"
command_args="serve -h 0.0.0.0 /path/to/db.db"
command_background=true
pidfile="/run/${RC_SVCNAME}.pid" 
             You then need to configure the service to run at boot and start it: 
             rc-update add datasette
rc-service datasette start

references:
[{"href": "https://www.alpinelinux.org/", "label": "Alpine Linux"}, {"href": "https://www.gentoo.org/", "label": "Gentoo"}]


id: deploying:deploying-buildpacks
page: deploying
ref: deploying-buildpacks
title: Deploying using buildpacks
breadcrumbs: ["Deploying Datasette"]
content:
Some hosting providers such as  Heroku ,  DigitalOcean App Platform  and  Scalingo  support the  Buildpacks standard  for deploying Python web applications. 
             Deploying Datasette on these platforms requires two files:  requirements.txt  and  Procfile . 
             The  requirements.txt  file lets the platform know which Python packages should be installed. It should contain  datasette  at a minimum, but can also list any Datasette plugins you wish to install - for example: 
             datasette
datasette-vega 
             The  Procfile  lets the hosting platform know how to run the command that serves web traffic. It should look like this: 
             web: datasette . -h 0.0.0.0 -p $PORT --cors 
             The  $PORT  environment variable is provided by the hosting platform.  --cors  enables CORS requests from JavaScript running on other websites to your domain - omit this if you don't want to allow CORS. You can add additional Datasette  Settings  options here too. 
             These two files should be enough to deploy Datasette on any host that supports buildpacks. Datasette will serve any SQLite files that are included in the root directory of the application. 
             If you want to build SQLite files or download them as part of the deployment process you can do so using a  bin/post_compile  file. For example, the following  bin/post_compile  will download an example database that will then be served by Datasette: 
             wget https://fivethirtyeight.datasettes.com/fivethirtyeight.db 
             simonw/buildpack-datasette-demo  is an example GitHub repository showing a Datasette configuration that can be deployed to a buildpack-supporting host.

references:
[{"href": "https://www.heroku.com/", "label": "Heroku"}, {"href": "https://www.digitalocean.com/docs/app-platform/", "label": "DigitalOcean App Platform"}, {"href": "https://scalingo.com/", "label": "Scalingo"}, {"href": "https://buildpacks.io/", "label": "Buildpacks standard"}, {"href": "https://github.com/simonw/buildpack-datasette-demo", "label": "simonw/buildpack-datasette-demo"}]


id: deploying:deploying-proxy
page: deploying
ref: deploying-proxy
title: Running Datasette behind a proxy
breadcrumbs: ["Deploying Datasette"]
references: []
content:
You may wish to run Datasette behind an Apache or nginx proxy, using a path within your existing site. 
             You can use the  base_url  configuration setting to tell Datasette to serve traffic with a specific URL prefix. For example, you could run Datasette like this: 
             datasette my-database.db --setting base_url /my-datasette/ -p 8009 
             This will run Datasette with the following URLs: 
             
                 
                     http://127.0.0.1:8009/my-datasette/  - the Datasette homepage 
                 
                 
                     http://127.0.0.1:8009/my-datasette/my-database  - the page for the  my-database.db  database 
                 
                 
                     http://127.0.0.1:8009/my-datasette/my-database/some_table  - the page for the  some_table  table 
                 
             
             You can now set your nginx or Apache server to proxy the  /my-datasette/  path to this Datasette instance.


id: deploying:nginx-proxy-configuration
page: deploying
ref: nginx-proxy-configuration
title: Nginx proxy configuration
breadcrumbs: ["Deploying Datasette", "Running Datasette behind a proxy"]
references: [{"href": "https://nginx.org/", "label": "nginx"}]
content:
Here is an example of an  nginx  configuration file that will proxy traffic to Datasette: 
                 daemon off;

events {
  worker_connections  1024;
}
http {
  server {
    listen 80;
    location /my-datasette {
      proxy_pass http://127.0.0.1:8009/my-datasette;
      proxy_set_header Host $host;
    }
  }
} 
                 You can also use the  --uds  option to Datasette to listen on a Unix domain socket instead of a port, configuring the nginx upstream proxy like this: 
                 daemon off;
events {
  worker_connections  1024;
}
http {
  server {
    listen 80;
    location /my-datasette {
      proxy_pass http://datasette/my-datasette;
      proxy_set_header Host $host;
    }
  }
  upstream datasette {
    server unix:/tmp/datasette.sock;
  }
} 
                 Then run Datasette with  datasette --uds /tmp/datasette.sock path/to/database.db --setting base_url /my-datasette/ .


id: deploying:apache-proxy-configuration
page: deploying
ref: apache-proxy-configuration
title: Apache proxy configuration
breadcrumbs: ["Deploying Datasette", "Running Datasette behind a proxy"]
content:
For  Apache , you can use the  ProxyPass  directive. First make sure the following lines are uncommented: 
                 LoadModule proxy_module lib/httpd/modules/mod_proxy.so
LoadModule proxy_http_module lib/httpd/modules/mod_proxy_http.so 
                 Then add these directives to proxy traffic: 
                 ProxyPass /my-datasette/ http://127.0.0.1:8009/my-datasette/
ProxyPreserveHost On 
                 A live demo of Datasette running behind Apache using this proxy setup can be seen at  datasette-apache-proxy-demo.datasette.io/prefix/ . The code for that demo can be found in the  demos/apache-proxy  directory. 
                 Using  --uds  you can use Unix domain sockets similar to the nginx example: 
                 ProxyPass /my-datasette/ unix:/tmp/datasette.sock|http://localhost/my-datasette/ 
                 The  ProxyPreserveHost On  directive ensures that the original  Host:  header from the incoming request is passed through to Datasette. Datasette needs this to correctly assemble links to other pages using the  .absolute_url(request, path)  method.

references:
[{"href": "https://httpd.apache.org/", "label": "Apache"}, {"href": "https://datasette-apache-proxy-demo.datasette.io/prefix/", "label": "datasette-apache-proxy-demo.datasette.io/prefix/"}, {"href": "https://github.com/simonw/datasette/tree/main/demos/apache-proxy", "label": "demos/apache-proxy"}, {"href": "https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#proxypreservehost", "label": "ProxyPreserveHost On"}]


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: authentication:authentication
page: authentication
ref: authentication
title: Authentication and permissions
breadcrumbs: []
references: []
content:
Datasette doesn't 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-explained
page: authentication
ref: authentication-permissions-explained
title: How permissions are resolved
breadcrumbs: ["Authentication and permissions", "Permissions"]
references: []
content:
The  datasette.permission_allowed(actor, action, resource=None, default=...)  method is called to check if an actor is allowed to perform a specific action. 
                 This method asks every plugin that implements the  permission_allowed(datasette, actor, action, resource)  hook if the actor is allowed to perform the action. 
                 Each plugin can return  True  to indicate that the actor is allowed to perform the action,  False  if they are not allowed and  None  if the plugin has no opinion on the matter. 
                 False  acts as a veto - if any plugin returns  False  then the permission check is denied. Otherwise, if any plugin returns  True  then the permission check is allowed. 
                 The  resource  argument can be used to specify a specific resource that the action is being performed against. Some permissions, such as  view-instance , do not involve a resource. Others such as  view-database  have a resource that is a string naming the database. Permissions that take both a database name and the name of a table, view or canned query within that database use a resource that is a tuple of two strings,  (database_name, resource_name) . 
                 Plugins that implement the  permission_allowed()  hook can decide if they are going to consider the provided resource or not.


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  in the datasette.yaml file . 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 ): 
                 [[[cog
from metadata_doc import config_example
import textwrap
config_example(cog, textwrap.dedent(
  """
    allow:
      id: root
    """).strip(),
    "YAML", "JSON"
  ) 
                 ]]] 
                 [[[end]]] 
                 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 ): 
                 [[[cog
from metadata_doc import config_example
import textwrap
config_example(cog, textwrap.dedent(
  """
    allow: false
    """).strip(),
    "YAML", "JSON"
  ) 
                 ]]] 
                 [[[end]]] 
                 An  "allow"  of  true  allows all access ( demo ): 
                 [[[cog
from metadata_doc import config_example
import textwrap
config_example(cog, textwrap.dedent(
  """
    allow: true
    """).strip(),
    "YAML", "JSON"
  ) 
                 ]]] 
                 [[[end]]] 
                 Allow keys can provide a list of values. These will match any actor that has any of those values ( allow demo ,  deny demo ): 
                 [[[cog
from metadata_doc import config_example
import textwrap
config_example(cog, textwrap.dedent(
  """
    allow:
      id:
      - simon
      - cleopaws
    """).strip(),
    "YAML", "JSON"
  ) 
                 ]]] 
                 [[[end]]] 
                 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 ): 
                 [[[cog
from metadata_doc import config_example
import textwrap
config_example(cog, textwrap.dedent(
  """
    allow:
      roles:
      - developer
    """).strip(),
    "YAML", "JSON"
  ) 
                 ]]] 
                 [[[end]]] 
                 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 ): 
                 [[[cog
from metadata_doc import config_example
import textwrap
config_example(cog, textwrap.dedent(
  """
    allow:
      id: "*"
    """).strip(),
    "YAML", "JSON"
  ) 
                 ]]] 
                 [[[end]]] 
                 You can specify that only unauthenticated actors (from anonymous HTTP requests) should be allowed access using the special  "unauthenticated": true  key in an allow block ( allow demo ,  deny demo ): 
                 [[[cog
from metadata_doc import config_example
import textwrap
config_example(cog, textwrap.dedent(
  """
    allow:
      unauthenticated: true
    """).strip(),
    "YAML", "JSON"
  ) 
                 ]]] 
                 [[[end]]] 
                 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" : 
                 [[[cog
from metadata_doc import config_example
import textwrap
config_example(cog, textwrap.dedent(
  """
    allow:
      id:
      - simon
      - cleopaws
      role: ops
    """).strip(),
    "YAML", "JSON"
  ) 
                 ]]] 
                 [[[end]]] 
                 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-config
page: authentication
ref: authentication-permissions-config
title: Access permissions in
breadcrumbs: ["Authentication and permissions"]
references: []
content:
There are two ways to configure permissions using  datasette.yaml  (or  datasette.json ). 
             For simple visibility permissions you can use  "allow"  blocks in the root, database, table and query sections. 
             For other permissions you can use a  "permissions"  block, described  in the next section . 
             You can limit who is allowed to view different parts of your Datasette instance using  "allow"  keys in your  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: Access to an instance
breadcrumbs: ["Authentication and permissions", "Access permissions in "]
content:
Here's how to restrict access to your entire Datasette instance to just the  "id": "root"  user: 
                 [[[cog
from metadata_doc import config_example
config_example(cog, """
    title: My private Datasette instance
    allow:
      id: root
  """) 
                 ]]] 
                 [[[end]]] 
                 To deny access to all users, you can use  "allow": false : 
                 [[[cog
config_example(cog, """
    title: My entirely inaccessible instance
    allow: false
""") 
                 ]]] 
                 [[[end]]] 
                 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: Access to specific databases
breadcrumbs: ["Authentication and permissions", "Access permissions in "]
references: []
content:
To limit access to a specific  private.db  database to just authenticated users, use the  "allow"  block like this: 
                 [[[cog
config_example(cog, """
    databases:
      private:
        allow:
          id: "*"
""") 
                 ]]] 
                 [[[end]]]


id: authentication:authentication-permissions-table
page: authentication
ref: authentication-permissions-table
title: Access to specific tables and views
breadcrumbs: ["Authentication and permissions", "Access permissions in "]
content:
To limit access to the  users  table in your  bakery.db  database: 
                 [[[cog
config_example(cog, """
    databases:
      bakery:
        tables:
          users:
            allow:
              id: '*'
""") 
                 ]]] 
                 [[[end]]] 
                 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: Access to specific canned queries
breadcrumbs: ["Authentication and permissions", "Access permissions in "]
references: []
content:
Canned queries  allow you to configure named SQL queries in your  datasette.yaml  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 : 
                 [[[cog
config_example(cog, """
    databases:
      dogs:
        queries:
          add_name:
            sql: INSERT INTO names (name) VALUES (:name)
            write: true
            allow:
              id:
              - root
""") 
                 ]]] 
                 [[[end]]]


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", "Access permissions in "]
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: 
                 [[[cog
config_example(cog, """
    allow_sql: false
""") 
                 ]]] 
                 [[[end]]] 
                 To enable just the  root user  to execute SQL for all databases in your instance, use the following: 
                 [[[cog
config_example(cog, """
    allow_sql:
      id: root
""") 
                 ]]] 
                 [[[end]]] 
                 To limit this ability for just one specific database, use this: 
                 [[[cog
config_example(cog, """
    databases:
      mydatabase:
        allow_sql:
          id: root
""") 
                 ]]] 
                 [[[end]]]

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:authentication-permissions-other
page: authentication
ref: authentication-permissions-other
title: Other permissions in
breadcrumbs: ["Authentication and permissions"]
references: []
content:
For all other permissions, you can use one or more  "permissions"  blocks in your  datasette.yaml  configuration file. 
             To grant access to the  permissions debug tool  to all signed in users, you can grant  permissions-debug  to any actor with an  id  matching the wildcard  *  by adding this a the root of your configuration: 
             [[[cog
config_example(cog, """
    permissions:
      debug-menu:
        id: '*'
""") 
             ]]] 
             [[[end]]] 
             To grant  create-table  to the user with  id  of  editor  for the  docs  database: 
             [[[cog
config_example(cog, """
    databases:
      docs:
        permissions:
          create-table:
            id: editor
""") 
             ]]] 
             [[[end]]] 
             And for  insert-row  against the  reports  table in that  docs  database: 
             [[[cog
config_example(cog, """
    databases:
      docs:
        tables:
          reports:
            permissions:
              insert-row:
                id: editor
""") 
             ]]] 
             [[[end]]] 
             The  permissions debug tool  can be useful for helping test permissions that you have configured in this way.


id: authentication:createtokenview
page: authentication
ref: createtokenview
title: API Tokens
breadcrumbs: ["Authentication and permissions"]
references: []
content:
Datasette includes a default mechanism for generating API tokens that can be used to authenticate requests. 
             Authenticated users can create new API tokens using a form on the  /-/create-token  page. 
             Tokens created in this way can be further restricted to only allow access to specific actions, or to limit those actions to specific databases, tables or queries. 
             Created tokens can then be passed in the  Authorization: Bearer $token  header of HTTP requests to Datasette. 
             A token created by a user will include that user's  "id"  in the token payload, so any permissions granted to that user based on their ID can be made available to the token as well. 
             When one of these a token accompanies a request, the actor for that request will have the following shape: 
             {
    "id": "user_id",
    "token": "dstok",
    "token_expires": 1667717426
} 
             The  "id"  field duplicates the ID of the actor who first created the token. 
             The  "token"  field identifies that this actor was authenticated using a Datasette signed token ( dstok ). 
             The  "token_expires"  field, if present, indicates that the token will expire after that integer timestamp. 
             The  /-/create-token  page cannot be accessed by actors that are authenticated with a  "token": "some-value"  property. This is to prevent API tokens from being used to create more tokens. 
             Datasette plugins that implement their own form of API token authentication should follow this convention. 
             You can disable the signed token feature entirely using the  allow_signed_tokens  setting.


id: authentication:authentication-cli-create-token
page: authentication
ref: authentication-cli-create-token
title: datasette create-token
breadcrumbs: ["Authentication and permissions", "API Tokens"]
references: []
content:
You can also create tokens on the command line using the  datasette create-token  command. 
                 This command takes one required argument - the ID of the actor to be associated with the created token. 
                 You can specify a  -e/--expires-after  option in seconds. If omitted, the token will never expire. 
                 The command will sign the token using the  DATASETTE_SECRET  environment variable, if available. You can also pass the secret using the  --secret  option. 
                 This means you can run the command locally to create tokens for use with a deployed Datasette instance, provided you know that instance's secret. 
                 To create a token for the  root  actor that will expire in one hour: 
                 datasette create-token root --expires-after 3600 
                 To create a token that never expires using a specific secret: 
                 datasette create-token root --secret my-secret-goes-here


id: authentication:authentication-cli-create-token-restrict
page: authentication
ref: authentication-cli-create-token-restrict
title: Restricting the actions that a token can perform
breadcrumbs: ["Authentication and permissions", "API Tokens", "datasette create-token"]
references: []
content:
Tokens created using  datasette create-token ACTOR_ID  will inherit all of the permissions of the actor that they are associated with. 
                     You can pass additional options to create tokens that are restricted to a subset of that actor's permissions. 
                     To restrict the token to just specific permissions against all available databases, use the  --all  option: 
                     datasette create-token root --all insert-row --all update-row 
                     This option can be passed as many times as you like. In the above example the token will only be allowed to insert and update rows. 
                     You can also restrict permissions such that they can only be used within specific databases: 
                     datasette create-token root --database mydatabase insert-row 
                     The resulting token will only be able to insert rows, and only to tables in the  mydatabase  database. 
                     Finally, you can restrict permissions to individual resources - tables, SQL views and  named queries  - within a specific database: 
                     datasette create-token root --resource mydatabase mytable insert-row 
                     These options have short versions:  -a  for  --all ,  -d  for  --database  and  -r  for  --resource . 
                     You can add  --debug  to see a JSON representation of the token that has been created. Here's a full example: 
                     datasette create-token root \
    --secret mysecret \
    --all view-instance \
    --all view-table \
    --database docs view-query \
    --resource docs documents insert-row \
    --resource docs documents update-row \
    --debug 
                     This example outputs the following: 
                     dstok_.eJxFizEKgDAMRe_y5w4qYrFXERGxDkVsMI0uxbubdjFL8l_ez1jhwEQCA6Fjjxp90qtkuHawzdjYrh8MFobLxZ_wBH0_gtnAF-hpS5VfmF8D_lnd97lHqUJgLd6sls4H1qwlhA.nH_7RecYHj5qSzvjhMU95iy0Xlc

Decoded:

{
  "a": "root",
  "token": "dstok",
  "t": 1670907246,
  "_r": {
    "a": [
      "vi",
      "vt"
    ],
    "d": {
      "docs": [
        "vq"
      ]
    },
    "r": {
      "docs": {
        "documents": [
          "ir",
          "ur"
        ]
      }
    }
  }
}


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). 
             It shows the thirty most recent permission checks that have been carried out by the Datasette instance. 
             It also provides an interface for running hypothetical permission checks against a hypothetical actor. This is a useful way of confirming that your configured permissions work in the way you expect. 
             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-insert-row
page: authentication
ref: permissions-insert-row
title: insert-row
breadcrumbs: ["Authentication and permissions", "Built-in permissions"]
references: []
content:
Actor is allowed to insert rows into a table. 
                 
                     
                         resource  - tuple: (string, string) 
                         
                             The name of the database, then the name of the table 
                         
                     
                 
                 Default  deny .


id: authentication:permissions-delete-row
page: authentication
ref: permissions-delete-row
title: delete-row
breadcrumbs: ["Authentication and permissions", "Built-in permissions"]
references: []
content:
Actor is allowed to delete rows from a table. 
                 
                     
                         resource  - tuple: (string, string) 
                         
                             The name of the database, then the name of the table 
                         
                     
                 
                 Default  deny .


id: authentication:permissions-update-row
page: authentication
ref: permissions-update-row
title: update-row
breadcrumbs: ["Authentication and permissions", "Built-in permissions"]
references: []
content:
Actor is allowed to update rows in a table. 
                 
                     
                         resource  - tuple: (string, string) 
                         
                             The name of the database, then the name of the table 
                         
                     
                 
                 Default  deny .


id: authentication:permissions-create-table
page: authentication
ref: permissions-create-table
title: create-table
breadcrumbs: ["Authentication and permissions", "Built-in permissions"]
references: []
content:
Actor is allowed to create a database table. 
                 
                     
                         resource  - string 
                         
                             The name of the database 
                         
                     
                 
                 Default  deny .


id: authentication:permissions-alter-table
page: authentication
ref: permissions-alter-table
title: alter-table
breadcrumbs: ["Authentication and permissions", "Built-in permissions"]
references: []
content:
Actor is allowed to alter a database table. 
                 
                     
                         resource  - tuple: (string, string) 
                         
                             The name of the database, then the name of the table 
                         
                     
                 
                 Default  deny .


id: authentication:permissions-drop-table
page: authentication
ref: permissions-drop-table
title: drop-table
breadcrumbs: ["Authentication and permissions", "Built-in permissions"]
references: []
content:
Actor is allowed to drop a database table. 
                 
                     
                         resource  - tuple: (string, string) 
                         
                             The name of the database, then the name of the table 
                         
                     
                 
                 Default  deny .


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: 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-tracing
page: writing_plugins
ref: writing-plugins-tracing
title: Tracing plugin hooks
breadcrumbs: ["Writing plugins"]
references: []
content:
The  DATASETTE_TRACE_PLUGINS  environment variable turns on detailed tracing showing exactly which hooks are being run. This can be useful for understanding how Datasette is using your plugin. 
             DATASETTE_TRACE_PLUGINS=1 datasette mydb.db 
             Example output: 
             actor_from_request:
{   'datasette': <datasette.app.Datasette object at 0x100bc7220>,
    'request': <asgi.Request method="GET" url="http://127.0.0.1:4433/">}
Hook implementations:
[   <HookImpl plugin_name='codespaces', plugin=<module 'datasette_codespaces' from '.../site-packages/datasette_codespaces/__init__.py'>>,
    <HookImpl plugin_name='datasette.actor_auth_cookie', plugin=<module 'datasette.actor_auth_cookie' from '.../datasette/datasette/actor_auth_cookie.py'>>,
    <HookImpl plugin_name='datasette.default_permissions', plugin=<module 'datasette.default_permissions' from '.../datasette/default_permissions.py'>>]
Results:
[{'id': 'root'}]


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 inside  datasette.yaml  like so: 
             [[[cog
from metadata_doc import metadata_example
metadata_example(cog, {
    "databases": {
        "sf-trees": {
            "plugins": {
                "datasette-cluster-map": {
                    "latitude_column": "xlat",
                    "longitude_column": "xlng"
                }
            }
        }
    }
}) 
             ]]] 
             [[[end]]] 
             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  datasette.yaml : 
             [[[cog
metadata_example(cog, {
    "plugins": {
        "datasette-cluster-map": {
            "latitude_column": "xlat",
            "longitude_column": "xlng"
        }
    }
}) 
             ]]] 
             [[[end]]] 
             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: [{"href": "https://datasette.io/plugins", "label": "plugins directory"}]
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 . 
             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.


id: writing_plugins:writing-plugins-extra-hooks
page: writing_plugins
ref: writing-plugins-extra-hooks
title: Plugins that define new plugin hooks
breadcrumbs: ["Writing plugins"]
content:
Plugins can define new plugin hooks that other plugins can use to further extend their functionality. 
             datasette-graphql  is one example of a plugin that does this. It defines a new hook called  graphql_extra_fields ,  described here , which other plugins can use to define additional fields that should be included in the GraphQL schema. 
             To define additional hooks, add a file to the plugin called  datasette_your_plugin/hookspecs.py  with content that looks like this: 
             from pluggy import HookspecMarker

hookspec = HookspecMarker("datasette")


@hookspec
def name_of_your_hook_goes_here(datasette):
    "Description of your hook." 
             You should define your own hook name and arguments here, following the documentation for  Pluggy specifications . Make sure to pick a name that is unlikely to clash with hooks provided by any other plugins. 
             Then, to register your plugin hooks, add the following code to your  datasette_your_plugin/__init__.py  file: 
             from datasette.plugins import pm
from . import hookspecs

pm.add_hookspecs(hookspecs) 
             This will register your plugin hooks as part of the  datasette  plugin hook namespace. 
             Within your plugin code you can trigger the hook using this pattern: 
             from datasette.plugins import pm

for (
    plugin_return_value
) in pm.hook.name_of_your_hook_goes_here(
    datasette=datasette
):
    # Do something with plugin_return_value
    pass 
             Other plugins will then be able to register their own implementations of your hook using this syntax: 
             from datasette import hookimpl


@hookimpl
def name_of_your_hook_goes_here(datasette):
    return "Response from this plugin hook" 
             These plugin implementations can accept 0 or more of the named arguments that you defined in your hook specification.

references:
[{"href": "https://github.com/simonw/datasette-graphql", "label": "datasette-graphql"}, {"href": "https://github.com/simonw/datasette-graphql/blob/main/README.md#adding-custom-fields-with-plugins", "label": "described here"}, {"href": "https://pluggy.readthedocs.io/en/stable/#specs", "label": "Pluggy specifications"}]


id: events:id1
page: events
ref: id1
title: Events
breadcrumbs: []
references: []
content:
Datasette includes a mechanism for tracking events that occur while the software is running. This is primarily intended to be used by plugins, which can both trigger events and listen for events. 
         The core Datasette application triggers events when certain things happen. This page describes those events. 
         Plugins can listen for events using the  track_event(datasette, event)  plugin hook, which will be called with instances of the following classes - or additional classes  registered by other plugins . 
         
         
         
         
             class   datasette.events. LoginEvent actor :   dict   |   None 
             
                 Event name:  login 
                 A user (represented by  event.actor ) has logged in. 
             
         
         
         
             class   datasette.events. LogoutEvent actor :   dict   |   None 
             
                 Event name:  logout 
                 A user (represented by  event.actor ) has logged out. 
             
         
         
         
             class   datasette.events. CreateTokenEvent actor :   dict   |   None expires_after :   int   |   None restrict_all :   list restrict_database :   dict restrict_resource :   dict 
             
                 Event name:  create-token 
                 A user created an API token. 
                 
                     
                         Variables 
                         
                             
                                 
                                     expires_after  -- Number of seconds after which this token will expire. 
                                 
                                 
                                     restrict_all  -- Restricted permissions for this token. 
                                 
                                 
                                     restrict_database  -- Restricted database permissions for this token. 
                                 
                                 
                                     restrict_resource  -- Restricted resource permissions for this token. 
                                 
                             
                         
                     
                 
             
         
         
         
             class   datasette.events. CreateTableEvent actor :   dict   |   None database :   str table :   str schema :   str 
             
                 Event name:  create-table 
                 A new table has been created in the database. 
                 
                     
                         Variables 
                         
                             
                                 
                                     database  -- The name of the database where the table was created. 
                                 
                                 
                                     table  -- The name of the table that was created 
                                 
                                 
                                     schema  -- The SQL schema definition for the new table. 
                                 
                             
                         
                     
                 
             
         
         
         
             class   datasette.events. DropTableEvent actor :   dict   |   None database :   str table :   str 
             
                 Event name:  drop-table 
                 A table has been dropped from the database. 
                 
                     
                         Variables 
                         
                             
                                 
                                     database  -- The name of the database where the table was dropped. 
                                 
                                 
                                     table  -- The name of the table that was dropped 
                                 
                             
                         
                     
                 
             
         
         
         
             class   datasette.events. AlterTableEvent actor :   dict   |   None database :   str table :   str before_schema :   str after_schema :   str 
             
                 Event name:  alter-table 
                 A table has been altered. 
                 
                     
                         Variables 
                         
                             
                                 
                                     database  -- The name of the database where the table was altered 
                                 
                                 
                                     table  -- The name of the table that was altered 
                                 
                                 
                                     before_schema  -- The table's SQL schema before the alteration 
                                 
                                 
                                     after_schema  -- The table's SQL schema after the alteration 
                                 
                             
                         
                     
                 
             
         
         
         
             class   datasette.events. InsertRowsEvent actor :   dict   |   None database :   str table :   str num_rows :   int ignore :   bool replace :   bool 
             
                 Event name:  insert-rows 
                 Rows were inserted into a table. 
                 
                     
                         Variables 
                         
                             
                                 
                                     database  -- The name of the database where the rows were inserted. 
                                 
                                 
                                     table  -- The name of the table where the rows were inserted. 
                                 
                                 
                                     num_rows  -- The number of rows that were requested to be inserted. 
                                 
                                 
                                     ignore  -- Was ignore set? 
                                 
                                 
                                     replace  -- Was replace set? 
                                 
                             
                         
                     
                 
             
         
         
         
             class   datasette.events. UpsertRowsEvent actor :   dict   |   None database :   str table :   str num_rows :   int 
             
                 Event name:  upsert-rows 
                 Rows were upserted into a table. 
                 
                     
                         Variables 
                         
                             
                                 
                                     database  -- The name of the database where the rows were inserted. 
                                 
                                 
                                     table  -- The name of the table where the rows were inserted. 
                                 
                                 
                                     num_rows  -- The number of rows that were requested to be inserted. 
                                 
                             
                         
                     
                 
             
         
         
         
             class   datasette.events. UpdateRowEvent actor :   dict   |   None database :   str table :   str pks :   list 
             
                 Event name:  update-row 
                 A row was updated in a table. 
                 
                     
                         Variables 
                         
                             
                                 
                                     database  -- The name of the database where the row was updated. 
                                 
                                 
                                     table  -- The name of the table where the row was updated. 
                                 
                                 
                                     pks  -- The primary key values of the updated row. 
                                 
                             
                         
                     
                 
             
         
         
         
             class   datasette.events. DeleteRowEvent actor :   dict   |   None database :   str table :   str pks :   list 
             
                 Event name:  delete-row 
                 A row was deleted from a table. 
                 
                     
                         Variables 
                         
                             
                                 
                                     database  -- The name of the database where the row was deleted. 
                                 
                                 
                                     table  -- The name of the table where the row was deleted. 
                                 
                                 
                                     pks  -- The primary key values of the deleted row.


id: javascript_plugins:id1
page: javascript_plugins
ref: id1
title: JavaScript plugins
breadcrumbs: []
references: []
content:
Datasette can run custom JavaScript in several different ways: 
         
             
                 Datasette plugins written in Python can use the  extra_js_urls()  or  extra_body_script()  plugin hooks to inject JavaScript into a page 
             
             
                 Datasette instances with  custom templates  can include additional JavaScript in those templates 
             
             
                 The  extra_js_urls  key in  datasette.yaml   can be used to include extra JavaScript 
             
         
         There are no limitations on what this JavaScript can do. It is executed directly by the browser, so it can manipulate the DOM, fetch additional data and do anything else that JavaScript is capable of. 
         
             Custom JavaScript has security implications, especially for authenticated Datasette instances where the JavaScript might run in the context of the authenticated user. It's important to carefully review any JavaScript you run in your Datasette instance.


id: javascript_plugins:javascript-datasette-init
page: javascript_plugins
ref: javascript-datasette-init
title: The datasette_init event
breadcrumbs: ["JavaScript plugins"]
references: []
content:
Datasette emits a custom event called  datasette_init  when the page is loaded. This event is dispatched on the  document  object, and includes a  detail  object with a reference to the  datasetteManager  object. 
             Your JavaScript code can listen out for this event using  document.addEventListener()  like this: 
             document.addEventListener("datasette_init", function (evt) {
    const manager = evt.detail;
    console.log("Datasette version:", manager.VERSION);
});


id: javascript_plugins:javascript-datasette-manager
page: javascript_plugins
ref: javascript-datasette-manager
title: datasetteManager
breadcrumbs: ["JavaScript plugins"]
references: []
content:
The  datasetteManager  object 
             
                 
                     VERSION  - string 
                     
                         The version of Datasette 
                     
                 
                 
                     plugins  -  Map() 
                     
                         A Map of currently loaded plugin names to plugin implementations 
                     
                 
                 
                     registerPlugin(name, implementation) 
                     
                         Call this to register a plugin, passing its name and implementation 
                     
                 
                 
                     selectors  - object 
                     
                         An object providing named aliases to useful CSS selectors,  listed below


id: javascript_plugins:id2
page: javascript_plugins
ref: id2
title: JavaScript plugin objects
breadcrumbs: ["JavaScript plugins"]
references: []
content:
JavaScript plugins are blocks of code that can be registered with Datasette using the  registerPlugin()  method on the  datasetteManager  object. 
             The  implementation  object passed to this method should include a  version  key defining the plugin version, and one or more of the following named functions providing the implementation of the plugin:


id: javascript_plugins:javascript-plugins-makeabovetablepanelconfigs
page: javascript_plugins
ref: javascript-plugins-makeabovetablepanelconfigs
title: makeAboveTablePanelConfigs()
breadcrumbs: ["JavaScript plugins", "JavaScript plugin objects"]
references: []
content:
This method should return a JavaScript array of objects defining additional panels to be added to the top of the table page. Each object should have the following: 
                 
                     
                         id  - string 
                         
                             A unique string ID for the panel, for example  map-panel 
                         
                     
                     
                         label  - string 
                         
                             A human-readable label for the panel 
                         
                     
                     
                         render(node)  - function 
                         
                             A function that will be called with a DOM node to render the panel into 
                         
                     
                 
                 This example shows how a plugin might define a single panel: 
                 document.addEventListener('datasette_init', function(ev) {
  ev.detail.registerPlugin('panel-plugin', {
    version: 0.1,
    makeAboveTablePanelConfigs: () => {
      return [
        {
          id: 'first-panel',
          label: 'First panel',
          render: node => {
            node.innerHTML = '<h2>My custom panel</h2><p>This is a custom panel that I added using a JavaScript plugin</p>';
          }
        }
      ]
    }
  });
}); 
                 When a page with a table loads, all registered plugins that implement  makeAboveTablePanelConfigs()  will be called and panels they return will be added to the top of the table page.


id: javascript_plugins:javascript-plugins-makecolumnactions
page: javascript_plugins
ref: javascript-plugins-makecolumnactions
title: makeColumnActions(columnDetails)
breadcrumbs: ["JavaScript plugins", "JavaScript plugin objects"]
references: []
content:
This method, if present, will be called when Datasette is rendering the cog action menu icons that appear at the top of the table view. By default these include options like "Sort ascending/descending" and "Facet by this", but plugins can return additional actions to be included in this menu. 
                 The method will be called with a  columnDetails  object with the following keys: 
                 
                     
                         columnName  - string 
                         
                             The name of the column 
                         
                     
                     
                         columnNotNull  - boolean 
                         
                             True if the column is defined as NOT NULL 
                         
                     
                     
                         columnType  - string 
                         
                             The SQLite data type of the column 
                         
                     
                     
                         isPk  - boolean 
                         
                             True if the column is part of the primary key 
                         
                     
                 
                 It should return a JavaScript array of objects each with a  label  and  onClick  property: 
                 
                     
                         label  - string 
                         
                             The human-readable label for the action 
                         
                     
                     
                         onClick(evt)  - function 
                         
                             A function that will be called when the action is clicked 
                         
                     
                 
                 The  evt  object passed to the  onClick  is the standard browser event object that triggered the click. 
                 This example plugin adds two menu items - one to copy the column name to the clipboard and another that displays the column metadata in an  alert()  window: 
                 document.addEventListener('datasette_init', function(ev) {
  ev.detail.registerPlugin('column-name-plugin', {
    version: 0.1,
    makeColumnActions: (columnDetails) => {
      return [
        {
          label: 'Copy column to clipboard',
          onClick: async (evt) => {
            await navigator.clipboard.writeText(columnDetails.columnName)
          }
        },
        {
          label: 'Alert column metadata',
          onClick: () => alert(JSON.stringify(columnDetails, null, 2))
        }
      ];
    }
  });
});


id: javascript_plugins:javascript-datasette-manager-selectors
page: javascript_plugins
ref: javascript-datasette-manager-selectors
title: Selectors
breadcrumbs: ["JavaScript plugins"]
references: []
content:
These are available on the  selectors  property of the  datasetteManager  object. 
             const DOM_SELECTORS = {
  /** Should have one match */
  jsonExportLink: ".export-links a[href*=json]",

  /** Event listeners that go outside of the main table, e.g. existing scroll listener */
  tableWrapper: ".table-wrapper",
  table: "table.rows-and-columns",
  aboveTablePanel: ".above-table-panel",

  // These could have multiple matches
  /** Used for selecting table headers. Use makeColumnActions if you want to add menu items. */
  tableHeaders: `table.rows-and-columns th`,

  /** Used to add "where"  clauses to query using direct manipulation */
  filterRows: ".filter-row",
  /** Used to show top available enum values for a column ("facets") */
  facetResults: ".facet-results [data-column]",
};


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 the  Datasette Discord .

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://datasette.io/discord", "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://datasette.io/discord", "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://datasette.io/discord", "label": "Datasette Discord"}]


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 Configuration Configuration via the command-line datasette.yaml  reference Settings Plugin configuration Permissions configuration Canned queries configuration Custom CSS and JavaScript 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 datasette create-token Pages and API endpoints Top-level index Database Hidden tables Queries 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 Default representation Different shapes Pagination Special JSON arguments Table arguments Column filter arguments Special table arguments Expanding foreign key references Discovering the JSON for a page Enabling CORS The JSON write API Inserting rows Upserting rows Updating a row Deleting a row Creating a table Creating a table from example data Dropping tables 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 How permissions are resolved Defining permissions with "allow" blocks The /-/allow-debug tool Access permissions in  datasette.yaml Access to an instance Access to specific databases Access to specific tables and views Access to specific canned queries Controlling the ability to execute arbitrary SQL Other permissions in  datasette.yaml API Tokens datasette create-token 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 insert-row delete-row update-row create-table alter-table drop-table 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 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 Metadata reference Top-level metadata Database-level metadata Table-level metadata Settings Using --setting Configuration directory mode Settings default_allow_sql default_page_size sql_time_limit_ms max_returned_rows max_insert_rows num_sql_threads allow_facet default_facet_size facet_time_limit_ms facet_suggest_time_limit_ms suggest_facets allow_download allow_signed_tokens max_signed_tokens_ttl 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 /-/config /-/databases /-/threads /-/actor /-/messages Custom pages and templates 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 Controlling which plugins are loaded Seeing what plugins are installed Plugin configuration Secret configuration values Writing plugins Tracing plugin hooks 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 Plugins that define new plugin hooks JavaScript plugins The datasette_init event datasetteManager JavaScript plugin objects makeAboveTablePanelConfigs() makeColumnActions(columnDetails) Selectors Plugin hooks prepare_connection(conn, database, datasette) prepare_jinja2_environment(env, datasette) Page extras extra_template_vars(template, database, table, columns, view_name, request, datasette) extra_css_urls(template, database, table, columns, view_name, request, datasette) extra_js_urls(template, database, table, columns, view_name, request, datasette) extra_body_script(template, database, table, columns, view_name, request, datasette) publish_subcommand(publish) render_cell(row, value, column, table, database, datasette, request) register_output_renderer(datasette) register_routes(datasette) register_commands(cli) register_facet_classes() register_permissions(datasette) asgi_wrapper(datasette) startup(datasette) canned_queries(datasette, database, actor) actor_from_request(datasette, request) actors_from_ids(datasette, actor_ids) jinja2_environment_from_request(datasette, request, env) filters_from_request(request, database, table, datasette) permission_allowed(datasette, actor, action, resource) register_magic_parameters(datasette) forbidden(datasette, request, message) handle_exception(datasette, request, exception) skip_csrf(datasette, scope) get_metadata(datasette, key, database, table) menu_links(datasette, actor, request) Action hooks table_actions(datasette, actor, database, table, request) view_actions(datasette, actor, database, view, request) query_actions(datasette, actor, database, query_name, request, sql, params) row_actions(datasette, actor, request, database, table, row) database_actions(datasette, actor, database, request) homepage_actions(datasette, actor, request) Template slots top_homepage(datasette, request) top_database(datasette, request, database) top_table(datasette, request, database, table) top_row(datasette, request, database, table, row) top_query(datasette, request, database, sql) top_canned_query(datasette, request, database, query_name) Event tracking track_event(datasette, event) register_events(datasette) Testing plugins Setting up a Datasette test instance Using datasette.client in tests 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 .permissions .plugin_config(plugin_name, database=None, table=None) await .render_template(template, context=None, request=None) await .actors_from_ids(actor_ids) await .permission_allowed(actor, action, resource=None, default=...) await .ensure_permissions(actor, permissions) await .check_visibility(actor, action=None, resource=None, permissions=None) .create_token(actor_id, expires_after=None, restrict_all=None, restrict_database=None, restrict_resource=None) .get_permission(name_or_abbr) .get_database(name) .get_internal_database() .add_database(db, name=None, route=None) .add_memory_database(name) .remove_database(name) await .track_event(event) .sign(value, namespace="default") .unsign(value, namespace="default") .add_message(request, message, type=datasette.INFO) .absolute_url(request, path) .setting(key) .resolve_database(request) .resolve_table(request) .resolve_row(request) 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, transaction=True) await db.execute_isolated_fn(fn) db.close() Database introspection CSRF protection Datasette's internal database The datasette.utils module parse_metadata(content) await_me_maybe(value) named_parameters(sql) Tilde encoding datasette.tracer Tracing child tasks Import shortcuts Events LoginEvent LogoutEvent CreateTokenEvent CreateTableEvent DropTableEvent AlterTableEvent InsertRowsEvent UpsertRowsEvent UpdateRowEvent DeleteRowEvent 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.64.8 (2023-06-21) 0.64.7 (2023-06-12) 1.0a13 (2024-03-12) 1.0a12 (2024-02-29) 1.0a11 (2024-02-19) 1.0a10 (2024-02-17) 1.0a9 (2024-02-16) Alter table support for create, insert, upsert and update Permissions fix for the upsert API Permission checks now consider opinions from every plugin Other changes 1.0a8 (2024-02-07) Configuration JavaScript plugins Plugin hooks Documentation Minor fixes 0.64.6 (2023-12-22) 0.64.5 (2023-10-08) 1.0a7 (2023-09-21) 0.64.4 (2023-09-21) 1.0a6 (2023-09-07) 1.0a5 (2023-08-29) 1.0a4 (2023-08-21) 1.0a3 (2023-08-09) Smaller changes 0.64.2 (2023-03-08) 0.64.1 (2023-01-11) 0.64 (2023-01-09) 0.63.3 (2022-12-17) 1.0a2 (2022-12-14) 1.0a1 (2022-12-01) 1.0a0 (2022-11-29) Signed API tokens Write API 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: 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: 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.


id: json_api:json-api-default
page: json_api
ref: json-api-default
title: Default representation
breadcrumbs: ["JSON API"]
references: []
content:
The default JSON representation of data from a SQLite table or custom query
                looks like this: 
             {
  "ok": true,
  "rows": [
    {
      "id": 3,
      "name": "Detroit"
    },
    {
      "id": 2,
      "name": "Los Angeles"
    },
    {
      "id": 4,
      "name": "Memnonia"
    },
    {
      "id": 1,
      "name": "San Francisco"
    }
  ],
  "truncated": false
} 
             "ok"  is always  true  if an error did not occur. 
             The  "rows"  key is a list of objects, each one representing a row. 
             The  "truncated"  key lets you know if the query was truncated. This can happen if a SQL query returns more than 1,000 results (or the  max_returned_rows  setting). 
             For table pages, an additional key  "next"  may be present. This indicates that the next page in the pagination set can be retrieved using  ?_next=VALUE .


id: json_api:json-api-shapes
page: json_api
ref: json-api-shapes
title: Different shapes
breadcrumbs: ["JSON API"]
references: []
content:
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=objects  -  "rows"  is a list of JSON key/value objects - the default 
                 
                 
                     ?_shape=arrays  -  "rows"  is a list of lists, where the order of values in each list matches the order of the columns 
                 
                 
                     ?_shape=array  - a JSON array of objects - effectively just the  "rows"  key from the default representation 
                 
                 
                     ?_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=arrays  looks like this: 
             {
  "ok": true,
  "next": null,
  "rows": [
    [3, "Detroit"],
    [2, "Los Angeles"],
    [4, "Memnonia"],
    [1, "San Francisco"]
  ]
} 
             _shape=array  looks like this: 
             [
  {
    "id": 3,
    "name": "Detroit"
  },
  {
    "id": 2,
    "name": "Los Angeles"
  },
  {
    "id": 4,
    "name": "Memnonia"
  },
  {
    "id": 1,
    "name": "San Francisco"
  }
] 
             _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__notcontains=value 
                         
                             Rows where the string column does not contain the specified value ( column not 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 . 
                         
                     
                     
                         ?_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: json_api:json-api-cors
page: json_api
ref: json-api-cors
title: Enabling CORS
breadcrumbs: ["JSON API"]
references: []
content:
If you start Datasette with the  --cors  option, each JSON endpoint will be
                served with the following additional HTTP headers: 
             [[[cog
from datasette.utils import add_cors_headers
import textwrap
headers = {}
add_cors_headers(headers)
output = "\n".join("{}: {}".format(k, v) for k, v in headers.items())
cog.out("\n::\n\n")
cog.out(textwrap.indent(output, '    '))
cog.out("\n\n") 
             ]]] 
             Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Expose-Headers: Link
Access-Control-Allow-Methods: GET, POST, HEAD, OPTIONS
Access-Control-Max-Age: 3600 
             [[[end]]] 
             This allows JavaScript running on any domain to make cross-origin
                requests to interact with the Datasette API. 
             If you start Datasette without the  --cors  option only JavaScript running on
                the same domain as Datasette will be able to access the API. 
             Here's how to serve  data.db  with CORS enabled: 
             datasette data.db --cors


id: json_api:json-api-write
page: json_api
ref: json-api-write
title: The JSON write API
breadcrumbs: ["JSON API"]
references: []
content:
Datasette provides a write API for JSON data. This is a POST-only API that requires an authenticated API token, see  API Tokens . The token will need to have the specified  Permissions .


id: json_api:tableinsertview
page: json_api
ref: tableinsertview
title: Inserting rows
breadcrumbs: ["JSON API", "The JSON write API"]
references: []
content:
This requires the  insert-row  permission. 
                 A single row can be inserted using the  "row"  key: 
                 POST /<database>/<table>/-/insert
Content-Type: application/json
Authorization: Bearer dstok_<rest-of-token> 
                 {
    "row": {
        "column1": "value1",
        "column2": "value2"
    }
} 
                 If successful, this will return a  201  status code and the newly inserted row, for example: 
                 {
    "rows": [
        {
            "id": 1,
            "column1": "value1",
            "column2": "value2"
        }
    ]
} 
                 To insert multiple rows at a time, use the same API method but send a list of dictionaries as the  "rows"  key: 
                 POST /<database>/<table>/-/insert
Content-Type: application/json
Authorization: Bearer dstok_<rest-of-token> 
                 {
    "rows": [
        {
            "column1": "value1",
            "column2": "value2"
        },
        {
            "column1": "value3",
            "column2": "value4"
        }
    ]
} 
                 If successful, this will return a  201  status code and a  {"ok": true}  response body. 
                 The maximum number rows that can be submitted at once defaults to 100, but this can be changed using the  max_insert_rows  setting. 
                 To return the newly inserted rows, add the  "return": true  key to the request body: 
                 {
    "rows": [
        {
            "column1": "value1",
            "column2": "value2"
        },
        {
            "column1": "value3",
            "column2": "value4"
        }
    ],
    "return": true
} 
                 This will return the same  "rows"  key as the single row example above. There is a small performance penalty for using this option. 
                 If any of your rows have a primary key that is already in use, you will get an error and none of the rows will be inserted: 
                 {
    "ok": false,
    "errors": [
        "UNIQUE constraint failed: new_table.id"
    ]
} 
                 Pass  "ignore": true  to ignore these errors and insert the other rows: 
                 {
    "rows": [
        {
            "id": 1,
            "column1": "value1",
            "column2": "value2"
        },
        {
            "id": 2,
            "column1": "value3",
            "column2": "value4"
        }
    ],
    "ignore": true
} 
                 Or you can pass  "replace": true  to replace any rows with conflicting primary keys with the new values. This requires the  update-row  permission. 
                 Pass  "alter: true  to automatically add any missing columns to the table. This requires the  alter-table  permission.


id: json_api:tableupsertview
page: json_api
ref: tableupsertview
title: Upserting rows
breadcrumbs: ["JSON API", "The JSON write API"]
references: []
content:
An upsert is an insert or update operation. If a row with a matching primary key already exists it will be updated - otherwise a new row will be inserted. 
                 The upsert API is mostly the same shape as the  insert API . It requires both the  insert-row  and  update-row  permissions. 
                 POST /<database>/<table>/-/upsert
Content-Type: application/json
Authorization: Bearer dstok_<rest-of-token> 
                 {
    "rows": [
        {
            "id": 1,
            "title": "Updated title for 1",
            "description": "Updated description for 1"
        },
        {
            "id": 2,
            "description": "Updated description for 2",
        },
        {
            "id": 3,
            "title": "Item 3",
            "description": "Description for 3"
        }
    ]
} 
                 Imagine a table with a primary key of  id  and which already has rows with  id  values of  1  and  2 . 
                 The above example will: 
                 
                     
                         Update the row with  id  of  1  to set both  title  and  description  to the new values 
                     
                     
                         Update the row with  id  of  2  to set  title  to the new value -  description  will be left unchanged 
                     
                     
                         Insert a new row with  id  of  3  and both  title  and  description  set to the new values 
                     
                 
                 Similar to  /-/insert , a  row  key with an object can be used instead of a  rows  array to upsert a single row. 
                 If successful, this will return a  200  status code and a  {"ok": true}  response body. 
                 Add  "return": true  to the request body to return full copies of the affected rows after they have been inserted or updated: 
                 {
    "rows": [
        {
            "id": 1,
            "title": "Updated title for 1",
            "description": "Updated description for 1"
        },
        {
            "id": 2,
            "description": "Updated description for 2",
        },
        {
            "id": 3,
            "title": "Item 3",
            "description": "Description for 3"
        }
    ],
    "return": true
} 
                 This will return the following: 
                 {
    "ok": true,
    "rows": [
        {
            "id": 1,
            "title": "Updated title for 1",
            "description": "Updated description for 1"
        },
        {
            "id": 2,
            "title": "Item 2",
            "description": "Updated description for 2"
        },
        {
            "id": 3,
            "title": "Item 3",
            "description": "Description for 3"
        }
    ]
} 
                 When using upsert you must provide the primary key column (or columns if the table has a compound primary key) for every row, or you will get a  400  error: 
                 {
    "ok": false,
    "errors": [
        "Row 0 is missing primary key column(s): \"id\""
    ]
} 
                 If your table does not have an explicit primary key you should pass the SQLite  rowid  key instead. 
                 Pass  "alter: true  to automatically add any missing columns to the table. This requires the  alter-table  permission.


id: json_api:rowupdateview
page: json_api
ref: rowupdateview
title: Updating a row
breadcrumbs: ["JSON API", "The JSON write API"]
references: []
content:
To update a row, make a  POST  to  /<database>/<table>/<row-pks>/-/update . This requires the  update-row  permission. 
                 POST /<database>/<table>/<row-pks>/-/update
Content-Type: application/json
Authorization: Bearer dstok_<rest-of-token> 
                 {
    "update": {
        "text_column": "New text string",
        "integer_column": 3,
        "float_column": 3.14
    }
} 
                 <row-pks>  here is the  tilde-encoded  primary key value of the row to update - or a comma-separated list of primary key values if the table has a composite primary key. 
                 You only need to pass the columns you want to update. Any other columns will be left unchanged. 
                 If successful, this will return a  200  status code and a  {"ok": true}  response body. 
                 Add  "return": true  to the request body to return the updated row: 
                 {
    "update": {
        "title": "New title"
    },
    "return": true
} 
                 The returned JSON will look like this: 
                 {
    "ok": true,
    "row": {
        "id": 1,
        "title": "New title",
        "other_column": "Will be present here too"
    }
} 
                 Any errors will return  {"errors": ["... descriptive message ..."], "ok": false} , and a  400  status code for a bad input or a  403  status code for an authentication or permission error. 
                 Pass  "alter: true  to automatically add any missing columns to the table. This requires the  alter-table  permission.


id: json_api:rowdeleteview
page: json_api
ref: rowdeleteview
title: Deleting a row
breadcrumbs: ["JSON API", "The JSON write API"]
references: []
content:
To delete a row, make a  POST  to  /<database>/<table>/<row-pks>/-/delete . This requires the  delete-row  permission. 
                 POST /<database>/<table>/<row-pks>/-/delete
Content-Type: application/json
Authorization: Bearer dstok_<rest-of-token> 
                 <row-pks>  here is the  tilde-encoded  primary key value of the row to delete - or a comma-separated list of primary key values if the table has a composite primary key. 
                 If successful, this will return a  200  status code and a  {"ok": true}  response body. 
                 Any errors will return  {"errors": ["... descriptive message ..."], "ok": false} , and a  400  status code for a bad input or a  403  status code for an authentication or permission error.


id: json_api:tablecreateview
page: json_api
ref: tablecreateview
title: Creating a table
breadcrumbs: ["JSON API", "The JSON write API"]
references: []
content:
To create a table, make a  POST  to  /<database>/-/create . This requires the  create-table  permission. 
                 POST /<database>/-/create
Content-Type: application/json
Authorization: Bearer dstok_<rest-of-token> 
                 {
    "table": "name_of_new_table",
    "columns": [
        {
            "name": "id",
            "type": "integer"
        },
        {
            "name": "title",
            "type": "text"
        }
    ],
    "pk": "id"
} 
                 The JSON here describes the table that will be created: 
                 
                     
                         table  is the name of the table to create. This field is required. 
                     
                     
                         columns  is a list of columns to create. Each column is a dictionary with  name  and  type  keys. 
                         
                             
                                 name  is the name of the column. This is required. 
                             
                             
                                 type  is the type of the column. This is optional - if not provided,  text  will be assumed. The valid types are  text ,  integer ,  float  and  blob . 
                             
                         
                     
                     
                         pk  is the primary key for the table. This is optional - if not provided, Datasette will create a SQLite table with a hidden  rowid  column. 
                         If the primary key is an integer column, it will be configured to automatically increment for each new record. 
                         If you set this to  id  without including an  id  column in the list of  columns , Datasette will create an auto-incrementing integer ID column for you. 
                     
                     
                         pks  can be used instead of  pk  to create a compound primary key. It should be a JSON list of column names to use in that primary key. 
                     
                     
                         ignore  can be set to  true  to ignore existing rows by primary key if the table already exists. 
                     
                     
                         replace  can be set to  true  to replace existing rows by primary key if the table already exists. This requires the  update-row  permission. 
                     
                     
                         alter  can be set to  true  if you want to automatically add any missing columns to the table. This requires the  alter-table  permission. 
                     
                 
                 If the table is successfully created this will return a  201  status code and the following response: 
                 {
    "ok": true,
    "database": "data",
    "table": "name_of_new_table",
    "table_url": "http://127.0.0.1:8001/data/name_of_new_table",
    "table_api_url": "http://127.0.0.1:8001/data/name_of_new_table.json",
    "schema": "CREATE TABLE [name_of_new_table] (\n   [id] INTEGER PRIMARY KEY,\n   [title] TEXT\n)"
}


id: json_api:tablecreateview-example
page: json_api
ref: tablecreateview-example
title: Creating a table from example data
breadcrumbs: ["JSON API", "The JSON write API"]
references: []
content:
Instead of specifying  columns  directly you can instead pass a single example  row  or a list of  rows .
                    Datasette will create a table with a schema that matches those rows and insert them for you: 
                 POST /<database>/-/create
Content-Type: application/json
Authorization: Bearer dstok_<rest-of-token> 
                 {
    "table": "creatures",
    "rows": [
        {
            "id": 1,
            "name": "Tarantula"
        },
        {
            "id": 2,
            "name": "Kākāpō"
        }
    ],
    "pk": "id"
} 
                 Doing this requires both the  create-table  and  insert-row  permissions. 
                 The  201  response here will be similar to the  columns  form, but will also include the number of rows that were inserted as  row_count : 
                 {
    "ok": true,
    "database": "data",
    "table": "creatures",
    "table_url": "http://127.0.0.1:8001/data/creatures",
    "table_api_url": "http://127.0.0.1:8001/data/creatures.json",
    "schema": "CREATE TABLE [creatures] (\n   [id] INTEGER PRIMARY KEY,\n   [name] TEXT\n)",
    "row_count": 2
} 
                 You can call the create endpoint multiple times for the same table provided you are specifying the table using the  rows  or  row  option. New rows will be inserted into the table each time. This means you can use this API if you are unsure if the relevant table has been created yet. 
                 If you pass a row to the create endpoint with a primary key that already exists you will get an error that looks like this: 
                 {
    "ok": false,
    "errors": [
        "UNIQUE constraint failed: creatures.id"
    ]
} 
                 You can avoid this error by passing the same  "ignore": true  or  "replace": true  options to the create endpoint as you can to the  insert endpoint . 
                 To use the  "replace": true  option you will also need the  update-row  permission. 
                 Pass  "alter": true  to automatically add any missing columns to the existing table that are present in the rows you are submitting. This requires the  alter-table  permission.


id: json_api:tabledropview
page: json_api
ref: tabledropview
title: Dropping tables
breadcrumbs: ["JSON API", "The JSON write API"]
references: []
content:
To drop a table, make a  POST  to  /<database>/<table>/-/drop . This requires the  drop-table  permission. 
                 POST /<database>/<table>/-/drop
Content-Type: application/json
Authorization: Bearer dstok_<rest-of-token> 
                 Without a POST body this will return a status  200  with a note about how many rows will be deleted: 
                 {
    "ok": true,
    "database": "<database>",
    "table": "<table>",
    "row_count": 5,
    "message": "Pass \"confirm\": true to confirm"
} 
                 If you pass the following POST body: 
                 {
    "confirm": true
} 
                 Then the table will be dropped and a status  200  response of  {"ok": true}  will be returned. 
                 Any errors will return  {"errors": ["... descriptive message ..."], "ok": false} , and a  400  status code for a bad input or a  403  status code for an authentication or permission error.


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: changelog:id2
page: changelog
ref: id2
title: 0.64.8 (2023-06-21)
breadcrumbs: ["Changelog"]
content:
Security improvement: 404 pages used to reflect content from the URL path, which could be used to display misleading information to Datasette users. 404 errors no longer display additional information from the URL. ( #2359 ) 
                 
                 
                     Backported a better fix for correctly extracting named parameters from canned query SQL against SQLite 3.46.0. ( #2353 )

references:
[{"href": "https://github.com/simonw/datasette/issues/2359", "label": "#2359"}, {"href": "https://github.com/simonw/datasette/issues/2353", "label": "#2353"}]


id: changelog:id3
page: changelog
ref: id3
title: 0.64.7 (2023-06-12)
breadcrumbs: ["Changelog"]
references: [{"href": "https://github.com/simonw/datasette/issues/2353", "label": "#2353"}]
content:
Fixed a bug where canned queries with named parameters threw an error when run against SQLite 3.46.0. ( #2353 )


id: changelog:v1-0-a13
page: changelog
ref: v1-0-a13
title: 1.0a13 (2024-03-12)
breadcrumbs: ["Changelog"]
content:
Each of the key concepts in Datasette now has an  actions menu , which plugins can use to add additional functionality targeting that entity. 
             
                 
                     Plugin hook:  view_actions()  for actions that can be applied to a SQL view. ( #2297 ) 
                 
                 
                     Plugin hook:  homepage_actions()  for actions that apply to the instance homepage. ( #2298 ) 
                 
                 
                     Plugin hook:  row_actions()  for actions that apply to the row page. ( #2299 ) 
                 
                 
                     Action menu items for all of the  *_actions()  plugin hooks can now return an optional  "description"  key, which will be displayed in the menu below the action label. ( #2294 ) 
                 
                 
                     Plugin hooks  documentation page is now organized with additional headings. ( #2300 ) 
                 
                 
                     Improved the display of action buttons on pages that also display metadata. ( #2286 ) 
                 
                 
                     The header and footer of the page now uses a subtle gradient effect, and options in the navigation menu are better visually defined. ( #2302 ) 
                 
                 
                     Table names that start with an underscore now default to hidden. ( #2104 ) 
                 
                 
                     pragma_table_list  has been added to the allow-list of SQLite pragma functions supported by Datasette.  select * from pragma_table_list()  is no longer blocked. ( #2104 )

references:
[{"href": "https://github.com/simonw/datasette/issues/2297", "label": "#2297"}, {"href": "https://github.com/simonw/datasette/issues/2298", "label": "#2298"}, {"href": "https://github.com/simonw/datasette/issues/2299", "label": "#2299"}, {"href": "https://github.com/simonw/datasette/issues/2294", "label": "#2294"}, {"href": "https://github.com/simonw/datasette/issues/2300", "label": "#2300"}, {"href": "https://github.com/simonw/datasette/issues/2286", "label": "#2286"}, {"href": "https://github.com/simonw/datasette/issues/2302", "label": "#2302"}, {"href": "https://github.com/simonw/datasette/issues/2104", "label": "#2104"}, {"href": "https://github.com/simonw/datasette/issues/2104#issuecomment-1982352475", "label": "#2104"}]


id: changelog:v1-0-a12
page: changelog
ref: v1-0-a12
title: 1.0a12 (2024-02-29)
breadcrumbs: ["Changelog"]
content:
New  query_actions()  plugin hook, similar to  table_actions()  and  database_actions() . Can be used to add a menu of actions to the canned query or arbitrary SQL query page. ( #2283 ) 
                 
                 
                     New design for the button that opens the query, table and database actions menu. ( #2281 ) 
                 
                 
                     "does not contain" table filter for finding rows that do not contain a string. ( #2287 ) 
                 
                 
                     Fixed a bug in the  makeColumnActions(columnDetails)  JavaScript plugin mechanism where the column action menu was not fully reset in between each interaction. ( #2289 )

references:
[{"href": "https://github.com/simonw/datasette/issues/2283", "label": "#2283"}, {"href": "https://github.com/simonw/datasette/issues/2281", "label": "#2281"}, {"href": "https://github.com/simonw/datasette/issues/2287", "label": "#2287"}, {"href": "https://github.com/simonw/datasette/issues/2289", "label": "#2289"}]


id: changelog:v1-0-a11
page: changelog
ref: v1-0-a11
title: 1.0a11 (2024-02-19)
breadcrumbs: ["Changelog"]
content:
The  "replace": true  argument to the  /db/table/-/insert  API now requires the actor to have the  update-row  permission. ( #2279 ) 
                 
                 
                     Fixed some UI bugs in the interactive permissions debugging tool. ( #2278 ) 
                 
                 
                     The column action menu now aligns better with the cog icon, and positions itself taking into account the width of the browser window. ( #2263 )

references:
[{"href": "https://github.com/simonw/datasette/issues/2279", "label": "#2279"}, {"href": "https://github.com/simonw/datasette/issues/2278", "label": "#2278"}, {"href": "https://github.com/simonw/datasette/issues/2263", "label": "#2263"}]


id: changelog:v1-0-a10
page: changelog
ref: v1-0-a10
title: 1.0a10 (2024-02-17)
breadcrumbs: ["Changelog"]
references: [{"href": "https://github.com/simonw/datasette/issues/2277", "label": "#2277"}]
content:
The only changes in this alpha correspond to the way Datasette handles database transactions. ( #2277 ) 
             
                 
                     The  database.execute_write_fn()  method has a new  transaction=True  parameter. This defaults to  True  which means all functions executed using this method are now automatically wrapped in a transaction - previously the functions needed to roll transaction handling on their own, and many did not. 
                 
                 
                     Pass  transaction=False  to  execute_write_fn()  if you want to manually handle transactions in your function. 
                 
                 
                     Several internal Datasette features, including parts of the  JSON write API , had been failing to wrap their operations in a transaction. This has been fixed by the new  transaction=True  default.


id: changelog:v1-0-a9
page: changelog
ref: v1-0-a9
title: 1.0a9 (2024-02-16)
breadcrumbs: ["Changelog"]
references: []
content:
This alpha release adds basic alter table support to the Datasette Write API and fixes a permissions bug relating to the  /upsert  API endpoint.


id: changelog:alter-table-support-for-create-insert-upsert-and-update
page: changelog
ref: alter-table-support-for-create-insert-upsert-and-update
title: Alter table support for create, insert, upsert and update
breadcrumbs: ["Changelog", "1.0a9 (2024-02-16)"]
references: [{"href": "https://github.com/simonw/datasette/issues/2101", "label": "#2101"}]
content:
The  JSON write API  can now be used to apply simple alter table schema changes, provided the acting actor has the new  alter-table  permission. ( #2101 ) 
                 The only alter operation supported so far is adding new columns to an existing table. 
                 
                     
                         The  /db/-/create  API now adds new columns during large operations to create a table based on incoming example  "rows" , in the case where one of the later rows includes columns that were not present in the earlier batches. This requires the  create-table  but not the  alter-table  permission. 
                     
                     
                         When  /db/-/create  is called with rows in a situation where the table may have been already created, an  "alter": true  key can be included to indicate that any missing columns from the new rows should be added to the table. This requires the  alter-table  permission. 
                     
                     
                         /db/table/-/insert  and  /db/table/-/upsert  and  /db/table/row-pks/-/update  all now also accept  "alter": true , depending on the  alter-table  permission. 
                     
                 
                 Operations that alter a table now fire the new  alter-table event .


id: changelog:permissions-fix-for-the-upsert-api
page: changelog
ref: permissions-fix-for-the-upsert-api
title: Permissions fix for the upsert API
breadcrumbs: ["Changelog", "1.0a9 (2024-02-16)"]
references: [{"href": "https://github.com/simonw/datasette/issues/2262", "label": "#2262"}]
content:
The  /database/table/-/upsert API  had a minor permissions bug, only affecting Datasette instances that had configured the  insert-row  and  update-row  permissions to apply to a specific table rather than the database or instance as a whole. Full details in issue  #2262 . 
                 To avoid similar mistakes in the future the  datasette.permission_allowed()  method now specifies  default=  as a keyword-only argument.


id: changelog:permission-checks-now-consider-opinions-from-every-plugin
page: changelog
ref: permission-checks-now-consider-opinions-from-every-plugin
title: Permission checks now consider opinions from every plugin
breadcrumbs: ["Changelog", "1.0a9 (2024-02-16)"]
references: [{"href": "https://github.com/simonw/datasette/issues/2275", "label": "#2275"}]
content:
The  datasette.permission_allowed()  method previously consulted every plugin that implemented the  permission_allowed()  plugin hook and obeyed the opinion of the last plugin to return a value. ( #2275 ) 
                 Datasette now consults every plugin and checks to see if any of them returned  False  (the veto rule), and if none of them did, it then checks to see if any of them returned  True . 
                 This is explained at length in the new documentation covering  How permissions are resolved .


id: changelog:other-changes
page: changelog
ref: other-changes
title: Other changes
breadcrumbs: ["Changelog", "1.0a9 (2024-02-16)"]
content:
The new  DATASETTE_TRACE_PLUGINS=1 environment variable  turns on detailed trace output for every executed plugin hook, useful for debugging and understanding how the plugin system works at a low level. ( #2274 ) 
                     
                     
                         Datasette on Python 3.9 or above marks its non-cryptographic uses of the MD5 hash function as  usedforsecurity=False , for compatibility with FIPS systems. ( #2270 ) 
                     
                     
                         SQL relating to  Datasette's internal database  now executes inside a transaction, avoiding a potential database locked error. ( #2273 ) 
                     
                     
                         The  /-/threads  debug page now identifies the database in the name associated with each dedicated write thread. ( #2265 ) 
                     
                     
                         The  /db/-/create  API now fires a  insert-rows  event if rows were inserted after the table was created. ( #2260 )

references:
[{"href": "https://github.com/simonw/datasette/issues/2274", "label": "#2274"}, {"href": "https://github.com/simonw/datasette/issues/2270", "label": "#2270"}, {"href": "https://github.com/simonw/datasette/issues/2273", "label": "#2273"}, {"href": "https://github.com/simonw/datasette/issues/2265", "label": "#2265"}, {"href": "https://github.com/simonw/datasette/issues/2260", "label": "#2260"}]


id: changelog:v1-0-a8
page: changelog
ref: v1-0-a8
title: 1.0a8 (2024-02-07)
breadcrumbs: ["Changelog"]
content:
This alpha release continues the migration of Datasette's configuration from  metadata.yaml  to the new  datasette.yaml  configuration file, introduces a new system for JavaScript plugins and adds several new plugin hooks. 
             See  Datasette 1.0a8: JavaScript plugins, new plugin hooks and plugin configuration in datasette.yaml  for an annotated version of these release notes.

references:
[{"href": "https://simonwillison.net/2024/Feb/7/datasette-1a8/", "label": "Datasette 1.0a8: JavaScript plugins, new plugin hooks and plugin configuration in datasette.yaml"}]


id: changelog:configuration
page: changelog
ref: configuration
title: Configuration
breadcrumbs: ["Changelog", "1.0a8 (2024-02-07)"]
content:
Plugin configuration now lives in the  datasette.yaml configuration file , passed to Datasette using the  -c/--config  option. Thanks, Alex Garcia. ( #2093 ) 
                         datasette -c datasette.yaml 
                         Where  datasette.yaml  contains configuration that looks like this: 
                         plugins:
  datasette-cluster-map:
    latitude_column: xlat
    longitude_column: xlon 
                         Previously plugins were configured in  metadata.yaml , which was confusing as plugin settings were unrelated to database and table metadata. 
                     
                     
                         The  -s/--setting  option can now be used to set plugin configuration as well. See  Configuration via the command-line  for details. ( #2252 ) 
                         The above YAML configuration example using  -s/--setting  looks like this: 
                         datasette mydatabase.db \
  -s plugins.datasette-cluster-map.latitude_column xlat \
  -s plugins.datasette-cluster-map.longitude_column xlon 
                     
                     
                         The new  /-/config  page shows the current instance configuration, after redacting keys that could contain sensitive data such as API keys or passwords. ( #2254 ) 
                     
                     
                         Existing Datasette installations may already have configuration set in  metadata.yaml  that should be migrated to  datasette.yaml . To avoid breaking these installations, Datasette will silently treat table configuration, plugin configuration and allow blocks in metadata as if they had been specified in configuration instead. ( #2247 ) ( #2248 ) ( #2249 ) 
                     
                 
                 Note that the  datasette publish  command has not yet been updated to accept a  datasette.yaml  configuration file. This will be addressed in  #2195  but for the moment you can include those settings in  metadata.yaml  instead.

references:
[{"href": "https://github.com/simonw/datasette/issues/2093", "label": "#2093"}, {"href": "https://github.com/simonw/datasette/issues/2252", "label": "#2252"}, {"href": "https://github.com/simonw/datasette/issues/2254", "label": "#2254"}, {"href": "https://github.com/simonw/datasette/issues/2247", "label": "#2247"}, {"href": "https://github.com/simonw/datasette/issues/2248", "label": "#2248"}, {"href": "https://github.com/simonw/datasette/issues/2249", "label": "#2249"}, {"href": "https://github.com/simonw/datasette/issues/2195", "label": "#2195"}]


id: changelog:javascript-plugins
page: changelog
ref: javascript-plugins
title: JavaScript plugins
breadcrumbs: ["Changelog", "1.0a8 (2024-02-07)"]
content:
Datasette now includes a  JavaScript plugins mechanism , allowing JavaScript to customize Datasette in a way that can collaborate with other plugins. 
                 This provides two initial hooks, with more to come in the future: 
                 
                     
                         makeAboveTablePanelConfigs()  can add additional panels to the top of the table page. 
                     
                     
                         makeColumnActions()  can add additional actions to the column menu. 
                     
                 
                 Thanks  Cameron Yick  for contributing this feature. ( #2052 )

references:
[{"href": "https://github.com/hydrosquall", "label": "Cameron Yick"}, {"href": "https://github.com/simonw/datasette/pull/2052", "label": "#2052"}]


id: changelog:plugin-hooks
page: changelog
ref: plugin-hooks
title: Plugin hooks
breadcrumbs: ["Changelog", "1.0a8 (2024-02-07)"]
content:
New  jinja2_environment_from_request(datasette, request, env)  plugin hook, which can be used to customize the current Jinja environment based on the incoming request. This can be used to modify the template lookup path based on the incoming request hostname, among other things. ( #2225 ) 
                     
                     
                         New  family of template slot plugin hooks :  top_homepage ,  top_database ,  top_table ,  top_row ,  top_query ,  top_canned_query . Plugins can use these to provide additional HTML to be injected at the top of the corresponding pages. ( #1191 ) 
                     
                     
                         
                             
                                 New  track_event() mechanism  for plugins to emit and receive events when certain events occur within Datasette. ( #2240 ) 
                                 
                                     
                                         
                                             Plugins can register additional event classes using  register_events(datasette) . 
                                         
                                         
                                             They can then trigger those events with the  datasette.track_event(event)  internal method. 
                                         
                                         
                                             Plugins can subscribe to notifications of events using the  track_event(datasette, event)  plugin hook. 
                                         
                                         
                                             Datasette core now emits  login ,  logout ,  create-token ,  create-table ,  drop-table ,  insert-rows ,  upsert-rows ,  update-row ,  delete-row  events,  documented here . 
                                         
                                     
                                 
                             
                         
                     
                     
                         New internal function for plugin authors:  await db.execute_isolated_fn(fn) , for creating a new SQLite connection, executing code and then closing that connection, all while preventing other code from writing to that particular database. This connection will not have the  prepare_connection()  plugin hook executed against it, allowing plugins to perform actions that might otherwise be blocked by existing connection configuration. ( #2218 )

references:
[{"href": "https://github.com/simonw/datasette/issues/2225", "label": "#2225"}, {"href": "https://github.com/simonw/datasette/issues/1191", "label": "#1191"}, {"href": "https://github.com/simonw/datasette/issues/2240", "label": "#2240"}, {"href": "https://github.com/simonw/datasette/issues/2218", "label": "#2218"}]


id: changelog:documentation
page: changelog
ref: documentation
title: Documentation
breadcrumbs: ["Changelog", "1.0a8 (2024-02-07)"]
content:
Documentation describing  how to write tests that use signed actor cookies  using  datasette.client.actor_cookie() . ( #1830 ) 
                     
                     
                         Documentation on how to  register a plugin for the duration of a test . ( #2234 ) 
                     
                     
                         The  configuration documentation  now shows examples of both YAML and JSON for each setting.

references:
[{"href": "https://github.com/simonw/datasette/issues/1830", "label": "#1830"}, {"href": "https://github.com/simonw/datasette/issues/2234", "label": "#2234"}]


id: changelog:minor-fixes
page: changelog
ref: minor-fixes
title: Minor fixes
breadcrumbs: ["Changelog", "1.0a8 (2024-02-07)"]
content:
Datasette no longer attempts to run SQL queries in parallel when rendering a table page, as this was leading to some rare crashing bugs. ( #2189 ) 
                     
                     
                         Fixed warning:  DeprecationWarning: pkg_resources is deprecated as an API  ( #2057 ) 
                     
                     
                         Fixed bug where  ?_extra=columns  parameter returned an incorrectly shaped response. ( #2230 )

references:
[{"href": "https://github.com/simonw/datasette/issues/2189", "label": "#2189"}, {"href": "https://github.com/simonw/datasette/issues/2057", "label": "#2057"}, {"href": "https://github.com/simonw/datasette/issues/2230", "label": "#2230"}]


id: changelog:id4
page: changelog
ref: id4
title: 0.64.6 (2023-12-22)
breadcrumbs: ["Changelog"]
references: [{"href": "https://github.com/simonw/datasette/issues/2214", "label": "#2214"}]
content:
Fixed a bug where CSV export with expanded labels could fail if a foreign key reference did not correctly resolve. ( #2214 )


id: changelog:id5
page: changelog
ref: id5
title: 0.64.5 (2023-10-08)
breadcrumbs: ["Changelog"]
references: [{"href": "https://github.com/simonw/datasette/issues/2197", "label": "#2197"}]
content:
Dropped dependency on  click-default-group-wheel , which could cause a dependency conflict. ( #2197 )


id: changelog:v1-0-a6
page: changelog
ref: v1-0-a6
title: 1.0a6 (2023-09-07)
breadcrumbs: ["Changelog"]
content:
New plugin hook:  actors_from_ids(datasette, actor_ids)  and an internal method to accompany it,  await .actors_from_ids(actor_ids) . This mechanism is intended to be used by plugins that may need to display the actor who was responsible for something managed by that plugin: they can now resolve the recorded IDs of actors into the full actor objects. ( #2181 ) 
                 
                 
                     DATASETTE_LOAD_PLUGINS  environment variable for  controlling which plugins  are loaded by Datasette. ( #2164 ) 
                 
                 
                     Datasette now checks if the user has permission to view a table linked to by a foreign key before turning that foreign key into a clickable link. ( #2178 ) 
                 
                 
                     The  execute-sql  permission now implies that the actor can also view the database and instance. ( #2169 ) 
                 
                 
                     Documentation describing a pattern for building plugins that themselves  define further hooks  for other plugins. ( #1765 ) 
                 
                 
                     Datasette is now tested against the Python 3.12 preview. ( #2175 )

references:
[{"href": "https://github.com/simonw/datasette/issues/2181", "label": "#2181"}, {"href": "https://github.com/simonw/datasette/issues/2164", "label": "#2164"}, {"href": "https://github.com/simonw/datasette/issues/2178", "label": "#2178"}, {"href": "https://github.com/simonw/datasette/issues/2169", "label": "#2169"}, {"href": "https://github.com/simonw/datasette/issues/1765", "label": "#1765"}, {"href": "https://github.com/simonw/datasette/pull/2175", "label": "#2175"}]


id: changelog:v1-0-a5
page: changelog
ref: v1-0-a5
title: 1.0a5 (2023-08-29)
breadcrumbs: ["Changelog"]
content:
When restrictions are applied to  API tokens , those restrictions now behave slightly differently: applying the  view-table  restriction will imply the ability to  view-database  for the database containing that table, and both  view-table  and  view-database  will imply  view-instance . Previously you needed to create a token with restrictions that explicitly listed  view-instance  and  view-database  and  view-table  in order to view a table without getting a permission denied error. ( #2102 ) 
                 
                 
                     New  datasette.yaml  (or  .json ) configuration file, which can be specified using  datasette -c path-to-file . The goal here to consolidate settings, plugin configuration, permissions, canned queries, and other Datasette configuration into a single single file, separate from  metadata.yaml . The legacy  settings.json  config file used for  Configuration directory mode  has been removed, and  datasette.yaml  has a  "settings"  section where the same settings key/value pairs can be included. In the next future alpha release, more configuration such as plugins/permissions/canned queries will be moved to the  datasette.yaml  file. See  #2093  for more details. Thanks, Alex Garcia. 
                 
                 
                     The  -s/--setting  option can now take dotted paths to nested settings. These will then be used to set or over-ride the same options as are present in the new configuration file. ( #2156 ) 
                 
                 
                     New  --actor '{"id": "json-goes-here"}'  option for use with  datasette --get  to treat the simulated request as being made by a specific actor, see  datasette --get . ( #2153 ) 
                 
                 
                     The Datasette  _internal  database has had some changes. It no longer shows up in the  datasette.databases  list by default, and is now instead available to plugins using the  datasette.get_internal_database() . Plugins are invited to use this as a private database to store configuration and settings and secrets that should not be made visible through the default Datasette interface. Users can pass the new   --internal internal.db  option to persist that internal database to disk. Thanks, Alex Garcia. ( #2157 ).

references:
[{"href": "https://github.com/simonw/datasette/issues/2102", "label": "#2102"}, {"href": "https://github.com/simonw/datasette/issues/2093", "label": "#2093"}, {"href": "https://github.com/simonw/datasette/issues/2156", "label": "#2156"}, {"href": "https://github.com/simonw/datasette/issues/2153", "label": "#2153"}, {"href": "https://github.com/simonw/datasette/issues/2157", "label": "#2157"}]


id: changelog:v1-0-a4
page: changelog
ref: v1-0-a4
title: 1.0a4 (2023-08-21)
breadcrumbs: ["Changelog"]
content:
This alpha fixes a security issue with the  /-/api  API explorer. On authenticated Datasette instances (instances protected using plugins such as  datasette-auth-passwords ) the API explorer interface could reveal the names of databases and tables within the protected instance. The data stored in those tables was not revealed. 
             For more information and workarounds, read  the security advisory . The issue has been present in every previous alpha version of Datasette 1.0: versions 1.0a0, 1.0a1, 1.0a2 and 1.0a3. 
             Also in this alpha: 
             
                 
                     The new  datasette plugins --requirements  option outputs a list of currently installed plugins in Python  requirements.txt  format, useful for duplicating that installation elsewhere. ( #2133 ) 
                 
                 
                     Writable canned queries  can now define a  on_success_message_sql  field in their configuration, containing a SQL query that should be executed upon successful completion of the write operation in order to generate a message to be shown to the user. ( #2138 ) 
                 
                 
                     The automatically generated border color for a database is now shown in more places around the application. ( #2119 ) 
                 
                 
                     Every instance of example shell script code in the documentation should now include a working copy button, free from additional syntax. ( #2140 )

references:
[{"href": "https://datasette.io/plugins/datasette-auth-passwords", "label": "datasette-auth-passwords"}, {"href": "https://github.com/simonw/datasette/security/advisories/GHSA-7ch3-7pp7-7cpq", "label": "the security advisory"}, {"href": "https://github.com/simonw/datasette/issues/2133", "label": "#2133"}, {"href": "https://github.com/simonw/datasette/issues/2138", "label": "#2138"}, {"href": "https://github.com/simonw/datasette/issues/2119", "label": "#2119"}, {"href": "https://github.com/simonw/datasette/issues/2140", "label": "#2140"}]


id: changelog:v1-0-a3
page: changelog
ref: v1-0-a3
title: 1.0a3 (2023-08-09)
breadcrumbs: ["Changelog"]
content:
This alpha release previews the updated design for Datasette's default JSON API. ( #782 ) 
             The new  default JSON representation  for both table pages ( /dbname/table.json ) and arbitrary SQL queries ( /dbname.json?sql=... ) is now shaped like this: 
             {
  "ok": true,
  "rows": [
    {
      "id": 3,
      "name": "Detroit"
    },
    {
      "id": 2,
      "name": "Los Angeles"
    },
    {
      "id": 4,
      "name": "Memnonia"
    },
    {
      "id": 1,
      "name": "San Francisco"
    }
  ],
  "truncated": false
} 
             Tables will include an additional  "next"  key for pagination, which can be passed to  ?_next=  to fetch the next page of results. 
             The various  ?_shape=  options continue to work as before - see  Different shapes  for details. 
             A new  ?_extra=  mechanism is available for tables, but has not yet been stabilized or documented. Details on that are available in  #262 .

references:
[{"href": "https://github.com/simonw/datasette/issues/782", "label": "#782"}, {"href": "https://github.com/simonw/datasette/issues/262", "label": "#262"}]


id: changelog:smaller-changes
page: changelog
ref: smaller-changes
title: Smaller changes
breadcrumbs: ["Changelog", "1.0a3 (2023-08-09)"]
content:
Datasette documentation now shows YAML examples for  Metadata  by default, with a tab interface for switching to JSON. ( #1153 ) 
                     
                     
                         register_output_renderer(datasette)  plugins now have access to  error  and  truncated  arguments, allowing them to display error messages and take into account truncated results. ( #2130 ) 
                     
                     
                         render_cell()  plugin hook now also supports an optional  request  argument. ( #2007 ) 
                     
                     
                         New  Justfile  to support development workflows for Datasette using  Just . 
                     
                     
                         datasette.render_template()  can now accepts a  datasette.views.Context  subclass as an alternative to a dictionary. ( #2127 ) 
                     
                     
                         datasette install -e path  option for editable installations, useful while developing plugins. ( #2106 ) 
                     
                     
                         When started with the  --cors  option Datasette now serves an  Access-Control-Max-Age: 3600  header, ensuring CORS OPTIONS requests are repeated no more than once an hour. ( #2079 ) 
                     
                     
                         Fixed a bug where the  _internal  database could display  None  instead of  null  for in-memory databases. ( #1970 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1153", "label": "#1153"}, {"href": "https://github.com/simonw/datasette/issues/2130", "label": "#2130"}, {"href": "https://github.com/simonw/datasette/issues/2007", "label": "#2007"}, {"href": "https://github.com/casey/just", "label": "Just"}, {"href": "https://github.com/simonw/datasette/issues/2127", "label": "#2127"}, {"href": "https://github.com/simonw/datasette/issues/2106", "label": "#2106"}, {"href": "https://github.com/simonw/datasette/issues/2079", "label": "#2079"}, {"href": "https://github.com/simonw/datasette/issues/1970", "label": "#1970"}]


id: changelog:id7
page: changelog
ref: id7
title: 0.64.2 (2023-03-08)
breadcrumbs: ["Changelog"]
references: [{"href": "https://github.com/simonw/datasette/issues/2036", "label": "#2036"}]
content:
Fixed a bug with  datasette publish cloudrun  where deploys all used the same Docker image tag. This was mostly inconsequential as the service is deployed as soon as the image has been pushed to the registry, but could result in the incorrect image being deployed if two different deploys for two separate services ran at exactly the same time. ( #2036 )


id: changelog:id8
page: changelog
ref: id8
title: 0.64.1 (2023-01-11)
breadcrumbs: ["Changelog"]
content:
Documentation now links to a current source of information for installing Python 3. ( #1987 ) 
                 
                 
                     Incorrectly calling the Datasette constructor using  Datasette("path/to/data.db")  instead of  Datasette(["path/to/data.db"])  now returns a useful error message. ( #1985 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1987", "label": "#1987"}, {"href": "https://github.com/simonw/datasette/issues/1985", "label": "#1985"}]


id: changelog:id9
page: changelog
ref: id9
title: 0.64 (2023-01-09)
breadcrumbs: ["Changelog"]
content:
Datasette now  strongly recommends against allowing arbitrary SQL queries if you are using SpatiaLite . SpatiaLite includes SQL functions that could cause the Datasette server to crash. See  SpatiaLite  for more details. 
                 
                 
                     New  default_allow_sql  setting, providing an easier way to disable all arbitrary SQL execution by end users:  datasette --setting default_allow_sql off . See also  Controlling the ability to execute arbitrary SQL . ( #1409 ) 
                 
                 
                     Building a location to time zone API with SpatiaLite  is a new Datasette tutorial showing how to safely use SpatiaLite to create a location to time zone API. 
                 
                 
                     New documentation about  how to debug problems loading SQLite extensions . The error message shown when an extension cannot be loaded has also been improved. ( #1979 ) 
                 
                 
                     Fixed an accessibility issue: the  <select>  elements in the table filter form now show an outline when they are currently focused. ( #1771 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1409", "label": "#1409"}, {"href": "https://datasette.io/tutorials/spatialite", "label": "Building a location to time zone API with SpatiaLite"}, {"href": "https://github.com/simonw/datasette/issues/1979", "label": "#1979"}, {"href": "https://github.com/simonw/datasette/issues/1771", "label": "#1771"}]


id: changelog:id10
page: changelog
ref: id10
title: 0.63.3 (2022-12-17)
breadcrumbs: ["Changelog"]
content:
Fixed a bug where  datasette --root , when running in Docker, would only output the URL to sign in root when the server shut down, not when it started up. ( #1958 ) 
                 
                 
                     You no longer need to ensure  await datasette.invoke_startup()  has been called in order for Datasette to start correctly serving requests - this is now handled automatically the first time the server receives a request. This fixes a bug experienced when Datasette is served directly by an ASGI application server such as Uvicorn or Gunicorn. It also fixes a bug with the  datasette-gunicorn  plugin. ( #1955 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1958", "label": "#1958"}, {"href": "https://datasette.io/plugins/datasette-gunicorn", "label": "datasette-gunicorn"}, {"href": "https://github.com/simonw/datasette/issues/1955", "label": "#1955"}]


id: changelog:v1-0-a2
page: changelog
ref: v1-0-a2
title: 1.0a2 (2022-12-14)
breadcrumbs: ["Changelog"]
content:
The third Datasette 1.0 alpha release adds upsert support to the JSON API, plus the ability to specify finely grained permissions when creating an API token. 
             See  Datasette 1.0a2: Upserts and finely grained permissions  for an extended, annotated version of these release notes. 
             
                 
                     New  /db/table/-/upsert  API,  documented here . upsert is an update-or-insert: existing rows will have specified keys updated, but if no row matches the incoming primary key a brand new row will be inserted instead. ( #1878 ) 
                 
                 
                     New  register_permissions(datasette)  plugin hook. Plugins can now register named permissions, which will then be listed in various interfaces that show available permissions. ( #1940 ) 
                 
                 
                     The  /db/-/create  API for  creating a table  now accepts  "ignore": true  and  "replace": true  options when called with the  "rows"  property that creates a new table based on an example set of rows. This means the API can be called multiple times with different rows, setting rules for what should happen if a primary key collides with an existing row. ( #1927 ) 
                 
                 
                     Arbitrary permissions can now be configured at the instance, database and resource (table, SQL view or canned query) level in Datasette's  Metadata  JSON and YAML files. The new  "permissions"  key can be used to specify which actors should have which permissions. See  Other permissions in datasette.yaml  for details. ( #1636 ) 
                 
                 
                     The  /-/create-token  page can now be used to create API tokens which are restricted to just a subset of actions, including against specific databases or resources. See  API Tokens  for details. ( #1947 ) 
                 
                 
                     Likewise, the  datasette create-token  CLI command can now create tokens with  a subset of permissions . ( #1855 ) 
                 
                 
                     New  datasette.create_token() API method  for programmatically creating signed API tokens. ( #1951 ) 
                 
                 
                     /db/-/create  API now requires actor to have  insert-row  permission in order to use the  "row"  or  "rows"  properties. ( #1937 )

references:
[{"href": "https://simonwillison.net/2022/Dec/15/datasette-1a2/", "label": "Datasette 1.0a2: Upserts and finely grained permissions"}, {"href": "https://github.com/simonw/datasette/issues/1878", "label": "#1878"}, {"href": "https://github.com/simonw/datasette/issues/1940", "label": "#1940"}, {"href": "https://github.com/simonw/datasette/issues/1927", "label": "#1927"}, {"href": "https://github.com/simonw/datasette/issues/1636", "label": "#1636"}, {"href": "https://github.com/simonw/datasette/issues/1947", "label": "#1947"}, {"href": "https://github.com/simonw/datasette/issues/1855", "label": "#1855"}, {"href": "https://github.com/simonw/datasette/issues/1951", "label": "#1951"}, {"href": "https://github.com/simonw/datasette/issues/1937", "label": "#1937"}]


id: changelog:v1-0-a1
page: changelog
ref: v1-0-a1
title: 1.0a1 (2022-12-01)
breadcrumbs: ["Changelog"]
content:
Write APIs now serve correct CORS headers if Datasette is started in  --cors  mode. See the full list of  CORS headers  in the documentation. ( #1922 ) 
                 
                 
                     Fixed a bug where the  _memory  database could be written to even though writes were not persisted. ( #1917 ) 
                 
                 
                     The  https://latest.datasette.io/  demo instance now includes an  ephemeral  database which can be used to test Datasette's write APIs, using the new  datasette-ephemeral-tables  plugin to drop any created tables after five minutes. This database is only available if you sign in as the root user using the link on the homepage. ( #1915 ) 
                 
                 
                     Fixed a bug where hitting the write endpoints with a  GET  request returned a 500 error. It now returns a 405 (method not allowed) error instead. ( #1916 ) 
                 
                 
                     The list of endpoints in the API explorer now lists mutable databases first. ( #1918 ) 
                 
                 
                     The  "ignore": true  and  "replace": true  options for the insert API are  now documented . ( #1924 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1922", "label": "#1922"}, {"href": "https://github.com/simonw/datasette/issues/1917", "label": "#1917"}, {"href": "https://latest.datasette.io/", "label": "https://latest.datasette.io/"}, {"href": "https://datasette.io/plugins/datasette-ephemeral-tables", "label": "datasette-ephemeral-tables"}, {"href": "https://github.com/simonw/datasette/issues/1915", "label": "#1915"}, {"href": "https://github.com/simonw/datasette/issues/1916", "label": "#1916"}, {"href": "https://github.com/simonw/datasette/issues/1918", "label": "#1918"}, {"href": "https://github.com/simonw/datasette/issues/1924", "label": "#1924"}]


id: changelog:v1-0-a0
page: changelog
ref: v1-0-a0
title: 1.0a0 (2022-11-29)
breadcrumbs: ["Changelog"]
content:
This first alpha release of Datasette 1.0 introduces a brand new collection of APIs for writing to the database ( #1850 ), as well as a new API token mechanism baked into Datasette core. Previously, API tokens have only been supported by installing additional plugins. 
             This is very much a preview: expect many more backwards incompatible API changes prior to the full 1.0 release. 
             Feedback enthusiastically welcomed, either through  issue comments  or via the  Datasette Discord  community.

references:
[{"href": "https://github.com/simonw/datasette/issues/1850", "label": "#1850"}, {"href": "https://github.com/simonw/datasette/issues/1850", "label": "issue comments"}, {"href": "https://datasette.io/discord", "label": "Datasette Discord"}]


id: changelog:signed-api-tokens
page: changelog
ref: signed-api-tokens
title: Signed API tokens
breadcrumbs: ["Changelog", "1.0a0 (2022-11-29)"]
content:
New  /-/create-token  page allowing authenticated users to create signed API tokens that can act on their behalf, see  API Tokens . ( #1852 ) 
                     
                     
                         New  datasette create-token  command for creating tokens from the command line:  datasette create-token . 
                     
                     
                         New  allow_signed_tokens  setting which can be used to turn off signed token support. ( #1856 ) 
                     
                     
                         New  max_signed_tokens_ttl  setting for restricting the maximum allowed duration of a signed token. ( #1858 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1852", "label": "#1852"}, {"href": "https://github.com/simonw/datasette/issues/1856", "label": "#1856"}, {"href": "https://github.com/simonw/datasette/issues/1858", "label": "#1858"}]


id: changelog:write-api
page: changelog
ref: write-api
title: Write API
breadcrumbs: ["Changelog", "1.0a0 (2022-11-29)"]
content:
New API explorer at  /-/api  for trying out the API. ( #1871 ) 
                     
                     
                         /db/-/create  API for  Creating a table . ( #1882 ) 
                     
                     
                         /db/table/-/insert  API for  Inserting rows . ( #1851 ) 
                     
                     
                         /db/table/-/drop  API for  Dropping tables . ( #1874 ) 
                     
                     
                         /db/table/pk/-/update  API for  Updating a row . ( #1863 ) 
                     
                     
                         /db/table/pk/-/delete  API for  Deleting a row . ( #1864 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1871", "label": "#1871"}, {"href": "https://github.com/simonw/datasette/issues/1882", "label": "#1882"}, {"href": "https://github.com/simonw/datasette/issues/1851", "label": "#1851"}, {"href": "https://github.com/simonw/datasette/issues/1874", "label": "#1874"}, {"href": "https://github.com/simonw/datasette/issues/1863", "label": "#1863"}, {"href": "https://github.com/simonw/datasette/issues/1864", "label": "#1864"}]


id: changelog:id11
page: changelog
ref: id11
title: 0.63.2 (2022-11-18)
breadcrumbs: ["Changelog"]
references: [{"href": "https://github.com/simonw/datasette/issues/1905", "label": "#1905"}]
content:
Fixed a bug in  datasette publish heroku  where deployments failed due to an older version of Python being requested. ( #1905 ) 
                 
                 
                     New  datasette publish heroku --generate-dir <dir>  option for generating a Heroku deployment directory without deploying it.


id: changelog:id12
page: changelog
ref: id12
title: 0.63.1 (2022-11-10)
breadcrumbs: ["Changelog"]
content:
Fixed a bug where Datasette's table filter form would not redirect correctly when run behind a proxy using the  base_url  setting. ( #1883 ) 
                 
                 
                     SQL query is now shown wrapped in a  <textarea>  if a query exceeds a time limit. ( #1876 ) 
                 
                 
                     Fixed an intermittent "Too many open files" error while running the test suite. ( #1843 ) 
                 
                 
                     New  db.close()  internal method.

references:
[{"href": "https://github.com/simonw/datasette/issues/1883", "label": "#1883"}, {"href": "https://github.com/simonw/datasette/issues/1876", "label": "#1876"}, {"href": "https://github.com/simonw/datasette/issues/1843", "label": "#1843"}]


id: changelog:id13
page: changelog
ref: id13
title: 0.63 (2022-10-27)
breadcrumbs: ["Changelog"]
content:
See  Datasette 0.63: The annotated release notes  for more background on the changes in this release.

references:
[{"href": "https://simonwillison.net/2022/Oct/27/datasette-0-63/", "label": "Datasette 0.63: The annotated release notes"}]


id: changelog:features
page: changelog
ref: features
title: Features
breadcrumbs: ["Changelog", "0.63 (2022-10-27)"]
content:
Now tested against Python 3.11. Docker containers used by  datasette publish  and  datasette package  both now use that version of Python. ( #1853 ) 
                     
                     
                         --load-extension  option now supports entrypoints. Thanks, Alex Garcia. ( #1789 ) 
                     
                     
                         Facet size can now be set per-table with the new  facet_size  table metadata option. ( #1804 ) 
                     
                     
                         The  truncate_cells_html  setting now also affects long URLs in columns. ( #1805 ) 
                     
                     
                         The non-JavaScript SQL editor textarea now increases height to fit the SQL query. ( #1786 ) 
                     
                     
                         Facets are now displayed with better line-breaks in long values. Thanks, Daniel Rech. ( #1794 ) 
                     
                     
                         The  settings.json  file used in  Configuration directory mode  is now validated on startup. ( #1816 ) 
                     
                     
                         SQL queries can now include leading SQL comments, using  /* ... */  or  -- ...  syntax. Thanks,  Charles Nepote. ( #1860 ) 
                     
                     
                         SQL query is now re-displayed when terminated with a time limit error. ( #1819 ) 
                     
                     
                         The  inspect data  mechanism is now used to speed up server startup - thanks, Forest Gregg. ( #1834 ) 
                     
                     
                         In  Configuration directory mode  databases with filenames ending in  .sqlite  or  .sqlite3  are now automatically added to the Datasette instance. ( #1646 ) 
                     
                     
                         Breadcrumb navigation display now respects the current user's permissions. ( #1831 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1853", "label": "#1853"}, {"href": "https://github.com/simonw/datasette/pull/1789", "label": "#1789"}, {"href": "https://github.com/simonw/datasette/issues/1804", "label": "#1804"}, {"href": "https://github.com/simonw/datasette/issues/1805", "label": "#1805"}, {"href": "https://github.com/simonw/datasette/issues/1786", "label": "#1786"}, {"href": "https://github.com/simonw/datasette/pull/1794", "label": "#1794"}, {"href": "https://github.com/simonw/datasette/issues/1816", "label": "#1816"}, {"href": "https://github.com/simonw/datasette/issues/1860", "label": "#1860"}, {"href": "https://github.com/simonw/datasette/issues/1819", "label": "#1819"}, {"href": "https://github.com/simonw/datasette/issues/1834", "label": "#1834"}, {"href": "https://github.com/simonw/datasette/issues/1646", "label": "#1646"}, {"href": "https://github.com/simonw/datasette/issues/1831", "label": "#1831"}]


id: changelog:plugin-hooks-and-internals
page: changelog
ref: plugin-hooks-and-internals
title: Plugin hooks and internals
breadcrumbs: ["Changelog", "0.63 (2022-10-27)"]
content:
The  prepare_jinja2_environment(env, datasette)  plugin hook now accepts an optional  datasette  argument. Hook implementations can also now return an  async  function which will be awaited automatically. ( #1809 ) 
                     
                     
                         Database(is_mutable=)  now defaults to  True . ( #1808 ) 
                     
                     
                         The  datasette.check_visibility()  method now accepts an optional  permissions=  list, allowing it to take multiple permissions into account at once when deciding if something should be shown as public or private. This has been used to correctly display padlock icons in more places in the Datasette interface. ( #1829 ) 
                     
                     
                         Datasette no longer enforces upper bounds on its dependencies. ( #1800 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1809", "label": "#1809"}, {"href": "https://github.com/simonw/datasette/issues/1808", "label": "#1808"}, {"href": "https://github.com/simonw/datasette/issues/1829", "label": "#1829"}, {"href": "https://github.com/simonw/datasette/issues/1800", "label": "#1800"}]


id: changelog:id14
page: changelog
ref: id14
title: Documentation
breadcrumbs: ["Changelog", "0.63 (2022-10-27)"]
content:
New tutorial:  Cleaning data with sqlite-utils and Datasette . 
                     
                     
                         Screenshots in the documentation are now maintained using  shot-scraper , as described in  Automating screenshots for the Datasette documentation using shot-scraper . ( #1844 ) 
                     
                     
                         More detailed command descriptions on the  CLI reference  page. ( #1787 ) 
                     
                     
                         New documentation on  Running Datasette using OpenRC  - thanks, Adam Simpson. ( #1825 )

references:
[{"href": "https://datasette.io/tutorials/clean-data", "label": "Cleaning data with sqlite-utils and Datasette"}, {"href": "https://shot-scraper.datasette.io/", "label": "shot-scraper"}, {"href": "https://simonwillison.net/2022/Oct/14/automating-screenshots/", "label": "Automating screenshots for the Datasette documentation using shot-scraper"}, {"href": "https://github.com/simonw/datasette/issues/1844", "label": "#1844"}, {"href": "https://github.com/simonw/datasette/issues/1787", "label": "#1787"}, {"href": "https://github.com/simonw/datasette/pull/1825", "label": "#1825"}]


id: changelog:id15
page: changelog
ref: id15
title: 0.62 (2022-08-14)
breadcrumbs: ["Changelog"]
content:
Datasette can now run entirely in your browser using WebAssembly. Try out  Datasette Lite , take a look  at the code  or read more about it in  Datasette Lite: a server-side Python web application running in a browser . 
             Datasette now has a  Discord community  for questions and discussions about Datasette and its ecosystem of projects.

references:
[{"href": "https://lite.datasette.io/", "label": "Datasette Lite"}, {"href": "https://github.com/simonw/datasette-lite", "label": "at the code"}, {"href": "https://simonwillison.net/2022/May/4/datasette-lite/", "label": "Datasette Lite: a server-side Python web application running in a browser"}, {"href": "https://datasette.io/discord", "label": "Discord community"}]


id: changelog:id16
page: changelog
ref: id16
title: Features
breadcrumbs: ["Changelog", "0.62 (2022-08-14)"]
content:
Datasette is now compatible with  Pyodide .  This is the enabling technology behind  Datasette Lite . ( #1733 ) 
                     
                     
                         Database file downloads now implement conditional GET using ETags. ( #1739 ) 
                     
                     
                         HTML for facet results and suggested results has been extracted out into new templates  _facet_results.html  and  _suggested_facets.html . Thanks, M. Nasimul Haque. ( #1759 ) 
                     
                     
                         Datasette now runs some SQL queries in parallel. This has limited impact on performance, see  this research issue  for details. 
                     
                     
                         New  --nolock  option for ignoring file locks when opening read-only databases. ( #1744 ) 
                     
                     
                         Spaces in the database names in URLs are now encoded as  +  rather than  ~20 . ( #1701 ) 
                     
                     
                         <Binary: 2427344 bytes>  is now displayed as  <Binary: 2,427,344 bytes>  and is accompanied by tooltip showing "2.3MB". ( #1712 ) 
                     
                     
                         The base Docker image used by  datasette publish cloudrun ,  datasette package  and the  official Datasette image  has been upgraded to  3.10.6-slim-bullseye .  ( #1768 ) 
                     
                     
                         Canned writable queries against immutable databases now show a warning message. ( #1728 ) 
                     
                     
                         datasette publish cloudrun  has a new  --timeout  option which can be used to increase the time limit applied by the Google Cloud build environment. Thanks, Tim Sherratt. ( #1717 ) 
                     
                     
                         datasette publish cloudrun  has new  --min-instances  and  --max-instances  options. ( #1779 )

references:
[{"href": "https://pyodide.org/", "label": "Pyodide"}, {"href": "https://lite.datasette.io/", "label": "Datasette Lite"}, {"href": "https://github.com/simonw/datasette/issues/1733", "label": "#1733"}, {"href": "https://github.com/simonw/datasette/issues/1739", "label": "#1739"}, {"href": "https://github.com/simonw/datasette/pull/1759", "label": "#1759"}, {"href": "https://github.com/simonw/datasette/issues/1727", "label": "this research issue"}, {"href": "https://github.com/simonw/datasette/issues/1744", "label": "#1744"}, {"href": "https://github.com/simonw/datasette/issues/1701", "label": "#1701"}, {"href": "https://github.com/simonw/datasette/issues/1712", "label": "#1712"}, {"href": "https://hub.docker.com/datasetteproject/datasette", "label": "official Datasette image"}, {"href": "https://github.com/simonw/datasette/issues/1768", "label": "#1768"}, {"href": "https://github.com/simonw/datasette/issues/1728", "label": "#1728"}, {"href": "https://github.com/simonw/datasette/pull/1717", "label": "#1717"}, {"href": "https://github.com/simonw/datasette/issues/1779", "label": "#1779"}]


id: changelog:id17
page: changelog
ref: id17
title: Plugin hooks
breadcrumbs: ["Changelog", "0.62 (2022-08-14)"]
content:
New plugin hook:  handle_exception() , for custom handling of exceptions caught by Datasette. ( #1770 ) 
                     
                     
                         The  render_cell()  plugin hook is now also passed a  row  argument, representing the  sqlite3.Row  object that is being rendered. ( #1300 ) 
                     
                     
                         The  configuration directory  is now stored in  datasette.config_dir , making it available to plugins. Thanks, Chris Amico. ( #1766 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1770", "label": "#1770"}, {"href": "https://github.com/simonw/datasette/issues/1300", "label": "#1300"}, {"href": "https://github.com/simonw/datasette/pull/1766", "label": "#1766"}]


id: changelog:bug-fixes
page: changelog
ref: bug-fixes
title: Bug fixes
breadcrumbs: ["Changelog", "0.62 (2022-08-14)"]
content:
Don't show the facet option in the cog menu if faceting is not allowed. ( #1683 ) 
                     
                     
                         ?_sort  and  ?_sort_desc  now work if the column that is being sorted has been excluded from the query using  ?_col=  or  ?_nocol= . ( #1773 ) 
                     
                     
                         Fixed bug where  ?_sort_desc  was duplicated in the URL every time the Apply button was clicked. ( #1738 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1683", "label": "#1683"}, {"href": "https://github.com/simonw/datasette/issues/1773", "label": "#1773"}, {"href": "https://github.com/simonw/datasette/issues/1738", "label": "#1738"}]


id: changelog:id18
page: changelog
ref: id18
title: Documentation
breadcrumbs: ["Changelog", "0.62 (2022-08-14)"]
content:
Examples in the documentation now include a copy-to-clipboard button. ( #1748 ) 
                     
                     
                         Documentation now uses the  Furo  Sphinx theme. ( #1746 ) 
                     
                     
                         Code examples in the documentation are now all formatted using Black. ( #1718 ) 
                     
                     
                         Request.fake()  method is now documented, see  Request object . 
                     
                     
                         New documentation for plugin authors:  Registering a plugin for the duration of a test . ( #903 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1748", "label": "#1748"}, {"href": "https://github.com/pradyunsg/furo", "label": "Furo"}, {"href": "https://github.com/simonw/datasette/issues/1746", "label": "#1746"}, {"href": "https://github.com/simonw/datasette/issues/1718", "label": "#1718"}, {"href": "https://github.com/simonw/datasette/issues/903", "label": "#903"}]


id: changelog:id19
page: changelog
ref: id19
title: 0.61.1 (2022-03-23)
breadcrumbs: ["Changelog"]
content:
Fixed a bug where databases with a different route from their name (as used by the  datasette-hashed-urls plugin ) returned errors when executing custom SQL queries. ( #1682 )

references:
[{"href": "https://datasette.io/plugins/datasette-hashed-urls", "label": "datasette-hashed-urls plugin"}, {"href": "https://github.com/simonw/datasette/issues/1682", "label": "#1682"}]


id: changelog:id20
page: changelog
ref: id20
title: 0.61 (2022-03-23)
breadcrumbs: ["Changelog"]
content:
In preparation for Datasette 1.0, this release includes two potentially backwards-incompatible changes. Hashed URL mode has been moved to a separate plugin, and the way Datasette generates URLs to databases and tables with special characters in their name such as  /  and  .  has changed. 
             Datasette also now requires Python 3.7 or higher. 
             
                 
                     URLs within Datasette now use a different encoding scheme for tables or databases that include "special" characters outside of the range of  a-zA-Z0-9_- . This scheme is explained here:  Tilde encoding . ( #1657 ) 
                 
                 
                     Removed hashed URL mode from Datasette. The new  datasette-hashed-urls  plugin can be used to achieve the same result, see  datasette-hashed-urls  for details. ( #1661 ) 
                 
                 
                     Databases can now have a custom path within the Datasette instance that is independent of the database name, using the  db.route  property. ( #1668 ) 
                 
                 
                     Datasette is now covered by a  Code of Conduct . ( #1654 ) 
                 
                 
                     Python 3.6 is no longer supported. ( #1577 ) 
                 
                 
                     Tests now run against Python 3.11-dev. ( #1621 ) 
                 
                 
                     New  datasette.ensure_permissions(actor, permissions)  internal method for checking multiple permissions at once. ( #1675 ) 
                 
                 
                     New  datasette.check_visibility(actor, action, resource=None)  internal method for checking if a user can see a resource that would otherwise be invisible to unauthenticated users. ( #1678 ) 
                 
                 
                     Table and row HTML pages now include a  <link rel="alternate" type="application/json+datasette" href="...">  element and return a  Link: URL; rel="alternate"; type="application/json+datasette"  HTTP header pointing to the JSON version of those pages. ( #1533 ) 
                 
                 
                     Access-Control-Expose-Headers: Link  is now added to the CORS headers, allowing remote JavaScript to access that header. 
                 
                 
                     Canned queries are now shown at the top of the database page, directly below the SQL editor. Previously they were shown at the bottom, below the list of tables. ( #1612 ) 
                 
                 
                     Datasette now has a default favicon. ( #1603 ) 
                 
                 
                     sqlite_stat  tables are now hidden by default. ( #1587 ) 
                 
                 
                     SpatiaLite tables  data_licenses ,  KNN  and  KNN2  are now hidden by default. ( #1601 ) 
                 
                 
                     SQL query tracing mechanism now works for queries executed in  asyncio  sub-tasks, such as those created by  asyncio.gather() . ( #1576 ) 
                 
                 
                     datasette.tracer  mechanism is now documented. 
                 
                 
                     Common Datasette symbols can now be imported directly from the top-level  datasette  package, see  Import shortcuts . Those symbols are  Response ,  Forbidden ,  NotFound ,  hookimpl ,  actor_matches_allow . ( #957 ) 
                 
                 
                     /-/versions  page now returns additional details for libraries used by SpatiaLite. ( #1607 ) 
                 
                 
                     Documentation now links to the  Datasette Tutorials . 
                 
                 
                     Datasette will now also look for SpatiaLite in  /opt/homebrew  - thanks, Dan Peterson. ( #1649 ) 
                 
                 
                     Fixed bug where  custom pages  did not work on Windows. Thanks, Robert Christie. ( #1545 ) 
                 
                 
                     Fixed error caused when a table had a column named  n . ( #1228 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1657", "label": "#1657"}, {"href": "https://github.com/simonw/datasette/issues/1661", "label": "#1661"}, {"href": "https://github.com/simonw/datasette/issues/1668", "label": "#1668"}, {"href": "https://github.com/simonw/datasette/blob/main/CODE_OF_CONDUCT.md", "label": "Code of Conduct"}, {"href": "https://github.com/simonw/datasette/issues/1654", "label": "#1654"}, {"href": "https://github.com/simonw/datasette/issues/1577", "label": "#1577"}, {"href": "https://github.com/simonw/datasette/issues/1621", "label": "#1621"}, {"href": "https://github.com/simonw/datasette/issues/1675", "label": "#1675"}, {"href": "https://github.com/simonw/datasette/issues/1678", "label": "#1678"}, {"href": "https://github.com/simonw/datasette/issues/1533", "label": "#1533"}, {"href": "https://github.com/simonw/datasette/issues/1612", "label": "#1612"}, {"href": "https://github.com/simonw/datasette/issues/1603", "label": "#1603"}, {"href": "https://github.com/simonw/datasette/issues/1587", "label": "#1587"}, {"href": "https://github.com/simonw/datasette/issues/1601", "label": "#1601"}, {"href": "https://github.com/simonw/datasette/issues/1576", "label": "#1576"}, {"href": "https://github.com/simonw/datasette/issues/957", "label": "#957"}, {"href": "https://github.com/simonw/datasette/issues/1607", "label": "#1607"}, {"href": "https://datasette.io/tutorials", "label": "Datasette Tutorials"}, {"href": "https://github.com/simonw/datasette/pull/1649", "label": "#1649"}, {"href": "https://github.com/simonw/datasette/issues/1545", "label": "#1545"}, {"href": "https://github.com/simonw/datasette/issues/1228", "label": "#1228"}]


id: changelog:id21
page: changelog
ref: id21
title: 0.60.2 (2022-02-07)
breadcrumbs: ["Changelog"]
references: [{"href": "https://github.com/simonw/datasette/issues/1632", "label": "#1632"}]
content:
Fixed a bug where Datasette would open the same file twice with two different database names if you ran  datasette file.db file.db . ( #1632 )


id: changelog:id22
page: changelog
ref: id22
title: 0.60.1 (2022-01-20)
breadcrumbs: ["Changelog"]
references: [{"href": "https://github.com/simonw/datasette/issues/1609", "label": "#1609"}]
content:
Fixed a bug where installation on Python 3.6 stopped working due to a change to an underlying dependency. This release can now be installed on Python 3.6, but is the last release of Datasette that will support anything less than Python 3.7. ( #1609 )


id: changelog:plugins-and-internals
page: changelog
ref: plugins-and-internals
title: Plugins and internals
breadcrumbs: ["Changelog", "0.60 (2022-01-13)"]
content:
New plugin hook:  filters_from_request(request, database, table, datasette) , which runs on the table page and can be used to support new custom query string parameters that modify the SQL query. ( #473 ) 
                     
                     
                         Added two additional methods for writing to the database:  await db.execute_write_script(sql, block=True)  and  await db.execute_write_many(sql, params_seq, block=True) . ( #1570 ) 
                     
                     
                         The  db.execute_write()  internal method now defaults to blocking until the write operation has completed. Previously it defaulted to queuing the write and then continuing to run code while the write was in the queue. ( #1579 ) 
                     
                     
                         Database write connections now execute the  prepare_connection(conn, database, datasette)  plugin hook. ( #1564 ) 
                     
                     
                         The  Datasette()  constructor no longer requires the  files=  argument, and is now documented at  Datasette class . ( #1563 ) 
                     
                     
                         The tracing feature now traces write queries, not just read queries. ( #1568 ) 
                     
                     
                         The query string variables exposed by  request.args  will now include blank strings for arguments such as  foo  in  ?foo=&bar=1  rather than ignoring those parameters entirely. ( #1551 )

references:
[{"href": "https://github.com/simonw/datasette/issues/473", "label": "#473"}, {"href": "https://github.com/simonw/datasette/issues/1570", "label": "#1570"}, {"href": "https://github.com/simonw/datasette/issues/1579", "label": "#1579"}, {"href": "https://github.com/simonw/datasette/issues/1564", "label": "#1564"}, {"href": "https://github.com/simonw/datasette/issues/1563", "label": "#1563"}, {"href": "https://github.com/simonw/datasette/issues/1568", "label": "#1568"}, {"href": "https://github.com/simonw/datasette/issues/1551", "label": "#1551"}]


id: changelog:faceting
page: changelog
ref: faceting
title: Faceting
breadcrumbs: ["Changelog", "0.60 (2022-01-13)"]
content:
The number of unique values in a facet is now always displayed. Previously it was only displayed if the user specified  ?_facet_size=max . ( #1556 ) 
                     
                     
                         Facets of type  date  or  array  can now be configured in  metadata.json , see  Facets in metadata . Thanks, David Larlet. ( #1552 ) 
                     
                     
                         New  ?_nosuggest=1  parameter for table views, which disables facet suggestion. ( #1557 ) 
                     
                     
                         Fixed bug where  ?_facet_array=tags&_facet=tags  would only display one of the two selected facets. ( #625 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1556", "label": "#1556"}, {"href": "https://github.com/simonw/datasette/issues/1552", "label": "#1552"}, {"href": "https://github.com/simonw/datasette/issues/1557", "label": "#1557"}, {"href": "https://github.com/simonw/datasette/issues/625", "label": "#625"}]


id: changelog:other-small-fixes
page: changelog
ref: other-small-fixes
title: Other small fixes
breadcrumbs: ["Changelog", "0.60 (2022-01-13)"]
content:
Made several performance improvements to the database schema introspection code that runs when Datasette first starts up. ( #1555 ) 
                     
                     
                         Label columns detected for foreign keys are now case-insensitive, so  Name  or  TITLE  will be detected in the same way as  name  or  title . ( #1544 ) 
                     
                     
                         Upgraded Pluggy dependency to 1.0. ( #1575 ) 
                     
                     
                         Now using  Plausible analytics  for the Datasette documentation. 
                     
                     
                         explain query plan  is now allowed with varying amounts of whitespace in the query. ( #1588 ) 
                     
                     
                         New  CLI reference  page showing the output of  --help  for each of the  datasette  sub-commands. This lead to several small improvements to the help copy. ( #1594 ) 
                     
                     
                         Fixed bug where writable canned queries could not be used with custom templates.  ( #1547 ) 
                     
                     
                         Improved fix for a bug where columns with a underscore prefix could result in unnecessary hidden form fields. ( #1527 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1555", "label": "#1555"}, {"href": "https://github.com/simonw/datasette/issues/1544", "label": "#1544"}, {"href": "https://github.com/simonw/datasette/issues/1575", "label": "#1575"}, {"href": "https://plausible.io/", "label": "Plausible analytics"}, {"href": "https://github.com/simonw/datasette/issues/1588", "label": "#1588"}, {"href": "https://github.com/simonw/datasette/issues/1594", "label": "#1594"}, {"href": "https://github.com/simonw/datasette/issues/1547", "label": "#1547"}, {"href": "https://github.com/simonw/datasette/issues/1527", "label": "#1527"}]


id: changelog:id24
page: changelog
ref: id24
title: 0.59.4 (2021-11-29)
breadcrumbs: ["Changelog"]
content:
Fixed bug where columns with a leading underscore could not be removed from the interactive filters list. ( #1527 ) 
                 
                 
                     Fixed bug where columns with a leading underscore were not correctly linked to by the "Links from other tables" interface on the row page. ( #1525 ) 
                 
                 
                     Upgraded dependencies  aiofiles ,  black  and  janus .

references:
[{"href": "https://github.com/simonw/datasette/issues/1527", "label": "#1527"}, {"href": "https://github.com/simonw/datasette/issues/1525", "label": "#1525"}]


id: changelog:id25
page: changelog
ref: id25
title: 0.59.3 (2021-11-20)
breadcrumbs: ["Changelog"]
content:
Fixed numerous bugs when running Datasette  behind a proxy  with a prefix URL path using the  base_url  setting. A live demo of this mode is now available at  datasette-apache-proxy-demo.datasette.io/prefix/ . ( #1519 ,  #838 ) 
                 
                 
                     ?column__arraycontains=  and  ?column__arraynotcontains=  table parameters now also work against SQL views. ( #448 ) 
                 
                 
                     ?_facet_array=column  no longer returns incorrect counts if columns contain the same value more than once.

references:
[{"href": "https://datasette-apache-proxy-demo.datasette.io/prefix/", "label": "datasette-apache-proxy-demo.datasette.io/prefix/"}, {"href": "https://github.com/simonw/datasette/issues/1519", "label": "#1519"}, {"href": "https://github.com/simonw/datasette/issues/838", "label": "#838"}, {"href": "https://github.com/simonw/datasette/issues/448", "label": "#448"}]


id: changelog:id26
page: changelog
ref: id26
title: 0.59.2 (2021-11-13)
breadcrumbs: ["Changelog"]
content:
Column names with a leading underscore now work correctly when used as a facet. ( #1506 ) 
                 
                 
                     Applying  ?_nocol=  to a column no longer removes that column from the filtering interface. ( #1503 ) 
                 
                 
                     Official Datasette Docker container now uses Debian Bullseye as the base image. ( #1497 ) 
                 
                 
                     Datasette is four years old today! Here's the  original release announcement  from 2017.

references:
[{"href": "https://github.com/simonw/datasette/issues/1506", "label": "#1506"}, {"href": "https://github.com/simonw/datasette/issues/1503", "label": "#1503"}, {"href": "https://github.com/simonw/datasette/issues/1497", "label": "#1497"}, {"href": "https://simonwillison.net/2017/Nov/13/datasette/", "label": "original release announcement"}]


id: changelog:id27
page: changelog
ref: id27
title: 0.59.1 (2021-10-24)
breadcrumbs: ["Changelog"]
content:
Fix compatibility with Python 3.10. ( #1482 ) 
                 
                 
                     Documentation on how to use  Named parameters  with integer and floating point values. ( #1496 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1482", "label": "#1482"}, {"href": "https://github.com/simonw/datasette/issues/1496", "label": "#1496"}]


id: changelog:id28
page: changelog
ref: id28
title: 0.59 (2021-10-14)
breadcrumbs: ["Changelog"]
content:
Columns can now have associated metadata descriptions in  metadata.json , see  Column descriptions . ( #942 ) 
                 
                 
                     New  register_commands()  plugin hook allows plugins to register additional Datasette CLI commands, e.g.  datasette mycommand file.db . ( #1449 ) 
                 
                 
                     Adding  ?_facet_size=max  to a table page now shows the number of unique values in each facet. ( #1423 ) 
                 
                 
                     Upgraded dependency  httpx 0.20  - the undocumented  allow_redirects=  parameter to  datasette.client  is now  follow_redirects= , and defaults to  False  where it previously defaulted to  True . ( #1488 ) 
                 
                 
                     The  --cors  option now causes Datasette to return the  Access-Control-Allow-Headers: Authorization  header, in addition to  Access-Control-Allow-Origin: * . ( #1467 ) 
                 
                 
                     Code that figures out which named parameters a SQL query takes in order to display form fields for them is no longer confused by strings that contain colon characters. ( #1421 ) 
                 
                 
                     Renamed  --help-config  option to  --help-settings . ( #1431 ) 
                 
                 
                     datasette.databases  property is now a documented API. ( #1443 ) 
                 
                 
                     The  base.html  template now wraps everything other than the  <footer>  in a  <div class="not-footer">  element, to help with advanced CSS customization. ( #1446 ) 
                 
                 
                     The  render_cell()  plugin hook can now return an awaitable function. This means the hook can execute SQL queries. ( #1425 ) 
                 
                 
                     register_routes(datasette)  plugin hook now accepts an optional  datasette  argument. ( #1404 ) 
                 
                 
                     New  hide_sql  canned query option for defaulting to hiding the SQL query used by a canned query, see  Additional canned query options . ( #1422 ) 
                 
                 
                     New  --cpu  option for  datasette publish cloudrun . ( #1420 ) 
                 
                 
                     If  Rich  is installed in the same virtual environment as Datasette, it will be used to provide enhanced display of error tracebacks on the console. ( #1416 ) 
                 
                 
                     datasette.utils   parse_metadata(content)  function, used by the new  datasette-remote-metadata plugin , is now a documented API. ( #1405 ) 
                 
                 
                     Fixed bug where  ?_next=x&_sort=rowid  could throw an error. ( #1470 ) 
                 
                 
                     Column cog menu no longer shows the option to facet by a column that is already selected by the default facets in metadata. ( #1469 )

references:
[{"href": "https://github.com/simonw/datasette/issues/942", "label": "#942"}, {"href": "https://github.com/simonw/datasette/issues/1449", "label": "#1449"}, {"href": "https://github.com/simonw/datasette/issues/1423", "label": "#1423"}, {"href": "https://github.com/encode/httpx/releases/tag/0.20.0", "label": "httpx 0.20"}, {"href": "https://github.com/simonw/datasette/issues/1488", "label": "#1488"}, {"href": "https://github.com/simonw/datasette/pull/1467", "label": "#1467"}, {"href": "https://github.com/simonw/datasette/issues/1421", "label": "#1421"}, {"href": "https://github.com/simonw/datasette/issues/1431", "label": "#1431"}, {"href": "https://github.com/simonw/datasette/issues/1443", "label": "#1443"}, {"href": "https://github.com/simonw/datasette/issues/1446", "label": "#1446"}, {"href": "https://github.com/simonw/datasette/issues/1425", "label": "#1425"}, {"href": "https://github.com/simonw/datasette/issues/1404", "label": "#1404"}, {"href": "https://github.com/simonw/datasette/issues/1422", "label": "#1422"}, {"href": "https://github.com/simonw/datasette/issues/1420", "label": "#1420"}, {"href": "https://github.com/willmcgugan/rich", "label": "Rich"}, {"href": "https://github.com/simonw/datasette/issues/1416", "label": "#1416"}, {"href": "https://datasette.io/plugins/datasette-remote-metadata", "label": "datasette-remote-metadata plugin"}, {"href": "https://github.com/simonw/datasette/issues/1405", "label": "#1405"}, {"href": "https://github.com/simonw/datasette/issues/1470", "label": "#1470"}, {"href": "https://github.com/simonw/datasette/issues/1469", "label": "#1469"}]


id: changelog:id29
page: changelog
ref: id29
title: 0.58.1 (2021-07-16)
breadcrumbs: ["Changelog"]
references: [{"href": "https://github.com/simonw/datasette/issues/1231", "label": "#1231"}]
content:
Fix for an intermittent race condition caused by the  refresh_schemas()  internal function. ( #1231 )


id: changelog:id30
page: changelog
ref: id30
title: 0.58 (2021-07-14)
breadcrumbs: ["Changelog"]
content:
New  datasette --uds /tmp/datasette.sock  option for binding Datasette to a Unix domain socket, see  proxy documentation  ( #1388 ) 
                 
                 
                     "searchmode": "raw"  table metadata option for defaulting a table to executing SQLite full-text search syntax without first escaping it, see  Advanced SQLite search queries . ( #1389 ) 
                 
                 
                     New plugin hook:  get_metadata(datasette, key, database, table) , for returning custom metadata for an instance, database or table. Thanks, Brandon Roberts! ( #1384 ) 
                 
                 
                     New plugin hook:  skip_csrf(datasette, scope) , for opting out of CSRF protection based on the incoming request. ( #1377 ) 
                 
                 
                     The  menu_links() ,  table_actions()  and  database_actions()  plugin hooks all gained a new optional  request  argument providing access to the current request. ( #1371 ) 
                 
                 
                     Major performance improvement for Datasette faceting. ( #1394 ) 
                 
                 
                     Improved documentation for  Running Datasette behind a proxy  to recommend using  ProxyPreservehost On  with Apache. ( #1387 ) 
                 
                 
                     POST  requests to endpoints that do not support that HTTP verb now return a 405 error. 
                 
                 
                     db.path  can now be provided as a  pathlib.Path  object, useful when writing unit tests for plugins. Thanks, Chris Amico. ( #1365 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1388", "label": "#1388"}, {"href": "https://github.com/simonw/datasette/issues/1389", "label": "#1389"}, {"href": "https://github.com/simonw/datasette/issues/1384", "label": "#1384"}, {"href": "https://github.com/simonw/datasette/issues/1377", "label": "#1377"}, {"href": "https://github.com/simonw/datasette/issues/1371", "label": "#1371"}, {"href": "https://github.com/simonw/datasette/issues/1394", "label": "#1394"}, {"href": "https://github.com/simonw/datasette/issues/1387", "label": "#1387"}, {"href": "https://github.com/simonw/datasette/issues/1365", "label": "#1365"}]


id: changelog:id31
page: changelog
ref: id31
title: 0.57.1 (2021-06-08)
breadcrumbs: ["Changelog"]
content:
Fixed visual display glitch with global navigation menu. ( #1367 ) 
                 
                 
                     No longer truncates the list of table columns displayed on the  /database  page. ( #1364 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1367", "label": "#1367"}, {"href": "https://github.com/simonw/datasette/issues/1364", "label": "#1364"}]


id: changelog:id32
page: changelog
ref: id32
title: 0.57 (2021-06-05)
breadcrumbs: ["Changelog"]
content:
This release fixes a  reflected cross-site scripting  security hole with the  ?_trace=1  feature. You should upgrade to this version, or to Datasette 0.56.1, as soon as possible. ( #1360 ) 
             
             In addition to the security fix, this release includes  ?_col=  and  ?_nocol=  options for controlling which columns are displayed for a table,  ?_facet_size=  for increasing the number of facet results returned, re-display of your SQL query should an error occur and numerous bug fixes.

references:
[{"href": "https://owasp.org/www-community/attacks/xss/#reflected-xss-attacks", "label": "reflected cross-site scripting"}, {"href": "https://github.com/simonw/datasette/issues/1360", "label": "#1360"}]


id: changelog:new-features
page: changelog
ref: new-features
title: New features
breadcrumbs: ["Changelog", "0.57 (2021-06-05)"]
content:
If an error occurs while executing a user-provided SQL query, that query is now re-displayed in an editable form along with the error message. ( #619 ) 
                     
                     
                         New  ?_col=  and  ?_nocol=  parameters to show and hide columns in a table, plus an interface for hiding and showing columns in the column cog menu. ( #615 ) 
                     
                     
                         A new  ?_facet_size=  parameter for customizing the number of facet results returned on a table or view page. ( #1332 ) 
                     
                     
                         ?_facet_size=max  sets that to the maximum, which defaults to 1,000 and is controlled by the the  max_returned_rows  setting. If facet results are truncated the … at the bottom of the facet list now links to this parameter. ( #1337 ) 
                     
                     
                         ?_nofacet=1  option to disable all facet calculations on a page, used as a performance optimization for CSV exports and  ?_shape=array/object . ( #1349 ,  #263 ) 
                     
                     
                         ?_nocount=1  option to disable full query result counts. ( #1353 ) 
                     
                     
                         ?_trace=1  debugging option is now controlled by the new  trace_debug  setting, which is turned off by default. ( #1359 )

references:
[{"href": "https://github.com/simonw/datasette/issues/619", "label": "#619"}, {"href": "https://github.com/simonw/datasette/issues/615", "label": "#615"}, {"href": "https://github.com/simonw/datasette/issues/1332", "label": "#1332"}, {"href": "https://github.com/simonw/datasette/issues/1337", "label": "#1337"}, {"href": "https://github.com/simonw/datasette/issues/1349", "label": "#1349"}, {"href": "https://github.com/simonw/datasette/issues/263", "label": "#263"}, {"href": "https://github.com/simonw/datasette/issues/1353", "label": "#1353"}, {"href": "https://github.com/simonw/datasette/issues/1359", "label": "#1359"}]


id: changelog:bug-fixes-and-other-improvements
page: changelog
ref: bug-fixes-and-other-improvements
title: Bug fixes and other improvements
breadcrumbs: ["Changelog", "0.57 (2021-06-05)"]
content:
Custom pages  now work correctly when combined with the  base_url  setting. ( #1238 ) 
                     
                     
                         Fixed intermittent error displaying the index page when the user did not have permission to access one of the tables. Thanks, Guy Freeman. ( #1305 ) 
                     
                     
                         Columns with the name "Link" are no longer incorrectly displayed in bold. ( #1308 ) 
                     
                     
                         Fixed error caused by tables with a single quote in their names. ( #1257 ) 
                     
                     
                         Updated dependencies:  pytest-asyncio ,  Black ,  jinja2 ,  aiofiles ,  click , and  itsdangerous . 
                     
                     
                         The official Datasette Docker image now supports  apt-get install . ( #1320 ) 
                     
                     
                         The Heroku runtime used by  datasette publish heroku  is now  python-3.8.10 .

references:
[{"href": "https://github.com/simonw/datasette/issues/1238", "label": "#1238"}, {"href": "https://github.com/simonw/datasette/issues/1305", "label": "#1305"}, {"href": "https://github.com/simonw/datasette/issues/1308", "label": "#1308"}, {"href": "https://github.com/simonw/datasette/issues/1257", "label": "#1257"}, {"href": "https://github.com/simonw/datasette/issues/1320", "label": "#1320"}]


id: changelog:id33
page: changelog
ref: id33
title: 0.56.1 (2021-06-05)
breadcrumbs: ["Changelog"]
content:
This release fixes a  reflected cross-site scripting  security hole with the  ?_trace=1  feature. You should upgrade to this version, or to Datasette 0.57, as soon as possible. ( #1360 )

references:
[{"href": "https://owasp.org/www-community/attacks/xss/#reflected-xss-attacks", "label": "reflected cross-site scripting"}, {"href": "https://github.com/simonw/datasette/issues/1360", "label": "#1360"}]


id: changelog:id34
page: changelog
ref: id34
title: 0.56 (2021-03-28)
breadcrumbs: ["Changelog"]
content:
Documentation improvements, bug fixes and support for SpatiaLite 5. 
             
                 
                     The SQL editor can now be resized by dragging a handle. ( #1236 ) 
                 
                 
                     Fixed a bug with JSON faceting and the  __arraycontains  filter caused by tables with spaces in their names. ( #1239 ) 
                 
                 
                     Upgraded  httpx  dependency. ( #1005 ) 
                 
                 
                     JSON faceting is now suggested even if a column contains blank strings. ( #1246 ) 
                 
                 
                     New  datasette.add_memory_database()  method. ( #1247 ) 
                 
                 
                     The  Response.asgi_send()  method is now documented. ( #1266 ) 
                 
                 
                     The official Datasette Docker image now bundles SpatiaLite version 5. ( #1278 ) 
                 
                 
                     Fixed a  no such table: pragma_database_list  bug when running Datasette against SQLite versions prior to SQLite 3.16.0. ( #1276 ) 
                 
                 
                     HTML lists displayed in table cells are now styled correctly. Thanks, Bob Whitelock. ( #1141 ,  #1252 ) 
                 
                 
                     Configuration directory mode now correctly serves immutable databases that are listed in  inspect-data.json . Thanks Campbell Allen and Frankie Robertson. ( #1031 ,  #1229 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1236", "label": "#1236"}, {"href": "https://github.com/simonw/datasette/issues/1239", "label": "#1239"}, {"href": "https://github.com/simonw/datasette/issues/1005", "label": "#1005"}, {"href": "https://github.com/simonw/datasette/issues/1246", "label": "#1246"}, {"href": "https://github.com/simonw/datasette/issues/1247", "label": "#1247"}, {"href": "https://github.com/simonw/datasette/issues/1266", "label": "#1266"}, {"href": "https://github.com/simonw/datasette/issues/1278", "label": "#1278"}, {"href": "https://github.com/simonw/datasette/issues/1276", "label": "#1276"}, {"href": "https://github.com/simonw/datasette/issues/1141", "label": "#1141"}, {"href": "https://github.com/simonw/datasette/pull/1252", "label": "#1252"}, {"href": "https://github.com/simonw/datasette/pull/1031", "label": "#1031"}, {"href": "https://github.com/simonw/datasette/pull/1229", "label": "#1229"}]


id: changelog:id35
page: changelog
ref: id35
title: 0.55 (2021-02-18)
breadcrumbs: ["Changelog"]
content:
Support for cross-database SQL queries and built-in support for serving via HTTPS. 
             
                 
                     The new  --crossdb  command-line option causes Datasette to attach up to ten database files to the same  /_memory  database connection. This enables cross-database SQL queries, including the ability to use joins and unions to combine data from tables that exist in different database files. See  Cross-database queries  for details. ( #283 ) 
                 
                 
                     --ssl-keyfile  and  --ssl-certfile  options can be used to specify a TLS certificate, allowing Datasette to serve traffic over  https://  without needing to run it behind a separate proxy. ( #1221 ) 
                 
                 
                     The  /:memory:  page has been renamed (and redirected) to  /_memory  for consistency with the new  /_internal  database introduced in Datasette 0.54. ( #1205 ) 
                 
                 
                     Added plugin testing documentation on  Using pdb for errors thrown inside Datasette . ( #1207 ) 
                 
                 
                     The  official Datasette Docker image  now uses Python 3.7.10, applying  the latest security fix  for that Python version. ( #1235 )

references:
[{"href": "https://github.com/simonw/datasette/issues/283", "label": "#283"}, {"href": "https://github.com/simonw/datasette/issues/1221", "label": "#1221"}, {"href": "https://github.com/simonw/datasette/issues/1205", "label": "#1205"}, {"href": "https://github.com/simonw/datasette/issues/1207", "label": "#1207"}, {"href": "https://hub.docker.com/r/datasetteproject/datasette", "label": "official Datasette Docker image"}, {"href": "https://www.python.org/downloads/release/python-3710/", "label": "the latest security fix"}, {"href": "https://github.com/simonw/datasette/issues/1235", "label": "#1235"}]


id: changelog:id36
page: changelog
ref: id36
title: 0.54.1 (2021-02-02)
breadcrumbs: ["Changelog"]
references: [{"href": "https://github.com/simonw/datasette/issues/1214", "label": "#1214"}]
content:
Fixed a bug where  ?_search=  and  ?_sort=  parameters were incorrectly duplicated when the filter form on the table page was re-submitted. ( #1214 )


id: changelog:id37
page: changelog
ref: id37
title: 0.54 (2021-01-25)
breadcrumbs: ["Changelog"]
content:
The two big new features in this release are the  _internal  SQLite in-memory database storing details of all connected databases and tables, and support for JavaScript modules in plugins and additional scripts. 
             For additional commentary on this release, see  Datasette 0.54, the annotated release notes .

references:
[{"href": "https://simonwillison.net/2021/Jan/25/datasette/", "label": "Datasette 0.54, the annotated release notes"}]


id: changelog:the-internal-database
page: changelog
ref: the-internal-database
title: The _internal database
breadcrumbs: ["Changelog", "0.54 (2021-01-25)"]
content:
As part of ongoing work to help Datasette handle much larger numbers of connected databases and tables (see  Datasette Library ) Datasette now maintains an in-memory SQLite database with details of all of the attached databases, tables, columns, indexes and foreign keys. ( #1150 ) 
                 This will support future improvements such as a searchable, paginated homepage of all available tables. 
                 You can explore an example of this database by  signing in as root  to the  latest.datasette.io  demo instance and then navigating to  latest.datasette.io/_internal . 
                 Plugins can use these tables to introspect attached data in an efficient way. Plugin authors should note that this is not yet considered a stable interface, so any plugins that use this may need to make changes prior to Datasette 1.0 if the  _internal  table schemas change.

references:
[{"href": "https://github.com/simonw/datasette/issues/417", "label": "Datasette Library"}, {"href": "https://github.com/simonw/datasette/issues/1150", "label": "#1150"}, {"href": "https://latest.datasette.io/login-as-root", "label": "signing in as root"}, {"href": "https://latest.datasette.io/_internal", "label": "latest.datasette.io/_internal"}]


id: changelog:named-in-memory-database-support
page: changelog
ref: named-in-memory-database-support
title: Named in-memory database support
breadcrumbs: ["Changelog", "0.54 (2021-01-25)"]
references: [{"href": "https://github.com/simonw/datasette/issues/1151", "label": "#1151"}]
content:
As part of the work building the  _internal  database, Datasette now supports named in-memory databases that can be shared across multiple connections. This allows plugins to create in-memory databases which will persist data for the lifetime of the Datasette server process. ( #1151 ) 
                 The new  memory_name=  parameter to the  Database class  can be used to create named, shared in-memory databases.


id: changelog:javascript-modules
page: changelog
ref: javascript-modules
title: JavaScript modules
breadcrumbs: ["Changelog", "0.54 (2021-01-25)"]
content:
JavaScript modules  were introduced in ECMAScript 2015 and provide native browser support for the  import  and  export  keywords. 
                 To use modules, JavaScript needs to be included in  <script>  tags with a  type="module"  attribute. 
                 Datasette now has the ability to output  <script type="module">  in places where you may wish to take advantage of modules. The  extra_js_urls  option described in  Custom CSS and JavaScript  can now be used with modules, and module support is also available for the  extra_body_script()  plugin hook. ( #1186 ,  #1187 ) 
                 datasette-leaflet-freedraw  is the first example of a Datasette plugin that takes advantage of the new support for JavaScript modules. See  Drawing shapes on a map to query a SpatiaLite database  for more on this plugin.

references:
[{"href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules", "label": "JavaScript modules"}, {"href": "https://github.com/simonw/datasette/issues/1186", "label": "#1186"}, {"href": "https://github.com/simonw/datasette/issues/1187", "label": "#1187"}, {"href": "https://datasette.io/plugins/datasette-leaflet-freedraw", "label": "datasette-leaflet-freedraw"}, {"href": "https://simonwillison.net/2021/Jan/24/drawing-shapes-spatialite/", "label": "Drawing shapes on a map to query a SpatiaLite database"}]


id: changelog:code-formatting-with-black-and-prettier
page: changelog
ref: code-formatting-with-black-and-prettier
title: Code formatting with Black and Prettier
breadcrumbs: ["Changelog", "0.54 (2021-01-25)"]
content:
Datasette adopted  Black  for opinionated Python code formatting in June 2019. Datasette now also embraces  Prettier  for JavaScript formatting, which like Black is enforced by tests in continuous integration. Instructions for using these two tools can be found in the new section on  Code formatting  in the contributors documentation. ( #1167 )

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


id: changelog:id38
page: changelog
ref: id38
title: Other changes
breadcrumbs: ["Changelog", "0.54 (2021-01-25)"]
content:
Datasette can now open multiple database files with the same name, e.g. if you run  datasette path/to/one.db path/to/other/one.db . ( #509 ) 
                     
                     
                         datasette publish cloudrun  now sets  force_https_urls  for every deployment, fixing some incorrect  http://  links. ( #1178 ) 
                     
                     
                         Fixed a bug in the example nginx configuration in  Running Datasette behind a proxy . ( #1091 ) 
                     
                     
                         The  Datasette Ecosystem  documentation page has been reduced in size in favour of the  datasette.io   tools  and  plugins  directories. ( #1182 ) 
                     
                     
                         The request object now provides a  request.full_path  property, which returns the path including any query string. ( #1184 ) 
                     
                     
                         Better error message for disallowed  PRAGMA  clauses in SQL queries. ( #1185 ) 
                     
                     
                         datasette publish heroku  now deploys using  python-3.8.7 . 
                     
                     
                         New plugin testing documentation on  Testing outbound HTTP calls with pytest-httpx . ( #1198 ) 
                     
                     
                         All  ?_*  query string parameters passed to the table page are now persisted in hidden form fields, so parameters such as  ?_size=10  will be correctly passed to the next page when query filters are changed. ( #1194 ) 
                     
                     
                         Fixed a bug loading a database file called  test-database (1).sqlite . ( #1181 )

references:
[{"href": "https://github.com/simonw/datasette/issues/509", "label": "#509"}, {"href": "https://github.com/simonw/datasette/issues/1178", "label": "#1178"}, {"href": "https://github.com/simonw/datasette/issues/1091", "label": "#1091"}, {"href": "https://datasette.io/tools", "label": "tools"}, {"href": "https://datasette.io/plugins", "label": "plugins"}, {"href": "https://github.com/simonw/datasette/issues/1182", "label": "#1182"}, {"href": "https://github.com/simonw/datasette/issues/1184", "label": "#1184"}, {"href": "https://github.com/simonw/datasette/issues/1185", "label": "#1185"}, {"href": "https://github.com/simonw/datasette/issues/1198", "label": "#1198"}, {"href": "https://github.com/simonw/datasette/issues/1194", "label": "#1194"}, {"href": "https://github.com/simonw/datasette/issues/1181", "label": "#1181"}]


id: changelog:id39
page: changelog
ref: id39
title: 0.53 (2020-12-10)
breadcrumbs: ["Changelog"]
content:
Datasette has an official project website now, at  https://datasette.io/ . This release mainly updates the documentation to reflect the new site. 
             
                 
                     New  ?column__arraynotcontains=  table filter. ( #1132 ) 
                 
                 
                     datasette serve  has a new  --create  option, which will create blank database files if they do not already exist rather than exiting with an error. ( #1135 ) 
                 
                 
                     New  ?_header=off  option for CSV export which omits the CSV header row,  documented here . ( #1133 ) 
                 
                 
                     "Powered by Datasette" link in the footer now links to  https://datasette.io/ . ( #1138 ) 
                 
                 
                     Project news no longer lives in the README - it can now be found at  https://datasette.io/news . ( #1137 )

references:
[{"href": "https://datasette.io/", "label": "https://datasette.io/"}, {"href": "https://github.com/simonw/datasette/issues/1132", "label": "#1132"}, {"href": "https://github.com/simonw/datasette/issues/1135", "label": "#1135"}, {"href": "https://github.com/simonw/datasette/issues/1133", "label": "#1133"}, {"href": "https://datasette.io/", "label": "https://datasette.io/"}, {"href": "https://github.com/simonw/datasette/issues/1138", "label": "#1138"}, {"href": "https://datasette.io/news", "label": "https://datasette.io/news"}, {"href": "https://github.com/simonw/datasette/issues/1137", "label": "#1137"}]


id: changelog:id41
page: changelog
ref: id41
title: 0.52.4 (2020-12-05)
breadcrumbs: ["Changelog"]
content:
Show  pysqlite3  version on  /-/versions , if installed. ( #1125 ) 
                 
                 
                     Errors output by Datasette (e.g. for invalid SQL queries) now go to  stderr , not  stdout . ( #1131 ) 
                 
                 
                     Fix for a startup error on windows caused by unnecessary  from os import EX_CANTCREAT  - thanks, Abdussamet Koçak.  ( #1094 )

references:
[{"href": "https://github.com/coleifer/pysqlite3", "label": "pysqlite3"}, {"href": "https://github.com/simonw/datasette/issues/1125", "label": "#1125"}, {"href": "https://github.com/simonw/datasette/issues/1131", "label": "#1131"}, {"href": "https://github.com/simonw/datasette/issues/1094", "label": "#1094"}]


id: changelog:id43
page: changelog
ref: id43
title: 0.52.2 (2020-12-02)
breadcrumbs: ["Changelog"]
content:
Generated columns from SQLite 3.31.0 or higher are now correctly displayed. ( #1116 ) 
                 
                 
                     Error message if you attempt to open a SpatiaLite database now suggests using  --load-extension=spatialite  if it detects that the extension is available in a common location. ( #1115 ) 
                 
                 
                     OPTIONS  requests against the  /database  page no longer raise a 500 error. ( #1100 ) 
                 
                 
                     Databases larger than 32MB that are published to Cloud Run can now be downloaded. ( #749 ) 
                 
                 
                     Fix for misaligned cog icon on table and database pages. Thanks, Abdussamet Koçak. ( #1121 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1116", "label": "#1116"}, {"href": "https://github.com/simonw/datasette/issues/1115", "label": "#1115"}, {"href": "https://github.com/simonw/datasette/issues/1100", "label": "#1100"}, {"href": "https://github.com/simonw/datasette/issues/749", "label": "#749"}, {"href": "https://github.com/simonw/datasette/issues/1121", "label": "#1121"}]


id: changelog:id44
page: changelog
ref: id44
title: 0.52.1 (2020-11-29)
breadcrumbs: ["Changelog"]
content:
Documentation on  Testing plugins  now recommends using  datasette.client . ( #1102 ) 
                 
                 
                     Fix bug where compound foreign keys produced broken links. ( #1098 ) 
                 
                 
                     datasette --load-module=spatialite  now also checks for  /usr/local/lib/mod_spatialite.so . Thanks, Dan Peterson. ( #1114 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1102", "label": "#1102"}, {"href": "https://github.com/simonw/datasette/issues/1098", "label": "#1098"}, {"href": "https://github.com/simonw/datasette/issues/1114", "label": "#1114"}]


id: changelog:id45
page: changelog
ref: id45
title: 0.52 (2020-11-28)
breadcrumbs: ["Changelog"]
content:
This release includes a number of changes relating to an internal rebranding effort: Datasette's  configuration  mechanism (things like  datasette --config default_page_size:10 ) has been renamed to  settings . 
             
                 
                     New  --setting default_page_size 10  option as a replacement for  --config default_page_size:10  (note the lack of a colon). The  --config  option is deprecated but will continue working until Datasette 1.0. ( #992 ) 
                 
                 
                     The  /-/config  introspection page is now  /-/settings , and the previous page redirects to the new one. ( #1103 ) 
                 
                 
                     The  config.json  file in  Configuration directory mode  is now called  settings.json . ( #1104 ) 
                 
                 
                     The undocumented  datasette.config()  internal method has been replaced by a documented  .setting(key)  method. ( #1107 ) 
                 
             
             Also in this release: 
             
                 
                     New plugin hook:  database_actions(datasette, actor, database, request) , which adds menu items to a new cog menu shown at the top of the database page. ( #1077 ) 
                 
                 
                     datasette publish cloudrun  has a new  --apt-get-install  option that can be used to install additional Ubuntu packages as part of the deployment. This is useful for deploying the new  datasette-ripgrep plugin . ( #1110 ) 
                 
                 
                     Swept the documentation to remove words that minimize involved difficulty. ( #1089 ) 
                 
             
             And some bug fixes: 
             
                 
                     Foreign keys linking to rows with blank label columns now display as a hyphen, allowing those links to be clicked. ( #1086 ) 
                 
                 
                     Fixed bug where row pages could sometimes 500 if the underlying queries exceeded a time limit. ( #1088 ) 
                 
                 
                     Fixed a bug where the table action menu could appear partially obscured by the edge of the page. ( #1084 )

references:
[{"href": "https://github.com/simonw/datasette/issues/992", "label": "#992"}, {"href": "https://github.com/simonw/datasette/issues/1103", "label": "#1103"}, {"href": "https://github.com/simonw/datasette/issues/1104", "label": "#1104"}, {"href": "https://github.com/simonw/datasette/issues/1107", "label": "#1107"}, {"href": "https://github.com/simonw/datasette/issues/1077", "label": "#1077"}, {"href": "https://github.com/simonw/datasette-ripgrep", "label": "datasette-ripgrep plugin"}, {"href": "https://github.com/simonw/datasette/issues/1110", "label": "#1110"}, {"href": "https://github.com/simonw/datasette/issues/1089", "label": "#1089"}, {"href": "https://github.com/simonw/datasette/issues/1086", "label": "#1086"}, {"href": "https://github.com/simonw/datasette/issues/1088", "label": "#1088"}, {"href": "https://github.com/simonw/datasette/issues/1084", "label": "#1084"}]


id: changelog:id47
page: changelog
ref: id47
title: 0.51 (2020-10-31)
breadcrumbs: ["Changelog"]
references: []
content:
A new visual design, plugin hooks for adding navigation options, better handling of binary data, URL building utility methods and better support for running Datasette behind a proxy.


id: changelog:new-visual-design
page: changelog
ref: new-visual-design
title: New visual design
breadcrumbs: ["Changelog", "0.51 (2020-10-31)"]
content:
Datasette is no longer white and grey with blue and purple links!  Natalie Downe  has been working on a visual refresh, the first iteration of which is included in this release. ( #1056 )

references:
[{"href": "https://twitter.com/natbat", "label": "Natalie Downe"}, {"href": "https://github.com/simonw/datasette/pull/1056", "label": "#1056"}]


id: changelog:plugins-can-now-add-links-within-datasette
page: changelog
ref: plugins-can-now-add-links-within-datasette
title: Plugins can now add links within Datasette
breadcrumbs: ["Changelog", "0.51 (2020-10-31)"]
content:
A number of existing Datasette plugins add new pages to the Datasette interface, providig tools for things like  uploading CSVs ,  editing table schemas  or  configuring full-text search . 
                 Plugins like this can now link to themselves from other parts of Datasette interface. The  menu_links(datasette, actor, request)  hook ( #1064 ) lets plugins add links to Datasette's new top-right application menu, and the  table_actions(datasette, actor, database, table, request)  hook ( #1066 ) adds links to a new "table actions" menu on the table page. 
                 The demo at  latest.datasette.io  now includes some example plugins. To see the new table actions menu first  sign into that demo as root  and then visit the  facetable  table to see the new cog icon menu at the top of the page.

references:
[{"href": "https://github.com/simonw/datasette-upload-csvs", "label": "uploading CSVs"}, {"href": "https://github.com/simonw/datasette-edit-schema", "label": "editing table schemas"}, {"href": "https://github.com/simonw/datasette-configure-fts", "label": "configuring full-text search"}, {"href": "https://github.com/simonw/datasette/issues/1064", "label": "#1064"}, {"href": "https://github.com/simonw/datasette/issues/1066", "label": "#1066"}, {"href": "https://latest.datasette.io/", "label": "latest.datasette.io"}, {"href": "https://latest.datasette.io/login-as-root", "label": "sign into that demo as root"}, {"href": "https://latest.datasette.io/fixtures/facetable", "label": "facetable"}]


id: changelog:binary-data
page: changelog
ref: binary-data
title: Binary data
breadcrumbs: ["Changelog", "0.51 (2020-10-31)"]
content:
SQLite tables can contain binary data in  BLOB  columns. Datasette now provides links for users to download this data directly from Datasette, and uses those links to make binary data available from CSV exports. See  Binary data  for more details. ( #1036  and  #1034 ).

references:
[{"href": "https://github.com/simonw/datasette/issues/1036", "label": "#1036"}, {"href": "https://github.com/simonw/datasette/issues/1034", "label": "#1034"}]


id: changelog:url-building
page: changelog
ref: url-building
title: URL building
breadcrumbs: ["Changelog", "0.51 (2020-10-31)"]
references: [{"href": "https://github.com/simonw/datasette/issues/904", "label": "#904"}]
content:
The new  datasette.urls  family of methods can be used to generate URLs to key pages within the Datasette interface, both within custom templates and Datasette plugins. See  Building URLs within plugins  for more details. ( #904 )


id: changelog:running-datasette-behind-a-proxy
page: changelog
ref: running-datasette-behind-a-proxy
title: Running Datasette behind a proxy
breadcrumbs: ["Changelog", "0.51 (2020-10-31)"]
content:
The  base_url  configuration option is designed to help run Datasette on a specific path behind a proxy - for example if you want to run an instance of Datasette at  /my-datasette/  within your existing site's URL hierarchy, proxied behind nginx or Apache. 
                 Support for this configuration option has been greatly improved ( #1023 ), and guidelines for using it are now available in a new documentation section on  Running Datasette behind a proxy . ( #1027 )

references:
[{"href": "https://github.com/simonw/datasette/issues/1023", "label": "#1023"}, {"href": "https://github.com/simonw/datasette/issues/1027", "label": "#1027"}]


id: changelog:id48
page: changelog
ref: id48
title: Smaller changes
breadcrumbs: ["Changelog", "0.51 (2020-10-31)"]
content:
Wide tables shown within Datasette now scroll horizontally ( #998 ). This is achieved using a new  <div class="table-wrapper">  element which may impact the implementation of some plugins (for example  this change to datasette-cluster-map ). 
                     
                     
                         New  debug-menu  permission. ( #1068 ) 
                     
                     
                         Removed  --debug  option, which didn't do anything. ( #814 ) 
                     
                     
                         Link:  HTTP header pagination. ( #1014 ) 
                     
                     
                         x  button for clearing filters. ( #1016 ) 
                     
                     
                         Edit SQL button on canned queries, ( #1019 ) 
                     
                     
                         --load-extension=spatialite  shortcut. ( #1028 ) 
                     
                     
                         scale-in animation for column action menu. ( #1039 ) 
                     
                     
                         Option to pass a list of templates to  .render_template()  is now documented. ( #1045 ) 
                     
                     
                         New  datasette.urls.static_plugins()  method. ( #1033 ) 
                     
                     
                         datasette -o  option now opens the most relevant page. ( #976 ) 
                     
                     
                         datasette --cors  option now enables access to  /database.db  downloads. ( #1057 ) 
                     
                     
                         Database file downloads now implement cascading permissions, so you can download a database if you have  view-database-download  permission even if you do not have permission to access the Datasette instance. ( #1058 ) 
                     
                     
                         New documentation on  Designing URLs for your plugin . ( #1053 )

references:
[{"href": "https://github.com/simonw/datasette/issues/998", "label": "#998"}, {"href": "https://github.com/simonw/datasette-cluster-map/commit/fcb4abbe7df9071c5ab57defd39147de7145b34e", "label": "this change to datasette-cluster-map"}, {"href": "https://github.com/simonw/datasette/issues/1068", "label": "#1068"}, {"href": "https://github.com/simonw/datasette/issues/814", "label": "#814"}, {"href": "https://github.com/simonw/datasette/issues/1014", "label": "#1014"}, {"href": "https://github.com/simonw/datasette/issues/1016", "label": "#1016"}, {"href": "https://github.com/simonw/datasette/issues/1019", "label": "#1019"}, {"href": "https://github.com/simonw/datasette/issues/1028", "label": "#1028"}, {"href": "https://github.com/simonw/datasette/issues/1039", "label": "#1039"}, {"href": "https://github.com/simonw/datasette/issues/1045", "label": "#1045"}, {"href": "https://github.com/simonw/datasette/issues/1033", "label": "#1033"}, {"href": "https://github.com/simonw/datasette/issues/976", "label": "#976"}, {"href": "https://github.com/simonw/datasette/issues/1057", "label": "#1057"}, {"href": "https://github.com/simonw/datasette/issues/1058", "label": "#1058"}, {"href": "https://github.com/simonw/datasette/issues/1053", "label": "#1053"}]


id: changelog:id49
page: changelog
ref: id49
title: 0.50.2 (2020-10-09)
breadcrumbs: ["Changelog"]
references: [{"href": "https://github.com/simonw/datasette/issues/1011", "label": "#1011"}]
content:
Fixed another bug introduced in 0.50 where column header links on the table page were broken. ( #1011 )


id: changelog:id50
page: changelog
ref: id50
title: 0.50.1 (2020-10-09)
breadcrumbs: ["Changelog"]
references: [{"href": "https://github.com/simonw/datasette/issues/1010", "label": "#1010"}]
content:
Fixed a bug introduced in 0.50 where the export as JSON/CSV links on the table, row and query pages were broken. ( #1010 )


id: changelog:id51
page: changelog
ref: id51
title: 0.50 (2020-10-09)
breadcrumbs: ["Changelog"]
content:
The key new feature in this release is the  column actions  menu on the table page ( #891 ). This can be used to sort a column in ascending or descending order, facet data by that column or filter the table to just rows that have a value for that column. 
             Plugin authors can use the new  datasette.client  object to make internal HTTP requests from their plugins, allowing them to make use of Datasette's JSON API. ( #943 ) 
             New  Deploying Datasette  documentation with guides for deploying Datasette on a Linux server  using systemd  or to hosting providers  that support buildpacks . ( #514 ,  #997 ) 
             Other improvements in this release: 
             
                 
                     Publishing to Google Cloud Run  documentation now covers Google Cloud SDK options. Thanks, Geoffrey Hing. ( #995 ) 
                 
                 
                     New  datasette -o  option which opens your browser as soon as Datasette starts up. ( #970 ) 
                 
                 
                     Datasette now sets  sqlite3.enable_callback_tracebacks(True)  so that errors in custom SQL functions will display tracebacks. ( #891 ) 
                 
                 
                     Fixed two rendering bugs with column headers in portrait mobile view. ( #978 ,  #980 ) 
                 
                 
                     New  db.table_column_details(table)  introspection method for retrieving full details of the columns in a specific table, see  Database introspection . 
                 
                 
                     Fixed a routing bug with custom page wildcard templates. ( #996 ) 
                 
                 
                     datasette publish heroku  now deploys using Python 3.8.6. 
                 
                 
                     New  datasette publish heroku --tar=  option. ( #969 ) 
                 
                 
                     OPTIONS  requests against HTML pages no longer return a 500 error. ( #1001 ) 
                 
                 
                     Datasette now supports Python 3.9. 
                 
             
             See also  Datasette 0.50: The annotated release notes .

references:
[{"href": "https://github.com/simonw/datasette/issues/891", "label": "#891"}, {"href": "https://github.com/simonw/datasette/issues/943", "label": "#943"}, {"href": "https://github.com/simonw/datasette/issues/514", "label": "#514"}, {"href": "https://github.com/simonw/datasette/issues/997", "label": "#997"}, {"href": "https://github.com/simonw/datasette/pull/995", "label": "#995"}, {"href": "https://github.com/simonw/datasette/issues/970", "label": "#970"}, {"href": "https://github.com/simonw/datasette/issues/891", "label": "#891"}, {"href": "https://github.com/simonw/datasette/issues/978", "label": "#978"}, {"href": "https://github.com/simonw/datasette/issues/980", "label": "#980"}, {"href": "https://github.com/simonw/datasette/issues/996", "label": "#996"}, {"href": "https://github.com/simonw/datasette/issues/969", "label": "#969"}, {"href": "https://github.com/simonw/datasette/issues/1001", "label": "#1001"}, {"href": "https://simonwillison.net/2020/Oct/9/datasette-0-50/", "label": "Datasette 0.50: The annotated release notes"}]


id: changelog:id52
page: changelog
ref: id52
title: 0.49.1 (2020-09-15)
breadcrumbs: ["Changelog"]
references: [{"href": "https://github.com/simonw/datasette/issues/967", "label": "#967"}]
content:
Fixed a bug with writable canned queries that use magic parameters but accept no non-magic arguments. ( #967 )


id: changelog:id53
page: changelog
ref: id53
title: 0.49 (2020-09-14)
breadcrumbs: ["Changelog"]
content:
See also  Datasette 0.49: The annotated release notes . 
             
                 
                     Writable canned queries now expose a JSON API, see  JSON API for writable canned queries . ( #880 ) 
                 
                 
                     New mechanism for defining page templates with custom path parameters - a template file called  pages/about/{slug}.html  will be used to render any requests to  /about/something . See  Path parameters for pages . ( #944 ) 
                 
                 
                     register_output_renderer()  render functions can now return a  Response . ( #953 ) 
                 
                 
                     New  --upgrade  option for  datasette install . ( #945 ) 
                 
                 
                     New  datasette --pdb  option. ( #962 ) 
                 
                 
                     datasette --get  exit code now reflects the internal HTTP status code. ( #947 ) 
                 
                 
                     New  raise_404()  template function for returning 404 errors. ( #964 ) 
                 
                 
                     datasette publish heroku  now deploys using Python 3.8.5 
                 
                 
                     Upgraded  CodeMirror  to 5.57.0. ( #948 ) 
                 
                 
                     Upgraded code style to Black 20.8b1. ( #958 ) 
                 
                 
                     Fixed bug where selected facets were not correctly persisted in hidden form fields on the table page. ( #963 ) 
                 
                 
                     Renamed the default error template from  500.html  to  error.html . 
                 
                 
                     Custom error pages are now documented, see  Custom error pages . ( #965 )

references:
[{"href": "https://simonwillison.net/2020/Sep/15/datasette-0-49/", "label": "Datasette 0.49: The annotated release notes"}, {"href": "https://github.com/simonw/datasette/issues/880", "label": "#880"}, {"href": "https://github.com/simonw/datasette/issues/944", "label": "#944"}, {"href": "https://github.com/simonw/datasette/issues/953", "label": "#953"}, {"href": "https://github.com/simonw/datasette/issues/945", "label": "#945"}, {"href": "https://github.com/simonw/datasette/issues/962", "label": "#962"}, {"href": "https://github.com/simonw/datasette/issues/947", "label": "#947"}, {"href": "https://github.com/simonw/datasette/issues/964", "label": "#964"}, {"href": "https://codemirror.net/", "label": "CodeMirror"}, {"href": "https://github.com/simonw/datasette/issues/948", "label": "#948"}, {"href": "https://github.com/simonw/datasette/issues/958", "label": "#958"}, {"href": "https://github.com/simonw/datasette/issues/963", "label": "#963"}, {"href": "https://github.com/simonw/datasette/issues/965", "label": "#965"}]


id: changelog:id54
page: changelog
ref: id54
title: 0.48 (2020-08-16)
breadcrumbs: ["Changelog"]
content:
Datasette documentation now lives at  docs.datasette.io . 
                 
                 
                     db.is_mutable  property is now documented and tested, see  Database introspection . 
                 
                 
                     The  extra_template_vars ,  extra_css_urls ,  extra_js_urls  and  extra_body_script  plugin hooks now all accept the same arguments. See  extra_template_vars(template, database, table, columns, view_name, request, datasette)  for details. ( #939 ) 
                 
                 
                     Those hooks now accept a new  columns  argument detailing the table columns that will be rendered on that page. ( #938 ) 
                 
                 
                     Fixed bug where plugins calling  db.execute_write_fn()  could hang Datasette if the connection failed. ( #935 ) 
                 
                 
                     Fixed bug with the  ?_nl=on  output option and binary data. ( #914 )

references:
[{"href": "https://docs.datasette.io/", "label": "docs.datasette.io"}, {"href": "https://github.com/simonw/datasette/issues/939", "label": "#939"}, {"href": "https://github.com/simonw/datasette/issues/938", "label": "#938"}, {"href": "https://github.com/simonw/datasette/issues/935", "label": "#935"}, {"href": "https://github.com/simonw/datasette/issues/914", "label": "#914"}]


id: changelog:id55
page: changelog
ref: id55
title: 0.47.3 (2020-08-15)
breadcrumbs: ["Changelog"]
references: [{"href": "https://github.com/simonw/datasette/issues/934", "label": "#934"}]
content:
The  datasette --get  command-line mechanism now ensures any plugins using the  startup()  hook are correctly executed. ( #934 )


id: changelog:id56
page: changelog
ref: id56
title: 0.47.2 (2020-08-12)
content: Fixed an issue with the Docker image published to Docker Hub . ( #931 )
breadcrumbs: ["Changelog"]
references:
[{"href": "https://hub.docker.com/r/datasetteproject/datasette", "label": "published to Docker Hub"}, {"href": "https://github.com/simonw/datasette/issues/931", "label": "#931"}]


id: changelog:id57
page: changelog
ref: id57
title: 0.47.1 (2020-08-11)
breadcrumbs: ["Changelog"]
references: [{"href": "https://github.com/simonw/datasette/issues/930", "label": "#930"}]
content:
Fixed a bug where the  sdist  distribution of Datasette was not correctly including the template files. ( #930 )


id: changelog:id58
page: changelog
ref: id58
title: 0.47 (2020-08-11)
breadcrumbs: ["Changelog"]
content:
Datasette now has  a GitHub discussions forum  for conversations about the project that go beyond just bug reports and issues. 
                 
                 
                     Datasette can now be installed on macOS using Homebrew! Run  brew install simonw/datasette/datasette . See  Using Homebrew . ( #335 ) 
                 
                 
                     Two new commands:  datasette install name-of-plugin  and  datasette uninstall name-of-plugin . These are equivalent to  pip install  and  pip uninstall  but automatically run in the same virtual environment as Datasette, so users don't have to figure out where that virtual environment is - useful for installations created using Homebrew or  pipx . See  Installing plugins . ( #925 ) 
                 
                 
                     A new command-line option,  datasette --get , accepts a path to a URL within the Datasette instance. It will run that request through Datasette (without starting a web server) and print out the response. See  datasette --get  for an example. ( #926 )

references:
[{"href": "https://github.com/simonw/datasette/discussions", "label": "a GitHub discussions forum"}, {"href": "https://github.com/simonw/datasette/issues/335", "label": "#335"}, {"href": "https://github.com/simonw/datasette/issues/925", "label": "#925"}, {"href": "https://github.com/simonw/datasette/issues/926", "label": "#926"}]


id: changelog:id59
page: changelog
ref: id59
title: 0.46 (2020-08-09)
breadcrumbs: ["Changelog"]
content:
This release contains a security fix related to authenticated writable canned queries. If you are using this feature you should upgrade as soon as possible. 
             
             
                 
                     Security fix:  CSRF tokens were incorrectly included in read-only canned query forms, which could allow them to be leaked to a sophisticated attacker. See  issue 918  for details. 
                 
                 
                     Datasette now supports GraphQL via the new  datasette-graphql  plugin - see  GraphQL in Datasette with the new datasette-graphql plugin . 
                 
                 
                     Principle git branch has been renamed from  master  to  main . ( #849 ) 
                 
                 
                     New debugging tool:  /-/allow-debug tool  ( demo here ) helps test allow blocks against actors, as described in  Defining permissions with "allow" blocks . ( #908 ) 
                 
                 
                     New logo for the documentation, and a new project tagline: "An open source multi-tool for exploring and publishing data". 
                 
                 
                     Whitespace in column values is now respected on display, using  white-space: pre-wrap . ( #896 ) 
                 
                 
                     New  await request.post_body()  method for accessing the raw POST body, see  Request object . ( #897 ) 
                 
                 
                     Database file downloads now include a  content-length  HTTP header, enabling download progress bars. ( #905 ) 
                 
                 
                     File downloads now also correctly set the suggested file name using a  content-disposition  HTTP header. ( #909 ) 
                 
                 
                     tests  are now excluded from the Datasette package properly - thanks, abeyerpath. ( #456 ) 
                 
                 
                     The Datasette package published to PyPI now includes  sdist  as well as  bdist_wheel . 
                 
                 
                     Better titles for canned query pages. ( #887 ) 
                 
                 
                     Now only loads Python files from a directory passed using the  --plugins-dir  option - thanks, Amjith Ramanujam. ( #890 ) 
                 
                 
                     New documentation section on  Publishing to Vercel .

references:
[{"href": "https://github.com/simonw/datasette/issues/918", "label": "issue 918"}, {"href": "https://github.com/simonw/datasette-graphql", "label": "datasette-graphql"}, {"href": "https://simonwillison.net/2020/Aug/7/datasette-graphql/", "label": "GraphQL in Datasette with the new datasette-graphql plugin"}, {"href": "https://github.com/simonw/datasette/issues/849", "label": "#849"}, {"href": "https://latest.datasette.io/-/allow-debug", "label": "demo here"}, {"href": "https://github.com/simonw/datasette/issues/908", "label": "#908"}, {"href": "https://github.com/simonw/datasette/issues/896", "label": "#896"}, {"href": "https://github.com/simonw/datasette/issues/897", "label": "#897"}, {"href": "https://github.com/simonw/datasette/issues/905", "label": "#905"}, {"href": "https://github.com/simonw/datasette/issues/909", "label": "#909"}, {"href": "https://github.com/simonw/datasette/issues/456", "label": "#456"}, {"href": "https://github.com/simonw/datasette/issues/887", "label": "#887"}, {"href": "https://github.com/simonw/datasette/pull/890", "label": "#890"}]


id: changelog:id60
page: changelog
ref: id60
title: 0.45 (2020-07-01)
breadcrumbs: ["Changelog"]
content:
See also  Datasette 0.45: The annotated release notes . 
             Magic parameters for canned queries, a log out feature, improved plugin documentation and four new plugin hooks.

references:
[{"href": "https://simonwillison.net/2020/Jul/1/datasette-045/", "label": "Datasette 0.45: The annotated release notes"}]


id: changelog:magic-parameters-for-canned-queries
page: changelog
ref: magic-parameters-for-canned-queries
title: Magic parameters for canned queries
breadcrumbs: ["Changelog", "0.45 (2020-07-01)"]
references: [{"href": "https://github.com/simonw/datasette/issues/842", "label": "#842"}]
content:
Canned queries now support  Magic parameters , which can be used to insert or select automatically generated values. For example: 
                 insert into logs
  (user_id, timestamp)
values
  (:_actor_id, :_now_datetime_utc) 
                 This inserts the currently authenticated actor ID and the current datetime. ( #842 )


id: changelog:log-out
page: changelog
ref: log-out
title: Log out
breadcrumbs: ["Changelog", "0.45 (2020-07-01)"]
references: [{"href": "https://github.com/simonw/datasette/issues/840", "label": "#840"}]
content:
The  ds_actor cookie  can be used by plugins (or by Datasette's  --root mechanism ) to authenticate users. The new  /-/logout  page provides a way to clear that cookie. 
                 A "Log out" button now shows in the global navigation provided the user is authenticated using the  ds_actor  cookie. ( #840 )


id: changelog:better-plugin-documentation
page: changelog
ref: better-plugin-documentation
title: Better plugin documentation
breadcrumbs: ["Changelog", "0.45 (2020-07-01)"]
content:
The plugin documentation has been re-arranged into four sections, including a brand new section on testing plugins. ( #687 ) 
                 
                     
                         Plugins  introduces Datasette's plugin system and describes how to install and configure plugins. 
                     
                     
                         Writing plugins  describes how to author plugins, from  one-off single file plugins to packaged plugins that can be published to PyPI. It also describes how to start a plugin using the new  datasette-plugin  cookiecutter template. 
                     
                     
                         Plugin hooks  is a full list of detailed documentation for every Datasette plugin hook. 
                     
                     
                         Testing plugins  describes how to write tests for Datasette plugins, using  pytest  and  HTTPX .

references:
[{"href": "https://github.com/simonw/datasette/issues/687", "label": "#687"}, {"href": "https://github.com/simonw/datasette-plugin", "label": "datasette-plugin"}, {"href": "https://docs.pytest.org/", "label": "pytest"}, {"href": "https://www.python-httpx.org/", "label": "HTTPX"}]


id: changelog:new-plugin-hooks
page: changelog
ref: new-plugin-hooks
title: New plugin hooks
breadcrumbs: ["Changelog", "0.45 (2020-07-01)"]
content:
register_magic_parameters(datasette)  can be used to define new types of magic canned query parameters. 
                     
                     
                         startup(datasette)  can run custom code when Datasette first starts up.  datasette-init  is a new plugin that uses this hook to create database tables and views on startup if they have not yet been created. ( #834 ) 
                     
                     
                         canned_queries(datasette, database, actor)  lets plugins provide additional canned queries beyond those defined in Datasette's metadata. See  datasette-saved-queries  for an example of this hook in action. ( #852 ) 
                     
                     
                         forbidden(datasette, request, message)  is a hook for customizing how Datasette responds to 403 forbidden errors. ( #812 )

references:
[{"href": "https://github.com/simonw/datasette-init", "label": "datasette-init"}, {"href": "https://github.com/simonw/datasette/issues/834", "label": "#834"}, {"href": "https://github.com/simonw/datasette-saved-queries", "label": "datasette-saved-queries"}, {"href": "https://github.com/simonw/datasette/issues/852", "label": "#852"}, {"href": "https://github.com/simonw/datasette/issues/812", "label": "#812"}]


id: changelog:id61
page: changelog
ref: id61
title: Smaller changes
breadcrumbs: ["Changelog", "0.45 (2020-07-01)"]
content:
Cascading view permissions - so if a user has  view-table  they can view the table page even if they do not have  view-database  or  view-instance . ( #832 ) 
                     
                     
                         CSRF protection no longer applies to  Authentication: Bearer token  requests or requests without cookies. ( #835 ) 
                     
                     
                         datasette.add_message()  now works inside plugins. ( #864 ) 
                     
                     
                         Workaround for "Too many open files" error in test runs. ( #846 ) 
                     
                     
                         Respect existing  scope["actor"]  if already set by ASGI middleware. ( #854 ) 
                     
                     
                         New process for shipping  Alpha and beta releases . ( #807 ) 
                     
                     
                         {{ csrftoken() }}  now works when plugins render a template using  datasette.render_template(..., request=request) . ( #863 ) 
                     
                     
                         Datasette now creates a single  Request object  and uses it throughout the lifetime of the current HTTP request. ( #870 )

references:
[{"href": "https://github.com/simonw/datasette/issues/832", "label": "#832"}, {"href": "https://github.com/simonw/datasette/issues/835", "label": "#835"}, {"href": "https://github.com/simonw/datasette/issues/864", "label": "#864"}, {"href": "https://github.com/simonw/datasette/issues/846", "label": "#846"}, {"href": "https://github.com/simonw/datasette/issues/854", "label": "#854"}, {"href": "https://github.com/simonw/datasette/issues/807", "label": "#807"}, {"href": "https://github.com/simonw/datasette/issues/863", "label": "#863"}, {"href": "https://github.com/simonw/datasette/issues/870", "label": "#870"}]


id: changelog:id62
page: changelog
ref: id62
title: 0.44 (2020-06-11)
breadcrumbs: ["Changelog"]
content:
See also  Datasette 0.44: The annotated release notes . 
             Authentication and permissions, writable canned queries, flash messages, new plugin hooks and more.

references:
[{"href": "https://simonwillison.net/2020/Jun/12/annotated-release-notes/", "label": "Datasette 0.44: The annotated release notes"}]


id: changelog:authentication
page: changelog
ref: authentication
title: Authentication
breadcrumbs: ["Changelog", "0.44 (2020-06-11)"]
content:
Prior to this release the Datasette ecosystem has treated authentication as exclusively the realm of plugins, most notably through  datasette-auth-github . 
                 0.44 introduces  Authentication and permissions  as core Datasette concepts ( #699 ). This enables different plugins to share responsibility for authenticating requests - you might have one plugin that handles user accounts and another one that allows automated access via API keys, for example. 
                 You'll need to install plugins if you want full user accounts, but default Datasette can now authenticate a single root user with the new  --root  command-line option, which outputs a one-time use URL to  authenticate as a root actor  ( #784 ): 
                 datasette fixtures.db --root 
                 http://127.0.0.1:8001/-/auth-token?token=5b632f8cd44b868df625f5a6e2185d88eea5b22237fd3cc8773f107cc4fd6477
INFO:     Started server process [14973]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8001 (Press CTRL+C to quit) 
                 Plugins can implement new ways of authenticating users using the new  actor_from_request(datasette, request)  hook.

references:
[{"href": "https://github.com/simonw/datasette-auth-github", "label": "datasette-auth-github"}, {"href": "https://github.com/simonw/datasette/issues/699", "label": "#699"}, {"href": "https://github.com/simonw/datasette/issues/784", "label": "#784"}]


id: changelog:permissions
page: changelog
ref: permissions
title: Permissions
breadcrumbs: ["Changelog", "0.44 (2020-06-11)"]
references: [{"href": "https://github.com/simonw/datasette/issues/788", "label": "#788"}]
content:
Datasette also now has a built-in concept of  Permissions . The permissions system answers the following question: 
                 
                     Is this  actor  allowed to perform this  action , optionally against this particular  resource ? 
                 
                 You can use the new  "allow"  block syntax in  metadata.json  (or  metadata.yaml ) to set required permissions at the instance, database, table or canned query level. For example, to restrict access to the  fixtures.db  database to the  "root"  user: 
                 {
    "databases": {
        "fixtures": {
            "allow": {
                "id" "root"
            }
        }
    }
} 
                 See  Defining permissions with "allow" blocks  for more details. 
                 Plugins can implement their own custom permission checks using the new  permission_allowed(datasette, actor, action, resource)  hook. 
                 A new debug page at  /-/permissions  shows recent permission checks, to help administrators and plugin authors understand exactly what checks are being performed. This tool defaults to only being available to the root user, but can be exposed to other users by plugins that respond to the  permissions-debug  permission. ( #788 )


id: changelog:writable-canned-queries
page: changelog
ref: writable-canned-queries
title: Writable canned queries
breadcrumbs: ["Changelog", "0.44 (2020-06-11)"]
content:
Datasette's  Canned queries  feature lets you define SQL queries in  metadata.json  which can then be executed by users visiting a specific URL.  https://latest.datasette.io/fixtures/neighborhood_search  for example. 
                 Canned queries were previously restricted to  SELECT , but Datasette 0.44 introduces the ability for canned queries to execute  INSERT  or  UPDATE  queries as well, using the new  "write": true  property ( #800 ): 
                 {
    "databases": {
        "dogs": {
            "queries": {
                "add_name": {
                    "sql": "INSERT INTO names (name) VALUES (:name)",
                    "write": true
                }
            }
        }
    }
} 
                 See  Writable canned queries  for more details.

references:
[{"href": "https://latest.datasette.io/fixtures/neighborhood_search", "label": "https://latest.datasette.io/fixtures/neighborhood_search"}, {"href": "https://github.com/simonw/datasette/issues/800", "label": "#800"}]


id: changelog:flash-messages
page: changelog
ref: flash-messages
title: Flash messages
breadcrumbs: ["Changelog", "0.44 (2020-06-11)"]
content:
Writable canned queries needed a mechanism to let the user know that the query has been successfully executed. The new flash messaging system ( #790 ) allows messages to persist in signed cookies which are then displayed to the user on the next page that they visit. Plugins can use this mechanism to display their own messages, see  .add_message(request, message, type=datasette.INFO)  for details. 
                 You can try out the new messages using the  /-/messages  debug tool, for example at  https://latest.datasette.io/-/messages

references:
[{"href": "https://github.com/simonw/datasette/issues/790", "label": "#790"}, {"href": "https://latest.datasette.io/-/messages", "label": "https://latest.datasette.io/-/messages"}]


id: changelog:signed-values-and-secrets
page: changelog
ref: signed-values-and-secrets
title: Signed values and secrets
breadcrumbs: ["Changelog", "0.44 (2020-06-11)"]
references: []
content:
Both flash messages and user authentication needed a way to sign values and set signed cookies. Two new methods are now available for plugins to take advantage of this mechanism:  .sign(value, namespace="default")  and  .unsign(value, namespace="default") . 
                 Datasette will generate a secret automatically when it starts up, but to avoid resetting the secret (and hence invalidating any cookies) every time the server restarts you should set your own secret. You can pass a secret to Datasette using the new  --secret  option or with a  DATASETTE_SECRET  environment variable. See  Configuring the secret  for more details. 
                 You can also set a secret when you deploy Datasette using  datasette publish  or  datasette package  - see  Using secrets with datasette publish . 
                 Plugins can now sign values and verify their signatures using the  datasette.sign()  and  datasette.unsign()  methods.


id: changelog:csrf-protection
page: changelog
ref: csrf-protection
title: CSRF protection
breadcrumbs: ["Changelog", "0.44 (2020-06-11)"]
references: [{"href": "https://github.com/simonw/datasette/issues/798", "label": "#798"}]
content:
Since writable canned queries are built using POST forms, Datasette now ships with  CSRF protection  ( #798 ). This applies automatically to any POST request, which means plugins need to include a  csrftoken  in any POST forms that they render. They can do that like so: 
                 <input type="hidden" name="csrftoken" value="{{ csrftoken() }}">


id: changelog:cookie-methods
page: changelog
ref: cookie-methods
title: Cookie methods
breadcrumbs: ["Changelog", "0.44 (2020-06-11)"]
references: []
content:
Plugins can now use the new  response.set_cookie()  method to set cookies. 
                 A new  request.cookies  method on the :ref:internals_request` can be used to read incoming cookies.


id: changelog:register-routes-plugin-hooks
page: changelog
ref: register-routes-plugin-hooks
title: register_routes() plugin hooks
breadcrumbs: ["Changelog", "0.44 (2020-06-11)"]
references: [{"href": "https://github.com/simonw/datasette/issues/819", "label": "#819"}]
content:
Plugins can now register new views and routes via the  register_routes(datasette)  plugin hook ( #819 ). View functions can be defined that accept any of the current  datasette  object, the current  request , or the ASGI  scope ,  send  and  receive  objects.


id: changelog:id63
page: changelog
ref: id63
title: Smaller changes
breadcrumbs: ["Changelog", "0.44 (2020-06-11)"]
content:
New internals documentation for  Request object  and  Response class . ( #706 ) 
                     
                     
                         request.url  now respects the  force_https_urls  config setting. closes ( #781 ) 
                     
                     
                         request.args.getlist()  returns  []  if missing. Removed  request.raw_args  entirely. ( #774 ) 
                     
                     
                         New  datasette.get_database()  method. 
                     
                     
                         Added  _  prefix to many private, undocumented methods of the Datasette class. ( #576 ) 
                     
                     
                         Removed the  db.get_outbound_foreign_keys()  method which duplicated the behaviour of  db.foreign_keys_for_table() . 
                     
                     
                         New  await datasette.permission_allowed()  method. 
                     
                     
                         /-/actor  debugging endpoint for viewing the currently authenticated actor. 
                     
                     
                         New  request.cookies  property. 
                     
                     
                         /-/plugins  endpoint now shows a list of hooks implemented by each plugin, e.g.  https://latest.datasette.io/-/plugins?all=1 
                     
                     
                         request.post_vars()  method no longer discards empty values. 
                     
                     
                         New "params" canned query key for explicitly setting named parameters, see  Canned query parameters . ( #797 ) 
                     
                     
                         request.args  is now a  MultiParams  object. 
                     
                     
                         Fixed a bug with the  datasette plugins  command. ( #802 ) 
                     
                     
                         Nicer pattern for using  make_app_client()  in tests. ( #395 ) 
                     
                     
                         New  request.actor  property. 
                     
                     
                         Fixed broken CSS on nested 404 pages. ( #777 ) 
                     
                     
                         New  request.url_vars  property. ( #822 ) 
                     
                     
                         Fixed a bug with the  python tests/fixtures.py  command for outputting Datasette's testing fixtures database and plugins. ( #804 ) 
                     
                     
                         datasette publish heroku  now deploys using Python 3.8.3. 
                     
                     
                         Added a warning that the  register_facet_classes()  hook is unstable and may change in the future. ( #830 ) 
                     
                     
                         The  {"$env": "ENVIRONMENT_VARIBALE"}  mechanism (see  Secret configuration values ) now works with variables inside nested lists. ( #837 )

references:
[{"href": "https://github.com/simonw/datasette/issues/706", "label": "#706"}, {"href": "https://github.com/simonw/datasette/issues/781", "label": "#781"}, {"href": "https://github.com/simonw/datasette/issues/774", "label": "#774"}, {"href": "https://github.com/simonw/datasette/issues/576", "label": "#576"}, {"href": "https://latest.datasette.io/-/plugins?all=1", "label": "https://latest.datasette.io/-/plugins?all=1"}, {"href": "https://github.com/simonw/datasette/issues/797", "label": "#797"}, {"href": "https://github.com/simonw/datasette/issues/802", "label": "#802"}, {"href": "https://github.com/simonw/datasette/issues/395", "label": "#395"}, {"href": "https://github.com/simonw/datasette/issues/777", "label": "#777"}, {"href": "https://github.com/simonw/datasette/issues/822", "label": "#822"}, {"href": "https://github.com/simonw/datasette/issues/804", "label": "#804"}, {"href": "https://github.com/simonw/datasette/issues/830", "label": "#830"}, {"href": "https://github.com/simonw/datasette/issues/837", "label": "#837"}]


id: changelog:the-road-to-datasette-1-0
page: changelog
ref: the-road-to-datasette-1-0
title: The road to Datasette 1.0
breadcrumbs: ["Changelog", "0.44 (2020-06-11)"]
content:
I've assembled a  milestone for Datasette 1.0 . The focus of the 1.0 release will be the following: 
                 
                     
                         Signify confidence in the quality/stability of Datasette 
                     
                     
                         Give plugin authors confidence that their plugins will work for the whole 1.x release cycle 
                     
                     
                         Provide the same confidence to developers building against Datasette JSON APIs 
                     
                 
                 If you have thoughts about what you would like to see for Datasette 1.0 you can join  the conversation on issue #519 .

references:
[{"href": "https://github.com/simonw/datasette/milestone/7", "label": "milestone for Datasette 1.0"}, {"href": "https://github.com/simonw/datasette/issues/519", "label": "the conversation on issue #519"}]


id: changelog:id64
page: changelog
ref: id64
title: 0.43 (2020-05-28)
breadcrumbs: ["Changelog"]
content:
The main focus of this release is a major upgrade to the  register_output_renderer(datasette)  plugin hook, which allows plugins to provide new output formats for Datasette such as  datasette-atom  and  datasette-ics . 
             
                 
                     Redesign of  register_output_renderer(datasette)  to provide more context to the render callback and support an optional  "can_render"  callback that controls if a suggested link to the output format is provided. ( #581 ,  #770 ) 
                 
                 
                     Visually distinguish float and integer columns - useful for figuring out why order-by-column might be returning unexpected results. ( #729 ) 
                 
                 
                     The  Request object , which is passed to several plugin hooks, is now documented. ( #706 ) 
                 
                 
                     New  metadata.json  option for setting a custom default page size for specific tables and views, see  Setting a custom page size . ( #751 ) 
                 
                 
                     Canned queries can now be configured with a default URL fragment hash, useful when working with plugins such as  datasette-vega , see  Additional canned query options . ( #706 ) 
                 
                 
                     Fixed a bug in  datasette publish  when running on operating systems where the  /tmp  directory lives in a different volume, using a backport of the Python 3.8  shutil.copytree()  function. ( #744 ) 
                 
                 
                     Every plugin hook is now covered by the unit tests, and a new unit test checks that each plugin hook has at least one corresponding test. ( #771 ,  #773 )

references:
[{"href": "https://github.com/simonw/datasette-atom", "label": "datasette-atom"}, {"href": "https://github.com/simonw/datasette-ics", "label": "datasette-ics"}, {"href": "https://github.com/simonw/datasette/issues/581", "label": "#581"}, {"href": "https://github.com/simonw/datasette/issues/770", "label": "#770"}, {"href": "https://github.com/simonw/datasette/issues/729", "label": "#729"}, {"href": "https://github.com/simonw/datasette/issues/706", "label": "#706"}, {"href": "https://github.com/simonw/datasette/issues/751", "label": "#751"}, {"href": "https://github.com/simonw/datasette-vega", "label": "datasette-vega"}, {"href": "https://github.com/simonw/datasette/issues/706", "label": "#706"}, {"href": "https://github.com/simonw/datasette/issues/744", "label": "#744"}, {"href": "https://github.com/simonw/datasette/issues/771", "label": "#771"}, {"href": "https://github.com/simonw/datasette/issues/773", "label": "#773"}]


id: changelog:id65
page: changelog
ref: id65
title: 0.42 (2020-05-08)
breadcrumbs: ["Changelog"]
references: [{"href": "https://github.com/simonw/datasette/issues/685", "label": "#685"}]
content:
A small release which provides improved internal methods for use in plugins, along with documentation. See  #685 . 
             
                 
                     Added documentation for  db.execute() , see  await db.execute(sql, ...) . 
                 
                 
                     Renamed  db.execute_against_connection_in_thread()  to  db.execute_fn()  and made it a documented method, see  await db.execute_fn(fn) . 
                 
                 
                     New  results.first()  and  results.single_value()  methods, plus documentation for the  Results  class - see  Results .


id: changelog:id66
page: changelog
ref: id66
title: 0.41 (2020-05-06)
breadcrumbs: ["Changelog"]
content:
You can now create  custom pages  within your Datasette instance using a custom template file. For example, adding a template file called  templates/pages/about.html  will result in a new page being served at  /about  on your instance. See the  custom pages documentation  for full details, including how to return custom HTTP headers, redirects and status codes. ( #648 ) 
             Configuration directory mode  ( #731 ) allows you to define a custom Datasette instance as a directory. So instead of running the following: 
             datasette one.db two.db \
  --metadata=metadata.json \
  --template-dir=templates/ \
  --plugins-dir=plugins \
  --static css:css 
             You can instead arrange your files in a single directory called  my-project  and run this: 
             datasette my-project/ 
             Also in this release: 
             
                 
                     New  NOT LIKE  table filter:  ?colname__notlike=expression . ( #750 ) 
                 
                 
                     Datasette now has a  pattern portfolio  at  /-/patterns  - e.g.  https://latest.datasette.io/-/patterns . This is a page that shows every Datasette user interface component in one place, to aid core development and people building custom CSS themes. ( #151 ) 
                 
                 
                     SQLite  PRAGMA functions  such as  pragma_table_info(tablename)  are now allowed in Datasette SQL queries. ( #761 ) 
                 
                 
                     Datasette pages now consistently return a  content-type  of  text/html; charset=utf-8" . ( #752 ) 
                 
                 
                     Datasette now handles an ASGI  raw_path  value of  None , which should allow compatibility with the  Mangum  adapter for running ASGI apps on AWS Lambda. Thanks, Colin Dellow. ( #719 ) 
                 
                 
                     Installation documentation now covers how to  Using pipx . ( #756 ) 
                 
                 
                     Improved the documentation for  Full-text search . ( #748 )

references:
[{"href": "https://github.com/simonw/datasette/issues/648", "label": "#648"}, {"href": "https://github.com/simonw/datasette/issues/731", "label": "#731"}, {"href": "https://github.com/simonw/datasette/issues/750", "label": "#750"}, {"href": "https://latest.datasette.io/-/patterns", "label": "https://latest.datasette.io/-/patterns"}, {"href": "https://github.com/simonw/datasette/issues/151", "label": "#151"}, {"href": "https://www.sqlite.org/pragma.html#pragfunc", "label": "PRAGMA functions"}, {"href": "https://github.com/simonw/datasette/issues/761", "label": "#761"}, {"href": "https://github.com/simonw/datasette/issues/752", "label": "#752"}, {"href": "https://github.com/erm/mangum", "label": "Mangum"}, {"href": "https://github.com/simonw/datasette/pull/719", "label": "#719"}, {"href": "https://github.com/simonw/datasette/issues/756", "label": "#756"}, {"href": "https://github.com/simonw/datasette/issues/748", "label": "#748"}]


id: changelog:id67
page: changelog
ref: id67
title: 0.40 (2020-04-21)
breadcrumbs: ["Changelog"]
content:
Datasette  Metadata  can now be provided as a YAML file as an optional alternative to JSON. ( #713 ) 
                 
                 
                     Removed support for  datasette publish now , which used the the now-retired Zeit Now v1 hosting platform. A new plugin,  datasette-publish-now , can be installed to publish data to Zeit ( now Vercel ) Now v2. ( #710 ) 
                 
                 
                     Fixed a bug where the  extra_template_vars(request, view_name)  plugin hook was not receiving the correct  view_name . ( #716 ) 
                 
                 
                     Variables added to the template context by the  extra_template_vars()  plugin hook are now shown in the  ?_context=1  debugging mode (see  template_debug ). ( #693 ) 
                 
                 
                     Fixed a bug where the "templates considered" HTML comment was no longer being displayed. ( #689 ) 
                 
                 
                     Fixed a  datasette publish  bug where  --plugin-secret  would over-ride plugin configuration in the provided  metadata.json  file. ( #724 ) 
                 
                 
                     Added a new CSS class for customizing the canned query page. ( #727 )

references:
[{"href": "https://github.com/simonw/datasette/issues/713", "label": "#713"}, {"href": "https://github.com/simonw/datasette-publish-now", "label": "datasette-publish-now"}, {"href": "https://vercel.com/blog/zeit-is-now-vercel", "label": "now Vercel"}, {"href": "https://github.com/simonw/datasette/issues/710", "label": "#710"}, {"href": "https://github.com/simonw/datasette/issues/716", "label": "#716"}, {"href": "https://github.com/simonw/datasette/issues/693", "label": "#693"}, {"href": "https://github.com/simonw/datasette/issues/689", "label": "#689"}, {"href": "https://github.com/simonw/datasette/issues/724", "label": "#724"}, {"href": "https://github.com/simonw/datasette/issues/727", "label": "#727"}]


id: changelog:id68
page: changelog
ref: id68
title: 0.39 (2020-03-24)
breadcrumbs: ["Changelog"]
content:
New  base_url  configuration setting for serving up the correct links while running Datasette under a different URL prefix. ( #394 ) 
                 
                 
                     New metadata settings  "sort"  and  "sort_desc"  for setting the default sort order for a table. See  Setting a default sort order . ( #702 ) 
                 
                 
                     Sort direction arrow now displays by default on the primary key. This means you only have to click once (not twice) to sort in reverse order. ( #677 ) 
                 
                 
                     New  await Request(scope, receive).post_vars()  method for accessing POST form variables. ( #700 ) 
                 
                 
                     Plugin hooks  documentation now links to example uses of each plugin. ( #709 )

references:
[{"href": "https://github.com/simonw/datasette/issues/394", "label": "#394"}, {"href": "https://github.com/simonw/datasette/issues/702", "label": "#702"}, {"href": "https://github.com/simonw/datasette/issues/677", "label": "#677"}, {"href": "https://github.com/simonw/datasette/issues/700", "label": "#700"}, {"href": "https://github.com/simonw/datasette/issues/709", "label": "#709"}]


id: changelog:id69
page: changelog
ref: id69
title: 0.38 (2020-03-08)
breadcrumbs: ["Changelog"]
content:
The  Docker build  of Datasette now uses SQLite 3.31.1, upgraded from 3.26. ( #695 ) 
                 
                 
                     datasette publish cloudrun  now accepts an optional  --memory=2Gi  flag for setting the Cloud Run allocated memory to a value other than the default (256Mi). ( #694 ) 
                 
                 
                     Fixed bug where templates that shipped with plugins were sometimes not being correctly loaded. ( #697 )

references:
[{"href": "https://hub.docker.com/r/datasetteproject/datasette", "label": "Docker build"}, {"href": "https://github.com/simonw/datasette/issues/695", "label": "#695"}, {"href": "https://github.com/simonw/datasette/issues/694", "label": "#694"}, {"href": "https://github.com/simonw/datasette/issues/697", "label": "#697"}]


id: changelog:id70
page: changelog
ref: id70
title: 0.37.1 (2020-03-02)
breadcrumbs: ["Changelog"]
references: [{"href": "https://github.com/simonw/datasette/issues/688", "label": "#688"}]
content:
Don't attempt to count table rows to display on the index page for databases > 100MB. ( #688 ) 
                 
                 
                     Print exceptions if they occur in the write thread rather than silently swallowing them. 
                 
                 
                     Handle the possibility of  scope["path"]  being a string rather than bytes 
                 
                 
                     Better documentation for the  extra_template_vars(template, database, table, columns, view_name, request, datasette)  plugin hook.


id: changelog:id71
page: changelog
ref: id71
title: 0.37 (2020-02-25)
breadcrumbs: ["Changelog"]
content:
Plugins now have a supported mechanism for writing to a database, using the new  .execute_write()  and  .execute_write_fn()  methods.  Documentation . ( #682 ) 
                 
                 
                     Immutable databases that have had their rows counted using the  inspect  command now use the calculated count more effectively - thanks, Kevin Keogh. ( #666 ) 
                 
                 
                     --reload  no longer restarts the server if a database file is modified, unless that database was opened immutable mode with  -i . ( #494 ) 
                 
                 
                     New  ?_searchmode=raw  option turns off escaping for FTS queries in  ?_search=  allowing full use of SQLite's  FTS5 query syntax . ( #676 )

references:
[{"href": "https://github.com/simonw/datasette/issues/682", "label": "#682"}, {"href": "https://github.com/simonw/datasette/pull/666", "label": "#666"}, {"href": "https://github.com/simonw/datasette/issues/494", "label": "#494"}, {"href": "https://www.sqlite.org/fts5.html#full_text_query_syntax", "label": "FTS5 query syntax"}, {"href": "https://github.com/simonw/datasette/issues/676", "label": "#676"}]


id: changelog:id72
page: changelog
ref: id72
title: 0.36 (2020-02-21)
breadcrumbs: ["Changelog"]
content:
The  datasette  object passed to plugins now has API documentation:  Datasette class . ( #576 ) 
                 
                 
                     New methods on  datasette :  .add_database()  and  .remove_database()  -  documentation . ( #671 ) 
                 
                 
                     prepare_connection()  plugin hook now takes optional  datasette  and  database  arguments -  prepare_connection(conn, database, datasette) . ( #678 ) 
                 
                 
                     Added three new plugins and one new conversion tool to the  The Datasette Ecosystem .

references:
[{"href": "https://github.com/simonw/datasette/issues/576", "label": "#576"}, {"href": "https://github.com/simonw/datasette/issues/671", "label": "#671"}, {"href": "https://github.com/simonw/datasette/issues/678", "label": "#678"}]


id: changelog:id73
page: changelog
ref: id73
title: 0.35 (2020-02-04)
breadcrumbs: ["Changelog"]
content:
Added five new plugins and one new conversion tool to the  The Datasette Ecosystem . 
                 
                 
                     The  Datasette  class has a new  render_template()  method which can be used by plugins to render templates using Datasette's pre-configured  Jinja  templating library. 
                 
                 
                     You can now execute SQL queries that start with a  -- comment  - thanks, Jay Graves ( #653 )

references:
[{"href": "https://jinja.palletsprojects.com/", "label": "Jinja"}, {"href": "https://github.com/simonw/datasette/pull/653", "label": "#653"}]


id: changelog:id74
page: changelog
ref: id74
title: 0.34 (2020-01-29)
breadcrumbs: ["Changelog"]
content:
_search=  queries are now correctly escaped using a new  escape_fts()  custom SQL function. This means you can now run searches for strings like  park.  without seeing errors. ( #651 ) 
                 
                 
                     Google Cloud Run  is no longer in beta, so  datasette publish cloudrun  has been updated to work even if the user has not installed the  gcloud  beta components package. Thanks, Katie McLaughlin ( #660 ) 
                 
                 
                     datasette package  now accepts a  --port  option for specifying which port the resulting Docker container should listen on. ( #661 )

references:
[{"href": "https://github.com/simonw/datasette/issues/651", "label": "#651"}, {"href": "https://cloud.google.com/run/", "label": "Google Cloud Run"}, {"href": "https://github.com/simonw/datasette/pull/660", "label": "#660"}, {"href": "https://github.com/simonw/datasette/issues/661", "label": "#661"}]


id: changelog:id75
page: changelog
ref: id75
title: 0.33 (2019-12-22)
breadcrumbs: ["Changelog"]
content:
rowid  is now included in dropdown menus for filtering tables ( #636 ) 
                 
                 
                     Columns are now only suggested for faceting if they have at least one value with more than one record ( #638 ) 
                 
                 
                     Queries with no results now display "0 results" ( #637 ) 
                 
                 
                     Improved documentation for the  --static  option ( #641 ) 
                 
                 
                     asyncio task information is now included on the  /-/threads  debug page 
                 
                 
                     Bumped Uvicorn dependency 0.11 
                 
                 
                     You can now use  --port 0  to listen on an available port 
                 
                 
                     New  template_debug  setting for debugging templates, e.g.  https://latest.datasette.io/fixtures/roadside_attractions?_context=1  ( #654 )

references:
[{"href": "https://github.com/simonw/datasette/issues/636", "label": "#636"}, {"href": "https://github.com/simonw/datasette/issues/638", "label": "#638"}, {"href": "https://github.com/simonw/datasette/issues/637", "label": "#637"}, {"href": "https://github.com/simonw/datasette/issues/641", "label": "#641"}, {"href": "https://latest.datasette.io/fixtures/roadside_attractions?_context=1", "label": "https://latest.datasette.io/fixtures/roadside_attractions?_context=1"}, {"href": "https://github.com/simonw/datasette/issues/654", "label": "#654"}]


id: changelog:id76
page: changelog
ref: id76
title: 0.32 (2019-11-14)
breadcrumbs: ["Changelog"]
content:
Datasette now renders templates using  Jinja async mode . This means plugins can provide custom template functions that perform asynchronous actions, for example the new  datasette-template-sql  plugin which allows custom templates to directly execute SQL queries and render their results. ( #628 )

references:
[{"href": "https://jinja.palletsprojects.com/en/2.10.x/api/#async-support", "label": "Jinja async mode"}, {"href": "https://github.com/simonw/datasette-template-sql", "label": "datasette-template-sql"}, {"href": "https://github.com/simonw/datasette/issues/628", "label": "#628"}]


id: changelog:id77
page: changelog
ref: id77
title: 0.31.2 (2019-11-13)
breadcrumbs: ["Changelog"]
content:
Fixed a bug where  datasette publish heroku  applications failed to start ( #633 ) 
                 
                 
                     Fix for  datasette publish  with just  --source_url  - thanks, Stanley Zheng ( #572 ) 
                 
                 
                     Deployments to Heroku now use Python 3.8.0 ( #632 )

references:
[{"href": "https://github.com/simonw/datasette/issues/633", "label": "#633"}, {"href": "https://github.com/simonw/datasette/issues/572", "label": "#572"}, {"href": "https://github.com/simonw/datasette/issues/632", "label": "#632"}]


id: changelog:id79
page: changelog
ref: id79
title: 0.31 (2019-11-11)
breadcrumbs: ["Changelog"]
content:
This version adds compatibility with Python 3.8 and breaks compatibility with Python 3.5. 
             If you are still running Python 3.5 you should stick with  0.30.2 , which you can install like this: 
             pip install datasette==0.30.2 
             
                 
                     Format SQL button now works with read-only SQL queries - thanks, Tobias Kunze ( #602 ) 
                 
                 
                     New  ?column__notin=x,y,z  filter for table views ( #614 ) 
                 
                 
                     Table view now uses  select col1, col2, col3  instead of  select * 
                 
                 
                     Database filenames can now contain spaces - thanks, Tobias Kunze ( #590 ) 
                 
                 
                     Removed obsolete  ?_group_count=col  feature ( #504 ) 
                 
                 
                     Improved user interface and documentation for  datasette publish cloudrun  ( #608 ) 
                 
                 
                     Tables with indexes now show the  CREATE INDEX  statements on the table page ( #618 ) 
                 
                 
                     Current version of  uvicorn  is now shown on  /-/versions 
                 
                 
                     Python 3.8 is now supported! ( #622 ) 
                 
                 
                     Python 3.5 is no longer supported.

references:
[{"href": "https://github.com/simonw/datasette/pull/602", "label": "#602"}, {"href": "https://github.com/simonw/datasette/issues/614", "label": "#614"}, {"href": "https://github.com/simonw/datasette/pull/590", "label": "#590"}, {"href": "https://github.com/simonw/datasette/issues/504", "label": "#504"}, {"href": "https://github.com/simonw/datasette/issues/608", "label": "#608"}, {"href": "https://github.com/simonw/datasette/issues/618", "label": "#618"}, {"href": "https://www.uvicorn.org/", "label": "uvicorn"}, {"href": "https://github.com/simonw/datasette/issues/622", "label": "#622"}]


id: changelog:id80
page: changelog
ref: id80
title: 0.30.2 (2019-11-02)
breadcrumbs: ["Changelog"]
content:
/-/plugins  page now uses distribution name e.g.  datasette-cluster-map  instead of the name of the underlying Python package ( datasette_cluster_map ) ( #606 ) 
                 
                 
                     Array faceting is now only suggested for columns that contain arrays of strings ( #562 ) 
                 
                 
                     Better documentation for the  --host  argument ( #574 ) 
                 
                 
                     Don't show  None  with a broken link for the label on a nullable foreign key ( #406 )

references:
[{"href": "https://github.com/simonw/datasette/issues/606", "label": "#606"}, {"href": "https://github.com/simonw/datasette/issues/562", "label": "#562"}, {"href": "https://github.com/simonw/datasette/issues/574", "label": "#574"}, {"href": "https://github.com/simonw/datasette/issues/406", "label": "#406"}]


id: changelog:id81
page: changelog
ref: id81
title: 0.30.1 (2019-10-30)
breadcrumbs: ["Changelog"]
content:
Fixed bug where  ?_where=  parameter was not persisted in hidden form fields ( #604 ) 
                 
                 
                     Fixed bug with .JSON representation of row pages - thanks, Chris Shaw ( #603 )

references:
[{"href": "https://github.com/simonw/datasette/issues/604", "label": "#604"}, {"href": "https://github.com/simonw/datasette/issues/603", "label": "#603"}]


id: changelog:id82
page: changelog
ref: id82
title: 0.30 (2019-10-18)
breadcrumbs: ["Changelog"]
content:
Added  /-/threads  debugging page 
                 
                 
                     Allow  EXPLAIN WITH...  ( #583 ) 
                 
                 
                     Button to format SQL - thanks, Tobias Kunze ( #136 ) 
                 
                 
                     Sort databases on homepage by argument order - thanks, Tobias Kunze ( #585 ) 
                 
                 
                     Display metadata footer on custom SQL queries - thanks, Tobias Kunze ( #589 ) 
                 
                 
                     Use  --platform=managed  for  publish cloudrun  ( #587 ) 
                 
                 
                     Fixed bug returning non-ASCII characters in CSV ( #584 ) 
                 
                 
                     Fix for  /foo  v.s.  /foo-bar  bug ( #601 )

references:
[{"href": "https://github.com/simonw/datasette/issues/583", "label": "#583"}, {"href": "https://github.com/simonw/datasette/issues/136", "label": "#136"}, {"href": "https://github.com/simonw/datasette/issues/585", "label": "#585"}, {"href": "https://github.com/simonw/datasette/pull/589", "label": "#589"}, {"href": "https://github.com/simonw/datasette/issues/587", "label": "#587"}, {"href": "https://github.com/simonw/datasette/issues/584", "label": "#584"}, {"href": "https://github.com/simonw/datasette/issues/601", "label": "#601"}]


id: changelog:id83
page: changelog
ref: id83
title: 0.29.3 (2019-09-02)
breadcrumbs: ["Changelog"]
content:
Fixed implementation of CodeMirror on database page ( #560 ) 
                 
                 
                     Documentation typo fixes - thanks, Min ho Kim ( #561 ) 
                 
                 
                     Mechanism for detecting if a table has FTS enabled now works if the table name used alternative escaping mechanisms ( #570 ) - for compatibility with  a recent change to sqlite-utils .

references:
[{"href": "https://github.com/simonw/datasette/issues/560", "label": "#560"}, {"href": "https://github.com/simonw/datasette/pull/561", "label": "#561"}, {"href": "https://github.com/simonw/datasette/issues/570", "label": "#570"}, {"href": "https://github.com/simonw/sqlite-utils/pull/57", "label": "a recent change to sqlite-utils"}]


id: changelog:id84
page: changelog
ref: id84
title: 0.29.2 (2019-07-13)
breadcrumbs: ["Changelog"]
content:
Bumped  Uvicorn  to 0.8.4, fixing a bug where the query string was not included in the server logs. ( #559 ) 
                 
                 
                     Fixed bug where the navigation breadcrumbs were not displayed correctly on the page for a custom query. ( #558 ) 
                 
                 
                     Fixed bug where custom query names containing unicode characters caused errors.

references:
[{"href": "https://www.uvicorn.org/", "label": "Uvicorn"}, {"href": "https://github.com/simonw/datasette/issues/559", "label": "#559"}, {"href": "https://github.com/simonw/datasette/issues/558", "label": "#558"}]


id: changelog:id85
page: changelog
ref: id85
title: 0.29.1 (2019-07-11)
breadcrumbs: ["Changelog"]
content:
Fixed bug with static mounts using relative paths which could lead to traversal exploits ( #555 ) - thanks Abdussamet Kocak! 
                 
                 
                     Datasette can now be run as a module:  python -m datasette  ( #556 ) - thanks, Abdussamet Kocak!

references:
[{"href": "https://github.com/simonw/datasette/issues/555", "label": "#555"}, {"href": "https://github.com/simonw/datasette/issues/556", "label": "#556"}]


id: changelog:asgi
page: changelog
ref: asgi
title: ASGI
breadcrumbs: ["Changelog", "0.29 (2019-07-07)"]
content:
ASGI  is the Asynchronous Server Gateway Interface standard. I've been wanting to convert Datasette into an ASGI application for over a year -  Port Datasette to ASGI #272  tracks thirteen months of intermittent development - but with Datasette 0.29 the change is finally released. This also means Datasette now runs on top of  Uvicorn  and no longer depends on  Sanic . 
                 I wrote about the significance of this change in  Porting Datasette to ASGI, and Turtles all the way down . 
                 The most exciting consequence of this change is that Datasette plugins can now take advantage of the ASGI standard.

references:
[{"href": "https://asgi.readthedocs.io/", "label": "ASGI"}, {"href": "https://github.com/simonw/datasette/issues/272", "label": "Port Datasette to ASGI #272"}, {"href": "https://www.uvicorn.org/", "label": "Uvicorn"}, {"href": "https://github.com/huge-success/sanic", "label": "Sanic"}, {"href": "https://simonwillison.net/2019/Jun/23/datasette-asgi/", "label": "Porting Datasette to ASGI, and Turtles all the way down"}]


id: changelog:new-plugin-hook-asgi-wrapper
page: changelog
ref: new-plugin-hook-asgi-wrapper
title: New plugin hook: asgi_wrapper
breadcrumbs: ["Changelog", "0.29 (2019-07-07)"]
content:
The  asgi_wrapper(datasette)  plugin hook allows plugins to entirely wrap the Datasette ASGI application in their own ASGI middleware. ( #520 ) 
                 Two new plugins take advantage of this hook: 
                 
                     
                         datasette-auth-github  adds a authentication layer: users will have to sign in using their GitHub account before they can view data or interact with Datasette. You can also use it to restrict access to specific GitHub users, or to members of specified GitHub  organizations  or  teams . 
                     
                     
                         datasette-cors  allows you to configure  CORS headers  for your Datasette instance. You can use this to enable JavaScript running on a whitelisted set of domains to make  fetch()  calls to the JSON API provided by your Datasette instance.

references:
[{"href": "https://github.com/simonw/datasette/issues/520", "label": "#520"}, {"href": "https://github.com/simonw/datasette-auth-github", "label": "datasette-auth-github"}, {"href": "https://help.github.com/en/articles/about-organizations", "label": "organizations"}, {"href": "https://help.github.com/en/articles/organizing-members-into-teams", "label": "teams"}, {"href": "https://github.com/simonw/datasette-cors", "label": "datasette-cors"}, {"href": "https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS", "label": "CORS headers"}]


id: changelog:new-plugin-hook-extra-template-vars
page: changelog
ref: new-plugin-hook-extra-template-vars
title: New plugin hook: extra_template_vars
breadcrumbs: ["Changelog", "0.29 (2019-07-07)"]
content:
The  extra_template_vars(template, database, table, columns, view_name, request, datasette)  plugin hook allows plugins to inject their own additional variables into the Datasette template context. This can be used in conjunction with custom templates to customize the Datasette interface.  datasette-auth-github  uses this hook to add custom HTML to the new top navigation bar (which is designed to be modified by plugins, see  #540 ).

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


id: changelog:secret-plugin-configuration-options
page: changelog
ref: secret-plugin-configuration-options
title: Secret plugin configuration options
breadcrumbs: ["Changelog", "0.29 (2019-07-07)"]
content:
Plugins like  datasette-auth-github  need a safe way to set secret configuration options. Since the default mechanism for configuring plugins exposes those settings in  /-/metadata  a new mechanism was needed.  Secret configuration values  describes how plugins can now specify that their settings should be read from a file or an environment variable: 
                 {
    "plugins": {
        "datasette-auth-github": {
            "client_secret": {
                "$env": "GITHUB_CLIENT_SECRET"
            }
        }
    }
} 
                 These plugin secrets can be set directly using  datasette publish . See  Custom metadata and plugins  for details. ( #538  and  #543 )

references:
[{"href": "https://github.com/simonw/datasette-auth-github", "label": "datasette-auth-github"}, {"href": "https://github.com/simonw/datasette/issues/538", "label": "#538"}, {"href": "https://github.com/simonw/datasette/issues/543", "label": "#543"}]


id: changelog:v0-29-medium-changes
page: changelog
ref: v0-29-medium-changes
title: Easier custom templates for table rows
breadcrumbs: ["Changelog", "0.29 (2019-07-07)"]
references: []
content:
If you want to customize the display of individual table rows, you can do so using a  _table.html  template include that looks something like this: 
                 {% for row in display_rows %}
    <div>
        <h2>{{ row["title"] }}</h2>
        <p>{{ row["description"] }}<lp>
        <p>Category: {{ row.display("category_id") }}</p>
    </div>
{% endfor %} 
                 This is a  backwards incompatible change . If you previously had a custom template called  _rows_and_columns.html  you need to rename it to  _table.html . 
                 See  Custom templates  for full details.


id: changelog:through-for-joins-through-many-to-many-tables
page: changelog
ref: through-for-joins-through-many-to-many-tables
title: ?_through= for joins through many-to-many tables
breadcrumbs: ["Changelog", "0.29 (2019-07-07)"]
content:
The new  ?_through={json}  argument to the Table view allows records to be filtered based on a many-to-many relationship. See  Special table arguments  for full documentation - here's  an example . ( #355 ) 
                 This feature was added to help support  facet by many-to-many , which isn't quite ready yet but will be coming in the next Datasette release.

references:
[{"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"}, {"href": "https://github.com/simonw/datasette/issues/355", "label": "#355"}, {"href": "https://github.com/simonw/datasette/issues/551", "label": "facet by many-to-many"}]


id: changelog:small-changes
page: changelog
ref: small-changes
title: Small changes
breadcrumbs: ["Changelog", "0.29 (2019-07-07)"]
content:
Databases published using  datasette publish  now open in  Immutable mode . ( #469 ) 
                     
                     
                         ?col__date=  now works for columns containing spaces 
                     
                     
                         Automatic label detection (for deciding which column to show when linking to a foreign key) has been improved. ( #485 ) 
                     
                     
                         Fixed bug where pagination broke when combined with an expanded foreign key. ( #489 ) 
                     
                     
                         Contributors can now run  pip install -e .[docs]  to get all of the dependencies needed to build the documentation, including  cd docs && make livehtml  support. 
                     
                     
                         Datasette's dependencies are now all specified using the  ~=  match operator. ( #532 ) 
                     
                     
                         white-space: pre-wrap  now used for table creation SQL. ( #505 ) 
                     
                 
                 Full list of commits  between 0.28 and 0.29.

references:
[{"href": "https://github.com/simonw/datasette/issues/469", "label": "#469"}, {"href": "https://github.com/simonw/datasette/issues/485", "label": "#485"}, {"href": "https://github.com/simonw/datasette/issues/489", "label": "#489"}, {"href": "https://github.com/simonw/datasette/issues/532", "label": "#532"}, {"href": "https://github.com/simonw/datasette/issues/505", "label": "#505"}, {"href": "https://github.com/simonw/datasette/compare/0.28...0.29", "label": "Full list of commits"}]


id: changelog:id87
page: changelog
ref: id87
title: 0.28 (2019-05-19)
content: A salmagundi of new features!
breadcrumbs: ["Changelog"]
references:
[{"href": "https://adamj.eu/tech/2019/01/18/a-salmagundi-of-django-alpha-announcements/", "label": "salmagundi"}]


id: changelog:v0-28-databases-that-change
page: changelog
ref: v0-28-databases-that-change
title: Supporting databases that change
breadcrumbs: ["Changelog", "0.28 (2019-05-19)"]
content:
From the beginning of the project, Datasette has been designed with read-only databases in mind. If a database is guaranteed not to change it opens up all kinds of interesting opportunities - from taking advantage of SQLite immutable mode and HTTP caching to bundling static copies of the database directly in a Docker container.  The interesting ideas in Datasette  explores this idea in detail. 
                 As my goals for the project have developed, I realized that read-only databases are no longer the right default. SQLite actually supports concurrent access very well provided only one thread attempts to write to a database at a time, and I keep encountering sensible use-cases for running Datasette on top of a database that is processing inserts and updates. 
                 So, as-of version 0.28 Datasette no longer assumes that a database file will not change. It is now safe to point Datasette at a SQLite database which is being updated by another process. 
                 Making this change was a lot of work - see tracking tickets  #418 ,  #419  and  #420 . It required new thinking around how Datasette should calculate table counts (an expensive operation against a large, changing database) and also meant reconsidering the "content hash" URLs Datasette has used in the past to optimize the performance of HTTP caches. 
                 Datasette can still run against immutable files and gains numerous performance benefits from doing so, but this is no longer the default behaviour. Take a look at the new  Performance and caching  documentation section for details on how to make the most of Datasette against data that you know will be staying read-only and immutable.

references:
[{"href": "https://simonwillison.net/2018/Oct/4/datasette-ideas/", "label": "The interesting ideas in Datasette"}, {"href": "https://github.com/simonw/datasette/issues/418", "label": "#418"}, {"href": "https://github.com/simonw/datasette/issues/419", "label": "#419"}, {"href": "https://github.com/simonw/datasette/issues/420", "label": "#420"}]


id: changelog:v0-28-faceting
page: changelog
ref: v0-28-faceting
title: Faceting improvements, and faceting plugins
breadcrumbs: ["Changelog", "0.28 (2019-05-19)"]
content:
Datasette  Facets  provide an intuitive way to quickly summarize and interact with data. Previously the only supported faceting technique was column faceting, but 0.28 introduces two powerful new capabilities: facet-by-JSON-array and the ability to define further facet types using plugins. 
                 Facet by array ( #359 ) is only available if your SQLite installation provides the  json1  extension. Datasette will automatically detect columns that contain JSON arrays of values and offer a faceting interface against those columns - useful for modelling things like tags without needing to break them out into a new table. See  Facet by JSON array  for more. 
                 The new  register_facet_classes()  plugin hook ( #445 ) can be used to register additional custom facet classes. Each facet class should provide two methods:  suggest()  which suggests facet selections that might be appropriate for a provided SQL query, and  facet_results()  which executes a facet operation and returns results. Datasette's own faceting implementations have been refactored to use the same API as these plugins.

references:
[{"href": "https://github.com/simonw/datasette/issues/359", "label": "#359"}, {"href": "https://github.com/simonw/datasette/pull/445", "label": "#445"}]


id: changelog:v0-28-publish-cloudrun
page: changelog
ref: v0-28-publish-cloudrun
title: datasette publish cloudrun
breadcrumbs: ["Changelog", "0.28 (2019-05-19)"]
content:
Google Cloud Run  is a brand new serverless hosting platform from Google, which allows you to build a Docker container which will run only when HTTP traffic is received and will shut down (and hence cost you nothing) the rest of the time. It's similar to Zeit's Now v1 Docker hosting platform which sadly is  no longer accepting signups  from new users. 
                 The new  datasette publish cloudrun  command was contributed by Romain Primet ( #434 ) and publishes selected databases to a new Datasette instance running on Google Cloud Run. 
                 See  Publishing to Google Cloud Run  for full documentation.

references:
[{"href": "https://cloud.google.com/run/", "label": "Google Cloud Run"}, {"href": "https://hyperion.alpha.spectrum.chat/zeit/now/cannot-create-now-v1-deployments~d206a0d4-5835-4af5-bb5c-a17f0171fb25?m=MTU0Njk2NzgwODM3OA==", "label": "no longer accepting signups"}, {"href": "https://github.com/simonw/datasette/pull/434", "label": "#434"}]


id: changelog:v0-28-register-output-renderer
page: changelog
ref: v0-28-register-output-renderer
title: register_output_renderer plugins
breadcrumbs: ["Changelog", "0.28 (2019-05-19)"]
content:
Russ Garrett implemented a new Datasette plugin hook called  register_output_renderer  ( #441 ) which allows plugins to create additional output renderers in addition to Datasette's default  .json  and  .csv . 
                 Russ's in-development  datasette-geo  plugin includes  an example  of this hook being used to output  .geojson  automatically converted from SpatiaLite.

references:
[{"href": "https://github.com/simonw/datasette/pull/441", "label": "#441"}, {"href": "https://github.com/russss/datasette-geo", "label": "datasette-geo"}, {"href": "https://github.com/russss/datasette-geo/blob/d4cecc020848bbde91e9e17bf352f7c70bc3dccf/datasette_plugin_geo/geojson.py", "label": "an example"}]


id: changelog:v0-28-medium-changes
page: changelog
ref: v0-28-medium-changes
title: Medium changes
breadcrumbs: ["Changelog", "0.28 (2019-05-19)"]
content:
Datasette now conforms to the  Black coding style  ( #449 ) - and has a unit test to enforce this in the future 
                     
                     
                         
                             
                                 New  Special table arguments : 
                                 
                                     
                                         
                                             ?columnname__in=value1,value2,value3  filter for executing SQL IN queries against a table, see  Table arguments  ( #433 ) 
                                         
                                         
                                             ?columnname__date=yyyy-mm-dd  filter which returns rows where the spoecified datetime column falls on the specified date ( 583b22a ) 
                                         
                                         
                                             ?tags__arraycontains=tag  filter which acts against a JSON array contained in a column ( 78e45ea ) 
                                         
                                         
                                             ?_where=sql-fragment  filter for the table view  ( #429 ) 
                                         
                                         
                                             ?_fts_table=mytable  and  ?_fts_pk=mycolumn  query string options can be used to specify which FTS table to use for a search query - see  Configuring full-text search for a table or view  ( #428 ) 
                                         
                                     
                                 
                             
                         
                     
                     
                         You can now pass the same table filter multiple times - for example,  ?content__not=world&content__not=hello  will return all rows where the content column is neither  hello  or  world  ( #288 ) 
                     
                     
                         You can now specify  about  and  about_url  metadata (in addition to  source  and  license ) linking to further information about a project - see  Source, license and about 
                     
                     
                         New  ?_trace=1  parameter now adds debug information showing every SQL query that was executed while constructing the page ( #435 ) 
                     
                     
                         datasette inspect  now just calculates table counts, and does not introspect other database metadata ( #462 ) 
                     
                     
                         Removed  /-/inspect  page entirely - this will be replaced by something similar in the future, see  #465 
                     
                     
                         Datasette can now run against an in-memory SQLite database. You can do this by starting it without passing any files or by using the new  --memory  option to  datasette serve . This can be useful for experimenting with SQLite queries that do not access any data, such as  SELECT 1+1  or  SELECT sqlite_version() .

references:
[{"href": "https://github.com/python/black", "label": "Black coding style"}, {"href": "https://github.com/simonw/datasette/pull/449", "label": "#449"}, {"href": "https://github.com/simonw/datasette/issues/433", "label": "#433"}, {"href": "https://github.com/simonw/datasette/commit/583b22aa28e26c318de0189312350ab2688c90b1", "label": "583b22a"}, {"href": "https://github.com/simonw/datasette/commit/78e45ead4d771007c57b307edf8fc920101f8733", "label": "78e45ea"}, {"href": "https://github.com/simonw/datasette/issues/429", "label": "#429"}, {"href": "https://github.com/simonw/datasette/issues/428", "label": "#428"}, {"href": "https://github.com/simonw/datasette/issues/288", "label": "#288"}, {"href": "https://github.com/simonw/datasette/issues/435", "label": "#435"}, {"href": "https://github.com/simonw/datasette/issues/462", "label": "#462"}, {"href": "https://github.com/simonw/datasette/issues/465", "label": "#465"}]


id: changelog:id88
page: changelog
ref: id88
title: Small changes
breadcrumbs: ["Changelog", "0.28 (2019-05-19)"]
content:
We now show the size of the database file next to the download link ( #172 ) 
                     
                     
                         New  /-/databases  introspection page shows currently connected databases ( #470 ) 
                     
                     
                         Binary data is no longer displayed on the table and row pages ( #442  - thanks, Russ Garrett) 
                     
                     
                         New show/hide SQL links on custom query pages ( #415 ) 
                     
                     
                         The  extra_body_script  plugin hook now accepts an optional  view_name  argument ( #443  - thanks, Russ Garrett) 
                     
                     
                         Bumped Jinja2 dependency to 2.10.1 ( #426 ) 
                     
                     
                         All table filters are now documented, and documentation is enforced via unit tests ( 2c19a27 ) 
                     
                     
                         New project guideline: master should stay shippable at all times! ( 31f36e1 ) 
                     
                     
                         Fixed a bug where  sqlite_timelimit()  occasionally failed to clean up after itself ( bac4e01 ) 
                     
                     
                         We no longer load additional plugins when executing pytest ( #438 ) 
                     
                     
                         Homepage now links to database views if there are less than five tables in a database ( #373 ) 
                     
                     
                         The  --cors  option is now respected by error pages ( #453 ) 
                     
                     
                         datasette publish heroku  now uses the  --include-vcs-ignore  option, which means it works under Travis CI ( #407 ) 
                     
                     
                         datasette publish heroku  now publishes using Python 3.6.8 ( 666c374 ) 
                     
                     
                         Renamed  datasette publish now  to  datasette publish nowv1  ( #472 ) 
                     
                     
                         datasette publish nowv1  now accepts multiple  --alias  parameters ( 09ef305 ) 
                     
                     
                         Removed the  datasette skeleton  command ( #476 ) 
                     
                     
                         The  documentation on how to build the documentation  now recommends  sphinx-autobuild

references:
[{"href": "https://github.com/simonw/datasette/issues/172", "label": "#172"}, {"href": "https://github.com/simonw/datasette/issues/470", "label": "#470"}, {"href": "https://github.com/simonw/datasette/pull/442", "label": "#442"}, {"href": "https://github.com/simonw/datasette/issues/415", "label": "#415"}, {"href": "https://github.com/simonw/datasette/pull/443", "label": "#443"}, {"href": "https://github.com/simonw/datasette/pull/426", "label": "#426"}, {"href": "https://github.com/simonw/datasette/commit/2c19a27d15a913e5f3dd443f04067169a6f24634", "label": "2c19a27"}, {"href": "https://github.com/simonw/datasette/commit/31f36e1b97ccc3f4387c80698d018a69798b6228", "label": "31f36e1"}, {"href": "https://github.com/simonw/datasette/commit/bac4e01f40ae7bd19d1eab1fb9349452c18de8f5", "label": "bac4e01"}, {"href": "https://github.com/simonw/datasette/issues/438", "label": "#438"}, {"href": "https://github.com/simonw/datasette/issues/373", "label": "#373"}, {"href": "https://github.com/simonw/datasette/issues/453", "label": "#453"}, {"href": "https://github.com/simonw/datasette/pull/407", "label": "#407"}, {"href": "https://github.com/simonw/datasette/commit/666c37415a898949fae0437099d62a35b1e9c430", "label": "666c374"}, {"href": "https://github.com/simonw/datasette/issues/472", "label": "#472"}, {"href": "https://github.com/simonw/datasette/commit/09ef305c687399384fe38487c075e8669682deb4", "label": "09ef305"}, {"href": "https://github.com/simonw/datasette/issues/476", "label": "#476"}]


id: changelog:id90
page: changelog
ref: id90
title: 0.27 (2019-01-31)
breadcrumbs: ["Changelog"]
content:
New command:  datasette plugins  ( documentation ) shows you the currently installed list of plugins. 
                 
                 
                     Datasette can now output  newline-delimited JSON  using the new  ?_shape=array&_nl=on  query string option. 
                 
                 
                     Added documentation on  The Datasette Ecosystem . 
                 
                 
                     Now using Python 3.7.2 as the base for the official  Datasette Docker image .

references:
[{"href": "http://ndjson.org/", "label": "newline-delimited JSON"}, {"href": "https://hub.docker.com/r/datasetteproject/datasette/", "label": "Datasette Docker image"}]


id: changelog:id91
page: changelog
ref: id91
title: 0.26.1 (2019-01-10)
breadcrumbs: ["Changelog"]
content:
/-/versions  now includes SQLite  compile_options  ( #396 ) 
                 
                 
                     datasetteproject/datasette  Docker image now uses SQLite 3.26.0 ( #397 ) 
                 
                 
                     Cleaned up some deprecation warnings under Python 3.7

references:
[{"href": "https://github.com/simonw/datasette/issues/396", "label": "#396"}, {"href": "https://hub.docker.com/r/datasetteproject/datasette", "label": "datasetteproject/datasette"}, {"href": "https://github.com/simonw/datasette/issues/397", "label": "#397"}]


id: changelog:id92
page: changelog
ref: id92
title: 0.26 (2019-01-02)
breadcrumbs: ["Changelog"]
references: [{"href": "https://github.com/simonw/datasette/issues/393", "label": "#393"}]
content:
datasette serve --reload  now restarts Datasette if a database file changes on disk. 
                 
                 
                     datasette publish now  now takes an optional  --alias mysite.now.sh  argument. This will attempt to set an alias after the deploy completes. 
                 
                 
                     Fixed a bug where the advanced CSV export form failed to include the currently selected filters ( #393 )


id: changelog:id93
page: changelog
ref: id93
title: 0.25.2 (2018-12-16)
breadcrumbs: ["Changelog"]
references: []
content:
datasette publish heroku  now uses the  python-3.6.7  runtime 
                 
                 
                     Added documentation on  how to build the documentation 
                 
                 
                     Added documentation covering  our release process 
                 
                 
                     Upgraded to pytest 4.0.2


id: changelog:id94
page: changelog
ref: id94
title: 0.25.1 (2018-11-04)
breadcrumbs: ["Changelog"]
references: [{"href": "https://github.com/simonw/datasette/issues/366", "label": "#366"}]
content:
Documentation improvements plus a fix for publishing to Zeit Now. 
             
                 
                     datasette publish now  now uses Zeit's v1 platform, to work around the new 100MB image limit. Thanks, @slygent - closes  #366 .


id: changelog:id95
page: changelog
ref: id95
title: 0.25 (2018-09-19)
breadcrumbs: ["Changelog"]
content:
New plugin hooks, improved database view support and an easier way to use more recent versions of SQLite. 
             
                 
                     New  publish_subcommand  plugin hook. A plugin can now add additional  datasette publish  publishers in addition to the default  now  and  heroku , both of which have been refactored into default plugins.  publish_subcommand documentation . Closes  #349 
                 
                 
                     New  render_cell  plugin hook. Plugins can now customize how values are displayed in the HTML tables produced by Datasette's browsable interface.  datasette-json-html  and  datasette-render-images  are two new plugins that use this hook.  render_cell documentation . Closes  #352 
                 
                 
                     New  extra_body_script  plugin hook, enabling plugins to provide additional JavaScript that should be added to the page footer.  extra_body_script documentation . 
                 
                 
                     extra_css_urls  and  extra_js_urls  hooks now take additional optional parameters, allowing them to be more selective about which pages they apply to.  Documentation . 
                 
                 
                     You can now use the  sortable_columns metadata setting  to explicitly enable sort-by-column in the interface for database views, as well as for specific tables. 
                 
                 
                     The new  fts_table  and  fts_pk  metadata settings can now be used to  explicitly configure full-text search for a table or a view , even if that table is not directly coupled to the SQLite FTS feature in the database schema itself. 
                 
                 
                     Datasette will now use  pysqlite3  in place of the standard library  sqlite3  module if it has been installed in the current environment. This makes it much easier to run Datasette against a more recent version of SQLite, including the just-released  SQLite 3.25.0  which adds window function support. More details on how to use this in  #360 
                 
                 
                     New mechanism that allows  plugin configuration options  to be set using  metadata.json .

references:
[{"href": "https://github.com/simonw/datasette/issues/349", "label": "#349"}, {"href": "https://github.com/simonw/datasette-json-html", "label": "datasette-json-html"}, {"href": "https://github.com/simonw/datasette-render-images", "label": "datasette-render-images"}, {"href": "https://github.com/simonw/datasette/issues/352", "label": "#352"}, {"href": "https://github.com/coleifer/pysqlite3", "label": "pysqlite3"}, {"href": "https://www.sqlite.org/releaselog/3_25_0.html", "label": "SQLite 3.25.0"}, {"href": "https://github.com/simonw/datasette/issues/360", "label": "#360"}]


id: changelog:id96
page: changelog
ref: id96
title: 0.24 (2018-07-23)
breadcrumbs: ["Changelog"]
content:
A number of small new features: 
             
                 
                     datasette publish heroku  now supports  --extra-options , fixes  #334 
                 
                 
                     Custom error message if SpatiaLite is needed for specified database, closes  #331 
                 
                 
                     New config option:  truncate_cells_html  for  truncating long cell values  in HTML view - closes  #330 
                 
                 
                     Documentation for  datasette publish and datasette package , closes  #337 
                 
                 
                     Fixed compatibility with Python 3.7 
                 
                 
                     datasette publish heroku  now supports app names via the  -n  option, which can also be used to overwrite an existing application [Russ Garrett] 
                 
                 
                     Title and description metadata can now be set for  canned SQL queries , closes  #342 
                 
                 
                     New  force_https_on  config option, fixes  https://  API URLs when deploying to Zeit Now - closes  #333 
                 
                 
                     ?_json_infinity=1  query string argument for handling Infinity/-Infinity values in JSON, closes  #332 
                 
                 
                     URLs displayed in the results of custom SQL queries are now URLified, closes  #298

references:
[{"href": "https://github.com/simonw/datasette/issues/334", "label": "#334"}, {"href": "https://github.com/simonw/datasette/issues/331", "label": "#331"}, {"href": "https://github.com/simonw/datasette/issues/330", "label": "#330"}, {"href": "https://github.com/simonw/datasette/issues/337", "label": "#337"}, {"href": "https://github.com/simonw/datasette/issues/342", "label": "#342"}, {"href": "https://github.com/simonw/datasette/issues/333", "label": "#333"}, {"href": "https://github.com/simonw/datasette/issues/332", "label": "#332"}, {"href": "https://github.com/simonw/datasette/issues/298", "label": "#298"}]


id: changelog:id105
page: changelog
ref: id105
title: 0.23.2 (2018-07-07)
breadcrumbs: ["Changelog"]
content:
Minor bugfix and documentation release. 
             
                 
                     CSV export now respects  --cors , fixes  #326 
                 
                 
                     Installation instructions , including docker image - closes  #328 
                 
                 
                     Fix for row pages for tables with / in, closes  #325

references:
[{"href": "https://github.com/simonw/datasette/issues/326", "label": "#326"}, {"href": "https://github.com/simonw/datasette/issues/328", "label": "#328"}, {"href": "https://github.com/simonw/datasette/issues/325", "label": "#325"}]


id: changelog:id109
page: changelog
ref: id109
title: 0.23.1 (2018-06-21)
breadcrumbs: ["Changelog"]
content:
Minor bugfix release. 
             
                 
                     Correctly display empty strings in HTML table, closes  #314 
                 
                 
                     Allow "." in database filenames, closes  #302 
                 
                 
                     404s ending in slash redirect to remove that slash, closes  #309 
                 
                 
                     Fixed incorrect display of compound primary keys with foreign key
                        references. Closes  #319 
                 
                 
                     Docs + example of canned SQL query using || concatenation. Closes  #321 
                 
                 
                     Correctly display facets with value of 0 - closes  #318 
                 
                 
                     Default 'expand labels' to checked in CSV advanced export

references:
[{"href": "https://github.com/simonw/datasette/issues/314", "label": "#314"}, {"href": "https://github.com/simonw/datasette/issues/302", "label": "#302"}, {"href": "https://github.com/simonw/datasette/issues/309", "label": "#309"}, {"href": "https://github.com/simonw/datasette/issues/319", "label": "#319"}, {"href": "https://github.com/simonw/datasette/issues/321", "label": "#321"}, {"href": "https://github.com/simonw/datasette/issues/318", "label": "#318"}]


id: changelog:id116
page: changelog
ref: id116
title: 0.23 (2018-06-18)
breadcrumbs: ["Changelog"]
content:
This release features CSV export, improved options for foreign key expansions,
                new configuration settings and improved support for SpatiaLite. 
             See  datasette/compare/0.22.1...0.23  for a full list of
                commits added since the last release.

references:
[{"href": "https://github.com/simonw/datasette/compare/0.22.1...0.23", "label": "datasette/compare/0.22.1...0.23"}]


id: changelog:csv-export
page: changelog
ref: csv-export
title: CSV export
breadcrumbs: ["Changelog", "0.23 (2018-06-18)"]
content:
Any Datasette table, view or custom SQL query can now be exported as CSV. 
                 
                 Check out the  CSV export documentation  for more details, or
                    try the feature out on
                     https://fivethirtyeight.datasettes.com/fivethirtyeight/bechdel%2Fmovies 
                 If your table has more than  max_returned_rows  (default 1,000)
                    Datasette provides the option to  stream all rows . This option takes advantage
                    of async Python and Datasette's efficient  pagination  to
                    iterate through the entire matching result set and stream it back as a
                    downloadable CSV file.

references:
[{"href": "https://fivethirtyeight.datasettes.com/fivethirtyeight/bechdel%2Fmovies", "label": "https://fivethirtyeight.datasettes.com/fivethirtyeight/bechdel%2Fmovies"}]


id: changelog:foreign-key-expansions
page: changelog
ref: foreign-key-expansions
title: Foreign key expansions
breadcrumbs: ["Changelog", "0.23 (2018-06-18)"]
references: []
content:
When Datasette detects a foreign key reference it attempts to resolve a label
                    for that reference (automatically or using the  Specifying the label column for a table  metadata
                    option) so it can display a link to the associated row. 
                 This expansion is now also available for JSON and CSV representations of the
                    table, using the new  _labels=on  query string option. See
                     Expanding foreign key references  for more details.


