Migrating from the GraphQL API
What is changing and when
The GraphQL API is deprecated as of October 26, 2026. Nothing changes on that day, and existing clusters keep working. Weaviate Cloud switches GraphQL off on December 1, 2026 for clusters that have not used it in the last 30 days, and on May 1, 2027 for all remaining clusters. Self-hosted Weaviate keeps GraphQL working until at least May 1, 2027. The first minor release after that date disables it by default, and you can switch it back on. It is removed in a later release, with no date set yet.
| Stage | Weaviate Cloud | Self-hosted |
|---|---|---|
| Deprecated, October 26, 2026 | Existing clusters keep working. New clusters are created with GraphQL disabled, as they already were before. | Nothing changes. GraphQL keeps working until at least May 1, 2027. |
| Disabled by default | Already the case for new clusters. | In the first minor release after May 1, 2027. You can switch GraphQL back on. |
| Switched off or removed | Switched off on December 1, 2026 for clusters that have not used GraphQL in the last 30 days. Switched off on all remaining clusters on May 1, 2027. | Removed in a later release. No date is set yet. |
Who this guide is for
This guide is for developers who have working GraphQL Get and Aggregate queries and need to express the same searches through one of Weaviate's other query APIs. Typical reasons to be here:
- The GraphQL API is deprecated. Code that still sends GraphQL queries stops working once GraphQL is disabled, so it needs to move before the dates above.
- You are moving an application to a new Weaviate Cloud cluster, which is created with GraphQL disabled.
- You are building in the browser or on an edge runtime, where a plain gRPC client cannot run.
- You maintain an HTTP-only stack, such as a no-code or BI tool, or a language without an official Weaviate client such as PHP or Ruby.
Start at the top of this table and take the first row that describes your stack.
| Your application | Recommended path | Why |
|---|---|---|
| Server or container app | Client libraries | For Python, JavaScript/TypeScript on Node, Go, Java, and C#. Covers every Get and Aggregate construct and gives you typed results and generated request objects. The recommended path, and the right one for the large majority of applications. |
| Browser or edge runtime | TypeScript client for web | For the browser and runtimes such as Cloudflare Workers or Vercel. The same client API, carried over gRPC-Web so it works where plain gRPC cannot. |
| No client library available | REST Search API | For a no-code or BI platform, or a language with no official client such as PHP or Ruby. Plain-HTTP JSON endpoints whose request bodies mirror your GraphQL queries closely. |
The three paths are at three different maturity levels. Check the Status column before you commit to one.
| Path | Status | Availability |
|---|---|---|
| Official client libraries | Generally available | Python, JavaScript/TypeScript, Go, Java, and C# are generally available. |
@weaviate/web (browser and edge) | Generally available | 3.15.0 and up. Requires Weaviate v1.38.3 or later. See the web client page. |
| REST Search API | Preview | Enabled on all Weaviate Cloud clusters. For open-source releases, enabled by default from v1.39.7. In v1.39.0 through v1.39.6 it needs EXPERIMENTAL_REST_SEARCH_ENABLED set to true. See REST Search API. |
Do you need to migrate
You need to migrate if any part of your application sends GraphQL to Weaviate. The two checks below tell you whether it does, and let you prove it before GraphQL is switched off.
Find your GraphQL usage
GraphQL can reach Weaviate in three ways, and an older client can send it without you writing a single query.
Where GraphQL hides, and which client versions use it
Your code sends GraphQL queries if it does any of the following:
- Calls the endpoint directly. It sends HTTP requests to
/v1/graphqlor/v1/graphql/batch, for example withcurlor a generic HTTP library. - Uses a raw GraphQL helper. Some clients can pass a GraphQL query string straight to the server. These helpers always use GraphQL, whatever the client generation.
- Uses an older client generation. Older clients, including the Go client
v5, build their queries as GraphQL under the hood.
Check each client your code uses against this table:
| Client | Queries over gRPC from | Still uses GraphQL when |
|---|---|---|
Python weaviate-client | Search: v4.4.0. Aggregate: v4.11.0, against Weaviate v1.29.0 or later | Aggregate and len(collection) fall back to GraphQL on Weaviate older than v1.29.0. client.graphql_raw_query() always uses GraphQL. |
TypeScript weaviate-client | Search: v3.0.0. Aggregate: v3.4.0, against Weaviate v1.29.0 or later | Aggregate falls back to GraphQL on Weaviate older than v1.29.0. |
Java io.weaviate:client6 | 6.0.0 | Never. |
| C# | 1.0.0 | Never. |
Go weaviate-go-client/v6 | v6.0.0 | Never. |
Go weaviate-go-client/v5 | Never | Always. client.GraphQL().Get(), .Aggregate(), .Explore() and .Raw() are its search API. |
Test with GraphQL disabled
A test instance with GraphQL switched off proves that your application no longer depends on it.
How to switch GraphQL off, and what a disabled endpoint returns
Run your application against an instance with GraphQL switched off before you rely on it. On a non-production self-hosted instance, set the DISABLE_GRAPHQL environment variable to true. It is also available as the disable_graphql runtime configuration key.
With GraphQL disabled, POST /v1/graphql and POST /v1/graphql/batch return HTTP 422 with this body:
{"error":[{"message":"graphql api is disabled"}]}
Authorization runs first, so a caller without read permission gets 403 instead. The Python client and the Go client v5 return an error that carries the same status code and message.
A new Weaviate Cloud cluster is already in this state, so you can also test against one.
Client libraries (recommended)
The client libraries cover every Get and Aggregate construct shown on this page, so each one has a direct equivalent. They query over gRPC, and give you typed results and generated request objects.
GraphQL syntax mapping
| GraphQL | Client concept | How-to |
|---|---|---|
Get { Collection { ... } } | Fetch objects from a collection | Search patterns and basics |
nearText | Near-text search | Search with text |
nearObject | Near-object search | Search with an existing object |
nearVector | Near-vector search, passing your own query vector | Search with a vector |
nearImage and the other media operators | Multimodal search | Search with image |
bm25 | Keyword search, including search operators and property boosts | Keyword search |
hybrid | Hybrid search, including alpha, fusion type, and search vectors | Hybrid search |
where | Filters, built with the client's filter helpers | Filters |
limit, offset | limit and offset arguments | Paginate with limit and offset |
after (cursor) | Cursor-based iteration over all objects | Cursor with after |
autocut | auto_limit / autoLimit | Limit result groups |
sort | Sorting on object retrieval. Sorting is not combined with a search operator. See sorting considerations | Sorting |
_additional { ... } | return_metadata / returnMetadata, returned as a metadata object on each result | Retrieve metadata values |
_additional { vector } | include_vector / returnVectors | Retrieve the object vector |
groupBy | Grouped results | Group results |
Aggregate { ... } | The collection's aggregate API, including per-property statistics | Aggregate data |
_additional { generate { ... } } | Retrieval augmented generation (single prompt and grouped task) | Retrieval Augmented Generation (RAG) |
Cross-reference selection (... on Target { ... }) | return_references / returnReferences | Retrieve cross-referenced properties |
tenant | A tenant-scoped collection handle | Multi-tenancy |
consistencyLevel | A consistency-level-scoped collection handle | Replication |
TypeScript client for web
If your application runs in a browser or on an edge runtime such as Cloudflare Workers or Vercel, a plain gRPC client cannot run there. @weaviate/web is a browser and edge build of the v3 TypeScript client that carries the query path over gRPC-Web, so you write the same client code you would on Node against a single HTTP(S) endpoint.
Install steps, the server version floor and the browser notes are on the web client page.
REST Search API
As of Oct 26, 2026, the REST Search API is a preview feature.
The REST Search API is a set of plain-HTTP JSON endpoints intended for stacks that cannot run a client library. Request and response field names may still change, so this path is not recommended for production use yet. It is a deliberate subset of what GraphQL offers. Read Differences to check before you convert and Queries without a REST equivalent yet before you convert anything.
There are six available POST endpoints:
| Endpoint | GraphQL equivalent |
|---|---|
POST /v1/search/{collection}/near-text | Get with nearText |
POST /v1/search/{collection}/bm25 | Get with bm25 |
POST /v1/search/{collection}/hybrid | Get with hybrid |
POST /v1/search/{collection}/near-object | Get with nearObject |
POST /v1/search/{collection}/near-vector | Get with nearVector |
POST /v1/aggregate/{collection} | Aggregate |
The near-vector endpoint is available from v1.39.8. It takes your own query vector, so it also works on collections without a vectorizer.
An example request
The GraphQL query you are converting from is documented in the GraphQL reference. Its REST equivalent looks like this:
REST Search API: POST /v1/search/Movie/near-text
{
"query": ["space opera"],
"limit": 5,
"where": {
"operator": "And",
"operands": [
{ "path": ["year"], "operator": "GreaterThanEqual", "valueInt": 1980 },
{ "path": ["category"], "operator": "Equal", "valueText": "scifi" }
]
},
"returnProperties": ["title", "year"],
"returnMetadata": ["distance"]
}
The where value is the exact same JSON you pass in GraphQL. It uses the same 15 operators, the same value fields, and the same cross-reference paths, parsed by the same code. There is no new filter language to learn.
The response is a flat envelope rather than the GraphQL data.Get.<Collection> nesting:
{
"results": [
{
"id": "00000000-0000-0000-0000-000000000000",
"properties": { "title": "Dune", "year": 2021 },
"metadata": { "distance": 0.12 }
}
],
"tookMs": 8
}
Differences to check before you convert
These are the conversions most likely to cost you time. Some return a perfectly successful response carrying a different answer from the one GraphQL gave. Others change the way a failure reaches your code. The silent ones are the dangerous ones. Read this list before the feature gaps below.
What changes, and what to do about it
| Difference | What happens | What to do |
|---|---|---|
Default limit | GraphQL Get falls back to QUERY_DEFAULTS_LIMIT_GRAPHQL (100 by default). The REST endpoints fall back to QUERY_DEFAULTS_LIMIT (10 by default) and never reach the GraphQL value. A query you convert without an explicit limit silently returns far fewer rows. | Always set limit explicitly. This is the single most common silent difference. |
| Unknown body fields are ignored | The endpoints accept and discard fields they do not recognize, for platform parity with the rest of the REST API. A typo, or a field borrowed from GraphQL, produces a successful response that ignored your intent. | Check every field name against the tables on this page. |
bm25.searchOperator | There is no REST field for the keyword search operator (And, Or, AndCross) or minimumOrTokensMatch. Supplying one is ignored, so the search runs with the default operator. | Use a client library if your results depend on the search operator. |
Get's group argument | The vector-grouping group argument has no REST field and raises no error. Results come back ungrouped. | Use a client library for grouped results. |
A body field named generate | Silently ignored, and the response contains no generated text. The reserved singlePrompt and groupedTask fields do return 422. | See Queries without a REST equivalent yet. |
certainty on a bm25 search | Accepted in returnMetadata, but certainty cannot be computed for a keyword search, so the field is simply absent from the response. | Request score instead for keyword searches. |
int64 precision | Integers travel as JSON numbers and are handled as float64, so values beyond 2^53 lose precision. | For large integers, use a client library, or store the value as text. |
_additional is renamed | Retrieval metadata arrives under a metadata key, not _additional. Properties and cross-references are also split into their own properties and references objects rather than sitting flat on the result, and the object id is a top-level envelope field. | Update your response parsing. Note id is not a valid returnMetadata entry. Supplying it returns 422. |
| Error signalling | GraphQL returns HTTP 200 with an errors[] array. The REST endpoints use real HTTP status codes (400, 401, 403, 404, 413, 422, 429, 500, 503) with a body of the form {"error":[{"message":"..."}]}. | Check the status code, not just the body. Client code that only inspected errors[] will treat failures as successes. |
| Embedding-provider failures | When the vectorizer module fails, the response is 500, never 502, with a deliberately short message. The provider's own response goes to the server log, because it can quote credentials. | Read the server log for provider errors. Do not parse the client-facing body. |
| The same validation, surfaced differently | Both APIs reject a malformed where filter with the same underlying message. That covers And/Or with no operands, a Not with more than one, a non-RFC3339 valueDate, a ContainsAny/ContainsAll/ContainsNone without an array value, and IsNull without indexNullState. GraphQL returns the error inside errors[] under HTTP 200. Here it arrives as a status code. Only the missing-operands case is caught at the API layer and returns 400. The other four are rejected deeper in the engine and return 500. | Do not treat these 500s as transient server trouble: a retry or alerting rule keyed on 5xx will retry forever and page someone. Read the message before retrying. |
fusionType values differ | GraphQL's rankedFusion and relativeScoreFusion are ranked and relativeScore here. A copied GraphQL value returns 422. | Translate the value. See the cheat sheet below. |
Queries without a REST equivalent yet
These capabilities are not part of the preview. They fail in two very different ways, so check the status column before you port anything: some are reserved and return 422 or 400, which shows the gap in your logs. Others are simply not recognized, and the request returns 200 with the capability having had no effect at all. The silent ones are the ones to watch. If you need one of these, please open a GitHub issue.
What is missing, and what each returns
| GraphQL capability | REST status | Path |
|---|---|---|
hybrid.vector, supplying the vector leg of a hybrid search | The hybrid route exists but has no vector field, so the value is silently ignored (200) and the search runs against a server-computed vector instead of yours. | Client libraries or the web client. |
Aggregate property statistics such as sum, mean, median, mode, minimum, maximum, topOccurrences, totalTrue, and the rest | Aggregate is count-only. A property:statistic entry in returnMetrics returns 422. | Client libraries |
Aggregate over a search, over and objectLimit | Reserved, returns 422. | Client libraries |
RAG, generate(singleResult:) and generate(groupedResult:) | singlePrompt and groupedTask are reserved and return 422. | Client libraries |
Multiple target vectors, targetVectors and targets | targetVector takes a single string. On near-text, near-object, and hybrid an array returns 400. On bm25 there is no targetVector field at all, so any value, string or array, is silently ignored (200) through the unknown-field path. Multi-target weighting is not available. | Client libraries |
Media search, nearImage, nearAudio, nearVideo, nearDepth, nearThermal, nearImu | No routes. | Client libraries |
groupBy in Get | Reserved, but the status depends on the shape you send. The GraphQL-shaped object ({path, groups, objectsPerGroup}) is the form you are most likely to port, and it returns 400, because the reserved field is typed as a string. A bare string returns 422 "groupBy is not yet supported". | Client libraries |
rerank | Reserved, returns 422. | Client libraries |
sort and the after cursor | Not fields on the search endpoints, so both are silently ignored (200, with the result set unchanged and unsorted). Sorting is not combined with search operators, as noted in sorting considerations, and both remain available on GET /v1/objects. | Client libraries, or GET /v1/objects |
nearText concept steering, moveTo, moveAwayFrom and autocorrect | Not fields on the near-text route, so all three are silently ignored (200, with the result set unchanged and unsteered). | Client libraries |
Vectors in the response, _additional { vector } | Vectors are never returned by these endpoints. | Client libraries, or GET /v1/objects |
GraphQL to REST cheat sheet
Body fields are camelCase. Field names not listed here are not recognized, and unrecognized fields are ignored rather than rejected.
GraphQL to REST field map
| GraphQL | REST request body |
|---|---|
nearText: { concepts: ["x", "y"] } | POST /v1/search/{c}/near-text with "query": ["x", "y"]. Always an array, even for one concept |
nearText: { certainty: 0.7 } or { distance: 0.3 } | "certainty": 0.7 or "distance": 0.3. One or the other, not both |
bm25: { query: "x", properties: ["title^2"] } | POST /v1/search/{c}/bm25 with "query": "x", "queryProperties": ["title^2"] |
hybrid: { query: "x", alpha: 0.5 } | POST /v1/search/{c}/hybrid with "query": "x", "alpha": 0.5 |
hybrid: { fusionType: rankedFusion } | "fusionType": "ranked" |
hybrid: { fusionType: relativeScoreFusion } | "fusionType": "relativeScore" |
hybrid: { maxVectorDistance: 0.4 } | "maxVectorDistance": 0.4. Requires alpha greater than 0, otherwise 400 |
nearObject: { id: "<uuid>" } | POST /v1/search/{c}/near-object with "id": "<uuid>". Required, and must be a well-formed UUID |
nearVector: { vector: [0.1, 0.2] } | POST /v1/search/{c}/near-vector with "vector": [0.1, 0.2]. Required. Accepts certainty or distance like near-text |
targetVectors: ["title_vector"] | "targetVector": "title_vector". A single string |
where: { ...WhereFilter... } | "where": { ...the same JSON... } |
limit: 5, offset: 10 | "limit": 5, "offset": 10 |
autocut: 1 | "autoLimit": 1 |
A property selection such as title year | "returnProperties": ["title", "year"]. Omit the field for all non-reference properties, or pass [] for none |
hasAuthor { ... on Author { name } } | "returnReferences": [{ "linkOn": "hasAuthor", "targetCollection": "Author", "returnProperties": ["name"] }]. Dotted paths in returnProperties return 400 |
_additional { distance score explainScore } | "returnMetadata": ["distance", "score", "explainScore"]. The response key is metadata |
_additional { creationTimeUnix lastUpdateTimeUnix } | "returnMetadata": ["creationTime", "lastUpdateTime"] |
_additional { id } | Nothing to request. id is always returned in the envelope. Passing "id" in returnMetadata returns 422 |
tenant: "tenantA" | "tenant": "tenantA" |
consistencyLevel: ONE | "consistencyLevel": "ONE". Uppercase only, "one" returns 422 |
Aggregate { C(groupBy: ["category"]) { meta { count } } } | POST /v1/aggregate/{c} with "groupBy": "category", "returnMetrics": ["count"]. Use a bare property name, not a path. A dotted value returns 422 |
Aggregate { C { meta { count } } } | POST /v1/aggregate/{c} with an empty body, or "returnMetrics": ["count"] |
One more request limit worth knowing: limit, offset, and their sum are each capped by QUERY_MAXIMUM_RESULTS, and exceeding any of them returns 400. From v1.39.7, a request body larger than 4 MiB returns 413.
GraphQL features without an equivalent
These GraphQL features have no equivalent in the gRPC API, the client libraries or the REST Search API today:
Exploreask, from theqnamodules- Summarization, from the
sum-transformersmodule - Named entity recognition, from the
ner-transformersmodule spellCheck- The
_additionalpropertiesfeatureProjection,semanticPath,interpretationandclassification
If one of these is essential to your application, tell us through the channels under Questions and feedback. Within reason, we will add what is missing.
Further resources
- GraphQL API (deprecated) - the GraphQL API reference
- gRPC-Web - the browser-reachable gRPC interface
- Client libraries - installation and connection for each language
- Web client - install steps and browser notes for
@weaviate/web - Search patterns and basics - the how-to family this guide links throughout
- Connect to a cluster - where to find your Weaviate Cloud endpoint URLs
Questions and feedback
All three paths are actively being developed, and feedback on this guide, especially a query you could not convert, directly shapes what gets built next.
We are helping with the migration through the support portal and support@weaviate.io. If a query has no equivalent in the paths above, tell us. We will work with you to find a solution, for example by adding additional parameters on the REST Search API.
Have a question or feedback? Here's how to reach us.
