Appwrite is now available as a DigitalOcean 1-click app! Click to install! 🚀
Docs

Pagination

Pagination is the process of dividing data into discrete pages. In Appwrite, it is achieved by using an offset or a cursor, which both come with their own use case and benefits.

Offset Pagination

Using limit and offset you can achieve one of the most common approaches to pagination. With limit you can define to how many documents that can be returned from one request, which can be up to a maximum of 100 documents. The offset is simply the number of records you wish to skip before selecting records.

  • Web

    import { Client, Databases, Query } from "appwrite";
    
    const client = new Client()
        .setEndpoint('https://[HOSTNAME_OR_IP]/v1')
        .setProject('[PROJECT_ID]');
    
    const databases = new Databases(client);
                    
    // Page 1
    const page1 = await databases.listDocuments(
        '[DATABASE_ID]',
        '[COLLECTION_ID]',
        [
            Query.limit(25),
            Query.offset(0)
        ]
    );
    
    // Page 2
    const page2 = await databases.listDocuments(
        '[DATABASE_ID]',
        '[COLLECTION_ID]',
        [
            Query.limit(25),
            Query.offset(25)
        ]
    );
  • Flutter

    import 'package:appwrite/appwrite.dart';
    
    void main() async {
        final client = Client()
            .setEndpoint('https://[HOSTNAME_OR_IP]/v1')
            .setProject('[PROJECT_ID]');
    
        final databases = Databases(client);
    
        final page1 = await databases.listDocuments(
            databaseId: '[DATABASE_ID]',
            collectionId: '[COLLECTION_ID]',
            queries: [
                Query.limit(25),
                Query.offset(0)
            ]
        );
    
        final page2 = await databases.listDocuments(
            databaseId: '[DATABASE_ID]',
            collectionId: '[COLLECTION_ID]',
            queries: [
                Query.limit(25),
                Query.offset(25)
            ]
        );
    }
    
  • Android

    import io.appwrite.Client
    import io.appwrite.Query
    import io.appwrite.services.Databases
    
    suspend fun main() {
        val client = Client(applicationContext)
            .setEndpoint("https://[HOSTNAME_OR_IP]/v1")
            .setProject("[PROJECT_ID]")
    
        val databases = Databases(client)
    
        val page1 = databases.listDocuments(
            databaseId = "[DATABASE_ID]",
            collectionId = "[COLLECTION_ID]",
            queries = [
                Query.limit(25),
                Query.offset(0)
            ]
        )
    
        val page2 = databases.listDocuments(
            databaseId = "[DATABASE_ID]",
            collectionId = "[COLLECTION_ID]",
            queries = [
                Query.limit(25),
                Query.offset(25)
            ]
        )
    }
  • iOS

    import Appwrite
    import AppwriteModels
    
    func main() async throws {
        let client = Client()
            .setEndpoint("https://[HOSTNAME_OR_IP]/v1")
            .setProject("[PROJECT_ID]")
    
        let databases = Databases(client)
    
        let page1 = try await databases.listDocuments(
            databaseId: "[DATABASE_ID]",
            collectionId: "[COLLECTION_ID]",
            queries: [
                Query.limit(25),
                Query.offset(0)
            ]
        )
    
        let page2 = try await databases.listDocuments(
            databaseId: "[DATABASE_ID]",
            collectionId: "[COLLECTION_ID]",
            queries: [
                Query.limit(25),
                Query.offset(25)
            ]
        )
    }

The maximum offset is 5000, since the request gets slower as the number of records increases because the database has to read up to the offset number of rows to know where it should start selecting data. Also when there is data added in high frequency - the individual pages might skip results.

Cursor Pagination

The cursor is a unique identifier for a specific record, which acts as a pointer to the next record we want to start querying from to get the next page of results. Additionally to the cursor you can pass the cursor position as the cursorDirection before and after allowing you to get paginate back and forth.

  • Web

    import { Databases, Query } from "appwrite";
    
    const client = new Client()
        .setEndpoint("https://[HOSTNAME_OR_IP]/v1")
        .setProject("[PROJECT_ID]");
    
    const databases = new Databases(client);
    
    // Page 1
    const page1 = await databases.listDocuments(
        '[DATABASE_ID]',
        '[COLLECTION_ID]',
        [
            Query.limit(25),
        ]
    );
    
    const lastId = page1.documents[page1.documents.length - 1].$id;
    
    // Page 2
    const page2 = await databases.listDocuments(
        '[DATABASE_ID]',
        '[COLLECTION_ID]',
        [
            Query.limit(25),
            Query.cursorAfter(lastId),
        ]
    );
  • Flutter

    import 'package:appwrite/appwrite.dart';
    
    void main() async {
        final client = Client()
            .setEndpoint('https://[HOSTNAME_OR_IP]/v1')
            .setProject('[PROJECT_ID]');
    
        final databases = Databases(client);
    
        final page1 = await databases.listDocuments(
            databaseId: '[DATABASE_ID]',
            collectionId: '[COLLECTION_ID]',
            queries: [
                Query.limit(25)
            ]
        );
    
        final lastId = page1.documents[page1.documents.length - 1].$id;
    
        final page2 = await databases.listDocuments(
            databaseId: '[DATABASE_ID]',
            collectionId: '[COLLECTION_ID]',
            queries: [
                Query.limit(25),
                Query.cursorAfter(lastId)
            ]
        );
    
    }
    
  • Android

    import android.util.Log
    import io.appwrite.AppwriteException
    import io.appwrite.Client
    import io.appwrite.Query
    import io.appwrite.services.Databases
    
    suspend fun main() {
        val client = Client(applicationContext)
            .setEndpoint("https://[HOSTNAME_OR_IP]/v1")
            .setProject("[PROJECT_ID]")
    
        val databases = Databases(client)
    
        val page1 = databases.listDocuments(
            databaseId = "[DATABASE_ID]",
            collectionId = "[COLLECTION_ID]",
            queries = [
                Query.limit(25)
            ]
        )
    
        val lastId = page1.documents[page1.documents.size - 1].$id
    
        val page2 = databases.listDocuments(
            databaseId = "[DATABASE_ID]",
            collectionId = "[COLLECTION_ID]",
            queries = [
                Query.limit(25),
                Query.cursorAfter(lastId)
            ]
        )
    }
  • iOS

    import Appwrite
    import AppwriteModels
    
    func main() async throws {
        let client = Client()
          .setEndpoint("https://[HOSTNAME_OR_IP]/v1")
          .setProject("[PROJECT_ID]")
    
        let databases = Databases(client)
    
        let page1 = try await databases.listDocuments(
            databaseId: "[DATABASE_ID]",
            collectionId: "[COLLECTION_ID]",
            queries: [
                Query.limit(25)
            ]
        )
    
        let lastId = page1.documents[page1.documents.count - 1].$id
    
        let page2 = try await databases.listDocuments(
            databaseId: "[DATABASE_ID]",
            collectionId: "[COLLECTION_ID]",
            queries: [
                Query.limit(25),
                Query.cursorAfter(lastId)
            ]
        )
    }

When to use what?

The different scenarios in which offset or cursor pagination make the most sense depends on the data itself and how often new records are added. When querying static data, the performance cost alone may not be enough for you to use a cursor, as the added complexity that comes with it may be more than you need.

Offset pagination should be used for static data with an indicator to what is the current page and how many pages are available in total. For example a list with up to 20 pages or static data like a list of countries or currencies.

Cursor pagination should be used high-frequency data which is lazy-loaded with infinite scrolling. For example a feed, comment section, chat history or high volume datasets.