Dev ToolsArticle
By embedding sandboxed HTML/JS apps directly alongside SQLite databases, Datasette becomes a secure, zero-maintenance internal tool platform.
For years, Datasette has occupied a highly specific, well-loved niche in the developer ecosystem: it is the ultimate tool for exploring, querying, and publishing structured data. If you had a SQLite database, Datasette gave you an instant web UI and a JSON API to query it. It was, fundamentally, a read-only window into your data.
But a series of recent architectural shifts has quietly transformed Datasette from a passive data explorer into an active application runtime. The culmination of this evolution is the launch of datasette-apps
, a new plugin that allows developers to host self-contained HTML and JavaScript applications directly inside a Datasette instance.
This is not just a minor feature addition; it is a fundamental shift in how we should think about lightweight web applications. By marrying the rapid prototyping of LLM-generated frontends with a secure, sandboxed relational database backend, Datasette is positioning itself as a zero-install, secure-by-default application hosting platform. For developers tasked with building internal tools, dashboards, and data utilities, this approach challenges the status quo of modern web development.
The Evolution from Data Explorer to Application Runtime #
To understand why datasette-apps
matters, you have to look at the trajectory of the Datasette 1.0 alpha releases leading up to June 2026. For a long time, Datasette was strictly read-only. If you wanted to write to the database, you had to write custom Python plugins or manage the SQLite file externally.
That changed dramatically over the last few months:
Datasette 1.0a31 (May 2026) introduced the ability to execute SQL write queries and save "stored queries" (formerly canned queries) directly within the platform.Datasette Agent (May 2026) launched as an extensible AI assistant for interacting with SQLite databases.Datasette 1.0a34 (June 2026) added native UI tools to insert, edit, and delete rows directly from table pages, including hooks for custom column types.
With write capabilities, stored queries, and a robust JSON API, Datasette had already become a viable backend for custom applications. Historically, developers leveraged this by hosting a separate frontend on Vercel or Netlify and querying the Datasette API. Indeed, Datasette creator Simon Willison notes that one of his earliest projects at Eventbrite was an internal search engine built this way, where client-side JavaScript constructed SQL queries directly against the Datasette API.
datasette-apps
cuts out the middleman. Instead of managing separate hosting, deployment pipelines, and CORS configurations for your frontend, you upload your HTML/JS application directly into Datasette. The database and the application live in the same process, on the same domain, yet remain strictly isolated.
Anatomy of the Sandbox: Securing Untrusted Code #
Hosting arbitrary, user-defined, or LLM-generated HTML and JavaScript on the same domain as a highly sensitive database is a security nightmare. An authenticated Datasette instance can contain proprietary business data, user records, or system credentials. If an embedded application could access the parent window's DOM, read cookies, or access localStorage
, a single malicious or buggy script could exfiltrate the entire database.
To solve this, datasette-apps
implements a defense-in-depth security model using a combination of iframe sandboxing, immutable Content Security Policies (CSP), and isolated message channels.
1. The Sandboxed Iframe
Each application runs inside an iframe configured with highly restrictive sandbox attributes:
<iframe sandbox="allow-scripts allow-forms" srcdoc="...">
By omitting allow-same-origin
, the browser treats the iframe as having a unique, opaque origin. Even though the iframe is served from your Datasette domain, the browser blocks it from accessing the parent window's DOM, reading cookies, or accessing localStorage
and sessionStorage
keys associated with the host domain.
2. The Immutable CSP
While the sandbox blocks local data access, it does not inherently stop the iframe from making network requests. A malicious script could still use fetch()
to send sensitive data to an external server.
To prevent data exfiltration, datasette-apps
injects a strict Content Security Policy directly into the iframe's header via a <meta>
tag:
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: blob:;">
This policy blocks all external network requests. Because it is defined in a <meta>
tag at the very top of the document, the browser enforces it immediately. Crucially, even if malicious JavaScript executes inside the frame, it cannot modify or remove this CSP header once the browser has parsed it; the policy remains immutable for the lifetime of that frame.
3. Communication via MessageChannel
With the iframe completely locked down, it cannot even make standard HTTP requests back to the Datasette API. To allow the application to actually query the database, datasette-apps
establishes a secure communication bridge.
While the initial prototype used standard window.postMessage()
, the production implementation utilizes the MessageChannel
API. When the parent window instantiates the iframe, it creates a MessageChannel
and transfers one of its ports to the iframe.
This design offers a distinct security advantage: if the iframe is somehow navigated to an untrusted external URL, the message channel automatically closes. This prevents a hijacked or redirected frame from continuing to send SQL execution commands to the parent window. Through this channel, the iframe sends structured requests (such as "run this read-only SQL query"), which the parent validates against an allow-list of databases before executing and returning the results.
The Developer Angle: Workflow, Adoption, and Trade-offs #
For a working developer, datasette-apps
changes the math of building internal tools.
What It Replaces
Traditionally, if a non-technical team needed a custom dashboard to view and update a specific dataset, you had two choices:
The Custom Micro-App: Spin up a React/Vite frontend, a Node/Express backend, configure an ORM, set up authentication, manage Docker containers, and deploy it to AWS or Fly.io.The Low-Code Platform: Use Retool or Retool-alternatives. This is faster but introduces vendor lock-in, licensing costs, and complex integration steps.
datasette-apps
offers a third way. You write a single, self-contained HTML file containing your CSS and JavaScript. You can use modern utility frameworks like Tailwind CSS (via inline styles or data URIs) or standard vanilla JS.
The Workflow
To build a custom tool, your workflow looks like this:
Define the Data: Load your data into SQLite using Datasette's importing tools.Write the Frontend: Create an HTML file. Because the app runs in a sandbox, you don't write complex API fetching code. Instead, you use the injected Datasette bridge to run SQL queries directly from your client-side JS:
// Conceptual example of querying the parent Datasette instance
const response = await datasette.query("select * from events order by date desc limit 10");
renderTimeline(response.rows);
Deploy: Upload the HTML file to your Datasette instance. It is now instantly accessible to anyone with permissions to view that Datasette instance, inheriting Datasette's existing authentication and access controls.
Debugging and Logging
Because the CSP is so strict, debugging can be tricky—legitimate assets (like external images or fonts) will frequently trip CSP violations. To mitigate this, datasette-apps
captures these errors inside the iframe and transmits them back to the parent frame, displaying them in a visible log console. This makes it easy to see exactly which resource was blocked and adjust your app accordingly.
The Trade-offs
This architecture is incredibly elegant, but it is not a silver bullet. Developers must weigh several limitations:
No External Dependencies via CDN: Because the CSP blocks external network requests, you cannot easily pull in heavy external libraries (like React or D3) from CDNs like unpkg or cdnjs unless they are bundled directly into your single HTML file or explicitly allowed via custom CSP configurations. You are largely limited to writing vanilla JS, using small libraries embedded as data URIs, or relying on pre-bundled single-file builds.State Management Complexity: Because the sandbox blockslocalStorage
, your application cannot easily persist local UI state (like active filters or UI themes) across page refreshes unless you write that state back to the SQLite database via stored write queries.Query Performance: Every SQL query must pass through theMessageChannel
serialization boundary, be parsed by the parent, executed against SQLite, serialized back to JSON, and sent back through the channel. For massive datasets or highly frequent queries, this IPC overhead will be slower than a direct native database connection.
The AI Connection: Giving "Artifacts" a Database #
One of the most compelling aspects of datasette-apps
is its relationship with generative AI. The plugin was originally conceived as a way to build a "Claude Artifacts" style interface for datasette-agent
.
We are currently living in an era of "vibe-coding," where LLMs can generate highly functional, beautiful single-file HTML/JS applications in seconds. However, these AI-generated tools have always suffered from a fatal flaw: they lack a persistent, relational state. An AI can build you a gorgeous kanban board or a timeline visualizer, but the moment you refresh the page, your data is gone.
By hosting these AI-generated frontends inside Datasette, you instantly solve the state problem. You can ask an LLM to "write a custom timeline visualization HTML page that queries the events
table in my SQLite database," upload it to Datasette, and you have a fully functional, persistent, database-backed application in under two minutes.
The Verdict: A Genuine Shift for Internal Tooling #
datasette-apps
is not hype; it is a highly pragmatic solution to a problem developers face daily. It recognizes that for a vast class of internal applications, the database is the application. By leveraging the browser's native security boundaries (iframe
- CSP) and a clean IPC channel (
MessageChannel
), it bypasses the need for complex backend API routing, authentication middleware, and deployment pipelines.
While it won't replace your core SaaS product's architecture, it should absolutely replace how you build internal admin panels, data visualization dashboards, and quick-and-dirty CRUD utilities. It turns SQLite and Datasette into a legitimate, secure application server for the AI era.
Sources & further reading #
Datasette Apps: Host custom HTML applications inside Datasette— simonwillison.net
Priya Nair· AI & Developer Experience Writer
Priya covers AI frameworks, developer productivity tooling, and the startup ecosystem across South and Southeast Asia, bringing a researcher's rigour and a practitioner's empathy to every story. She is deeply sceptical of benchmarks and asks hard questions so her readers don't have to.
Discussion 0 #
No comments yet
Be the first to weigh in.