---
title: Build Your First Glazed Command
description: Quick hands-on tutorial to build, run, and use a Glazed command with structured output
doc_version: 1
last_updated: 2026-07-02
---


# 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.

```bash
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:

```go
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.

```go
// 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.

```go

// 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.

```go
// 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.Command`s 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

```go
// 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.

```go
// 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)
    }
}
```

### Initialize Logging (Recommended)

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.

```go
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:

```bash
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.

```bash
# 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.

```bash
# 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](../topics/07-dual-commands.md).**

**Key pattern:**

```go
// 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:**

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

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

See [Dual Commands](../topics/07-dual-commands.md) 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:

```go
// 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:

```go
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:

```go
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.
