feat(docs) Add a SKILL to write stronger documentation and apply it to guides/api/securing-your-api (#48018)

Closes DOCS-1176

## Summary

This PR adds documentation-writing guidance for humans and agents, then
applies it to the “Securing your API” guide.

## Changes

- Add a documentation word list based on Google’s style guide and
existing MDX lint rules.
- Add a shared `docs-guides` Agent Skill with Cursor and Claude
integration.
- Expand contributing guidance for information types, procedures,
chunking, links, admonitions, grammar, and terminology.
- Restructure “Securing your API” into contextual and procedural
sections.
- Add section navigation, cross-references, transitions, and procedural
outcomes.
- Reduce repeated admonitions and improve scannability.

## Manual testing

1. Open `/docs/guides/api/securing-your-api` in Preview and compare to
Live.
https://docs-git-docs-restructure-api-supabase.vercel.app/docs/guides/api/securing-your-api
2. See that the content is improved and clear with no important context
removed.
3. See the Admonitions that are no longer marked as admonitions. See the
content still makes sense.
4. Review the diff of `CONTRIBUTING.md` and `WORD_LIST.md`.
5. See that you agree with the new rules and that they are clear.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Updated documentation-writing guidelines with clearer standards for
structure, formatting, components, diagrams, terminology, and
navigation.
* Added a comprehensive word and style reference for consistent
documentation language.
* Reworked the API security guide with clearer guidance on grants, RLS,
dedicated schemas, pre-request checks, rate limiting, and API keys.
* Added documentation authoring workflow guidance, including validation
and formatting steps.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Miranda Limonczenko authored and GitHub committed 2026-07-17 18:48:45 +00:00
1 parent c914163a06
commit c1d010a699
4 files changed
+1324 -257

No files matched your search

+97
View File
@@ -0,0 +1,97 @@
---
name: docs-content
description: Write, edit, organize, and review Supabase content anywhere in apps/docs — guides, explainers, tutorials, troubleshooting entries, reference docs, and partials. Use for MDX/TOML authoring, frontmatter, navigation, terminology, links, code samples, content listings, and docs validation.
---
# Supabase docs authoring
## Sources of truth
Before changing docs content:
1. Read `apps/docs/CONTRIBUTING.md` for content types, structure, components, and
style.
2. Read `apps/docs/WORD_LIST.md` for preferred terminology, spelling, and
capitalization.
3. Inspect nearby content of the same type and the relevant navigation section
before deciding on file placement or structure. Guides, explainers, and
tutorials live under `apps/docs/content/guides`. Troubleshooting entries live
under `apps/docs/content/troubleshooting` and use TOML frontmatter — follow
`_template.mdx` in that directory rather than a guide's YAML frontmatter.
Reference docs are generated from `apps/docs/spec` and library source, so
look for the spec file or repo definition instead of editing rendered output
directly.
When guidance conflicts, follow `apps/docs/CONTRIBUTING.md`. Match literal code,
API names, UI labels, and third-party product names even when they differ from the
word list.
## Writing workflow
1. Identify the document type: explainer, tutorial, guide, or reference, per
`apps/docs/CONTRIBUTING.md`. A guide is a concise procedure for a targeted
task; a tutorial covers a larger goal and includes more explanatory context;
an explainer is conceptual and prose-based; reference content is factual,
like a dictionary entry. Troubleshooting entries follow their own TOML
structure rather than these four types.
2. Define the reader's goal and prerequisites before drafting.
3. Classify substantial sections as contextual, procedural, or reference content.
In a mixed page, group sections by information type so that context doesn't
interrupt the procedural path.
4. For a long or mixed page, add a short introduction that links to its major
section groups and tells readers when to use each one. Skip this navigation
when a short page is already easy to scan.
5. Connect contextual sections to their corresponding procedures when useful.
Add introductions to section groups, transitions between information types,
and outcomes after procedures. Don't link every adjacent section.
6. Use second person, present tense, short paragraphs, and ordered steps for
sequential actions.
7. Search `apps/docs/WORD_LIST.md` when introducing or reviewing technical terms,
UI actions, abbreviations, and potentially ambiguous language.
8. Keep code samples executable in their stated context and consistent with
repository formatting. Clearly mark intentionally omitted code. Use lowercase
SQL keywords.
9. Reuse repeated content through `apps/docs/content/_partials` instead of copying
it.
10. Add new guide, explainer, and tutorial pages to
`apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts`.
File placement alone doesn't add a page to navigation. Troubleshooting
entries are indexed automatically and don't need a navigation entry.
11. Use `/docs/...` paths for pages in Supabase docs and site-root paths such as
`/dashboard` for pages outside docs. Use descriptive link text and sparse
admonitions with the appropriate severity.
## Validation
From `apps/docs`, run:
```bash
pnpm lint:mdx
pnpm build:guides-markdown
```
`pnpm lint:mdx` covers all content under `apps/docs/content`, including
troubleshooting entries. `pnpm build:guides-markdown` only applies to guides,
explainers, and tutorials.
From the repository root, run `pnpm format` to apply Prettier to any changed
MDX (and other) files. This enforces repo-wide formatting rules, including
lowercase SQL keyword casing in code samples.
Run broader type checking or tests when the change affects MDX components,
content listings, navigation code, or generated output.
For a mixed page, verify that context and procedures are grouped, introductory
navigation links resolve to the intended sections, related context and procedures
are cross-referenced where useful, and transitions make the reading path clear.
Treat lint replacements as suggestions when context matters. Rewrite the sentence
instead of applying a replacement that changes its technical meaning.
Anchor IDs are generated from heading text at render time, and nothing in CI
checks that `#anchor` links still resolve. Before renaming, removing, or
substantially rewording a heading, run
`grep -rn "#<old-anchor-slug>" apps/docs/content` to find in-page and
cross-file links that target it, and update every match. If a heading needs a
stable anchor independent of its wording, pin it with a custom anchor, for
example `## Some heading [#some-heading]`.
+107 -78
View File
@@ -8,13 +8,14 @@ Here are some general guidelines on writing docs for Supabase.
## General principles
Docs should be helpful, quick to read, and easy to understand. We have an audience of global readers who speak different native languages.
Write helpful, concise, and understandable documentation. We have a global audience whose members speak different native languages.
To make docs as clear as possible:
- Write for the user. Think about what task they want to complete by reading your doc. Tell them what, and only what, they need to know.
- Write like you talk. Use words and sentences that sound natural when speaking. Cut unnecessary words. Read your writing out loud to help you choose the clearest and simplest phrases.
- Each paragraph should have one topic only. Start a new paragraph whenever you change the topic. Don't worry about paragraphs being too short.
- Write like you talk. Conversational English is easier for a global audience to understand and localize. Many readers who use English as an additional language learn conversational rather than academic English. Use words and sentences that sound natural when speaking. Cut unnecessary words. Read your writing out loud to help you choose the clearest and simplest phrases.
- Prefer short, direct sentences. Express one relationship at a time, and avoid unnecessary compound structures. This makes each sentence easier to understand, localize, and interpret consistently.
- Cover one topic in each paragraph. Start a new paragraph whenever you change the topic. Don't worry about paragraphs being too short.
- Avoid using idioms and colloquialisms, such as `piece of cake`. These phrases are often specific to a region or culture.
- Refer to the reader as `you`. Don't use `we` to refer to the reader. Use `we` only to refer to the Supabase team.
@@ -31,7 +32,7 @@ Explainers help the reader to learn a topic. They are conceptual and mostly pros
- Some examples of _when_ to use it
- A high-level explanation of _how_ it works
They shouldn't include:
Explainers don't include:
- Instructions on how to use it
@@ -39,7 +40,7 @@ They shouldn't include:
Tutorials are goal-oriented. They help a reader to finish a large, complex goal, such as setting up a web app that uses multiple Supabase features.
Tutorials mix prose explanations with procedures (lists of steps for the reader to follow). They provide context for why certain instructions are given.
Tutorials mix prose explanations with procedures. Procedures are lists of steps for the reader to follow. Tutorials provide context for why certain instructions are given.
For inspiration, see [an example of a tutorial](https://supabase.com/docs/guides/getting-started/tutorials/with-nextjs).
@@ -47,22 +48,35 @@ For inspiration, see [an example of a tutorial](https://supabase.com/docs/guides
Guides are also goal-oriented, but they focus on shorter, more targeted tasks. For example, a guide might explain how to set up user login for an app.
Guides contain mostly procedures. Think of an instruction manual for building a desk: it's a list of concise steps that the user can go through quickly.
Guides contain mostly procedures: concise steps that readers can follow in sequence.
For inspiration, see [an example of a guide](https://supabase.com/docs/guides/auth/auth-email).
Begin each guide with a sentence that declares its intent, such as `This guide explains how to set up email login.` This helps readers and agents confirm that the guide matches their goal and expected outcome.
Keep procedures focused on what the reader must do. Move substantial background or conceptual explanations into a separate section or an explainer. Cross-reference the authoritative explanation instead of repeating it in the procedure. This keeps the action path scannable, gives readers optional depth, and maintains one source of truth.
- Recommended: `This guide explains how to enable Row Level Security. To learn how Row Level Security controls access, see [Row Level Security](...).`
- Not recommended: Begin with several paragraphs about how Row Level Security works before stating what the guide helps the reader do.
**Mixed information types:** When a guide contains substantial context or reference material, group sections by information type. Keep contextual and reference sections separate from the procedure group so that background information doesn't interrupt the action path.
**Navigation:** Begin a long guide with a short outline of its major section groups. Link to each group and state when a reader should use it. Don't add section navigation to a short guide when the headings are already easy to scan.
**Cross-references and glue:** Connect contextual sections to their corresponding procedures when the relationship helps readers navigate. Add a brief introduction to each section group, a transition when the information type changes, and an outcome after a procedure. Add links selectively rather than linking every adjacent section.
For inspiration, see [an example of a guide](/docs/guides/auth/auth-email-passwordless).
### Reference
References are factual and to the point. Think of dictionary entries.
They should include:
References include:
- Function parameters
- Return types
- Code samples
- Warnings for critical errors (for example, missteps that can cause data loss)
- Warnings about critical errors, such as missteps that can cause data loss
They shouldn't include:
References don't include:
- Explanations of the context for a feature
- Examples of use cases
@@ -72,7 +86,7 @@ They shouldn't include:
Most docs pages are contained in the `apps/docs/content` directory. Some docs sections are federated from other repositories, for example [`pg_graphql`](https://github.com/supabase/pg_graphql/tree/master/docs). Reference docs are generated from spec files in the `spec` directory.
You can usually identify a federated or reference doc because it uses a Next.js dynamic route (for example, `[[...slug]].tsx`). Look for the spec file import or the repo definition to find the content location.
You can usually identify a federated or reference doc because it uses a Next.js dynamic route. For example, it might use `[[...slug]].tsx`. Look for the spec file import or the repo definition to find the content location.
Example spec file import:
@@ -94,12 +108,12 @@ Check the sections for [guide structure](#guide-structure) and [reference struct
## Guide structure
The Supabase docs use [MDX](https://mdxjs.com/). Guides are written in unstructured prose as MDX documents.
The Supabase docs use [MDX](https://mdxjs.com/). Guides are MDX documents that combine concise prose with structured procedures.
Adding a new guide requires:
- YAML frontmatter
- A navigation entry (in a separate file)
- A navigation entry in a separate file
Frontmatter looks like this. `title` is mandatory. There are also optional properties that you can use to control the page display, including `subtitle`, `tocVideo`, and `hideToc`.
@@ -112,7 +126,7 @@ hideToc: true
The navigation is defined in [`NavigationMenu.constants.ts`](https://github.com/supabase/supabase/blob/master/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts).
Add an entry with the `name`, `url`, and (optional) `icon` for your page.
Add an entry with the `name`, `url`, and optional `icon` for your page.
## Reference structure
@@ -120,13 +134,13 @@ Reference docs are produced from the reference specs and library source code. A
### Common spec file
Each type of library (for example, language SDK or CLI) has a common spec file. For example, see the [spec file for the language SDKs](https://github.com/supabase/supabase/blob/master/apps/docs/spec/common-client-libs-sections.json). This file contains definitions for the common SDK functions:
Each type of library, such as a language SDK or CLI, has a common spec file. For example, see the [spec file for the language SDKs](https://github.com/supabase/supabase/blob/master/apps/docs/spec/common-client-libs-sections.json). This file contains definitions for the common SDK functions:
- **id** - Identifies the function
- **title** - Human-readable title
- **slug** - URL slug
- **product** - Supabase product that owns the function. For example, database operations are owned by `database`, and auth functions are owned by`auth`
- **type** - `function` for a structured function definition or `markdown` for a prose explainer section.
- `id`: Identifies the function
- `title`: Provides the human-readable title
- `slug`: Provides the URL slug
- `product`: Identifies the Supabase product that owns the function. For example, database operations are owned by `database`, and Auth operations are owned by `auth`.
- `type`: Uses `function` for a structured function definition or `markdown` for a prose explainer section
To add a new function, manually add an entry to this common file.
@@ -140,10 +154,10 @@ Each function contains a description, code examples, and optional notes. The par
If you're a library maintainer, follow these steps when updating function parameters or return values:
1. Get your changes merged to `master` in your library
2. This will kick off an action that automatically updates the spec file in the library's `gh-pages` branch
3. Run `make` in `/spec` of the `supabase/supabase` repo. This will regenerate all of the `tsdoc` files that the docs site uses
4. You should now see the changes you've made in the docs site locally
1. Merge your changes into the library's `master` branch.
2. Wait for the action to update the specification in the `gh-pages` branch.
3. Run `make` from `apps/docs/spec` in the `supabase/supabase` repository.
4. Verify the changes on your local documentation site.
## Content reuse
@@ -153,23 +167,32 @@ To use a partial, import it into your MDX file. You can also set up a partial to
## Components and elements
Docs include normal Markdown elements such as lists, and custom components such as admonitions (callouts).
Docs include normal Markdown elements such as lists and custom components such as admonitions, also known as callouts.
Here are some guidelines for using elements:
### Admonitions
Admonitions (or callouts) draw reader attention to an important point or an aside. They highlight important information, but get less effective if they're overused.
Admonitions draw reader attention to an important point or an aside. They highlight important information, but get less effective if they're overused.
Use admonitions sparingly. Don't stack them on top of each other.
Use an admonition when a reader might otherwise miss information that affects the outcome of their task, or when you want to separate helpful but optional guidance from the main flow. Don't use an admonition for information that belongs in the main explanation or procedure.
Use admonitions sparingly. Don't stack them on top of each other or use them as decoration.
Begin every admonition with its impact and purpose: the "so what." Use the first sentence to tell the reader why the information matters, such as what could happen, what changes, or what benefit they gain. Add background or instructions after the impact is clear.
For example:
- Recommended: `Deleting this project permanently removes its database and backups. Export any data that you want to keep before you continue.`
- Not recommended: `Before you continue, there are a few things that you should know about project deletion.`
Choose the appropriate `type` for your admonition:
- `danger` to warn the user about any missteps that could cause data loss or data leaks
- `deprecation` to notify the user about features that are (or will soon be) deprecated
- `caution` to warn about anything that could cause a bug or serious user inconvenience
- `tip` to point out helpful but optional actions
- `note` for anything else
- `danger`: Warn about actions or conditions that could cause data loss, expose sensitive data, or create another severe and difficult-to-reverse outcome. State the consequence first, and then explain how to avoid it.
- `deprecation`: Identify a deprecated feature or behavior. State how the change affects the reader, and then provide the supported alternative or migration path.
- `caution`: Warn about behavior that could cause bugs, failed operations, unexpected results, or serious inconvenience but doesn't rise to the severity of `danger`.
- `tip`: Share an optional shortcut, optimization, or best practice that helps the reader complete the task more effectively. The main procedure must still work without it.
- `note`: Highlight an important prerequisite, constraint, or clarification that doesn't represent a risk. If the information is essential to completing a step, include it in the procedure instead.
```
<Admonition type="note" title="Optional title">
@@ -189,13 +212,13 @@ Keep code lines short to avoid scrolling. For example, you can split long shell
- **JavaScript/TypeScript**
The `supabase` repo uses Prettier, which also formats JS/TS in code blocks. Your PR is blocked from merging if the Prettier check fails. Ensure that your code blocks are formatted by running `npm run format`, or by setting up auto-formatting in your IDE.
The `supabase` repository uses Prettier, which also formats JS/TS in code blocks. Your PR is blocked from merging if the Prettier check fails. From the repository root, run `pnpm format`, or set up automatic formatting in your IDE.
- **SQL**
Prefer lowercase for SQL. For example, `select * from table` rather than `SELECT * FROM table`.
Optionally specify a filename for the codeblock by including it after the opening backticks and language specifier:
Optionally specify a filename for the code block by including it after the opening backticks and language specifier:
````md
```ts environment.ts
@@ -211,6 +234,16 @@ Optionally highlight lines by using `mark=${lineNumber}`.
```
````
### Emphasis
Use **bold**, _italics_, and `code` formatting for distinct purposes. Don't use them interchangeably or to add visual emphasis alone.
- **Bold**: Mark UI labels the reader interacts with, such as buttons, menu items, and field names. For example, `Click **Save**.` Also use bold for a term the reader must not miss, such as `**Never** commit your service role key.`
- _Italics_: Introduce a new term the first time you define it, or reference a title, such as a book or a third-party product name written in italics by convention. Use italics sparingly. Don't use italics for UI labels or for general emphasis.
- `Code`: Mark anything the reader types or copies verbatim, or anything the system reads literally. This includes filenames, paths, commands, flags, environment variables, function and parameter names, configuration keys, and literal values. For example, `` Set `SUPABASE_URL` in your `.env` file. ``
If a phrase fits more than one category, pick the most specific one. A command name is `code`, not **bold**, even though the reader also interacts with it.
### Content listings
Overview and index pages use a single `<ContentListings id="..." />` component for curated link sections such as "Get started", "Next steps", "Examples", or "Resources". Refer to [`storage.data.ts`](data/content-listings/storage.data.ts) and [`storage.mdx`](content/guides/storage.mdx) for a full example.
@@ -218,7 +251,7 @@ Overview and index pages use a single `<ContentListings id="..." />` component f
**Prompt to add content listings:**
```text
Add a content listing block for [TOPIC] / [SECTION] (for example, Storage / Examples).
Add a content listing block for [TOPIC] / [SECTION]. For example, use Storage / Examples.
Follow CONTRIBUTING § Content listings in apps/docs.
Copy structure from `storageGetStarted` in apps/docs/data/content-listings/storage.data.ts.
Pick a globally-unique kebab-case id like `[topic]-[section]`.
@@ -227,11 +260,11 @@ Run `pnpm test:local lib/content-listings.test.ts` from apps/docs.
**Manually add content listings:**
1. Add or update a `ContentListingGroup` export in [`data/content-listings/[topic].data.ts`](data/content-listings/). The `id` field must be globally unique across all listing groups (e.g. `storage-get-started`, not just `get-started`) — it is used both as the lookup key and as the telemetry `listingId`.
1. Add or update a `ContentListingGroup` export in [`data/content-listings/[topic].data.ts`](data/content-listings/). The `id` field must be globally unique across all listing groups. For example, use `storage-get-started` rather than `get-started`. The ID is both the lookup key and the telemetry `listingId`.
2. Place the component inline in guide MDX, for example `<ContentListings id="storage-get-started" />`. Use a partial only when the block is reused or gated with `$Show` at the partial level.
3. Run `pnpm test:local lib/content-listings.test.ts` from `apps/docs`.
Code snippets for manually adding content listings are available in [`.vscode/content-listing.code-snippets`](../../.vscode/content-listing.code-snippets): `cl-data` (data export with namespaced id) and `cl-inline` (MDX component).
Code snippets for manually adding content listings are available in [`.vscode/content-listing.code-snippets`](../../.vscode/content-listing.code-snippets). Use `cl-data` for a data export with a namespaced ID. Use `cl-inline` for an MDX component.
### Footnotes
@@ -240,7 +273,7 @@ Don't use footnotes.
### Graphs
Render diagrams (flowcharts, sequence diagrams, entity-relationship diagrams, etc.) by writing a fenced code block with `mermaid` as the language. The MDX renderer routes these blocks through the shared `Mermaid` component, so theming follows light/dark mode automatically.
Render diagrams, including flowcharts, sequence diagrams, and entity-relationship diagrams, by writing a fenced code block with `mermaid` as the language. The MDX renderer routes these blocks through the shared `Mermaid` component, so theming follows light and dark mode automatically.
For the full list of supported diagram types and their syntax, see the [official Mermaid diagram reference](https://mermaid.js.org/intro/syntax-reference.html).
@@ -259,7 +292,7 @@ sequenceDiagram
```
````
Flowchart (`flowchart` accepts a direction like `LR`, `TD`, etc.):
The `flowchart` keyword accepts a direction such as `LR` or `TD`:
````mdx
```mermaid
@@ -273,9 +306,9 @@ flowchart LR
A few tips:
- Use the standard Mermaid diagram keywords (`sequenceDiagram`, `flowchart`, `erDiagram`, etc.) on the first line of the block.
- Use a standard Mermaid diagram keyword, such as `sequenceDiagram`, `flowchart`, or `erDiagram`, on the first line of the block.
- Keep diagrams focused on a single flow or concept. If a diagram gets too dense, split it into multiple smaller diagrams.
- Wrap node labels that contain special characters (`*`, `/`, spaces, punctuation) in double quotes, for example `A["content/**/*.md"]`.
- Wrap node labels that contain special characters in double quotes. Special characters include `*`, `/`, spaces, and punctuation. For example, use `A["content/**/*.md"]`.
- Don't hardcode colors. The component themes the diagram automatically so it matches both light and dark mode.
- Use diagrams to support the prose, not replace it. Explain the key takeaway in text near the diagram.
@@ -283,17 +316,34 @@ A few tips:
Images are uploaded in the `apps/docs/public/img` folder.
For vector illustrations, use `svg`. For screenshots and non-vector graphics, use `png`. (These are automatically converted to `webp` for supported browsers.)
For vector illustrations, use `.svg` files. For screenshots and non-vector graphics, use `.png` files. Supported browsers receive `.webp` versions automatically.
Redact any sensitive information, such as API keys.
### Links
Link text should be descriptive. The reader should understand where the link goes from reading the link text alone. This is important for accessibility. For example, don't use `here` as link text.
Use descriptive link text that tells the reader where the link goes. This is important for accessibility. For example, don't use `here` as link text.
But link text shouldn't be too long. Use the shortest part of the link that is descriptive enough. For example, `see the [reference section](/link)` rather than `[see the reference section](/link)`.
Keep link text concise. Use the shortest part of the link that is descriptive enough. For example, `see the [reference section](/link)` rather than `[see the reference section](/link)`.
Use relative links when linking within the `supabase.com` domain. For example, `[link to another page in Supabase docs](/docs/guides/getting-started)`.
Don't include the `https://supabase.com` origin when linking to pages on `supabase.com`. Use a `/docs/...` path for a page in Supabase docs, such as `[getting started](/docs/guides/getting-started)`. Use a site-root path for a page outside docs, such as `[open the Supabase Dashboard](/dashboard)`.
### Procedures
Use a procedure when a human or agent must perform actions to reach an outcome. The procedural format makes that expectation explicit.
Write sequential actions as an ordered list. Begin each step with an imperative verb, and include one action or a closely related set of actions per step. Give the reader enough context to know where to act.
Apply the [Information Mapping chunking principle](https://informationmapping.com/blogs/news/writing-for-the-web-the-magical-number-seven-plus-or-minus-two) to procedures. Present 7 ± 2 related steps at a time. This gives readers a manageable chunk of five to nine actions. Aim for the lower end of the range when the task is complex or unfamiliar.
If a procedure has more than nine steps, group related steps into named phases or smaller procedures. If one step contains multiple distinct actions, split it into separate steps. Don't add steps to reach a minimum. The range is a guideline for organizing information, not a required procedure length.
An apparent one-step procedure can become two steps when there is a real orientation action. For example:
1. Open a terminal in your project directory.
2. Run `supabase start`.
The first step establishes the operating context for both readers and agents. Don't add a redundant orientation step to a genuinely atomic instruction. For example, write `Click **Save**.` instead of adding `Locate the **Save** button` as a separate step.
### Lists
@@ -319,7 +369,7 @@ Don't nest lists more than two deep.
Use tabs to provide alternative instructions for different platforms or languages.
The `queryGroup` param is optional. It lets you link directly to a tab by using the query group as a query param in the URL, for example: `https://supabase.com/docs/my-page?packagemanager=ts`
The optional `queryGroup` prop lets you link directly to a tab. For this example, use `/docs/my-page?packagemanager=npm`.
```
<Tabs
@@ -344,59 +394,38 @@ The `queryGroup` param is optional. It lets you link directly to a tab by using
### Videos
Include videos as TOC (Table of Contents) videos rather than putting them in the main text.
Include videos as table of contents (TOC) videos instead of placing them in the main text.
You can define a TOC video in the page frontmatter:
```yaml
---
tocVideo: 'rzglqRdZUQE',
tocVideo: 'rzglqRdZUQE'
---
```
## Styling, formatting, and grammar
Don't worry too much about grammar rules. Grammar is useful if, and only if, it makes your writing clearer. For example, you can use sentence fragments if they're self-explanatory.
Grammar is useful when it makes your writing clearer. Use complete sentences by default because they identify the actor and action. This reduces ambiguity for readers, translators, and agents. Use sentence fragments only where they improve scanning, such as headings, labels, or short list items.
Headings guide the reader's eye and organize the page, but they don't carry information by themselves. Make the content beneath a heading understandable without relying on the heading. The first sentence can restate the heading, even if it sounds redundant. Readers often skim headings and then return to the section that interests them, so use the opening sentence to confirm the context.
Don't use parentheses for asides or supplementary information. Rewrite that information as part of the sentence or as a separate sentence. Use parentheses to introduce an acronym after spelling out its meaning, such as full-text search (FTS), or to mark an item as `(Optional)`. Parentheses that are required by Markdown links or code syntax aren't prose parentheticals.
That said, a few rules help keep the docs concise, consistent, and clear:
- Format headings in sentence case. Capitalize the first word and any proper nouns. All other words are lowercase. For example, `Set up authentication` rather than `Set Up Authentication`.
- Use the Oxford comma (a comma before the `and` that marks the last item in a list). For example, `realtime, database, and authentication` rather than `realtime, database and authentication`.
- Use the Oxford comma. Place a comma before the `and` that marks the last item in a list. For example, use `functions, tables, and indexes` rather than `functions, tables and indexes`.
- Use the present tense as much as possible. For example, `the AI assistant answers your question` rather than `the AI assistant will answer your question`.
## Word usage and spelling
Use American English. If in doubt, consult the [Merriam-Webster dictionary](https://www.merriam-webster.com/).
Here are some exceptions and Supabase-specific guidelines.
### General word usage
- **Filler words**: You can often make your writing more concise by removing these words. (Some of these words can also sound patronizing.) Most filler, marketing, and vague-verb phrases are flagged by `supa-mdx-lint` as warnings, with suggested alternatives where a direct replacement exists. Run `pnpm lint:mdx` in `apps/docs` to check your changes.
- Actually
- Easy, easily
- Just
- Let's
- Please
- Simple, simply
- **UI elements**
- Buttons are `click`ed.
- Checkboxes are `select`ed.
- Toggles are `enable`d and `disable`d.
- Labels of UI elements are bolded. For example, `Click **Confirm**.`
### Word list
- `Frontend` isn't hyphenated (not `front-end`).
- `Backend` isn't hyphenated (not `back-end`).
- `Login` is a noun. `Log in` is a verb.
- `Postgres` is capitalized, except in code, and used instead of `PostgreSQL`.
- `Setup` is a noun. `Set up` is a verb.
- `Supabase` is capitalized (not `supabase`), except in code.
- `Supabase Platform` is in title case (not `Supabase platform`).
Follow the [Supabase documentation word list](./WORD_LIST.md) for preferred spelling, capitalization, and usage. The word list includes the terminology rules checked by `supa-mdx-lint`. Run `pnpm lint:mdx` in `apps/docs` to check your changes.
## Search
Search is handled using a Supabase instance. During CI, [a script](https://github.com/supabase/supabase/blob/master/apps/docs/scripts/search/generate-embeddings.ts) aggregates all content sources (for example, guides, reference docs, etc), indexes them using OpenAI embeddings, and stores them in a Supabase database.
Search uses a Supabase instance. During CI, [a script](https://github.com/supabase/supabase/blob/master/apps/docs/scripts/search/generate-embeddings.ts) collects guides, reference documentation, and other content. The script creates OpenAI embeddings and stores the search index in a Supabase database.
Search uses a hybrid of native Postgres FTS and embedding similarity search based on [`pgvector`](https://github.com/pgvector/pgvector). At runtime, a PostgREST call triggers the RPC that runs the weighted FTS search, and an [Edge Function](https://github.com/supabase/supabase/tree/master/supabase/functions) is executed to perform the embedding search.
Search combines native Postgres full-text search (FTS) with embedding similarity search based on [`pgvector`](https://github.com/pgvector/pgvector). At runtime, a PostgREST call invokes the weighted FTS RPC. An [Edge Function](https://github.com/supabase/supabase/tree/master/supabase/functions) runs the embedding search.
+920
View File
@@ -0,0 +1,920 @@
# Supabase documentation word list
Use this list when you write or review Supabase documentation. It records preferred
spelling, capitalization, and usage for terms that commonly appear in developer
documentation.
This list supplements [CONTRIBUTING.md](./CONTRIBUTING.md). If the two documents
conflict, follow `CONTRIBUTING.md`. Match literal code, API names, UI labels, and
third-party product names even when they differ from this guidance, and format them
as code or UI text as appropriate.
Many unambiguous rules in this list are checked by `supa-mdx-lint`. Run
`pnpm lint:mdx` from `apps/docs` after editing MDX. A lint warning still requires
judgment: rewrite the sentence instead of applying a replacement that changes its
meaning.
## Numbers and symbols
### `+`
Don't use `+` to mean _or later_.
- Recommended: Postgres 15 or later
- Not recommended: Postgres 15+
### `&`
Use _and_ instead of `&` in prose, headings, navigation, and tables of contents.
Keep `&` when it is part of a UI label, code, or a space-constrained table or
diagram label.
## A
### abbreviations
Spell out an unfamiliar abbreviation on first use. Don't expand familiar technical
abbreviations such as API, CPU, HTML, HTTP, or SQL unless the audience needs it.
Use `for example` instead of `e.g.` when practical. If space is constrained, write
`e.g.` with both periods. Use `that is` instead of `i.e.`.
The linter warns about malformed forms of `e.g.` and about `i.e.`.
### abort
Use _stop_, _exit_, _cancel_, or _end_ in general prose. Use `abort` when it is the
name of a command, signal, API, or operation.
### above
Don't use _above_ to refer to a location in a document or UI. Link to or name the
section or control. For versions, use _later_.
### access
When possible, use a more specific verb such as _view_, _find_, _edit_, _open_, or
_use_. Keep _access_ when it accurately describes authorization or connectivity.
### admin
Use _administrator_ in prose. Use _admin_ when it is part of a product name, API,
role, command, or UI label.
### AI
You can use _AI_ without spelling out _artificial intelligence_ when the audience
is familiar with the term.
### allowlist and denylist
Use _allowlist_ and _denylist_ as nouns. Prefer a precise verb that describes the
action instead of using either term as a verb.
- Recommended: Allow requests from the IP address.
- Recommended: Add the IP address to the allowlist.
- Not recommended: Allowlist the IP address.
Don't use _blacklist_ or _whitelist_. The linter reports these terms as errors.
When a literal code item contains one of them, format the item as code and explain
what it does.
### allows you to
Use _lets you_, or make the reader the subject of the sentence.
- Recommended: You can query the table.
- Recommended: The API lets you query the table.
- Not recommended: The API allows you to query the table.
### alpha and beta
Use lowercase when describing a release stage. Preserve capitalization when it is
part of an official product name.
### among and between
Use _between_ for distinct items, even when there are more than two. Use _among_
for members of a group or items that aren't distinct.
### and/or
Rewrite to use _and_, _or_, or explicitly state that either or both apply.
### API
Use _API_ for a web API or a language-specific API. Don't use _API_ to mean an
individual method, function, class, or endpoint.
### app and application
Use _app_ for web and mobile software intended for end users. Use _application_
when it is part of an established term, such as _application programming
interface_, or when the distinction is technically useful.
### as and since
Use _because_ when you mean causation. _As_ and _since_ can be mistaken for
references to time.
### authentication and authorization
Authentication verifies an identity. Authorization determines what an
authenticated identity can access or do. Don't use the terms interchangeably.
Avoid _authN_ and _authZ_ in prose. Use _authentication_ and _authorization_.
### auto-
Follow the spelling established by the relevant technology. Common closed forms
include _autoscaling_, _autofill_, and _autogenerate_. Don't invent a hyphenated
variation when an established form exists.
## B
### backend
Write _backend_, not _back-end_ or _back end_.
### base64
Use _base64_ in general prose. Use the capitalization required by a formal name or
literal code item.
### below
Don't use _below_ to refer to a location in a document or UI. Link to or name the
section or control. For versions, use _earlier_.
### black-box, gray-box, and white-box
Prefer a description of what the monitoring or testing method can observe. If the
established term is necessary, define it on first use.
### boolean
Use the spelling and capitalization of the programming-language type when
referring to code. Use lowercase _boolean_ for the abstract data type and uppercase
_Boolean_ for Boolean logic.
### button
Use _button_ only for an element that is actually a button. In desktop
instructions, users _click_ a button. Preserve the exact button label and format
it in bold.
## C
### can, may, might, must, and should
- Use _can_ for ability, permission, or an optional action.
- Use _might_ for possibility or an uncertain outcome.
- Reserve _may_ for policy or legal guidance when possible.
- Use _must_ or _need to_ for a requirement.
- Avoid ambiguous _should_. State whether an action is required, recommended, or
optional.
### checkboxes
Users _select_ and _clear_ checkboxes. Don't use _check_, _uncheck_, or _deselect_
for these actions.
### click
Use _click_ for buttons, links, and other controls in a desktop interface. Don't
write _click on_. Use _tap_ when the environment is specifically a touch
interface.
### click here
Don't use _click here_ or _here_ as link text. Describe the destination or action.
### client
In API documentation, a _client_ is usually an app that sends requests. Don't use
_client_ as an abbreviation for _client library_ when that could be ambiguous.
Use _concurrent connections_, not _concurrent clients_, when discussing database
connections. The linter checks this usage.
### codebase
Write _codebase_, not _code base_.
### command-line interface
Name the specific interface, such as _Supabase CLI_. Use _CLI_ after the name is
clear.
### config
Use _configuration_ in general prose. Keep _config_ when referring to a literal
file, command, property, or established technical name.
### console and dashboard
Use the product's official name. Don't use _console_ and _dashboard_
interchangeably, and don't call a UI a dashboard unless it presents a dashboard.
Use _Supabase Dashboard_ for the Supabase product.
### currently
Avoid _currently_ when the sentence describes the product's present behavior.
State the behavior directly.
## D
### data
Treat _data_ as a singular mass noun: _the data is_ and _less data_.
### data center
Write _data center_, not _datacenter_.
### data source
Use _data source_ in prose. Preserve `datasource` when it is a code item or
official product term.
### data type
Write _data type_, not _datatype_.
### deprecate
Use _deprecated_ when use is discouraged, usually because support will end. Don't
use it to mean _removed_, _deleted_, or _unavailable_.
### dialog
Use _dialog_ for a UI element that presents information or asks for input. Don't
use _dialogue_ or _popup_.
### directory and folder
Use _directory_ in command-line contexts and _folder_ in graphical interfaces.
Match the product UI when it uses a specific term.
### disable
Use _disable_ or _turn off_ for an available feature or option. Don't use
_disabled_ to mean that something is broken or unavailable.
### display
_Display_ is a transitive verb and requires an object.
- Recommended: The Dashboard displays the query results.
- Recommended: The query results appear.
- Not recommended: The query results display.
### docs
Use _documentation_ in prose. Use _docs_ in informal contributor instructions,
repository paths, URLs, or established product names.
### dropdown
Prefer the specific control name, such as _list_ or _menu_. Use _dropdown_ only
when the distinction matters, and don't use _drop-down_.
### dummy
Don't use _dummy_ for placeholders or sample values. Use _placeholder_, _sample_,
or a name that describes the value's role. For the statistical concept commonly
called a dummy variable, use _indicator variable_ or another established,
context-appropriate term.
## E
### easy, quick, and simple
Avoid claiming that a task is _easy_, _quick_, or _simple_. These words can be
subjective and usually add no information. The linter warns about _easy_,
_easily_, _quickly_, _simple_, and _simply_.
### email
Write _email_, not _e-mail_. Don't use _email_ as a verb; use _send email_.
### enable
Use _enable_ or _turn on_ consistently for activating a feature. When describing
capability, prefer _lets you_ over _enables you_.
### endpoint
Write _endpoint_, not _end point_. Don't use _endpoint_ when the more specific
term is _function_, _method_, or _route_.
### enter
Use _enter_ for adding text to a field. Use _type_ only when the physical act of
typing matters.
### etc.
Avoid _etc._, _and so on_, and _and more_. Introduce a non-exhaustive list with
_including_, _such as_, or _for example_.
### execute
Use _run_ when it has the same meaning. Keep _execute_ when it is the precise
technical term, such as an execute permission or query execution plan.
### extract
Use _extract_ instead of _unarchive_, _uncompress_, _untar_, or _unzip_ in prose.
Preserve literal command names.
## F
### fail over and failover
Use _fail over_ as a verb. Use _failover_ as a noun or adjective.
### filename
Write _filename_, not _file name_.
### file system
Write _file system_, not _filesystem_, unless the latter is part of a code item or
official name.
### fill in and fill out
Users _fill in_ individual fields and _fill out_ an entire form.
### first person
Address the reader as _you_. Don't use singular first person (_I_, _me_, _my_, or
_mine_); the linter reports it as an error.
Use _we_ only when it clearly refers to Supabase, not when it means the writer and
reader together.
### foo, bar, and baz
Use meaningful placeholder names that help explain the example. Keep conventional
placeholder names only when the convention itself is relevant.
### frontend
Write _frontend_, not _front-end_ or _front end_.
## H
### hardcode and hardcoded
Write _hardcode_ and _hardcoded_ without a hyphen.
### health and healthy
When possible, state the observable condition, such as _responding_, _available_,
or _passing its health check_. Don't use _healthy_ when it could be ambiguous or
anthropomorphic.
### higher and lower
For version ranges, use _later_ and _earlier_, not _higher_ and _lower_.
### hover
Use _hold the pointer over_ when the reader must wait for the interface to react.
Use _point to_ when no waiting is required.
### HTTPS
Write _HTTPS_, not _HTTPs_.
## I
### ID
Write _ID_, not _Id_ or _id_, except when matching code. Use _identifier_ when it
is clearer.
### impact
Use _impact_ as a noun. Prefer _affect_ as the verb.
- Recommended: The change affects performance.
- Not recommended: The change impacts performance.
### index
Use _indexes_ as the plural in database documentation. Use _indices_ only in
domains where it is the established term.
### ingest
Use _import_, _load_, or _copy_ for simple data movement. Use _ingest_ when the
operation also performs substantial processing.
### in order to
Use _to_ unless _in order to_ is necessary to prevent ambiguity. The linter warns
about _in order to_.
### inline
Write _inline_, not _in-line_.
### internet
Use lowercase _internet_ except at the beginning of a sentence.
## J
### just
Remove _just_ when it is filler. If it means _only_ or _previously_, use the more
specific word. The linter warns about _just_.
## K
### key
Don't use _key_ to mean _important_. When referring to a technical key, identify
the kind of key on first use.
### key-value pair
Write _key-value pair_, not _key/value pair_ or _key value pair_.
### kill
Use _stop_, _exit_, _cancel_, or _end_ in general prose. Preserve _kill_ for
literal commands, signals, and established technical operations.
## L
### later and earlier
Use _later_ and _earlier_ for version ranges.
- Recommended: Version 2.2 or later
- Not recommended: Version 2.2 or higher
### latest, new, and soon
Avoid time-relative descriptions that become stale. Provide a version, date, or
specific product state instead.
### leverage
Use _use_ or a more specific verb. The linter warns about _leverage_.
### lifecycle
Write _lifecycle_, not _life cycle_ or _life-cycle_.
### login and log in
Use _login_ as a noun or adjective and _log in_ as a verb. Follow the terminology
in the product UI when it uses _sign in_.
- Recommended: Open the login page, and then log in.
- Not recommended: Login to the Dashboard.
## M
### marketing language
Describe measurable behavior instead of making promotional claims. The linter
warns about:
- _best in class_ and _best-in-class_
- _cutting edge_ and _cutting-edge_
- _effortlessly_
- _game changer_ and _game-changer_
- _hassle free_ and _hassle-free_
- _powerful_
- _seamlessly_
### master and slave
Don't use _master_ and _slave_ together. Prefer terms that describe the
relationship accurately, such as _primary and replica_, _controller and worker_,
or _publisher and subscriber_.
When a literal code item uses either term, format it as code, explain it, and use
the preferred term afterward.
### media type
Use _media type_ rather than _MIME type_. Use _content type_ when referring to the
`Content-Type` HTTP header or when it prevents ambiguity.
### microservices
Write _microservices_, not _micro-services_.
### might
Use _might_ for possibility or an uncertain outcome.
### must
Use _must_ or _need to_ for a requirement. Don't use _must_ for a recommendation.
## N
### native
Use a more precise term when possible, such as _built-in_,
_platform-specific_, or _compiled_. Don't use _native_ to describe people.
### numbers in product versions
Write an explicit comparison, such as _version 3.0 or later_. Don't use _newer_,
_older_, _higher_, _lower_, or a trailing `+`.
## O
### OAuth 2.0
Write _OAuth 2.0_, not _OAuth2_, _OAuth 2_, or _Oauth_.
### obviously and of course
Remove these phrases. They can sound dismissive and don't help the reader. The
linter warns about both.
### once
Use _after_ if that is what you mean. Use _once_ only to mean one time.
### on-premises
Write _on-premises_, not _on-premise_, _on premise_, or _on prem_.
## P
### performant
Use a measurable or specific description, such as _lower latency_, _uses less
memory_, or _handles more concurrent connections_.
### persist
Avoid using _persist_ as a transitive verb.
- Recommended: Store the session.
- Recommended: Make the session persistent.
- Not recommended: Persist the session.
### plain text and plaintext
Use _plain text_ in general contexts. Use _plaintext_ in cryptography.
### please
Don't use _please_ in normal instructions. Use it only when asking permission,
apologizing for an inconvenience, or requesting an action that primarily benefits
Supabase. The linter warns about _please_.
### plugin
Use _plugin_ as a noun and _plug in_ as a verb.
### popup
Use the specific UI element, such as _dialog_, _menu_, or _window_. Don't use
_popup_ or _pop-up_ as a generic noun.
### Postgres
Use _Postgres_, not _PostgreSQL_, outside code and literal third-party names. The
linter checks this usage.
### powered by
Prefer _with_, _by_, or _through_, depending on the relationship. The linter warns
about _powered by_.
### prior to and subsequent to
Use _before_ and _after_. The linter checks both phrases.
## R
### read-only
Always hyphenate _read-only_.
### Realtime
Capitalize _Realtime_ when referring to the Supabase product. Use lowercase
_real-time_ as an adjective with its ordinary meaning.
### repository
Prefer _repository_ in documentation prose. _Repo_ is acceptable in informal
contributor instructions and when space is constrained.
### retry
Use _retry_ as a verb or noun. Write around _retriable_, _retryable_, _triable_,
and _tryable_ when practical.
### run time and runtime
Use _runtime_ for an execution environment. Use _run time_ for the time when a
program runs or the duration of a run.
## S
### sanity check
Use _preliminary check_, _confidence check_, or a description of what the check
validates.
### screenshot
Use _screenshot_ as a noun. Use _take a screenshot_, not _screenshot_ as a verb.
Redact secrets and personal information from screenshots.
### select
Use _select_ for choosing an item, selecting text, or marking a checkbox. Preserve
the exact UI label in bold.
### sensitive and confidential
_Sensitive data_ is data whose disclosure might cause harm. _Confidential data_ is
protected against unauthorized access. Use the term that describes the relevant
risk or control.
### setup and set up
Use _setup_ as a noun or adjective and _set up_ as a verb.
- Recommended: Complete the setup to set up authentication.
- Not recommended: Setup authentication.
### singular they
Use _they_, _them_, and _their_ as gender-neutral singular pronouns. Don't use
_s/he_, _he/she_, _(s)he_, or _him/her_. The linter reports these forms as errors.
### slang abbreviations
Don't use internet slang in documentation. The linter warns about _tl;dr_, _ymmv_,
_rtfm_, _imo_, and _fwiw_.
### spin up
Use _create_ or _start_ unless you are literally describing a spinning disk.
### SQL
Write _a SQL query_, not _an SQL query_. Use lowercase SQL keywords in code
examples unless uppercase is required by the surrounding convention.
### SSH
Don't use _SSH_ or `ssh` as a verb.
- Recommended: Connect to the server by using SSH.
- Recommended: Use the `ssh` command.
- Not recommended: SSH into the server.
### startup and start up
Use _startup_ as a noun or adjective and _start up_ as a verb.
### Supabase
Capitalize _Supabase_ outside code. Use _Supabase Platform_ with both words
capitalized. Match literal package names, commands, URLs, and code.
## T
### table name
Write _table name_ as two words. Format a specific table name as code.
### target
Avoid using _target_ as a verb for people. Use _intended for_, _designed for_, or
another description of the audience.
### terminate
Use _stop_, _exit_, _cancel_, or _end_ unless _terminate_ has a specific technical
meaning in the documented context.
### third party and third-party
Use _third party_ as a noun and _third-party_ as an adjective. Don't abbreviate
either form with `3rd`.
### this and that
Add a noun after _this_ or _that_ when the reference could be unclear.
- Recommended: This setting controls connection pooling.
- Not recommended: This controls connection pooling.
### timeout and time out
Use _timeout_ as a noun or adjective and _time out_ as a verb.
### timestamp
Write _timestamp_, not _time stamp_.
### time zone and time-zone
Use _time zone_ as a noun and _time-zone_ as an adjective.
### toggles
Users _enable_ and _disable_ features with toggles. Match and bold the visible
label. Don't instruct the reader to _click the toggle_ when the intended state can
be stated directly.
## U
### UI
Use the specific interface or page name when possible. Use _UI_ only when
discussing a user interface as a general concept.
Match visible UI labels exactly and format them in bold. Describe the element with
the correct noun when it improves clarity, such as _the **Connect** button_ or
_the **Database password** field_.
### URL
Use _URL_, not _web address_, when writing for developers. Use descriptive link
text rather than exposing a URL unless the URL itself is the subject.
### user
Address the reader as _you_. Use _user_ for a person who uses the software that
the reader is building or administering.
### utilize
Use _use_. Use _utilization_ only when referring to the measured proportion of a
resource in use. The linter warns about forms of _utilize_ and _utilise_.
## V
### vague verbs
Describe the concrete action. The linter suggests:
- _view and resolve errors_ instead of _handle errors_
- _create, edit, or delete tables_ instead of _manage tables_
- _query and update data_ instead of _work with data_
Choose a different precise verb if the suggested replacement doesn't match the
actual operation.
### versus
Write _versus_ in prose, not _vs._ Use `vs` only when it is part of a literal name
or when space is constrained.
## W
### web
Use lowercase _web_. Use the capitalization established by formal names such as
_WebAssembly_.
### we
Don't use _we_ to mean the writer and reader together. Use _you_ for the reader.
_We_ is acceptable when it unambiguously means Supabase.
### while
Use _while_ for events that occur at the same time. Use _although_ or _whereas_
for contrast. Use _while_, not _whilst_; the linter checks _whilst_.
### will and would
Use present tense for current product behavior. Use _will_ for an actual future
event, not a predictable result. Replace _would_ with _can_ when describing
capability.
### workload
Use a more specific term, such as _app_, _service_, _database_, or _job_, when the
meaning is known. If _workload_ is the established technical term, define its
scope on first use.
## Y
### you
Address the reader as _you_. Use _user_ only for a person who uses the software
that the reader is developing or administering.
## Lint-enforced phrase groups
The alphabetical entries explain the intent behind the rules. This section mirrors
the exact terminology checks configured in
`supa-mdx-lint/Rule004ExcludeWords`. Update this section when those rules change.
### Filler
The linter warns about _actually_, _easily_, _easy_, _just_, _let's_,
_obviously_, _of course_, _please_, _quickly_, _simple_, _simply_, and
_that's it_. Remove the term or state the intended meaning directly.
### Marketing language
The linter warns about _best in class_, _best-in-class_, _cutting edge_,
_cutting-edge_, _effortlessly_, _game changer_, _game-changer_, _hassle free_,
_hassle-free_, _powerful_, and _seamlessly_. Describe specific behavior or
measurable results instead.
### Vague verbs
The linter suggests _view and resolve errors_ for _handle errors_, _create, edit,
or delete tables_ for _manage tables_, and _query and update data_ for _work with
data_. Use a different precise replacement when the suggestion doesn't match the
operation.
### Apologies
The linter warns about _oops_ and _sorry_. State what happened directly. Apologize
only when an apology is genuinely useful to the reader.
### First person
The linter reports _I_, _I'm_, _me_, _my_, and _mine_ as errors. Address the
reader as _you_ and use an explicit noun for other actors.
### Gender-neutral pronouns
The linter reports _s/he_, _he/she_, _(s)he_, and _him/her_ as errors. Use the
singular _they_ or rewrite the sentence.
### Inclusive language
The linter reports these terms as errors:
- _mankind_: use _humankind_ or _people_
- _manmade_: use _manufactured_, _artificial_, or _synthetic_
- _middleman_: use _intermediary_
- _blacklist_: use _denylist_ or a more precise term
- _whitelist_: use _allowlist_ or a more precise term
### Abbreviations
The linter corrects _eg._ and _eg_ to _e.g._. It replaces _i.e._, _ie._, and
_ie_ with _that is_. Prefer _for example_ and _that is_ in prose when space
allows.
### Powered by
The linter warns about _powered by_. Use _with_, _by_, or _through_, depending on
the relationship.
### Preferred usage
The linter suggests:
- _Postgres_ for _PostgreSQL_
- _concurrent connections_ for _concurrent clients_
- _use_ for _utilize_ and _utilise_
- _uses_ for _utilizes_ and _utilises_
- _using_ for _utilizing_ and _utilising_
### Direct, concise language
The linter warns about these phrases:
- _aforementioned_: name the item
- _amongst_: use _among_
- _endeavor_ or _endeavour_: use _try_
- _facilitate_: use _help_ or describe the action
- _for the purpose of_: use _to_
- _in order to_: use _to_
- _leverage_: use _use_ or a more precise verb
- _prior to_: use _before_
- _subsequent to_: use _after_
- _whilst_: use _while_
### Internet slang
The linter warns about _tl;dr_, _ymmv_, _rtfm_, _imo_, and _fwiw_. Write out the
meaning or remove the aside.
## Attribution
Portions of this word list are modifications based on work created and shared by
Google and used according to the terms of the
[Creative Commons Attribution 4.0 License](https://creativecommons.org/licenses/by/4.0/).
See the
[Google developer documentation style guide word list](https://developers.google.com/style/word-list)
for the original work. Supabase-specific guidance and adaptations are maintained
in this repository.
+200 -179
View File
@@ -4,13 +4,139 @@ title: 'Securing your API'
description: 'Secure your Data API with explicit grants and Postgres Row Level Security.'
---
The Data API is designed to work with Postgres' built-in access controls. Two layers work together:
This guide explains how to secure the Data API with Postgres grants, Row Level Security, dedicated schemas, and request checks.
1. **Grants** determine which Postgres roles (`anon`, `authenticated`, `service_role`) can reach a given table, view, or function over the Data API.
2. **Row Level Security (RLS) policies** then determine which rows those roles can read or modify from the tables exposed in step 1.
3. **Both together** grant control _whether_ a role can touch an object. RLS controls _what_ rows they see.
Use the guide in two parts:
## Grant access explicitly
- [Understand Data API security](#understand-data-api-security) explains how the controls work and when to use them.
- [Configure Data API security](#configure-data-api-security) groups the procedures for applying those controls.
Read the first section when you need to choose a security approach. Go directly to the second section when you know which controls you need to configure.
## Understand Data API security
This section provides the context for the procedures later in the guide.
### Grants and RLS
The Data API works with two layers of Postgres access control:
1. **Grants** determine which Postgres roles can reach a table, view, or function over the Data API. These roles include `anon`, `authenticated`, and `service_role`.
2. **Row Level Security (RLS) policies** determine which rows those roles can read or modify.
Grants control whether a role can access an object. RLS controls which rows the role can access. Use both controls for every exposed object.
To apply these controls, see [Grant access explicitly](#grant-access-explicitly) and [Enable RLS policies](#enable-rls-policies).
### Default privileges
On existing projects, tables created in `public` receive `SELECT`, `INSERT`, `UPDATE`, and `DELETE` privileges for `anon`, `authenticated`, and `service_role` by default. Functions receive `EXECUTE`. These grants make new objects reachable through the Data API, even when you don't intend to expose them.
Supabase is changing the platform default to revoke these automatic grants so that exposure becomes opt-in. See [the platform defaults discussion](https://github.com/orgs/supabase/discussions/45329) in the Supabase GitHub discussions.
The default privileges are part of the standard Supabase permission model and don't bypass RLS. The internal `supabase_admin` role grants them to `anon`, `authenticated`, and `service_role`, but it can't authenticate through the Data API. See [`pg_default_acl`](https://www.postgresql.org/docs/current/catalog-pg-default-acl.html) in the Postgres documentation and [`supabase_admin`](/docs/guides/database/postgres/roles#supabaseadmin) in the Supabase documentation.
To prevent automatic grants on new objects, see [Revoke default privileges](#revoke-default-privileges).
### Dedicated API schemas
A dedicated schema adds another boundary around your Data API. Objects in a schema such as `api` define the API surface. Internal tables and helper functions remain in schemas that aren't exposed.
You can control access with grants in any schema. A dedicated schema makes the exposed surface easier to identify and audit. See [Using Custom Schemas](/docs/guides/api/using-custom-schemas) for setup steps.
### Pre-request checks
RLS policies don't cover every API security requirement. Add pre-request checks for requirements such as:
- Enforcing per-IP or per-user rate limits.
- Checking custom or additional API keys before allowing further access.
- Rejecting requests after exceeding a quota or requiring payment.
- Disallowing direct access to certain tables, views, or functions in exposed schemas.
A Postgres pre-request function reads request information and performs these checks before serving a response. For example, the function can count requests or verify an API key.
To add a check, see [Configure a pre-request function](#configure-a-pre-request-function).
<$Partial path="db_pre_request_warning.mdx" />
### Request information
Use the Postgres `current_setting()` function to access request information:
```sql
-- Get all headers sent in the request
select current_setting('request.headers', true)::json;
-- Get one header with a JSON arrow operator
select current_setting('request.headers', true)::json->>'user-agent';
-- Get cookies
select current_setting('request.cookies', true)::json;
```
| `current_setting()` | Example | Description |
| ------------------- | ----------------------------------------------- | ------------------------------------ |
| `request.method` | `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE` | Request's method |
| `request.path` | `table` | Table's path |
| `request.path` | `view` | View's path |
| `request.path` | `rpc/function` | Function's path |
| `request.headers` | `{ "User-Agent": "...", ... }` | JSON object of the request's headers |
| `request.cookies` | `{ "cookieA": "...", "cookieB": "..." }` | JSON object of the request's cookies |
| `request.jwt` | `{ "sub": "a7194ea3-...", ... }` | JSON object of the JWT payload |
To access the client's IP address, look up the `X-Forwarded-For` header in the `request.headers` setting:
```sql
select split_part(
current_setting('request.headers', true)::json->>'x-forwarded-for',
',', 1); -- takes the client IP before the first comma
```
See [Pre-request](https://postgrest.org/en/stable/references/transactions.html#pre-request) in the PostgREST documentation and [X-Forwarded-For](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For) in the MDN documentation.
For complete implementations that use this request information, see [Pre-request examples](#pre-request-examples).
### Error responses
A pre-request function can raise an exception to stop a request. This example returns an HTTP 402 Payment Required response with a `hint` and an `X-Powered-By` header:
```sql
raise sqlstate 'PGRST' using
message = json_build_object(
'code', '123',
'message', 'Payment Required',
'details', 'Quota exceeded',
'hint', 'Upgrade your plan')::text,
detail = json_build_object(
'status', 402,
'headers', json_build_object(
'X-Powered-By', 'Nerd Rage'))::text;
```
The exception produces this HTTP response:
```http
HTTP/1.1 402 Payment Required
Content-Type: application/json; charset=utf-8
X-Powered-By: Nerd Rage
{
"message": "Payment Required",
"details": "Quota exceeded",
"hint": "Upgrade your plan",
"code": "123"
}
```
Use JSON functions and operators to build dynamic responses from exceptions. Include the `status_text` key in the `detail` clause when you use a custom HTTP status code such as 419. See [JSON Functions and Operators](https://www.postgresql.org/docs/current/functions-json.html) in the Postgres documentation.
For PostgREST 11 or earlier, use the legacy syntax for raising errors. [Check your PostgREST version](/dashboard/project/_/settings/infrastructure) in the Dashboard. See [Raise errors with HTTP status codes](https://postgrest.org/en/stable/references/errors.html#raise-errors-with-http-status-codes) in the PostgREST documentation.
## Configure Data API security
This section groups the procedures for configuring each security control. Apply the procedures that match your architecture.
### Grant access explicitly
A table isn't reachable through the Data API unless you have granted a role privileges on it. Grant the minimum privileges each role needs. For example:
@@ -18,7 +144,7 @@ A table isn't reachable through the Data API unless you have granted a role priv
-- Read-only access for anonymous clients
grant select on table public.your_table to anon;
-- Full access for signed-in users (still subject to RLS)
-- Full access for signed-in users; RLS still applies
grant select, insert, update, delete on table public.your_table to authenticated;
-- Full access for server-side code using the service role
@@ -38,47 +164,34 @@ If a required grant is missing, PostgREST returns a `42501` error with a hint th
}
```
See [the Database API 42501 errors troubleshooting guide](/docs/guides/troubleshooting/database-api-42501-errors) for the full troubleshooting flow.
See [Database API 42501 errors](/docs/guides/troubleshooting/database-api-42501-errors) for the full troubleshooting flow.
<Admonition type="tip">
**Migration:** Bundle grants with your RLS setup in the same migration. The `grant` command controls role access. The `enable row level security` command and policies control row access.
Bundle grants with your RLS setup in the same migration. They belong together: `grant` controls role access, `enable row level security` and policies control row access.
### Revoke default privileges
</Admonition>
Revoke automatic grants when you want new objects in `public` to remain inaccessible until you grant access:
## Default privileges for new tables and functions
1. Open the [SQL Editor](/dashboard/project/_/sql/new).
2. Run the following statements:
By default on existing projects, tables and functions you create in `public` are automatically granted `SELECT`, `INSERT`, `UPDATE`, `DELETE` (or `EXECUTE` for functions) to `anon`, `authenticated`, and `service_role`. That means a new table is reachable through the Data API the moment it lands, even if you forgot to enable RLS or did not intend to expose it.
```sql
alter default privileges for role postgres in schema public
revoke select, insert, update, delete on tables from anon, authenticated, service_role;
Supabase is moving the platform default to **revoke** these automatic grants, so that exposure becomes opt-in, read more about the change in [this changelog entry](https://github.com/orgs/supabase/discussions/45329).
alter default privileges for role postgres in schema public
revoke execute on functions from anon, authenticated, service_role;
To opt an existing project in today, open the [SQL Editor](/dashboard/project/_/sql/new) and run:
alter default privileges for role postgres in schema public
revoke usage, select on sequences from anon, authenticated, service_role;
```sql
alter default privileges for role postgres in schema public
revoke select, insert, update, delete on tables from anon, authenticated, service_role;
alter default privileges for role postgres in schema public
revoke execute on functions from public;
```
alter default privileges for role postgres in schema public
revoke execute on functions from anon, authenticated, service_role;
New tables, functions, and sequences now require explicit grants before Data API roles can access them.
alter default privileges for role postgres in schema public
revoke usage, select on sequences from anon, authenticated, service_role;
alter default privileges for role postgres in schema public
revoke execute on functions from public;
```
<Admonition type="tip">
These default privileges pose no direct security risk. They appear in [pg_default_acl](https://www.postgresql.org/docs/current/catalog-pg-default-acl.html), granted by [supabase_admin](/docs/guides/database/postgres/roles#supabaseadmin) to `anon`, `authenticated`, and `service_role`. The default privileges are intentional and part of Supabase's standard permission model. The `supabase_admin` role is an internal management role that can't authenticate through the Data API.
</Admonition>
## Use a dedicated API schema
If you want an extra boundary around your Data API, lock down the `public` schema and expose a dedicated schema, such as `api`, instead. You can control access with grants in any schema, but this can make the surface easier to reason about: objects in `api` represent your Data API, while internal tables and helper functions stay in schemas that are not exposed. See [Using Custom Schemas](/docs/guides/api/using-custom-schemas) for setup steps.
## Disable the Data API
### Disable the Data API
If your app never uses Supabase client libraries, REST, or GraphQL data endpoints, turn the Data API off:
@@ -87,19 +200,17 @@ If your app never uses Supabase client libraries, REST, or GraphQL data endpoint
With the Data API disabled, none of the auto-generated REST endpoints respond, regardless of grants or RLS.
## Add RLS policies
Enable Row Level Security (RLS) on all tables and views you have exposed via the Data API. You can then write RLS policies to grant users access to specific database rows based on their authentication token.
For functions, RLS does not apply. Instead, control access by granting `EXECUTE` privileges only to the roles that should be able to call the function, and review any `SECURITY DEFINER` functions carefully.
### Enable RLS policies
<Admonition type="danger">
Always enable Row Level Security on tables and views you expose via the Data API to protect your data. For functions, restrict access by granting `EXECUTE` only to appropriate roles.
Tables and views exposed through the Data API without RLS can be accessed by any role with matching grants. Enable RLS or add equivalent controls to prevent unauthorized access. RLS doesn't apply to functions, so grant `EXECUTE` only to the roles that need to call them. Review every `SECURITY DEFINER` function carefully.
</Admonition>
Any table created through the Supabase Dashboard will have RLS enabled by default. If you created the tables via the SQL editor or via another way, enable RLS like so:
Enable RLS on every table and view exposed through the Data API. You can then write policies that grant users access to specific rows based on their authentication token.
Tables created through the Supabase Dashboard have RLS enabled by default. Enable RLS explicitly for tables created in the SQL Editor or through another tool:
<Tabs
scrollable
@@ -124,128 +235,46 @@ alter table
</TabPanel>
</Tabs>
With RLS enabled, you can create Policies that allow or disallow users to access and update data. We provide a detailed guide for creating Row Level Security Policies in our [Authorization documentation](/docs/guides/database/postgres/row-level-security).
With RLS enabled, create policies that control which data users can access and update. See [Row Level Security](/docs/guides/database/postgres/row-level-security).
<Admonition type="danger">
### Configure a pre-request function
Any granted table **without RLS enabled** can be accessed by roles with matching Data API grants (for example, `anon`). Always make sure RLS is enabled, or that you've got other controls in place to avoid unauthorized access to your project's data.
Create and register a Postgres function to run checks before each Data API request:
</Admonition>
Before adding the check logic, review [Request information](#request-information) and [Error responses](#error-responses).
## Enforce additional rules on each request
1. Create a pre-request function:
Using Row Level Security policies may not always be adequate or sufficient to protect APIs.
```sql
create function public.check_request()
returns void
language plpgsql
security definer
as $$
begin
-- your logic here
end;
$$;
```
Here are some common situations where additional protections are necessary:
2. Register the function to run on every Data API request:
- Enforcing per-IP or per-user rate limits.
- Checking custom or additional API keys before allowing further access.
- Rejecting requests after exceeding a quota or requiring payment.
- Disallowing direct access to certain tables, views, or functions in exposed schemas.
```sql
alter role authenticator
set pgrst.db_pre_request = 'public.check_request';
```
You can build these cases in your application by creating a Postgres function that will read information from the request and perform additional checks, such as counting the number of requests received or checking that an API key is already registered in your database before serving the response.
3. Reload the PostgREST configuration:
Define a function like so:
```sql
notify pgrst, 'reload config';
```
```sql
create function public.check_request()
returns void
language plpgsql
security definer
as $$
begin
-- your logic here
end;
$$;
```
The function now runs before every Data API request. Add the checks that match your security requirements.
And register it to run on every Data API request using:
### Pre-request examples
```sql
alter role authenticator
set pgrst.db_pre_request = 'public.check_request';
```
This configures the `public.check_request` function to run on every Data API request. To have the changes take effect, you should run:
```sql
notify pgrst, 'reload config';
```
<$Partial path="db_pre_request_warning.mdx" />
Inside the function you can perform any additional checks on the request headers or JWT and raise an exception to prevent the request from completing. For example, this exception raises an HTTP 402 Payment Required response with a `hint` and additional `X-Powered-By` header:
```sql
raise sqlstate 'PGRST' using
message = json_build_object(
'code', '123',
'message', 'Payment Required',
'details', 'Quota exceeded',
'hint', 'Upgrade your plan')::text,
detail = json_build_object(
'status', 402,
'headers', json_build_object(
'X-Powered-By', 'Nerd Rage'))::text;
```
When raised within the `public.check_request` function, the resulting HTTP response will look like:
```http
HTTP/1.1 402 Payment Required
Content-Type: application/json; charset=utf-8
X-Powered-By: Nerd Rage
{
"message": "Payment Required",
"details": "Quota exceeded",
"hint": "Upgrade your plan",
"code": "123"
}
```
Use the [JSON operator functions](https://www.postgresql.org/docs/current/functions-json.html) to build rich and dynamic responses from exceptions.
If you use a custom HTTP status code like 419, you can supply the `status_text` key in the `detail` clause of the exception to describe the HTTP status.
If you're using PostgREST version 11 or lower ([find out your PostgREST version](/dashboard/project/_/settings/infrastructure)) a different and less powerful [syntax](https://postgrest.org/en/stable/references/errors.html#raise-errors-with-http-status-codes) needs to be used.
### Accessing request information
Like with RLS policies, you can access information about the request by using the `current_setting()` Postgres function. Here are some examples on how this works:
```sql
-- To get all the headers sent in the request
SELECT current_setting('request.headers', true)::json;
-- To get a single header, you can use JSON arrow operators
SELECT current_setting('request.headers', true)::json->>'user-agent';
-- Access Cookies
SELECT current_setting('request.cookies', true)::json;
```
| `current_setting()` | Example | Description |
| ------------------- | ----------------------------------------------- | ------------------------------------ |
| `request.method` | `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE` | Request's method |
| `request.path` | `table` | Table's path |
| `request.path` | `view` | View's path |
| `request.path` | `rpc/function` | Functions's path |
| `request.headers` | `{ "User-Agent": "...", ... }` | JSON object of the request's headers |
| `request.cookies` | `{ "cookieA": "...", "cookieB": "..." }` | JSON object of the request's cookies |
| `request.jwt` | `{ "sub": "a7194ea3-...", ... }` | JSON object of the JWT payload |
To access the IP address of the client look up the [X-Forwarded-For header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For) in the `request.headers` setting. For example:
```sql
SELECT split_part(
current_setting('request.headers', true)::json->>'x-forwarded-for',
',', 1); -- takes the client IP before the first comma (,)
```
Read more about [PostgREST's pre-request function](https://postgrest.org/en/stable/references/transactions.html#pre-request).
### Examples
Use these examples after you configure the pre-request function. Each example replaces the placeholder logic with a complete request check.
<Tabs
scrollable
@@ -256,14 +285,14 @@ Read more about [PostgREST's pre-request function](https://postgrest.org/en/stab
>
<TabPanel id="rate-limit-per-ip" label="Rate limit per IP">
You can only rate-limit `POST`, `PUT`, `PATCH` and `DELETE` requests. This is because `GET` and `HEAD` requests run in read-only mode, and will be served by [Read Replicas](/docs/guides/platform/read-replicas) which do not support writing to the database.
You can only rate-limit `POST`, `PUT`, `PATCH`, and `DELETE` requests. `GET` and `HEAD` requests run in read-only mode. They can be served by [Read Replicas](/docs/guides/platform/read-replicas), which don't support writing to the database.
Outline:
**Outcome:**
- A new row is added to a `private.rate_limits` table each time a modifying action is done to the database containing the IP address and the timestamp of the action.
- If there are over 100 requests from the same IP address in the last 5 minutes, the request is rejected with an HTTP 420 code.
- The `private.rate_limits` table records the IP address and timestamp of each write request.
- The function rejects requests with an HTTP 420 response when an IP address makes more than 100 write requests in 5 minutes.
Create the table:
**Create the table:**
```sql
create table private.rate_limits (
@@ -275,9 +304,9 @@ create table private.rate_limits (
create index rate_limits_ip_request_at_idx on private.rate_limits (ip, request_at desc);
```
The `private` schema is used as it cannot be accessed over the API!
The `private` schema prevents Data API access to the rate-limit records.
Create the `public.check_request` function:
**Create the request check:** Create the `public.check_request` function:
```sql
create function public.check_request()
@@ -317,7 +346,7 @@ end;
$$;
```
Finally, configure the `public.check_request()` function to run on every Data API request:
**Register the request check:** Configure the `public.check_request()` function to run on every Data API request:
```sql
alter role authenticator
@@ -326,32 +355,26 @@ alter role authenticator
notify pgrst, 'reload config';
```
<$Partial path="db_pre_request_warning.mdx" />
To clear old entries in the `private.rate_limits` table, set up a [pg_cron](/docs/guides/database/extensions/pg_cron) job to clean them up.
**Clean up old records:** Set up a [`pg_cron`](/docs/guides/database/extensions/pg_cron) job to delete old entries from `private.rate_limits`.
</TabPanel>
<TabPanel id="use-additional-api-key" label="Use additional API keys">
Some applications can benefit from using additional API keys managed by the application **in addition to the [Supabase API keys](/docs/guides/getting-started/api-keys)**. This is commonly necessary in cases like:
Use application-managed API keys when you need another access check. This approach applies to applications that:
- Applications that use the Data API without RLS policies.
- Applications that do not use [Supabase Auth](/auth) or any other authentication system and rely on the `anon` role.
- Use the Data API without RLS policies.
- Don't use [Supabase Auth](/auth) or another authentication system and rely on the `anon` role.
<Admonition type="tip">
**Required Supabase key:** The `apikey` header is mandatory and not configurable. If you use another API key, distribute both the publishable key and your application's custom key. See [API keys](/docs/guides/getting-started/api-keys).
Using the `apikey` header with the [Supabase API keys](/docs/guides/getting-started/api-keys) is mandatory and not configurable. If you use additional API keys, you have to distribute both the `publishable` API key and your application's custom API key.
</Admonition>
Outline:
**Outcome:**
- Your application requires the presence of the `x-app-api-key` header when the `anon` role is used to prevent abuse of your API.
- These API keys are stored in the `private.anon_api_keys` table, and are distributed independently.
- Each request using the `anon` role will be blocked with HTTP 403 if the `x-app-api-key` header is not registered in the table.
Set up the table:
**Create the table:**
```sql
create table private.anon_api_keys (
@@ -360,7 +383,7 @@ create table private.anon_api_keys (
);
```
Create the `public.check_request` function:
**Create the request check:** Create the `public.check_request` function:
```sql
create function public.check_request()
@@ -399,7 +422,7 @@ end;
$$;
```
Finally, configure the `public.check_request()` function to run on every Data API request:
**Register the request check:** Configure the `public.check_request()` function to run on every Data API request:
```sql
alter role authenticator
@@ -408,8 +431,6 @@ alter role authenticator
notify pgrst, 'reload config';
```
<$Partial path="db_pre_request_warning.mdx" />
</TabPanel>
</Tabs>