Framework

The framework layer, on top of core

framework and framework/ui sit on top of the primitives. Declare an entity and get the database, API, and tools; compose screens from components that already match your theme.

entitiesauthaccess controlmigrationscomponentstheming

Entities

Declare an entity in Go: fields, types, relations. One call gives you the table, REST endpoints, validation, an OpenAPI entry, and MCP tools.

main.go
9 lines
app.Entity("posts", framework.EntityConfig{
    Exposure: &framework.ExposureConfig{Public: true},
    Fields: []schema.Field{
        {Name: "title", Type: schema.String, Required: true},
        {Name: "body", Type: schema.Text},
        {Name: "status", Type: schema.Enum,
            Values: []string{"draft", "published"}, Default: "draft"},
    },
})

/docs/entity-declarations →

Auth

Login, sessions, OAuth, magic-link, 2FA, password reset. Each is a plugin you add to an auth manager; the middleware puts the signed-in user on the request.

auth.go
10 lines
authMgr := auth.New(auth.AuthConfig{
    DevMode:      true, // dev only: mints a per-process JWT secret; set JWTSecret in prod
    UserStore:    auth.NewEntityUserStore(db, "auth_users"),
    SessionStore: auth.NewEntitySessionStore(db, "auth_sessions"),
})
authMgr.Use(auth.NewCorePlugin())
if err := authMgr.Init(fwApp); err != nil {
    log.Fatal(err) // without a JWT secret (or DevMode) Init fails closed
}
fwApp.Use(auth.SessionMiddleware(authMgr))

/docs/auth →

Access control

Roles and permissions, plus per-user owner scoping. It fails closed: a request with no matching policy is denied, not allowed.

entities.go
9 lines
app.Entity("notes", framework.EntityConfig{
    Scope: &framework.ScopeConfig{OwnerField: "user_id"}, // every read and write is scoped to the signed-in user
    Exposure: &framework.ExposureConfig{
        Access: framework.AccessControl{
            Read: "notes:read", Create: "notes:write",
            Update: "notes:write", Delete: "notes:admin",
        },
    },
})

/docs/access-control →

Migrations

Versioned, ordered, reversible schema changes. In dev the framework can auto-migrate from the declared entities; in production you run the ordered migrations.

main.go
4 lines
// Derive the schema from the declared entities and apply it.
if err := framework.AutoMigrate(db, app.Registry); err != nil {
    log.Fatal(err)
}

/docs/migrations →

Components

framework/ui composes intent, not tags: PageHeader, DataTable, Form, Card, charts. Each ships its own CSS through the theme, so a page inherits your look for free.

screen.go
6 lines
ui.PageHeader(ui.PageHeaderConfig{
    Eyebrow:  "Settings",
    Title:    "Workspace settings",
    Subtitle: "Tune defaults for everyone on this workspace.",
    Actions:  ui.Button(ui.ButtonConfig{Label: "Save changes", Variant: ui.ButtonPrimary}),
})

/components: the live gallery →

Theming

One typed theme drives every color, space, and font as CSS variables. Change a token and every component updates. You never edit a component's CSS to reskin an app.

theme.go
7 lines
t := theme.Default(theme.Overrides{
    Primary:  "oklch(0.82 0.155 78)", // amber accent
    Dark:     &theme.Overrides{Primary: "oklch(0.82 0.155 78)"},
    Surface:  "oklch(0.17 0.006 75)",
    RadiusMd: 6,
})
site.WithTheme(t)

/docs/theming →

Where next