A golang interactive cli usually combines flags, environment variables (see where to set environment variables on Linux), and a golang prompt for input at runtime. This page walks through a minimal go prompt with bufio, a golang.org/x/term ReadPassword example (the common web search is term.ReadPassword(int(syscall.Stdin)), but int(os.Stdin.Fd()) is the clearer, portable spell), then promptui golang / go promptui for validation and selects, survey for multi-question flows, and the readline-style library c-bata/go-prompt when you need completions. For more stdin-only patterns, see go-prompt on this site.
Checked with Go 1.24 on 64-bit Linux (Fedora and Ubuntu family). Password echo requires a real terminal.
Golang prompt for input with the standard library
Line-based reading from stdin
bufio.Reader.ReadString stops at a delimiter such as newline and is enough for many CLIs:
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func inputPrompt(label string) (string, error) {
fmt.Fprint(os.Stderr, label+" ")
s, err := bufio.NewReader(os.Stdin).ReadString('\n')
if err != nil {
return "", err
}
return strings.TrimSpace(s), nil
}
func main() {
s, err := inputPrompt("Enter your message:")
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
fmt.Printf("Your message: %s\n", s)
}Run it locally in a terminal; type a line and press Enter. The program echoes your trimmed text on stdout.
golang.org/x/term ReadPassword for hidden input
term.ReadPassword disables local echo while the user types. Install the module in your own project:
go get golang.org/x/term@latestUse the file descriptor from os.Stdin rather than hard-coding syscall.Stdin (some search snippets write term.ReadPassword(int(syscall.Stdin)); on Linux that is often still fd 0, but os.Stdin.Fd() matches official examples and behaves correctly when stdin is a terminal):
package main
import (
"fmt"
"os"
"golang.org/x/term"
)
func readSecret(label string) (string, error) {
fmt.Fprint(os.Stderr, label+" ")
pw, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return "", err
}
fmt.Fprintln(os.Stderr)
return string(pw), nil
}
func main() {
s, err := readSecret("Password:")
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
fmt.Printf("Length: %d\n", len(s))
}Run in a real TTY; you should see the label on stderr, no visible characters while typing, then a line reporting the password length. In pipes or non-interactive jobs, expect an error instead of silent failure.
promptui golang: validation and select menus
promptui (module path github.com/manifoldco/promptui) covers labeled prompts, optional validation, and arrow-key selects—common when people search golang promptui or go promptui.
go get github.com/manifoldco/promptui@v0.9.0Validated float input:
package main
import (
"errors"
"fmt"
"strconv"
"github.com/manifoldco/promptui"
)
func main() {
validate := func(input string) error {
if _, err := strconv.ParseFloat(input, 64); err != nil {
return errors.New("enter a float")
}
return nil
}
p := promptui.Prompt{Label: "Enter a float", Validate: validate}
out, err := p.Run()
if err != nil {
fmt.Println("cancelled:", err)
return
}
fmt.Println("got:", out)
}Interactive select:
package main
import (
"fmt"
"github.com/manifoldco/promptui"
)
func main() {
sel := promptui.Select{
Label: "Pick one",
Items: []string{"alpha", "beta", "gamma"},
}
_, choice, err := sel.Run()
if err != nil {
fmt.Println("cancelled:", err)
return
}
fmt.Println("picked:", choice)
}Survey for multi-question CLIs
survey chains inputs and multi-selects. Use github.com/AlecAivazis/survey/v2. For MultiSelect, Default must be a []string or []int, not a bare string.
go get github.com/AlecAivazis/survey/v2@v2.3.7package main
import (
"fmt"
"strings"
"github.com/AlecAivazis/survey/v2"
)
func main() {
qs := []*survey.Question{
{Name: "name", Prompt: &survey.Input{Message: "Name?"}, Validate: survey.Required},
{
Name: "pet",
Prompt: &survey.MultiSelect{
Message: "Pets:",
Options: []string{"dogs", "cats", "birds"},
Default: []string{"dogs"},
},
},
}
var ans struct {
Name string
Pet []string `survey:"pet"`
}
if err := survey.Ask(qs, &ans); err != nil {
fmt.Println(err)
return
}
fmt.Printf("%s likes %s.\n", ans.Name, strings.Join(ans.Pet, ", "))
}go-prompt (c-bata) for completion-heavy CLIs
When you need a REPL with suggestions and not just a one-off question, github.com/c-bata/go-prompt is the project many users mean by go-prompt. It is heavier than promptui; pair it with your own command routing.
Summary
Building a golang interactive cli starts with stdin and flags, then layers golang prompt UX: golang.org/x/term ReadPassword with int(os.Stdin.Fd()) for hidden secrets (not pipes), promptui golang for quick validated prompts and selects, survey for multi-step forms, and c-bata go-prompt when completions drive the experience. Match the library to the interaction model, and always handle non-TTY environments where interactive prompts cannot run.

