Annotated version of this introductory video
Datasette is a tool for exploring and publishing data. It helps people take data of any shape, analyze and explore it, and publish it as an interactive website and accompanying API.
Datasette is aimed at data journalists, museum curators, archivists, local governments, scientists, researchers and anyone else who has data that they wish to share with the world. It is part of a wider ecosystem of 44 tools and 154 plugins dedicated to making working with structured data as productive as possible.
Try a demo and explore 33,000 power plants around the world, then follow the tutorial or take a look at some other examples of Datasette in action.
Then read how to get started with Datasette, subscribe to the newsletter and consider signing up for office hours for an in-person conversation about the project.
Exploratory data analysis
Import data from CSVs, JSON, database connections and more. Datasette will automatically show you patterns in your data and help you share your findings with your colleagues.
Instant data publishing
datasette publish lets you instantly publish your data to hosting providers like Google Cloud Run, Heroku or Vercel.
Rapid prototyping
Spin up a JSON API for any data in minutes. Use it to prototype and prove your ideas without building a custom backend.
Latest news and blog
16th September 2026 #
Datasette 0.65.5 and 1.0a40 are out, with a security fix for a table permisisons bypass. Datasette 1.0a40 also adds a mechanism for plugins to run background tasks.
11th September 2026
18th June 2026
11th June 2026
29th May 2026
24th May 2026
21st May 2026
13th May 2026
19th April 2026 #
Here's a new TIL on using SQL functions in Google Sheets to fetch data from Datasette.
15th April 2026 #
Datasette 1.0a27 changes how CSRF protection works in a way that simplifies form and API integration, and introduces a new RenameTableEvent for when a table is renamed by a SQL query.
18th March 2026 #
Datasette 1.0a26 adds a column_types system allowing columns to carry additional type information such as email or url, or extra types defined by plugins. This alpha also adds a UI for selecting and sorting visible columns on a table page.
25th February 2026 #
Datasette 1.0a25 adds write_wrapper() and register_token_handler() plugin hooks.
29th January 2026 #
Datasette 1.0a24 introduces a new await request.form(files=True) method for accepting file uploads, plus various other changes.
5th November 2025 #
Datasette 0.65.2 and Datasette 1.0a21 have been released with a security fix for an open redirect error, see this advisory. The latest Datasette alphas also include a breaking change to Datasette's permission system, described in detail in A new SQL-powered permissions system in Datasette 1.0a20.
6th February 2025 #
Datasette 1.0a17 is the latest Datasette 1.0 alpha release, with bug fixes and small feature improvements from the last few months.
Latest releases
13th November 2026
datasette-cluster-map 0.19 - Datasette plugin that shows a map for any data with latitude/longitude columns
- Now compatible with Datasette 1.0a42.
- Now depends on datasette-leaflet 0.3, which upgrades Leaflet to 1.9.4 and fixes the "Access blocked" tiles returned by
tile.openstreetmap.org. Tile requests now send the origin of your Datasette instance as theirRefererheader, but never the URL of the page showing the map. See Tile layers and the Referer header for how to change or disable this. - The map is now only shown once a row with a valid latitude and longitude has loaded. Tables with latitude and longitude columns that contained no values previously displayed an empty grey map.
- The default tile layer is now
https://tile.openstreetmap.org/{z}/{x}/{y}.png, without the{s}subdomain prefix, as recommended by OpenStreetMap. #48 - The "Showing X of Y" progress message now handles tables where Datasette did not return an exact count.
- Now requires Python 3.11 or higher.
datasette-leaflet 0.3 - A plugin that bundles Leaflet.js for Datasette
- Upgraded the bundled Leaflet from 1.7.1 to 1.9.4. #8
- The JavaScript is still an ES module, loaded with
import(datasette.leaflet.JAVASCRIPT_URL). Importing it continues to set a globalwindow.L, so Leaflet plugins that expect that global, such as Leaflet.markercluster, keep working. #12 - Tile layers now send the origin of the Datasette site as their
Refererheader by default. Datasette serves table pages withReferrer-Policy: no-referrer, which causedtile.openstreetmap.orgto respond with "Access blocked" tiles. The bundled Leaflet changes the default of thereferrerPolicytile layer option tostrict-origin-when-cross-origin, so tile servers seehttps://example.com/but never the path or query string of the page showing the map. This applies to every plugin that uses this copy of Leaflet. - New
tile_referrer_policyplugin setting for changing that policy, or turning it off withfalse. The value is available to JavaScript asdatasette.leaflet.TILE_REFERRER_POLICY. ThereferrerPolicyoption on an individual tile layer takes precedence. See Tile layers and the Referer header. - New
update_leaflet.pyscript for downloading and bundling a new version of Leaflet. - Now requires Python 3.11 or higher.
- Added an Apache 2.0
LICENSEfile. #11
11th November 2026
llm 0.37.2 - CLI utility and Python library for interacting with Large Language Models from organizations like OpenAI, Anthropic and Gemini plus local models installed on your own machine.
- Security: Models in
extra-openai-models.yamlwith anapi_baseand anapi_key_nameno longer send theOPENAI_API_KEYenvironment variable to that endpoint when the named key has not been stored. They now fail with aNo key founderror instead. GHSA-v9h7-jj4g-f862 - Security: Models with
responses: trueand anapi_baseno longer send your storedopenaikey or theOPENAI_API_KEYenvironment variable to that endpoint when run with-o chat_completions 1. This also applied tollm openai endpoint URL --responses -o chat_completions 1run without--key. GHSA-r639-rr7r-x72w -a/--attachmentand--at/--attachment-typenow work with pipes such as Bash process substitution, sollm "Describe this image" -a <(cat image.png)works. Previously LLM could not detect the type of a piped attachment, and--atcould lose its content before it was sent. Thanks, Steven Huang. #971, #1857- An empty attachment, such as
llm -a -with nothing on standard input or-awith an empty file, now shows the usualCould not determine mimetypeerror instead of a traceback. Thanks, Ansansan. #1853, #1854 llm openai endpoint addno longer breaksextra-openai-models.yamlwhen the existing list is indented or ends with a...document end marker. Previously the file stopped parsing, so LLM could not load any models until it was fixed by hand. Thanks, ledd. #1864, #1865llm openai endpoint list --jsonno longer crashes with aTypeErrorwhenextra-openai-models.yamlcontains an unquoted date, such asapi_version: 2024-02-01. Thanks, Steven Huang. #1850- In the Python API, reopening an embedding collection with a
model=other than the one it was created with now uses the collection's stored model. Previously new embeddings were calculated with the supplied model, mixing vectors from two models in one collection. Thanks, Chen Yufeiyang. #1859
8th November 2026
datasette 1.0a42 - An open source multi-tool for exploring and publishing data
A redesigned homepage, redesigned table, row and query pages, a new "Export data" dialog with TSV support, a column statistics feature, and Python 3.11 is now the minimum supported version.
Python 3.11 or higher
- Datasette now requires Python 3.11 or higher. This allowed the removal of several compatibility shims. (#2998)
- Datasette is now tested on Python 3.11, 3.12, 3.13, 3.14, and 3.15.
Export data dialog and TSV
- Table, query and row pages replace the "This data as..." links and the "Advanced export" box with an Export data button. This opens a dialog listing every available format - TSV, CSV, JSON, JSON array, newline-delimited JSON, JSON object and any plugin formats - each with Copy, View, Download and Copy URL actions. Old
#exportlinks open the dialog. See CSV and TSV export. (#3037) - New TSV export, for pasting into Excel and Google Sheets. TSV uses the same streaming code, options and URL parameters as CSV, with Excel-compatible quoting.
- register_output_renderer dictionaries can now include optional
labelanddescriptionkeys, shown in the Export data dialog, plusis_binaryandis_webpageflags that control which actions the dialog offers for that format.
Column statistics
- The column action menu has a new Column statistics item, available to actors with the
execute-sqlpermission. It opens a dialog showing counts of rows, nulls and distinct values, a breakdown of storage types, numeric statistics with a histogram, box plot and percentiles, text length statistics, a chart of dates grouped by month, year or decade, and the most common values. Every query it runs is listed, with a link to open it in the SQL editor. (#3038) - Column statistics are also available for the results of arbitrary SQL queries and stored queries.
Table page redesign
- A refreshed design for tables on the table, row and query pages, with right-aligned numbers and shadows indicating horizontally scrollable content. (#3032)
- Values taller than three lines are clamped to a preview with an "expand" button. Column headers offer an "expand all" toggle.
- A pin in the primary key column header freezes the header row and primary key columns while scrolling. On query pages it freezes just the header row. The choice is remembered.
- Tables that span more than one page now show a pagination bar, for example "Rows 101-150 of 1,761", with "Back to first page" and "Next page" buttons. This uses a new
?_count=beforeoption for the count endpoint, which counts the rows before a?_next=cursor. (#3043) - The row count is now shown in the table page heading, and the pagination bar sits directly above the table. (#3048)
- The
CREATE TABLEorCREATE VIEWstatement is now collapsed into a "Table schema" or "View definition" disclosure at the bottom of the page. - Facet values now show a faint bar indicating their share of the rows. Suggested facets and facet results have been restyled.
- New Clear filters button next to "Apply filters", shown when column filters are applied.
Row page redesign
- The row page now shows the row as a list with one field per line, each with a button to copy its value, in place of a single-row table. Long values are clamped with a toggle to show them in full, and the tables that reference the row are listed below it. (#3052)
- Backwards incompatible: the row page now includes a new
_row_fields.htmltemplate instead of_table.html. A custom_table.htmltemplate that applies to every table no longer changes the row page - provide a_row_fields.htmltemplate instead. Templates for a specific table,_table-mydatabase-mytable.htmland_table-row-mydatabase-mytable.html, still apply to the row page. See Custom templates. - The row page no longer contains a
table.rows-and-columnselement, so plugin JavaScript that looks for that table on row pages will not find it. The.above-table-panelelement plugins can attach to is still present.
Homepage redesign
- The homepage now shows one searchable catalog of tables, views and stored queries across every database, instead of grouping them by database. (#3049)
- A search box, focused when the page loads, matches table and view names, column names, table descriptions and the names, titles, descriptions and SQL of stored queries. Results update as you type and can be navigated with the arrow keys and Enter.
- Tables, views, queries and databases you visit are recorded in the browser's
localStorage, separately for each signed-in actor, and shown in a Recently visited section. The "Recent" list in the Jump to menu now uses the same history. - Database filter buttons (
?database=) are shown when more than one database is attached. The table list is built from the internalcatalog_*tables so it stays fast with thousands of tables, and is paginated 30 at a time. - New makeHomepageSections() JavaScript plugin hook for adding sections to the homepage. Items added to Jump to using the jump_items_sql(datasette, actor, request) plugin hook are included in search results.
- The stored query browser at
/-/querieshas been redesigned to match the new homepage. - Backwards incompatible: the
index.htmltemplate context has changed and is now documented in Template context. Customindex.htmltemplates that used keys such astables_and_views_truncatedortable_rows_sumwill need to be updated. The/.jsonoutput is unchanged.
Other changes
- App, database, table, row and column action menus have been redesigned to share positioning, dismissal and keyboard navigation behavior. See the new /-/patterns/menus page. (#2984)
- The /-/jump endpoint now supports
?type=filtering, for example?type=table&type=database. Items now include the URL arguments used to build their URL as fields, hidden tables are excluded, and stored queries use their title as their display name. (#3024, #3025) - The internal
catalog_tablestable has a newhiddenboolean column, populated for tables that Datasette treats as hidden from default table lists. - Error responses now use the JSON error format when the request sends an
Accept: application/jsonorContent-Type: application/jsonheader, not just for URLs ending in.json. Thanks, kokokoXUY. (#1949, #2961) - HTML table pages no longer send a
rel="next"HTTPLinkheader. (#3046) edit-tools.js,export-dialog.jsandcolumn-stats.jsare now loaded on demand, the first time they are needed, rather than on every page. The table page now only includes the editing tools loader if the actor can insert, alter, update or delete, indicated by a newrow_mutation_uitemplate context variable. (#3042)
Bug fixes
execute-writeoperations now work against tables with FTS index triggers, such as those created by sqlite-utilsenable_fts(create_triggers=True). The SQL analysis table now shows why an operation was rejected.- The
aboutandabout_urlmetadata are now displayed even if nosourceorlicenseis set. (#512, #2985) - The query page heading now says "returning more than N rows" when results are truncated.
- A stored query that references a missing
:_actor_Xkey now raises aKeyErrornaming that key. - Facet counts on the second and later pages of a table now count every matching row, and facet links no longer keep the
?_next=pagination cursor. Thanks, b6tang. (#3021, #3026) - The
?_json=option now decodes JSON columns on table pages, no longer replaces row values when combined with?_extra=columns, and works with?_shape=arraysand?_shape=arrayfirst. Thanks, IlyaKhalafi. (#3003, #3005) - Views with a BLOB column no longer return a 500 error, and CSV exports of those views now include a link to download each value. (#2601)
- Fixed a bug where Datasette running with base_url produced links and redirects that repeated the prefix, such as
/prefix/prefix/, when the prefix was included in the request path. This affected filter and sort redirects, column sort links, next page links, facet links and the query page. (#2347, #1633) - Sorting a table that has no primary key by a column hidden using
?_nocol=or?_col=no longer returns a 500 error when the results span more than one page. Thanks, feiiiiii5. (#2974, #2975) - Full-text search now works for FTS tables that declare their content table unquoted or with single quotes or backticks, for example
content=documents. Previously these tables showed no search box and?_search=was ignored. Thanks, b84330808. (#1991, #3011) datasette.plugin_config(plugin_name, database=...)now falls back to the top-level plugin configuration for a database that has no configuration of its own for that plugin. Thanks, pujitha24. (#2959, #3041)
4th November 2026
datasette-llm 0.1a10 - LLM integration for Datasette
3rd November 2026
datasette-agent 0.5a0 - An LLM-powered agent assistant for Datasette
llm 0.37.1 - CLI utility and Python library for interacting with Large Language Models from organizations like OpenAI, Anthropic and Gemini plus local models installed on your own machine.
llm openai endpoint addnow works whenextra-openai-models.yamlholds a list written in square brackets, such as the[]left behind by removing the last saved endpoint. Previously it wrote invalid YAML that stopped LLM from loading any models until the file was fixed by hand. Thanks, Steven Huang. #1845- Passing both
system=andmessages=toprompt()orchain()in the Python API no longer drops the system prompt for models that build their requests fromprompt.messages, including the default OpenAI Chat models. #1849 - Non-streaming prompts, such as those run with
--no-stream, no longer crash with anAttributeErrorwhen an OpenAI-compatible server leavesusageout of its response. Thanks, Tao An. #1697, #1698 llm embed --format blobnow outputs exactly the bytes of the embedding. It previously added a newline at the end, which made the output one byte too long to decode. Thanks, Arjan Mossel. #1848llm logsno longer crashes withJSON cannot hold BLOB valueswhen a tool result logged by an LLM version before 0.32 has an attachment stored in the database. Thanks, XQ. #1775llm logs -qno longer misses words at the boundary between two text parts, for example in a response that interleaves text with reasoning or tool calls.- When a prompt passes several user messages using
messages=,llm logs -qnow searches all of them rather than only the last one.
2nd November 2026
llm 0.37
New features:
- New OpenAI models:
gpt-6.1-solfor GPT-6.1 Sol,gpt-6.1-terrafor GPT-6.1 Terra andchat-latestfor Chat Latest. - The default model for users who have not set their own default is now GPT-6 Luna, OpenAI's cheapest frontier model. It was previously GPT-5.6 Luna. You can switch back using
llm models default gpt-5.6-luna. #1746 - New
llm openai endpoint add,list,removeandpathcommands for managing OpenAI-compatible models saved inextra-openai-models.yaml. See Configure an OpenAI-compatible model. #1838 - Embedding models can now accept typed attachments, which carry a MIME type and the path or URL they came from. Plugins declare the MIME types they support using
attachment_typesand receive matching inputs asllm.Attachmentobjects.llm embed,llm embed-multiandllm similargain-a/--attachmentand--at/--attachment-typeoptions. Collections store the MIME type in a newcontent_typecolumn, exposed asEntry.content_type. See llm embed and Writing plugins to add new embedding models. #1741 llm chatnow accepts-u/--usageto display token usage on standard error after each chat turn. Thanks, Bian Yuhan. #980, #1754- New
chain.on_response(callback)method in the Python API, which runs a callback for each completed response in a tool chain before the chain limit is checked or that response's tool calls are executed. See Running code when a response has completed. #1768 - Added support for Python 3.15 and dropped support for Python 3.10.
- Removed OpenAI models that have been shut down:
gpt-3.5-turbo,gpt-3.5-turbo-16k,gpt-3.5-turbo-instruct,gpt-4,gpt-4-turbo,gpt-4-turbo-2024-04-09,gpt-4.1-nano,gpt-5.2-chat-latest,o1,o1-2024-12-17,o3-miniando4-mini, along with their aliases such as3.5,chatgpt,4and4t. #1696
Bug fixes:
llm chatnow respectsllm logs off. Previously it logged every response even when logging had been turned off. #1203Response.reply()now passesbefore_callandafter_callto the tool calls it executes automatically, and keeps them for later replies. Previously a tool rejected bybefore_callwould still run. Thanks, 0d00ciallo0721. #1835, #1836llm embed-multi --filesno longer corrupts non-ASCII UTF-8 files by decoding them aslatin-1, which stored and embedded "Café" as "Café". Run the same command again to repair an existing collection: only the affected files will be embedded again. Thanks, Alex. #283, #1750llm promptandllm chatnow log each response in a tool chain as soon as it completes, so an error or hitting the chain limit no longer discards the earlier responses in that chain from the logs. #1768- Fixed
SQLITE_BUSYerrors when multiple processes logged responses to the same database at the same time. #1742 - Tool chains that finish within their chain limit no longer raise a
Chain limit of N exceedederror. The limit now only applies when the final allowed response requests another round of tool calls. Thanks, Bishop Kammeraad. #1757, #1766 - Fixed
AsyncChainResponse.log_to_db()raisingRuntimeErrorinside a running event loop, for example straight afterawait chain.text(). Thanks, Christopher Krah. #1727, #1734 - Fixed
llm prompt --usagecrashing with anAttributeErrorwhen used with--asyncand tools. Thanks, Ethan Stoner. #1709 Response.execute_tool_calls()now returns an error result for a tool with no implementation instead of raisingValueError, matching the behavior ofAsyncResponse.execute_tool_calls(). Thanks, Chen Yufeiyang. #1724- Toolboxes exposing more than one method now run
prepare()andprepare_async()once per instance, instead of once per method. Thanks, Jiachen Xie. #1761 - Tool specs passed to
-T/--toolwith keyword arguments whose names start withtrue,falseornull, such asMyTool(nullable=true), are no longer mistaken for a single positional JSON value. Thanks, Olamide Faniyan. #1704 llm logs -s/--shortnow truncates tool call arguments. #1772- A schema passed to a tool chain now applies to every request in that chain, including the final response. It was previously dropped after the first round of tool calls. Thanks, Chen Yufeiyang. #1222, #1825
Collection.embed()no longer silently skips a new ID when another item in the collection has the same content, matching the behavior ofCollection.embed_multi(). Thanks, nosmile99. #1789, #1812Collection.embed_multi()now raisesValueErrorif an embedding model returns the wrong number of embeddings for a batch, instead of silently losing rows or storing vectors against the wrong IDs. Thanks, Sharat Chandra Sai Boddu. #1722, #1726llm schemas listnow includes schemas used by recently logged responses. Thanks, Jiachen Xie. #1808llm schemas listno longer crashes on schemas with boolean property definitions such as{"properties": {"x": true}}. Thanks, Jiachen Xie. #1748AsyncConversation.prompt()now uses the tools set on the conversation, matchingConversation.prompt(). Thanks, Bishop Kammeraad. #1790, #1791- Calling
response.log_to_db()on a synchronous response that has not yet been read now runs the response first. Previously the logged turn was missing its output and token usage. Thanks, Allen Flux. #1817
31st October 2026
datasette-llm 0.1a9 - LLM integration for Datasette
12th October 2026
datasette-ports 0.4 - Find all currently running Datasette instances and list their ports
9th October 2026
ttok 1.0 - Count and truncate text based on tokens
ttoknow defaults to theo200k_basetokenizer used by the GPT-5 and GPT-6 families. #25- The
ttok --list-modelscommand now also shows how prefixes likegpt-5*are handled by the underlyingtiktokenlibrary.
8th October 2026
ttok 0.4
- Fixed the
UserWarning: The parameter --tokens is used more than oncewarning shown by recent versions of Click. #20, #24 --allow-specialcan now be used when counting or truncating tokens, not just with--encodeor--tokens. This meansfiles-to-prompt . | ttok --allow-specialnow works. #17- New
ttok --list-modelsoption for listing the models supported by your installed version oftiktoken. #18 - Documentation now lists newer models such as
gpt-4o,gpt-4.1,gpt-5,o1,o3ando4-mini, all of which use theo200k_baseencoding. #15 - ttok now requires Python 3.10 or higher.
- Packaging moved from
setup.pytopyproject.toml.
6th October 2026
datasette-atom 0.11a0 - Datasette plugin that adds a .atom output format
- Compatible with Datasette>1.0a41
24th September 2026
datasette 1.0a41 - An open source multi-tool for exploring and publishing data
OpenTelemetry support, a new JavaScript API for creating modal dialogs, and several smaller bug fixes.
OpenTelemetry
Datasette now supports OpenTelemetry traces and metrics for monitoring application performance. Thanks, Alex Garcia. (#1730, #2867)
- Traces cover HTTP requests, database queries and startup, including time spent waiting for SQL threads and queued writes.
- Metrics report query latency, time-limit interruptions, SQL thread usage, write queues and open connections.
- New tools for plugin authors help plugins add their own traces and metrics, with shared registry classes and pytest helpers.
To collect telemetry, configure an OpenTelemetry SDK and exporter, then run Datasette using opentelemetry-instrument. See OpenTelemetry for setup instructions.
Other features
- New DatasetteModal JavaScript API for plugins to create dialogs with Datasette's shared styles, keyboard behavior and focus handling. Datasette's built-in dialogs use the same API. (#2790, #2948)
Bug fixes
- Table pages now show measured query timings instead of always displaying 1.2ms. (#2446)
- Facet loading now ignores unrelated query parameters such as
?_facets=x, instead of returning a 500 error. Thanks, Peng Boyu. (#2949) - Foreign key values no longer link to tables that do not exist. Thanks, Dipak Chaudhari. (#1515, #2952)
- Fixed missing punctuation between table and view counts on the homepage, such as
0 tables1view. Thanks, Dipak Chaudhari. (#2012, #2951) - The sort menu now excludes primary keys that are not included in sortable_columns. Thanks, Sanjay Santhanam. (#1980, #2858)
22nd September 2026
llm 0.36 - CLI utility and Python library for interacting with Large Language Models from organizations like OpenAI, Anthropic and Gemini plus local models installed on your own machine.
New features:
- New OpenAI models:
gpt-6-solfor GPT-6 Sol andgpt-6-lunafor GPT-6 Luna. #1702 - Model plugins can now declare
supports_conversation = Falsefor models that only accept single-turn prompts. LLM raisesllm.ConversationNotSupportedwhen these models receive assistant or tool history, andllm chatrejects them before starting a session. See Models that do not support conversations. The first plugin to use this is llm-typesafe. #1692 - Reasoning traces in the Markdown output of
llm logsare now wrapped in<details><summary>tags. #1701
Bug fixes:
- Fixed
llm logs -tcrashing withKeyError: 'options_json'when displaying truncated logs with options or schemas. Thanks, rdslw. #1656 - Fixed
AsyncResponse.log_to_db()raisingsqlite3.ProgrammingErrorafter a response had been awaited. Structured messages and provider metadata are also preserved when logging restored async responses. Thanks, Roli Bosch. #1668 - Tool parameters and nested model fields named
titleare now preserved in generated input schemas. Thanks, Christopher Pruijsen. #1679, #1680 - Constructing
llm.Promptdirectly without options now initializes the model's defaultOptionsobject, fixing errors in providers that access option attributes. Thanks, Jane Li. #1028, #1663 - Closing an asynchronous
response.astream_events()stream before completion now closes the underlying provider generator, allowing it to release streaming connections and other resources. Thanks, Shunsuke. #1659 - SQLite connections opened by CLI commands are now closed when the command finishes, including on errors, avoiding unclosed-connection warnings.
Documentation:
- Corrected the structured output example in the advanced model plugins documentation to use
supports_schema = True. Thanks, Ethan Stoner. #1686