Conversation
| - [ ] RLS policies handle anon correctly | ||
| - [ ] Rate limiting for anon endpoints (API gateway) | ||
|
|
||
| **Disable anonymous access entirely:** |
There was a problem hiding this comment.
Maybe worth adding information on disabling anonymous access by unsetting db_anon_role property altogether? See https://docs.postgrest.org/en/v14/references/configuration.html#db-anon-role
|
Hey @gregnr, thank you for putting this up. Just letting you know that I'm working on a new repo structure that has a common build package called More info on this PR |
dc5dccf to
0ee4e36
Compare
|
Hey @gregnr Just a heads up — I rebased your branch to align with the new generic build system we merged in. The main change is that I removed the This should make the PR much easier to review since it now focuses purely on the PostgREST skill/rules you're introducing, without the build tooling changes mixed in. Sorry for the force push! I know that can be disruptive. Let me know if you have any questions or if something looks off. |
| **Important:** If PostgREST doesn't auto-detect the single-param pattern, use `Prefer: params=single-object`: | ||
|
|
||
| ```bash | ||
| curl "http://localhost:3000/rpc/process_order" \ | ||
| -X POST \ | ||
| -H "Content-Type: application/json" \ | ||
| -H "Prefer: params=single-object" \ | ||
| -d '{"customer_id": 123, "items": [...]}' | ||
| ``` |
There was a problem hiding this comment.
This is outdated and should be deleted, this prefer value has been dropped from latest versions.
| **Important:** If PostgREST doesn't auto-detect the single-param pattern, use `Prefer: params=single-object`: | |
| ```bash | |
| curl "http://localhost:3000/rpc/process_order" \ | |
| -X POST \ | |
| -H "Content-Type: application/json" \ | |
| -H "Prefer: params=single-object" \ | |
| -d '{"customer_id": 123, "items": [...]}' | |
| ``` |
|
|
||
| ## Test Mutations with Transaction Rollback | ||
|
|
||
| Use `Prefer: tx=rollback` to execute a mutation and see the result without persisting the changes. Perfect for validation and testing. |
There was a problem hiding this comment.
This requires setting the config db-tx-end = "commit-allow-override". https://docs.postgrest.org/en/v12/references/configuration.html#db-tx-end
Since this is not exposed on Supabase UI, the way users can enable on Supabase is by:
ALTER ROLE authenticator SET pgrst.db_tx_end TO 'commit-allow-override'`.
NOTIFY pgrst, 'reload config';
| -H "Accept: application/vnd.pgrst.plan+json; options=analyze|verbose" | ||
| ``` | ||
|
|
||
| **Note:** Requires proper configuration to allow plan output. In Supabase, this is available in the dashboard or via direct database access. |
There was a problem hiding this comment.
On Supabase, users can configure this with:
ALTER ROLE authenticator SET pgrst.db_plan_enabled to 'true';
This should only be enabled for debugging purposes.
| --- | ||
| title: Use Range Headers for HTTP-Standard Pagination | ||
| impact: MEDIUM | ||
| impactDescription: RFC 7233 compliant pagination with Content-Range response | ||
| tags: pagination, range, headers, rfc7233 | ||
| --- | ||
|
|
||
| ## Use Range Headers for HTTP-Standard Pagination | ||
|
|
||
| Use the `Range` header instead of query parameters for RFC 7233 compliant pagination. Response includes `Content-Range` with total count. | ||
|
|
||
| **Incorrect (mixing pagination approaches):** | ||
|
|
||
| ```bash | ||
| # Query params don't give you total count in headers | ||
| curl "http://localhost:3000/products?limit=10&offset=0" | ||
| # No Content-Range header in response | ||
| ``` | ||
|
|
||
| **Correct (Range header pagination):** | ||
|
|
||
| ```bash | ||
| # Request items 0-9 (first 10) | ||
| curl "http://localhost:3000/products" \ | ||
| -H "Range-Unit: items" \ | ||
| -H "Range: 0-9" | ||
|
|
||
| # Response includes: | ||
| # HTTP/1.1 206 Partial Content | ||
| # Content-Range: 0-9/1000 | ||
|
|
||
| # Next page: items 10-19 | ||
| curl "http://localhost:3000/products" \ | ||
| -H "Range-Unit: items" \ | ||
| -H "Range: 10-19" | ||
|
|
||
| # Open-ended range (from 50 to end) | ||
| curl "http://localhost:3000/products" \ | ||
| -H "Range-Unit: items" \ | ||
| -H "Range: 50-" | ||
| ``` | ||
|
|
||
| **supabase-js:** | ||
|
|
||
| ```typescript | ||
| // supabase-js uses range() which translates to limit/offset | ||
| const { data, count } = await supabase | ||
| .from('products') | ||
| .select('*', { count: 'exact' }) // Request count | ||
| .range(0, 9) | ||
|
|
||
| // count contains total number of rows | ||
| console.log(`Showing ${data.length} of ${count} items`) | ||
| ``` | ||
|
|
||
| **Response headers:** | ||
|
|
||
| ``` | ||
| HTTP/1.1 206 Partial Content | ||
| Content-Range: 0-9/1000 | ||
| Content-Type: application/json | ||
| ``` | ||
|
|
||
| | Header | Meaning | | ||
| |--------|---------| | ||
| | `206 Partial Content` | Partial result returned | | ||
| | `Content-Range: 0-9/1000` | Items 0-9 of 1000 total | | ||
| | `Content-Range: 0-9/*` | Total unknown (no count) | | ||
|
|
||
| **Combine with Prefer: count:** | ||
|
|
||
| ```bash | ||
| # Get exact count | ||
| curl "http://localhost:3000/products" \ | ||
| -H "Range-Unit: items" \ | ||
| -H "Range: 0-9" \ | ||
| -H "Prefer: count=exact" | ||
| # Content-Range: 0-9/1000 (exact count) | ||
|
|
||
| # Get estimated count (faster for large tables) | ||
| curl "http://localhost:3000/products" \ | ||
| -H "Range-Unit: items" \ | ||
| -H "Range: 0-9" \ | ||
| -H "Prefer: count=estimated" | ||
| ``` | ||
|
|
||
| **Benefits over query params:** | ||
| - HTTP standard compliance | ||
| - Total count in response headers | ||
| - Clear partial content semantics (206 vs 200) | ||
| - Client libraries often support Range natively | ||
|
|
||
| **Notes:** | ||
| - `Range-Unit: items` is required (PostgREST specific) | ||
| - Range is 0-indexed and inclusive | ||
| - Without Range header, all rows returned (up to max) | ||
|
|
||
| Reference: [PostgREST Range Headers](https://postgrest.org/en/stable/references/api/pagination_count.html#limits-and-pagination) |
There was a problem hiding this comment.
The Range header currently is wrong in PostgREST and will need a breaking change.
This hasn't been a priority since limit/offset do the same and it's more natural on the supabase-js side.
I'd suggest removing this file altogether to not confuse the agents.
| --- | |
| title: Use Range Headers for HTTP-Standard Pagination | |
| impact: MEDIUM | |
| impactDescription: RFC 7233 compliant pagination with Content-Range response | |
| tags: pagination, range, headers, rfc7233 | |
| --- | |
| ## Use Range Headers for HTTP-Standard Pagination | |
| Use the `Range` header instead of query parameters for RFC 7233 compliant pagination. Response includes `Content-Range` with total count. | |
| **Incorrect (mixing pagination approaches):** | |
| ```bash | |
| # Query params don't give you total count in headers | |
| curl "http://localhost:3000/products?limit=10&offset=0" | |
| # No Content-Range header in response | |
| ``` | |
| **Correct (Range header pagination):** | |
| ```bash | |
| # Request items 0-9 (first 10) | |
| curl "http://localhost:3000/products" \ | |
| -H "Range-Unit: items" \ | |
| -H "Range: 0-9" | |
| # Response includes: | |
| # HTTP/1.1 206 Partial Content | |
| # Content-Range: 0-9/1000 | |
| # Next page: items 10-19 | |
| curl "http://localhost:3000/products" \ | |
| -H "Range-Unit: items" \ | |
| -H "Range: 10-19" | |
| # Open-ended range (from 50 to end) | |
| curl "http://localhost:3000/products" \ | |
| -H "Range-Unit: items" \ | |
| -H "Range: 50-" | |
| ``` | |
| **supabase-js:** | |
| ```typescript | |
| // supabase-js uses range() which translates to limit/offset | |
| const { data, count } = await supabase | |
| .from('products') | |
| .select('*', { count: 'exact' }) // Request count | |
| .range(0, 9) | |
| // count contains total number of rows | |
| console.log(`Showing ${data.length} of ${count} items`) | |
| ``` | |
| **Response headers:** | |
| ``` | |
| HTTP/1.1 206 Partial Content | |
| Content-Range: 0-9/1000 | |
| Content-Type: application/json | |
| ``` | |
| | Header | Meaning | | |
| |--------|---------| | |
| | `206 Partial Content` | Partial result returned | | |
| | `Content-Range: 0-9/1000` | Items 0-9 of 1000 total | | |
| | `Content-Range: 0-9/*` | Total unknown (no count) | | |
| **Combine with Prefer: count:** | |
| ```bash | |
| # Get exact count | |
| curl "http://localhost:3000/products" \ | |
| -H "Range-Unit: items" \ | |
| -H "Range: 0-9" \ | |
| -H "Prefer: count=exact" | |
| # Content-Range: 0-9/1000 (exact count) | |
| # Get estimated count (faster for large tables) | |
| curl "http://localhost:3000/products" \ | |
| -H "Range-Unit: items" \ | |
| -H "Range: 0-9" \ | |
| -H "Prefer: count=estimated" | |
| ``` | |
| **Benefits over query params:** | |
| - HTTP standard compliance | |
| - Total count in response headers | |
| - Clear partial content semantics (206 vs 200) | |
| - Client libraries often support Range natively | |
| **Notes:** | |
| - `Range-Unit: items` is required (PostgREST specific) | |
| - Range is 0-indexed and inclusive | |
| - Without Range header, all rows returned (up to max) | |
| Reference: [PostgREST Range Headers](https://postgrest.org/en/stable/references/api/pagination_count.html#limits-and-pagination) |
| ``` | ||
|
|
||
| **Safety configuration:** | ||
| - PostgREST `db-max-rows` limits affected rows |
There was a problem hiding this comment.
This is false, should be deleted
| - PostgREST `db-max-rows` limits affected rows |
| **Safety configuration:** | ||
| - PostgREST `db-max-rows` limits affected rows | ||
| - RLS policies can restrict updates | ||
| - `max-affected` header provides request-level limit |
There was a problem hiding this comment.
This is a work in progress but should be noted supabase/postgres#1308
| - `max-affected` header provides request-level limit | |
| - `max-affected` header provides request-level limit | |
| - Enable the `safeupdate` extension to prevent unqualified UPDATEs |
| 2. Use RLS policies to restrict deletions | ||
| 3. Use `max-affected` header for safety limits | ||
| 4. Consider soft deletes for critical data | ||
| 5. Implement backup/audit trails |
There was a problem hiding this comment.
| 5. Implement backup/audit trails | |
| 5. Implement backup/audit trails | |
| 6. Enable the safeupdate module |
| - Only works with **to-one** relationships (M2O, O2O) | ||
| - Cannot spread to-many relationships (would create multiple rows) |
There was a problem hiding this comment.
This is outdated. @gregnr Perhaps you can ask the LLM to redo this considering the v13 version? Specifically this feature: https://docs.postgrest.org/en/v13/references/api/resource_embedding.html#spread-to-many-relationships
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
0ee4e36 to
c5789d8
Compare
Adds an agent skill for PostgREST / supabase-js best practices. Particularly helps LLMs navigate PostgREST syntax which is very powerful but can be complex. Demonstrates how to use common patterns directly via cURL but also using
supabase-js(which can be translated pretty easily to other sdk languages).