A command-line interface (CLI) for managing Shlink short URLs via the REST API v3.
- Create short URLs with custom slugs/paths
- List all short URLs with automatic pagination
- Support for multiple domains with easy alias configuration
- Three-tier configuration: config file, environment variables, and CLI flags
- Smart output: plain table for terminals, JSON for pipes
- XDG Base Directory compliant
go install github.com/ginsys/goshlink/cmd/goshlink@latestgit clone https://github.com/ginsys/goshlink.git
cd goshlink
go build ./cmd/goshlinkgoshlink supports three configuration sources with the following precedence:
- CLI flags (highest priority)
- Environment variables
- Config file (lowest priority)
base_url- Shlink instance URL (scheme + host + optional subpath)api_key- Shlink API keydefault_domain- Default domain for short URLs
list_sort- Sorting for plain output:"domain,path"(default) or"path,domain"
Config file location (XDG compliant):
- Primary:
$XDG_CONFIG_HOME/goshlink/config.toml - Fallback:
~/.config/goshlink/config.toml
Example config.toml:
base_url = "https://s.example.com"
api_key = "your-api-key-here"
default_domain = "s.example.com"
list_sort = "domain,path"
[domains]
prod = "s.example.com"
work = "go.example.net"
lab = "s.home.arpa"Note: The base_url should NOT include /rest/v3. The CLI automatically appends this.
Examples:
https://s.example.com→ API base:https://s.example.com/rest/v3https://s.example.com/shlink/→ API base:https://s.example.com/shlink/rest/v3
export GOSHLINK_BASE_URL="https://s.example.com"
export GOSHLINK_API_KEY="your-api-key-here"
export GOSHLINK_DEFAULT_DOMAIN="s.example.com"
export GOSHLINK_LIST_SORT="domain,path" # optionalGlobal flags available for all commands:
--base-url- Override base URL--api-key- Override API key--default-domain- Override default domain--output- Output mode:plainorjson
Domain aliases provide a convenient shorthand for frequently used domains. Define them in the [domains] section of your config file:
[domains]
prod = "s.example.com"
work = "go.example.net"
lab = "s.home.arpa"When using the --domain flag:
- If the value contains a
.(dot), it's treated as a literal domain - Otherwise, it's looked up as an alias
Examples:
# Using alias
goshlink create --domain prod mylink https://example.com
# Using literal domain
goshlink create --domain s.example.com mylink https://example.comAll commands have short aliases for convenience:
| Command | Aliases | Usage |
|---|---|---|
create |
c |
goshlink c <path> <url> |
list |
l, ls |
goshlink l |
config |
cf |
goshlink cf |
Examples:
goshlink c mylink https://example.com # create
goshlink l # list
goshlink cf # config wizardgoshlink configInteractively create or update the configuration file. This command prompts for:
- Shlink base URL
- API key
- Default domain
- Domain aliases (optional)
Configuration is saved to ~/.config/goshlink/config.toml.
Example:
$ goshlink config
Configure goshlink
==================
Shlink base URL: https://s.example.com
API key: your-api-key-here
Default domain: s.example.com
Add/modify domain aliases? [Y/n]: y
Alias name (empty to finish): prod
Domain for 'prod': s.example.com
Add another? [y/N]: n
Configuration saved to ~/.config/goshlink/config.tomlgoshlink create <path> <long-url>Arguments:
<path>- Custom slug/path for the short URL (must not start with/)<long-url>- The destination URL
Flags:
--domain <domain-or-alias>- Domain or alias (default:default_domainfrom config)
Examples:
# Basic usage (uses default_domain)
goshlink create mylink https://example.com/very/long/url
# With domain alias
goshlink create --domain work meeting https://zoom.us/j/123456789
# With literal domain
goshlink create --domain s.example.com docs https://docs.example.com
# Path with slashes (allowed)
goshlink create reports/2024/q1 https://example.com/reports/2024-q1.pdf
# Force JSON output
goshlink create --output json mylink https://example.comgoshlink listFlags:
--domain <domain-or-alias>- Filter by domain or alias--output <mode>- Output mode:plainorjson
Examples:
# List all short URLs
goshlink list
# Filter by domain alias
goshlink list --domain prod
# Filter by literal domain
goshlink list --domain s.example.com
# Force JSON output
goshlink list --output json
# Pipe to jq for processing
goshlink list | jq '.[] | select(.domain == "s.example.com")'goshlink supports two output modes:
Default when output is a terminal (TTY). Shows a 4-column table:
DOMAIN PATH SHORT_URL LONG_URL
s.example.com mylink https://s.example.com/mylink https://example.com
Sorting is configurable via list_sort config option. Default: domain (primary) and path (secondary).
Default when output is piped or redirected. Key API response fields:
Create:
{
"shortCode": "mylink",
"shortUrl": "https://s.example.com/mylink",
"longUrl": "https://example.com",
"dateCreated": "2024-01-12T10:30:00Z",
"domain": "s.example.com",
...
}List:
[
{
"shortCode": "mylink",
"shortUrl": "https://s.example.com/mylink",
"longUrl": "https://example.com",
...
},
...
]The output mode is automatically detected:
- TTY (terminal): plain text table
- Pipe/redirect: JSON
Override with --output:
# Force plain even when piping
goshlink list --output plain | less
# Force JSON in terminal
goshlink list --output jsonOption 1: Interactive Configuration (Recommended)
Use the built-in config wizard:
goshlink configThis will interactively prompt for:
- Shlink base URL
- API key
- Default domain
- Optional domain aliases
The configuration is automatically saved to ~/.config/goshlink/config.toml.
Option 2: Manual Configuration
- Create config directory:
mkdir -p ~/.config/goshlink- Create config file:
cat > ~/.config/goshlink/config.toml <<EOF
base_url = "https://s.example.com"
api_key = "your-api-key-here"
default_domain = "s.example.com"
[domains]
prod = "s.example.com"
work = "go.example.net"
EOF- Test configuration:
goshlink listCreate and verify:
goshlink create docs https://docs.example.com
goshlink list --domain prodExport all URLs to JSON:
goshlink list > urls.jsonFind URLs matching pattern:
goshlink list | jq '.[] | select(.longUrl | contains("github"))'Create URL on specific domain:
goshlink create --domain work standup https://meet.google.com/abc-defg-hijgoshlink provides clear error messages:
Missing configuration:
Error: missing required configuration: base_url, api_key
Invalid path:
Error: path must not start with '/'
API errors:
Error: creating short URL: HTTP 400: INVALID_SLUG - The custom slug is already in use
Unknown domain alias:
Error: unknown domain alias: unknown
The project includes a Makefile for common tasks:
make help # Show available targets (default)
make test # Run all tests
make build # Build the goshlink binary with version
make clean # Remove build artifacts
make release # Trigger release workflow (requires gh CLI)
make prerelease # Trigger prerelease workflow with auto-increment (requires gh CLI)
make release-local # Test release build locally (requires goreleaser)Release targets:
make release- Triggers final release workflow for current VERSIONmake prerelease- Auto-increments RC number and triggers prerelease- First run: creates
v0.1.0-rc1 - Subsequent runs:
v0.1.0-rc2,v0.1.0-rc3, etc.
- First run: creates
make release-local- Test GoReleaser locally without publishing
The project version is managed via a VERSION file in the repository root. This file contains the semantic version number (e.g., 0.1.0).
View current version:
cat VERSIONLocal builds:
make build
./goshlink --version # Shows: 0.1.0-dev-<gitsha>Local dev builds append -dev-<gitsha> to distinguish them from release builds.
Managing versions:
The VERSION file is the single source of truth for versioning. All releases are triggered from this file.
The project uses GitHub Actions for continuous integration and releases:
- Trigger: Push to
main, pull requests, or tags - Actions:
- Runs all tests
- Builds cross-platform binaries (linux/amd64, linux/arm64, darwin/amd64, darwin/arm64)
- Version format:
<version>-<datetime>-<gitsha>(e.g.,0.1.0-20240112T153045-abc1234) - Uploads binaries as workflow artifacts
Download artifacts from the Actions tab on GitHub to test builds.
Releases are created via manual workflow dispatch in GitHub Actions.
Creating a release:
Option A: Using Make (Recommended)
# Update VERSION if needed
echo "0.2.0" > VERSION
git commit -am "Bump to 0.2.0" && git push
# Trigger release
make release # Final release
make prerelease # Prerelease with auto-increment (rc1, rc2, etc.)The make prerelease command automatically:
- Queries existing releases from GitHub
- Finds next RC number (e.g.,
rc1→rc2→rc3) - Triggers workflow with correct suffix
Option B: Manual via GitHub UI
-
Update VERSION file (if needed):
echo "0.2.0" > VERSION git commit -am "Bump to 0.2.0" git push origin main
-
Trigger release via GitHub Actions:
- Go to GitHub → Actions → Release workflow
- Click "Run workflow" button
- Optional: Enter prerelease suffix (e.g.,
-rc1,-beta) - Click "Run workflow"
Workflow automatically:
- Reads
VERSIONfile - Creates git tag (e.g.,
v0.2.0orv0.2.0-rc1) - Builds binaries for all platforms
- Creates checksums
- Generates changelog
- Publishes GitHub release with binaries
Examples:
| Action | Command | Creates |
|---|---|---|
| First prerelease | make prerelease |
v0.1.0-rc1 |
| Next prerelease | make prerelease |
v0.1.0-rc2 |
| Final release | make release |
v0.1.0 |
Local release testing:
# Test release build without publishing
make release-localgo test ./...goshlink/
├── cmd/goshlink/ # CLI entry point
│ └── main.go
├── internal/
│ ├── config/ # Configuration management
│ │ ├── config.go
│ │ └── config_test.go
│ ├── shlink/ # Shlink API client
│ │ ├── client.go
│ │ ├── client_test.go
│ │ └── types.go
│ └── output/ # Output formatting
│ └── output.go
├── go.mod
├── go.sum
└── README.md
This tool uses Shlink REST API v3. The API path /rest/v3 is automatically appended to your configured base_url.
For more information about Shlink, visit shlink.io.
Contributions welcome! Please open an issue or pull request.
goshlink is licensed under the GNU General Public License version 3 (GPL-3.0-only).
- See
LICENSEfor the full license text. - Third-party dependencies are licensed under their respective terms; see
THIRD_PARTY_NOTICES.mdand theLICENSES/directory (generated viamake licenses).