home / docs

Menu

docs

Custom SQL query returning 101 rows (hide)

This data as json, CSV

rowidtitlecontentsections_ftsrank
1 Writing plugins 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. 335  
2 Tracing plugin hooks 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'}] 335  
3 Writing one-off plugins 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. 335  
4 Starting an installable plugin using cookiecutter 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. 335  
5 Packaging a plugin 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 . 335  
6 Static assets 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.static(path, plugin=plugin_name) method to generate cache-busting URLs to those assets that take the base_url setting into account, see .static(path, plugin=None, mount=None) . This can also be used from plugin templates as the static() template function: <script src="{{ static('plugin.js', plugin='datasette_plugin_name') }}" defer></script> 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. See Writing custom CSS for tips on writing CSS that is compatible with Datasette's default CSS, including details of the core class for applying Datasette's default form element styles. 335  
7 Custom templates 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. 335  
8 Writing plugins that accept configuration 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. 335  
9 Designing URLs for your plugin 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. 335  
10 Building URLs within plugins 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. 335  
11 Plugins that define new plugin hooks 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. 335  
12 The Datasette Ecosystem Datasette sits at the center of a growing ecosystem of open source tools aimed at making it as easy as possible to gather, analyze and publish interesting data. These tools are divided into two main groups: tools for building SQLite databases (for use with Datasette) and plugins that extend Datasette's functionality. The Datasette project website includes a directory of plugins and a directory of tools: Plugins directory on datasette.io Tools directory on datasette.io 335  
13 sqlite-utils sqlite-utils is a key building block for the wider Datasette ecosystem. It provides a collection of utilities for manipulating SQLite databases, both as a Python library and a command-line utility. Features include: Insert data into a SQLite database from JSON, CSV or TSV, automatically creating tables with the correct schema or altering existing tables to add missing columns. Configure tables for use with SQLite full-text search, including creating triggers needed to keep the search index up-to-date. Modify tables in ways that are not supported by SQLite's default ALTER TABLE syntax - for example changing the types of columns or selecting a new primary key for a table. Adding foreign keys to existing database tables. Extracting columns of data into a separate lookup table. 335  
14 Dogsheep Dogsheep is a collection of tools for personal analytics using SQLite and Datasette. The project provides tools like github-to-sqlite and twitter-to-sqlite that can import data from different sources in order to create a personal data warehouse. Personal Data Warehouses: Reclaiming Your Data is a talk that explains Dogsheep and demonstrates it in action. 335  
15 Getting started   335  
16 Play with a live demo The best way to experience Datasette for the first time is with a demo: datasette.io/global-power-plants 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 . 335  
17 Follow a tutorial 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. 335  
18 Datasette in your browser with Datasette Lite 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 . 335  
19 Try Datasette without installing anything with Codespaces GitHub Codespaces offers a free browser-based development environment that lets you run a development server without installing any local software. Here's a demo project on GitHub which you can use as the basis for your own experiments: github.com/datasette/datasette-studio The README file in that repository has instructions on how to get started. 335  
20 Using Datasette on your own computer 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", ... } ] } 335  
21 CLI reference The datasette CLI tool provides a number of commands. Running datasette without specifying a command runs the default command, datasette serve . See datasette serve for the full list of options for that command. [[[cog from datasette import cli from click.testing import CliRunner import textwrap def help(args): title = "datasette " + " ".join(args) cog.out("\n::\n\n") result = CliRunner().invoke(cli.cli, args) output = result.output.replace("Usage: cli ", "Usage: datasette ") cog.out(textwrap.indent(output, ' ')) cog.out("\n\n") ]]] [[[end]]] 335  
22 datasette --help Running datasette --help shows a list of all of the available commands. [[[cog help(["--help"]) ]]] Usage: datasette [OPTIONS] COMMAND [ARGS]... Datasette is an open source multi-tool for exploring and publishing data About Datasette: https://datasette.io/ Full documentation: https://docs.datasette.io/ Options: --version Show the version and exit. --help Show this message and exit. Commands: serve* Serve up specified SQLite database files with a web UI create-token Create a signed API token for the specified actor ID inspect Generate JSON summary of provided database files install Install plugins and packages from PyPI into the same... package Package SQLite files into a Datasette Docker container plugins List currently installed plugins publish Publish specified SQLite database files to the internet... uninstall Uninstall plugins and Python packages from the Datasette... [[[end]]] Additional commands added by plugins that use the register_commands(cli) hook will be listed here as well. 335  
23 datasette serve This command starts the Datasette web application running on your machine: datasette serve mydatabase.db Or since this is the default command you can run this instead: datasette mydatabase.db Once started you can access it at http://localhost:8001 [[[cog help(["serve", "--help"]) ]]] Usage: datasette serve [OPTIONS] [FILES]... Serve up specified SQLite database files with a web UI Options: -i, --immutable PATH Database files to open in immutable mode -h, --host TEXT Host for server. Defaults to 127.0.0.1 which means only connections from the local machine will be allowed. Use 0.0.0.0 to listen to all IPs and allow access from other machines. -p, --port INTEGER RANGE Port for server, defaults to 8001. Use -p 0 to automatically assign an available port. [0<=x<=65535] --uds TEXT Bind to a Unix domain socket --reload Automatically reload if code or metadata change detected - useful for development --cors Enable CORS by serving Access-Control-Allow- Origin: * --load-extension PATH:ENTRYPOINT? Path to a SQLite extension to load, and optional entrypoint --inspect-file TEXT Path to JSON file created using "datasette inspect" -m, --metadata FILENAME Path to JSON/YAML file containing license/source metadata --template-dir DIRECTORY Path to directory containing custom templates --plugins-dir DIRECTORY Path to directory containing custom plugins --static MOUNT:DIRECTORY Serve static files fr… 335  
24 Environment variables Some of the datasette serve options can be provided by environment variables: DATASETTE_SECRET : Equivalent to the --secret option. DATASETTE_SSL_KEYFILE : Equivalent to the --ssl-keyfile option. DATASETTE_SSL_CERTFILE : Equivalent to the --ssl-certfile option. DATASETTE_LOAD_EXTENSION : Equivalent to the --load-extension option. 335  
25 datasette --get The --get option to datasette serve (or just datasette ) specifies the path to a page within Datasette and causes Datasette to output the content from that path without starting the web server. This means that all of Datasette's functionality can be accessed directly from the command-line. For example: datasette --get '/-/versions.json' | jq . { "python": { "version": "3.8.5", "full": "3.8.5 (default, Jul 21 2020, 10:48:26) \n[Clang 11.0.3 (clang-1103.0.32.62)]" }, "datasette": { "version": "0.46+15.g222a84a.dirty" }, "asgi": "3.0", "uvicorn": "0.11.8", "sqlite": { "version": "3.32.3", "fts_versions": [ "FTS5", "FTS4", "FTS3" ], "extensions": { "json1": null }, "compile_options": [ "COMPILER=clang-11.0.3", "ENABLE_COLUMN_METADATA", "ENABLE_FTS3", "ENABLE_FTS3_PARENTHESIS", "ENABLE_FTS4", "ENABLE_FTS5", "ENABLE_GEOPOLY", "ENABLE_JSON1", "ENABLE_PREUPDATE_HOOK", "ENABLE_RTREE", "ENABLE_SESSION", "MAX_VARIABLE_NUMBER=250000", "THREADSAFE=1" ] } } You can use the --token TOKEN option to send an API token with the simulated request. Or you can make a request as a specific actor by passing a JSON representation of that actor to --actor : datasette --memory --actor '{"id": "root"}' --get '/-/actor.json' The exit code of datasette --get will be 0 if the request succeeds and 1 if the request produced an HTTP status code other than 200 - e.g. a 404 or 500 error. This lets you use datasette --get / to run tests against a Datasette application in a continuous integration environment such as GitHub Actions. 335  
26 datasette serve --help-settings This command outputs all of the available Datasette settings . These can be passed to datasette serve using datasette serve --setting name value . [[[cog help(["--help-settings"]) ]]] Settings: default_page_size Default page size for the table view (default=100) max_returned_rows Maximum rows that can be returned from a table or custom query (default=1000) max_insert_rows Maximum rows that can be inserted at a time using the bulk insert API (default=100) max_post_body_bytes Maximum size in bytes for a POST body read into memory, e.g. JSON API requests - set 0 to disable this limit (default=2097152) num_sql_threads Number of threads in the thread pool for executing SQLite queries (default=3) sql_time_limit_ms Time limit for a SQL query in milliseconds (default=1000) default_facet_size Number of values to return for requested facets (default=30) facet_time_limit_ms Time limit for calculating a requested facet (default=200) facet_suggest_time_limit_ms Time limit for calculating a suggested facet (default=50) allow_facet Allow users to specify columns to facet using ?_facet= parameter (default=True) allow_download Allow users to download the original SQLite database files (default=True) allow_signed_tokens Allow users to create and use signed API tokens (default=True) default_allow_sql Allow anyone to run arbitrary SQL queries (default=True) max_signed_… 335  
27 datasette plugins Output JSON showing all currently installed plugins, their versions, whether they include static files or templates and which Plugin hooks they use. [[[cog help(["plugins", "--help"]) ]]] Usage: datasette plugins [OPTIONS] List currently installed plugins Options: --all Include built-in default plugins --requirements Output requirements.txt of installed plugins --plugins-dir DIRECTORY Path to directory containing custom plugins --help Show this message and exit. [[[end]]] Example output: [ { "name": "datasette-geojson", "static": false, "templates": false, "version": "0.3.1", "hooks": [ "register_output_renderer" ] }, { "name": "datasette-geojson-map", "static": true, "templates": false, "version": "0.4.0", "hooks": [ "extra_body_script", "extra_css_urls", "extra_js_urls" ] }, { "name": "datasette-leaflet", "static": true, "templates": false, "version": "0.2.2", "hooks": [ "extra_body_script", "extra_template_vars" ] } ] 335  
28 datasette install Install new Datasette plugins. This command works like pip install but ensures that your plugins will be installed into the same environment as Datasette. This command: datasette install datasette-cluster-map Would install the datasette-cluster-map plugin. [[[cog help(["install", "--help"]) ]]] Usage: datasette install [OPTIONS] [PACKAGES]... Install plugins and packages from PyPI into the same environment as Datasette Options: -U, --upgrade Upgrade packages to latest version -r, --requirement PATH Install from requirements file -e, --editable TEXT Install a project in editable mode from this path --help Show this message and exit. [[[end]]] 335  
29 datasette uninstall Uninstall one or more plugins. [[[cog help(["uninstall", "--help"]) ]]] Usage: datasette uninstall [OPTIONS] PACKAGES... Uninstall plugins and Python packages from the Datasette environment Options: -y, --yes Don't ask for confirmation --help Show this message and exit. [[[end]]] 335  
30 datasette publish Shows a list of available deployment targets for publishing data with Datasette. Additional deployment targets can be added by plugins that use the publish_subcommand(publish) hook. [[[cog help(["publish", "--help"]) ]]] Usage: datasette publish [OPTIONS] COMMAND [ARGS]... Publish specified SQLite database files to the internet along with a Datasette-powered interface and API Options: --help Show this message and exit. Commands: cloudrun Publish databases to Datasette running on Cloud Run heroku Publish databases to Datasette running on Heroku [[[end]]] 335  
31 datasette publish cloudrun See Publishing to Google Cloud Run . [[[cog help(["publish", "cloudrun", "--help"]) ]]] Usage: datasette publish cloudrun [OPTIONS] [FILES]... Publish databases to Datasette running on Cloud Run Options: -m, --metadata FILENAME Path to JSON/YAML file containing metadata to publish --extra-options TEXT Extra options to pass to datasette serve --branch TEXT Install datasette from a GitHub branch e.g. main --template-dir DIRECTORY Path to directory containing custom templates --plugins-dir DIRECTORY Path to directory containing custom plugins --static MOUNT:DIRECTORY Serve static files from this directory at /MOUNT/... --install TEXT Additional packages (e.g. plugins) to install --plugin-secret <TEXT TEXT TEXT>... Secrets to pass to plugins, e.g. --plugin- secret datasette-auth-github client_id xxx --version-note TEXT Additional note to show on /-/versions --secret TEXT Secret used for signing secure values, such as signed cookies --title TEXT Title for metadata --license TEXT License label for metadata --license_url TEXT License URL for metadata --source TEXT Source label for metadata --source_url TEXT Source URL for metadata --about TEXT About label for metadata --about_url TEXT About URL for metadata -n, --name TEXT Application name to use when building --service TEXT Cloud Run service to deploy (or over-write) --spatialite Enable SpatialLite extension --show-files Output the generated Dockerfile and metad… 335  
32 datasette publish heroku See Publishing to Heroku . [[[cog help(["publish", "heroku", "--help"]) ]]] Usage: datasette publish heroku [OPTIONS] [FILES]... Publish databases to Datasette running on Heroku Options: -m, --metadata FILENAME Path to JSON/YAML file containing metadata to publish --extra-options TEXT Extra options to pass to datasette serve --branch TEXT Install datasette from a GitHub branch e.g. main --template-dir DIRECTORY Path to directory containing custom templates --plugins-dir DIRECTORY Path to directory containing custom plugins --static MOUNT:DIRECTORY Serve static files from this directory at /MOUNT/... --install TEXT Additional packages (e.g. plugins) to install --plugin-secret <TEXT TEXT TEXT>... Secrets to pass to plugins, e.g. --plugin- secret datasette-auth-github client_id xxx --version-note TEXT Additional note to show on /-/versions --secret TEXT Secret used for signing secure values, such as signed cookies --title TEXT Title for metadata --license TEXT License label for metadata --license_url TEXT License URL for metadata --source TEXT Source label for metadata --source_url TEXT Source URL for metadata --about TEXT About label for metadata --about_url TEXT About URL for metadata -n, --name TEXT Application name to use when deploying --tar TEXT --tar option to pass to Heroku, e.g. --tar=/usr/local/bin/gtar --generate-dir DIRECTORY Output generated application files and stop without deploying --h… 335  
33 datasette package Package SQLite files into a Datasette Docker container, see datasette package . [[[cog help(["package", "--help"]) ]]] Usage: datasette package [OPTIONS] FILES... Package SQLite files into a Datasette Docker container Options: -t, --tag TEXT Name for the resulting Docker container, can optionally use name:tag format -m, --metadata FILENAME Path to JSON/YAML file containing metadata to publish --extra-options TEXT Extra options to pass to datasette serve --branch TEXT Install datasette from a GitHub branch e.g. main --template-dir DIRECTORY Path to directory containing custom templates --plugins-dir DIRECTORY Path to directory containing custom plugins --static MOUNT:DIRECTORY Serve static files from this directory at /MOUNT/... --install TEXT Additional packages (e.g. plugins) to install --spatialite Enable SpatialLite extension --version-note TEXT Additional note to show on /-/versions --secret TEXT Secret used for signing secure values, such as signed cookies -p, --port INTEGER RANGE Port to run the server on, defaults to 8001 [1<=x<=65535] --title TEXT Title for metadata --license TEXT License label for metadata --license_url TEXT License URL for metadata --source TEXT Source label for metadata --source_url TEXT Source URL for metadata --about TEXT About label for metadata --about_url TEXT About URL for metadata --help Show this message and exit. [[[end]]] 335  
34 datasette inspect Outputs JSON representing introspected data about one or more SQLite database files. If you are opening an immutable database, you can pass this file to the --inspect-data option to improve Datasette's performance by allowing it to skip running row counts against the database when it first starts running: datasette inspect mydatabase.db > inspect-data.json datasette serve -i mydatabase.db --inspect-file inspect-data.json This performance optimization is used automatically by some of the datasette publish commands. You are unlikely to need to apply this optimization manually. [[[cog help(["inspect", "--help"]) ]]] Usage: datasette inspect [OPTIONS] [FILES]... Generate JSON summary of provided database files This can then be passed to "datasette --inspect-file" to speed up count operations against immutable database files. Options: --inspect-file TEXT --load-extension PATH:ENTRYPOINT? Path to a SQLite extension to load, and optional entrypoint --help Show this message and exit. [[[end]]] 335  
35 datasette create-token Create a signed API token, see datasette create-token . [[[cog help(["create-token", "--help"]) ]]] Usage: datasette create-token [OPTIONS] ID Create a signed API token for the specified actor ID Example: datasette create-token root --secret mysecret To allow only "view-database-download" for all databases: datasette create-token root --secret mysecret \ --all view-database-download To allow "create-table" against a specific database: datasette create-token root --secret mysecret \ --database mydb create-table To allow "insert-row" against a specific table: datasette create-token root --secret myscret \ --resource mydb mytable insert-row Restricted actions can be specified multiple times using multiple --all, --database, and --resource options. Add --debug to see a decoded version of the token. Options: --secret TEXT Secret used for signing the API tokens [required] -e, --expires-after INTEGER Token should expire after this many seconds -a, --all ACTION Restrict token to this action -d, --database DB ACTION Restrict token to this action on this database -r, --resource DB RESOURCE ACTION Restrict token to this action on this database resource (a table, SQL view or named query) --debug Show decoded token --plugins-dir DIRECTORY Path to directory containing custom plugins --help Show this message and exit. [[[end]]] 335  
36 Changelog   335  
37 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. 335  
38 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 ) 335  
39 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. … 335  
40 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… 335  
41 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. 335  
42 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… 335  
43 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… 335  
44 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. 335  
45 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 ) 335  
46   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. 335  
47 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 ) 335  
48 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. 335  
49 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=... . 335  
50 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. 335  
51 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 ) 335  
52 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 ) … 335  
53 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 ) 335  
54 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 ) 335  
55 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… 335  
56 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 ) 335  
57 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 ) 335  
58 1.0a27 (2026-04-15)   335  
59 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 ) 335  
60   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 ) 335  
61 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 ) 335  
62 1.0a26 (2026-03-18)   335  
63 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. 335  
64 UI for selecting columns and their order Table and view pages now include a dialog for selecting and re-ordering visible columns. ( #2661 ) 335  
65 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 ) 335  
66 1.0a25 (2026-02-25)   335  
67   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 335  
68   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. 335  
69   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 ) 335  
70 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 ) 335  
71 1.0a24 (2026-01-29)   335  
72   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. 335  
73   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. 335  
74   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 ) 335  
75 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 ) 335  
76 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 ) 335  
77 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 ) 335  
78 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 ) 335  
79 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. 335  
80 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. 335  
81 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… 335  
82 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. 335  
83 1.0a19 (2025-04-21) Tiny cosmetic bug fix for mobile display of table rows. ( #2479 ) 335  
84 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 ) 335  
85 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 ) 335  
86 0.65.1 (2024-11-28) Fixed bug with upgraded HTTPX 0.28.0 dependency. ( #2443 ) 335  
87 0.65 (2024-10-07) Upgrade for compatibility with Python 3.13 (by vendoring Pint dependency). ( #2434 ) Dropped support for Python 3.8. 335  
88 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 ) … 335  
89 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 ) 335  
90 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… 335  
91 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 ) 335  
92 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 ) 335  
93 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 ) 335  
94 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 ) 335  
95 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 ) 335  
96 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. 335  
97 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. 335  
98 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 . 335  
99 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. 335  
100 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 . 335  
101 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 ) 335  
Powered by Datasette