# sections

id: internals:datasette-create-token
page: internals
ref: datasette-create-token
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
title:
.create_token(actor_id, expires_after=None, restrict_all=None, restrict_database=None, restrict_resource=None)

content:
actor_id  - string 
                         
                             The ID of the actor to create a token for. 
                         
                     
                     
                         expires_after  - int, optional 
                         
                             The number of seconds after which the token should expire. 
                         
                     
                     
                         restrict_all  - iterable, optional 
                         
                             A list of actions that this token should be restricted to across all databases and resources. 
                         
                     
                     
                         restrict_database  - dict, optional 
                         
                             For restricting actions within specific databases, e.g.  {"mydb": ["view-table", "view-query"]} . 
                         
                     
                     
                         restrict_resource  - dict, optional 
                         
                             For restricting actions to specific resources (tables, SQL views and  Canned queries ) within a database. For example:  {"mydb": {"mytable": ["insert-row", "update-row"]}} . 
                         
                     
                 
                 This method returns a signed  API token  of the format  dstok_...  which can be used to authenticate requests to the Datasette API. 
                 All tokens must have an  actor_id  string indicating the ID of the actor which the token will act on behalf of. 
                 Tokens default to lasting forever, but can be set to expire after a given number of seconds using the  expires_after  argument. The following code creates a token for  user1  that will expire after an hour: 
                 token = datasette.create_token(
    actor_id="user1",
    expires_after=3600,
) 
                 The three  restrict_*  arguments can be used to create a token that has additional restrictions beyond what the associated actor is allowed to do. 
                 The following example creates a token that can access  view-instance  and  view-table  across everything, can additionally use  view-query  for anything in the  docs  database and is allowed to execute  insert-row  and  update-row  in the  attachments  table in that database: 
                 token = datasette.create_token(
    actor_id="user1",
    restrict_all=("view-instance", "view-table"),
    restrict_database={"docs": ("view-query",)},
    restrict_resource={
        "docs": {
            "attachments": ("insert-row", "update-row")
        }
    },
)


id: internals:datasette-get-permission
page: internals
ref: datasette-get-permission
title: .get_permission(name_or_abbr)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
name_or_abbr  - string 
                         
                             The name or abbreviation of the permission to look up, e.g.  view-table  or  vt . 
                         
                     
                 
                 Returns a  Permission object  representing the permission, or raises a  KeyError  if one is not found.


id: internals:datasette-get-database
page: internals
ref: datasette-get-database
title: .get_database(name)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
name  - string, optional 
                         
                             The name of the database - optional. 
                         
                     
                 
                 Returns the specified database object. Raises a  KeyError  if the database does not exist. Call this method without an argument to return the first connected database.


id: internals:datasette-add-database
page: internals
ref: datasette-add-database
title: .add_database(db, name=None, route=None)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
db  - datasette.database.Database instance 
                         
                             The database to be attached. 
                         
                     
                     
                         name  - string, optional 
                         
                             The name to be used for this database . If not specified Datasette will pick one based on the filename or memory name. 
                         
                     
                     
                         route  - string, optional 
                         
                             This will be used in the URL path. If not specified, it will default to the same thing as the  name . 
                         
                     
                 
                 The  datasette.add_database(db)  method lets you add a new database to the current Datasette instance. 
                 The  db  parameter should be an instance of the  datasette.database.Database  class. For example: 
                 from datasette.database import Database

datasette.add_database(
    Database(
        datasette,
        path="path/to/my-new-database.db",
    )
) 
                 This will add a mutable database and serve it at  /my-new-database . 
                 Use  is_mutable=False  to add an immutable database. 
                 .add_database()  returns the Database instance, with its name set as the  database.name  attribute. Any time you are working with a newly added database you should use the return value of  .add_database() , for example: 
                 db = datasette.add_database(
    Database(datasette, memory_name="statistics")
)
await db.execute_write(
    "CREATE TABLE foo(id integer primary key)"
)


id: internals:datasette-add-memory-database
page: internals
ref: datasette-add-memory-database
title: .add_memory_database(name)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
Adds a shared in-memory database with the specified name: 
                 datasette.add_memory_database("statistics") 
                 This is a shortcut for the following: 
                 from datasette.database import Database

datasette.add_database(
    Database(datasette, memory_name="statistics")
) 
                 Using either of these pattern will result in the in-memory database being served at  /statistics .


id: internals:datasette-remove-database
page: internals
ref: datasette-remove-database
title: .remove_database(name)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
name  - string 
                         
                             The name of the database to be removed. 
                         
                     
                 
                 This removes a database that has been previously added.  name=  is the unique name of that database.


id: internals:datasette-track-event
page: internals
ref: datasette-track-event
title: await .track_event(event)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
event  -  Event 
                         
                             An instance of a subclass of  datasette.events.Event . 
                         
                     
                 
                 Plugins can call this to track events, using classes they have previously registered. See  Event tracking  for details. 
                 The event will then be passed to all plugins that have registered to receive events using the  track_event(datasette, event)  hook. 
                 Example usage, assuming the plugin has previously registered the  BanUserEvent  class: 
                 await datasette.track_event(
    BanUserEvent(user={"id": 1, "username": "cleverbot"})
)


id: internals:datasette-sign
page: internals
ref: datasette-sign
title: .sign(value, namespace="default")
breadcrumbs: ["Internals for plugins", "Datasette class"]
content:
value  - any serializable type 
                         
                             The value to be signed. 
                         
                     
                     
                         namespace  - string, optional 
                         
                             An alternative namespace, see the  itsdangerous salt documentation . 
                         
                     
                 
                 Utility method for signing values, such that you can safely pass data to and from an untrusted environment. This is a wrapper around the  itsdangerous  library. 
                 This method returns a signed string, which can be decoded and verified using  .unsign(value, namespace="default") .

references:
[{"href": "https://itsdangerous.palletsprojects.com/en/1.1.x/serializer/#the-salt", "label": "itsdangerous salt documentation"}, {"href": "https://itsdangerous.palletsprojects.com/", "label": "itsdangerous"}]


id: internals:datasette-unsign
page: internals
ref: datasette-unsign
title: .unsign(value, namespace="default")
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
signed  - any serializable type 
                         
                             The signed string that was created using  .sign(value, namespace="default") . 
                         
                     
                     
                         namespace  - string, optional 
                         
                             The alternative namespace, if one was used. 
                         
                     
                 
                 Returns the original, decoded object that was passed to  .sign(value, namespace="default") . If the signature is not valid this raises a  itsdangerous.BadSignature  exception.


id: internals:datasette-add-message
page: internals
ref: datasette-add-message
title: .add_message(request, message, type=datasette.INFO)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
request  - Request 
                         
                             The current Request object 
                         
                     
                     
                         message  - string 
                         
                             The message string 
                         
                     
                     
                         type  - constant, optional 
                         
                             The message type -  datasette.INFO ,  datasette.WARNING  or  datasette.ERROR 
                         
                     
                 
                 Datasette's flash messaging mechanism allows you to add a message that will be displayed to the user on the next page that they visit. Messages are persisted in a  ds_messages  cookie. This method adds a message to that cookie. 
                 You can try out these messages (including the different visual styling of the three message types) using the  /-/messages  debugging tool.


id: internals:datasette-absolute-url
page: internals
ref: datasette-absolute-url
title: .absolute_url(request, path)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
request  - Request 
                         
                             The current Request object 
                         
                     
                     
                         path  - string 
                         
                             A path, for example  /dbname/table.json 
                         
                     
                 
                 Returns the absolute URL for the given path, including the protocol and host. For example: 
                 absolute_url = datasette.absolute_url(
    request, "/dbname/table.json"
)
# Would return "http://localhost:8001/dbname/table.json" 
                 The current request object is used to determine the hostname and protocol that should be used for the returned URL. The  force_https_urls  configuration setting is taken into account.


id: internals:datasette-setting
page: internals
ref: datasette-setting
title: .setting(key)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
key  - string 
                         
                             The name of the setting, e.g.  base_url . 
                         
                     
                 
                 Returns the configured value for the specified  setting . This can be a string, boolean or integer depending on the requested setting. 
                 For example: 
                 downloads_are_allowed = datasette.setting("allow_download")


id: internals:datasette-resolve-database
page: internals
ref: datasette-resolve-database
title: .resolve_database(request)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
request  -  Request object 
                         
                             A request object 
                         
                     
                 
                 If you are implementing your own custom views, you may need to resolve the database that the user is requesting based on a URL path. If the regular expression for your route declares a  database  named group, you can use this method to resolve the database object. 
                 This returns a  Database  instance. 
                 If the database cannot be found, it raises a  datasette.utils.asgi.DatabaseNotFound  exception - which is a subclass of  datasette.utils.asgi.NotFound  with a  .database_name  attribute set to the name of the database that was requested.


id: internals:datasette-resolve-table
page: internals
ref: datasette-resolve-table
title: .resolve_table(request)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
request  -  Request object 
                         
                             A request object 
                         
                     
                 
                 This assumes that the regular expression for your route declares both a  database  and a  table  named group. 
                 It returns a  ResolvedTable  named tuple instance with the following fields: 
                 
                     
                         db  -  Database 
                         
                             The database object 
                         
                     
                     
                         table  - string 
                         
                             The name of the table (or view) 
                         
                     
                     
                         is_view  - boolean 
                         
                             True  if this is a view,  False  if it is a table 
                         
                     
                 
                 If the database or table cannot be found it raises a  datasette.utils.asgi.DatabaseNotFound  exception. 
                 If the table does not exist it raises a  datasette.utils.asgi.TableNotFound  exception - a subclass of  datasette.utils.asgi.NotFound  with  .database_name  and  .table  attributes.


id: internals:datasette-resolve-row
page: internals
ref: datasette-resolve-row
title: .resolve_row(request)
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
request  -  Request object 
                         
                             A request object 
                         
                     
                 
                 This method assumes your route declares named groups for  database ,  table  and  pks . 
                 It returns a  ResolvedRow  named tuple instance with the following fields: 
                 
                     
                         db  -  Database 
                         
                             The database object 
                         
                     
                     
                         table  - string 
                         
                             The name of the table 
                         
                     
                     
                         sql  - string 
                         
                             SQL snippet that can be used in a  WHERE  clause to select the row 
                         
                     
                     
                         params  - dict 
                         
                             Parameters that should be passed to the SQL query 
                         
                     
                     
                         pks  - list 
                         
                             List of primary key column names 
                         
                     
                     
                         pk_values  - list 
                         
                             List of primary key values decoded from the URL 
                         
                     
                     
                         row  -  sqlite3.Row 
                         
                             The row itself 
                         
                     
                 
                 If the database or table cannot be found it raises a  datasette.utils.asgi.DatabaseNotFound  exception. 
                 If the table does not exist it raises a  datasette.utils.asgi.TableNotFound  exception. 
                 If the row cannot be found it raises a  datasette.utils.asgi.RowNotFound  exception. This has  .database_name ,  .table  and  .pk_values  attributes, extracted from the request path.


id: internals:internals-datasette-client
page: internals
ref: internals-datasette-client
title: datasette.client
breadcrumbs: ["Internals for plugins", "Datasette class"]
content:
Plugins can make internal simulated HTTP requests to the Datasette instance within which they are running. This ensures that all of Datasette's external JSON APIs are also available to plugins, while avoiding the overhead of making an external HTTP call to access those APIs. 
                 The  datasette.client  object is a wrapper around the  HTTPX Python library , providing an async-friendly API that is similar to the widely used  Requests library . 
                 It offers the following methods: 
                 
                     
                         await datasette.client.get(path, **kwargs)  - returns HTTPX Response 
                         
                             Execute an internal GET request against that path. 
                         
                     
                     
                         await datasette.client.post(path, **kwargs)  - returns HTTPX Response 
                         
                             Execute an internal POST request. Use  data={"name": "value"}  to pass form parameters. 
                         
                     
                     
                         await datasette.client.options(path, **kwargs)  - returns HTTPX Response 
                         
                             Execute an internal OPTIONS request. 
                         
                     
                     
                         await datasette.client.head(path, **kwargs)  - returns HTTPX Response 
                         
                             Execute an internal HEAD request. 
                         
                     
                     
                         await datasette.client.put(path, **kwargs)  - returns HTTPX Response 
                         
                             Execute an internal PUT request. 
                         
                     
                     
                         await datasette.client.patch(path, **kwargs)  - returns HTTPX Response 
                         
                             Execute an internal PATCH request. 
                         
                     
                     
                         await datasette.client.delete(path, **kwargs)  - returns HTTPX Response 
                         
                             Execute an internal DELETE request. 
                         
                     
                     
                         await datasette.client.request(method, path, **kwargs)  - returns HTTPX Response 
                         
                             Execute an internal request with the given HTTP method against that path. 
                         
                     
                 
                 These methods can be used with  datasette.urls  - for example: 
                 table_json = (
    await datasette.client.get(
        datasette.urls.table(
            "fixtures", "facetable", format="json"
        )
    )
).json() 
                 datasette.client  methods automatically take the current  base_url  setting into account, whether or not you use the  datasette.urls  family of methods to construct the path. 
                 For documentation on available  **kwargs  options and the shape of the HTTPX Response object refer to the  HTTPX Async documentation .

references:
[{"href": "https://www.python-httpx.org/", "label": "HTTPX Python library"}, {"href": "https://requests.readthedocs.io/", "label": "Requests library"}, {"href": "https://www.python-httpx.org/async/", "label": "HTTPX Async documentation"}]


id: internals:internals-datasette-urls
page: internals
ref: internals-datasette-urls
title: datasette.urls
breadcrumbs: ["Internals for plugins", "Datasette class"]
references: []
content:
The  datasette.urls  object contains methods for building URLs to pages within Datasette. Plugins should use this to link to pages, since these methods take into account any  base_url  configuration setting that might be in effect. 
                 
                     
                         datasette.urls.instance(format=None) 
                         
                             Returns the URL to the Datasette instance root page. This is usually  "/" . 
                         
                     
                     
                         datasette.urls.path(path, format=None) 
                         
                             Takes a path and returns the full path, taking  base_url  into account. 
                             For example,  datasette.urls.path("-/logout")  will return the path to the logout page, which will be  "/-/logout"  by default or  /prefix-path/-/logout  if  base_url  is set to  /prefix-path/ 
                         
                     
                     
                         datasette.urls.logout() 
                         
                             Returns the URL to the logout page, usually  "/-/logout" 
                         
                     
                     
                         datasette.urls.static(path) 
                         
                             Returns the URL of one of Datasette's default static assets, for example  "/-/static/app.css" 
                         
                     
                     
                         datasette.urls.static_plugins(plugin_name, path) 
                         
                             Returns the URL of one of the static assets belonging to a plugin. 
                             datasette.urls.static_plugins("datasette_cluster_map", "datasette-cluster-map.js")  would return  "/-/static-plugins/datasette_cluster_map/datasette-cluster-map.js" 
                         
                     
                     
                         datasette.urls.static(path) 
                         
                             Returns the URL of one of Datasette's default static assets, for example  "/-/static/app.css" 
                         
                     
                     
                         datasette.urls.database(database_name, format=None) 
                         
                             Returns the URL to a database page, for example  "/fixtures" 
                         
                     
                     
                         datasette.urls.table(database_name, table_name, format=None) 
                         
                             Returns the URL to a table page, for example  "/fixtures/facetable" 
                         
                     
                     
                         datasette.urls.query(database_name, query_name, format=None) 
                         
                             Returns the URL to a query page, for example  "/fixtures/pragma_cache_size" 
                         
                     
                 
                 These functions can be accessed via the  {{ urls }}  object in Datasette templates, for example: 
                 <a href="{{ urls.instance() }}">Homepage</a>
<a href="{{ urls.database("fixtures") }}">Fixtures database</a>
<a href="{{ urls.table("fixtures", "facetable") }}">facetable table</a>
<a href="{{ urls.query("fixtures", "pragma_cache_size") }}">pragma_cache_size query</a> 
                 Use the  format="json"  (or  "csv"  or other formats supported by plugins) arguments to get back URLs to the JSON representation. This is the path with  .json  added on the end. 
                 These methods each return a  datasette.utils.PrefixedUrlString  object, which is a subclass of the Python  str  type. This allows the logic that considers the  base_url  setting to detect if that prefix has already been applied to the path.


id: internals:internals-database
page: internals
ref: internals-database
title: Database class
breadcrumbs: ["Internals for plugins"]
references: []
content:
Instances of the  Database  class can be used to execute queries against attached SQLite databases, and to run introspection against their schemas.


id: internals:database-constructor
page: internals
ref: database-constructor
title: Database(ds, path=None, is_mutable=True, is_memory=False, memory_name=None)
breadcrumbs: ["Internals for plugins", "Database class"]
references: []
content:
The  Database()  constructor can be used by plugins, in conjunction with  .add_database(db, name=None, route=None) , to create and register new databases. 
                 The arguments are as follows: 
                 
                     
                         ds  -  Datasette class  (required) 
                         
                             The Datasette instance you are attaching this database to. 
                         
                     
                     
                         path  - string 
                         
                             Path to a SQLite database file on disk. 
                         
                     
                     
                         is_mutable  - boolean 
                         
                             Set this to  False  to cause Datasette to open the file in immutable mode. 
                         
                     
                     
                         is_memory  - boolean 
                         
                             Use this to create non-shared memory connections. 
                         
                     
                     
                         memory_name  - string or  None 
                         
                             Use this to create a named in-memory database. Unlike regular memory databases these can be accessed by multiple threads and will persist an changes made to them for the lifetime of the Datasette server process. 
                         
                     
                 
                 The first argument is the  datasette  instance you are attaching to, the second is a  path= , then  is_mutable  and  is_memory  are both optional arguments.


id: internals:database-hash
page: internals
ref: database-hash
title: db.hash
breadcrumbs: ["Internals for plugins", "Database class"]
references: []
content:
If the database was opened in immutable mode, this property returns the 64 character SHA-256 hash of the database contents as a string. Otherwise it returns  None .


id: internals:database-execute
page: internals
ref: database-execute
title: await db.execute(sql, ...)
breadcrumbs: ["Internals for plugins", "Database class"]
references: []
content:
Executes a SQL query against the database and returns the resulting rows (see  Results ). 
                 
                     
                         sql  - string (required) 
                         
                             The SQL query to execute. This can include  ?  or  :named  parameters. 
                         
                     
                     
                         params  - list or dict 
                         
                             A list or dictionary of values to use for the parameters. List for  ? , dictionary for  :named . 
                         
                     
                     
                         truncate  - boolean 
                         
                             Should the rows returned by the query be truncated at the maximum page size? Defaults to  True , set this to  False  to disable truncation. 
                         
                     
                     
                         custom_time_limit  - integer ms 
                         
                             A custom time limit for this query. This can be set to a lower value than the Datasette configured default. If a query takes longer than this it will be terminated early and raise a  dataette.database.QueryInterrupted  exception. 
                         
                     
                     
                         page_size  - integer 
                         
                             Set a custom page size for truncation, over-riding the configured Datasette default. 
                         
                     
                     
                         log_sql_errors  - boolean 
                         
                             Should any SQL errors be logged to the console in addition to being raised as an error? Defaults to  True .


id: internals:database-results
page: internals
ref: database-results
title: Results
breadcrumbs: ["Internals for plugins", "Database class"]
references: [{"href": "https://docs.python.org/3/library/sqlite3.html#row-objects", "label": "Row objects"}]
content:
The  db.execute()  method returns a single  Results  object. This can be used to access the rows returned by the query. 
                 Iterating over a  Results  object will yield SQLite  Row objects . Each of these can be treated as a tuple or can be accessed using  row["column"]  syntax: 
                 info = []
results = await db.execute("select name from sqlite_master")
for row in results:
    info.append(row["name"]) 
                 The  Results  object also has the following properties and methods: 
                 
                     
                         .truncated  - boolean 
                         
                             Indicates if this query was truncated - if it returned more results than the specified  page_size . If this is true then the results object will only provide access to the first  page_size  rows in the query result. You can disable truncation by passing  truncate=False  to the  db.query()  method. 
                         
                     
                     
                         .columns  - list of strings 
                         
                             A list of column names returned by the query. 
                         
                     
                     
                         .rows  - list of  sqlite3.Row 
                         
                             This property provides direct access to the list of rows returned by the database. You can access specific rows by index using  results.rows[0] . 
                         
                     
                     
                         .first()  - row or None 
                         
                             Returns the first row in the results, or  None  if no rows were returned. 
                         
                     
                     
                         .single_value() 
                         
                             Returns the value of the first column of the first row of results - but only if the query returned a single row with a single column. Raises a  datasette.database.MultipleValues  exception otherwise. 
                         
                     
                     
                         .__len__() 
                         
                             Calling  len(results)  returns the (truncated) number of returned results.


id: internals:database-execute-fn
page: internals
ref: database-execute-fn
title: await db.execute_fn(fn)
breadcrumbs: ["Internals for plugins", "Database class"]
references: []
content:
Executes a given callback function against a read-only database connection running in a thread. The function will be passed a SQLite connection, and the return value from the function will be returned by the  await . 
                 Example usage: 
                 def get_version(conn):
    return conn.execute(
        "select sqlite_version()"
    ).fetchall()[0][0]


version = await db.execute_fn(get_version)


id: internals:database-execute-write
page: internals
ref: database-execute-write
title: await db.execute_write(sql, params=None, block=True)
breadcrumbs: ["Internals for plugins", "Database class"]
references: []
content:
SQLite only allows one database connection to write at a time. Datasette handles this for you by maintaining a queue of writes to be executed against a given database. Plugins can submit write operations to this queue and they will be executed in the order in which they are received. 
                 This method can be used to queue up a non-SELECT SQL query to be executed against a single write connection to the database. 
                 You can pass additional SQL parameters as a tuple or dictionary. 
                 The method will block until the operation is completed, and the return value will be the return from calling  conn.execute(...)  using the underlying  sqlite3  Python library. 
                 If you pass  block=False  this behavior changes to "fire and forget" - queries will be added to the write queue and executed in a separate thread while your code can continue to do other things. The method will return a UUID representing the queued task. 
                 Each call to  execute_write()  will be executed inside a transaction.


id: internals:database-execute-write-script
page: internals
ref: database-execute-write-script
title: await db.execute_write_script(sql, block=True)
breadcrumbs: ["Internals for plugins", "Database class"]
content:
Like  execute_write()  but can be used to send multiple SQL statements in a single string separated by semicolons, using the  sqlite3   conn.executescript()  method. 
                 Each call to  execute_write_script()  will be executed inside a transaction.

references:
[{"href": "https://docs.python.org/3/library/sqlite3.html#sqlite3.Cursor.executescript", "label": "conn.executescript()"}]


id: internals:database-execute-write-many
page: internals
ref: database-execute-write-many
title: await db.execute_write_many(sql, params_seq, block=True)
breadcrumbs: ["Internals for plugins", "Database class"]
content:
Like  execute_write()  but uses the  sqlite3   conn.executemany()  method. This will efficiently execute the same SQL statement against each of the parameters in the  params_seq  iterator, for example: 
                 await db.execute_write_many(
    "insert into characters (id, name) values (?, ?)",
    [(1, "Melanie"), (2, "Selma"), (2, "Viktor")],
) 
                 Each call to  execute_write_many()  will be executed inside a transaction.

references:
[{"href": "https://docs.python.org/3/library/sqlite3.html#sqlite3.Cursor.executemany", "label": "conn.executemany()"}]


id: internals:database-execute-write-fn
page: internals
ref: database-execute-write-fn
title: await db.execute_write_fn(fn, block=True, transaction=True)
breadcrumbs: ["Internals for plugins", "Database class"]
references: []
content:
This method works like  .execute_write() , but instead of a SQL statement you give it a callable Python function. Your function will be queued up and then called when the write connection is available, passing that connection as the argument to the function. 
                 The function can then perform multiple actions, safe in the knowledge that it has exclusive access to the single writable connection for as long as it is executing. 
                 
                     fn  needs to be a regular function, not an  async def  function. 
                 
                 For example: 
                 def delete_and_return_count(conn):
    conn.execute("delete from some_table where id > 5")
    return conn.execute(
        "select count(*) from some_table"
    ).fetchone()[0]


try:
    num_rows_left = await database.execute_write_fn(
        delete_and_return_count
    )
except Exception as e:
    print("An error occurred:", e) 
                 The value returned from  await database.execute_write_fn(...)  will be the return value from your function. 
                 If your function raises an exception that exception will be propagated up to the  await  line. 
                 By default your function will be executed inside a transaction. You can pass  transaction=False  to disable this behavior, though if you do that you should be careful to manually apply transactions - ideally using the  with conn:  pattern, or you may see  OperationalError: database table is locked  errors. 
                 If you specify  block=False  the method becomes fire-and-forget, queueing your function to be executed and then allowing your code after the call to  .execute_write_fn()  to continue running while the underlying thread waits for an opportunity to run your function. A UUID representing the queued task will be returned. Any exceptions in your code will be silently swallowed.


id: internals:database-execute-isolated-fn
page: internals
ref: database-execute-isolated-fn
title: await db.execute_isolated_fn(fn)
breadcrumbs: ["Internals for plugins", "Database class"]
content:
This method works is similar to  execute_write_fn()  but executes the provided function in an entirely isolated SQLite connection, which is opened, used and then closed again in a single call to this method. 
                 The  prepare_connection()  plugin hook is not executed against this connection. 
                 This allows plugins to execute database operations that might conflict with how database connections are usually configured. For example, running a  VACUUM  operation while bypassing any restrictions placed by the  datasette-sqlite-authorizer  plugin. 
                 Plugins can also use this method to load potentially dangerous SQLite extensions, use them to perform an operation and then have them safely unloaded at the end of the call, without risk of exposing them to other connections. 
                 Functions run using  execute_isolated_fn()  share the same queue as  execute_write_fn() , which guarantees that no writes can be executed at the same time as the isolated function is executing. 
                 The return value of the function will be returned by this method. Any exceptions raised by the function will be raised out of the  await  line as well.

references:
[{"href": "https://github.com/datasette/datasette-sqlite-authorizer", "label": "datasette-sqlite-authorizer"}]


id: internals:database-close
page: internals
ref: database-close
title: db.close()
breadcrumbs: ["Internals for plugins", "Database class"]
references: []
content:
Closes all of the open connections to file-backed databases. This is mainly intended to be used by large test suites, to avoid hitting limits on the number of open files.


id: internals:internals-database-introspection
page: internals
ref: internals-database-introspection
title: Database introspection
breadcrumbs: ["Internals for plugins", "Database class"]
references: []
content:
The  Database  class also provides properties and methods for introspecting the database. 
                 
                     
                         db.name  - string 
                         
                             The name of the database - usually the filename without the  .db  prefix. 
                         
                     
                     
                         db.size  - integer 
                         
                             The size of the database file in bytes. 0 for  :memory:  databases. 
                         
                     
                     
                         db.mtime_ns  - integer or None 
                         
                             The last modification time of the database file in nanoseconds since the epoch.  None  for  :memory:  databases. 
                         
                     
                     
                         db.is_mutable  - boolean 
                         
                             Is this database mutable, and allowed to accept writes? 
                         
                     
                     
                         db.is_memory  - boolean 
                         
                             Is this database an in-memory database? 
                         
                     
                     
                         await db.attached_databases()  - list of named tuples 
                         
                             Returns a list of additional databases that have been connected to this database using the SQLite ATTACH command. Each named tuple has fields  seq ,  name  and  file . 
                         
                     
                     
                         await db.table_exists(table)  - boolean 
                         
                             Check if a table called  table  exists. 
                         
                     
                     
                         await db.view_exists(view)  - boolean 
                         
                             Check if a view called  view  exists. 
                         
                     
                     
                         await db.table_names()  - list of strings 
                         
                             List of names of tables in the database. 
                         
                     
                     
                         await db.view_names()  - list of strings 
                         
                             List of names of views in the database. 
                         
                     
                     
                         await db.table_columns(table)  - list of strings 
                         
                             Names of columns in a specific table. 
                         
                     
                     
                         await db.table_column_details(table)  - list of named tuples 
                         
                             Full details of the columns in a specific table. Each column is represented by a  Column  named tuple with fields  cid  (integer representing the column position),  name  (string),  type  (string, e.g.  REAL  or  VARCHAR(30) ),  notnull  (integer 1 or 0),  default_value  (string or None),  is_pk  (integer 1 or 0). 
                         
                     
                     
                         await db.primary_keys(table)  - list of strings 
                         
                             Names of the columns that are part of the primary key for this table. 
                         
                     
                     
                         await db.fts_table(table)  - string or None 
                         
                             The name of the FTS table associated with this table, if one exists. 
                         
                     
                     
                         await db.label_column_for_table(table)  - string or None 
                         
                             The label column that is associated with this table - either automatically detected or using the  "label_column"  key from  Metadata , see  Specifying the label column for a table . 
                         
                     
                     
                         await db.foreign_keys_for_table(table)  - list of dictionaries 
                         
                             Details of columns in this table which are foreign keys to other tables. A list of dictionaries where each dictionary is shaped like this:  {"column": string, "other_table": string, "other_column": string} . 
                         
                     
                     
                         await db.hidden_table_names()  - list of strings 
                         
                             List of tables which Datasette "hides" by default - usually these are tables associated with SQLite's full-text search feature, the SpatiaLite extension or tables hidden using the  Hiding tables  feature. 
                         
                     
                     
                         await db.get_table_definition(table)  - string 
                         
                             Returns the SQL definition for the table - the  CREATE TABLE  statement and any associated  CREATE INDEX  statements. 
                         
                     
                     
                         await db.get_view_definition(view)  - string 
                         
                             Returns the SQL definition of the named view. 
                         
                     
                     
                         await db.get_all_foreign_keys()  - dictionary 
                         
                             Dictionary representing both incoming and outgoing foreign keys for this table. It has two keys,  "incoming"  and  "outgoing" , each of which is a list of dictionaries with keys  "column" ,  "other_table"  and  "other_column" . For example: 
                             {
    "incoming": [],
    "outgoing": [
        {
            "other_table": "attraction_characteristic",
            "column": "characteristic_id",
            "other_column": "pk",
        },
        {
            "other_table": "roadside_attractions",
            "column": "attraction_id",
            "other_column": "pk",
        }
    ]
}


id: internals:internals-csrf
page: internals
ref: internals-csrf
title: CSRF protection
breadcrumbs: ["Internals for plugins"]
references: [{"href": "https://github.com/simonw/asgi-csrf", "label": "asgi-csrf"}]
content:
Datasette uses  asgi-csrf  to guard against CSRF attacks on form POST submissions. Users receive a  ds_csrftoken  cookie which is compared against the  csrftoken  form field (or  x-csrftoken  HTTP header) for every incoming request. 
             If your plugin implements a  <form method="POST">  anywhere you will need to include that token. You can do so with the following template snippet: 
             <input type="hidden" name="csrftoken" value="{{ csrftoken() }}"> 
             If you are rendering templates using the  await .render_template(template, context=None, request=None)  method the  csrftoken()  helper will only work if you provide the  request=  argument to that method. If you forget to do this you will see the following error: 
             form-urlencoded POST field did not match cookie 
             You can selectively disable CSRF protection using the  skip_csrf(datasette, scope)  hook.


id: internals:internals-internal
page: internals
ref: internals-internal
title: Datasette's internal database
breadcrumbs: ["Internals for plugins"]
references: []
content:
Datasette maintains an "internal" SQLite database used for configuration, caching, and storage. Plugins can store configuration, settings, and other data inside this database. By default, Datasette will use a temporary in-memory SQLite database as the internal database, which is created at startup and destroyed at shutdown. Users of Datasette can optionally pass in a  --internal  flag to specify the path to a SQLite database to use as the internal database, which will persist internal data across Datasette instances. 
             Datasette maintains tables called  catalog_databases ,  catalog_tables ,  catalog_columns ,  catalog_indexes ,  catalog_foreign_keys  with details of the attached databases and their schemas. These tables should not be considered a stable API - they may change between Datasette releases. 
             The internal database is not exposed in the Datasette application by default, which means private data can safely be stored without worry of accidentally leaking information through the default Datasette interface and API. However, other plugins do have full read and write access to the internal database. 
             Plugins can access this database by calling  internal_db = datasette.get_internal_database()  and then executing queries using the  Database API . 
             Plugin authors are asked to practice good etiquette when using the internal database, as all plugins use the same database to store data. For example: 
             
                 
                     Use a unique prefix when creating tables, indices, and triggers in the internal database. If your plugin is called  datasette-xyz , then prefix names with  datasette_xyz_* . 
                 
                 
                     Avoid long-running write statements that may stall or block other plugins that are trying to write at the same time. 
                 
                 
                     Use temporary tables or shared in-memory attached databases when possible. 
                 
                 
                     Avoid implementing features that could expose private data stored in the internal database by other plugins.


id: internals:internals-utils
page: internals
ref: internals-utils
title: The datasette.utils module
breadcrumbs: ["Internals for plugins"]
references: [{"href": "https://github.com/simonw/datasette/issues/new", "label": "opening an issue"}]
content:
The  datasette.utils  module contains various utility functions used by Datasette. As a general rule you should consider anything in this module to be unstable - functions and classes here could change without warning or be removed entirely between Datasette releases, without being mentioned in the release notes. 
             The exception to this rule is anything that is documented here. If you find a need for an undocumented utility function in your own work, consider  opening an issue  requesting that the function you are using be upgraded to documented and supported status.


id: internals:internals-utils-parse-metadata
page: internals
ref: internals-utils-parse-metadata
title: parse_metadata(content)
breadcrumbs: ["Internals for plugins", "The datasette.utils module"]
references: []
content:
This function accepts a string containing either JSON or YAML, expected to be of the format described in  Metadata . It returns a nested Python dictionary representing the parsed data from that string. 
                 If the metadata cannot be parsed as either JSON or YAML the function will raise a  utils.BadMetadataError  exception. 
                 
                 
                     datasette.utils. parse_metadata content :   str dict 
                     
                         Detects if content is JSON or YAML and parses it appropriately.


id: internals:internals-utils-await-me-maybe
page: internals
ref: internals-utils-await-me-maybe
title: await_me_maybe(value)
breadcrumbs: ["Internals for plugins", "The datasette.utils module"]
content:
Utility function for calling  await  on a return value if it is awaitable, otherwise returning the value. This is used by Datasette to support plugin hooks that can optionally return awaitable functions. Read more about this function in  The “await me maybe” pattern for Python asyncio . 
                 
                 
                     async   datasette.utils. await_me_maybe value :   Any Any 
                     
                         If value is callable, call it. If awaitable, await it. Otherwise return it.

references:
[{"href": "https://simonwillison.net/2020/Sep/2/await-me-maybe/", "label": "The “await me maybe” pattern for Python asyncio"}]


id: internals:internals-utils-derive-named-parameters
page: internals
ref: internals-utils-derive-named-parameters
title: derive_named_parameters(db, sql)
breadcrumbs: ["Internals for plugins", "The datasette.utils module"]
references: []
content:
Derive the list of named parameters referenced in a SQL query, using an  explain  query executed against the provided database. 
                 
                 
                     async   datasette.utils. derive_named_parameters db :   Database sql :   str List [ str ] 
                     
                         Given a SQL statement, return a list of named parameters that are used in the statement 
                         e.g. for  select * from foo where id=:id  this would return  ["id"]


id: internals:internals-tilde-encoding
page: internals
ref: internals-tilde-encoding
title: Tilde encoding
breadcrumbs: ["Internals for plugins", "The datasette.utils module"]
content:
Datasette uses a custom encoding scheme in some places, called  tilde encoding . This is primarily used for table names and row primary keys, to avoid any confusion between  /  characters in those values and the Datasette URLs that reference them. 
                 Tilde encoding uses the same algorithm as  URL percent-encoding , but with the  ~  tilde character used in place of  % . 
                 Any character other than  ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz0123456789_-  will be replaced by the numeric equivalent preceded by a tilde. For example: 
                 
                     
                         /  becomes  ~2F 
                     
                     
                         .  becomes  ~2E 
                     
                     
                         %  becomes  ~25 
                     
                     
                         ~  becomes  ~7E 
                     
                     
                         Space becomes  + 
                     
                     
                         polls/2022.primary  becomes  polls~2F2022~2Eprimary 
                     
                 
                 Note that the space character is a special case: it will be replaced with a  +  symbol. 
                 
                 
                 
                     datasette.utils. tilde_encode s :   str str 
                     
                         Returns tilde-encoded string - for example  /foo/bar  ->  ~2Ffoo~2Fbar 
                     
                 
                 
                 
                 
                     datasette.utils. tilde_decode s :   str str 
                     
                         Decodes a tilde-encoded string, so  ~2Ffoo~2Fbar  ->  /foo/bar

references:
[{"href": "https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding", "label": "URL percent-encoding"}]


id: internals:internals-tracer
page: internals
ref: internals-tracer
title: datasette.tracer
breadcrumbs: ["Internals for plugins"]
content:
Running Datasette with  --setting trace_debug 1  enables trace debug output, which can then be viewed by adding  ?_trace=1  to the query string for any page. 
             You can see an example of this at the bottom of  latest.datasette.io/fixtures/facetable?_trace=1 . The JSON output shows full details of every SQL query that was executed to generate the page. 
             The  datasette-pretty-traces  plugin can be installed to provide a more readable display of this information. You can see  a demo of that here . 
             You can add your own custom traces to the JSON output using the  trace()  context manager. This takes a string that identifies the type of trace being recorded, and records any keyword arguments as additional JSON keys on the resulting trace object. 
             The start and end time, duration and a traceback of where the trace was executed will be automatically attached to the JSON object. 
             This example uses trace to record the start, end and duration of any HTTP GET requests made using the function: 
             from datasette.tracer import trace
import httpx


async def fetch_url(url):
    with trace("fetch-url", url=url):
        async with httpx.AsyncClient() as client:
            return await client.get(url)

references:
[{"href": "https://latest.datasette.io/fixtures/facetable?_trace=1", "label": "latest.datasette.io/fixtures/facetable?_trace=1"}, {"href": "https://datasette.io/plugins/datasette-pretty-traces", "label": "datasette-pretty-traces"}, {"href": "https://latest-with-plugins.datasette.io/github/commits?_trace=1", "label": "a demo of that here"}]


id: internals:internals-tracer-trace-child-tasks
page: internals
ref: internals-tracer-trace-child-tasks
title: Tracing child tasks
breadcrumbs: ["Internals for plugins", "datasette.tracer"]
content:
If your code uses a mechanism such as  asyncio.gather()  to execute code in additional tasks you may find that some of the traces are missing from the display. 
                 You can use the  trace_child_tasks()  context manager to ensure these child tasks are correctly handled. 
                 from datasette import tracer

with tracer.trace_child_tasks():
    results = await asyncio.gather(
        # ... async tasks here
    ) 
                 This example uses the  register_routes()  plugin hook to add a page at  /parallel-queries  which executes two SQL queries in parallel using  asyncio.gather()  and returns their results. 
                 from datasette import hookimpl
from datasette import tracer


@hookimpl
def register_routes():
    async def parallel_queries(datasette):
        db = datasette.get_database()
        with tracer.trace_child_tasks():
            one, two = await asyncio.gather(
                db.execute("select 1"),
                db.execute("select 2"),
            )
        return Response.json(
            {
                "one": one.single_value(),
                "two": two.single_value(),
            }
        )

    return [
        (r"/parallel-queries$", parallel_queries),
    ] 
                 Note that running parallel SQL queries in this way has  been known to cause problems in the past , so treat this example with caution. 
                 Adding  ?_trace=1  will show that the trace covers both of those child tasks.

references:
[{"href": "https://github.com/simonw/datasette/issues/2189", "label": "been known to cause problems in the past"}]


id: internals:internals-shortcuts
page: internals
ref: internals-shortcuts
title: Import shortcuts
breadcrumbs: ["Internals for plugins"]
references: []
content:
The following commonly used symbols can be imported directly from the  datasette  module: 
             from datasette import Response
from datasette import Forbidden
from datasette import NotFound
from datasette import hookimpl
from datasette import actor_matches_allow


id: plugins:id1
page: plugins
ref: id1
title: Plugins
breadcrumbs: []
content:
Datasette's plugin system allows additional features to be implemented as Python
            code (or front-end JavaScript) which can be wrapped up in a separate Python
            package. The underlying mechanism uses  pluggy . 
         See the  Datasette plugins directory  for a list of existing plugins, or take a look at the
             datasette-plugin  topic on GitHub. 
         Things you can do with plugins include: 
         
             
                 Add visualizations to Datasette, for example
                     datasette-cluster-map  and
                     datasette-vega . 
             
             
                 Make new custom SQL functions available for use within Datasette, for example
                     datasette-haversine  and
                     datasette-jellyfish . 
             
             
                 Define custom output formats with custom extensions, for example  datasette-atom  and
                     datasette-ics . 
             
             
                 Add template functions that can be called within your Jinja custom templates,
                    for example  datasette-render-markdown . 
             
             
                 Customize how database values are rendered in the Datasette interface, for example
                     datasette-render-binary  and
                     datasette-pretty-json . 
             
             
                 Customize how Datasette's authentication and permissions systems work, for example  datasette-auth-passwords  and
                     datasette-permissions-sql .

references:
[{"href": "https://pluggy.readthedocs.io/", "label": "pluggy"}, {"href": "https://datasette.io/plugins", "label": "Datasette plugins directory"}, {"href": "https://github.com/topics/datasette-plugin", "label": "datasette-plugin"}, {"href": "https://github.com/simonw/datasette-cluster-map", "label": "datasette-cluster-map"}, {"href": "https://github.com/simonw/datasette-vega", "label": "datasette-vega"}, {"href": "https://github.com/simonw/datasette-haversine", "label": "datasette-haversine"}, {"href": "https://github.com/simonw/datasette-jellyfish", "label": "datasette-jellyfish"}, {"href": "https://github.com/simonw/datasette-atom", "label": "datasette-atom"}, {"href": "https://github.com/simonw/datasette-ics", "label": "datasette-ics"}, {"href": "https://github.com/simonw/datasette-render-markdown#markdown-in-templates", "label": "datasette-render-markdown"}, {"href": "https://github.com/simonw/datasette-render-binary", "label": "datasette-render-binary"}, {"href": "https://github.com/simonw/datasette-pretty-json", "label": "datasette-pretty-json"}, {"href": "https://github.com/simonw/datasette-auth-passwords", "label": "datasette-auth-passwords"}, {"href": "https://github.com/simonw/datasette-permissions-sql", "label": "datasette-permissions-sql"}]


id: plugins:plugins-installing
page: plugins
ref: plugins-installing
title: Installing plugins
breadcrumbs: ["Plugins"]
references: []
content:
If a plugin has been packaged for distribution using setuptools you can use the plugin by installing it alongside Datasette in the same virtual environment or Docker container. 
             You can install plugins using the  datasette install  command: 
             datasette install datasette-vega 
             You can uninstall plugins with  datasette uninstall : 
             datasette uninstall datasette-vega 
             You can upgrade plugins with  datasette install --upgrade  or  datasette install -U : 
             datasette install -U datasette-vega 
             This command can also be used to upgrade Datasette itself to the latest released version: 
             datasette install -U datasette 
             You can install multiple plugins at once by listing them as lines in a  requirements.txt  file like this: 
             datasette-vega
datasette-cluster-map 
             Then pass that file to  datasette install -r : 
             datasette install -r requirements.txt 
             The  install  and  uninstall  commands are thin wrappers around  pip install  and  pip uninstall , which ensure that they run  pip  in the same virtual environment as Datasette itself.


id: plugins:one-off-plugins-using-plugins-dir
page: plugins
ref: one-off-plugins-using-plugins-dir
title: One-off plugins using --plugins-dir
breadcrumbs: ["Plugins", "Installing plugins"]
references: []
content:
You can also define one-off per-project plugins by saving them as  plugin_name.py  functions in a  plugins/  folder and then passing that folder to  datasette  using the  --plugins-dir  option: 
                 datasette mydb.db --plugins-dir=plugins/


id: plugins:deploying-plugins-using-datasette-publish
page: plugins
ref: deploying-plugins-using-datasette-publish
title: Deploying plugins using datasette publish
breadcrumbs: ["Plugins", "Installing plugins"]
references: []
content:
The  datasette publish  and  datasette package  commands both take an optional  --install  argument. You can use this one or more times to tell Datasette to  pip install  specific plugins as part of the process: 
                 datasette publish cloudrun mydb.db --install=datasette-vega 
                 You can use the name of a package on PyPI or any of the other valid arguments to  pip install  such as a URL to a  .zip  file: 
                 datasette publish cloudrun mydb.db \
    --install=https://url-to-my-package.zip


id: plugins:plugins-datasette-load-plugins
page: plugins
ref: plugins-datasette-load-plugins
title: Controlling which plugins are loaded
breadcrumbs: ["Plugins"]
references: []
content:
Datasette defaults to loading every plugin that is installed in the same virtual environment as Datasette itself. 
             You can set the  DATASETTE_LOAD_PLUGINS  environment variable to a comma-separated list of plugin names to load a controlled subset of plugins instead. 
             For example, to load just the  datasette-vega  and  datasette-cluster-map  plugins, set  DATASETTE_LOAD_PLUGINS  to  datasette-vega,datasette-cluster-map : 
             export DATASETTE_LOAD_PLUGINS='datasette-vega,datasette-cluster-map'
datasette mydb.db 
             Or: 
             DATASETTE_LOAD_PLUGINS='datasette-vega,datasette-cluster-map' \
  datasette mydb.db 
             To disable the loading of all additional plugins, set  DATASETTE_LOAD_PLUGINS  to an empty string: 
             export DATASETTE_LOAD_PLUGINS=''
datasette mydb.db 
             A quick way to test this setting is to use it with the  datasette plugins  command: 
             DATASETTE_LOAD_PLUGINS='datasette-vega' datasette plugins 
             This should output the following: 
             [
    {
        "name": "datasette-vega",
        "static": true,
        "templates": false,
        "version": "0.6.2",
        "hooks": [
            "extra_css_urls",
            "extra_js_urls"
        ]
    }
]


id: plugins:plugins-installed
page: plugins
ref: plugins-installed
title: Seeing what plugins are installed
breadcrumbs: ["Plugins"]
content:
You can see a list of installed plugins by navigating to the  /-/plugins  page of your Datasette instance - for example:  https://fivethirtyeight.datasettes.com/-/plugins 
             You can also use the  datasette plugins  command: 
             datasette plugins 
             Which outputs: 
             [
    {
        "name": "datasette_json_html",
        "static": false,
        "templates": false,
        "version": "0.4.0"
    }
] 
             [[[cog
from datasette import cli
from click.testing import CliRunner
import textwrap, json
cog.out("\n")
result = CliRunner().invoke(cli.cli, ["plugins", "--all"])
# cog.out() with text containing newlines was unindenting for some reason
cog.outl("If you run ``datasette plugins --all`` it will include default plugins that ship as part of Datasette:\n")
cog.outl(".. code-block:: json\n")
plugins = [p for p in json.loads(result.output) if p["name"].startswith("datasette.")]
indented = textwrap.indent(json.dumps(plugins, indent=4), "    ")
for line in indented.split("\n"):
    cog.outl(line)
cog.out("\n\n") 
             ]]] 
             If you run  datasette plugins --all  it will include default plugins that ship as part of Datasette: 
             [
    {
        "name": "datasette.actor_auth_cookie",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "actor_from_request"
        ]
    },
    {
        "name": "datasette.blob_renderer",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "register_output_renderer"
        ]
    },
    {
        "name": "datasette.default_magic_parameters",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "register_magic_parameters"
        ]
    },
    {
        "name": "datasette.default_menu_links",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "menu_links"
        ]
    },
    {
        "name": "datasette.default_permissions",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "actor_from_request",
            "permission_allowed",
            "register_permissions",
            "skip_csrf"
        ]
    },
    {
        "name": "datasette.events",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "register_events"
        ]
    },
    {
        "name": "datasette.facets",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "register_facet_classes"
        ]
    },
    {
        "name": "datasette.filters",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "filters_from_request"
        ]
    },
    {
        "name": "datasette.forbidden",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "forbidden"
        ]
    },
    {
        "name": "datasette.handle_exception",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "handle_exception"
        ]
    },
    {
        "name": "datasette.publish.cloudrun",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "publish_subcommand"
        ]
    },
    {
        "name": "datasette.publish.heroku",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "publish_subcommand"
        ]
    },
    {
        "name": "datasette.sql_functions",
        "static": false,
        "templates": false,
        "version": null,
        "hooks": [
            "prepare_connection"
        ]
    }
] 
             [[[end]]] 
             You can add the  --plugins-dir=  option to include any plugins found in that directory. 
             Add  --requirements  to output a list of installed plugins that can then be installed in another Datasette instance using  datasette install -r requirements.txt : 
             datasette plugins --requirements 
             The output will look something like this: 
             datasette-codespaces==0.1.1
datasette-graphql==2.2
datasette-json-html==1.0.1
datasette-pretty-json==0.2.2
datasette-x-forwarded-host==0.1 
             To write that to a  requirements.txt  file, run this: 
             datasette plugins --requirements > requirements.txt

references:
[{"href": "https://fivethirtyeight.datasettes.com/-/plugins", "label": "https://fivethirtyeight.datasettes.com/-/plugins"}]


id: plugins:plugins-configuration
page: plugins
ref: plugins-configuration
title: Plugin configuration
breadcrumbs: ["Plugins"]
references: []
content:
Plugins can have their own configuration, embedded in a  configuration file . Configuration options for plugins live within a  "plugins"  key in that file, which can be included at the root, database or table level. 
             Here is an example of some plugin configuration for a specific table: 
             [[[cog
from metadata_doc import config_example
config_example(cog, {
    "databases": {
        "sf-trees": {
            "tables": {
                "Street_Tree_List": {
                    "plugins": {
                        "datasette-cluster-map": {
                            "latitude_column": "lat",
                            "longitude_column": "lng"
                        }
                    }
                }
            }
        }
    }
}) 
             ]]] 
             [[[end]]] 
             This tells the  datasette-cluster-map  column which latitude and longitude columns should be used for a table called  Street_Tree_List  inside a database file called  sf-trees.db .


id: plugins:plugins-configuration-secret
page: plugins
ref: plugins-configuration-secret
title: Secret configuration values
breadcrumbs: ["Plugins", "Plugin configuration"]
references: []
content:
Some plugins may need configuration that should stay secret - API keys for example. There are two ways in which you can store secret configuration values. 
                 As environment variables . If your secret lives in an environment variable that is available to the Datasette process, you can indicate that the configuration value should be read from that environment variable like so: 
                 [[[cog
config_example(cog, {
    "plugins": {
        "datasette-auth-github": {
            "client_secret": {
                "$env": "GITHUB_CLIENT_SECRET"
            }
        }
    }
}) 
                 ]]] 
                 [[[end]]] 
                 As values in separate files . Your secrets can also live in files on disk. To specify a secret should be read from a file, provide the full file path like this: 
                 [[[cog
config_example(cog, {
    "plugins": {
        "datasette-auth-github": {
            "client_secret": {
                "$file": "/secrets/client-secret"
            }
        }
    }
}) 
                 ]]] 
                 [[[end]]] 
                 If you are publishing your data using the  datasette publish  family of commands, you can use the  --plugin-secret  option to set these secrets at publish time. For example, using Heroku you might run the following command: 
                 datasette publish heroku my_database.db \
    --name my-heroku-app-demo \
    --install=datasette-auth-github \
    --plugin-secret datasette-auth-github client_id your_client_id \
    --plugin-secret datasette-auth-github client_secret your_client_secret 
                 This will set the necessary environment variables and add the following to the deployed  metadata.yaml : 
                 [[[cog
config_example(cog, {
    "plugins": {
        "datasette-auth-github": {
            "client_id": {
                "$env": "DATASETTE_AUTH_GITHUB_CLIENT_ID"
            },
            "client_secret": {
                "$env": "DATASETTE_AUTH_GITHUB_CLIENT_SECRET"
            }
        }
    }
}) 
                 ]]] 
                 [[[end]]]


id: ecosystem:ecosystem
page: ecosystem
ref: ecosystem
title: The Datasette Ecosystem
breadcrumbs: []
content:
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

references:
[{"href": "https://datasette.io/", "label": "Datasette project website"}, {"href": "https://datasette.io/plugins", "label": "Plugins directory on datasette.io"}, {"href": "https://datasette.io/tools", "label": "Tools directory on datasette.io"}]


id: ecosystem:sqlite-utils
page: ecosystem
ref: sqlite-utils
title: sqlite-utils
breadcrumbs: ["The Datasette Ecosystem"]
references: [{"href": "https://sqlite-utils.datasette.io/", "label": "sqlite-utils"}]
content:
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.


id: ecosystem:dogsheep
page: ecosystem
ref: dogsheep
title: Dogsheep
breadcrumbs: ["The Datasette Ecosystem"]
content:
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.

references:
[{"href": "https://dogsheep.github.io/", "label": "Dogsheep"}, {"href": "https://datasette.io/tools/github-to-sqlite", "label": "github-to-sqlite"}, {"href": "https://datasette.io/tools/twitter-to-sqlite", "label": "twitter-to-sqlite"}, {"href": "https://simonwillison.net/2020/Nov/14/personal-data-warehouses/", "label": "Personal Data Warehouses: Reclaiming Your Data"}]


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

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


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

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


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

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


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


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


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


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


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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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


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


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

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


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

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


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

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


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

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


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

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


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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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


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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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


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

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


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

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


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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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


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

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


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

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


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

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


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


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

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


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

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


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


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


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

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


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


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

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


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

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


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


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


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


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

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


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

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


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

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


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


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


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

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


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

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


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

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


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

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


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

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


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


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

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


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

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


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


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


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


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


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

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


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

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


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

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


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


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

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


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

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


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

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


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

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


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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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


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

references:
[{"href": "https://latest.datasette.io/fixtures/roadside_attractions?_through={%22table%22:%22roadside_attraction_characteristics%22,%22column%22:%22characteristic_id%22,%22value%22:%221%22}", "label": "an example"}, {"href": "https://github.com/simonw/datasette/issues/355", "label": "#355"}, {"href": "https://github.com/simonw/datasette/issues/551", "label": "facet by many-to-many"}]


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

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


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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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


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


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


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

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


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

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


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

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


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

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


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

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


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

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


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


id: changelog:new-configuration-settings
page: changelog
ref: new-configuration-settings
title: New configuration settings
breadcrumbs: ["Changelog", "0.23 (2018-06-18)"]
references: [{"href": "https://www.sqlite.org/pragma.html#pragma_cache_size", "label": "per-connection cache"}]
content:
Datasette's  Settings  now also supports boolean settings. A number of new
                    configuration options have been added: 
                 
                     
                         num_sql_threads  - the number of threads used to execute SQLite queries. Defaults to 3. 
                     
                     
                         allow_facet  - enable or disable custom  Facets  using the  _facet=  parameter. Defaults to on. 
                     
                     
                         suggest_facets  - should Datasette suggest facets? Defaults to on. 
                     
                     
                         allow_download  - should users be allowed to download the entire SQLite database? Defaults to on. 
                     
                     
                         allow_sql  - should users be allowed to execute custom SQL queries? Defaults to on. 
                     
                     
                         default_cache_ttl  - Default HTTP caching max-age header in seconds. Defaults to 365 days - caching can be disabled entirely by settings this to 0. 
                     
                     
                         cache_size_kb  - Set the amount of memory SQLite uses for its  per-connection cache , in KB. 
                     
                     
                         allow_csv_stream  - allow users to stream entire result sets as a single CSV file. Defaults to on. 
                     
                     
                         max_csv_mb  - maximum size of a returned CSV file in MB. Defaults to 100MB, set to 0 to disable this limit.


id: changelog:control-http-caching-with-ttl
page: changelog
ref: control-http-caching-with-ttl
title: Control HTTP caching with ?_ttl=
breadcrumbs: ["Changelog", "0.23 (2018-06-18)"]
content:
You can now customize the HTTP max-age header that is sent on a per-URL basis, using the new  ?_ttl=  query string parameter. 
                 You can set this to any value in seconds, or you can set it to 0 to disable HTTP caching entirely. 
                 Consider for example this query which returns a randomly selected member of the Avengers: 
                 select * from [avengers/avengers] order by random() limit 1 
                 If you hit the following page repeatedly you will get the same result, due to HTTP caching: 
                 /fivethirtyeight?sql=select+*+from+%5Bavengers%2Favengers%5D+order+by+random%28%29+limit+1 
                 By adding  ?_ttl=0  to the zero you can ensure the page will not be cached and get back a different super hero every time: 
                 /fivethirtyeight?sql=select+*+from+%5Bavengers%2Favengers%5D+order+by+random%28%29+limit+1&_ttl=0

references:
[{"href": "https://fivethirtyeight.datasettes.com/fivethirtyeight?sql=select+*+from+%5Bavengers%2Favengers%5D+order+by+random%28%29+limit+1", "label": "/fivethirtyeight?sql=select+*+from+%5Bavengers%2Favengers%5D+order+by+random%28%29+limit+1"}, {"href": "https://fivethirtyeight.datasettes.com/fivethirtyeight?sql=select+*+from+%5Bavengers%2Favengers%5D+order+by+random%28%29+limit+1&_ttl=0", "label": "/fivethirtyeight?sql=select+*+from+%5Bavengers%2Favengers%5D+order+by+random%28%29+limit+1&_ttl=0"}]


id: changelog:improved-support-for-spatialite
page: changelog
ref: improved-support-for-spatialite
title: Improved support for SpatiaLite
breadcrumbs: ["Changelog", "0.23 (2018-06-18)"]
content:
The  SpatiaLite module 
                    for SQLite adds robust geospatial features to the database. 
                 Getting SpatiaLite working can be tricky, especially if you want to use the most
                    recent alpha version (with support for K-nearest neighbor). 
                 Datasette now includes  extensive documentation on SpatiaLite , and thanks to  Ravi Kotecha  our GitHub
                    repo includes a  Dockerfile  that can build
                    the latest SpatiaLite and configure it for use with Datasette. 
                 The  datasette publish  and  datasette package  commands now accept a new
                     --spatialite  argument which causes them to install and configure SpatiaLite
                    as part of the container they deploy.

references:
[{"href": "https://www.gaia-gis.it/fossil/libspatialite/index", "label": "SpatiaLite module"}, {"href": "https://github.com/r4vi", "label": "Ravi Kotecha"}, {"href": "https://github.com/simonw/datasette/blob/master/Dockerfile", "label": "Dockerfile"}]


id: changelog:latest-datasette-io
page: changelog
ref: latest-datasette-io
title: latest.datasette.io
breadcrumbs: ["Changelog", "0.23 (2018-06-18)"]
content:
Every commit to Datasette master is now automatically deployed by Travis CI to
                     https://latest.datasette.io/  - ensuring there is always a live demo of the
                    latest version of the software. 
                 The demo uses  the fixtures  from our
                    unit tests, ensuring it demonstrates the same range of functionality that is
                    covered by the tests. 
                 You can see how the deployment mechanism works in our  .travis.yml  file.

references:
[{"href": "https://latest.datasette.io/", "label": "https://latest.datasette.io/"}, {"href": "https://github.com/simonw/datasette/blob/master/tests/fixtures.py", "label": "the fixtures"}, {"href": "https://github.com/simonw/datasette/blob/master/.travis.yml", "label": ".travis.yml"}]


id: changelog:miscellaneous
page: changelog
ref: miscellaneous
title: Miscellaneous
breadcrumbs: ["Changelog", "0.23 (2018-06-18)"]
content:
Got JSON data in one of your columns? Use the new  ?_json=COLNAME  argument
                            to tell Datasette to return that JSON value directly rather than encoding it
                            as a string. 
                     
                     
                         If you just want an array of the first value of each row, use the new
                             ?_shape=arrayfirst  option -  example .

references:
[{"href": "https://latest.datasette.io/fixtures.json?sql=select+neighborhood+from+facetable+order+by+pk+limit+101&_shape=arrayfirst", "label": "example"}]


id: changelog:id115
page: changelog
ref: id115
title: 0.22.1 (2018-05-23)
breadcrumbs: ["Changelog"]
content:
Bugfix release, plus we now use  versioneer  for our version numbers. 
             
                 
                     Faceting no longer breaks pagination, fixes  #282 
                 
                 
                     Add  __version_info__  derived from  __version__  [Robert Gieseke] 
                     This might be tuple of more than two values (major and minor
                        version) if commits have been made after a release. 
                 
                 
                     Add version number support with Versioneer. [Robert Gieseke] 
                     Versioneer Licence:
                        Public Domain (CC0-1.0) 
                     Closes  #273 
                 
                 
                     Refactor inspect logic [Russ Garrett]

references:
[{"href": "https://github.com/warner/python-versioneer", "label": "versioneer"}, {"href": "https://github.com/simonw/datasette/issues/282", "label": "#282"}, {"href": "https://github.com/simonw/datasette/issues/273", "label": "#273"}]


id: changelog:id118
page: changelog
ref: id118
title: 0.22 (2018-05-20)
breadcrumbs: ["Changelog"]
content:
The big new feature in this release is  Facets . Datasette can now apply faceted browse to any column in any table. It will also suggest possible facets. See the  Datasette Facets  announcement post for more details. 
             In addition to the work on facets: 
             
                 
                     Added  docs for introspection endpoints 
                 
                 
                     New  --config  option, added  --help-config , closes  #274 
                     Removed the  --page_size=  argument to  datasette serve  in favour of: 
                     datasette serve --config default_page_size:50 mydb.db 
                     Added new help section: 
                     datasette --help-config 
                     Config options:
  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)
  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) 
                 
                 
                     Only apply responsive table styles to  .rows-and-column 
                     Otherwise they interfere with tables in the description, e.g. on
                         https://fivethirtyeight.datasettes.com/fivethirtyeight/nba-elo%2Fnbaallelo 
                 
                 
                     Refactored views into new  views/  modules, refs  #256 
                 
                 
                     Documentation for SQLite full-text search  support, closes  #253 
                 
                 
                     /-/versions  now includes SQLite  fts_versions , closes  #252

references:
[{"href": "https://simonwillison.net/2018/May/20/datasette-facets/", "label": "Datasette Facets"}, {"href": "https://docs.datasette.io/en/stable/introspection.html", "label": "docs for introspection endpoints"}, {"href": "https://github.com/simonw/datasette/issues/274", "label": "#274"}, {"href": "https://fivethirtyeight.datasettes.com/fivethirtyeight/nba-elo%2Fnbaallelo", "label": "https://fivethirtyeight.datasettes.com/fivethirtyeight/nba-elo%2Fnbaallelo"}, {"href": "https://github.com/simonw/datasette/issues/256", "label": "#256"}, {"href": "https://docs.datasette.io/en/stable/full_text_search.html", "label": "Documentation for SQLite full-text search"}, {"href": "https://github.com/simonw/datasette/issues/253", "label": "#253"}, {"href": "https://github.com/simonw/datasette/issues/252", "label": "#252"}]


id: changelog:id123
page: changelog
ref: id123
title: 0.21 (2018-05-05)
breadcrumbs: ["Changelog"]
content:
New JSON  _shape=  options, the ability to set table  _size=  and a mechanism for searching within specific columns. 
             
                 
                     Default tests to using a longer timelimit 
                     Every now and then a test will fail in Travis CI on Python 3.5 because it hit
                        the default 20ms SQL time limit. 
                     Test fixtures now default to a 200ms time limit, and we only use the 20ms time
                        limit for the specific test that tests query interruption. This should make
                        our tests on Python 3.5 in Travis much more stable. 
                 
                 
                     Support  _search_COLUMN=text  searches, closes  #237 
                 
                 
                     Show version on  /-/plugins  page, closes  #248 
                 
                 
                     ?_size=max  option, closes  #249 
                 
                 
                     Added  /-/versions  and  /-/versions.json , closes  #244 
                     Sample output: 
                     {
  "python": {
    "version": "3.6.3",
    "full": "3.6.3 (default, Oct  4 2017, 06:09:38) \n[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.37)]"
  },
  "datasette": {
    "version": "0.20"
  },
  "sqlite": {
    "version": "3.23.1",
    "extensions": {
      "json1": null,
      "spatialite": "4.3.0a"
    }
  }
} 
                 
                 
                     Renamed  ?_sql_time_limit_ms=  to  ?_timelimit , closes  #242 
                 
                 
                     New  ?_shape=array  option + tweaks to  _shape , closes  #245 
                     
                         
                             Default is now  ?_shape=arrays  (renamed from  lists ) 
                         
                         
                             New  ?_shape=array  returns an array of objects as the root object 
                         
                         
                             Changed  ?_shape=object  to return the object as the root 
                         
                         
                             Updated docs 
                         
                     
                 
                 
                     FTS tables now detected by  inspect() , closes  #240 
                 
                 
                     New  ?_size=XXX  query string parameter for table view, closes  #229 
                     Also added documentation for all of the  _special  arguments. 
                     Plus deleted some duplicate logic implementing  _group_count . 
                 
                 
                     If  max_returned_rows==page_size , increment  max_returned_rows  - fixes  #230 
                 
                 
                     New  hidden: True  option for table metadata, closes  #239 
                 
                 
                     Hide  idx_*  tables if spatialite detected, closes  #228 
                 
                 
                     Added  class=rows-and-columns  to custom query results table 
                 
                 
                     Added CSS class  rows-and-columns  to main table 
                 
                 
                     label_column  option in  metadata.json  - closes  #234

references:
[{"href": "https://github.com/simonw/datasette/issues/237", "label": "#237"}, {"href": "https://github.com/simonw/datasette/issues/248", "label": "#248"}, {"href": "https://github.com/simonw/datasette/issues/249", "label": "#249"}, {"href": "https://github.com/simonw/datasette/issues/244", "label": "#244"}, {"href": "https://github.com/simonw/datasette/issues/242", "label": "#242"}, {"href": "https://github.com/simonw/datasette/issues/245", "label": "#245"}, {"href": "https://github.com/simonw/datasette/issues/240", "label": "#240"}, {"href": "https://github.com/simonw/datasette/issues/229", "label": "#229"}, {"href": "https://github.com/simonw/datasette/issues/230", "label": "#230"}, {"href": "https://github.com/simonw/datasette/issues/239", "label": "#239"}, {"href": "https://github.com/simonw/datasette/issues/228", "label": "#228"}, {"href": "https://github.com/simonw/datasette/issues/234", "label": "#234"}]


id: changelog:id136
page: changelog
ref: id136
title: 0.20 (2018-04-20)
breadcrumbs: ["Changelog"]
content:
Mostly new work on the  Plugins  mechanism: plugins can now bundle static assets and custom templates, and  datasette publish  has a new  --install=name-of-plugin  option. 
             
                 
                     Add col-X classes to HTML table on custom query page 
                 
                 
                     Fixed out-dated template in documentation 
                 
                 
                     Plugins can now bundle custom templates,  #224 
                 
                 
                     Added /-/metadata /-/plugins /-/inspect,  #225 
                 
                 
                     Documentation for --install option, refs  #223 
                 
                 
                     Datasette publish/package --install option,  #223 
                 
                 
                     Fix for plugins in Python 3.5,  #222 
                 
                 
                     New plugin hooks: extra_css_urls() and extra_js_urls(),  #214 
                 
                 
                     /-/static-plugins/PLUGIN_NAME/ now serves static/ from plugins 
                 
                 
                     <th> now gets class="col-X" - plus added col-X documentation 
                 
                 
                     Use to_css_class for table cell column classes 
                     This ensures that columns with spaces in the name will still
                        generate usable CSS class names. Refs  #209 
                 
                 
                     Add column name classes to <td>s, make PK bold [Russ Garrett] 
                 
                 
                     Don't duplicate simple primary keys in the link column [Russ Garrett] 
                     When there's a simple (single-column) primary key, it looks weird to
                        duplicate it in the link column. 
                     This change removes the second PK column and treats the link column as
                        if it were the PK column from a header/sorting perspective. 
                 
                 
                     Correct escaping for HTML display of row links [Russ Garrett] 
                 
                 
                     Longer time limit for test_paginate_compound_keys 
                     It was failing intermittently in Travis - see  #209 
                 
                 
                     Use application/octet-stream for downloadable databases 
                 
                 
                     Updated PyPI classifiers 
                 
                 
                     Updated PyPI link to pypi.org

references:
[{"href": "https://github.com/simonw/datasette/issues/224", "label": "#224"}, {"href": "https://github.com/simonw/datasette/issues/225", "label": "#225"}, {"href": "https://github.com/simonw/datasette/issues/223", "label": "#223"}, {"href": "https://github.com/simonw/datasette/issues/223", "label": "#223"}, {"href": "https://github.com/simonw/datasette/issues/222", "label": "#222"}, {"href": "https://github.com/simonw/datasette/issues/214", "label": "#214"}, {"href": "https://github.com/simonw/datasette/issues/209", "label": "#209"}, {"href": "https://github.com/simonw/datasette/issues/209", "label": "#209"}]


id: changelog:id145
page: changelog
ref: id145
title: 0.19 (2018-04-16)
breadcrumbs: ["Changelog"]
content:
This is the first preview of the new Datasette plugins mechanism. Only two
                plugin hooks are available so far - for custom SQL functions and custom template
                filters. There's plenty more to come - read  the documentation  and get involved in
                 the tracking ticket  if you
                have feedback on the direction so far. 
             
                 
                     Fix for  _sort_desc=sortable_with_nulls  test, refs  #216 
                 
                 
                     Fixed  #216  - paginate correctly when sorting by nullable column 
                 
                 
                     Initial documentation for plugins, closes  #213 
                     https://docs.datasette.io/en/stable/plugins.html 
                 
                 
                     New  --plugins-dir=plugins/  option ( #212 ) 
                     New option causing Datasette to load and evaluate all of the Python files in
                        the specified directory and register any plugins that are defined in those
                        files. 
                     This new option is available for the following commands: 
                     datasette serve mydb.db --plugins-dir=plugins/
datasette publish now/heroku mydb.db --plugins-dir=plugins/
datasette package mydb.db --plugins-dir=plugins/ 
                 
                 
                     Start of the plugin system, based on pluggy ( #210 ) 
                     Uses  https://pluggy.readthedocs.io/  originally created for the py.test project 
                     We're starting with two plugin hooks: 
                     prepare_connection(conn) 
                     This is called when a new SQLite connection is created. It can be used to register custom SQL functions. 
                     prepare_jinja2_environment(env) 
                     This is called with the Jinja2 environment. It can be used to register custom template tags and filters. 
                     An example plugin which uses these two hooks can be found at  https://github.com/simonw/datasette-plugin-demos  or installed using  pip install datasette-plugin-demos 
                     Refs  #14 
                 
                 
                     Return HTTP 405 on InvalidUsage rather than 500. [Russ Garrett] 
                     This also stops it filling up the logs. This happens for HEAD requests
                        at the moment - which perhaps should be handled better, but that's a
                        different issue.

references:
[{"href": "https://docs.datasette.io/en/stable/plugins.html", "label": "the documentation"}, {"href": "https://github.com/simonw/datasette/issues/14", "label": "the tracking ticket"}, {"href": "https://github.com/simonw/datasette/issues/216", "label": "#216"}, {"href": "https://github.com/simonw/datasette/issues/216", "label": "#216"}, {"href": "https://github.com/simonw/datasette/issues/213", "label": "#213"}, {"href": "https://docs.datasette.io/en/stable/plugins.html", "label": "https://docs.datasette.io/en/stable/plugins.html"}, {"href": "https://github.com/simonw/datasette/issues/212", "label": "#212"}, {"href": "https://github.com/simonw/datasette/issues/14", "label": "#210"}, {"href": "https://pluggy.readthedocs.io/", "label": "https://pluggy.readthedocs.io/"}, {"href": "https://github.com/simonw/datasette-plugin-demos", "label": "https://github.com/simonw/datasette-plugin-demos"}, {"href": "https://github.com/simonw/datasette/issues/14", "label": "#14"}]


id: changelog:id152
page: changelog
ref: id152
title: 0.18 (2018-04-14)
breadcrumbs: ["Changelog"]
content:
This release introduces  support for units ,
                contributed by Russ Garrett ( #203 ).
                You can now optionally specify the units for specific columns using  metadata.json .
                Once specified, units will be displayed in the HTML view of your table. They also become
                available for use in filters - if a column is configured with a unit of distance, you can
                request all rows where that column is less than 50 meters or more than 20 feet for example. 
             
                 
                     Link foreign keys which don't have labels. [Russ Garrett] 
                     This renders unlabeled FKs as simple links. 
                     Also includes bonus fixes for two minor issues: 
                     
                         
                             In foreign key link hrefs the primary key was escaped using HTML
                                escaping rather than URL escaping. This broke some non-integer PKs. 
                         
                         
                             Print tracebacks to console when handling 500 errors. 
                         
                     
                 
                 
                     Fix SQLite error when loading rows with no incoming FKs. [Russ
                        Garrett] 
                     This fixes an error caused by an invalid query when loading incoming FKs. 
                     The error was ignored due to async but it still got printed to the
                        console. 
                 
                 
                     Allow custom units to be registered with Pint. [Russ Garrett] 
                 
                 
                     Support units in filters. [Russ Garrett] 
                 
                 
                     Tidy up units support. [Russ Garrett] 
                     
                         
                             Add units to exported JSON 
                         
                         
                             Units key in metadata skeleton 
                         
                         
                             Docs 
                         
                     
                 
                 
                     Initial units support. [Russ Garrett] 
                     Add support for specifying units for a column in  metadata.json  and
                        rendering them on display using
                         pint

references:
[{"href": "https://docs.datasette.io/en/stable/metadata.html#specifying-units-for-a-column", "label": "support for units"}, {"href": "https://github.com/simonw/datasette/issues/203", "label": "#203"}, {"href": "https://pint.readthedocs.io/en/latest/", "label": "pint"}]


id: changelog:id155
page: changelog
ref: id155
title: 0.16 (2018-04-13)
breadcrumbs: ["Changelog"]
content:
Better mechanism for handling errors; 404s for missing table/database 
                     New error mechanism closes  #193 
                     404s for missing tables/databases closes  #184 
                 
                 
                     long_description in markdown for the new PyPI 
                 
                 
                     Hide SpatiaLite system tables. [Russ Garrett] 
                 
                 
                     Allow  explain select  /  explain query plan select   #201 
                 
                 
                     Datasette inspect now finds primary_keys  #195 
                 
                 
                     Ability to sort using form fields (for mobile portrait mode)  #199 
                     We now display sort options as a select box plus a descending checkbox, which
                        means you can apply sort orders even in portrait mode on a mobile phone where
                        the column headers are hidden.

references:
[{"href": "https://github.com/simonw/datasette/issues/193", "label": "#193"}, {"href": "https://github.com/simonw/datasette/issues/184", "label": "#184"}, {"href": "https://github.com/simonw/datasette/issues/201", "label": "#201"}, {"href": "https://github.com/simonw/datasette/issues/195", "label": "#195"}, {"href": "https://github.com/simonw/datasette/issues/199", "label": "#199"}]


id: changelog:id161
page: changelog
ref: id161
title: 0.15 (2018-04-09)
breadcrumbs: ["Changelog"]
content:
The biggest new feature in this release is the ability to sort by column. On the
                table page the column headers can now be clicked to apply sort (or descending
                sort), or you can specify  ?_sort=column  or  ?_sort_desc=column  directly
                in the URL. 
             
                 
                     table_rows  =>  table_rows_count ,  filtered_table_rows  =>
                         filtered_table_rows_count 
                     Renamed properties. Closes  #194 
                 
                 
                     New  sortable_columns  option in  metadata.json  to control sort options. 
                     You can now explicitly set which columns in a table can be used for sorting
                        using the  _sort  and  _sort_desc  arguments using  metadata.json : 
                     {
    "databases": {
        "database1": {
            "tables": {
                "example_table": {
                    "sortable_columns": [
                        "height",
                        "weight"
                    ]
                }
            }
        }
    }
} 
                     Refs  #189 
                 
                 
                     Column headers now link to sort/desc sort - refs  #189 
                 
                 
                     _sort  and  _sort_desc  parameters for table views 
                     Allows for paginated sorted results based on a specified column. 
                     Refs  #189 
                 
                 
                     Total row count now correct even if  _next  applied 
                 
                 
                     Use .custom_sql() for _group_count implementation (refs  #150 ) 
                 
                 
                     Make HTML title more readable in query template ( #180 ) [Ryan Pitts] 
                 
                 
                     New  ?_shape=objects/object/lists  param for JSON API ( #192 ) 
                     New  _shape=  parameter replacing old  .jsono  extension 
                     Now instead of this: 
                     /database/table.jsono 
                     We use the  _shape  parameter like this: 
                     /database/table.json?_shape=objects 
                     Also introduced a new  _shape  called  object  which looks like this: 
                     /database/table.json?_shape=object 
                     Returning an object for the rows key: 
                     ...
"rows": {
    "pk1": {
        ...
    },
    "pk2": {
        ...
    }
} 
                     Refs  #122 
                 
                 
                     Utility for writing test database fixtures to a .db file 
                     python tests/fixtures.py /tmp/hello.db 
                     This is useful for making a SQLite database of the test fixtures for
                        interactive exploration. 
                 
                 
                     Compound primary key  _next=  now plays well with extra filters 
                     Closes  #190 
                 
                 
                     Fixed bug with keyset pagination over compound primary keys 
                     Refs  #190 
                 
                 
                     Database/Table views inherit  source/license/source_url/license_url 
                        metadata 
                     If you set the  source_url/license_url/source/license  fields in your root
                        metadata those values will now be inherited all the way down to the database
                        and table templates. 
                     The  title/description  are NOT inherited. 
                     Also added unit tests for the HTML generated by the metadata. 
                     Refs  #185 
                 
                 
                     Add metadata, if it exists, to heroku temp dir ( #178 ) [Tony Hirst] 
                 
                 
                     Initial documentation for pagination 
                 
                 
                     Broke up test_app into test_api and test_html 
                 
                 
                     Fixed bug with .json path regular expression 
                     I had a table called  geojson  and it caused an exception because the regex
                        was matching  .json  and not  \.json 
                 
                 
                     Deploy to Heroku with Python 3.6.3

references:
[{"href": "https://github.com/simonw/datasette/issues/194", "label": "#194"}, {"href": "https://github.com/simonw/datasette/issues/189", "label": "#189"}, {"href": "https://github.com/simonw/datasette/issues/189", "label": "#189"}, {"href": "https://github.com/simonw/datasette/issues/189", "label": "#189"}, {"href": "https://github.com/simonw/datasette/issues/150", "label": "#150"}, {"href": "https://github.com/simonw/datasette/issues/180", "label": "#180"}, {"href": "https://github.com/simonw/datasette/issues/192", "label": "#192"}, {"href": "https://github.com/simonw/datasette/issues/122", "label": "#122"}, {"href": "https://github.com/simonw/datasette/issues/190", "label": "#190"}, {"href": "https://github.com/simonw/datasette/issues/190", "label": "#190"}, {"href": "https://github.com/simonw/datasette/issues/185", "label": "#185"}, {"href": "https://github.com/simonw/datasette/issues/178", "label": "#178"}]


id: changelog:id174
page: changelog
ref: id174
title: 0.14 (2017-12-09)
breadcrumbs: ["Changelog"]
content:
The theme of this release is customization: Datasette now allows every aspect
                of its presentation  to be customized 
                either using additional CSS or by providing entirely new templates. 
             Datasette's  metadata.json format 
                has also been expanded, to allow per-database and per-table metadata. A new
                 datasette skeleton  command can be used to generate a skeleton JSON file
                ready to be filled in with per-database and per-table details. 
             The  metadata.json  file can also be used to define
                 canned queries ,
                as a more powerful alternative to SQL views. 
             
                 
                     extra_css_urls / extra_js_urls  in metadata 
                     A mechanism in the  metadata.json  format for adding custom CSS and JS urls. 
                     Create a  metadata.json  file that looks like this: 
                     {
    "extra_css_urls": [
        "https://simonwillison.net/static/css/all.bf8cd891642c.css"
    ],
    "extra_js_urls": [
        "https://code.jquery.com/jquery-3.2.1.slim.min.js"
    ]
} 
                     Then start datasette like this: 
                     datasette mydb.db --metadata=metadata.json 
                     The CSS and JavaScript files will be linked in the  <head>  of every page. 
                     You can also specify a SRI (subresource integrity hash) for these assets: 
                     {
    "extra_css_urls": [
        {
            "url": "https://simonwillison.net/static/css/all.bf8cd891642c.css",
            "sri": "sha384-9qIZekWUyjCyDIf2YK1FRoKiPJq4PHt6tp/ulnuuyRBvazd0hG7pWbE99zvwSznI"
        }
    ],
    "extra_js_urls": [
        {
            "url": "https://code.jquery.com/jquery-3.2.1.slim.min.js",
            "sri": "sha256-k2WSCIexGzOj3Euiig+TlR8gA0EmPjuc79OEeY5L45g="
        }
    ]
} 
                     Modern browsers will only execute the stylesheet or JavaScript if the SRI hash
                        matches the content served. You can generate hashes using  https://www.srihash.org/ 
                 
                 
                     Auto-link column values that look like URLs ( #153 ) 
                 
                 
                     CSS styling hooks as classes on the body ( #153 ) 
                     Every template now gets CSS classes in the body designed to support custom
                        styling. 
                     The index template (the top level page at  / ) gets this: 
                     <body class="index"> 
                     The database template ( /dbname/ ) gets this: 
                     <body class="db db-dbname"> 
                     The table template ( /dbname/tablename ) gets: 
                     <body class="table db-dbname table-tablename"> 
                     The row template ( /dbname/tablename/rowid ) gets: 
                     <body class="row db-dbname table-tablename"> 
                     The  db-x  and  table-x  classes use the database or table names themselves IF
                        they are valid CSS identifiers. If they aren't, we strip any invalid
                        characters out and append a 6 character md5 digest of the original name, in
                        order to ensure that multiple tables which resolve to the same stripped
                        character version still have different CSS classes. 
                     Some examples (extracted from the unit tests): 
                     "simple" => "simple"
"MixedCase" => "MixedCase"
"-no-leading-hyphens" => "no-leading-hyphens-65bea6"
"_no-leading-underscores" => "no-leading-underscores-b921bc"
"no spaces" => "no-spaces-7088d7"
"-" => "336d5e"
"no $ characters" => "no--characters-59e024" 
                 
                 
                     datasette --template-dir=mytemplates/  argument 
                     You can now pass an additional argument specifying a directory to look for
                        custom templates in. 
                     Datasette will fall back on the default templates if a template is not
                        found in that directory. 
                 
                 
                     Ability to over-ride templates for individual tables/databases. 
                     It is now possible to over-ride templates on a per-database / per-row or per-
                        table basis. 
                     When you access e.g.  /mydatabase/mytable  Datasette will look for the following: 
                     - table-mydatabase-mytable.html
- table.html 
                     If you provided a  --template-dir  argument to datasette serve it will look in
                        that directory first. 
                     The lookup rules are as follows: 
                     Index page (/):
    index.html

Database page (/mydatabase):
    database-mydatabase.html
    database.html

Table page (/mydatabase/mytable):
    table-mydatabase-mytable.html
    table.html

Row page (/mydatabase/mytable/id):
    row-mydatabase-mytable.html
    row.html 
                     If a table name has spaces or other unexpected characters in it, the template
                        filename will follow the same rules as our custom  <body>  CSS classes
                        - for example, a table called "Food Trucks"
                        will attempt to load the following templates: 
                     table-mydatabase-Food-Trucks-399138.html
table.html 
                     It is possible to extend the default templates using Jinja template
                        inheritance. If you want to customize EVERY row template with some additional
                        content you can do so by creating a row.html template like this: 
                     {% extends "default:row.html" %}

{% block content %}
<h1>EXTRA HTML AT THE TOP OF THE CONTENT BLOCK</h1>
<p>This line renders the original block:</p>
{{ super() }}
{% endblock %} 
                 
                 
                     --static  option for datasette serve ( #160 ) 
                     You can now tell Datasette to serve static files from a specific location at a
                        specific mountpoint. 
                     For example: 
                     datasette serve mydb.db --static extra-css:/tmp/static/css 
                     Now if you visit this URL: 
                     http://localhost:8001/extra-css/blah.css 
                     The following file will be served: 
                     /tmp/static/css/blah.css 
                 
                 
                     Canned query support. 
                     Named canned queries can now be defined in  metadata.json  like this: 
                     {
    "databases": {
        "timezones": {
            "queries": {
                "timezone_for_point": "select tzid from timezones ..."
            }
        }
    }
} 
                     These will be shown in a new "Queries" section beneath "Views" on the database page. 
                 
                 
                     New  datasette skeleton  command for generating  metadata.json  ( #164 ) 
                 
                 
                     metadata.json  support for per-table/per-database metadata ( #165 ) 
                     Also added support for descriptions and HTML descriptions. 
                     Here's an example metadata.json file illustrating custom per-database and per-
                        table metadata: 
                     {
    "title": "Overall datasette title",
    "description_html": "This is a <em>description with HTML</em>.",
    "databases": {
        "db1": {
            "title": "First database",
            "description": "This is a string description & has no HTML",
            "license_url": "http://example.com/",
        "license": "The example license",
            "queries": {
              "canned_query": "select * from table1 limit 3;"
            },
            "tables": {
                "table1": {
                    "title": "Custom title for table1",
                    "description": "Tables can have descriptions too",
                    "source": "This has a custom source",
                    "source_url": "http://example.com/"
                }
            }
        }
    }
} 
                 
                 
                     Renamed  datasette build  command to  datasette inspect  ( #130 ) 
                 
                 
                     Upgrade to Sanic 0.7.0 ( #168 ) 
                     https://github.com/channelcat/sanic/releases/tag/0.7.0 
                 
                 
                     Package and publish commands now accept  --static  and  --template-dir 
                     Example usage: 
                     datasette package --static css:extra-css/ --static js:extra-js/ \
  sf-trees.db --template-dir templates/ --tag sf-trees --branch master 
                     This creates a local Docker image that includes copies of the templates/,
                        extra-css/ and extra-js/ directories. You can then run it like this: 
                     docker run -p 8001:8001 sf-trees 
                     For publishing to Zeit now: 
                     datasette publish now --static css:extra-css/ --static js:extra-js/ \
  sf-trees.db --template-dir templates/ --name sf-trees --branch master 
                 
                 
                     HTML comment showing which templates were considered for a page ( #171 )

references:
[{"href": "https://docs.datasette.io/en/stable/custom_templates.html", "label": "to be customized"}, {"href": "https://docs.datasette.io/en/stable/metadata.html", "label": "metadata.json format"}, {"href": "https://docs.datasette.io/en/stable/sql_queries.html#canned-queries", "label": "canned queries"}, {"href": "https://www.srihash.org/", "label": "https://www.srihash.org/"}, {"href": "https://github.com/simonw/datasette/issues/153", "label": "#153"}, {"href": "https://github.com/simonw/datasette/issues/153", "label": "#153"}, {"href": "https://github.com/simonw/datasette/issues/160", "label": "#160"}, {"href": "https://github.com/simonw/datasette/issues/164", "label": "#164"}, {"href": "https://github.com/simonw/datasette/issues/165", "label": "#165"}, {"href": "https://github.com/simonw/datasette/issues/130", "label": "#130"}, {"href": "https://github.com/simonw/datasette/issues/168", "label": "#168"}, {"href": "https://github.com/channelcat/sanic/releases/tag/0.7.0", "label": "https://github.com/channelcat/sanic/releases/tag/0.7.0"}, {"href": "https://github.com/simonw/datasette/issues/171", "label": "#171"}]


id: changelog:id183
page: changelog
ref: id183
title: 0.13 (2017-11-24)
breadcrumbs: ["Changelog"]
content:
Search now applies to current filters. 
                     Combined search into the same form as filters. 
                     Closes  #133 
                 
                 
                     Much tidier design for table view header. 
                     Closes  #147 
                 
                 
                     Added  ?column__not=blah  filter. 
                     Closes  #148 
                 
                 
                     Row page now resolves foreign keys. 
                     Closes  #132 
                 
                 
                     Further tweaks to select/input filter styling. 
                     Refs  #86  - thanks for the help, @natbat! 
                 
                 
                     Show linked foreign key in table cells. 
                 
                 
                     Added UI for editing table filters. 
                     Refs  #86 
                 
                 
                     Hide FTS-created tables on index pages. 
                     Closes  #129 
                 
                 
                     Add publish to heroku support [Jacob Kaplan-Moss] 
                     datasette publish heroku mydb.db 
                     Pull request  #104 
                 
                 
                     Initial implementation of  ?_group_count=column . 
                     URL shortcut for counting rows grouped by one or more columns. 
                     ?_group_count=column1&_group_count=column2  works as well. 
                     SQL generated looks like this: 
                     select "qSpecies", count(*) as "count"
from Street_Tree_List
group by "qSpecies"
order by "count" desc limit 100 
                     Or for two columns like this: 
                     select "qSpecies", "qSiteInfo", count(*) as "count"
from Street_Tree_List
group by "qSpecies", "qSiteInfo"
order by "count" desc limit 100 
                     Refs  #44 
                 
                 
                     Added  --build=master  option to datasette publish and package. 
                     The  datasette publish  and  datasette package  commands both now accept an
                        optional  --build  argument. If provided, this can be used to specify a branch
                        published to GitHub that should be built into the container. 
                     This makes it easier to test code that has not yet been officially released to
                        PyPI, e.g.: 
                     datasette publish now mydb.db --branch=master 
                 
                 
                     Implemented  ?_search=XXX  + UI if a FTS table is detected. 
                     Closes  #131 
                 
                 
                     Added  datasette --version  support. 
                 
                 
                     Table views now show expanded foreign key references, if possible. 
                     If a table has foreign key columns, and those foreign key tables have
                         label_columns , the TableView will now query those other tables for the
                        corresponding values and display those values as links in the corresponding
                        table cells. 
                     label_columns are currently detected by the  inspect()  function, which looks
                        for any table that has just two columns - an ID column and one other - and
                        sets the  label_column  to be that second non-ID column. 
                 
                 
                     Don't prevent tabbing to "Run SQL" button ( #117 ) [Robert Gieseke] 
                     See comment in  #115 
                 
                 
                     Add keyboard shortcut to execute SQL query ( #115 ) [Robert Gieseke] 
                 
                 
                     Allow  --load-extension  to be set via environment variable. 
                 
                 
                     Add support for  ?field__isnull=1  ( #107 ) [Ray N] 
                 
                 
                     Add spatialite, switch to debian and local build ( #114 ) [Ariel Núñez] 
                 
                 
                     Added  --load-extension  argument to datasette serve. 
                     Allows loading of SQLite extensions. Refs  #110 .

references:
[{"href": "https://github.com/simonw/datasette/issues/133", "label": "#133"}, {"href": "https://github.com/simonw/datasette/issues/147", "label": "#147"}, {"href": "https://github.com/simonw/datasette/issues/148", "label": "#148"}, {"href": "https://github.com/simonw/datasette/issues/132", "label": "#132"}, {"href": "https://github.com/simonw/datasette/issues/86", "label": "#86"}, {"href": "https://github.com/simonw/datasette/issues/86", "label": "#86"}, {"href": "https://github.com/simonw/datasette/issues/129", "label": "#129"}, {"href": "https://github.com/simonw/datasette/issues/104", "label": "#104"}, {"href": "https://github.com/simonw/datasette/issues/44", "label": "#44"}, {"href": "https://github.com/simonw/datasette/issues/131", "label": "#131"}, {"href": "https://github.com/simonw/datasette/issues/117", "label": "#117"}, {"href": "https://github.com/simonw/datasette/issues/115", "label": "#115"}, {"href": "https://github.com/simonw/datasette/issues/115", "label": "#115"}, {"href": "https://github.com/simonw/datasette/issues/107", "label": "#107"}, {"href": "https://github.com/simonw/datasette/issues/114", "label": "#114"}, {"href": "https://github.com/simonw/datasette/issues/110", "label": "#110"}]


id: changelog:id198
page: changelog
ref: id198
title: 0.12 (2017-11-16)
breadcrumbs: ["Changelog"]
content:
Added  __version__ , now displayed as tooltip in page footer ( #108 ). 
                 
                 
                     Added initial docs, including a changelog ( #99 ). 
                 
                 
                     Turned on auto-escaping in Jinja. 
                 
                 
                     Added a UI for editing named parameters ( #96 ). 
                     You can now construct a custom SQL statement using SQLite named
                        parameters (e.g.  :name ) and datasette will display form fields for
                        editing those parameters.  Here’s an example  which lets you see the
                        most popular names for dogs of different species registered through
                        various dog registration schemes in Australia. 
                 
             
             
             
                 
                     Pin to specific Jinja version. ( #100 ). 
                 
                 
                     Default to 127.0.0.1 not 0.0.0.0. ( #98 ). 
                 
                 
                     Added extra metadata options to publish and package commands. ( #92 ). 
                     You can now run these commands like so: 
                     datasette now publish mydb.db \
    --title="My Title" \
    --source="Source" \
    --source_url="http://www.example.com/" \
    --license="CC0" \
    --license_url="https://creativecommons.org/publicdomain/zero/1.0/" 
                     This will write those values into the metadata.json that is packaged with the
                        app. If you also pass  --metadata=metadata.json  that file will be updated with the extra
                        values before being written into the Docker image. 
                 
                 
                     Added production-ready Dockerfile ( #94 ) [Andrew
                        Cutler] 
                 
                 
                     New  ?_sql_time_limit_ms=10  argument to database and table page ( #95 ) 
                 
                 
                     SQL syntax highlighting with Codemirror ( #89 ) [Tom Dyson]

references:
[{"href": "https://github.com/simonw/datasette/issues/108", "label": "#108"}, {"href": "https://github.com/simonw/datasette/issues/99", "label": "#99"}, {"href": "https://github.com/simonw/datasette/issues/96", "label": "#96"}, {"href": "https://australian-dogs.now.sh/australian-dogs-3ba9628?sql=select+name%2C+count%28*%29+as+n+from+%28%0D%0A%0D%0Aselect+upper%28%22Animal+name%22%29+as+name+from+%5BAdelaide-City-Council-dog-registrations-2013%5D+where+Breed+like+%3Abreed%0D%0A%0D%0Aunion+all%0D%0A%0D%0Aselect+upper%28Animal_Name%29+as+name+from+%5BAdelaide-City-Council-dog-registrations-2014%5D+where+Breed_Description+like+%3Abreed%0D%0A%0D%0Aunion+all+%0D%0A%0D%0Aselect+upper%28Animal_Name%29+as+name+from+%5BAdelaide-City-Council-dog-registrations-2015%5D+where+Breed_Description+like+%3Abreed%0D%0A%0D%0Aunion+all%0D%0A%0D%0Aselect+upper%28%22AnimalName%22%29+as+name+from+%5BCity-of-Port-Adelaide-Enfield-Dog_Registrations_2016%5D+where+AnimalBreed+like+%3Abreed%0D%0A%0D%0Aunion+all%0D%0A%0D%0Aselect+upper%28%22Animal+Name%22%29+as+name+from+%5BMitcham-dog-registrations-2015%5D+where+Breed+like+%3Abreed%0D%0A%0D%0Aunion+all%0D%0A%0D%0Aselect+upper%28%22DOG_NAME%22%29+as+name+from+%5Bburnside-dog-registrations-2015%5D+where+DOG_BREED+like+%3Abreed%0D%0A%0D%0Aunion+all+%0D%0A%0D%0Aselect+upper%28%22Animal_Name%22%29+as+name+from+%5Bcity-of-playford-2015-dog-registration%5D+where+Breed_Description+like+%3Abreed%0D%0A%0D%0Aunion+all%0D%0A%0D%0Aselect+upper%28%22Animal+Name%22%29+as+name+from+%5Bcity-of-prospect-dog-registration-details-2016%5D+where%22Breed+Description%22+like+%3Abreed%0D%0A%0D%0A%29+group+by+name+order+by+n+desc%3B&breed=pug", "label": "Here’s an example"}, {"href": "https://github.com/simonw/datasette/issues/100", "label": "#100"}, {"href": "https://github.com/simonw/datasette/issues/98", "label": "#98"}, {"href": "https://github.com/simonw/datasette/issues/92", "label": "#92"}, {"href": "https://github.com/simonw/datasette/issues/94", "label": "#94"}, {"href": "https://github.com/simonw/datasette/issues/95", "label": "#95"}, {"href": "https://github.com/simonw/datasette/issues/89", "label": "#89"}]


id: changelog:id208
page: changelog
ref: id208
title: 0.11 (2017-11-14)
breadcrumbs: ["Changelog"]
references: []
content:
Added  datasette publish now --force  option. 
                     This calls  now  with  --force  - useful as it means you get a fresh copy of datasette even if Now has already cached that docker layer. 
                 
                 
                     Enable  --cors  by default when running in a container.


id: changelog:id209
page: changelog
ref: id209
title: 0.10 (2017-11-14)
breadcrumbs: ["Changelog"]
references: [{"href": "https://github.com/simonw/datasette/issues/83", "label": "#83"}]
content:
Fixed  #83  - 500 error on individual row pages. 
                 
                 
                     Stop using sqlite WITH RECURSIVE in our tests. 
                     The version of Python 3 running in Travis CI doesn't support this.


id: changelog:id211
page: changelog
ref: id211
title: 0.9 (2017-11-13)
breadcrumbs: ["Changelog"]
references: []
content:
Added  --sql_time_limit_ms  and  --extra-options . 
                     The serve command now accepts  --sql_time_limit_ms  for customizing the SQL time
                        limit. 
                     The publish and package commands now accept  --extra-options  which can be used
                        to specify additional options to be passed to the datasite serve command when
                        it executes inside the resulting Docker containers.


id: changelog:id212
page: changelog
ref: id212
title: 0.8 (2017-11-13)
breadcrumbs: ["Changelog"]
content:
V0.8 - added PyPI metadata, ready to ship. 
                 
                 
                     Implemented offset/limit pagination for views ( #70 ). 
                 
                 
                     Improved pagination. ( #78 ) 
                 
                 
                     Limit on max rows returned, controlled by  --max_returned_rows  option. ( #69 ) 
                     If someone executes 'select * from table' against a table with a million rows
                        in it, we could run into problems: just serializing that much data as JSON is
                        likely to lock up the server. 
                     Solution: we now have a hard limit on the maximum number of rows that can be
                        returned by a query. If that limit is exceeded, the server will return a
                         "truncated": true  field in the JSON. 
                     This limit can be optionally controlled by the new  --max_returned_rows 
                        option. Setting that option to 0 disables the limit entirely.

references:
[{"href": "https://github.com/simonw/datasette/issues/70", "label": "#70"}, {"href": "https://github.com/simonw/datasette/issues/78", "label": "#78"}, {"href": "https://github.com/simonw/datasette/issues/69", "label": "#69"}]


id: settings:using-setting
page: settings
ref: using-setting
title: Using --setting
breadcrumbs: ["Settings"]
references: []
content:
Datasette supports a number of settings. These can be set using the  --setting name value  option to  datasette serve . 
             You can set multiple settings at once like this: 
             datasette mydatabase.db \
  --setting default_page_size 50 \
  --setting sql_time_limit_ms 3500 \
  --setting max_returned_rows 2000 
             Settings can also be specified  in the database.yaml configuration file .


id: settings:config-dir
page: settings
ref: config-dir
title: Configuration directory mode
breadcrumbs: ["Settings"]
references: []
content:
Normally you configure Datasette using command-line options. For a Datasette instance with custom templates, custom plugins, a static directory and several databases this can get quite verbose: 
             datasette one.db two.db \
  --metadata=metadata.json \
  --template-dir=templates/ \
  --plugins-dir=plugins \
  --static css:css 
             As an alternative to this, you can run Datasette in  configuration directory  mode. Create a directory with the following structure: 
             # In a directory called my-app:
my-app/one.db
my-app/two.db
my-app/datasette.yaml
my-app/metadata.json
my-app/templates/index.html
my-app/plugins/my_plugin.py
my-app/static/my.css 
             Now start Datasette by providing the path to that directory: 
             datasette my-app/ 
             Datasette will detect the files in that directory and automatically configure itself using them. It will serve all  *.db  files that it finds, will load  metadata.json  if it exists, and will load the  templates ,  plugins  and  static  folders if they are present. 
             The files that can be included in this directory are as follows. All are optional. 
             
                 
                     *.db  (or  *.sqlite3  or  *.sqlite ) - SQLite database files that will be served by Datasette 
                 
                 
                     datasette.yaml  -  Configuration  for the Datasette instance 
                 
                 
                     metadata.json  -  Metadata  for those databases -  metadata.yaml  or  metadata.yml  can be used as well 
                 
                 
                     inspect-data.json  - the result of running  datasette inspect *.db --inspect-file=inspect-data.json  from the configuration directory - any database files listed here will be treated as immutable, so they should not be changed while Datasette is running 
                 
                 
                     templates/  - a directory containing  Custom templates 
                 
                 
                     plugins/  - a directory containing plugins, see  Writing one-off plugins 
                 
                 
                     static/  - a directory containing static files - these will be served from  /static/filename.txt , see  Serving static files


id: settings:id2
page: settings
ref: id2
title: Settings
breadcrumbs: ["Settings"]
references: []
content:
The following options can be set using  --setting name value , or by storing them in the  settings.json  file for use with  Configuration directory mode .


id: settings:setting-default-allow-sql
page: settings
ref: setting-default-allow-sql
title: default_allow_sql
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Should users be able to execute arbitrary SQL queries by default? 
                 Setting this to  off  causes permission checks for  execute-sql  to fail by default. 
                 datasette mydatabase.db --setting default_allow_sql off 
                 Another way to achieve this is to add  "allow_sql": false  to your  datasette.yaml  file, as described in  Controlling the ability to execute arbitrary SQL . This setting offers a more convenient way to do this.


id: settings:setting-default-page-size
page: settings
ref: setting-default-page-size
title: default_page_size
breadcrumbs: ["Settings", "Settings"]
references: []
content:
The default number of rows returned by the table page. You can over-ride this on a per-page basis using the  ?_size=80  query string parameter, provided you do not specify a value higher than the  max_returned_rows  setting. You can set this default using  --setting  like so: 
                 datasette mydatabase.db --setting default_page_size 50


id: settings:setting-sql-time-limit-ms
page: settings
ref: setting-sql-time-limit-ms
title: sql_time_limit_ms
breadcrumbs: ["Settings", "Settings"]
references: []
content:
By default, queries have a time limit of one second. If a query takes longer than this to run Datasette will terminate the query and return an error. 
                 If this time limit is too short for you, you can customize it using the  sql_time_limit_ms  limit - for example, to increase it to 3.5 seconds: 
                 datasette mydatabase.db --setting sql_time_limit_ms 3500 
                 You can optionally set a lower time limit for an individual query using the  ?_timelimit=100  query string argument: 
                 /my-database/my-table?qSpecies=44&_timelimit=100 
                 This would set the time limit to 100ms for that specific query. This feature is useful if you are working with databases of unknown size and complexity - a query that might make perfect sense for a smaller table could take too long to execute on a table with millions of rows. By setting custom time limits you can execute queries "optimistically" - e.g. give me an exact count of rows matching this query but only if it takes less than 100ms to calculate.


id: settings:setting-max-returned-rows
page: settings
ref: setting-max-returned-rows
title: max_returned_rows
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Datasette returns a maximum of 1,000 rows of data at a time. If you execute a query that returns more than 1,000 rows, Datasette will return the first 1,000 and include a warning that the result set has been truncated. You can use OFFSET/LIMIT or other methods in your SQL to implement pagination if you need to return more than 1,000 rows. 
                 You can increase or decrease this limit like so: 
                 datasette mydatabase.db --setting max_returned_rows 2000


id: settings:setting-max-insert-rows
page: settings
ref: setting-max-insert-rows
title: max_insert_rows
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Maximum rows that can be inserted at a time using the bulk insert API, see  Inserting rows . Defaults to 100. 
                 You can increase or decrease this limit like so: 
                 datasette mydatabase.db --setting max_insert_rows 1000


id: settings:setting-num-sql-threads
page: settings
ref: setting-num-sql-threads
title: num_sql_threads
breadcrumbs: ["Settings", "Settings"]
references: [{"href": "https://pyodide.org/", "label": "Pyodide"}]
content:
Maximum number of threads in the thread pool Datasette uses to execute SQLite queries. Defaults to 3. 
                 datasette mydatabase.db --setting num_sql_threads 10 
                 Setting this to 0 turns off threaded SQL queries entirely - useful for environments that do not support threading such as  Pyodide .


id: settings:setting-allow-facet
page: settings
ref: setting-allow-facet
title: allow_facet
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Allow users to specify columns they would like to facet on using the  ?_facet=COLNAME  URL parameter to the table view. 
                 This is enabled by default. If disabled, facets will still be displayed if they have been specifically enabled in  metadata.json  configuration for the table. 
                 Here's how to disable this feature: 
                 datasette mydatabase.db --setting allow_facet off


id: settings:setting-default-facet-size
page: settings
ref: setting-default-facet-size
title: default_facet_size
breadcrumbs: ["Settings", "Settings"]
references: []
content:
The default number of unique rows returned by  Facets  is 30. You can customize it like this: 
                 datasette mydatabase.db --setting default_facet_size 50


id: settings:setting-facet-time-limit-ms
page: settings
ref: setting-facet-time-limit-ms
title: facet_time_limit_ms
breadcrumbs: ["Settings", "Settings"]
references: []
content:
This is the time limit Datasette allows for calculating a facet, which defaults to 200ms: 
                 datasette mydatabase.db --setting facet_time_limit_ms 1000


id: settings:setting-facet-suggest-time-limit-ms
page: settings
ref: setting-facet-suggest-time-limit-ms
title: facet_suggest_time_limit_ms
breadcrumbs: ["Settings", "Settings"]
references: []
content:
When Datasette calculates suggested facets it needs to run a SQL query for every column in your table. The default for this time limit is 50ms to account for the fact that it needs to run once for every column. If the time limit is exceeded the column will not be suggested as a facet. 
                 You can increase this time limit like so: 
                 datasette mydatabase.db --setting facet_suggest_time_limit_ms 500


id: settings:setting-suggest-facets
page: settings
ref: setting-suggest-facets
title: suggest_facets
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Should Datasette calculate suggested facets? On by default, turn this off like so: 
                 datasette mydatabase.db --setting suggest_facets off


id: settings:setting-allow-download
page: settings
ref: setting-allow-download
title: allow_download
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Should users be able to download the original SQLite database using a link on the database index page? This is turned on by default. However, databases can only be downloaded if they are served in immutable mode and not in-memory. If downloading is unavailable for either of these reasons, the download link is hidden even if  allow_download  is on. To disable database downloads, use the following: 
                 datasette mydatabase.db --setting allow_download off


id: settings:setting-allow-signed-tokens
page: settings
ref: setting-allow-signed-tokens
title: allow_signed_tokens
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Should users be able to create signed API tokens to access Datasette? 
                 This is turned on by default. Use the following to turn it off: 
                 datasette mydatabase.db --setting allow_signed_tokens off 
                 Turning this setting off will disable the  /-/create-token  page,  described here . It will also cause any incoming  Authorization: Bearer dstok_...  API tokens to be ignored.


id: settings:setting-max-signed-tokens-ttl
page: settings
ref: setting-max-signed-tokens-ttl
title: max_signed_tokens_ttl
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Maximum allowed expiry time for signed API tokens created by users. 
                 Defaults to  0  which means no limit - tokens can be created that will never expire. 
                 Set this to a value in seconds to limit the maximum expiry time. For example, to set that limit to 24 hours you would use: 
                 datasette mydatabase.db --setting max_signed_tokens_ttl 86400 
                 This setting is enforced when incoming tokens are processed.


id: settings:setting-default-cache-ttl
page: settings
ref: setting-default-cache-ttl
title: default_cache_ttl
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Default HTTP caching max-age header in seconds, used for  Cache-Control: max-age=X . Can be over-ridden on a per-request basis using the  ?_ttl=  query string parameter. Set this to  0  to disable HTTP caching entirely. Defaults to 5 seconds. 
                 datasette mydatabase.db --setting default_cache_ttl 60


id: settings:setting-cache-size-kb
page: settings
ref: setting-cache-size-kb
title: cache_size_kb
breadcrumbs: ["Settings", "Settings"]
references: [{"href": "https://www.sqlite.org/pragma.html#pragma_cache_size", "label": "per-connection cache"}]
content:
Sets the amount of memory SQLite uses for its  per-connection cache , in KB. 
                 datasette mydatabase.db --setting cache_size_kb 5000


id: settings:setting-allow-csv-stream
page: settings
ref: setting-allow-csv-stream
title: allow_csv_stream
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Enables  the CSV export feature  where an entire table
                    (potentially hundreds of thousands of rows) can be exported as a single CSV
                    file. This is turned on by default - you can turn it off like this: 
                 datasette mydatabase.db --setting allow_csv_stream off


id: settings:setting-max-csv-mb
page: settings
ref: setting-max-csv-mb
title: max_csv_mb
breadcrumbs: ["Settings", "Settings"]
references: []
content:
The maximum size of CSV that can be exported, in megabytes. Defaults to 100MB.
                    You can disable the limit entirely by settings this to 0: 
                 datasette mydatabase.db --setting max_csv_mb 0


id: settings:setting-truncate-cells-html
page: settings
ref: setting-truncate-cells-html
title: truncate_cells_html
breadcrumbs: ["Settings", "Settings"]
references: []
content:
In the HTML table view, truncate any strings that are longer than this value.
                    The full value will still be available in CSV, JSON and on the individual row
                    HTML page. Set this to 0 to disable truncation. 
                 datasette mydatabase.db --setting truncate_cells_html 0


id: settings:setting-force-https-urls
page: settings
ref: setting-force-https-urls
title: force_https_urls
breadcrumbs: ["Settings", "Settings"]
references: []
content:
Forces self-referential URLs in the JSON output to always use the  https:// 
                    protocol. This is useful for cases where the application itself is hosted using
                    HTTP but is served to the outside world via a proxy that enables HTTPS. 
                 datasette mydatabase.db --setting force_https_urls 1


id: settings:setting-template-debug
page: settings
ref: setting-template-debug
title: template_debug
breadcrumbs: ["Settings", "Settings"]
content:
This setting enables template context debug mode, which is useful to help understand what variables are available to custom templates when you are writing them. 
                 Enable it like this: 
                 datasette mydatabase.db --setting template_debug 1 
                 Now you can add  ?_context=1  or  &_context=1  to any Datasette page to see the context that was passed to that template. 
                 Some examples: 
                 
                     
                         https://latest.datasette.io/?_context=1 
                     
                     
                         https://latest.datasette.io/fixtures?_context=1 
                     
                     
                         https://latest.datasette.io/fixtures/roadside_attractions?_context=1

references:
[{"href": "https://latest.datasette.io/?_context=1", "label": "https://latest.datasette.io/?_context=1"}, {"href": "https://latest.datasette.io/fixtures?_context=1", "label": "https://latest.datasette.io/fixtures?_context=1"}, {"href": "https://latest.datasette.io/fixtures/roadside_attractions?_context=1", "label": "https://latest.datasette.io/fixtures/roadside_attractions?_context=1"}]


id: settings:setting-trace-debug
page: settings
ref: setting-trace-debug
title: trace_debug
breadcrumbs: ["Settings", "Settings"]
content:
This setting enables appending  ?_trace=1  to any page in order to see the SQL queries and other trace information that was used to generate that page. 
                 Enable it like this: 
                 datasette mydatabase.db --setting trace_debug 1 
                 Some examples: 
                 
                     
                         https://latest.datasette.io/?_trace=1 
                     
                     
                         https://latest.datasette.io/fixtures/roadside_attractions?_trace=1 
                     
                 
                 See  datasette.tracer  for details on how to hook into this mechanism as a plugin author.

references:
[{"href": "https://latest.datasette.io/?_trace=1", "label": "https://latest.datasette.io/?_trace=1"}, {"href": "https://latest.datasette.io/fixtures/roadside_attractions?_trace=1", "label": "https://latest.datasette.io/fixtures/roadside_attractions?_trace=1"}]


id: settings:setting-base-url
page: settings
ref: setting-base-url
title: base_url
breadcrumbs: ["Settings", "Settings"]
references: []
content:
If you are running Datasette behind a proxy, it may be useful to change the root path used for the Datasette instance. 
                 For example, if you are sending traffic from  https://www.example.com/tools/datasette/  through to a proxied Datasette instance you may wish Datasette to use  /tools/datasette/  as its root URL. 
                 You can do that like so: 
                 datasette mydatabase.db --setting base_url /tools/datasette/


id: settings:setting-secret
page: settings
ref: setting-secret
title: Configuring the secret
breadcrumbs: ["Settings"]
references: []
content:
Datasette uses a secret string to sign secure values such as cookies. 
             If you do not provide a secret, Datasette will create one when it starts up. This secret will reset every time the Datasette server restarts though, so things like authentication cookies and  API tokens  will not stay valid between restarts. 
             You can pass a secret to Datasette in two ways: with the  --secret  command-line option or by setting a  DATASETTE_SECRET  environment variable. 
             datasette mydb.db --secret=SECRET_VALUE_HERE 
             Or: 
             export DATASETTE_SECRET=SECRET_VALUE_HERE
datasette mydb.db 
             One way to generate a secure random secret is to use Python like this: 
             python3 -c 'import secrets; print(secrets.token_hex(32))'
cdb19e94283a20f9d42cca50c5a4871c0aa07392db308755d60a1a5b9bb0fa52 
             Plugin authors make use of this signing mechanism in their plugins using  .sign(value, namespace="default")  and  .unsign(value, namespace="default") .


id: settings:setting-publish-secrets
page: settings
ref: setting-publish-secrets
title: Using secrets with datasette publish
breadcrumbs: ["Settings"]
references: []
content:
The  datasette publish  and  datasette package  commands both generate a secret for you automatically when Datasette is deployed. 
             This means that every time you deploy a new version of a Datasette project, a new secret will be generated. This will cause signed cookies to become invalid on every fresh deploy. 
             You can fix this by creating a secret that will be used for multiple deploys and passing it using the  --secret  option: 
             datasette publish cloudrun mydb.db --service=my-service --secret=cdb19e94283a20f9d42cca5


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

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


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

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


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

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


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

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


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

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


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


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


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


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


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


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

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


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

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


id: authentication:authentication-permissions-config
page: authentication
ref: authentication-permissions-config
title: Access permissions in
breadcrumbs: ["Authentication and permissions"]
references: []
content:
There are two ways to configure permissions using  datasette.yaml  (or  datasette.json ). 
             For simple visibility permissions you can use  "allow"  blocks in the root, database, table and query sections. 
             For other permissions you can use a  "permissions"  block, described  in the next section . 
             You can limit who is allowed to view different parts of your Datasette instance using  "allow"  keys in your  Configuration . 
             You can control the following: 
             
                 
                     Access to the entire Datasette instance 
                 
                 
                     Access to specific databases 
                 
                 
                     Access to specific tables and views 
                 
                 
                     Access to specific  Canned queries 
                 
             
             If a user cannot access a specific database, they will not be able to access tables, views or queries within that database. If a user cannot access the instance they will not be able to access any of the databases, tables, views or queries.


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

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


id: authentication:authentication-permissions-database
page: authentication
ref: authentication-permissions-database
title: Access to specific databases
breadcrumbs: ["Authentication and permissions", "Access permissions in "]
references: []
content:
To limit access to a specific  private.db  database to just authenticated users, use the  "allow"  block like this: 
                 [[[cog
config_example(cog, """
    databases:
      private:
        allow:
          id: "*"
""") 
                 ]]] 
                 [[[end]]]


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

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


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


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

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


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


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


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


