home / docs / sections_fts

Menu

sections_fts

795 rows

✎ View and edit SQL

This data as json, CSV (advanced)

Link rowid ▼ title content sections_fts rank
1 1 Configuration Datasette offers several ways to configure your Datasette instances: server settings, plugin configuration, authentication, and more. Most configuration can be handled using a datasette.yaml configuration file, passed to datasette using the -c/--config flag: datasette mydatabase.db --config datasette.yaml This file can also use JSON, as datasette.json . YAML is recommended over JSON due to its support for comments and multi-line strings. 300  
2 2 Configuration via the command-line The recommended way to configure Datasette is using a datasette.yaml file passed to -c/--config . You can also pass individual settings to Datasette using the -s/--setting option, which can be used multiple times: datasette mydatabase.db \ --setting settings.default_page_size 50 \ --setting settings.sql_time_limit_ms 3500 This option takes dotted-notation for the first argument and a value for the second argument. This means you can use it to set any configuration value that would be valid in a datasette.yaml file. It also works for plugin configuration, for example for datasette-cluster-map : datasette mydatabase.db \ --setting plugins.datasette-cluster-map.latitude_column xlat \ --setting plugins.datasette-cluster-map.longitude_column xlon If the value you provide is a valid JSON object or list it will be treated as nested data, allowing you to configure plugins that accept lists such as datasette-proxy-url : datasette mydatabase.db \ -s plugins.datasette-proxy-url.paths '[{"path": "/proxy", "backend": "http://example.com/"}]' This is equivalent to a datasette.yaml file containing the following: [[[cog from metadata_doc import config_example import textwrap config_example(cog, textwrap.dedent( """ plugins: datasette-proxy-url: paths: - path: /proxy backend: http://example.com/ """).strip() ) ]]] [[[end]]] 300  
3 3   The following example shows some of the valid configuration options that can exist inside datasette.yaml . [[[cog from metadata_doc import config_example import textwrap config_example(cog, textwrap.dedent( """ # Datasette settings block settings: default_page_size: 50 sql_time_limit_ms: 3500 max_returned_rows: 2000 # top-level plugin configuration plugins: datasette-my-plugin: key: valueA # Database and table-level configuration databases: your_db_name: # plugin configuration for the your_db_name database plugins: datasette-my-plugin: key: valueA tables: your_table_name: allow: # Only the root user can access this table id: root # plugin configuration for the your_table_name table # inside your_db_name database plugins: datasette-my-plugin: key: valueB """) ) ]]] [[[end]]] 300  
4 4 Settings Settings can be configured in datasette.yaml with the settings key: [[[cog from metadata_doc import config_example import textwrap config_example(cog, textwrap.dedent( """ # inside datasette.yaml settings: default_allow_sql: off default_page_size: 50 """).strip() ) ]]] [[[end]]] The full list of settings is available in the settings documentation . Settings can also be passed to Datasette using one or more --setting name value command line options.` 300  
5 5 Plugin configuration Datasette plugins often require configuration. This plugin configuration should be placed in plugins keys inside datasette.yaml . Most plugins are configured at the top-level of the file, using the plugins key: [[[cog from metadata_doc import config_example import textwrap config_example(cog, textwrap.dedent( """ # inside datasette.yaml plugins: datasette-my-plugin: key: my_value """).strip() ) ]]] [[[end]]] Some plugins can be configured at the database or table level. These should use a plugins key nested under the appropriate place within the databases object: [[[cog from metadata_doc import config_example import textwrap config_example(cog, textwrap.dedent( """ # inside datasette.yaml databases: my_database: # plugin configuration for the my_database database plugins: datasette-my-plugin: key: my_value my_other_database: tables: my_table: # plugin configuration for the my_table table inside the my_other_database database plugins: datasette-my-plugin: key: my_value """).strip() ) ]]] [[[end]]] 300  
6 6 Permissions configuration Datasette's authentication and permissions system can also be configured using datasette.yaml . Here is a simple example: [[[cog from metadata_doc import config_example import textwrap config_example(cog, textwrap.dedent( """ # Instance is only available to users 'sharon' and 'percy': allow: id: - sharon - percy # Only 'percy' is allowed access to the accounting database: databases: accounting: allow: id: percy """).strip() ) ]]] [[[end]]] Access permissions in datasette.yaml has the full details. 300  
7 7 Queries configuration Queries are named SQL queries that appear in the Datasette interface. They can be configured in datasette.yaml using the queries key at the database level: [[[cog from metadata_doc import config_example, config_example config_example(cog, { "databases": { "sf-trees": { "queries": { "just_species": { "sql": "select qSpecies from Street_Tree_List" } } } } }) ]]] [[[end]]] See the queries documentation for more, including how to configure writable queries . 300  
8 8 Custom CSS and JavaScript Datasette can load additional CSS and JavaScript files, configured in datasette.yaml like this: [[[cog from metadata_doc import config_example config_example(cog, """ extra_css_urls: - https://simonwillison.net/static/css/all.bf8cd891642c.css extra_js_urls: - https://code.jquery.com/jquery-3.2.1.slim.min.js """) ]]] [[[end]]] The extra CSS and JavaScript files will be linked in the <head> of every page: <link rel="stylesheet" href="https://simonwillison.net/static/css/all.bf8cd891642c.css"> <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script> You can also specify a SRI (subresource integrity hash) for these assets: [[[cog config_example(cog, """ extra_css_urls: - url: https://simonwillison.net/static/css/all.bf8cd891642c.css sri: sha384-9qIZekWUyjCyDIf2YK1FRoKiPJq4PHt6tp/ulnuuyRBvazd0hG7pWbE99zvwSznI extra_js_urls: - url: https://code.jquery.com/jquery-3.2.1.slim.min.js sri: sha256-k2WSCIexGzOj3Euiig+TlR8gA0EmPjuc79OEeY5L45g= """) ]]] [[[end]]] This will produce: <link rel="stylesheet" href="https://simonwillison.net/static/css/all.bf8cd891642c.css" integrity="sha384-9qIZekWUyjCyDIf2YK1FRoKiPJq4PHt6tp/ulnuuyRBvazd0hG7pWbE99zvwSznI" crossorigin="anonymous"> <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha256-k2WSCIexGzOj3Euiig+TlR8gA0EmPjuc79OEeY5L45g=" crossorigin="anonymous"></script> Modern browsers will only execute the stylesheet or JavaScript if the SRI hash matches the content served. You can generate hashes using www.srihash.org Items in "extra_js_urls" can specify "module": true if they reference JavaScript that uses JavaScript modules . This configuration: [[[cog config_example(cog, """ extra_js_urls: - url: http… 300  
9 9 Table configuration Datasette supports a number of table-level configuration options inside datasette.yaml . These are placed under databases.database_name.tables.table_name . 300  
10 10   By default Datasette tables are sorted by primary key. You can set a default sort order for a specific table using the sort or sort_desc properties: [[[cog from metadata_doc import config_example import textwrap config_example(cog, textwrap.dedent( """ databases: mydatabase: tables: example_table: sort: created """).strip() ) ]]] [[[end]]] Or use sort_desc to sort in descending order: [[[cog config_example(cog, textwrap.dedent( """ databases: mydatabase: tables: example_table: sort_desc: created """).strip() ) ]]] [[[end]]] 300  
11 11   Datasette defaults to displaying 100 rows per page, for both tables and views. You can change this on a per-table or per-view basis using the size key: [[[cog config_example(cog, textwrap.dedent( """ databases: mydatabase: tables: example_table: size: 10 """).strip() ) ]]] [[[end]]] This size can still be over-ridden by passing e.g. ?_size=50 in the query string. 300  
12 12   Datasette allows any column to be used for sorting by default. If you need to control which columns are available for sorting you can do so using sortable_columns : [[[cog config_example(cog, textwrap.dedent( """ databases: mydatabase: tables: example_table: sortable_columns: - height - weight """).strip() ) ]]] [[[end]]] This will restrict sorting of example_table to just the height and weight columns. You can also disable sorting entirely by setting "sortable_columns": [] You can use sortable_columns to enable specific sort orders for a view called name_of_view in the database my_database like so: [[[cog config_example(cog, textwrap.dedent( """ databases: my_database: tables: name_of_view: sortable_columns: - clicks - impressions """).strip() ) ]]] [[[end]]] 300  
13 13   Datasette's HTML interface attempts to display foreign key references as labelled hyperlinks. By default, it automatically detects a label column using the following rules (in order): If there is exactly one unique text column, use that. If there is a column called name or title (case-insensitive), use that. If the table has only two columns - a primary key and one other - use the non-primary-key column. You can override this automatic detection by specifying which column should be used for the link label with the label_column property: [[[cog config_example(cog, textwrap.dedent( """ databases: mydatabase: tables: example_table: label_column: title """).strip() ) ]]] [[[end]]] 300  
14 14   You can hide tables from the database listing view (in the same way that FTS and SpatiaLite tables are automatically hidden) using "hidden": true : [[[cog config_example(cog, textwrap.dedent( """ databases: mydatabase: tables: example_table: hidden: true """).strip() ) ]]] [[[end]]] 300  
15 15   You can turn on facets by default for specific tables. facet_size controls how many unique values are shown for each facet on that table (the default is controlled by the default_facet_size setting). See Facets in configuration for full details. [[[cog config_example(cog, textwrap.dedent( """ databases: sf-trees: tables: Street_Tree_List: facets: - qLegalStatus facet_size: 10 """).strip() ) ]]] [[[end]]] You can also specify array or date facets using JSON objects with a single key of array or date : facets: - array: tags - date: created 300  
16 16   These configure full-text search for a table or view. See Configuring full-text search for a table or view for full details. fts_table specifies which FTS table to use for search. fts_pk sets the primary key column if it is something other than rowid . searchmode can be set to "raw" to enable SQLite advanced search operators . [[[cog config_example(cog, textwrap.dedent( """ databases: russian-ads: tables: display_ads: fts_table: ads_fts fts_pk: id searchmode: raw """).strip() ) ]]] [[[end]]] 300  
17 17   You can assign semantic column types to columns, which affect how values are rendered, validated, transformed, and edited. Built-in column types include url , email , json , and textarea . Plugins can register additional column types using the register_column_types plugin hook. Column types can optionally declare which SQLite column types they apply to using sqlite_types . Datasette will reject incompatible assignments. The built-in url , email , json , and textarea column types are all restricted to TEXT columns. The simplest form maps column names to type name strings: [[[cog config_example(cog, textwrap.dedent( """ databases: mydatabase: tables: example_table: column_types: website: url contact: email extra_data: json notes: textarea """).strip() ) ]]] [[[end]]] For column types that accept additional configuration, use an object with type and config keys: [[[cog config_example(cog, textwrap.dedent( """ databases: mydatabase: tables: example_table: column_types: website: type: url config: prefix: "https://" """).strip() ) ]]] [[[end]]] 300  
18 18 Performance and caching Datasette runs on top of SQLite, and SQLite has excellent performance. For small databases almost any query should return in just a few milliseconds, and larger databases (100s of MBs or even GBs of data) should perform extremely well provided your queries make sensible use of database indexes. That said, there are a number of tricks you can use to improve Datasette's performance. 300  
19 19 Immutable mode If you can be certain that a SQLite database file will not be changed by another process you can tell Datasette to open that file in immutable mode . Doing so will disable all locking and change detection, which can result in improved query performance. This also enables further optimizations relating to HTTP caching, described below. To open a file in immutable mode pass it to the datasette command using the -i option: datasette -i data.db When you open a file in immutable mode like this Datasette will also calculate and cache the row counts for each table in that database when it first starts up, further improving performance. 300  
20 20 Using "datasette inspect" Counting the rows in a table can be a very expensive operation on larger databases. In immutable mode Datasette performs this count only once and caches the results, but this can still cause server startup time to increase by several seconds or more. If you know that a database is never going to change you can precalculate the table row counts once and store then in a JSON file, then use that file when you later start the server. To create a JSON file containing the calculated row counts for a database, use the following: datasette inspect data.db --inspect-file=counts.json Then later you can start Datasette against the counts.json file and use it to skip the row counting step and speed up server startup: datasette -i data.db --inspect-file=counts.json You need to use the -i immutable mode against the database file here or the counts from the JSON file will be ignored. You will rarely need to use this optimization in every-day use, but several of the datasette publish commands described in Publishing data use this optimization for better performance when deploying a database file to a hosting provider. 300  
21 21 HTTP caching If your database is immutable and guaranteed not to change, you can gain major performance improvements from Datasette by enabling HTTP caching. This can work at two different levels. First, it can tell browsers to cache the results of queries and serve future requests from the browser cache. More significantly, it allows you to run Datasette behind a caching proxy such as Varnish or use a cache provided by a hosted service such as Fastly or Cloudflare . This can provide incredible speed-ups since a query only needs to be executed by Datasette the first time it is accessed - all subsequent hits can then be served by the cache. Using a caching proxy in this way could enable a Datasette-backed visualization to serve thousands of hits a second while running Datasette itself on extremely inexpensive hosting. Datasette's integration with HTTP caches can be enabled using a combination of configuration options and query string arguments. The default_cache_ttl setting sets the default HTTP cache TTL for all Datasette pages. This is 5 seconds unless you change it - you can set it to 0 if you wish to disable HTTP caching entirely. You can also change the cache timeout on a per-request basis using the ?_ttl=10 query string parameter. This can be useful when you are working with the Datasette JSON API - you may decide that a specific query can be cached for a longer time, or maybe you need to set ?_ttl=0 for some requests for example if you are running a SQL order by random() query. 300  
22 22 datasette-hashed-urls If you open a database file in immutable mode using the -i option, you can be assured that the content of that database will not change for the lifetime of the Datasette server. The datasette-hashed-urls plugin implements an optimization where your database is served with part of the SHA-256 hash of the database contents baked into the URL. A database at /fixtures will instead be served at /fixtures-aa7318b , and a year-long cache expiry header will be returned with those pages. This will then be cached by both browsers and caching proxies such as Cloudflare or Fastly, providing a potentially significant performance boost. To install the plugin, run the following: datasette install datasette-hashed-urls Prior to Datasette 0.61 hashed URL mode was a core Datasette feature, enabled using the hash_urls setting. This implementation has now been removed in favor of the datasette-hashed-urls plugin. Prior to Datasette 0.28 hashed URL mode was the default behaviour for Datasette, since all database files were assumed to be immutable and unchanging. From 0.28 onwards the default has been to treat database files as mutable unless explicitly configured otherwise. 300  
23 23 Plugins Datasette's plugin system allows additional features to be implemented as Python code (or front-end JavaScript) which can be wrapped up in a separate Python package. The underlying mechanism uses pluggy . See the Datasette plugins directory for a list of existing plugins, or take a look at the datasette-plugin topic on GitHub. Things you can do with plugins include: Add visualizations to Datasette, for example datasette-cluster-map and datasette-vega . Make new custom SQL functions available for use within Datasette, for example datasette-haversine and datasette-jellyfish . Define custom output formats with custom extensions, for example datasette-atom and datasette-ics . Add template functions that can be called within your Jinja custom templates, for example datasette-render-markdown . Customize how database values are rendered in the Datasette interface, for example datasette-render-binary and datasette-pretty-json . Customize how Datasette's authentication and permissions systems work, for example datasette-auth-passwords and datasette-permissions-sql . 300  
24 24 Installing plugins If a plugin has been packaged for distribution using setuptools you can use the plugin by installing it alongside Datasette in the same virtual environment or Docker container. You can install plugins using the datasette install command: datasette install datasette-vega You can uninstall plugins with datasette uninstall : datasette uninstall datasette-vega You can upgrade plugins with datasette install --upgrade or datasette install -U : datasette install -U datasette-vega This command can also be used to upgrade Datasette itself to the latest released version: datasette install -U datasette You can install multiple plugins at once by listing them as lines in a requirements.txt file like this: datasette-vega datasette-cluster-map Then pass that file to datasette install -r : datasette install -r requirements.txt The install and uninstall commands are thin wrappers around pip install and pip uninstall , which ensure that they run pip in the same virtual environment as Datasette itself. 300  
25 25 One-off plugins using --plugins-dir You can also define one-off per-project plugins by saving them as plugin_name.py functions in a plugins/ folder and then passing that folder to datasette using the --plugins-dir option: datasette mydb.db --plugins-dir=plugins/ 300  
26 26 Deploying plugins using datasette publish The datasette publish and datasette package commands both take an optional --install argument. You can use this one or more times to tell Datasette to pip install specific plugins as part of the process: datasette publish cloudrun mydb.db --install=datasette-vega You can use the name of a package on PyPI or any of the other valid arguments to pip install such as a URL to a .zip file: datasette publish cloudrun mydb.db \ --install=https://url-to-my-package.zip 300  
27 27 Controlling which plugins are loaded Datasette defaults to loading every plugin that is installed in the same virtual environment as Datasette itself. You can set the DATASETTE_LOAD_PLUGINS environment variable to a comma-separated list of plugin names to load a controlled subset of plugins instead. For example, to load just the datasette-vega and datasette-cluster-map plugins, set DATASETTE_LOAD_PLUGINS to datasette-vega,datasette-cluster-map : export DATASETTE_LOAD_PLUGINS='datasette-vega,datasette-cluster-map' datasette mydb.db Or: DATASETTE_LOAD_PLUGINS='datasette-vega,datasette-cluster-map' \ datasette mydb.db To disable the loading of all additional plugins, set DATASETTE_LOAD_PLUGINS to an empty string: export DATASETTE_LOAD_PLUGINS='' datasette mydb.db A quick way to test this setting is to use it with the datasette plugins command: DATASETTE_LOAD_PLUGINS='datasette-vega' datasette plugins This should output the following: [ { "name": "datasette-vega", "static": true, "templates": false, "version": "0.6.2", "hooks": [ "extra_css_urls", "extra_js_urls" ] } ] 300  
28 28 Seeing what plugins are installed You can see a list of installed plugins by navigating to the /-/plugins page of your Datasette instance - for example: https://fivethirtyeight.datasettes.com/-/plugins You can also use the datasette plugins command: datasette plugins Which outputs: [ { "name": "datasette_json_html", "static": false, "templates": false, "version": "0.4.0" } ] [[[cog from datasette import cli from click.testing import CliRunner import textwrap, json cog.out("\n") result = CliRunner().invoke(cli.cli, ["plugins", "--all"]) # cog.out() with text containing newlines was unindenting for some reason cog.outl("If you run ``datasette plugins --all`` it will include default plugins that ship as part of Datasette:\n") cog.outl(".. code-block:: json\n") plugins = [p for p in json.loads(result.output) if p["name"].startswith("datasette.")] indented = textwrap.indent(json.dumps(plugins, indent=4), " ") for line in indented.split("\n"): cog.outl(line) cog.out("\n\n") ]]] If you run datasette plugins --all it will include default plugins that ship as part of Datasette: [ { "name": "datasette.actor_auth_cookie", "static": false, "templates": false, "version": null, "hooks": [ "actor_from_request" ] }, { "name": "datasette.blob_renderer", "static": false, "templates": false, "version": null, "hooks": [ "register_output_renderer" ] }, { "name": "datasette.default_actions", "static": false, "templates": false, "version": null, "hooks": [ "register_actions" ] }, { "name": "datasette.default_column_types", "static": false, "templates": false, "version": null, "hooks": [ "register_column_types" ] }, { "name": "d… 300  
29 29 Plugin configuration Plugins can have their own configuration, embedded in a configuration file . Configuration options for plugins live within a "plugins" key in that file, which can be included at the root, database or table level. Here is an example of some plugin configuration for a specific table: [[[cog from metadata_doc import config_example config_example(cog, { "databases": { "sf-trees": { "tables": { "Street_Tree_List": { "plugins": { "datasette-cluster-map": { "latitude_column": "lat", "longitude_column": "lng" } } } } } } }) ]]] [[[end]]] This tells the datasette-cluster-map column which latitude and longitude columns should be used for a table called Street_Tree_List inside a database file called sf-trees.db . 300  
30 30 Secret configuration values Some plugins may need configuration that should stay secret - API keys for example. There are two ways in which you can store secret configuration values. The /-/config introspection endpoint redacts the values of any configuration keys whose names contain one of these substrings: secret , key , password , token , hash or dsn . Name your plugin's secret configuration keys accordingly - for example api_key or client_secret - so they are automatically redacted there. As environment variables . If your secret lives in an environment variable that is available to the Datasette process, you can indicate that the configuration value should be read from that environment variable like so: [[[cog config_example(cog, { "plugins": { "datasette-auth-github": { "client_secret": { "$env": "GITHUB_CLIENT_SECRET" } } } }) ]]] [[[end]]] As values in separate files . Your secrets can also live in files on disk. To specify a secret should be read from a file, provide the full file path like this: [[[cog config_example(cog, { "plugins": { "datasette-auth-github": { "client_secret": { "$file": "/secrets/client-secret" } } } }) ]]] [[[end]]] If you are publishing your data using the datasette publish family of commands, you can use the --plugin-secret option to set these secrets at publish time. For example, using Heroku you might run the following command: datasette publish heroku my_database.db \ --name my-heroku-app-demo \ --install=datasette-auth-github \ --plugin-secret datasette-auth-github client_id your_client_id \ --plugin-secret datasette-auth-github client_secret your_client_secret This will set the necessary environment variables and add the following t… 300  
31 31 Changelog   300  
32 32 1.0a39 (2026-09-10) This alpha release includes security fixes for permissions, SQL construction, HTML rendering, authentication and caching, plus improvements to application startup and write execution. See 0.65.4 for fixes that have been backported to the stable 0.65.x branch. The Datasette blog has more details on these releases . Some of the security fixes include: Table and view permission checks now take SQLite's case-insensitive names into account. See How permissions are resolved . Viewing a full-text search index table now checks you have permission to view the table from which it draws its content. Viewing SQLite statistics tables ( sqlite_stat1 through sqlite_stat4 ) is now denied by a default. Table schema display now obeys the view-table permission. Table filters using ?_through= require permission to view the intermediate table. Foreign-key target and suggestion APIs, incoming foreign-key relationships and their row counts now respect view-table permission. Row endpoints check permissions before resolving primary keys, to avoid revealing the existence of an otherwise invisible primary key. Improved permission checks for the create-table API. See The JSON write API . The write SQL interface now checks view-table permission for tables referenced by CREATE VIEW statements. Fixed SQL identifier escaping for column names from untrusted database schemas. … 300  
33 33 Other improvements and fixes db.execute_write() now has a default execution time limit of 2,000ms. Plugins can override this using time_limit_ms= or disable it using time_limit_ms=None . This limit is independent of the sql_time_limit_ms setting for read queries. Application startup now runs through ASGI lifespan events before requests are accepted, with a first-request fallback for hosts without lifespan support. Thanks, Alex Garcia . ( #2887 ) datasette serve now runs startup hooks and Uvicorn on the same event loop, preserving background tasks started by plugins. The minimum Uvicorn version is now 0.29. Thanks, Alex Garcia . ( #2886 ) Non-blocking writes using execute_write_fn(..., block=False) now return a distinct task UUID for every call and work correctly with num_sql_threads=0 . Thanks, Zain Dana Harper . ( #2860 , #2859 ) Dropping a table now disables its full-text search index first. ( #2874 ) Fixed CREATE VIEW SQL analysis on Python 3.10. 300  
34 34 1.0a38 (2026-08-06) This release fixes a SQL injection security issue that affects Datasette instances that serve a mixture of public and private tables in the same database, with access configured using the Datasette permissions system . Site administrators who serve private tables in this way are advised to disable the execute-sql permission on that database to prevent users from accessing private tables using raw SQL queries. The bug that has been fixed would have allowed users with access to any public table to execute SQL injection attacks despite that restriction, giving them read-only access to data in private tables in the same database. This fix is also available in Datasette 0.65.3. 300  
35 35 1.0a37 (2026-07-14) Performance improvement for SQL-backed permission checks, plus an improved permission debugging interface. SQL used to resolve permission checks now aggregates permission rules before joining them to resources, improving performance on instances with large schemas. ( #2832 ) The Permission check view permission debugger now explains why a decision was allowed or denied, including the matching rules. The interactive form can also test a hypothetical actor supplied as JSON, and the permissions documentation now describes resolution rules in more detail. ( #2841 ) db.execute_write(sql, ..., transaction=True) has a new transaction= parameter, which can be set to False for statements such as VACUUM that cannot run inside a transaction. Write tasks now start their transactions using BEGIN IMMEDIATE , which also ensures that writes are rolled back if the task fails. ( #2831 ) Refreshing a database's schema in Datasette's internal catalog is now performed as a single atomic operation. ( #2831 ) Fixed schema introspection, table pages, facets and table counts for tables with names containing a ] character. Thanks, TowyTowy . ( #2431 , #2846 ) /-/plugins.json once again returns a top-level JSON array of plugin objects, reverting the object envelope introduced in 1.0a36. This should fix a large number of trivial test failures in existing plugins. ( #2842 , #2843 ) 300  
36 36 1.0a36 (2026-07-07) The signature features of this alpha are new UIs for inserting multiple rows at once (from TSV, CSV or JSON) and for creating a table from rows , plus a large number of small JSON API consistency fixes in preparation for a 1.0 stable release. Table pages now offer an "Insert multiple rows" mode in the row insertion dialog. This accepts pasted TSV, CSV or JSON, previews the parsed rows before inserting them, validates unknown columns as data is pasted and displays omitted auto integer primary keys as auto in the preview. ( #2813 ) The bulk insert UI can skip rows with existing primary keys, or update existing rows and insert new rows using the existing /<database>/<table>/-/upsert API when the actor has both insert-row and update-row permissions. ( #2813 ) The "Create table" dialog now includes a "Create table from data" mode. Paste TSV, CSV or JSON rows to preview inferred columns and types, choose the table name and primary key, then create the table and insert those rows in one step. ( #2813 ) Datasette's JSON APIs now consistently encode every BLOB value using the documented binary value JSON format , even when the bytes could be decoded as UTF-8 text. ( #2806 , #2822 ) The insert and edit row dialogs now provide a dedicated control for BLOB values. Existing binary values are shown by byte size, image values under 10MB are previewed as thumbnails, and replacements can be attached, dropped or pasted into the control. ( #2806 , #2822 ) The table and row JSON APIs now support ?_extra=column_details for returning SQLite schema details for columns, including declared type, SQLite affinity, primary key, NOT NULL , default and hidden-column metadata. … 300  
37 37 JSON API: breaking changes JSON error responses now use a single canonical format across every endpoint: {"ok": false, "error": "...", "errors": [...], "status": 400} . The error key joins all error messages together, errors is the full list of messages and status always matches the HTTP status code. The legacy title key is no longer included in JSON errors (it remains available to the HTML error template), and endpoints that previously returned bare {"error": ...} objects have been updated. See Error responses . Every JSON object success response now includes "ok": true , including introspection endpoints such as /-/versions and /-/settings . /-/plugins.json , /-/databases.json and /-/actions.json now return objects - {"ok": true, "plugins": [...]} and equivalents - instead of top-level JSON arrays, so these responses can gain additional keys in the future without a breaking change. The datasette plugins CLI command still outputs a plain array. /-/databases now only lists databases the current actor is allowed to view. It previously listed every attached database, including their filesystem paths, to any actor with view-instance . Requests with an invalid or expired Authorization: Bearer token now receive a 401 status with the standard error body and a WWW-Authenticate: Bearer error="invalid_token" header, instead of being silently treated as unauthenticated. Bearer tokens that no registered token handler recognizes are still ignored, so authentication plugins with their own token formats keep working. Plugin token handlers can raise the new datasette.TokenInvalid exception to trigger the same behavior. Permission errors for JSON requests now return the standa… 300  
38 38 JSON API: other improvements The write API endpoints now parse the request body as JSON regardless of the Content-Type header, so curl -d invocations work without remembering to set it. Invalid JSON is a 400 error. Cross-site request forgery remains prevented by Datasette's Origin and Sec-Fetch-Site checks. This also fixes a 500 error from the insert API when the Content-Type header was missing entirely. New Response.error(messages, status=400) helper for plugins that need to return a JSON error in Datasette's standard format. See Response class . New count_truncated extra for table JSON, included automatically whenever count is requested. true means the count reached Datasette's counting limit and the real number of rows may be higher. See Expanding JSON responses . JSON endpoints that are not part of the documented stable API now declare themselves with an "unstable" key in their responses. New documentation covering the grammar for boolean query string arguments , the reason upsert returns 200 where insert returns 201 , and advice for plugin authors on naming secret configuration keys so that /-/config redacts them automatically. 300  
39 39 1.0a35 (2026-06-23) This release adds UI for creating tables and altering tables , to complement the insert and update row interfaces added in 1.0a34 (2026-06-16) . New "Create table" interface in the database actions menu, backed by the /<database>/-/create JSON API . It can define columns, primary keys, custom column types, NOT NULL constraints, literal defaults, expression defaults and single-column foreign keys. ( #2787 ) New "Alter table" table action and /<database>/<table>/-/alter JSON API for changing existing tables: add, rename, reorder and drop columns; change column types, defaults, NOT NULL constraints, primary keys and foreign keys; and rename the table. The alter table dialog also includes a "Drop table" button. ( #2788 ) New /<database>/-/foreign-key-targets and /<database>/<table>/-/foreign-key-suggestions JSON APIs for discovering valid single-column foreign key targets and suggested relationships. New Template context documentation listing the variables available to custom templates for Datasette's core pages. Variables documented there are treated as a stable API for custom templates until Datasette 2.0. The documentation is generated from dataclass definitions next to the view code, with tests that compare the documented fields against the actual contexts rendered by the database, table, query and row pages. ( #1510 , #2127 , #1477 , #2803 ) The "Write to this database" page now includes a Create table starter template, alongside the existing Insert, Update and Delete templates. ( #2794 ) New static() template function and datasette.static() method for generating cache-busting static asset URLs based on the file contents. Static assets served with… 300  
40 40 1.0a34 (2026-06-16) The big feature in this alpha is tools to insert, edit and delete rows within the Datasette interface. These features are available on table pages, and edit and delete are also available as action items on the row page. The edit interface takes custom column types into account. Plugins that define their own column types can use JavaScript to customize how those column types are presented in the edit interface. datasette.allowed_many() method for resolving multiple permission checks at once . ( #2775 ) Permission checks are now cached on a per-request basis, speeding up table pages with multiple plugins that check permissions in order to populate the table actions menu . Fixed a warning about gen.throw(*sys.exc_info()) . ( #2776 ) New default custom column type textarea for multi-line text content. This is rendered as a <textarea> input in the edit UI. The json column type now implements client-side validation in the edit UI. The makeColumnField() JavaScript plugin hook allows plugins to define custom fields in the edit interface for their custom column types. New UI for inserting, editing, and deleting rows within Datasette. ( #2780 ) New /<database>/<table>/-/autocomplete?q=term autocomplete JSON API for rapid autocomplete search against the contents of a table. This is used by the edit interface to select related rows for foreign keys. You can try it out on the /-/debug/autocomplete debug page. New /<database>/<table>/-/fragment HTML fragment endpoint for returnin… 300  
41 41 1.0a33 (2026-06-11) Stored queries can now be edited and deleted through the web interface, and the JSON API ?_extra= mechanism has been extended to cover row and query pages in addition to tables. This release also fixes two security issues: an identifier-quoting bug involving table and column names that contain ] , and an open redirect. 300  
42 42 Editing and deleting stored queries The stored query page gained a "Query actions" menu with Edit this query and Delete this query links for actors with the necessary permissions. The owner of a query can always edit or delete it; for queries that are not private, any actor with the update-query or delete-query permission can do so too. Private queries remain editable and deletable only by their owner. See Stored queries for details. ( #2735 ) 300  
43 43   Row and query JSON pages now support the same ?_extra= mechanism as table pages. Row pages can request extras such as foreign_key_tables , query , metadata and database_color ; arbitrary SQL and stored query pages can request extras such as columns , query , metadata and private . The implementation was refactored into a registry of extra classes shared by all three page types. New generated reference documentation describes every ?_extra= parameter available on table, row and query JSON pages, with example output captured from a live Datasette instance at documentation build time. See Expanding JSON responses for the full list. You can explore the new extras using this Datasette extras API explorer tool . Other improvements and fixes to the extras mechanism: Extras that exist to serve the HTML interface ( filters , actions , display_rows ) are no longer advertised or reachable through the JSON API, where requesting them previously returned a 500 serialization error. The pre-1.0 ?_extras= (plural) parameter on row pages has been removed - use ?_extra=foreign_key_tables instead. 300  
44 44 Security fixes Fixed an identifier-quoting bug in datasette.utils.escape_sqlite() . Datasette uses this helper when constructing SQL around table and column names; identifiers containing ] could break out of SQLite bracket quoting and alter the generated SQL, for example by adding a UNION SELECT . Identifiers containing ] are now quoted using double quotes instead. ( #2677 ) Fixed an open redirect vulnerability. Requesting a path such as /\example.com/ produced a redirect with a Location: /\example.com header - browsers normalize backslashes to forward slashes, turning that into the protocol-relative URL //example.com and redirecting the user off-site. Any run of leading slashes and backslashes in a redirect path is now collapsed to a single slash. ( #2680 ) 300  
45 45 Bug fixes can_render() callbacks registered by the register_output_renderer() plugin hook now receive the result rows and columns for stored queries. Previously renderers that inspect the available columns - such as datasette-atom and datasette-ics - never appeared as export options on stored query pages. ( #2711 ) Fixed a 500 error from the /-/check permission debugging endpoint when checking query actions such as view-query , update-query and delete-query . ( #2756 ) Write queries that use a named parameter called :sql no longer fail with an error. ( #2761 ) db.execute_isolated_fn() now works against immutable databases, using a read-only connection that bypasses the write thread. It previously always attempted to open a writable connection, which would fail - breaking features built on top of it, such as the SQL analysis step used when storing a query. An exception raised while opening the connection for an isolated function no longer crashes the write thread. ( #2768 ) Facet counts are now displayed on the same line as the facet value instead of wrapping onto a second line. ( #2754 ) Datasette's pytest plugin no longer imports the rest of Datasette at pytest startup time. This means plugin test suites using pytest-cov now correctly record coverage of code that runs when datasette modules are first imported. 300  
46 46 1.0a32 (2026-05-31) SQLite INSERT ... RETURNING clauses are now supported by /db/-/execute-write , plus several fixes relating to the base_url setting . INSERT / UPDATE / DELETE statements that use SQLite's RETURNING clause now work correctly in the new /db/-/execute-write interface. Datasette fetches returned rows before committing the write transaction, displays them in the HTML UI and includes them in the "rows" key for the JSON API response. ( #2762 , #2763 ) Database.execute_write() now returns an ExecuteWriteResult object instead of the raw sqlite3.Cursor returned by conn.execute() . The new object exposes .rowcount , .lastrowid , .description , .truncated and .fetchall() , and adds return_all= and returning_limit= options for controlling how rows from RETURNING statements are buffered. ( #2763 ) Fixed the /-/jump navigation search endpoint when Datasette is served with a configured base_url . ( #2757 ) Fixed JSON and CSV export links, plus Link: alternate headers, on table, row and query pages when base_url is configured. These could previously be prefixed twice. ( #2759 ) Fixed several other base_url handling bugs, including the API explorer form actions and share links, the /-/patterns development page, permanent redirects such as /- to /-/ and database query redirects from /<database>?sql=... to /<database>/-/query?sql=... . 300  
47 47 1.0a31 (2026-05-28) Datasette now offers users with the necessary permissions the ability to both execute write queries against their database and to save stored queries (renamed from "canned queries") both privately and for use by other members of their Datasette instance. The ability to write is controlled by the new execute-write-sql permission, but the user also needs the relevant insert-row / update-row / delete-row / create-table /etc permissions for the query they are trying to execute. 300  
48 48 Write SQL UI New "Write to this database" interface at /<database>/-/execute-write for running arbitrary writable SQL against mutable databases. The form extracts named parameters, analyzes the SQL, shows the table operations that will be attempted, includes starter templates for INSERT , UPDATE and DELETE statements and links to a newly inserted row when a single-row insert succeeds. This is also available as a JSON API . ( #2742 ) Added the new execute-write-sql permission for running arbitrary writable SQL. Execution is also gated by table-level permissions such as insert-row , update-row and delete-row , and writes to attached databases are rejected. ( #2742 ) The write SQL analyzer now uses a deny-by-default model for unsupported operations. Reads from source tables require view-table permission, schema changes require create-table , alter-table or drop-table as appropriate, and row mutation statements require the full insert-row , update-row and delete-row permission set. SQL functions are allowed and are not separately permission-gated. ( #2748 ) User-supplied write SQL rejects both VACUUM operations and writes to SQLite virtual or shadow tables. These restrictions also apply to untrusted stored write queries; trusted queries in datasette.yml skip these filters. ( #2748 ) 300  
49 49 Stored queries The previous "canned queries" feature has been renamed and expanded into stored queries . Queries configured in datasette.yaml are now loaded into a new queries table in Datasette's internal database , alongside user-created stored queries. ( #2735 ) New stored query management API methods available to plugins: datasette.add_query() , datasette.update_query() , datasette.remove_query() , datasette.get_query() , datasette.list_queries() and datasette.count_queries() . These replace the removed datasette.get_canned_query() and datasette.get_canned_queries() methods. ( #2735 ) Users with store-query and execute-sql permission can create stored queries from the SQL query page or the new GET /<database>/-/queries/store form. ( #2735 ) The database page now shows a count and preview of stored queries, capped at five, and links to new paginated query lists at /-/queries and /<database>/-/queries . Those pages support search. ( #2735 ) Stored queries created by users default to private and untrusted. Private stored queries can only be viewed, updated or deleted by their owner, even if another actor has broad view-query , update-query or delete-query permission. Untrusted stored queries execute using the permissions of the actor running them. See Stored queries and Trusted stored queries for details. ( #2735 ) Configured queries from datasette.yaml are trusted by default, so they can execute with view-query permission alone. They can opt out of that behavior using is_trusted: false but cannot be made private; private queries are only available for user-created stored queries. ( #2735 ) … 300  
50 50 Plugin API changes The top_canned_query() plugin hook has been renamed to top_stored_query() . ( #2747 ) The canned_queries() plugin hook has been removed. Plugins can use the new stored query management methods together with startup() to register queries. ( #2735 ) 300  
51 51 Bug fixes Fixed a bug where visiting /<database>/-/query without a ?sql= parameter returned a 500 error. ( #2743 ) The datasette inspect command now correctly records row counts for tables with more than 10,000 rows. ( #2712 ) 300  
52 52 1.0a30 (2026-05-24) The "Jump to" menu, activated by hitting / or through the application menu, can now be extended by plugins. New "Jump to..." menu item, always visible, for triggering the previously undocumented / menu. ( #2725 ) The / jump-to search interface now covers databases, views, canned queries and plugin-provided items in addition to tables. The endpoint backing it has been renamed from /-/tables to /-/jump . New jump_items_sql(datasette, actor, request) plugin hook, allowing plugins to contribute additional items to the jump-to menu by returning SQL. JumpSQL queries run against Datasette's internal database by default, or can target another database using the optional database= argument. ( #2731 ) datasette.jump.JumpSQL.menu_item() is a shortcut for adding individual jump menu items that are not backed by resources in the internal catalog. New makeJumpSections(context) JavaScript plugin hook, allowing plugins to add custom blank-state sections to the jump-to menu before the user has typed a query. Debug menu links now appear in the jump-to menu instead of the top-right app menu, with descriptions for each debug item. Dropped Janus as a dependency, previously used to manage the write queue. This should not have any impact on plugin developers or end-users. ( #1752 ) Fixed a bug where stale tables and other related resources were not removed from catalog_* tables when a database was removed. ( #2723 ) New documented datasette.fixtures.populate_fixture_database(conn) helper for creating the f… 300  
53 53 1.0a29 (2026-05-12) New TokenRestrictions.abbreviated(datasette) utility method for creating "_r" dictionaries. ( #2695 ) Table headers and column options are now visible even if a table contains zero rows. ( #2701 ) Fixed bug with display of column actions dialog on Mobile Safari. ( #2708 ) Fixed bug where tests could crash with a segfault due to a race condition between Datasette.close() and Datasette.close() . ( #2709 ) 300  
54 54 1.0a28 (2026-04-16) Fixed a compatibility bug introduced in 1.0a27 where execute_write_fn() callbacks with a parameter name other than conn were seeing errors. ( #2691 ) The database.close() method now also shuts down the write connection for that database. New datasette.close() method for closing down all databases and resources associated with a Datasette instance. This is called automatically when the server shuts down. ( #2693 ) Datasette now includes a pytest plugin which automatically calls datasette.close() on temporary instances created in function-scoped fixtures and during tests. See Automatic cleanup of Datasette instances for details. This helps avoid running out of file descriptors in plugin test suites that were written before the Database(is_temp_disk=True) feature introduced in Datasette 1.0a27. ( #2692 ) 300  
55 55 1.0a27 (2026-04-15)   300  
56 56 CSRF protection no longer uses CSRF tokens Datasette's token-based CSRF protection has been replaced with a mechanism based on the Sec-Fetch-Site and Origin request headers, which are supported by all modern browsers . See this article by Filippo Valsorda for more details of this approach. This removes the need for CSRF tokens in forms and AJAX requests. ( #2689 ) 300  
57 57   Renaming a table within Datasette will now fire a new RenameTableEvent , which plugins can use to react by updating ACL records or re-assigning comments or other associated records to the new table name. ( #2681 ) This event will not be fired if the table is renamed by SQL running in some other process. The datasette.track_event() method can now be called from within a write operation (using database.execute_write() and related methods) and the event will be fired after the write transaction has successfully committed. ( #2682 ) 300  
58 58 Other changes New actor= parameter for datasette.client methods, allowing internal requests to be made as a specific actor. This is particularly useful for writing automated tests. ( #2688 ) New Database(is_temp_disk=True) option, used internally for the internal database. This helps resolve intermittent database locked errors caused by the internal database being in-memory as opposed to on-disk. ( #2683 ) ( #2684 ) The /<database>/<table>/-/upsert API ( docs ) now rejects rows with null primary key values. ( #1936 ) Improved example in the API explorer for the /-/upsert endpoint ( docs ). ( #1936 ) The /<database>.json endpoint now includes an "ok": true key, for consistency with other JSON API responses. call_with_supported_arguments() is now documented as a supported public API. ( #2678 ) 300  
59 59 1.0a26 (2026-03-18)   300  
60 60 New Table columns can now have custom column types assigned to them, using the new column_types table configuration option or at runtime using a new UI and POST /<database>/<table>/-/set-column-type JSON API. Built-in column types include url , email , and json , and plugins can register additional types using the new register_column_types() plugin hook. ( #2664 , #2671 ) Column types can customize HTML rendering, validate values written through the insert, update, and upsert APIs, and transform values returned by the JSON API. They can optionally restrict themselves to specific SQLite column types using sqlite_types . This feature also introduces a new set-column-type permission for assigning column types to a table. ( #2672 ) The render_cell() plugin hook now receives a column_type argument containing the assigned type instance, and a column type's own render_cell() method takes priority over the plugin hook chain. The datasette-files plugin will be the first to use this new feature. 300  
61 61 UI for selecting columns and their order Table and view pages now include a dialog for selecting and re-ordering visible columns. ( #2661 ) 300  
62 62 Other changes Fixed allowed_resources("view-query", actor) so actor-specific canned queries are returned correctly. Any plugin that defines a resources_sql() method on a Resource subclass needs to update to the new signature, see the resources_sql() method documentation for details. Column actions can now be accessed in mobile view via a new "Column actions" button. Previously they were not available on mobile because table headers are not displayed there. ( #2669 , #2670 ) Row pages now render foreign key values as links to the referenced row. ( #1592 ) The startup() plugin hook now fires after metadata and internal schema tables have been populated, so plugins can reliably inspect that state during startup. ( #2666 ) 300  
63 63 1.0a25 (2026-02-25)   300  
64 64   A new write_wrapper() plugin hook allows plugins to intercept and wrap database write operations. ( #2636 ) Plugins implement the hook as a generator-based context manager: @hookimpl def write_wrapper(datasette, database, request): def wrapper(conn): # Setup code runs before the write yield # Cleanup code runs after the write return wrapper 300  
65 65   A new register_token_handler() plugin hook allows plugins to provide custom token backends for API authentication. ( #2650 ) This includes a backwards incompatible change : the datasette.create_token() internal method is now an async method. Consult the upgrade guide for details on how to update your code. 300  
66 66   The render_cell() plugin hook now receives a pks parameter containing the list of primary key column names for the table being rendered. This avoids plugins needing to make redundant async calls to look up primary keys. ( #2641 ) 300  
67 67 Other changes Facets defined in metadata now preserve their configured order, instead of being sorted by result count. Request-based facets added via the _facet parameter are still sorted by result count and appear after metadata-defined facets. ( #2647 ) Fixed --reload incorrectly interpreting the serve command as a file argument. Thanks, Daniel Bates . ( #2646 ) 300  
68 68 1.0a24 (2026-01-29)   300  
69 69   Datasette now includes a request.form() method for parsing form submissions, including handling file uploads. ( #2626 ) This supports both application/x-www-form-urlencoded and multipart/form-data content types, and uses a new streaming multipart parser that processes uploads without buffering entire request bodies in memory. # Parse form fields (files are discarded by default) form = await request.form() username = form["username"] # Parse form fields AND file uploads form = await request.form(files=True) uploaded = form["avatar"] content = await uploaded.read() The returned FormData object provides dictionary-style access with support for multiple values per key via form.getlist("key") . Uploaded files are represented as UploadedFile objects with filename , content_type , size properties and async read() and seek() methods. Files smaller than 1MB are held in memory; larger files automatically spill to temporary files on disk. Configurable limits control maximum file size, request size, field counts and more. Several internal views (permissions debug, messages debug, create token) now use request.form() instead of request.post_vars() . request.post_vars() remains available for backwards compatibility but is no longer the recommended API for handling POST data. 300  
70 70   The table JSON API now supports ?_extra=render_cell , which returns the rendered HTML for each cell as produced by the render_cell plugin hook . Only columns whose rendered output differs from the default are included. ( #2619 ) The row JSON API also gains ?_extra=render_cell and ?_extra=foreign_key_tables extras, bringing it closer to parity with the table API. The row JSON API now returns "ok": true in its response, for consistency with the table API. 300  
71 71   The recommended development environment for Datasette now uses uv . You can now set up a development environment and run the test suite with just uv run pytest — no manual virtualenv or pip install step required. ( #2611 ) 300  
72 72 Other changes Plugins that raise datasette.utils.StartupError() during startup now display a clean error message instead of a full traceback. ( #2624 ) Schema refreshes are now throttled to at most once per second, providing a small performance increase. ( #2629 ) Minor performance improvement to remove_infinites — rows without infinity values now skip the list/dict reconstruction step. ( #2629 ) Filter inputs and the search input no longer trigger unwanted zoom on iOS Safari. Thanks, Daniel Olasubomi Sobowale . ( #2346 ) table_names() and get_all_foreign_keys() now return results in deterministic sorted order. ( #2628 ) Switched linting to ruff and fixed all lint errors. ( #2630 ) 300  
73 73 1.0a23 (2025-12-02) Fix for bug where a stale database entry in internal.db could cause a 500 error on the homepage. ( #2605 ) Cosmetic improvement to /-/actions page. ( #2599 ) 300  
74 74 1.0a22 (2025-11-13) datasette serve --default-deny option for running Datasette configured to deny all permissions by default . ( #2592 ) datasette.is_client() method for detecting if code is executing inside a datasette.client request . ( #2594 ) datasette.pm property can now be used to register and unregister plugins in tests . ( #2595 ) 300  
75 75 1.0a21 (2025-11-05) Fixes an open redirect security issue: Datasette instances would redirect to example.com/foo/bar if you accessed the path //example.com/foo/bar . Thanks to James Jefferies for the fix. ( #2429 ) Fixed datasette publish cloudrun to work with changes to the underlying Cloud Run architecture. ( #2511 ) New datasette --get /path --headers option for inspecting the headers returned by a path. ( #2578 ) New datasette.client.get(..., skip_permission_checks=True) parameter to bypass permission checks when making requests using the internal client. ( #2583 ) 300  
76 76 0.65.2 (2025-11-05) Fixes an open redirect security issue: Datasette instances would redirect to example.com/foo/bar if you accessed the path //example.com/foo/bar . Thanks to James Jefferies for the fix. ( #2429 ) Upgraded for compatibility with Python 3.14. Fixed datasette publish cloudrun to work with changes to the underlying Cloud Run architecture. ( #2511 ) Minor upgrades to fix warnings, including pkg_resources deprecation. 300  
77 77 1.0a20 (2025-11-03) This alpha introduces a major breaking change prior to the 1.0 release of Datasette concerning how Datasette's permission system works. 300  
78 78 Permission system redesign Previously the permission system worked using datasette.permission_allowed() checks which consulted all available plugins in turn to determine whether a given actor was allowed to perform a given action on a given resource. This approach could become prohibitively expensive for large lists of items - for example to determine the list of tables that a user could view in a large Datasette instance each plugin implementation of that hook would be fired for every table. The new design uses SQL queries against Datasette's internal catalog tables to derive the list of resources for which an actor has permission for a given action. This turns an N x M problem (N resources, M plugins) into a single SQL query. Plugins can use the new permission_resources_sql(datasette, actor, action) hook to return SQL fragments which will be used as part of that query. Plugins that use any of the following features will need to be updated to work with this and following alphas (and Datasette 1.0 stable itself): Checking permissions with datasette.permission_allowed() - this method has been replaced with datasette.allowed() . Implementing the permission_allowed() plugin hook - this hook has been removed in favor of permission_resources_sql() . Using register_permissions() to register permissions - this hook has been removed in favor of register_actions() . Consult the v1.0a20 upgrade guide for further details on how to upgrade affected plugins. Plugins can now make use of two new internal methods to help resolve permission checks: datasette.allowed_resources() returns a PaginatedResources object with a .re… 300  
79 79 Other changes The internal catalog_views table now tracks SQLite views alongside tables in the introspection database. ( #2495 ) Hitting the / brings up a search interface for navigating to tables that the current user can view. A new /-/tables endpoint supports this functionality. ( #2523 ) Datasette attempts to detect some configuration errors on startup. Datasette now supports Python 3.14 and no longer tests against Python 3.9. 300  
80 80 1.0a19 (2025-04-21) Tiny cosmetic bug fix for mobile display of table rows. ( #2479 ) 300  
81 81 1.0a18 (2025-04-16) Fix for incorrect foreign key references in the internal database schema. ( #2466 ) The prepare_connection() hook no longer runs for the internal database. ( #2468 ) Fixed bug where link: HTTP headers used invalid syntax. ( #2470 ) No longer tested against Python 3.8. Now tests against Python 3.13. FTS tables are now hidden by default if they correspond to a content table. ( #2477 ) Fixed bug with foreign key links to rows in databases with filenames containing a special character. Thanks, Jack Stratton . ( #2476 ) 300  
82 82 1.0a17 (2025-02-06) DATASETTE_SSL_KEYFILE and DATASETTE_SSL_CERTFILE environment variables as alternatives to --ssl-keyfile and --ssl-certfile . Thanks, Alex Garcia. ( #2422 ) SQLITE_EXTENSIONS environment variable has been renamed to DATASETTE_LOAD_EXTENSION . ( #2424 ) datasette serve environment variables are now documented here . The register_magic_parameters(datasette) plugin hook can now register async functions. ( #2441 ) Datasette is now tested against Python 3.13. Breadcrumbs on database and table pages now include a consistent self-link for resetting query string parameters. ( #2454 ) Fixed issue where Datasette could crash on metadata.json with nested values. ( #2455 ) New internal methods datasette.set_actor_cookie() and datasette.delete_actor_cookie() , described here . ( #1690 ) /-/permissions page now shows a list of all permissions registered by plugins. ( #1943 ) If a table has a single unique text column Datasette now detects that as the foreign key label for that table. ( #2458 ) The /-/permissions page now includes options for filtering or exclude permission checks recorded against the current user. ( #2460 ) Fixed a bug where replacing a database with a new one with the same name did not pick up the new database correctly. ( #2465 ) 300  
83 83 0.65.1 (2024-11-28) Fixed bug with upgraded HTTPX 0.28.0 dependency. ( #2443 ) 300  
84 84 0.65 (2024-10-07) Upgrade for compatibility with Python 3.13 (by vendoring Pint dependency). ( #2434 ) Dropped support for Python 3.8. 300  
85 85 1.0a16 (2024-09-05) This release focuses on performance, in particular against large tables, and introduces some minor breaking changes for CSS styling in Datasette plugins. Removed the unit conversions feature and its dependency, Pint. This means Datasette is now compatible with the upcoming Python 3.13. ( #2400 , #2320 ) The datasette --pdb option now uses the ipdb debugger if it is installed. You can install it using datasette install ipdb . Thanks, Tiago Ilieve . ( #2342 ) Fixed a confusing error that occurred if metadata.json contained nested objects. ( #2403 ) Fixed a bug with ?_trace=1 where it returned a blank page if the response was larger than 256KB. ( #2404 ) Tracing mechanism now also displays SQL queries that returned errors or ran out of time. datasette-pretty-traces 0.5 includes support for displaying this new type of trace. ( #2405 ) Fixed a text spacing with table descriptions on the homepage. ( #2399 ) Performance improvements for large tables: Suggested facets now only consider the first 1000 rows. ( #2406 ) Improved performance of date facet suggestion against large tables. ( #2407 ) Row counts stop at 10,000 rows when listing tables. ( #2398 ) … 300  
86 86 1.0a15 (2024-08-15) Datasette now defaults to hiding SQLite "shadow" tables, as seen in extensions such as SQLite FTS and sqlite-vec . Virtual tables that it makes sense to display, such as FTS core tables, are no longer hidden. Thanks, Alex Garcia . ( #2296 ) Fixed bug where running Datasette with one or more -s/--setting options could over-ride settings that were present in datasette.yml . ( #2389 ) The Datasette homepage is now duplicated at /-/ , using the default index.html template. This ensures that the information on that page is still accessible even if the Datasette homepage has been customized using a custom index.html template, for example on sites like datasette.io . ( #2393 ) Failed CSRF checks now display a more user-friendly error page. ( #2390 ) Fixed a bug where the json1 extension was not correctly detected on the /-/versions page. Thanks, Seb Bacon . ( #2326 ) Fixed a bug where the Datasette write API did not correctly accept Content-Type: application/json; charset=utf-8 . ( #2384 ) Fixed a bug where Datasette would fail to start if metadata.yml contained a queries block. ( #2386 ) 300  
87 87 1.0a14 (2024-08-05) This alpha introduces significant changes to Datasette's Metadata system, some of which represent breaking changes in advance of the full 1.0 release. The new Upgrade guide document provides detailed coverage of those breaking changes and how they affect plugin authors and Datasette API consumers. The /databasename?sql= interface and JSON API for executing arbitrary SQL queries can now be found at /databasename/-/query?sql= . Requests with a ?sql= parameter to the old endpoints will be redirected. Thanks, Alex Garcia . ( #2360 ) Metadata about tables, databases, instances and columns is now stored in Datasette's internal database . Thanks, Alex Garcia. ( #2341 ) Database write connections now execute using the IMMEDIATE isolation level for SQLite. This should help avoid a rare SQLITE_BUSY error that could occur when a transaction upgraded to a write mid-flight. ( #2358 ) Fix for a bug where canned queries with named parameters could fail against SQLite 3.46. ( #2353 ) Datasette now serves E-Tag headers for static files. Thanks, Agustin Bacigalup . ( #2306 ) Dropdown menus now use a z-index that should avoid them being hidden by plugins. ( #2311 ) Incorrect table and row names are no longer reflected back on the resulting 404 page. ( #2359 ) Improved documentation for async usage of the track_event(datasette, event) hook. ( #2319 ) Fixed some HTTPX deprecation warnings. ( #2307 ) Datasette now serves a <html lang="en"> attrib… 300  
88 88 0.64.8 (2024-06-21) 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 ) 300  
89 89 0.64.7 (2024-06-12) Fixed a bug where canned queries with named parameters threw an error when run against SQLite 3.46.0. ( #2353 ) 300  
90 90 1.0a13 (2024-03-12) 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 ) 300  
91 91 1.0a12 (2024-02-29) 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 ) 300  
92 92 1.0a11 (2024-02-19) 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 ) 300  
93 93 1.0a10 (2024-02-17) 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. 300  
94 94 1.0a9 (2024-02-16) This alpha release adds basic alter table support to the Datasette Write API and fixes a permissions bug relating to the /upsert API endpoint. 300  
95 95 Alter table support for create, insert, upsert and update 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 . 300  
96 96 Permissions fix for the upsert API 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. 300  
97 97 Permission checks now consider opinions from every plugin 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 . 300  
98 98 Other changes 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 ) 300  
99 99 1.0a8 (2024-02-07) 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. 300  
100 100 Configuration 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. 300  

Next page

Advanced export

JSON shape: default, array, newline-delimited

CSV options:

CREATE VIRTUAL TABLE "sections_fts" USING FTS5 (
    "title", "content",
    tokenize='porter',
    content="sections"
);
Powered by Datasette · Queries took 1.2ms