Skip to content

Paginated listing

When building dashboards, file managers, or catalog interfaces, you often need to list files or KV keys stored inside your Storage system. Because buckets and KV namespaces can contain millions of objects, retrieving everything in a single call is impossible.

Layeron Storage provides Cursor-Based Pagination via the .list() operation, allowing you to fetch objects page-by-page efficiently.

This example demonstrates writing an API endpoint for an administrative file dashboard that retrieves up to 20 files under a specific prefix, support filtering, and returns a cursor for the next page.

Terminal window
import { backend } from "@layeron/core"
import { storage } from "@layeron/modules"
const app = backend()
// Declare the media bucket
const mediaBucket = storage.bucket({
name: "project-media",
access: "private",
})
app.use(mediaBucket)
// Route: List Files with Cursor Pagination
app.get("/api/admin/files", async (request) => {
const url = new URL(request.url)
// Extract pagination query parameters
const prefix = url.searchParams.get("prefix") ?? "uploads/" // Filter by directory/folder prefix
const limit = parseInt(url.searchParams.get("limit") ?? "20", 10) // Limit size (default: 20)
const cursor = url.searchParams.get("cursor") ?? undefined; // Get the next-page cursor marker
// Query paginated objects from Storage
const listResult = await mediaBucket.list({
prefix,
limit: Math.min(limit, 100), // Enforce a safety ceiling of 100 items per page
cursor,
})
// Format list items
const files = listResult.items.map((item) => ({
key: item.key,
sizeBytes: item.size,
contentType: item.contentType ?? "application/octet-stream",
etag: item.etag,
uploadedAt: item.storedAt,
metadata: item.metadata,
}))
// Return items and next-page cursor
return Response.json({
files,
pagination: {
limit,
// The cursor is a string token generated by Cloudflare.
// If there are no more items left, cursor will be undefined.
nextCursor: listResult.cursor,
hasMore: !!listResult.truncated, // True if more items match the query
},
})
})

Client-Side Infinite Scroll Integration (Frontend)

Section titled “Client-Side Infinite Scroll Integration (Frontend)”

On your client-side frontend, fetch the first page, and supply the returned nextCursor on subsequent fetches to load more files:

Terminal window
let currentCursor = null
let filesList = []
async function loadNextPage() {
const url = new URL("/api/admin/files", window.location.origin)
url.searchParams.set("prefix", "uploads/")
url.searchParams.set("limit", "20")
if (currentCursor) {
url.searchParams.set("cursor", currentCursor)
}
const response = await fetch(url)
const data = await response.json()
// Append new files to the active list
filesList = [...filesList, ...data.files]
renderFiles(filesList)
// Update cursor pointer for the next scroll trigger
currentCursor = data.pagination.nextCursor
// Hide the "Load More" button if hasMore is false
if (!data.pagination.hasMore) {
document.getElementById("load-more-btn").style.display = "none"
}
}
  1. Prefix Filtering: Cloudflare’s storage engine evaluates the prefix query index-optimally. In this example, querying "uploads/" retrieves only keys starting with that string, effectively simulating hierarchical folder paths.
  2. Limit Boundaries: You can declare a custom limit per page (up to 100 items). Smaller limits speed up edge response and serialization times.
  3. Cursor Iteration: The returned cursor is a secure, stateful string generated by Cloudflare. When supplied in a subsequent .list({ cursor }) call, it acts as a high-performance database offset marker, continuing retrieval exactly where the previous page left off without needing to scan previous records.