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) ormilo-<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 theirPATH. - 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
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.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.A minimal plugin
The following is a complete plugin grounded in the reference example. It serves its manifest, wires the root command, and adds azones 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:
| Field | Environment variable | Description |
|---|---|---|
Org | DATUM_ORG | Current organization slug. |
Project | DATUM_PROJECT | Current project slug (may be empty). |
APIHost | DATUM_API_HOST | API base URL, e.g. api.datum.net. |
PluginAPIVersion | DATUM_PLUGIN_API_VERSION | Plugin API version the host declares. |
CredentialsHelper | DATUM_CREDENTIALS_HELPER | Absolute path to the datumctl binary. |
Session | DATUM_SESSION | Active session name (may be empty). |
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
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 withdatumctl 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:checksums.txt alongside your release archives, in goreleaser’s default two-column format.
Next steps
- Publishing catalogs — list your plugin so users install it by name
- Using plugins — how users install and run what you build