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.