Go 1.11 has reached end of support
and will be deprecated
on January 31, 2026. After deprecation, you won't be able to deploy Go 1.11
applications, even if your organization previously used an organization policy to
re-enable deployments of legacy runtimes. Your existing Go
1.11 applications will continue to run and receive traffic after their
deprecation date. We
recommend that you migrate to the latest supported version of Go.
Stay organized with collections
Save and categorize content based on your preferences.
Query cursors allow an application to retrieve a query's results in convenient
batches, and are recommended over using integer offsets for pagination.
See Queries for more information on structuring queries for your app.
Query cursors
Query cursors allow an application to retrieve a query's results in convenient
batches without incurring the overhead of a query offset. After performing a
retrieval operation, the application can obtain a
cursor, which is an opaque base64-encoded string marking the index position of
the last result retrieved. The application can save this string, for example in
Datastore, in Memcache, in a Task Queue task payload, or embedded in
a web page as an HTTP GET or POST parameter, and can then use the cursor as
the starting point for a subsequent retrieval operation to obtain the next batch
of results from the point where the previous retrieval ended. A retrieval can
also specify an end cursor, to limit the extent of the result set returned.
Offsets versus cursors
Although Datastore supports integer offsets, you should avoid
using them. Instead, use cursors. Using an offset only avoids returning the
skipped entities to your application, but these entities are still retrieved
internally. The skipped entities do affect the latency of the query, and your
application is billed for the read operations required to retrieve them. Using
cursors instead of offsets lets you avoid all these costs.
Query cursor example
In Go, an application obtains a cursor after retrieving query results by calling
the Iterator value's Cursor method. To retrieve
additional results from the point of the cursor, the application prepares a
similar query with the same entity kind, filters, and
sort orders, and passes the cursor to the query's
Start method before performing the retrieval:
// Create a query for all Person entities.q:=datastore.NewQuery("Person")// If the application stored a cursor during a previous request, use it.item,err:=memcache.Get(ctx,"person_cursor")iferr==nil{cursor,err:=datastore.DecodeCursor(string(item.Value))iferr==nil{q=q.Start(cursor)}}// Iterate over the results.t:=q.Run(ctx)for{varpPerson_,err:=t.Next(&p)iferr==datastore.Done{break}iferr!=nil{log.Errorf(ctx,"fetching next Person: %v",err)break}// Do something with the Person p}// Get updated cursor and store it for next time.ifcursor,err:=t.Cursor();err==nil{memcache.Set(ctx,&memcache.Item{Key:"person_cursor",Value:[]byte(cursor.String()),})}
Limitations of cursors
Cursors are subject to the following limitations:
A cursor can be used only by the same application that performed the original
query, and only to continue the same query. To use the cursor in a subsequent
retrieval operation, you must reconstitute the original query exactly,
including the same entity kind, ancestor filter, property filters, and sort
orders. It is not possible to retrieve results using a cursor without setting
up the same query from which it was originally generated.
Cursors don't always work as expected with a query that uses an inequality
filter or a sort order on a property with multiple values. The de-duplication
logic for such multiple-valued properties does not persist between retrievals,
possibly causing the same result to be returned more than once.
New App Engine releases might change internal implementation details,
invalidating cursors that depend on them. If an application attempts to use a
cursor that is no longer valid, Datastore
returns
an error.
Cursors and data updates
The cursor's position is defined as the location in the result list after the
last result returned. A cursor is not a relative position in the list
(it's not an offset); it's a marker to which Datastore can jump
when starting an index scan for results. If the results for a query change
between uses of a cursor, the query notices only changes that occur in results
after the cursor. If a new result appears before the cursor's position for the
query, it will not be returned when the results after the cursor are fetched.
Similarly, if an entity is no longer a result for a query but had appeared
before the cursor, the results that appear after the cursor do not change. If
the last result returned is removed from the result set, the cursor still knows
how to locate the next result.
When retrieving query results, you can use both a start cursor and an end cursor
to return a continuous group of results from Datastore. When
using a start and end cursor to retrieve the results, you are not guaranteed
that the size of the results will be the same as when you generated the cursors.
Entities may be added or deleted from Datastore between the
time the cursors are generated and when they are used in a query.
[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Hard to understand","hardToUnderstand","thumb-down"],["Incorrect information or sample code","incorrectInformationOrSampleCode","thumb-down"],["Missing the information/samples I need","missingTheInformationSamplesINeed","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2025-08-29 UTC."],[[["\u003cp\u003eThis API is designed for first-generation runtimes and is crucial when upgrading to corresponding second-generation runtimes, with a migration guide provided for App Engine Go 1.12+ users.\u003c/p\u003e\n"],["\u003cp\u003eQuery cursors enable applications to retrieve query results in batches, providing a more efficient method of pagination compared to using integer offsets, as they avoid retrieving skipped entities internally.\u003c/p\u003e\n"],["\u003cp\u003eAfter retrieving results, a cursor, which is an encoded string, can be obtained to mark the last result's position, allowing the application to save it and use it as a starting point for the next retrieval operation.\u003c/p\u003e\n"],["\u003cp\u003eCursors are limited to use by the originating application and require an exact replication of the initial query's conditions to be reused, while also being susceptible to invalidation from new App Engine releases.\u003c/p\u003e\n"],["\u003cp\u003eUsing cursors for pagination is preferred over offsets because it allows you to avoid costs and latency associated with retrieving entities internally that will just be skipped, in addition to being able to specify an end cursor to limit results.\u003c/p\u003e\n"]]],[],null,["# Query Cursors\n\n| This API is supported for first-generation runtimes and can be used when [upgrading to corresponding second-generation runtimes](/appengine/docs/standard/\n| go\n| /services/access). If you are updating to the App Engine Go 1.12+ runtime, refer to the [migration guide](/appengine/migration-center/standard/migrate-to-second-gen/go-differences) to learn about your migration options for legacy bundled services.\n\n*Query cursors* allow an application to retrieve a query's results in convenient\nbatches, and are recommended over using integer offsets for pagination.\nSee [Queries](/appengine/docs/legacy/standard/go111/datastore/queries) for more information on structuring queries for your app.\n\nQuery cursors\n-------------\n\n*Query cursors* allow an application to retrieve a query's results in convenient\nbatches without incurring the overhead of a query offset. After performing a\n[retrieval operation](/appengine/docs/legacy/standard/go111/datastore/retrieving-query-results), the application can obtain a\ncursor, which is an opaque base64-encoded string marking the index position of\nthe last result retrieved. The application can save this string, for example in\nDatastore, in Memcache, in a Task Queue task payload, or embedded in\na web page as an HTTP `GET` or `POST` parameter, and can then use the cursor as\nthe starting point for a subsequent retrieval operation to obtain the next batch\nof results from the point where the previous retrieval ended. A retrieval can\nalso specify an end cursor, to limit the extent of the result set returned.\n\nOffsets versus cursors\n----------------------\n\nAlthough Datastore supports integer offsets, you should avoid\nusing them. Instead, use cursors. Using an offset only avoids returning the\nskipped entities to your application, but these entities are still retrieved\ninternally. The skipped entities do affect the latency of the query, and your\napplication is billed for the read operations required to retrieve them. Using\ncursors instead of offsets lets you avoid all these costs.\n\nQuery cursor example\n--------------------\n\nIn Go, an application obtains a cursor after retrieving query results by calling\nthe `Iterator` value's [`Cursor`](/appengine/docs/legacy/standard/go111/datastore/reference#Iterator.Cursor) method. To retrieve\nadditional results from the point of the cursor, the application prepares a\nsimilar query with the same entity kind, filters, and\nsort orders, and passes the cursor to the query's\n[`Start`](/appengine/docs/legacy/standard/go111/datastore/reference#Query.Start) method before performing the retrieval: \n\n // Create a query for all Person entities.\n q := datastore.NewQuery(\"Person\")\n\n // If the application stored a cursor during a previous request, use it.\n item, err := memcache.Get(ctx, \"person_cursor\")\n if err == nil {\n \tcursor, err := datastore.DecodeCursor(string(item.Value))\n \tif err == nil {\n \t\tq = q.Start(cursor)\n \t}\n }\n\n // Iterate over the results.\n t := q.Run(ctx)\n for {\n \tvar p Person\n \t_, err := t.Next(&p)\n \tif err == datastore.Done {\n \t\tbreak\n \t}\n \tif err != nil {\n \t\tlog.Errorf(ctx, \"fetching next Person: %v\", err)\n \t\tbreak\n \t}\n \t// Do something with the Person p\n }\n\n // Get updated cursor and store it for next time.\n if cursor, err := t.Cursor(); err == nil {\n \tmemcache.Set(ctx, &memcache.Item{\n \t\tKey: \"person_cursor\",\n \t\tValue: []byte(cursor.String()),\n \t})\n }\n\n| **Caution:** Be careful when passing a Datastore cursor to a client, such as in a web form. Although the client cannot change the cursor value to access results outside of the original query, it is possible for it to decode the cursor to expose information about result entities, such as the application ID, entity kind, key name or numeric ID, ancestor keys, and properties used in the query's filters and sort orders. If you don't want users to have access to that information, you can encrypt the cursor, or store it and provide the user with an opaque key.\n\n### Limitations of cursors\n\nCursors are subject to the following limitations:\n\n- A cursor can be used only by the same application that performed the original query, and only to continue the same query. To use the cursor in a subsequent retrieval operation, you must reconstitute the original query exactly, including the same entity kind, ancestor filter, property filters, and sort orders. It is not possible to retrieve results using a cursor without setting up the same query from which it was originally generated.\n- Cursors don't always work as expected with a query that uses an inequality filter or a sort order on a property with multiple values. The de-duplication logic for such multiple-valued properties does not persist between retrievals, possibly causing the same result to be returned more than once.\n- New App Engine releases might change internal implementation details, invalidating cursors that depend on them. If an application attempts to use a cursor that is no longer valid, Datastore returns an error.\n\n### Cursors and data updates\n\nThe cursor's position is defined as the location in the result list after the\nlast result returned. A cursor is not a relative position in the list\n(it's not an offset); it's a marker to which Datastore can jump\nwhen starting an index scan for results. If the results for a query change\nbetween uses of a cursor, the query notices only changes that occur in results\nafter the cursor. If a new result appears before the cursor's position for the\nquery, it will not be returned when the results after the cursor are fetched.\nSimilarly, if an entity is no longer a result for a query but had appeared\nbefore the cursor, the results that appear after the cursor do not change. If\nthe last result returned is removed from the result set, the cursor still knows\nhow to locate the next result.\n\nWhen retrieving query results, you can use both a start cursor and an end cursor\nto return a continuous group of results from Datastore. When\nusing a start and end cursor to retrieve the results, you are not guaranteed\nthat the size of the results will be the same as when you generated the cursors.\nEntities may be added or deleted from Datastore between the\ntime the cursors are generated and when they are used in a query.\n\nWhat's next?\n------------\n\n- [Learn how to specify what a query returns and further control query\n results](/appengine/docs/legacy/standard/go111/datastore/retrieving-query-results).\n- Learn the [common restrictions](/appengine/docs/legacy/standard/go111/datastore/query-restrictions) for queries on Datastore.\n- [Understand data consistency](/appengine/docs/legacy/standard/go111/datastore/data-consistency) and how data consistency works with different types of queries on Datastore.\n- Learn the [basic syntax and structure of queries](/appengine/docs/legacy/standard/go111/datastore/queries) for Datastore."]]