Simon Willison’s Weblog

Subscribe

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 pytest

Then 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 build

Then 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:

  1. This endpoint is accessible without authentication by default (at /-/os)
  2. If you’re running Datasette on a public server, this information would be visible to anyone
  3. The information could help attackers understand your infrastructure setup

Suggestions:

You might want to:

  1. Add authentication requirements to this endpoint
  2. Make certain fields optional/configurable
  3. Add a warning in the README that this exposes system information
  4. 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 gitignore
  • Drop Python 3.9 and add Python 3.14 - to the GitHub workflows, also min version in pyproject.toml
  • Bump 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 /-/os

That’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.

More recent articles

This is Claude can write complete Datasette plugins now by Simon Willison, posted on 8th October 2025.

Part of series How I use LLMs and ChatGPT

  1. Designing agentic loops - Sept. 30, 2025, 3:20 p.m.
  2. Embracing the parallel coding agent lifestyle - Oct. 5, 2025, 12:06 p.m.
  3. Vibe engineering - Oct. 7, 2025, 2:32 p.m.
  4. Claude can write complete Datasette plugins now - Oct. 8, 2025, 11:43 p.m.

Previous: Vibe engineering

Monthly briefing

Sponsor me for $10/month and get a curated email digest of the month's most important LLM developments.

Pay me to send you less!

Sponsor & subscribe