Skip to content

Artisan Console

Introduction

Artisan is the CLI tool that comes with Goravel for interacting with the command line. You can access it using facades.Artisan(). This tool has several useful commands that can assist you in the development of your application. Utilize the following command to view all available commands.

shell
./artisan list

# or
go run . artisan list

Each command also has a "help" flag that shows and explains the arguments and options associated with the command:

shell
./artisan migrate --help

Instead of repeating ./artisan ... command, you may want to add an alias to your shell configuration with the terminal command below:

shell
echo -e "\r\nalias artisan=\"go run . artisan\"" >>~/.zshrc

Then you can simply run your commands like this:

shell
artisan make:controller DemoController

You can also use the artisan shell script to run built-in commands.

Generating Commands

You can use the make:command command to create a new command in the app/console/commands directory. Don't worry if this directory does not exist in your application, it will be created the first time you run the make:command command:

shell
./artisan make:command SendEmails
./artisan make:command user/SendEmails

Register Commands

All commands should be registered via the WithCommands function in the bootstrap/app.go file:

go
func Boot() contractsfoundation.Application {
	return foundation.Setup().
		WithCommands(Commands).
		WithConfig(config.Boot).
		Create()
}

A new command created by make:command will be registered automatically in the bootstrap/commands.go::Commands() function and the function will be called by WithCommands. You need register the command manually if you create the command file by yourself.

Filtering Commands

You may want to scope which built-in Artisan commands are registered in different environments — for example, hiding dev commands like make:*, package:*, and vendor:publish in production. The WithCommandsFilter method on ApplicationBuilder lets you return a positive list of command signatures to keep:

go
func Boot() contractsfoundation.Application {
	return foundation.Setup().
		WithCommands(Commands).
		WithCommandsFilter(func() []string {
			if facades.Config().GetString("app.env") == "production" {
				return []string{
					"up", "down", "key:generate", "about",
					"schedule:*", // glob
					"queue:*",    // glob
				}
			}
			return nil // keep everything in other environments
		}).
		WithConfig(config.Boot).
		Create()
}

The callback runs once at build time and each entry is matched against command.Signature() in one of two ways:

  • Exact match (no wildcard) — the signature must match the entry exactly.
  • Glob match (entry contains *) — checked using path.Match. * matches any sequence of non-/ characters.

The return value determines the filtering behavior:

  • Method not called — all commands are kept (default).
  • Return nil — all commands are kept (no filter applied).
  • Return []string{} — all commands are dropped.
  • Return entries — only commands whose signature matches an entry are kept.

Note: The filter applies to all commands including those added via WithCommands, so the user cannot bypass the filter by adding commands manually.

Command Structure

After generating your command, assign suitable values to the signature and description properties of the struct. The Handle method will be called when your command is executed. You need to implement your logic in this method.

go
package commands

import (
  "github.com/goravel/framework/contracts/console"
  "github.com/goravel/framework/contracts/console/command"
)

type SendEmails struct {
}

// Signature The name and signature of the console command.
func (receiver *SendEmails) Signature() string {
  return "send:emails"
}

// Description The console command description.
func (receiver *SendEmails) Description() string {
  return "Send emails"
}

// Extend The console command extend.
func (receiver *SendEmails) Extend() command.Extend {
  return command.Extend{}
}

// Handle Execute the console command.
func (receiver *SendEmails) Handle(ctx console.Context) error {
  return nil
}

Command I/O

Retrieving Input

When you write console commands, it's typical to collect user input through arguments or options. With Goravel, it's extremely easy to retrieve the arguments and options that the user provides.

Arguments

Follow the arguments after the command:

shell
./artisan send:emails SUBJECT EMAIL_1 EMAIL_2

Definition:

go
// send:emails <subject> <email...>
func (receiver *SendEmails) Extend() command.Extend {
	return command.Extend{
		Arguments: []command.Argument{
			&command.ArgumentString{
				Name:     "subject",
				Usage:    "subject of email",
				Required: true,
			},
			&command.ArgumentStringSlice{
				Name:  "emails",
				Usage: "target emails",
				Min:   1,
				Max:   -1,
			},
		},
	}
}

Supported agrument types : ArgumentFloat32, ArgumentFloat64, ArgumentInt, ArgumentInt8, ArgumentInt16, ArgumentInt32, ArgumentInt64, ArgumentString, ArgumentUint, ArgumentUint8, ArgumentUint16, ArgumentUint32, ArgumentUint64, ArgumentTimestamp, ArgumentFloat32Slice, ArgumentFloat64Slice, ArgumentIntSlice, ArgumentInt8Slice, ArgumentInt16Slice, ArgumentInt32Slice, ArgumentInt64Slice, ArgumentStringSlice, ArgumentUintSlice, ArgumentUint8Slice, ArgumentUint16Slice, ArgumentUint32Slice, ArgumentUint64Slice, ArgumentTimestampSlice

Argument types with single value support next fields:

go
	Name     string // the name of this argument
	Value    T      // the default value of this argument
	Usage    string // the usage text to show
	Required bool   // if this argument is required

Slice argument types fields:

go
	Name  string // the name of this argument
	Value T      // the default value of this argument
	Usage string // the usage text to show
	Min   int    // the min num of occurrences of this argument
	Max   int    // the max num of occurrences of this argument, set to -1 for unlimited

Timestamp arguments additionally supports Layouts []string field, that should be filled with supported layouts

Get arguments:

go
func (receiver *SendEmails) Handle(ctx console.Context) error {
  subject := ctx.ArgumentString("subject")
  emails := ctx.ArgumentStringSlice("emails")

  return nil
}

Alternatively, it is possible to access arguments directly:

go
func (receiver *SendEmails) Handle(ctx console.Context) error {
  name := ctx.Argument(0)
  email := ctx.Argument(1)
  all := ctx.Arguments()

  return nil
}

Options

Options, like arguments, are another form of user input. Options are prefixed by two hyphens (--) when they are provided via the command line.

Definition:

go
func (receiver *ListCommand) Extend() command.Extend {
  return command.Extend{
    Flags: []command.Flag{
      &command.StringFlag{
        Name:    "lang",
        Value:   "default",
        Aliases: []string{"l"},
        Usage:   "language for the greeting",
      },
    },
  }
}

Get:

go
func (receiver *ListCommand) Handle(ctx console.Context) error {
  lang := ctx.Option("lang")

  return nil
}

Usage:

shell
./artisan emails --lang Chinese
./artisan emails -l Chinese

Except command.StringFlag, we can also use other type Flag and Option*: StringSliceFlag, BoolFlag, Float64Flag, Float64SliceFlag, IntFlag, IntSliceFlag, Int64Flag, Int64SliceFlag.

Prompting For Input

Asking Questions

In addition to arguments and options, you may also prompt the user for input during the execution of a command. The Ask method will prompt the user with the given question and return their response:

go
func (receiver *SendEmails) Handle(ctx console.Context) error {
  email, err := ctx.Ask("What is your email address?")

  return err
}

Additionally, you can pass options to the Ask method as optional second argument:

go
func (receiver *SendEmails) Handle(ctx console.Context) error {
    name, err := ctx.Ask("What is your name?", console.AskOption{
        Default: "Krishan",
    })

    return err
}

// Available options
type AskOption struct {
    // Default the default value for the input.
    Default string
    // Description the input description.
    Description string
    // Lines the number of lines for the input.(use for multiple lines text)
    Lines int
    // Limit the character limit for the input.
    Limit int
    // Multiple determines if input is single line or multiple lines text
    Multiple bool
    // Placeholder the input placeholder.
    Placeholder string
    // Prompt the prompt message.(use for single line input)
    Prompt string
    // Validate the input validation function.
    Validate func(string) error
}

Sometimes you may need to hide the user input, such as when prompting for a password. You can use the Secret method to hide the user input:

go
func (receiver *SendEmails) Handle(ctx console.Context) error {
    password, err := ctx.Secret("What is the password?", console.SecretOption{
        Validate: func (s string) error {
            if len(s) < 8 {
                return errors.New("password length should be at least 8")
            }
            return nil
        },
    })

    return err
}

// Available options
type SecretOption struct {
    // Default the default value for the input.
    Default string
    // Description the input description.
    Description string
    // Limit the character limit for the input.
    Limit int
    // Placeholder the input placeholder.
    Placeholder string
    // Validate the input validation function.
    Validate func(string) error
}

Confirming Actions

If you need to ask the user to confirm an action before proceeding, you may use the Confirm method. By default, this method will return false unless the user select affirmative option.

go
if ctx.Confirm("Do you wish to continue?") {
    // ...
}

You can also pass a second argument to the Confirm method to customize the default value, label of the affirmative and negative buttons:

go
if ctx.Confirm("Do you wish to continue?", console.ConfirmOption{
	Default : true,
	Affirmative : "Yes",
	Negative : "No",
}) {
    // ...
}

// Available options
type ConfirmOption struct {
    // Affirmative label for the affirmative button.
    Affirmative string
    // Default the default value for the input.
    Default bool
    // Description the input description.
    Description string
    // Negative label for the negative button.
    Negative string
}

Single Select Questions

If you need to ask the user to select an option from a list of options, you may use the Choice method. The Choice method will return the value of the selected option:

go
question := "What is your favorite programming language?"
options := []console.Choice{
    {Key: "go", Value: "Go"},
    {Key: "php", Value: "PHP"},
    {Key: "python", Value: "Python"},
    {Key: "cpp", Value: "C++", Selected: true},
}
color, err := ctx.Choice(question, options)

Additionally, you can pass options to the Choice method as optional second argument:

go
question := "What is your favorite programming language?"
options := []console.Choice{
    {Key: "go", Value: "Go"},
    {Key: "php", Value: "PHP"},
    {Key: "python", Value: "Python"},
    {Key: "cpp", Value: "C++", Selected: true},
}

color, err := ctx.Choice(question, options, console.ChoiceOption{
    Default: "go",
})

// Available options
type ChoiceOption struct {
    // Default the default value for the input.
    Default string
    // Description the input description.
    Description string
    // Validate the input validation function.
    Validate func(string) error
}

Multiple Select Questions

If you need to ask the user to select multiple options from a list of options, you may use the MultiSelect method. The MultiSelect method will return the values of the selected options:

go
question := "What are your favorite programming languages?"
options := []console.Choice{
    {Key: "go", Value: "Go"},
    {Key: "php", Value: "PHP"},
    {Key: "python", Value: "Python"},
    {Key: "cpp", Value: "C++", Selected: true},
}
colors, err := ctx.MultiSelect(question, options)

Additionally, you can pass options to the MultiSelect method as optional second argument:

go
question := "What are your favorite programming languages?"
options := []console.Choice{
    {Key: "go", Value: "Go"},
    {Key: "php", Value: "PHP"},
    {Key: "python", Value: "Python"},
    {Key: "cpp", Value: "C++", Selected: true},
}

colors, err := ctx.MultiSelect(question, options, console.MultiSelectOption{
    Default: []string{"go", "php"},
})

// Available options
type MultiSelectOption struct {
    // Default the default value for the input.
    Default []string
    // Description the input description.
    Description string
    // Filterable determines if the choices can be filtered, type `/` to starting filter.
    Filterable bool
    // Limit the number of choices that can be selected.
    Limit int
    // Validate the input validation function.
    Validate func([]string) error
}

Writing Output

Sometimes you may need to write output to the console. Goravel provides several methods to assist you in writing output to the console. Each of the method have their appropriate colorized output. For example, Error will display the text in red.

go
func (receiver *SendEmails) Handle(ctx console.Context) error {
  ctx.Comment("This is a comment message")
  ctx.Info("This is an info message")
  ctx.Error("This is an error message")
  ctx.Line("This is a line message")
  ctx.Warning("This is a warning message")
  return nil
}

There are few helpers to write to console with respective color:

go
ctx.Green("This is a green message")
ctx.Greenln("This is a green line message")
ctx.Red("This is a red message")
ctx.Redln("This is a red line message")
ctx.Yellow("This is a yellow message")
ctx.Yellowln("This is a yellow line message")
ctx.Black("This is a black message")
ctx.Blackln("This is a black line message")

You can use the NewLine method to write a new line to the console:

go
// write single blank line
ctx.NewLine()

// write multiple blank lines
ctx.NewLine(2)

Tables

You may use the Table method to render structured data in a tabular format. The method accepts headers and rows, and writes the rendered table directly to the console:

go
func (receiver *SendEmails) Handle(ctx console.Context) error {
    headers := []string{"ID", "Email", "Status"}
    rows := [][]string{
        {"1", "a@example.com", "Queued"},
        {"2", "b@example.com", "Sent"},
    }

    ctx.Table(headers, rows)

    return nil
}

You can pass a console.TableOption as the third argument to customize borders, dimensions, and styles.

go
ctx.Table(headers, rows, console.TableOption{
    Width: 80,
})

Progress Bars

For long-running tasks, it is often helpful to provide the user with some indication of how much time the task will take. You may use the WithProgressBar method to display a progress bar.

go
items := []any{"item1", "item2", "item3"}
_, err := ctx.WithProgressBar(items, func(item any) error {
    // performTask(item)
    return nil
})

Sometimes you may need to update the progress bar manually. You can use the CreateProgressBar method to update the progress bar:

go
users := []string{"user1", "user2", "user3"}
bar := ctx.CreateProgressBar(len(users))

err := bar.Start()

for _, user := range users {
    // process user
    bar.Advance()

	// sleep for a while to simulate processing
    time.Sleep(time.Millisecond * 50)
}

err = bar.Finish()

Spinner

If you need to display a spinner while a task is running, you may use the Spinner method.

go
err := ctx.Spinner("Loading...", console.SpinnerOption{
    Action: func() error {
        // when to stop the spinner
        time.Sleep(2 * time.Second)
        return nil
    },
})

Divider

To show terminal-width divider you may use Divider method.

go
ctx.Divider()     // ----------
ctx.Divider("=>") // =>=>=>=>=>

Graceful Shutdown

By default, pressing Ctrl+C (or sending SIGTERM) cancels the console.Context passed to Handle. The framework runs Handle in a goroutine, so it returns context.Canceled as soon as the signal fires and the process exits. Commands that need to release resources — closing network listeners, draining queues, flushing buffers — can opt into a cleanup callback by implementing the optional console.Shutdownable interface.

go
type Shutdownable interface {
  Shutdown(ctx Context) error
}

When a command implements Shutdownable, the framework races Handle against the signal context. If Handle returns first, the framework then calls Shutdown with a fresh console.Context (the original is already cancelled) and a 30s budget so the command can finish any cleanup. If the signal fires first, the framework calls Shutdown with the same fresh context and 30s budget, then returns; Handle is left to run on its own in the goroutine and the process exits.

console.Context now embeds context.Context, so commands can use <-ctx.Done() directly, pass ctx to functions that expect a context.Context, and call ctx.Deadline() / ctx.Err() / ctx.Value(key) without an accessor.

go
package commands

import (
  "errors"
  "net/http"

  "github.com/goravel/framework/contracts/console"
  "github.com/goravel/framework/contracts/console/command"
)

type Serve struct {
  server *http.Server
}

func (r *Serve) Signature() string   { return "serve" }
func (r *Serve) Description() string { return "Start the HTTP server" }
func (r *Serve) Extend() command.Extend {
  return command.Extend{Category: "server"}
}

func (r *Serve) Handle(ctx console.Context) error {
  if err := r.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
    return err
  }
  return nil
}

func (r *Serve) Shutdown(ctx console.Context) error {
  ctx.Info("received signal, shutting down gracefully...")
  return r.server.Shutdown(ctx)
}

The built-in schedule:run command is a real example. Its Handle blocks on schedule.Run(), and on signal the framework calls Shutdown, which delegates to schedule.Shutdown(ctx) so scheduled tasks get a chance to finish their work.

Commands that do not implement Shutdownable keep the original behavior — the process exits as soon as the signal is received.

Category

You can set a set of commands to the same category, convenient in ./artisan list:

go
// Extend The console command extend.
func (receiver *ConsoleMakeCommand) Extend() command.Extend {
  return command.Extend{
    Category: "make",
  }
}

Programmatically Executing Commands

Sometimes you may wish to execute an Artisan command outside of the CLI, you can use the Call method on the facades.Artisan() to operate this.

go
facades.Route().Get("/", func(c *gin.Context) {
  facades.Artisan().Call("emails")
  facades.Artisan().Call("emails --lang Chinese name") // With arguments and options
})

Disabling Print Colors

Some commands print colors by default, such as the list command. However, in some terminals or logs, the color values may be garbled. You can use the --no-ansi option to disable the print colors:

shell
./artisan list --no-ansi

Released under the MIT License