datasette-plugins

Writing Datasette plugins using Python and the pluggy plugin system. Use when Claude needs to: (1) Create a new Datasette plugin, (2) Implement plugin hooks like prepare_connection, register_routes, render_cell, etc., (3) Add custom SQL functions, (4) Create custom output renderers, (5) Add authentication or permissions logic, (6) Extend Datasette's UI with menus, actions, or templates, (7) Package a plugin for distribution on PyPI

16 stars

Best use case

datasette-plugins is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Writing Datasette plugins using Python and the pluggy plugin system. Use when Claude needs to: (1) Create a new Datasette plugin, (2) Implement plugin hooks like prepare_connection, register_routes, render_cell, etc., (3) Add custom SQL functions, (4) Create custom output renderers, (5) Add authentication or permissions logic, (6) Extend Datasette's UI with menus, actions, or templates, (7) Package a plugin for distribution on PyPI

Teams using datasette-plugins should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/datasette-plugins/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/development/datasette-plugins/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/datasette-plugins/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How datasette-plugins Compares

Feature / Agentdatasette-pluginsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Writing Datasette plugins using Python and the pluggy plugin system. Use when Claude needs to: (1) Create a new Datasette plugin, (2) Implement plugin hooks like prepare_connection, register_routes, render_cell, etc., (3) Add custom SQL functions, (4) Create custom output renderers, (5) Add authentication or permissions logic, (6) Extend Datasette's UI with menus, actions, or templates, (7) Package a plugin for distribution on PyPI

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Datasette Plugin Development

## Overview

Datasette plugins extend Datasette's functionality using Python and the [pluggy](https://pluggy.readthedocs.io/) plugin system. Plugins can add SQL functions, custom routes, authentication, UI elements, and more.

## Quick Start: One-off Plugin

Create `plugins/my_plugin.py`:

```python
from datasette import hookimpl

@hookimpl
def prepare_connection(conn):
    conn.create_function("hello_world", 0, lambda: "Hello world!")
```

Run with: `datasette serve mydb.db --plugins-dir=plugins/`

## Installable Plugin Structure

For distributable plugins, use this structure:

```
datasette-my-plugin/
├── pyproject.toml
├── datasette_my_plugin/
│   ├── __init__.py      # Plugin implementation
│   ├── static/          # Optional: JS/CSS files
│   └── templates/       # Optional: Jinja2 templates
└── tests/
    └── test_plugin.py
```

### pyproject.toml

```toml
[project]
name = "datasette-my-plugin"
version = "0.1.0"
description = "My Datasette plugin"
requires-python = ">=3.10"
dependencies = ["datasette"]
[dependency-groups]
dev = [
    "pytest",
    "pytest-asyncio"
]

[project.entry-points.datasette]
my_plugin = "datasette_my_plugin"

[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
```

## Core Plugin Hooks

See [references/hooks.md](references/hooks.md) for complete hook documentation.

### Most Common Hooks

| Hook | Purpose |
|------|---------|
| `prepare_connection(conn, database, datasette)` | Register custom SQL functions |
| `register_routes(datasette)` | Add custom URL routes |
| `startup(datasette)` | Initialize on server start |
| `render_cell(row, value, column, table, database, datasette, request)` | Customize cell display |
| `extra_template_vars(...)` | Add template variables |
| `actor_from_request(datasette, request)` | Custom authentication |
| `permission_allowed(datasette, actor, action, resource)` | Custom permissions |

### Example: Custom SQL Function

```python
from datasette import hookimpl
import hashlib

@hookimpl
def prepare_connection(conn):
    conn.create_function("md5", 1, lambda s: hashlib.md5(s.encode()).hexdigest())
```

### Example: Custom Route

```python
from datasette import hookimpl, Response

@hookimpl
def register_routes():
    return [
        (r"^/-/my-page$", my_page_view),
    ]

async def my_page_view(datasette, request):
    return Response.html("<h1>My Custom Page</h1>")
```

### Example: Startup Hook

```python
@hookimpl
def startup(datasette):
    async def inner():
        db = datasette.get_database()
        await db.execute_write("""
            CREATE TABLE IF NOT EXISTS my_table (id INTEGER PRIMARY KEY, data TEXT)
        """)
    return inner
```

## Plugin Configuration

Plugins read configuration from `datasette.yaml`:

```yaml
plugins:
  datasette-my-plugin:
    option1: value1
    option2: value2
```

Access in plugin:

```python
@hookimpl
def startup(datasette):
    config = datasette.plugin_config("datasette-my-plugin") or {}
    my_option = config.get("option1", "default")
```

### Secret Configuration

Use environment variables:

```yaml
plugins:
  datasette-my-plugin:
    api_key:
      $env: MY_API_KEY
```

Or files:

```yaml
plugins:
  datasette-my-plugin:
    api_key:
      $file: /secrets/api-key
```

## Testing Plugins

```python
from datasette.app import Datasette
import pytest

@pytest.mark.asyncio
async def test_plugin_installed():
    datasette = Datasette(memory=True)
    response = await datasette.client.get("/-/plugins.json")
    assert response.status_code == 200
    plugins = {p["name"] for p in response.json()}
    assert "datasette-my-plugin" in plugins

@pytest.mark.asyncio
async def test_custom_route():
    datasette = Datasette(memory=True)
    response = await datasette.client.get("/-/my-page")
    assert response.status_code == 200
    assert "My Custom Page" in response.text
```

Run tests: `pytest`

## Response Types

```python
from datasette import Response

# HTML response
Response.html("<h1>Hello</h1>")

# JSON response
Response.json({"key": "value"})

# Text response
Response.text("Plain text")

# Redirect
Response.redirect("/other-page")

# Custom response
Response(body, content_type="text/plain", status=200, headers={})
```

## URL Design

Use `/-/` prefix to avoid conflicts with database names:

- `/-/my-feature` - Global feature
- `/dbname/-/my-feature` - Database-specific
- `/dbname/tablename/-/my-feature` - Table-specific

## Static Assets & Templates

Static files in `static/` are served at:
`/-/static-plugins/PLUGIN_NAME/filename.js`

Templates in `templates/` override Datasette defaults. Priority:
1. `--template-dir` argument
2. Plugin templates
3. Datasette defaults

## Common Patterns

### Adding Menu Items

```python
@hookimpl
def menu_links(datasette, actor):
    return [{"href": "/-/my-page", "label": "My Feature"}]
```

### Table Actions

```python
@hookimpl
def table_actions(datasette, actor, database, table):
    return [{"href": f"/{database}/{table}/-/action", "label": "My Action"}]
```

### Custom Output Renderer

```python
@hookimpl
def register_output_renderer(datasette):
    return {
        "extension": "csv",
        "render": render_csv,
    }

async def render_csv(datasette, columns, rows):
    # Return Response object
    pass
```

### Event Tracking

```python
@hookimpl
def track_event(datasette, event):
    print(f"Event: {event.name}, Actor: {event.actor}")
```

## Debugging

Enable hook tracing:

```bash
DATASETTE_TRACE_PLUGINS=1 datasette mydb.db
```

## Key Imports

```python
from datasette import hookimpl, Response
from datasette.app import Datasette
from datasette.filters import FilterArguments
from datasette.permissions import Action, Resource, PermissionSQL
import markupsafe  # For safe HTML in render_cell
```

Related Skills

bgo

10
from diegosouzapw/awesome-omni-skill

Automates the complete Blender build-go workflow, from building and packaging your extension/add-on to removing old versions, installing, enabling, and launching Blender for quick testing and iteration.

Coding & Development

developing-frontend-apps

16
from diegosouzapw/awesome-omni-skill

Frontend application development best practices. Use when building, modifying, or reviewing frontend applications, React components, UI components, client-side JavaScript/TypeScript, CSS/styling, single-page applications, or web application architecture.

developing-claude-agent-sdk-agents

16
from diegosouzapw/awesome-omni-skill

Build AI agents with the Claude Agent SDK (TypeScript/Python). Covers creating agents, custom tools, hooks, subagents, MCP integration, permissions, sessions, and deployment. Use when building, reviewing, debugging, or deploying SDK-based agents. Invoke PROACTIVELY when user mentions Agent SDK, claude-agent-sdk, ClaudeSDKClient, query(), or building autonomous agents.

developing-backend-services

16
from diegosouzapw/awesome-omni-skill

Backend service development best practices. Use when designing, building, or reviewing backend services, REST APIs, gRPC services, microservices, webhooks, message queues, or server-side applications regardless of language or framework.

dev_standards_skill

16
from diegosouzapw/awesome-omni-skill

Development standards and architecture management skill. Enforces modular design, low coupling, clean code practices, and maintains project architecture graph for quick context understanding. Language-agnostic, works with TypeScript, Python, Go, Rust, Java, and more. Use when starting development tasks, refactoring, or analyzing project structure.

dev.shortcuts

16
from diegosouzapw/awesome-omni-skill

Mandatory shortcut trigger and usage guidance. ALWAYS check if shortcut applies before responding to ANY coding or development request.

dev-workflow-planning

16
from diegosouzapw/awesome-omni-skill

Structured development workflows using /brainstorm, /write-plan, and /execute-plan patterns. Transform ad-hoc conversations into systematic project execution with hypothesis-driven planning, incremental implementation, and progress tracking.

dev-swarm-tech-specs

16
from diegosouzapw/awesome-omni-skill

Define technical specifications including tech stack, security, theme standards (from UX mockup), coding standards, and testing standards. Use when user asks to define tech specs, choose tech stack, or start Stage 7 after architecture.

dev-swarm-stage-architecture

16
from diegosouzapw/awesome-omni-skill

Design the complete system architecture including components, data flow, infrastructure, database schema, and API design. Use when starting stage 07 (architecture) or when user asks about system design, tech stack, or database schema.

dev-specialisms:fly-deploy

16
from diegosouzapw/awesome-omni-skill

Quick MVP deployment to fly.io for JavaScript (Next.js, RedwoodSDK, Express), Rust (Axum, Rocket), Python (FastAPI), and generic Dockerfiles. Use when deploying applications to fly.io, setting up databases (Postgres, volumes, Tigris object storage), managing secrets, configuring custom domains, setting up GitHub Actions workflows, creating review apps for pull requests, or troubleshooting fly.io deployments. Covers complete deployment workflows from initial setup through production.

dev-expert

16
from diegosouzapw/awesome-omni-skill

Development patterns for React, Vue, Laravel, Next.js, React Native - state management, forms, API integration

dev-coding

16
from diegosouzapw/awesome-omni-skill

Implement features as a Principal Engineering Developer