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-infouv venvuv sync --extra testuv 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 builduv 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 detailsEnvironment variables:
HOME—Reveals user’s home directory pathUSER—Reveals the usernamePATH—Reveals filesystem structure and software installation locationsVIRTUAL_ENV—Could reveal project paths and directory structure
python_executable—Reveals filesystem paths and user directoriesModerately sensitive:
CPU count and memory info—Could reveal infrastructure capacityKernel 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 anyoneThe information could help attackers understand your infrastructure setup
Suggestions:
You might want to:
Add authentication requirements to this endpointMake certain fields optional/configurableAdd a warning in the README that this exposes system informationConsider 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.
