The PGRST103 error (HTTP 416: Range Not Satisfiable) occurs when you request a data range using .range() or pagination parameters that exceeds the available rows in your Supabase table. This guide walks through validating pagination bounds, fetching total counts before querying, and handling out-of-bounds requests gracefully.
PostgREST enforces HTTP Range semantics before executing your query. When you use the `.range()` method or supply `Range` headers to paginate through results, PostgREST validates that the requested offset and limit fall within the dataset. If the offset exceeds the total number of rows, or if you request a negative limit, PostgREST returns HTTP 416 (Range Not Satisfiable) with a PGRST103 error body. This is a client-side input validation problem: your pagination parameters do not match the available data, so the server refuses to serve that unsatisfiable range.
Before calling .range(), confirm that both the offset and limit are positive integers and that the sum does not exceed your expected row count:
const pageSize = 10;
const page = 5; // user clicks page 5
// Calculate offset (0-based)
const offset = pageSize * (page - 1); // 40
// Validate before range()
if (offset < 0 || pageSize <= 0) {
console.error("Invalid pagination parameters");
return;
}
const { data, error } = await supabase
.from('your_table')
.select('*')
.order('id')
.range(offset, offset + pageSize - 1);
if (error?.code === 'PGRST103') {
// Offset exceeds available rows
console.error("Requested page is beyond available data");
}The range(from, to) method is 0-based and inclusive, so .range(0, 9) fetches 10 rows.
Use count: 'exact' to get the total number of rows before issuing range queries. This lets you validate the offset against a known total:
const { count, error: countError } = await supabase
.from('your_table')
.select('*', { count: 'exact', head: true })
.eq('status', 'active'); // Apply same filters as your data query
if (countError) {
console.error("Failed to fetch count:", countError);
return;
}
console.log(`Total rows: ${count}`);
// Now calculate safe pagination
const pageSize = 10;
const maxPage = Math.ceil(count / pageSize);
const requestedPage = Math.min(page, maxPage); // Clamp to valid range
const offset = pageSize * (requestedPage - 1);
const limit = Math.min(pageSize, count - offset);
const { data } = await supabase
.from('your_table')
.select('*')
.eq('status', 'active') // Same filter as count
.order('id')
.range(offset, offset + limit - 1);This prevents ever requesting a range beyond the available data.
Without an explicit order, the range behavior is undefined and can change if your data or indexes shift. Always add a deterministic order clause:
const { data } = await supabase
.from('your_table')
.select('*')
.order('id', { ascending: true }) // Explicit order
.range(0, 9);This ensures rows are returned in a consistent order and pagination is predictable.
Wrap range queries in error handling so out-of-bounds requests do not crash your UI:
async function fetchPage(page, pageSize) {
try {
const offset = pageSize * (page - 1);
const { data, error, status } = await supabase
.from('your_table')
.select('*')
.order('id')
.range(offset, offset + pageSize - 1);
if (status === 416 || error?.code === 'PGRST103') {
// Out of bounds, return empty array
console.warn(`Page ${page} is beyond available data`);
return [];
}
if (error) throw error;
return data;
} catch (err) {
console.error("Fetch error:", err.message);
throw err;
}
}This prevents 416 errors from breaking user experience when pagination parameters are invalid.
The PGRST103 error message was truncated in older PostgREST versions (particularly when using Prefer: count=exact). If you are running PostgREST 10.x, upgrade to 12.x or later for complete error details and better RFC 7233 Range compliance:
# Supabase Cloud upgrades automatically
# Self-hosted: check your PostgREST version
psql -d your_db -c "SELECT version();"
# Update PostgREST container or binary to 12.x+Newer versions include fixes for edge cases like when offset equals the total row count.
PGRST103 is part of PostgREST's HTTP Range validation (error group 3) that enforces RFC 7233 semantics before any SQL runs. The error is deterministic: if your offset exceeds the row count, PostgREST always rejects the range. This is different from a 500 or query timeoutβit means your pagination parameters are mathematically invalid. For production apps, always fetch count and clamp pagination values to [0, max_page). In edge cases where data changes rapidly, a count-then-query pattern can still produce stale results if rows are deleted between the count and the range query, so idempotent offset logic (e.g., clamping to max_page) is safer than throwing errors.
email_conflict_identity_not_deletable: Cannot delete identity because of email conflict
How to fix "Cannot delete identity because of email conflict" in Supabase
mfa_challenge_expired: MFA challenge has expired
How to fix "mfa_challenge_expired: MFA challenge has expired" in Supabase
conflict: Database conflict, usually related to concurrent requests
How to fix "database conflict usually related to concurrent requests" in Supabase
phone_exists: Phone number already exists
How to fix "phone_exists" in Supabase
StorageApiError: resource_already_exists
StorageApiError: Resource already exists