This guide enumerates every code change a v4 caller needs to make to move to the v5 API surface. The package name is identical (opensearchapi); the import path changes from /v4 to /v5, so most call sites only need the new import plus a handful of surface tweaks documented below.
For runtime semantics (partial-failure errors, default Router) see README.md. For the version-history rationale see ../UPGRADING.md. For best-practices guidance see ../guides/usage-error_handling.md.
Most of the changes below are applied for you by osapilint, the API-shape migration tool in this repository. It is a separate Go module. Build the binary from an opensearch-go checkout, then run it against your module:
# Build the tool from an opensearch-go checkout.
git clone https://github.com/opensearch-project/opensearch-go
(cd opensearch-go/cmd/osapilint && go build -o "$(go env GOPATH)/bin/osapilint" .)
# From your module root. Source (v4) is auto-detected from imports; target defaults to v5.
osapilint rewrite ./... # dry run: review the intended edits first
osapilint rewrite -w ./... # then apply
# Bump the dependency and build.
go get github.com/opensearch-project/opensearch-go/v5 && go build ./...
# Catch runtime-only hazards the compiler misses (e.g. testify Equal on now-typed fields).
osapilint vet -fix ./...rewrite performs the import-path bump, type renames (e.g. DocumentGetReq -> GetReq), field renames (e.g. DocumentID -> ID, Timeout -> TimedOut), and value-to-pointer adjustments. Run without -w first for a dry-run preview. It prints the behavioral follow-ups it cannot make mechanically (see "Manual follow-ups" below), and fails loudly rather than guess if it encounters an unclassified field change. The sections below document the full set of changes so you can review the tool's output or migrate by hand.
In v5 the opensearchapi package is code-generated from the OpenSearch API specification, replacing the hand-written v4 package. The same surface shipped inside the v4 module at v5preview/opensearchapi/ for early adopters; v5 promotes it to the module root.
The renames in this guide (DocumentID -> ID, optional Params becoming *Params, partial-failure type renames) are unfortunate but unavoidable: this is the one-time cost of switching opensearchapi from hand-written types to a code-generated client sourced from the OpenSearch API specification.
We're sorry for the churn. The trade is that future spec evolutions arrive as additive types and methods rather than coordinated rename pulls, the surface stays in lockstep with the server, and bug fixes flow through the spec instead of being re-translated by hand. After this conversion, you should not see another wave of renames at this scale.
// v4
import "github.com/opensearch-project/opensearch-go/v4/opensearchapi"
// v5
import "github.com/opensearch-project/opensearch-go/v5/opensearchapi"The package qualifier (opensearchapi.X) does not change. The only edit per file is replacing v4 with v5 in the import path.
Most body-bearing operations now expose a typed Body field (e.g. *SearchBody) alongside a BodyReader io.Reader escape hatch: build a typed body with Body, or pass a raw io.Reader via BodyReader.
Within opensearchapi/, the bulk, NDJSON, and single-document write operations are the exception: BulkReq, BulkStreamReq, MSearchReq, MSearchTemplateReq, CreateReq, IndexReq, and IndicesCreateDataStreamReq keep a single raw Body io.Reader and have no BodyReader field. The generated plugin packages under plugins/ use the same typed-Body/BodyReader split, with their own per-package set of raw-body operations.
// v4
client.Search(ctx, &opensearchapi.SearchReq{
Indices: []string{"products"},
Params: opensearchapi.SearchParams{Size: opensearch.ToPointer(20)},
})
// v5
client.Search(ctx, &opensearchapi.SearchReq{
Indices: []string{"products"},
Params: &opensearchapi.SearchParams{Size: opensearch.ToPointer(20)},
})Pointer-typed Params lets callers pass nil when no parameters are needed and keeps the struct cheap to copy. Size itself has been *int since v4.0.0 and is unchanged here -- only the surrounding Params value became a pointer.
On Go 1.26+ you can write
new(20)in place ofopensearch.ToPointer(20); both produce a*int.
Common query parameters are now grouped into embedded structs (TimeoutParams, DebugParams) shared across every operation. Fields like Timeout, Pretty, Human, and ErrorTrace are set through the embedded struct:
// v4
Params: opensearchapi.SearchParams{Timeout: 5 * time.Second, Pretty: true}
// v5
Params: &opensearchapi.SearchParams{
TimeoutParams: opensearchapi.TimeoutParams{Timeout: 5 * time.Second},
DebugParams: opensearchapi.DebugParams{Pretty: true},
}A deliberate false now reaches the wire. Previously, the zero value of bool was indistinguishable from "not set", so callers could not turn off a server-side default that the server treats as on-by-default.
// v5
params := opensearchapi.SearchParams{
AllowNoIndices: opensearch.ToPointer(false), // explicit false
}opensearch.ToPointer(v) is a generic helper. It is deprecated; once the module's go directive moves to Go 1.26, new(false) literals work directly.
The v5 and v4 opensearchapi/ packages carry the same high-level partial-failure error types (*PartialBulkError, *PartialSearchError, *ShardFailureError, *MultiSearchItemError, *MSearchErrors, *MSearchTemplateErrors). The internal field types diverge because v5 is generated from the OpenSearch API specification:
| Field role | v4 (hand-written) | v5 (generated) |
|---|---|---|
| Per-shard failure element | ResponseShardsFailure |
ShardSearchFailure |
| Per-sub-response error envelope | inline *DocumentError |
embedded ErrorRespBase |
| Shard envelope on responses | ResponseShards |
ShardStatistics |
Code that only reads top-level fields (PartialSearchError.FailedShards, .TotalShards) compiles unchanged. Code that walks the per-shard failure slice needs to switch type names.
opensearchapi.NewClient (and NewDefaultClient) inject opensearchtransport.NewDefaultRouter when the caller leaves config.Client.Router nil. v4 left Router nil.
The OPENSEARCH_GO_ROUTER env var acts as an opt-out (false / 0 keeps Router nil). See guides/config-envvars.md Default router injection for the full truth table and rationale.
Config.Errors is a *errmask.ErrorMask. The default differs:
| Surface | Config.Errors == nil means |
Effect |
|---|---|---|
| v4 | errmask.All |
mask everything (preserves pre-bitfield behavior) |
| v5 | errmask.Empty |
report every partial-failure category |
Concretely: a v4 caller who never set Config.Errors does not see partial failures as error. The same code on v5 does.
If you need v4-shaped silence on v5, set Errors: errmask.New(errmask.All) explicitly. If you want to opt v4 in to v5-style surfacing, set Errors: errmask.New().
The OPENSEARCH_GO_ERROR_MASK env var overrides whatever Config.Errors resolves to. See guides/usage-error_handling.md for the full guide.
Document and point-in-time operations are reached through sub-clients on the Client, grouping them by API family rather than flattening every operation onto the top-level Client.
Document operations move onto client.Doc:
| v4 / earlier | v5 |
|---|---|
client.Index(...) |
client.Doc.Index(...) |
client.Create(...) |
client.Doc.Create(...) |
client.Get(...) |
client.Doc.Get(...) |
client.GetSource(...) |
client.Doc.GetSource(...) |
client.Exists(...) |
client.Doc.Exists(...) |
client.ExistsSource(...) |
client.Doc.ExistsSource(...) |
client.Delete(...) |
client.Doc.Delete(...) |
client.Update(...) |
client.Doc.Update(...) |
client.MGet(...) |
client.Doc.MGet(...) |
client.Bulk(...) |
client.Doc.Bulk(...) |
client.BulkStream(...) |
client.Doc.BulkStream(...) |
client.TermVectors(...) |
client.Doc.TermVectors(...) |
client.MTermVectors(...) |
client.Doc.MTermVectors(...) |
Point-in-time operations move onto client.PIT, dropping the redundant PIT/Pits suffix from the method name:
| v4 / earlier | v5 |
|---|---|
client.CreatePIT(...) |
client.PIT.Create(...) |
client.DeletePIT(...) |
client.PIT.Delete(...) |
client.GetAllPits(...) |
client.PIT.GetAll(...) |
client.DeleteAllPits(...) |
client.PIT.DeleteAll(...) |
The request and response types are unchanged (opensearchapi.IndexReq, opensearchapi.CreatePITReq, and so on); only the canonical call path changes. The Doc sub-client is also reachable as client.Document, and PIT as client.PointInTime, for convenience.
To ease migration, the client ships forwarder methods so existing call sites keep compiling. These forward to the canonical sub-client method:
- Top-level
client.Bulk,client.MGet, andclient.Updateforward to theirclient.Doc.*equivalents (these were top-level methods historically).client.Indexis not retained as a top-level forwarder:Indexis the indices sub-client field onClient, and a field and method of that name cannot coexist; useclient.Doc.Index. client.Document.Sourceforwards toclient.Doc.GetSource.client.PointInTime.Getforwards toclient.PIT.GetAll.
The forwarders are generated by default. They can be suppressed at generation time with osgen api --emit-v4-compat=false, and marked deprecated with --emit-v4-deprecation=true. New code should prefer the canonical sub-client methods.
Index-template and data-stream operations are reached through client.Indices (e.g. client.Indices.PutIndexTemplate, client.Indices.CreateDataStream, client.Indices.GetDataStream); the standalone client.IndexTemplate, client.ComponentTemplate, client.Template, and client.DataStream fields are gone. Stored-script operations remain on the top-level Client (client.PutScript, client.GetScript, ...).
Plugin APIs (k-NN, ML, Security, ISM, ...) live under plugins/ as independent packages that share the same opensearch.Client transport.
- Update import paths from
/v4to/v5. - For operations with a typed
Body, move rawio.Readerbodies fromBodytoBodyReader. - Wrap
Paramsliterals in&(or use theParams: nilshorthand). - Move
Timeout/Pretty/Human/ErrorTraceinto the embeddedTimeoutParams/DebugParams. - Wrap optional
boolquery-param values inopensearch.ToPointer(...). - Route document calls through
client.Doc.*and point-in-time calls throughclient.PIT.*. - Decide whether to set
Config.Errorsexplicitly. v5 reports every partial-failure category by default. - Decide whether to override
Config.Client.RouterorOPENSEARCH_GO_ROUTER. v5 injects the default router. - Re-run your test suite. Spec-driven type renames in partial-failure field elements may surface as compile errors at call sites that walk shard-failure slices.
README.md- full v5 usage guide.../guides/usage-error_handling.md- error-handling best practices.../UPGRADING.md- version-history index.plugins/README.md- plugin client usage.