lvt-customize

star 0

Use when customizing generated LiveTemplate code - covers editing handlers/templates, understanding generated structure, changing behavior, and common customization patterns

livetemplate By livetemplate schedule Updated 12/21/2025

name: lvt-customize description: Use when customizing generated LiveTemplate code - covers editing handlers/templates, understanding generated structure, changing behavior, and common customization patterns

lvt:customize

Customize generated LiveTemplate resources, views, and templates.

๐ŸŽฏ ACTIVATION RULES

Context Detection

This skill typically runs in existing LiveTemplate projects (.lvtrc exists).

โœ… Context Established By:

  1. Project context - .lvtrc exists (most common scenario)
  2. Agent context - User is working with lvt-assistant agent
  3. Keyword context - User mentions "lvt", "livetemplate", or "lt"

Keyword matching (case-insensitive): lvt, livetemplate, lt

Trigger Patterns

With Context: โœ… Generic prompts related to this skill's purpose

Without Context (needs keywords): โœ… Must mention "lvt", "livetemplate", or "lt" โŒ Generic requests without keywords


Overview

LiveTemplate generates working code that you customize for your needs. All generated files are meant to be edited - lvt won't overwrite your changes.

Safe to customize:

  • โœ“ Handler files (.go)
  • โœ“ Template files (.tmpl)
  • โœ“ CSS/styling
  • โœ“ Business logic

Don't customize directly:

  • โœ— Generated database models (database/models/)
  • โœ— Schema files (schema.sql) - use migrations instead

Generated Code Structure

project/
โ”œโ”€โ”€ app/
โ”‚   โ”œโ”€โ”€ products/
โ”‚   โ”‚   โ”œโ”€โ”€ products.go        โ† Handler (customize this)
โ”‚   โ”‚   โ”œโ”€โ”€ products.tmpl      โ† Template (customize this)
โ”‚   โ”‚   โ””โ”€โ”€ products_test.go   โ† Tests (customize this)
โ”‚   โ””โ”€โ”€ dashboard/
โ”‚       โ”œโ”€โ”€ dashboard.go       โ† View handler
โ”‚       โ””โ”€โ”€ dashboard.tmpl     โ† View template
โ”œโ”€โ”€ database/
โ”‚   โ”œโ”€โ”€ models/                โ† Generated by sqlc (don't edit)
โ”‚   โ”œโ”€โ”€ schema.sql             โ† Use migrations, not direct edits
โ”‚   โ””โ”€โ”€ queries.sql            โ† Safe to add custom queries
โ”œโ”€โ”€ shared/                    โ† Shared utilities
โ””โ”€โ”€ cmd/
    โ””โ”€โ”€ myapp/
        โ””โ”€โ”€ main.go            โ† Routes auto-injected (safe to edit)

Common Customizations

1. Change Handler Logic

Generated handler:

// app/products/products.go
func Handler(queries *models.Queries) http.HandlerFunc {
    return livetemplate.Handler(func(r *http.Request, lt *livetemplate.LiveTemplate) error {
        // Default: List all products
        products, err := queries.ListProducts(r.Context())

        return lt.Render("products", products)
    })
}

Add filtering:

func Handler(queries *models.Queries) http.HandlerFunc {
    return livetemplate.Handler(func(r *http.Request, lt *livetemplate.LiveTemplate) error {
        // Get query parameter
        category := r.URL.Query().Get("category")

        var products []models.Product
        var err error

        if category != "" {
            // Custom filtered query
            products, err = queries.ListProductsByCategory(r.Context(), category)
        } else {
            products, err = queries.ListProducts(r.Context())
        }

        if err != nil {
            return err
        }

        return lt.Render("products", map[string]interface{}{
            "Products": products,
            "Category": category,
        })
    })
}

2. Customize Templates

Generated template:

<!-- app/products/products.tmpl -->
<div class="container">
    <h1>Products</h1>
    {{range .Products}}
        <div class="product">
            <h2>{{.Name}}</h2>
            <p>{{.Price}}</p>
        </div>
    {{end}}
</div>

Add custom styling and structure:

<div class="container mx-auto px-4 py-8">
    <div class="flex justify-between items-center mb-6">
        <h1 class="text-3xl font-bold">Products</h1>
        <button class="btn-primary">Add Product</button>
    </div>

    <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
    {{range .Products}}
        <div class="card hover:shadow-lg transition-shadow">
            <img src="/images/{{.ImageURL}}" alt="{{.Name}}" />
            <h2 class="font-semibold text-xl">{{.Name}}</h2>
            <p class="text-gray-600">${{printf "%.2f" .Price}}</p>
            <button class="btn-secondary">Add to Cart</button>
        </div>
    {{end}}
    </div>
</div>

3. Add Custom Queries

Add to queries.sql:

-- database/queries.sql

-- Generated queries (don't remove)
-- name: ListProducts :many
SELECT * FROM products ORDER BY created_at DESC;

-- Your custom queries (add below)
-- name: ListProductsByCategory :many
SELECT * FROM products
WHERE category = ?
ORDER BY created_at DESC;

-- name: GetFeaturedProducts :many
SELECT * FROM products
WHERE featured = true
ORDER BY popularity DESC
LIMIT 10;

-- name: SearchProducts :many
SELECT * FROM products
WHERE name LIKE '%' || ? || '%'
OR description LIKE '%' || ? || '%';

After editing queries.sql:

# Regenerate models
lvt migration up

4. Add Validation

// app/products/products.go
func Handler(queries *models.Queries) http.HandlerFunc {
    return livetemplate.Handler(func(r *http.Request, lt *livetemplate.LiveTemplate) error {
        if r.Method == http.MethodPost {
            name := r.FormValue("name")
            priceStr := r.FormValue("price")

            // Validation
            if name == "" {
                return lt.Error("Name is required")
            }

            price, err := strconv.ParseFloat(priceStr, 64)
            if err != nil || price < 0 {
                return lt.Error("Invalid price")
            }

            // Create product
            product, err := queries.CreateProduct(r.Context(), models.CreateProductParams{
                Name:  name,
                Price: price,
            })
            if err != nil {
                return err
            }

            return lt.Redirect("/products")
        }

        // ... rest of handler
    })
}

5. Add Authentication

// app/admin/admin.go
func Handler(queries *models.Queries) http.HandlerFunc {
    return livetemplate.Handler(func(r *http.Request, lt *livetemplate.LiveTemplate) error {
        // Check authentication
        user := getUserFromSession(r)
        if user == nil {
            return lt.Redirect("/login")
        }

        if !user.IsAdmin {
            return lt.Error("Unauthorized", http.StatusForbidden)
        }

        // ... admin logic
    })
}

Changing CSS Framework

Generate with different kit:

# Create app with specific kit
lvt new myapp --kit multi     # default, Tailwind CSS
lvt new myapp --kit single    # Tailwind CSS SPA
lvt new myapp --kit simple    # no CSS (semantic HTML)

# Generate resource with specific CSS
lvt gen resource products name --css tailwind
lvt gen resource products name --css none

Template Helpers

LiveTemplate templates have access to Go template functions:

<!-- Date formatting -->
{{.CreatedAt.Format "2006-01-02"}}

<!-- Conditional -->
{{if .Published}}
    <span class="badge-success">Published</span>
{{else}}
    <span class="badge-warning">Draft</span>
{{end}}

<!-- Range with index -->
{{range $i, $product := .Products}}
    <div class="item-{{$i}}">{{$product.Name}}</div>
{{end}}

<!-- Printf for formatting -->
<p>${{printf "%.2f" .Price}}</p>

<!-- Nested data -->
{{range .Products}}
    <h2>{{.Name}}</h2>
    {{range .Reviews}}
        <p>{{.Comment}}</p>
    {{end}}
{{end}}

WebSocket Actions

Generated resources include WebSocket support for real-time updates:

// app/products/products.go
func Handler(queries *models.Queries) http.HandlerFunc {
    return livetemplate.Handler(func(r *http.Request, lt *livetemplate.LiveTemplate) error {
        // Listen for WebSocket actions
        action := lt.Action()

        switch action {
        case "add":
            name := lt.FormValue("name")
            price := lt.FormValueFloat("price")

            product, err := queries.CreateProduct(r.Context(), models.CreateProductParams{
                Name:  name,
                Price: price,
            })
            if err != nil {
                return err
            }

            // Re-render with new data
            products, _ := queries.ListProducts(r.Context())
            return lt.Render("products", products)

        case "delete":
            id := lt.FormValueInt("id")

            err := queries.DeleteProduct(r.Context(), id)
            if err != nil {
                return err
            }

            products, _ := queries.ListProducts(r.Context())
            return lt.Render("products", products)

        default:
            // Initial load
            products, err := queries.ListProducts(r.Context())
            if err != nil {
                return err
            }

            return lt.Render("products", products)
        }
    })
}

Common Patterns

Pagination

func Handler(queries *models.Queries) http.HandlerFunc {
    return livetemplate.Handler(func(r *http.Request, lt *livetemplate.LiveTemplate) error {
        page := 1
        if p := r.URL.Query().Get("page"); p != "" {
            page, _ = strconv.Atoi(p)
        }

        limit := 20
        offset := (page - 1) * limit

        products, err := queries.ListProductsPaginated(r.Context(), models.ListProductsPaginatedParams{
            Limit:  int64(limit),
            Offset: int64(offset),
        })

        return lt.Render("products", map[string]interface{}{
            "Products": products,
            "Page":     page,
            "NextPage": page + 1,
            "PrevPage": page - 1,
        })
    })
}

Error Handling

func Handler(queries *models.Queries) http.HandlerFunc {
    return livetemplate.Handler(func(r *http.Request, lt *livetemplate.LiveTemplate) error {
        products, err := queries.ListProducts(r.Context())
        if err != nil {
            // Log error
            log.Printf("Error loading products: %v", err)

            // Show user-friendly message
            return lt.Error("Unable to load products. Please try again.")
        }

        if len(products) == 0 {
            // Handle empty state
            return lt.Render("products", map[string]interface{}{
                "Products": products,
                "Empty":    true,
            })
        }

        return lt.Render("products", products)
    })
}

Complex Data Structures

type ProductWithReviews struct {
    Product models.Product
    Reviews []models.Review
    AvgRating float64
}

func Handler(queries *models.Queries) http.HandlerFunc {
    return livetemplate.Handler(func(r *http.Request, lt *livetemplate.LiveTemplate) error {
        products, _ := queries.ListProducts(r.Context())

        var enriched []ProductWithReviews
        for _, p := range products {
            reviews, _ := queries.GetProductReviews(r.Context(), p.ID)

            var total float64
            for _, r := range reviews {
                total += r.Rating
            }

            avg := 0.0
            if len(reviews) > 0 {
                avg = total / float64(len(reviews))
            }

            enriched = append(enriched, ProductWithReviews{
                Product: p,
                Reviews: reviews,
                AvgRating: avg,
            })
        }

        return lt.Render("products", enriched)
    })
}

File Organization Tips

Keep related code together:

app/products/
โ”œโ”€โ”€ products.go           # Main handler
โ”œโ”€โ”€ products.tmpl         # Main template
โ”œโ”€โ”€ products_test.go      # Tests
โ”œโ”€โ”€ helpers.go            # Product-specific helpers
โ””โ”€โ”€ validation.go         # Product validation logic

Shared utilities:

project/
โ”œโ”€โ”€ app/
โ”‚   โ””โ”€โ”€ products/
โ”œโ”€โ”€ shared/
โ”‚   โ”œโ”€โ”€ auth/             # Shared auth logic
โ”‚   โ”œโ”€โ”€ validation/       # Shared validators
โ”‚   โ””โ”€โ”€ helpers/          # Shared helpers
โ””โ”€โ”€ ...

Quick Reference

I want to... How
Change handler logic Edit app/<name>/<name>.go
Customize HTML/UI Edit app/<name>/<name>.tmpl
Add custom query Edit database/queries.sql + run lvt migration up
Add validation Add checks in handler before database calls
Change CSS framework Use --css flag when generating
Add authentication Check session/user in handler
Handle WebSocket actions Use lt.Action() in handler
Pass complex data Use map[string]interface{} or custom structs

Remember

โœ“ All generated .go and .tmpl files are meant to be edited โœ“ lvt won't overwrite your customizations โœ“ Add custom SQL queries to queries.sql โœ“ Run lvt migration up after changing queries โœ“ Use Go template syntax in .tmpl files โœ“ WebSocket actions update the page automatically

โœ— Don't edit generated models in database/models/ โœ— Don't edit schema.sql directly - use migrations โœ— Don't forget to run migrations after query changes

Install via CLI
npx skills add https://github.com/livetemplate/lvt --skill lvt-customize
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator
livetemplate
livetemplate Explore all skills →