Skip to main content
A datumctl plugin is a standalone binary that datumctl runs on your users’ behalf. When a user runs datumctl dns zones list, datumctl finds your datumctl-dns binary, injects the current context and a credentials helper, and hands off to it. This guide walks through building a plugin in Go with the official SDK, go.datum.net/datumctl/plugin.
The SDK reads only environment variables and execs subprocesses, so your plugin depends on it without pulling in any datumctl internals. A worked reference plugin lives at examples/plugin-dns in the datumctl repository.

The model

  • A plugin is a binary named datumctl-<name> (for datumctl-native plugins) or milo-<name> (for portable milo-os platform plugins).
  • Users install it with datumctl plugin install, which places it in the managed plugins directory (~/.datumctl/plugins/ by default), or it can live on their PATH.
  • Users then run it as datumctl <name> — datumctl forwards the arguments, injects context and credentials, and forwards shell completion.
  • Before running a managed plugin, datumctl verifies its SHA256 fingerprint every time.

Building your plugin

1

Add the SDK dependency

2

Declare a manifest and serve it

Call plugin.ServeManifest at the very top of main(). datumctl invokes your binary with --plugin-manifest to read its metadata; ServeManifest handles that protocol and exits before your command logic runs.
3

Build a root command with pre-wired flags

plugin.NewRootCmd returns a Cobra command with --org, --project, and --output flags already wired to the values datumctl injects.
4

Read context and fetch a token

plugin.Context() reads the injected DATUM_* environment, and plugin.Token() calls the datumctl credentials helper for a fresh, short-lived access token. Call Token() immediately before each API request.

A minimal plugin

The following is a complete plugin grounded in the reference example. It serves its manifest, wires the root command, and adds a zones list subcommand that acquires a token and calls the Datum Cloud API:

The injected context

plugin.Context() returns the values datumctl sets before running your plugin — these come from the user’s active context: plugin.Token() runs the credentials helper ($DATUM_CREDENTIALS_HELPER auth get-token, adding --session when a session is active) and returns the token. Because tokens are short-lived, fetch one immediately before each request rather than caching it.

Building and testing locally

Shell completion is automatic: datumctl forwards completion requests to your binary, so your Cobra command’s built-in completion works when users run datumctl dns <TAB>. For flag-first commands, wrap your completion function with plugin.WithFlagCompletion to surface flags on a bare <TAB>.

Trust and verification for authors

datumctl treats your plugin as untrusted code, which shapes how you distribute it:
  • Ship checksums. Whether users install from a catalog or directly from a GitHub release, datumctl verifies a SHA256 checksum before running your binary. Publish accurate checksums for every release archive.
  • Managed installs are fingerprinted. After install, datumctl records the binary’s hash and re-verifies it on every run. Rebuilding or replacing the binary in place will cause datumctl to refuse it until it’s reinstalled.
  • A binary a user drops on their PATH is blocked by default. datumctl will not run an unmanaged datumctl-<name> binary until the user explicitly trusts it with datumctl plugin trust <name>.

Getting your plugin into a catalog

Once your plugin is released with archives and checksums, list it in a catalog so users can install it by name. See Publishing catalogs for the manifest format, validation, and hosting. Users can always install directly from a GitHub release without a catalog:
This path requires a checksums.txt alongside your release archives, in goreleaser’s default two-column format.

Next steps

Last modified on July 6, 2026