Quick hands-on tutorial to build, run, and use a Glazed command with structured output
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:
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 directorygo mod init creates a Go module for dependency trackingglazed provides structured data processing capabilitiescobra handles command-line parsing (Glazed builds on this framework)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"
)
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:
Command Struct: ListUsersCommand embeds *cmds.CommandDescription, which contains command metadata (name, help text, fields)
Settings Struct: ListUsersSettings maps command-line flags to Go fields using struct tags. The glazed tags provide automatic type conversion and validation.
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:
vals.DecodeSectionInto() populates the settings struct from resolved values with automatic parsing and validationgenerateMockUsers() simulates data retrieval with the parsed settingstypes.Row objects instead of using direct output functionstypes.MRP("key", value) creates key-value pairs for each data fieldThe 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 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:
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)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--help outputThese are common stumbling blocks once you move beyond a single toy command:
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(...).
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.
Grouping commands (βdocuments β¦β, βquiz β¦β)
For multi-command CLIs, you can either:
cmds.WithParents("documents") to declare parent groups in metadata, orcobra.Commands and attach Glazed-built subcommands to them.The tutorial focuses on a single command; real apps usually need grouping.
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.
HTTP-backed commands need careful URL handling
If your command calls a REST API, avoid naive string concatenation for URLs. Handle:
http://host/prefix)url.PathEscape)url.Values)// 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:
var _ cmds.GlazeCommand = &ListUsersCommand{} line ensures the struct implements the required interface at compile timegenerateMockUsers() with actual data sources in productionGlazed 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:
logging.AddLoggingSectionToRootCommand(rootCmd, "<use-name>").logging.InitLoggerFromCobra(cmd) in PersistentPreRunE.logging.SetupLoggingFromValues(parsedSections) after parsing for per-command logging settings.Integration steps:
NewListUsersCommand() creates the Glazed command with configurationcli.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.help.NewHelpSystem() and help_cmd.SetupCobraRootCommand() provide enhanced help functionalityBuilt-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:
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:
--help displays auto-generated field descriptions and examples with enhanced formatting--name-filter Engineering displays only users matching the filter criteriahelp command provides contextual documentation and field guidanceThe 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:
types.Row and GlazeProcessor pattern--fields id,name,email displays only specified columns--sort-columns name sorts alphabetically (use --sort-columns -name for reverse order)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.
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.
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 outputGlazeCommand for structured dataType Safety: Use settings structs with glazed tags for automatic field parsing 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.
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"),
),
)
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
}
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.
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.
Study the patterns demonstrated in this tutorial:
CommandDescription and use settings structsglazed tags for automatic parsingtypes.Row objects for multi-format supportThese foundational patterns enable building professional CLI applications with Glazed's structured data processing capabilities.