There's no registry upload step for a Go SDK — you release one by pushing a git tag matching go.mod's module path, and the Go module proxy picks it up the first time someone requests that version. That's the whole release mechanic. The part teams get wrong is versioning past v1: any breaking v2 release needs a /v2 suffix appended to the module path itself, in both go.mod and every import statement, because Go's dependency resolution treats a major-version bump as a different module, not a newer version of the same one.
How a Go release actually works
Per go.dev's publishing guide, the sequence is:
go mod tidy
go test ./...
git commit -m "mymodule: changes for v0.1.0"
git tag v0.1.0
git push origin v0.1.0
That's it — pushing the tag is the release. There's no separate "publish" command the way npm publish or cargo publish works. The first time anyone runs go get example.com/mymodule@v0.1.0, or you explicitly prime it:
GOPROXY=proxy.golang.org go list -m example.com/mymodule@v0.1.0
the module proxy fetches that tag from your repository, caches it, and serves it to every future request. Your module's import path (example.com/mymodule) has to match where the repo actually lives, since go get resolves it directly from that path.
Tags are immutable — don't fix a bad release by re-tagging
Once a version is fetched by anyone, Go's tooling records a checksum and checks every future download against it. If you delete and re-push a tag with different content at the same version number, clients that already fetched the original will get a security error on the mismatch, and clients fetching fresh will get whatever's now at that tag — a split-brain state across your users. Never retag a version that might have been fetched. If a release is broken, publish a new version instead.
Retracting a bad version
If a version genuinely shouldn't be used — it has a security issue, or you tagged it before you meant to — use Go's retract directive in go.mod instead of deleting anything:
module example.com/mymodule
go 1.23
retract v1.2.0 // published with a broken auth header
A retraction doesn't remove the version from the proxy cache or break existing users pinned to it — it tells go get and go mod tidy not to select that version for new or updated dependencies, and surfaces a warning to anyone who has it pinned. This is the correct fix for "we shipped a bad tag," not deleting the tag.
Going to v2: the part that actually differs from v1
Go's module system treats major version 2 and above as a structurally different module from v1 — this is deliberate, per go.dev's major version guide: a user has to actively opt into a v2 upgrade by changing their import path, rather than getting breaking changes silently through a routine go get -u.
What changes:
go.mod's module line gets a /v2 suffix:
// v1
module example.com/mymodule
// v2
module example.com/mymodule/v2
Every import in your own code updates to match:
// v1
import "example.com/mymodule/package1"
// v2
import "example.com/mymodule/v2/package1"
Your users' import paths change too — this is the part that makes v2 a real migration for consumers, not just a version bump. go get example.com/mymodule/v2 pulls in a module that Go's resolver considers entirely separate from v1, which means:
- You can keep publishing v1 patches and v2 releases from the same repository, on separate branches, indefinitely, if you need to support both.
- Nothing about a v2 release automatically migrates a v1 user — they have to change their import statements and re-test, same as adopting a new dependency.
- Tooling and CI that assumes "the module path" is a stable string need to account for the fact that it now has a version segment baked in.
Repository layout for v2
The common pattern, per go.dev's guide: branch from your latest v1 release, then update the module path in that branch:
git checkout -b v2
# edit go.mod: module example.com/mymodule/v2
# update every internal import to the new path
Some teams instead put v2+ code in a /v2 subdirectory on the main branch rather than a separate branch — both are valid layouts; go.dev doesn't mandate one, and the choice mostly comes down to whether you expect to actively maintain both major versions in parallel.
A release checklist for a generated Go SDK
- Run
go mod tidyandgo vet ./...against the freshly generated code — a generator can produce code that builds but has vet warnings worth catching before a tag goes out. - Confirm
go.mod's module path matches the exact repository path your users willgo getfrom. - Tag with a SemVer-correct version — a breaking change in this release means the next version needs the
/v2treatment, not just a major-number bump in the tag alone. - Push the tag, then
go list -m yourmodule@vX.Y.Zagainstproxy.golang.orgto confirm the proxy picked it up. - If this release is a breaking v2, update the module path in
go.modand every internal import first — do this as its own commit before the tag, so the tagged commit is self-consistent. - Never delete or retag a pushed version. If something's wrong, ship a new version or use
retract.
Where a generated Go SDK fits
This post covers releasing a Go SDK you already have. If you're generating one from an OpenAPI spec, Generate a Go SDK from OpenAPI compares oapi-codegen, ogen, and OpenAPI Generator's go target, with oapi-codegen as the default recommendation. Sourced also generates a preview Go SDK directly — a Scale-plan feature: add Go in your project's language picker and Sourced runs the generation and packaging step, no local Go toolchain required. What Sourced doesn't do yet is the tag-and-push release step above — that's manual for the Go preview, same as for a Go client generated any other way, until registry publish automation ships for preview languages. TypeScript and Python remain the two fully managed languages with automated publishing today.
If you're tracking which SDK version your customers are actually running after a v2 release, see Track SDK versions in API logs after leaving Stainless — the same header-based telemetry approach applies to any generator, not just a Stainless migration; it's exactly the visibility you want before deciding whether a v1 deprecation is safe.
What to do this week
- If you haven't generated a Go SDK yet, start with the Go generation guide or add Go as a Sourced preview target.
- Confirm your
go.modmodule path exactly matches your repository's import path before your first tag — mismatches here are the most common first-release mistake. - If your next release has any breaking change, plan the
/v2module-path migration as its own step, not an afterthought after the tag is already pushed. - Validate the OpenAPI spec behind the SDK with the OpenAPI validator before regenerating — a spec issue that breaks Go codegen breaks every other generated output from the same spec too.
FAQ
Do I need to register my Go module anywhere before releasing it?
No. There's no account or registration step — pushing a correctly formatted git tag to a public repository is the entire release mechanism. The module proxy discovers your module the first time anyone requests it.
What happens if I delete a git tag after someone has already fetched it?
Go's tooling checksums modules on first fetch and verifies future downloads against that checksum. Deleting and re-pushing a tag with different content causes a checksum mismatch for anyone re-fetching, surfaced as a security error — never do this. Publish a new version instead.
Do I really need the /v2 suffix, or is a new git tag like v2.0.0 enough?
The tag alone isn't enough. Per go.dev's major version guide, Go's resolver identifies a module by its module path, and that path must include /v2 (or /v3, etc.) for any major version 2 or higher — without the path suffix, tooling won't correctly resolve the major version boundary, regardless of what the git tag says.
Can I maintain v1 and v2 of my Go SDK at the same time?
Yes — since Go treats them as separate modules, you can keep releasing v1 patches from one branch (or directory) and v2 releases from another, indefinitely, as long as you're willing to maintain both.
Does Sourced release my generated Go SDK's git tags for me?
Not yet. Sourced generates the Go SDK as a Scale-plan preview download; the tag-and-push release process in this post is a step you run yourself. TypeScript and Python get fully managed, automated publishing today.
Create hosted docs from your repo or start free — unlimited OpenAPI previews, no credit card required, and (on the Scale plan) a Go SDK preview generated from the same spec.