Skip to content

Migrating to v0.20

v0.20 reshapes the CSS / icon story (Tailwind v4 in, mucss / bootstrap / bsicons / alpine out), tightens template-function coupling so missing contribs fail fast, and ships a default auth layout. This guide walks the concrete before/after patterns for the upgrade from v0.18.x.

For the full list of changes, see the v0.20 changelog. For the rationale behind individual decisions, see the reference docs linked from each section.

Quick orientation

What you used What replaces it Section
justfile + just <task> .mise.toml + mise run <task> Tooling
contrib/mucss or contrib/bootstrap Tailwind v4 via cmd/burrow-tailwind CSS stack
contrib/bsicons + cfg.RegisterIconFunc Inline-SVG defines + {{ icon "..." }} Icons
contrib/alpine Vendor it yourself if you really need it Removed contribs
Documents() []any Documents() []document.Document HasDocuments
srv.Registry().AllCLICommands() srv.CLICommands() CLI subcommands
Implicit auth template-func availability Declared Dependencies() Template funcs + deps
auth.DefaultAuthLayout() == "" (host layout leak) auth/layout shipped by contrib/auth Auth default layout

Tooling

justfile is gone. Install mise and run mise install once after cloning; common tasks then live under mise run:

mise install              # one-time: Go toolchain + dev tools + tailwindcss
mise run test             # was: just test
mise run lint             # was: just lint
mise run fmt              # was: just fmt
mise tasks                # list all available tasks

CSS stack

Both contrib/mucss and contrib/bootstrap are deleted along with the µCSS / Bootstrap CSS bundles and the theme-switcher dropdown. Burrow ships no CSS bundle of its own anymore — every in-tree contrib (admin, auth, jobs) is written in Tailwind v4 utility classes.

For new projects, follow docs/guide/tailwind.md. The short version:

# 1. Install the Tailwind CLI wrapper as a Go tool
go get -tool github.com/oliverandrich/burrow/cmd/burrow-tailwind

# 2. Drop a minimal tailwind.css at the project root
cat > tailwind.css <<'EOF'
@import "tailwindcss";
@import "./.tailwind/sources.css";
EOF

# 3. Build once (or pass --watch in dev)
go tool burrow-tailwind -i tailwind.css -o internal/app/static/app.min.css --minify

burrow-tailwind auto-generates .tailwind/sources.css with @source directives for every Burrow contrib's template directory — new contribs are picked up automatically on go get -u. Dark mode follows prefers-color-scheme; the framework no longer ships theme-switcher machinery (vendor it in your shell app if you want a user toggle).

If you're not switching to Tailwind, vendor the deleted contrib's CSS into your own staticfiles directory and link it from your layout — it's now your code to maintain.

Icons

contrib/bsicons, the generated Bootstrap-Icons wrapper, and the entire AppConfig.RegisterIconFunc / IconFuncs / IconFunc API surface are gone. Icons are now plain {{ define "<app>/icon_<name>" }} blocks rendered via the new {{ icon }} template function.

Before:

// In your app's Configure:
cfg.RegisterIconFunc("iconPeople", bsicons.People)
<!-- In templates: -->
{{ iconPeople }}

After (define the icon once, reference it by name everywhere):

<!-- internal/<app>/templates/<app>/icons.html -->
{{ define "myapp/icon_people" -}}
<svg viewBox="0 0 16 16" class="h-4 w-4"><path d="..."/></svg>
{{- end }}
<!-- Anywhere in your templates: -->
{{ icon "myapp/icon_people" }}

NavItem.Icon, NavLink.Icon, and admin.DashboardItem.Icon are now string (the template-define name) instead of template.HTML:

{Label: "Users", URL: "/admin/users", Icon: "myapp/icon_people"}

HasDocuments

Documents() now returns []document.Document instead of []any. Types embedding document.Base satisfy the marker automatically — most apps just swap the slice type and import:

// Before:
func (a *App) Documents() []any {
    return []any{&User{}, &Post{}}
}

// After:
import "github.com/oliverandrich/den/document"

func (a *App) Documents() []document.Document {
    return []document.Document{&User{}, &Post{}}
}

CLI subcommands

Replace srv.Registry().AllCLICommands() with srv.CLICommands() in your top-level cli.Command:

cmd := &cli.Command{
    Flags:    srv.Flags(nil),
    Action:   srv.Run,
    Commands: srv.CLICommands(), // was: srv.Registry().AllCLICommands()
}

The new method wraps each subcommand's Action to run the framework boot lifecycle (open DB, run Configure() on every app) before the original Action fires. Without it, contrib subcommands like auth promote execute against uninitialised apps and fail with auth app not initialized. AllCLICommands is still available as a low-level escape hatch.

Template funcs + deps

The core base FuncMap no longer stubs csrfToken, csrfField, csrfHxHeaders, or lang. Templates using these funcs without the providing contrib now fail at parse time with a clear "function not defined" error instead of silently rendering empty values.

Affected default Dependencies():

Contrib Before After
auth [session] [session, csrf, staticfiles]
admin [staticfiles, htmx, messages] [staticfiles, htmx, messages, csrf, auth]
jobs (not declared) [admin]

If your host app already registered all the contribs auth needed in practice, this change is transparent. If you registered auth.New() without csrf or staticfiles, the server now refuses to boot until you add them.

Auth default layout

auth.DefaultAuthLayout() returns "auth/layout" (a navbar-less Tailwind shell that links app/app.min.css) instead of "". Hosts that previously relied on the empty-string default — auth pages inheriting srv.SetLayout's layout, leaking the host's navbar into login pages — get the new clean look automatically.

If your shell app sits at internal/app/ and owns app/app.min.css (the Tailwind guide convention), it just works. If your CSS lives somewhere else, override:

auth.New(
    auth.WithAuthLayout("myapp/auth-layout"),  // your own template define
)

Passing "" opts back into the pre-v0.20 "inherit host layout" behaviour.

Removed contribs

  • contrib/alpine — Burrow's design shifted toward SSR + htmx + OOB updates where Alpine is not load-bearing. Vendor alpine.js into your own staticfiles directory if you still need it.
  • contrib/mucss, contrib/bootstrap, contrib/bsicons — see CSS stack and Icons.
  • example/themes — was a µCSS accent-variant preview, no longer meaningful.

Den 0.13.1

Burrow now depends on Den 0.13.1. The top-level CRUD wrappers collapsed:

  • den.Insert(ctx, db, doc) / den.Update(ctx, db, doc)den.Save(ctx, db, doc) (branches on doc.ID)
  • den.InsertMany(ctx, db, docs)den.SaveAll(ctx, db, docs)
  • den.DeleteMany[T](ctx, db, conds)den.NewQuery[T](db, conds...).Delete(ctx)
  • den.FindOneAndUpdate[T](ctx, db, fields, conds)den.NewQuery[T](db, conds...).UpdateOne(ctx, fields)

burrow.OpenDBWithoutValidation is gone — Den's struct-tag validation is now always-on. Drop the validate:"..." tag from any struct whose data violates the rule, or fix the data.

See the Den changelog for the full migration table.

Smaller items

  • admin/pagination — the per-contrib jobs/pagination / auth/pagination / notes/pagination defines collapsed into a single canonical admin/pagination template shipped by contrib/admin. Same (dict "Page" .Page "BasePath" "..." "RawQuery" .RawQuery) contract. Downstream apps that override one of the old names must switch to overriding admin/pagination instead.
  • Test helpers moved out of the root burrow package into a sibling burrowtest/ (and contrib/auth/authtest/ for auth-specific helpers). Replace burrow.TestDB(t, &Note{}) with burrowtest.DB(t) + den.Register(ctx, db, &Note{}); burrow.TestErrorExecContext / burrow.TestErrorExecMiddleware are now burrowtest.ErrorExecContext / burrowtest.ErrorExecMiddleware. New: burrowtest.TempDSN(t) for tests that need a DSN string, burrowtest.StubApp(name) for tests that need to satisfy a contrib's Dependencies() declaration without exercising it.
  • Configuration helpersRequestPath / WithRequestPath and TemplateExec / WithTemplateExecutor are documented under Core Helpers; admin.RequestPath and friends are now deprecated thin wrappers.

Stuck?

If you hit a case this guide doesn't cover, please open an issue — we'll fold the resolution back into this page.