Claude can write complete Datasette plugins now
8th October 2025
This isn’t necessarily surprising, but it’s worth noting anyway. Claude Sonnet 4.5 is capable of building a full Datasette plugin now.
I’ve seen models complete aspects of this in the past, but today is the first time I’ve shipped a new plugin where every line of code and test was written by Claude, with minimal prompting from myself.
The plugin is called datasette-os-info. It’s a simple debugging tool—all it does is add a /-/os JSON page which dumps out as much information as it can about the OS it’s running on. Here’s a live demo on my TIL website.
I built it to help experiment with changing the Docker base container that Datasette uses to publish images to one that uses Python 3.14.
Here’s the full set of commands I used to create the plugin. I started with my datasette-plugin cookiecutter template:
uvx cookiecutter gh:simonw/datasette-plugin
[1/8] plugin_name (): os-info
[2/8] description (): Information about the current OS
[3/8] hyphenated (os-info):
[4/8] underscored (os_info):
[5/8] github_username (): datasette
[6/8] author_name (): Simon Willison
[7/8] include_static_directory ():
[8/8] include_templates_directory (): This created a datasette-os-info directory with the initial pyproject.toml and tests/ and datasette_os_info/__init__.py files. Here’s an example of that starter template.
I created a uv virtual environment for it, installed the initial test dependencies and ran pytest to check that worked:
cd datasette-os-info
uv venv
uv sync --extra test
uv run pytestThen I fired up Claude Code in that directory in YOLO mode:
claude --dangerously-skip-permissions(I actually used my claude-yolo shortcut which runs the above.)
Then, in Claude, I told it how to run the tests:
Run uv run pytest
When that worked, I told it to build the plugin:
This is a Datasette plugin which should add a new page /-/os which returns pretty-printed JSON about the current operating system - implement it. I want to pick up as many details as possible across as many OS as possible, including if possible figuring out the base image if it is in a docker container - otherwise the Debian OS release name and suchlike would be good
... and that was it! Claude implemented the plugin using Datasette’s register_routes() plugin hook to add the /-/os page,and then without me prompting it to do so built this basic test as well.
It ran the new test, spotted a bug (it had guessed a non-existent Response(..., default_repr=) parameter), fixed the bug and declared itself done.
I built myself a wheel:
uv pip install build
uv run python -m buildThen uploaded that to an S3 bucket and deployed it to test it out using datasette publish ... --install URL-to-wheel. It did exactly what I had hoped—here’s what that /-/os page looked like:
{
"platform": {
"system": "Linux",
"release": "4.4.0",
"version": "#1 SMP Sun Jan 10 15:06:54 PST 2016",
"machine": "x86_64",
"processor": "",
"architecture": [
"64bit",
""
],
"platform": "Linux-4.4.0-x86_64-with-glibc2.41",
"python_version": "3.14.0",
"python_implementation": "CPython"
},
"hostname": "localhost",
"cpu_count": 2,
"linux": {
"os_release": {
"PRETTY_NAME": "Debian GNU/Linux 13 (trixie)",
"NAME": "Debian GNU/Linux",
"VERSION_ID": "13",
"VERSION": "13 (trixie)",
"VERSION_CODENAME": "trixie",
"DEBIAN_VERSION_FULL": "13.1",
"ID": "debian",
"HOME_URL": "https://www.debian.org/",
"SUPPORT_URL": "https://www.debian.org/support",
"BUG_REPORT_URL": "https://bugs.debian.org/"
},
"debian_version": "13.1",
"kernel": {
"sysname": "Linux",
"nodename": "localhost",
"release": "4.4.0",
"version": "#1 SMP Sun Jan 10 15:06:54 PST 2016",
"machine": "x86_64"
},
"cpu_model": "unknown",
"memory": {
"MemTotal": "1048576 kB",
"MemFree": "958244 kB",
"MemAvailable": "958244 kB",
"SwapTotal": "0 kB",
"SwapFree": "0 kB"
}
},
"environment": {
"HOME": "/root",
"PATH": "/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
},
"python_executable": "/usr/local/bin/python3.14"
}I dread to think how long it would have taken me to figure out all of those different useful OS details!
To finish up, I had Claude fill out the README via a couple of extra prompts:
Add usage instructions to the README (about the /-/os page)
Then had a brief conversation about how safe the output was:
Is there anything potentially sensitive in that output?
Claude said:
Yes, there are several potentially sensitive items in the output:
Most sensitive:
- hostname—Could reveal internal naming schemes or organization details
- Environment variables:
- HOME—Reveals user’s home directory path
- USER—Reveals the username
- PATH—Reveals filesystem structure and software installation locations
- VIRTUAL_ENV—Could reveal project paths and directory structure
- python_executable—Reveals filesystem paths and user directories
Moderately sensitive:
- CPU count and memory info—Could reveal infrastructure capacity
- Kernel version details—Could potentially be used to identify specific vulnerabilities
Considerations:
- This endpoint is accessible without authentication by default (at /-/os)
- If you’re running Datasette on a public server, this information would be visible to anyone
- The information could help attackers understand your infrastructure setup
Suggestions:
You might want to:
- Add authentication requirements to this endpoint
- Make certain fields optional/configurable
- Add a warning in the README that this exposes system information
- Consider filtering out or redacting sensitive paths (replace usernames with ***, etc.)
Would you like me to add any of these protections to the plugin?
In this case I’m fine with the current behavior provided people understand the risks, so I told it:
Add the warning to the README
And then (because the warning it added was quite long):
Drop the bit about configuring the permission system, just have a short warning telling people to review what it exposes
And requested an extra README note:
Add a note that you can also see the output by running: datasette --get /-/os
Three last prompts:
Add uv.lock to gitignoreDrop Python 3.9 and add Python 3.14 - to the GitHub workflows, also min version in pyproject.tomlBump to setup-python@v6
... and that was the project finished. I pushed it to GitHub, configured Trusted Publishing for it on PyPI and posted the 0.1 release, which ran this GitHub Actions publish.yml and deployed that release to datasette-os-info on PyPI.
Now that it’s live you can try it out without even installing Datasette using a uv one-liner like this:
uv run --isolated \
--with datasette-os-info \
datasette --get /-/osThat’s using the --get PATH CLI option to show what that path in the Datasette instance would return, as described in the Datasette documentation.
I’ve shared my full Claude Code transcript in a Gist.
A year ago I’d have been very impressed by this. Today I wasn’t even particularly surprised that this worked—the coding agent pattern implemented by Claude Code is spectacularly effective when you combine it with pre-existing templates, and Datasette has been aroung for long enough now that plenty of examples of plugins have made it into the training data for the leading models.