Announcing the Appwrite OSS Fund, Learn More! 🤑
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

    // Page 1
    const page1 = await sdk.database.listDocuments('movies', [], 25, 0);
    // Page 2
    const page2 = await sdk.database.listDocuments('movies', [], 25, 25);
            
  • Flutter

    import 'package:appwrite/appwrite.dart';
    
    void main() async {
        final client = Client();
        final database = Database(client);
        try {
            final page1 = await database.listDocuments(
                collectionId: 'movies',
                limit: 25,
                offset: 0);
            final page2 = await database.listDocuments(
                collectionId: 'movies',
                limit: 25,
                offset: 25);
        } on AppwriteException catch(e) {
            print(e);
        }
    }
    
  • Android

    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import kotlinx.coroutines.GlobalScope
    import kotlinx.coroutines.launch
    import io.appwrite.Client
    import io.appwrite.services.Database
    import io.appwrite.Query
    
    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            val client = Client(applicationContext)
                .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
                .setProject("5df5acd0d48c2") // Your project ID
    
            val database = Database(client)
    
            GlobalScope.launch {
                val page1 = database.listDocuments(
                    collectionId = "[COLLECTION_ID]",
                    limit = 25,
                    offset = 0,
                )
    
                val page2 = database.listDocuments(
                    collectionId = "[COLLECTION_ID]",
                    limit = 25,
                    offset = 25,
                )    
            }
        }
    }
  • iOS

    import Appwrite
    
    func main() async throws {
        let client = Client()
          .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
          .setProject("my_project") // Your project ID
    
        let database = Database(client)
        // Page 1
        var list = try await database.listDocuments(
            collectionId: "[COLLECTION_ID]",
            limit: 25,
            offset: 0
        )
        print(list.toMap())
    
        // Page 2
        list = try await database.listDocuments(
            collectionId: "[COLLECTION_ID]",
            limit: 25,
            offset: 25
        )
        print(list.toMap())
    }

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

    // Page 1
    const page1 = await sdk.database.listDocuments('movies', [], 25, 0);
    const lastId = results.documents[results.documents.length - 1].$id;
    
    // Page 2
    const page2 = await sdk.database.listDocuments('movies', [], 25, 0, lastId);
            
  • Flutter

    import 'package:appwrite/appwrite.dart';
    
    void main() async {
        final client = Client();
        final database = Database(client);
        try {
            final page1 = await database.listDocuments(
                collectionId: 'movies',
                limit: 25);
            final lastId = page1.documents[page1.documents.length - 1].$id;
    
            // Page 2
            final page2 = await database.listDocuments(
                collectionId: 'movies',
                cursor: lastId);
        } on AppwriteException catch(e) {
            print(e);
        }
    }
    
  • Android

    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import kotlinx.coroutines.GlobalScope
    import kotlinx.coroutines.launch
    import io.appwrite.Client
    import io.appwrite.services.Database
    import io.appwrite.Query
    
    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            val client = Client(applicationContext)
                .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
                .setProject("5df5acd0d48c2") // Your project ID
    
            val database = Database(client)
    
            GlobalScope.launch {
                val page1 = database.listDocuments(
                    collectionId = "[COLLECTION_ID]",
                    limit = 25,
                )
                val lastId = page1.documents[page1.documents.length - 1].$id
    
                // Page 2
                val page2 = database.listDocuments(
                    collectionId = "[COLLECTION_ID]",
                    cursor = lastId,
                )    
            }
        }
    }
  • iOS

    import Appwrite
    
    func main() async throws {
        let client = Client()
          .setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
          .setProject("my_project") // Your project ID
    
        let database = Database(client)
        
        // Page 1
        var list = try await database.listDocuments(
            collectionId: "[COLLECTION_ID]",
            limit: 25,
        )
    
        let lastId = list.documents[list.documents.count - 1].$id
                
        // Page 2
        list = try await database.listDocuments(
            collectionId: "[COLLECTION_ID]",
            cursor: lastId,
        )
        print(list.toMap())
    }

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.