# How Gitea Organizes Its Command Structure: A Deep Dive into the CLI Architecture

> Explore Gitea's command structure, organized hierarchically using urfave/cli v3. Learn how sub-commands like web, serv, and admin function within the CLI architecture.

- Repository: [Gitea/gitea](https://github.com/go-gitea/gitea)
- Tags: deep-dive
- Published: 2026-03-07

---

**Gitea uses the urfave/cli v3 framework to organize commands in a hierarchical structure where the root command in [`cmd/main.go`](https://github.com/go-gitea/gitea/blob/main/cmd/main.go) registers sub-commands like `web`, `serv`, and `admin`, with `web` set as the default when no arguments are provided.**

The command-line interface in the **go-gitea/gitea** repository follows a clean, modular architecture built on the **urfave/cli v3** framework. Understanding how Gitea organizes its command structure reveals a three-tier system: global flag definitions in the root command, standalone sub-command objects for each feature, and centralized registration logic that wires everything together.

## The Root Command Architecture (`NewMainApp`)

All CLI logic originates in [`cmd/main.go`](https://github.com/go-gitea/gitea/blob/main/cmd/main.go), where the `NewMainApp` function constructs the root `*cli.Command` value. This root command serves as the container for global configuration and the entry point for all sub-commands.

The root command defines several critical properties:

- **Global Flags**: `--work-path`, `-c` (config file), and `--custom-path` are available to all sub-commands
- **Default Command**: `app.DefaultCommand = CmdWeb.Name` ensures running `gitea` without arguments starts the web server
- **Before Hook**: `PrepareConsoleLoggerLevel(log.INFO)` initializes the logger before any sub-command executes

Registration happens in two distinct slices:

```go
// Commands that require a config file
subCmdWithConfig := []*cli.Command{
    CmdWeb, CmdServ, CmdHook, CmdAdmin, /* ... */
}

// Standalone commands that don't need configuration
subCmdStandalone := []*cli.Command{
    cmdConfig(), cmdCert(), CmdGenerate, CmdDocs,
}

app.Commands = append(app.Commands, subCmdWithConfig...)
app.Commands = append(app.Commands, subCmdStandalone...)

```

*Source:* [[`cmd/main.go`](https://github.com/go-gitea/gitea/blob/main/cmd/main.go)](https://github.com/go-gitea/gitea/blob/main/cmd/main.go)

## Sub-Command Organization

Each major feature in Gitea implements its own `*cli.Command` value with dedicated `Name`, `Usage`, `Flags`, `Before`, and `Action` fields.

### Web Server Command (`web`)

The `CmdWeb` variable in [`cmd/web.go`](https://github.com/go-gitea/gitea/blob/main/cmd/web.go) defines the primary entry point for running Gitea as an HTTP server:

```go
var CmdWeb = &cli.Command{
    Name:  "web",
    Usage: "Start Gitea web server",
    Before: PrepareConsoleLoggerLevel(log.INFO),
    Action: runWeb,
    Flags: []cli.Flag{
        &cli.StringFlag{Name: "port", Aliases: []string{"p"}, Value: "3000"},
        &cli.BoolFlag{Name: "quiet", Aliases: []string{"q"}},
        &cli.BoolFlag{Name: "verbose"},
    },
}

```

*Source:* [[`cmd/web.go`](https://github.com/go-gitea/gitea/blob/main/cmd/web.go)](https://github.com/go-gitea/gitea/blob/main/cmd/web.go)

### SSH Servant Command (`serv`)

The internal SSH handler uses `CmdServ`, which is marked as **Hidden** because it should only be invoked by the SSH daemon, not end users:

```go
var CmdServ = &cli.Command{
    Name:        "serv",
    Usage:       "(internal) Should only be called by SSH shell",
    Hidden:      true,
    Before:      PrepareConsoleLoggerLevel(log.FATAL),
    Action:      runServ,
    Flags: []cli.Flag{
        &cli.BoolFlag{Name: "enable-pprof"},
        &cli.BoolFlag{Name: "debug"},
    },
}

```

*Source:* [[`cmd/serv.go`](https://github.com/go-gitea/gitea/blob/main/cmd/serv.go)](https://github.com/go-gitea/gitea/blob/main/cmd/serv.go)

### Admin Command with Nested Sub-Commands

The `admin` command demonstrates hierarchical organization through its `Commands` slice, allowing syntax like `gitea admin user create`:

```go
var CmdAdmin = &cli.Command{
    Name:  "admin",
    Usage: "Perform common administrative operations",
    Commands: []*cli.Command{
        subcmdUser,
        subcmdRepoSyncReleases,
        subcmdRegenerate,
        subcmdAuth,
        subcmdSendMail,
    },
}

```

Each nested command (e.g., `subcmdUser`) follows the same `*cli.Command` pattern with its own `Action` implementation.

*Source:* [[`cmd/admin.go`](https://github.com/go-gitea/gitea/blob/main/cmd/admin.go)](https://github.com/go-gitea/gitea/blob/main/cmd/admin.go)

## Shared Helper Utilities

Common functionality lives in [`cmd/cmd.go`](https://github.com/go-gitea/gitea/blob/main/cmd/cmd.go) to prevent duplication across the command structure:

- **`PrepareConsoleLoggerLevel`** – Determines log level based on `--quiet` or `--debug` flags
- **`prepareWorkPathAndCustomConf`** – Extracts global flags and initializes the `setting` package
- **`argsSet`** and **`confirm`** – Validation helpers for interactive CLI operations

*Source:* [[`cmd/cmd.go`](https://github.com/go-gitea/gitea/blob/main/cmd/cmd.go)](https://github.com/go-gitea/gitea/blob/main/cmd/cmd.go)

## Execution Flow from Main to Action

The runtime path through Gitea's command structure follows a precise sequence:

1. **`main()`** (repository root) calls `cmd.NewMainApp()` to build the command tree, then invokes `cmd.RunMainApp(app, os.Args...)`
2. **urfave/cli** parses arguments, executes the root command's `Before` hook (logger setup), and identifies the target sub-command (or falls back to the default `web`)
3. The selected sub-command's `Before` hook runs (if defined), followed by its `Action` function (`runWeb`, `runServ`, `runAdmin`, etc.) to execute feature-specific logic

## Adding Custom Commands to Gitea

Extending the CLI requires creating a new `*cli.Command` value and registering it in `NewMainApp`:

```go
// cmd/example.go
package cmd

import "github.com/urfave/cli/v3"

var CmdExample = &cli.Command{
    Name:  "example",
    Usage: "Demonstrates a custom sub-command",
    Flags: []cli.Flag{
        &cli.StringFlag{Name: "msg", Value: "hello"},
    },
    Action: runExample,
}

func runExample(ctx context.Context, c *cli.Command) error {
    println("Message:", c.String("msg"))
    return nil
}

```

Register in [`cmd/main.go`](https://github.com/go-gitea/gitea/blob/main/cmd/main.go):

```go
subCmdWithConfig = append(subCmdWithConfig, CmdExample)

```

Now `gitea example --msg "custom greeting"` executes the new logic.

## Summary

- **Framework**: Gitea builds its command structure on **urfave/cli v3**, using `*cli.Command` objects for every feature
- **Root Command**: `NewMainApp` in [`cmd/main.go`](https://github.com/go-gitea/gitea/blob/main/cmd/main.go) defines global flags, sets `web` as the default command, and registers all sub-commands
- **Two Categories**: Commands are split between `subCmdWithConfig` (requires settings) and `subCmdStandalone` (independent utilities)
- **Hierarchical Nesting**: The `admin` command demonstrates nested sub-commands for organizing related operations
- **Shared Logic**: [`cmd/cmd.go`](https://github.com/go-gitea/gitea/blob/main/cmd/cmd.go) provides common helpers for logging setup and configuration path handling
- **Execution**: The flow moves from `main()` → root `Before` → sub-command `Before` → sub-command `Action`

## Frequently Asked Questions

### What CLI framework does Gitea use for its command structure?

Gitea uses the **urfave/cli v3** framework to define its command structure. According to the go-gitea/gitea source code, every command is implemented as a `*cli.Command` value with standardized fields for `Name`, `Usage`, `Flags`, `Before`, and `Action` hooks.

### How does Gitea handle the default command when no arguments are provided?

When you run `gitea` without specifying a sub-command, the executable defaults to the `web` command. This is configured in [`cmd/main.go`](https://github.com/go-gitea/gitea/blob/main/cmd/main.go) via `app.DefaultCommand = CmdWeb.Name`, which ensures the HTTP server starts automatically without requiring users to type `gitea web` explicitly.

### What is the difference between subCmdWithConfig and subCmdStandalone?

**`subCmdWithConfig`** contains commands like `web`, `serv`, and `admin` that require the Gitea configuration file and database connection to function. **`subCmdStandalone`** includes utilities like `cert`, `generate`, and `docs` that operate independently without loading the full application settings. This separation allows certain CLI tools to run faster and without environment dependencies.

### Where are shared CLI helpers defined in the Gitea codebase?

Shared utilities for the command structure are located in [`cmd/cmd.go`](https://github.com/go-gitea/gitea/blob/main/cmd/cmd.go). This file contains `PrepareConsoleLoggerLevel` for setting up logging, `prepareWorkPathAndCustomConf` for handling global flags like `--work-path`, and validation helpers like `argsSet` that are reused across multiple sub-commands in the `cmd` package.