Skip to content

Studio

LiteORM ships an embedded database studio — a browser admin GUI for inspecting and editing any LiteORM-backed database — as a standard net/http handler you mount in your own server. It works on every backend, needs no Go models to get started, and adds nothing to your build unless you ask for it. For the natural-language-to-SQL features it exposes, see the WithAI hook below; to lock it down for a public deployment, jump to Mount it safely.

The studio is a separate module, liteorm.org/studio, so the dependency lands only when you import it:

Terminal window
go get liteorm.org/studio

studio.Handler(db, opts...) takes an already-opened *liteorm.DB and returns a plain http.Handler. It does not open a connection of its own — you pass it the database your app already owns:

import (
"net/http"
"liteorm.org/dialect/sqlite"
"liteorm.org/studio"
)
db, _ := sqlite.Open("app.db")
defer db.Close()
// Mount anywhere; wrap with your own auth.
http.Handle("/studio/", http.StripPrefix("/studio", studio.Handler(db)))
http.ListenAndServe(":8080", nil)

Then open http://localhost:8080/studio/ (the trailing slash matters). Because it is just an http.Handler, it drops into the stdlib mux, chi, echo, or gin, and you wrap it with the same authentication middleware you already use.

  • Works on every backend with no Go models required. Point it at a database your app already owns and it introspects tables, columns, types, primary keys, and foreign-key navigation from the live catalog — schema-wide, so it scales to hundreds of tables.
  • Browse, filter, edit. Page/sort/filter the grid, full-table search, follow foreign keys, edit cells inline with type-aware editors, insert and delete rows.
  • SQL editor, import/export, system info. Run read or write SQL with a result grid; export and import CSV / JSON / SQL; inspect connection, server, and per-dialect database settings.
  • Theme — a system / light / dark switch.
  • AI, opt-in. Natural-language filters, English-to-SQL, automatic result charts, and query analysis through one server-side hook — see below.

Out of the box the studio is introspection-only: it reads everything it shows from the live database catalog, so an unfamiliar database works with zero configuration. Registering your models with studio.WithModels is purely additive — it layers each model’s LiteORM schema on top of catalog introspection:

studio.Handler(db, studio.WithModels(&User{}, &Post{}))

With models registered, belongs-to relations become navigable foreign keys, and Go types refine datatypes the catalog can’t express — a bool stored as SQLite INTEGER renders as a checkbox rather than 0/1. Tables without a registered model still work fully from the catalog alone; registration only enriches the ones it covers.

I want… Do…
To poke at a database quickly Nothing — introspection-only is the default
Relation navigation and true Go types studio.WithModels(&A{}, &B{}, …)

The studio is an admin surface with a raw-SQL escape hatch, and it ships no authentication of its own — always wrap it with your middleware and never serve it unauthenticated on a public address:

mux := http.NewServeMux()
mux.Handle("/admin/db/", requireAdmin(http.StripPrefix("/admin/db",
studio.Handler(db, studio.WithModels(&User{}, &Post{})))))
http.ListenAndServe(":8080", mux)

Narrow what it exposes with the options below, and for a public demo lock it read-only at compile time:

Option / flag Effect
studio.WithReadOnly() Disables every write endpoint; a blocked edit returns a clear 403, not a silent failure
studio.WithDisableSQL() Removes the raw SQL editor entirely
-tags studio_readonly Forces read-only at compile time regardless of options — writes and import are off and the SQL editor refuses any non-read statement (including a write smuggled inside a WITH … CTE)

Building with -tags studio_readonly is the unbreakable lock for a public, unauthenticated demo: no runtime config can loosen it. studio.Hardened() reports whether the binary was built that way, and GET /api/config exposes "hardened": true so the UI can label itself. Seed the database directly through LiteORM before mounting — the lock only covers the HTTP surface, not your own code.

The studio’s toolbar drives import and export in CSV, JSON, and SQL. Exports cover a single table or the whole database (a CSV export of the whole database arrives as a zip of per-table files). Imports run inside a single transaction, so a bad row rolls the whole file back atomically. Two safety rules apply: the SQL import format executes only INSERT statements, and it is additionally gated by WithDisableSQL() — disable the SQL editor and SQL import goes with it. Under WithReadOnly() (or -tags studio_readonly) all imports are off.

The studio’s AI features — natural-language filters, English-to-SQL, automatic result charts, and query analysis — are opt-in and LLM-agnostic. You supply one server-side function via studio.WithAI; the studio assembles the prompt (schema context — column names and types — but never row data) and your function returns the model’s text. Your API key never reaches the browser.

The hook’s type is:

type AIFunc func(ctx context.Context, req studio.AIRequest) (string, error)
type AIRequest struct {
Prompt string // the user's text plus the assembled schema context
Task string // "table-filter", "sql-generation", "sql-visualization", "query-insights"
}

Wire it to any provider. Here it is backed by Anthropic’s Claude — the studio has already built the full prompt (schema context plus the required output format), so the function just relays prompt → model → text:

import (
"context"
"strings"
"github.com/anthropics/anthropic-sdk-go"
"liteorm.org/studio"
)
func claudeAI(client anthropic.Client) studio.AIFunc {
return func(ctx context.Context, req studio.AIRequest) (string, error) {
resp, err := client.Messages.New(ctx, anthropic.MessageNewParams{
Model: anthropic.ModelClaudeHaiku4_5_20251001,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock(req.Prompt)),
},
})
if err != nil {
return "", err
}
var b strings.Builder
for _, block := range resp.Content {
if t, ok := block.AsAny().(anthropic.TextBlock); ok {
b.WriteString(t.Text)
}
}
return b.String(), nil
}
}
// then:
studio.Handler(db, studio.WithAI(claudeAI(client)))

A fast, cheap model like Haiku is the right fit for short SQL and filter generation; swap the model constant for a higher-quality one if you prefer. When WithAI is not supplied the studio hides its AI affordances entirely — the AI endpoint isn’t even registered.

Options, security guidance, and the per-dialect “plug into an existing database” demos live with the module — see the liteorm.org/studio API reference and the studio repository.

  • Security — locking the studio down as part of a broader hardening pass.
  • AI agents & skills — the Agent Skills that help an assistant write LiteORM code, including a studio skill.
  • API reference: pkg.go.dev/liteorm.org/studio (Handler, WithModels, WithReadOnly, WithDisableSQL, WithAI, AIFunc, AIRequest, Hardened).