---
name: datasette-plugin-writer
description: Guide for writing Datasette plugins. This skill should be used when users want to create or develop plugins for Datasette, including information about plugin hooks, the cookiecutter template, database APIs, request/response handling, and plugin configuration.
license: Apache-2.0
---
# Writing Datasette Plugins
Use this skill to build plugins for Datasette, the open source multi-tool for exploring and publishing data.
## Quick Start with Cookiecutter Template
Start a new plugin using the datasette-plugin cookiecutter template with newline-delimited variables:
```bash
echo "plugin_name
description of plugin
plugin-hyphenated-name
plugin_underscored_name
github_username
Author Name
y
y" | uvx cookiecutter gh:simonw/datasette-plugin
```
Example for a plugin called "my-cool-plugin":
```bash
echo "my cool plugin
A plugin that does cool things
my-cool-plugin
my_cool_plugin
username
Your Name
y
y" | uvx cookiecutter gh:simonw/datasette-plugin
```
The last two `y` responses enable static/ and templates/ directories.
After creating the plugin:
```bash
cd datasette-my-cool-plugin
python -m venv venv
source venv/bin/activate
pip install -e '.[test]'
datasette plugins # Verify plugin is visible
python -m pytest # Run tests
```
## Plugin Structure
A typical plugin structure:
```
datasette-my-plugin/
├── datasette_my_plugin/
│ ├── __init__.py # Plugin hooks go here
│ ├── static/ # Optional: CSS, JavaScript
│ └── templates/ # Optional: Custom templates
├── tests/
│ └── test_my_plugin.py
├── setup.py or pyproject.toml
└── README.md
```
## Essential Plugin Hooks
### prepare_connection(conn, database, datasette)
Register custom SQL functions. Called when SQLite connections are created:
```python
from datasette import hookimpl
@hookimpl
def prepare_connection(conn):
conn.create_function("hello_world", 0, lambda: "Hello world!")
```
### register_routes(datasette)
Add custom URL routes. Return list of (regex, view_function) pairs:
```python
from datasette import hookimpl, Response
async def my_page(request):
return Response.html("
Hello!
")
@hookimpl
def register_routes():
return [
(r"^/-/my-page$", my_page)
]
```
View functions can accept: datasette, request, scope, send, receive.
### render_cell(row, value, column, table, database, datasette, request)
Customize how table cell values are displayed:
```python
from datasette import hookimpl
import markupsafe
@hookimpl
def render_cell(value, column):
if column == "stars":
return markupsafe.Markup("⭐" * int(value))
```
### extra_template_vars(template, database, table, columns, view_name, request, datasette)
Add variables to template context:
```python
@hookimpl
def extra_template_vars(request, datasette):
return {
"user_agent": request.headers.get("user-agent"),
"custom_data": "value"
}
```
Can also return async functions for database queries.
### table_actions(datasette, actor, database, table, request)
Add menu items to table pages:
```python
@hookimpl
def table_actions(datasette, database, table):
return [{
"href": datasette.urls.path(f"/-/export/{database}/{table}"),
"label": "Export this table",
"description": "Download as CSV"
}]
```
### actor_from_request(datasette, request)
Implement authentication. Return actor dict or None:
```python
@hookimpl
def actor_from_request(request):
token = request.args.get("_token")
if token == "secret":
return {"id": "user123", "name": "Alice"}
```
Can return async function for database lookups.
### permission_allowed(datasette, actor, action, resource)
Control permissions. Return True (allow), False (deny), or None (no opinion):
```python
@hookimpl
def permission_allowed(actor, action, resource):
if action == "execute-sql" and actor and actor.get("id") == "admin":
return True
```
## Request and Response Objects
### Request Object
Available in many plugin hooks:
```python
request.method # "GET" or "POST"
request.url # Full URL
request.path # Path without query string
request.full_path # Path with query string
request.query_string # Query string without ?
request.args # MultiParams object for query params
request.args.get("key") # Get single param value
request.args.getlist("key") # Get list of values
request.headers # Dict of headers (lowercase keys)
request.cookies # Dict of cookies
request.actor # Current authenticated actor or None
request.url_vars # Variables from URL regex
# Async methods:
body = await request.post_body() # Raw POST body as bytes
form_vars = await request.post_vars() # Form data as dict
```
### Response Object
Create responses in view functions:
```python
from datasette.utils.asgi import Response
# HTML response
return Response.html("