name: kongctl-extension-builder description: Scaffold and maintain kongctl CLI extensions. Use when a user wants to create a script or Go extension, define extension command paths, use the kongctl Go SDK helper, test install/link workflows, or debug extension context handling. license: Apache-2.0 metadata: product: kongctl category: extensions
kongctl extension builder
Goal
Help users create a runnable kongctl CLI extension with a valid
kongctl-extension.yaml, an executable runtime, and local and remote test
workflows.
Core Rules
- Extensions run as separate processes; do not import
internal/...packages fromkongctl. - The manifest file must be named
kongctl-extension.yaml. - Required manifest fields are:
schema_version: 1publishernameruntime.command- at least one
command_paths[].path
- Extension IDs use
publisher/name, and both segments should be lowercase path-safe identifiers. - v1 command paths may contribute below
getorlist, or define a custom root verb that does not collide with built-ins. - Built-in root segments such as
getandlistcannot declare aliases. runtime.commandmust be relative to the extension root and already executable.kongctldoes not compile source during install.versioninkongctl-extension.yamlis optional. For remotely installed extensions, prefer Git tags/releases as the package version source; kongctl displays the release tag, ref, or short commit when the manifest omits a version.- GitHub repository names do not need a
kongctl-*prefix. Use names that are clear for humans and keep the manifest ID inpublisher/nameform. - Extension-specific args and flags should be shown directly in examples, such
as
kongctl get foo --limit 10. Use--only as an escape hatch when an extension needs to receive a token that collides with a hostkongctlflag, such as--output,--profile,--help, or their shorthands. - For Go extensions, prefer
github.com/kong/kongctl/pkg/sdkover hand-rolled context parsing. Use it to load the runtime context, create an authenticatedsdk-konnect-goclient, run reentrantkongctlcommands, and render output with parent--outputand--jqsettings. - Do not make Go extensions call
kongctl apias the primary Konnect access path when typed SDK access is appropriate. Use reentrantkongctlonly for script extensions, debugging, or commands that are easier to delegate back to the host. - Put extension diagnostics on stderr when stdout needs to preserve structured
output from a reentrant
kongctlcommand.
Workflow
- Choose a runtime style:
- shell script for small wrappers
- Go binary for richer parsing or reusable logic
- Create
kongctl-extension.yamlwith one or more command paths. - Add the runtime file named by
runtime.command. - Make script runtimes executable with
chmod +x. - Test with a development link:
kongctl link extension <extension-dir> kongctl get extension <publisher>/<name> kongctl get <resource> - Use local install when testing managed package copying:
kongctl install extension <extension-dir> kongctl list extensions kongctl uninstall extension <publisher>/<name> - Prepare remote repositories so install can consume release archives:
- release archives are preferred over source clone fallback
kongctl-extension.yamlmust be at the archive rootruntime.commandpoints to a runnable file inside the archive- publish one archive asset per release, or include the target platform in each platform-specific asset name
- use
.tar.gzfor Go binary releases because it preserves executable bits reliably
kongctl install extension <owner>/<repo> kongctl install extension <owner>/<repo> --ref <branch-or-tag> kongctl install extension <owner>/<repo>@<tag> kongctl install extension <owner>/<repo>@<tag> --yes kongctl upgrade extension <publisher>/<name> kongctl upgrade extension <owner>/<repo> kongctl upgrade extension <publisher>/<name>@<tag-or-version> --yes kongctl upgrade extension kongctl upgrade extensions
When no compatible release archive exists, kongctl install extension <owner>/<repo> falls back to cloning the repository. Source fallback is only
valid when the repository root already contains kongctl-extension.yaml and an
already-runnable script or binary referenced by runtime.command.
For release-artifact installs, kongctl upgrade extension <publisher>/<name>
or kongctl upgrade extension <owner>/<repo> selects the latest compatible
GitHub release asset from the originally recorded repository. Add
@<tag-or-version> to pin the upgrade target, for example
kongctl upgrade extension kong/debug@0.2.0 or
kongctl upgrade extension kong/kongctl-ext-debug@0.2.0. A bare semantic
version tries the exact release tag first and then a v-prefixed tag.
Source-clone installs require an explicit @<tag|ref|commit> target because
there is no stable "latest" release asset to resolve.
kongctl upgrade extension and kongctl upgrade extensions without a selector
upgrade all installed GitHub release-asset extensions. Linked extensions, local
path installs, and GitHub source-clone installs are skipped in batch mode.
Remote installs show a trust warning prompt with the selected source, extension
name, asset or ref, executable, command paths, and package/manifest/executable
hashes. Use --yes for automated tests or release workflow verification.
Minimal Manifest
schema_version: 1
publisher: kong
name: foo
runtime:
command: kongctl-ext-foo
command_paths:
- path:
- name: get
- name: foo
aliases: [foos]
Script Runtime Pattern
Use shell for lightweight wrappers and debugging helpers.
#!/bin/sh
set -eu
echo "context_path=${KONGCTL_EXTENSION_CONTEXT:-}" >&2
kongctl_bin="kongctl"
if [ -n "${KONGCTL_EXTENSION_CONTEXT:-}" ] &&
[ -r "$KONGCTL_EXTENSION_CONTEXT" ]; then
kongctl_bin=$(sed -n \
's/^[[:space:]]*"kongctl_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
"$KONGCTL_EXTENSION_CONTEXT")
fi
"$kongctl_bin" api get /v2/control-planes
Go Runtime Pattern
Use Go when argument parsing, typed Konnect access, JSON handling, or richer
errors matter. Import the public kongctl SDK helper, never
github.com/kong/kongctl/internal/....
import "github.com/kong/kongctl/pkg/sdk"
pkg/sdk provides:
sdk.LoadRuntimeContextFromEnv()runtimeCtx.Args()for extension-specific args after host flags are removedruntimeCtx.DataDir()for extension-owned persistent filesruntimeCtx.KonnectSDK(ctx)for an authenticatedsdk-konnect-goclientruntimeCtx.Output().Render(display, raw)for native output and jq behaviorruntimeCtx.RunKongctl(ctx, args...)for deliberate reentrant host calls
Use this shape for Go runtimes:
func run() error {
runtimeCtx, err := sdk.LoadRuntimeContextFromEnv()
if err != nil {
return err
}
ctx := context.Background()
konnect, err := runtimeCtx.KonnectSDK(ctx)
if err != nil {
return fmt.Errorf("create Konnect SDK client: %w", err)
}
res, err := konnect.Me.GetUsersMe(ctx)
if err != nil {
return fmt.Errorf("get current user: %w", err)
}
display := map[string]any{
"profile": runtimeCtx.Resolved.Profile,
"user": res.GetUser(),
}
return runtimeCtx.Output().Render(display, res.GetUser())
}
For Render(display, raw), text output uses display; JSON/YAML and jq use
raw. When raw is omitted, the display value is used for all formats. Keep
stdout for command data and send diagnostics to stderr.
For a dedicated Go extension repository, use a normal module:
go mod init github.com/<owner>/<repo>
go get github.com/kong/kongctl@<released-version>
go mod tidy
While developing against an unreleased local kongctl checkout, use a temporary replace directive:
replace github.com/kong/kongctl => /path/to/kongctl
Remove local replace directives before publishing release artifacts unless the
repository intentionally vendors or pins an internal development checkout.
Commit go.sum for Go extension repositories.
Release Artifact Workflow
For GitHub installs, prefer a release artifact that extracts to this shape:
kongctl-extension.yaml
bin/kongctl-ext-foo
README.md
With:
runtime:
command: bin/kongctl-ext-foo
For a Go extension, add a workflow like this and adjust the binary package path and artifact names for the repository:
name: release
on:
push:
tags:
- "v*"
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- goos: linux
goarch: amd64
- goos: darwin
goarch: arm64
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- run: mkdir -p dist/package/bin
- run: |
CGO_ENABLED=0 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} \
go build -o dist/package/bin/kongctl-ext-foo ./cmd/kongctl-ext-foo
cp kongctl-extension.yaml README.md dist/package/
chmod +x dist/package/bin/kongctl-ext-foo
archive="dist/kongctl-ext-foo-${{ matrix.goos }}-"
archive="${archive}${{ matrix.goarch }}.tar.gz"
tar -C dist/package -czf "$archive" \
kongctl-extension.yaml README.md bin/kongctl-ext-foo
- uses: softprops/action-gh-release@v2
with:
files: dist/*.tar.gz
For a script extension, publish a single universal archive:
mkdir -p dist/package/bin
cp kongctl-extension.yaml README.md dist/package/
cp kongctl-ext-foo dist/package/bin/
chmod +x dist/package/bin/kongctl-ext-foo
tar -C dist/package -czf dist/kongctl-ext-foo-universal.tar.gz \
kongctl-extension.yaml README.md bin/kongctl-ext-foo
The installer accepts .tar.gz, .tgz, and .zip assets. If a release has
more than one archive asset, platform-specific assets should include the current
GOOS and GOARCH in the name, such as
kongctl-ext-foo-linux-amd64.tar.gz.
Runtime Context
When an extension runs, kongctl sets KONGCTL_EXTENSION_CONTEXT to a
generated context.json file. Read this file for:
- matched command path
- remaining extension arguments
- selected profile
- resolved base URL
- output, jq, color theme, and log settings
- extension data directory
- host
kongctlpath and version
Never expect secrets in context.json. Go extensions should use
github.com/kong/kongctl/pkg/sdk when they need a configured Konnect client or
native output rendering. Script extensions can invoke kongctl api or other
built-in commands as subprocesses when they need host-authenticated Konnect
calls. Child kongctl commands inherit the parent extension context unless
they explicitly override flags such as --profile, --output, or
--base-url.
Good Educational Extension Ideas
- Debug context: print
context.json, remaining args, and reentrant command behavior. - Who am I: call
kongctl get meand render the current user in the parent output format. - Go current user: use
pkg/sdkto create a Konnect client and rendersdk-konnect-goresults with parent output settings. - Control plane summary: call
kongctl apito list control planes and print a compact operational summary. - Portal/API report: combine a few
kongctl apicalls into a read-only report that would be awkward as a one-liner. - Declarative helper: wrap existing
kongctl plan,diff, orapplycommand sequences for a team-specific workflow.
Prefer read-only examples for public educational repositories. They are easier to try safely and demonstrate auth/context reuse without destructive setup.
Examples
- Script extension:
docs/examples/extensions/script - Go extension:
docs/examples/extensions/go
The Go example must be built before linking because install and link expect
runtime.command to point to an existing executable.