Build Your First Glazed Command

Quick hands-on tutorial to build, run, and use a Glazed command with structured output

Sections

Terminology & Glossary
πŸ“– Documentation
Navigation
74 sectionsv0.1
πŸ“„ Build Your First Glazed Command β€” glaze help build-first-command
build-first-command

Build Your First Glazed Command

Quick hands-on tutorial to build, run, and use a Glazed command with structured output

Tutorialtutorialcommandsquick-startglazed

Build Your First Glazed Command

Glazed enables you to build CLI commands that automatically support multiple output formats without writing format-specific code. By implementing the GlazeCommand interface and yielding structured data as types.Row objects, your command can output JSON, YAML, CSV, and formatted tables through a single implementation. This tutorial walks you through creating a complete user management command that demonstrates these core patterns.

Learning objectives:

  • Create a functional CLI command with filtering and limiting options
  • Implement automatic support for multiple output formats
  • Learn fundamental patterns for structured data processing in Glazed
  • Understand command configuration and field handling

Prerequisites

  • Go 1.25+ installed
  • Basic familiarity with Go and command-line tools

Step 1: Set Up Your Project

A Glazed project requires minimal setup with two key dependencies. The framework integrates with Cobra for command-line parsing while adding structured data processing capabilities on top.

mkdir glazed-quickstart
cd glazed-quickstart
go mod init glazed-quickstart
go get github.com/go-go-golems/glazed
go get github.com/spf13/cobra

Project structure:

  • glazed-quickstart serves as the project directory
  • go mod init creates a Go module for dependency tracking
  • Two key dependencies:
    • glazed provides structured data processing capabilities
    • cobra handles command-line parsing (Glazed builds on this framework)

Step 2: Create Your First Command

Create main.go with the complete command implementation:

package main

import (
    "context"
    "fmt"
    "os"
    "strings"
    "time"

    "github.com/go-go-golems/glazed/pkg/cli"
    "github.com/go-go-golems/glazed/pkg/cmds"
    "github.com/go-go-golems/glazed/pkg/cmds/fields"
    "github.com/go-go-golems/glazed/pkg/cmds/schema"
    "github.com/go-go-golems/glazed/pkg/cmds/values"
    "github.com/go-go-golems/glazed/pkg/help"
    help_cmd "github.com/go-go-golems/glazed/pkg/help/cmd"
    "github.com/go-go-golems/glazed/pkg/middlewares"
    "github.com/go-go-golems/glazed/pkg/types"
    "github.com/spf13/cobra"
)

Command Structure

Every Glazed command follows a consistent pattern: a command struct embeds *cmds.CommandDescription for metadata, and a settings struct maps command-line flags to Go fields using struct tags for type-safe field access.

// Step 2.1: Define your command struct
type ListUsersCommand struct {
    *cmds.CommandDescription
}

// Step 2.2: Define settings for type-safe field access
type ListUsersSettings struct {
    Limit      int    `glazed:"limit"`      // Maps to --limit flag
    NameFilter string `glazed:"name-filter"` // Maps to --name-filter flag
    Active     bool   `glazed:"active-only"` // Maps to --active-only flag
}

Key components:

  1. Command Struct: ListUsersCommand embeds *cmds.CommandDescription, which contains command metadata (name, help text, fields)

  2. Settings Struct: ListUsersSettings maps command-line flags to Go fields using struct tags. The glazed tags provide automatic type conversion and validation.

Core Command Logic

The GlazeCommand interface requires implementing RunIntoGlazeProcessor, which receives resolved values and a processor for structured output. Instead of writing directly to stdout, you create types.Row objects that the processor can format into multiple output types automatically.


// Step 2.3: Implement the GlazeCommand interface
func (c *ListUsersCommand) RunIntoGlazeProcessor(
    ctx context.Context,
    vals *values.Values,
    gp middlewares.Processor,
) error {
    // Parse settings from command line
    settings := &ListUsersSettings{}
    if err := vals.DecodeSectionInto(schema.DefaultSlug, settings); err != nil {
        return err
    }

    // Simulate getting users (in real app, this would be a database call)
    users := generateMockUsers(settings.Limit, settings.NameFilter, settings.Active)

    // Output structured data as rows
    for _, user := range users {
        row := types.NewRow(
            types.MRP("id", user.ID),
            types.MRP("name", user.Name),
            types.MRP("email", user.Email),
            types.MRP("department", user.Department),
            types.MRP("active", user.Active),
            types.MRP("created_at", user.CreatedAt.Format("2006-01-02")),
        )
        
        if err := gp.AddRow(ctx, row); err != nil {
            return err
        }
    }

    return nil
}

Implementation details:

  1. Settings Extraction: vals.DecodeSectionInto() populates the settings struct from resolved values with automatic parsing and validation
  2. Business Logic: generateMockUsers() simulates data retrieval with the parsed settings
  3. Structured Output: Creates types.Row objects instead of using direct output functions
  4. Row Structure: types.MRP("key", value) creates key-value pairs for each data field

The GlazeProcessor collects these rows and can output them in multiple formats without additional format-specific code.

Important β€” Decode values into a struct: Always decode resolved values into your settings struct using vals.DecodeSectionInto(schema.DefaultSlug, &YourSettings{}). Avoid reading Cobra flags directly; decoding ensures defaults, validation, and help text stay consistent with your schema field definitions and active sections.

Command Configuration and Fields

Command configuration combines your custom fields with Glazed's built-in output formatting capabilities. The settings.NewGlazedSchema() helper adds standard flags like --output, --fields, and --sort-columns, while your custom field definitions specify the command's business logic inputs.

// Step 2.4: Create constructor function
func NewListUsersCommand() (*ListUsersCommand, error) {
    // Create glazed schema section for output formatting options
    // Note: cli.BuildCobraCommand will also auto-add this section for GlazeCommand implementations.
    glazedSection, err := settings.NewGlazedSchema()
    if err != nil {
        return nil, err
    }

    // Create command settings section for debugging features
    commandSettingsSection, err := cli.NewCommandSettingsSection()
    if err != nil {
        return nil, err
    }

    // Define command with fields
    cmdDesc := cmds.NewCommandDescription(
        "list-users",
        cmds.WithShort("List users in the system"),
        cmds.WithLong(`
List all users with optional filtering and limiting.
Supports multiple output formats including JSON, YAML, CSV, and tables.

Examples:
  list-users                           # List all users as table
  list-users --limit 5                 # Show only first 5 users
  list-users --name-filter admin       # Filter users containing "admin"
  list-users --active-only             # Show only active users
  list-users --output json             # Output as JSON
  list-users --output csv              # Output as CSV
        `),
        
        // Define command flags
        cmds.WithFlags(
            fields.New(
                "limit",
                fields.TypeInteger,
                fields.WithDefault(10),
                fields.WithHelp("Maximum number of users to show"),
                fields.WithShortFlag("l"),
            ),
            fields.New(
                "name-filter",
                fields.TypeString,
                fields.WithDefault(""),
                fields.WithHelp("Filter users by name or email"),
                fields.WithShortFlag("f"),
            ),
            fields.New(
                "active-only",
                fields.TypeBool,
                fields.WithDefault(false),
                fields.WithHelp("Show only active users"),
                fields.WithShortFlag("a"),
            ),
        ),
        
        // Add glazed and command settings sections
        cmds.WithSectionsList(glazedSection, commandSettingsSection),
    )

    return &ListUsersCommand{
        CommandDescription: cmdDesc,
    }, nil
}

Configuration components:

  1. Glazed Schema Section: settings.NewGlazedSchema() adds built-in fields like --output, --fields, --sort-columns (and cli.BuildCobraCommand will auto-add it for GlazeCommand implementations if you don't)
  2. Command Settings Section: cli.NewCommandSettingsSection() adds debugging and configuration fields:
    • --print-parsed-fields: Debug field parsing
    • --print-schema: Show command schema
    • --load-fields-from-file: Load settings from JSON file
  3. Command Metadata: Defines command name, short description, and comprehensive help text with usage examples
  4. Field Definitions: Each flag specifies:
    • Type: Integer, String, Bool with automatic validation
    • Default Value: Behavior when the flag is not specified
    • Help Text: Displayed in --help output
    • Short Flag: Single-letter abbreviations for convenience
  5. Section Composition: Combines custom fields with Glazed's built-in sections

Practical Gotchas (Not Obvious from β€œHello World”)

These are common stumbling blocks once you move beyond a single toy command:

  1. schema.Section is an interface β€” don’t use *schema.Section
    If you start composing multi-section schemas (e.g. an api section + a documents section), remember that schema.Section is an interface. Returning or storing *schema.Section is a β€œpointer to interface” and will produce confusing compiler errors. Prefer returning schema.Section (the interface) or the concrete *schema.SectionImpl returned by schema.NewSection(...).

  2. Field types vary by Glazed version
    Not every repository/version has every field type you might expect (for example, some setups don’t have a built-in β€œduration” type). A pragmatic pattern is --timeout-seconds as an integer and then convert to time.Duration in code.

  3. Grouping commands (β€œdocuments …”, β€œquiz …”)
    For multi-command CLIs, you can either:

    • use cmds.WithParents("documents") to declare parent groups in metadata, or
    • create explicit parent cobra.Commands and attach Glazed-built subcommands to them.

    The tutorial focuses on a single command; real apps usually need grouping.

  4. Pointers in table output
    Table output will print Go pointer values as addresses (0xc000...). If your API types use *int/*string for nullable fields, dereference (or convert to nil/concrete values) before adding them to a types.Row.

  5. HTTP-backed commands need careful URL handling
    If your command calls a REST API, avoid naive string concatenation for URLs. Handle:

    • base URLs with optional path prefixes (http://host/prefix)
    • path segment escaping (url.PathEscape)
    • query params (url.Values)

Interface Compliance and Mock Data

// Ensure interface compliance
var _ cmds.GlazeCommand = &ListUsersCommand{}

// Mock data structures and generation
type User struct {
    ID         int
    Name       string
    Email      string
    Department string
    Active     bool
    CreatedAt  time.Time
}

func generateMockUsers(limit int, filter string, activeOnly bool) []User {
    allUsers := []User{
        {1, "Alice Johnson", "alice@company.com", "Engineering", true, time.Date(2023, 1, 15, 0, 0, 0, 0, time.UTC)},
        {2, "Bob Smith", "bob@company.com", "Marketing", true, time.Date(2023, 2, 20, 0, 0, 0, 0, time.UTC)},
        {3, "Charlie Brown", "charlie@company.com", "Engineering", false, time.Date(2023, 3, 10, 0, 0, 0, 0, time.UTC)},
        {4, "Diana Prince", "diana@company.com", "HR", true, time.Date(2023, 4, 5, 0, 0, 0, 0, time.UTC)},
        {5, "Eve Adams", "eve@company.com", "Sales", true, time.Date(2023, 5, 12, 0, 0, 0, 0, time.UTC)},
        {6, "Frank Miller", "frank@company.com", "Engineering", false, time.Date(2023, 6, 8, 0, 0, 0, 0, time.UTC)},
        {7, "Grace Hopper", "grace@company.com", "Engineering", true, time.Date(2023, 7, 22, 0, 0, 0, 0, time.UTC)},
        {8, "Henry Ford", "henry@company.com", "Operations", true, time.Date(2023, 8, 14, 0, 0, 0, 0, time.UTC)},
    }

    var filtered []User
    for _, user := range allUsers {
        // Apply active filter
        if activeOnly && !user.Active {
            continue
        }
        
        // Apply text filter
        if filter != "" {
            if !strings.Contains(strings.ToLower(user.Name), strings.ToLower(filter)) && 
               !strings.Contains(strings.ToLower(user.Email), strings.ToLower(filter)) && 
               !strings.Contains(strings.ToLower(user.Department), strings.ToLower(filter)) {
                continue
            }
        }
        
        filtered = append(filtered, user)
        
        // Apply limit
        if len(filtered) >= limit {
            break
        }
    }

    return filtered
}


Implementation notes:

  1. Interface Compliance Check: The var _ cmds.GlazeCommand = &ListUsersCommand{} line ensures the struct implements the required interface at compile time
  2. Mock Data: Provides realistic sample data for development and testing. Replace generateMockUsers() with actual data sources in production
  3. Filtering Logic: Demonstrates how command fields control data processing

CLI Application Integration

Glazed commands integrate with standard Cobra applications through the cli.BuildCobraCommand() builder function. This function handles the conversion between Glazed's field section system and Cobra's flag parsing, automatically configuring output processing and help text generation. You can pass parser and mode options via CobraParserConfig and CobraOption helpers.

// Step 3: Set up CLI application
func main() {
    // Create root command
    rootCmd := &cobra.Command{
        Use:   "glazed-quickstart",
        Short: "A quick start example of Glazed commands",
        Long:  "Demonstrates how to build commands with Glazed framework",
    }

    // Create and register our command
    listUsersCmd, err := NewListUsersCommand()
    if err != nil {
        fmt.Fprintf(os.Stderr, "Error creating command: %v\n", err)
        os.Exit(1)
    }

    // Convert to Cobra command with enhanced options
    cobraListUsersCmd, err := cli.BuildCobraCommand(listUsersCmd,
        cli.WithParserConfig(cli.CobraParserConfig{
            AppName:           "glazed-quickstart",
            ShortHelpSections: []string{schema.DefaultSlug},
        }),
    )
    if err != nil {
        fmt.Fprintf(os.Stderr, "Error building command: %v\n", err)
        os.Exit(1)
    }

    // Add to root command
    rootCmd.AddCommand(cobraListUsersCmd)

    // Setup enhanced help system
    helpSystem := help.NewHelpSystem()
    help_cmd.SetupCobraRootCommand(helpSystem, rootCmd)

    // Execute
    if err := rootCmd.Execute(); err != nil {
        os.Exit(1)
    }
}

Glazed provides a logging section you can attach to your root command. This exposes logging-related flags and initializes logging based on configuration. Initialize the logger in PersistentPreRunE using Cobra-parsed flags so logging is active before your command logic runs.

package main

import (
    "github.com/go-go-golems/glazed/pkg/cmds/logging"
    "github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
    Use: "glazed-quickstart",
    PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
        // Initialize logger after Cobra has parsed flags
        return logging.InitLoggerFromCobra(cmd)
    },
}

func main() {
    // Add logging flags (log-level, log-format) to the root command
    _ = logging.AddLoggingSectionToRootCommand(rootCmd, "glazed-quickstart")

    // ... register commands, help system, etc.
    _ = rootCmd.Execute()
}

Key points:

  • Add logging flags with logging.AddLoggingSectionToRootCommand(rootCmd, "<use-name>").
  • Initialize logging early with logging.InitLoggerFromCobra(cmd) in PersistentPreRunE.
  • Alternatively, you can call logging.SetupLoggingFromValues(parsedSections) after parsing for per-command logging settings.

Integration steps:

  1. Root Command: Creates a standard Cobra root command as the application entry point
  2. Command Creation: NewListUsersCommand() creates the Glazed command with configuration
  3. Enhanced Cobra Bridge: Use cli.WithParserConfig to pass a CobraParserConfig that customizes parser behavior (for example AppName for env loading and ShortHelpSections for help). Only set MiddlewaresFunc when you want to replace the built-in chain.
  4. Registration: Adds the converted command as a subcommand
  5. Help System Setup: help.NewHelpSystem() and help_cmd.SetupCobraRootCommand() provide enhanced help functionality
  6. Execution: Starts the CLI application and processes command-line arguments

Built-in Command Features

The built-in Cobra parser path (leave MiddlewaresFunc nil) provides several useful debugging and configuration features automatically when you set AppName. CobraCommandDefaultMiddlewares is a lower-level helper for flags, args, and defaults only.

  • --print-parsed-fields: Shows how fields were parsed from different sources
  • --print-yaml: Outputs the command's configuration as YAML
  • --print-schema: Displays the command's field schema
  • --config-file: Explicit config file path (overlays supported via resolver)

Environment-Backed Settings

To read settings from environment variables, keep the default parser chain and use the AppName prefix:

export GLAZED_QUICKSTART_LIMIT=3
export GLAZED_QUICKSTART_ACTIVE_ONLY=true
./glazed-quickstart list-users --print-parsed-fields

The parsed-field output will show env entries alongside Cobra flags and defaults. If you supply a custom MiddlewaresFunc, you must re-add env loading yourself.

Enhanced Help System

The Glazed help system (help.NewHelpSystem() and help_cmd.SetupCobraRootCommand()) adds advanced help capabilities:

  • Contextual Help: Provides detailed help based on command context and available sections
  • Field Documentation: Automatically generates help text from field definitions
  • Section-Aware Help: Shows relevant fields based on active sections
  • Rich Formatting: Enhanced formatting for better readability in terminal output

Step 3: Build and Test Your Command

Testing validates that your command properly parses fields, processes data according to the business logic, and integrates correctly with Glazed's output system.

# Build the application
go build -o glazed-quickstart

# Test basic functionality
./glazed-quickstart list-users --help

# Try different field combinations
./glazed-quickstart list-users
./glazed-quickstart list-users --limit 3
./glazed-quickstart list-users --name-filter Engineering
./glazed-quickstart list-users --active-only

# Test built-in debugging features
./glazed-quickstart list-users --print-parsed-fields
./glazed-quickstart list-users --print-schema
./glazed-quickstart list-users --print-yaml

# Test enhanced help system
./glazed-quickstart help
./glazed-quickstart list-users --help

Expected behavior:

  1. Help Text: --help displays auto-generated field descriptions and examples with enhanced formatting
  2. Field Validation: Invalid values trigger automatic validation errors
  3. Default Behavior: Without flags, shows the first 10 users in table format
  4. Filtering: --name-filter Engineering displays only users matching the filter criteria
  5. Help Command: help command provides contextual documentation and field guidance

Step 4: Multiple Output Formats

The primary benefit of using types.Row objects is automatic support for multiple output formats. Glazed's built-in processors can convert the same structured data into JSON, YAML, CSV, and formatted tables without any additional code in your command implementation.

# Table output (default)
./glazed-quickstart list-users --limit 3

# JSON output
./glazed-quickstart list-users --limit 3 --output json

# YAML output
./glazed-quickstart list-users --limit 3 --output yaml

# CSV output
./glazed-quickstart list-users --limit 3 --output csv

# Select specific fields
./glazed-quickstart list-users --fields id,name,email

# Sort by field
./glazed-quickstart list-users --sort-columns name

# Combine options
./glazed-quickstart list-users --name-filter Engineering --output json --fields name,department

Key capabilities demonstrated:

  1. Zero Additional Code: All output formats work automatically through the types.Row and GlazeProcessor pattern
  2. Field Selection: --fields id,name,email displays only specified columns
  3. Sorting: --sort-columns name sorts alphabetically (use --sort-columns -name for reverse order)
  4. Composability: All flags combine seamlessly for flexible data presentation

Step 5: Dual Commands (Advanced)

Some commands benefit from providing both human-readable text output and machine-parseable structured data. Glazed supports this pattern through dual commands that implement both BareCommand and GlazeCommand interfaces.

For the complete guide, see Dual Commands.

Key pattern:

// Implement both interfaces
var _ cmds.BareCommand = &StatusCommand{}
var _ cmds.GlazeCommand = &StatusCommand{}

// Build with dual mode
cli.BuildCobraCommand(cmd,
    cli.WithDualMode(true),
    cli.WithGlazeToggleFlag("with-glaze-output"),
)

Testing:

# Classic mode (default)
./glazed-quickstart status

# Glaze mode
./glazed-quickstart status --with-glaze-output --output json

See Dual Commands for advanced options like WithDefaultToGlaze, setting default output format, and common patterns.

Best Practices and Patterns

This tutorial demonstrates several architectural patterns that form the foundation of robust Glazed applications. Following these patterns ensures your commands integrate well with the framework and provide consistent user experiences.

Command Organization

Single Responsibility: Each command should focus on one task. Use command groups to organize related functionality rather than building complex monolithic commands.

Clear Interfaces: Implement the appropriate command interface for your use case:

  • BareCommand for simple text output
  • GlazeCommand for structured data
  • Both interfaces for dual-mode commands

Type Safety: Use settings structs with glazed tags for automatic field parsing and validation.

Error Handling and Validation

Input Validation: Validate business rules in your command implementation, not just field types:

// Validate business rules after field parsing
if settings.Limit < 1 {
    return fmt.Errorf("limit must be at least 1, got %d", settings.Limit)
}
if settings.Limit > 1000 {
    return fmt.Errorf("limit cannot exceed 1000 (got %d)", settings.Limit)
}

Descriptive Errors: Provide context and suggestions in error messages to help users correct issues.

Advanced Field Types

Glazed supports various field types beyond basic strings, integers, and booleans:

cmds.WithFlags(
    // File field validates file exists
    fields.New(
        "config-file",
        fields.TypeFile,
        fields.WithHelp("Configuration file path"),
    ),
    
    // Choice field limits valid options
    fields.New(
        "output-format",
        fields.TypeChoice,
        fields.WithChoices("json", "yaml", "xml"),
        fields.WithDefault("json"),
        fields.WithHelp("Output format"),
    ),
)

Production Patterns

Structured Logging: Add logging for debugging and monitoring:

func (c *ListUsersCommand) RunIntoGlazeProcessor(ctx context.Context, vals *values.Values, gp middlewares.Processor) error {
    settings := &ListUsersSettings{}
    if err := vals.DecodeSectionInto(schema.DefaultSlug, settings); err != nil {
        return fmt.Errorf("failed to parse settings: %w", err)
    }
    
    log.Debug().Int("limit", settings.Limit).Str("filter", settings.NameFilter).Msg("fetching users")
    
    users, err := fetchUsersFromDatabase(settings)
    if err != nil {
        return fmt.Errorf("failed to fetch users: %w", err)
    }
    
    log.Info().Int("count", len(users)).Msg("successfully fetched users")
    
    for _, user := range users {
        row := types.NewRowFromStruct(&user, true)
        if err := gp.AddRow(ctx, row); err != nil {
            return fmt.Errorf("failed to add user row: %w", err)
        }
    }
    
    return nil
}

Next Steps

Learn Core Concepts

glaze help sections-guide

Learn about field sections for organizing reusable configuration sets across commands.

glaze help middlewares-guide

Understand data processing pipelines and how to transform structured output.

Build Complete Applications

glaze help commands-reference

Explore command organization patterns for building complex CLI application suites.

glaze help custom-section

Create domain-specific field sections for your application's needs.

Advanced Topics

Study the patterns demonstrated in this tutorial:

  • Command Structure: Embed CommandDescription and use settings structs
  • Type Safety: Leverage glazed tags for automatic parsing
  • Output Flexibility: Use types.Row objects for multi-format support
  • Interface Design: Choose appropriate command interfaces for your use case

These foundational patterns enable building professional CLI applications with Glazed's structured data processing capabilities.