mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
Merge branch 'master' into feat/credit-burndown
This commit is contained in:
commit
095597fc31
975 files changed
+39352
-18142
No files matched your search
@@ -10,11 +10,13 @@ The generated API contract has three specs: API v1, API v2, and Platform. Their
|
||||
## Update types
|
||||
|
||||
1. Make the API/schema change and ensure it is deployed to production before relying on a type PR. Production is the merge-gate source of truth.
|
||||
2. Run `pnpm api:codegen` against a running local API environment. It fetches all three local OpenAPI specs and updates the committed files.
|
||||
2. Update the committed types, either:
|
||||
- Run `pnpm api:codegen` against a running local API environment. It fetches all three local OpenAPI specs and updates the committed files.
|
||||
- Or, if you don't have a local API environment running, run `pnpm api:codegen:prod`. It fetches the three production OpenAPI specs directly and overwrites the committed files with them (no diffing — it always writes).
|
||||
3. Inspect and commit only the intended generated type changes.
|
||||
4. Run `pnpm api:verify-types`. It fetches the three production OpenAPI specs, regenerates types with the repository tooling, and compares them with the committed files.
|
||||
|
||||
Complete the update only when `pnpm api:verify-types` passes after the production deployment is available.
|
||||
Complete the update only when `pnpm api:verify-types` passes after the production deployment is available. If you used `api:codegen:prod`, this should already pass since the committed files came straight from production.
|
||||
|
||||
## Interpret verification
|
||||
|
||||
|
||||
@@ -21,14 +21,14 @@ Improves **existing** Supabase docs pages: structure, order, connective text, an
|
||||
|
||||
1. **Read before you rewrite.** Open the target page and nearby pages of the same type. Name the reader's goal and the page type before moving sections.
|
||||
2. **Don't invent product truth. Verify it, in its own PR.** Style and structure work preserves behavior claims, UI labels, and positioning as written. Correcting a claim is PR 3 work, and adding one belongs to the additions branches above it. Those PRs follow [`write-the-docs`](../write-the-docs/SKILL.md) grounding rules: read the code, separate shipped behavior from product intent, and flag what you inferred.
|
||||
3. **Follow CONTRIBUTING.md and WORD_LIST.md** for voice, terminology, and formatting. See [`apps/docs/CONTRIBUTING.md`](../../../apps/docs/CONTRIBUTING.md) and [`apps/docs/WORD_LIST.md`](../../../apps/docs/WORD_LIST.md).
|
||||
3. **Follow the [style guide](../../../apps/docs/style-guide/README.md)** for voice, terminology, structure, and components.
|
||||
4. **Prefer brevity.** Use broad strokes when mechanical detail doesn't help the reader's task. Cut redundancy. Don't over-explain.
|
||||
5. **Reuse sibling skills.** Get IA and architecture from [`ask-the-docs`](../ask-the-docs/SKILL.md). Get validation and self-review from [`review-the-docs`](../review-the-docs/SKILL.md). Apply the shared pitfalls in [`write-the-docs/reference/common-pitfalls.md`](../write-the-docs/reference/common-pitfalls.md) rather than duplicating them here.
|
||||
5. **Reuse sibling skills.** Get IA and architecture from [`ask-the-docs`](../ask-the-docs/SKILL.md). Get validation and self-review from [`review-the-docs`](../review-the-docs/SKILL.md). Apply the style guide's [timeless documentation](../../../apps/docs/style-guide/03-page-structure.md#write-timeless-documentation) rather than duplicating it here.
|
||||
6. **One change type per diff.** A diff that mixes reworded prose with moved sections is unreviewable, because the reader can't tell a move from a rewrite. Separate them by commit in a single PR, or by branch in a stack.
|
||||
|
||||
## Phase 0: Size and split
|
||||
|
||||
1. Identify the document type per CONTRIBUTING.md. The types are explainer, tutorial, guide, reference, and troubleshooting.
|
||||
1. Identify the document type per [`03-page-structure.md`](../../../apps/docs/style-guide/03-page-structure.md#document-types). The types are explainer, tutorial, guide, reference, and troubleshooting.
|
||||
2. State the reader's goal and prerequisites in one or two lines.
|
||||
3. Note structural problems: mixed information types interrupting a procedure, missing intro navigation on a long page, weak transitions, redundancy, or over-explained mechanics.
|
||||
4. Sort the diagnosis into the buckets below. **Drop any bucket that comes back empty, and say so.** Style, structure, and technical revision take one commit or branch each. Additions take as many as the content needs, so the edit has no fixed size. A style edit plus a structural edit is the common shape, because most pages that need restructuring are already correct. Two buckets is a complete result, not a truncated one.
|
||||
@@ -52,20 +52,20 @@ Inline changes only. Nothing in this PR moves a line from one place to another.
|
||||
**Rewrite:**
|
||||
|
||||
- Use second person, present tense, short paragraphs, and ordered steps for sequential actions.
|
||||
- Put procedures in procedure format, per the Procedures section of CONTRIBUTING.md. Start each step with an imperative verb, keep one action or a closely related set per step, present 7 ± 2 steps per chunk, and group anything longer into named phases or smaller procedures.
|
||||
- Apply the inline rules in CONTRIBUTING.md for admonitions, emphasis, links, lists, and the "Styling, formatting, and grammar" section.
|
||||
- Put procedures in procedure format. Per-step formatting is in [`02-elements.md`](../../../apps/docs/style-guide/02-elements.md#procedures); how many steps to present at once and when to split into phases is in [`03-page-structure.md`](../../../apps/docs/style-guide/03-page-structure.md#chunking).
|
||||
- Apply [`02-elements.md`](../../../apps/docs/style-guide/02-elements.md) for admonitions, emphasis, links, and lists, and [`01-voice-and-tone.md`](../../../apps/docs/style-guide/01-voice-and-tone.md) for person, tense, and sentence-level grammar.
|
||||
- Keep code samples executable in their stated context, and mark intentionally omitted code. Prefer partials under `apps/docs/content/_partials/` over copied blocks.
|
||||
- **Check the alt text on every image, and open the image to do it.** Alt text on an existing page usually names the topic rather than describing the picture, and a topic name is what the nearby heading already says. Describe what a reader who can't see it would need: the labeled parts, the relationships between them, and any values the diagram carries. This is a rewrite of existing text, so it belongs in this PR.
|
||||
|
||||
**Cut:**
|
||||
|
||||
- Restated points and mechanical over-explanation.
|
||||
- The shared pitfalls in [`common-pitfalls.md`](../write-the-docs/reference/common-pitfalls.md): timelessness, internal planning context in shipped MDX, redundancy, single-item lists, and admonition restatement.
|
||||
- Terminology that doesn't match [`apps/docs/WORD_LIST.md`](../../../apps/docs/WORD_LIST.md). Crawl the list for terms already on the page, not only the ones you introduce. An existing page is where nonconforming terminology accumulates.
|
||||
- Time-anchored language and unshipped features: [timeless documentation](../../../apps/docs/style-guide/03-page-structure.md#write-timeless-documentation). Internal planning context in shipped MDX: [keep internal context out](../../../apps/docs/style-guide/03-page-structure.md#keep-internal-context-out). Repeated points: [brevity](../../../apps/docs/style-guide/01-voice-and-tone.md#brevity). Single-item lists and restated admonitions: [`02-elements.md`](../../../apps/docs/style-guide/02-elements.md).
|
||||
- Terminology that doesn't match [`WORD_LIST.md`](../../../apps/docs/style-guide/WORD_LIST.md). Run both passes from [Use with an AI agent](../../../apps/docs/style-guide/WORD_LIST.md#use-with-an-ai-agent): [Phrase groups](../../../apps/docs/style-guide/WORD_LIST.md#phrase-groups) gives you every literal term list in one read, then `grep '^### ' WORD_LIST.md` and open only the entries matching words on the page. Cover terms already on the page, not only the ones you introduce. An existing page is where nonconforming terminology accumulates.
|
||||
|
||||
## PR 2: Structure
|
||||
|
||||
Apply the **Mixed information types**, **Navigation**, and **Cross-references and glue** guidance in the Guides section of [`apps/docs/CONTRIBUTING.md`](../../../apps/docs/CONTRIBUTING.md).
|
||||
Apply [`03-page-structure.md`](../../../apps/docs/style-guide/03-page-structure.md) — section grouping, navigation, and glue. This section is the procedure for doing that on an existing page; the rules live in the guide.
|
||||
|
||||
**Work in this order, and settle the outline before you move a line.** A restructure invalidates every branch above it in the stack, so each revision costs a full restack, and a restack is where content gets dropped in conflict resolution. Reworking the shape twice costs far more than getting it right once.
|
||||
|
||||
@@ -77,36 +77,19 @@ Write the matched heading texts down. For the rest of this PR they are immutable
|
||||
|
||||
### 2. Classify every substantial section
|
||||
|
||||
Each one is **procedural** (the reader performs actions), **contextual** (the reader needs to understand something before acting), or **reference** (the reader looks something up).
|
||||
Classify each one against the information types in [`03-page-structure.md`](../../../apps/docs/style-guide/03-page-structure.md#information-types), then collapse them into the three buckets you'll group by: **procedural** (Procedure), **contextual** (Concept and Process), and **reference** (Structure and Fact). A principle or a fact usually rides along in the section it qualifies rather than getting one of its own.
|
||||
|
||||
**Classify by what the reader is doing, not by what the section is about.** Subject matter is the trap: on a page about tables every section is "about tables", so grouping by topic produces one task-named bucket that quietly collects the background as well. A reader opens a section on schemas to understand something, not to do something, so it is context no matter how much it is about tables.
|
||||
The guide has the two rules that decide the hard cases: classify by what the reader is doing rather than what the section is about, and split a section that serves two types instead of filing it under the larger half.
|
||||
|
||||
**A section serving two classes gets split, not filed under the larger half.** Give the new half a heading, keep the heading text of the half that stays, and cross-reference the two. One cross-reference costs less than a reader hunting for the half they need.
|
||||
Write the bucket for every section down before moving anything. A page where every section lands in one bucket is a page that wasn't really classified.
|
||||
|
||||
### 3. Write the target outline before touching the file
|
||||
|
||||
Produce the whole heading tree, with levels, and check it against the locked list from step 1. Put it in front of the requester along with the Phase 0 diagnosis. The outline is the artifact that gets revised, not the page.
|
||||
|
||||
Order the groups: a short conceptual opener when the page serves newcomers, then procedures, then context, then reference. The action path runs uninterrupted and the background sits after it.
|
||||
Order the groups per [`03-page-structure.md`](../../../apps/docs/style-guide/03-page-structure.md#grouping-sections), which has the order that works and a worked outline. A short concept opener can precede the procedure group; keep that group uninterrupted, and put the remaining background after it.
|
||||
|
||||
A guide about database tables settled here:
|
||||
|
||||
```
|
||||
## What is a table? <- short conceptual opener
|
||||
## Creating and managing tables <- procedures
|
||||
### Creating tables
|
||||
### Securing your tables
|
||||
### Loading data
|
||||
### Joining tables with foreign keys
|
||||
## How tables are organized <- context
|
||||
### Primary keys
|
||||
### Relationships between tables
|
||||
### Schemas
|
||||
## Reference
|
||||
### Data types
|
||||
```
|
||||
|
||||
"Joining tables with foreign keys" held both classes. The steps kept the heading and stayed in the procedures group; the idea of a relational database moved to "Relationships between tables" in the context group.
|
||||
When a section held two types and you split it in step 2, the half that keeps the original heading text stays in its original group, so the locked anchor from step 1 survives. The new half takes a new heading and moves to the group its type belongs to.
|
||||
|
||||
### 4. Move, then add the glue the new shape needs
|
||||
|
||||
@@ -187,7 +170,7 @@ Run this per change type, before you submit the commit or branch that carries it
|
||||
- [ ] Section groups follow information type, and procedures aren't interrupted by long context
|
||||
- [ ] Intro navigation is present only when the page needs it, and links resolve
|
||||
- [ ] Connective text is selective, not link spam
|
||||
- [ ] Voice matches CONTRIBUTING.md and WORD_LIST.md
|
||||
- [ ] Voice matches [`01-voice-and-tone.md`](../../../apps/docs/style-guide/01-voice-and-tone.md) and terminology matches [`WORD_LIST.md`](../../../apps/docs/style-guide/WORD_LIST.md)
|
||||
- [ ] Every image has alt text that describes the image, checked against the image itself
|
||||
- [ ] No invented behavior or positioning
|
||||
- [ ] Shared pitfalls checklist considered
|
||||
@@ -209,13 +192,14 @@ Run this per change type, before you submit the commit or branch that carries it
|
||||
|
||||
**Style and structure:**
|
||||
|
||||
- Mixed information types, navigation, and glue: the Guides section of [`apps/docs/CONTRIBUTING.md`](../../../apps/docs/CONTRIBUTING.md)
|
||||
- Procedure format: the Procedures section of [`apps/docs/CONTRIBUTING.md`](../../../apps/docs/CONTRIBUTING.md)
|
||||
- Terminology: [`apps/docs/WORD_LIST.md`](../../../apps/docs/WORD_LIST.md)
|
||||
- Style guide entry point: [`style-guide/README.md`](../../../apps/docs/style-guide/README.md)
|
||||
- Section grouping, navigation, glue, and chunking: [`03-page-structure.md`](../../../apps/docs/style-guide/03-page-structure.md)
|
||||
- Procedure format and other components: [`02-elements.md`](../../../apps/docs/style-guide/02-elements.md)
|
||||
- Terminology: [`WORD_LIST.md`](../../../apps/docs/style-guide/WORD_LIST.md)
|
||||
|
||||
**Sibling skills:**
|
||||
|
||||
- Pitfalls and drafting mechanics: [`common-pitfalls.md`](../write-the-docs/reference/common-pitfalls.md), [`drafting-mechanics.md`](../write-the-docs/reference/drafting-mechanics.md)
|
||||
- Drafting mechanics: [`drafting-mechanics.md`](../write-the-docs/reference/drafting-mechanics.md)
|
||||
- Runnable verification: [`test-the-docs`](../test-the-docs/SKILL.md)
|
||||
- Architecture and IA: [`ask-the-docs`](../ask-the-docs/SKILL.md)
|
||||
- Net-new drafts: [`write-the-docs`](../write-the-docs/SKILL.md)
|
||||
@@ -20,7 +20,7 @@ _Self-serve first ([agent skills](../../../../apps/docs/CONTRIBUTING.md#ai-agent
|
||||
- **Examples are runnable and have been tested** (commands, code, expected result)
|
||||
- **Correct stage** like GA is stated; limitations are named honestly.
|
||||
- The page **lives in the right place** in the IA and links to and from related pages.
|
||||
- Terminology and formatting match existing docs (and style guide once it lands).
|
||||
- Terminology and formatting match the [style guide](../../../../apps/docs/style-guide/README.md).
|
||||
|
||||
### 1. Frame
|
||||
|
||||
|
||||
@@ -106,17 +106,17 @@ Inspect changed files from `gh pr view` or:
|
||||
gh pr diff <number> --repo supabase/supabase --name-only
|
||||
```
|
||||
|
||||
| PR type | Path signals | Primary skill section |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------- |
|
||||
| **Markdown-schema handler** | `apps/docs/internals/markdown-schema/`, `generate-guides-markdown.ts` | [Schema handler review](#schema-handler-review) |
|
||||
| **Pipeline / internals** | `apps/docs/internals/` (not just one new handler) | [Pipeline review](#pipeline-review) |
|
||||
| **Content-only MDX** | `apps/docs/content/**` only | [Content review](#content-review) |
|
||||
| **Tutorial / quickstart** | `apps/docs/content/guides/**/tutorials/`, `quickstarts/`, plus `examples/` | [Tutorial review](#tutorial-review) → also `work-linear-issue` |
|
||||
| **Example app only** | `examples/**` without matching MDX | [Example review](#example-review) |
|
||||
| **Studio ↔ docs links** | `apps/studio/**` | [Studio review](#studio-review) |
|
||||
| **Docs UI / components** | `apps/docs/components/`, `apps/docs/features/` (no pipeline) | [Component review](#component-review) |
|
||||
| **Docs tooling** | `.agents/skills/`, `apps/docs/AGENTS.md`, `apps/docs/CONTRIBUTING.md`, `apps/docs/DEVELOPERS.md` | [Docs tooling review](#docs-tooling-review) |
|
||||
| **Mixed** | Multiple path groups above | Run each applicable section; note overlap |
|
||||
| PR type | Path signals | Primary skill section |
|
||||
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
|
||||
| **Markdown-schema handler** | `apps/docs/internals/markdown-schema/`, `generate-guides-markdown.ts` | [Schema handler review](#schema-handler-review) |
|
||||
| **Pipeline / internals** | `apps/docs/internals/` (not just one new handler) | [Pipeline review](#pipeline-review) |
|
||||
| **Content-only MDX** | `apps/docs/content/**` only | [Content review](#content-review) |
|
||||
| **Tutorial / quickstart** | `apps/docs/content/guides/**/tutorials/`, `quickstarts/`, plus `examples/` | [Tutorial review](#tutorial-review) → also `work-linear-issue` |
|
||||
| **Example app only** | `examples/**` without matching MDX | [Example review](#example-review) |
|
||||
| **Studio ↔ docs links** | `apps/studio/**` | [Studio review](#studio-review) |
|
||||
| **Docs UI / components** | `apps/docs/components/`, `apps/docs/features/` (no pipeline) | [Component review](#component-review) |
|
||||
| **Docs tooling** | `.agents/skills/`, `apps/docs/AGENTS.md`, `apps/docs/CONTRIBUTING.md`, `apps/docs/DEVELOPERS.md`, `apps/docs/style-guide/` | [Docs tooling review](#docs-tooling-review) |
|
||||
| **Mixed** | Multiple path groups above | Run each applicable section; note overlap |
|
||||
|
||||
When a PR spans types (e.g. schema handler + component refactor), run **all** matching sections.
|
||||
|
||||
@@ -202,14 +202,22 @@ Verify both guides and reference output when `generate-reference-markdown.ts` or
|
||||
|
||||
MDX prose, partials, navigation — no pipeline or example changes.
|
||||
|
||||
Check the prose against [`apps/docs/CONTRIBUTING.md`](../../../apps/docs/CONTRIBUTING.md) and
|
||||
[`apps/docs/WORD_LIST.md`](../../../apps/docs/WORD_LIST.md) yourself. No CI or local
|
||||
check covers terminology. CodeRabbit reviews style, terminology, and structure
|
||||
on `apps/docs/content/**/*.mdx`, but only once the PR is open.
|
||||
Check the prose against the [style guide](../../../apps/docs/style-guide/README.md) yourself. No CI or local
|
||||
check covers style or terminology. CodeRabbit reviews style, terminology, and
|
||||
structure on `apps/docs/content/**/*.mdx`, but only once the PR is open.
|
||||
|
||||
Check [`WORD_LIST.md`](../../../apps/docs/style-guide/WORD_LIST.md) against the finished page last, using the
|
||||
two-pass protocol in [Use with an AI agent](../../../apps/docs/style-guide/WORD_LIST.md#use-with-an-ai-agent): [Phrase
|
||||
groups](../../../apps/docs/style-guide/WORD_LIST.md#phrase-groups) for the literal term lists, then `grep '^### '
|
||||
WORD_LIST.md` and read only the entries matching words on the page. Include terms
|
||||
the author didn't introduce.
|
||||
|
||||
Checklist:
|
||||
|
||||
- [ ] Prose follows CONTRIBUTING.md and the word list
|
||||
- [ ] Voice follows [`01-voice-and-tone.md`](../../../apps/docs/style-guide/01-voice-and-tone.md)
|
||||
- [ ] Section grouping and chunking follow [`03-page-structure.md`](../../../apps/docs/style-guide/03-page-structure.md)
|
||||
- [ ] Components follow [`02-elements.md`](../../../apps/docs/style-guide/02-elements.md)
|
||||
- [ ] Terminology matches [`WORD_LIST.md`](../../../apps/docs/style-guide/WORD_LIST.md)
|
||||
- [ ] Frontmatter valid (`title`, `description` where required)
|
||||
- [ ] Internal links resolve (`/docs/guides/...`, not broken anchors)
|
||||
- [ ] `$CodeSample` paths match existing example directories
|
||||
@@ -293,7 +301,8 @@ Checklist:
|
||||
- [ ] `.claude/skills` is still a single Git symlink to `../.agents/skills` — no per-skill symlinks or copies under `.claude/`
|
||||
- [ ] Cross-skill links resolve: relative for in-repo skills; absolute `docs-agent-skills` URLs only for skills that remain in that private repo
|
||||
- [ ] No personal vault paths, Obsidian references, or private-process-only instructions
|
||||
- [ ] `apps/docs/CONTRIBUTING.md` / `DEVELOPERS.md` pointers match skill names and checklist stages
|
||||
- [ ] `apps/docs/CONTRIBUTING.md` / `DEVELOPERS.md` / `AGENTS.md` pointers match skill names and checklist stages
|
||||
- [ ] A style rule added to a skill belongs in `apps/docs/style-guide/` instead, with the skill pointing at it
|
||||
- [ ] Reference files under a skill stay near the ~250-line guideline (split if bloated)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -3,7 +3,7 @@ name: write-the-docs
|
||||
description: >-
|
||||
Draft new or updated Supabase docs content for a feature or launch,
|
||||
grounded in product intent (Linear when available), a read of the actual
|
||||
code, and the docs style guide once one exists. Use when asked to write
|
||||
code, and the docs style guide. Use when asked to write
|
||||
docs for a new feature, a product launch, or a Linear ticket that needs
|
||||
net-new content rather than a bug fix. Not for implementing existing docs
|
||||
bug reports — use work-linear-issue for that. Not for restructuring
|
||||
@@ -18,7 +18,7 @@ Drafts net-new Supabase docs content (or product-grounded rewrites) for a featur
|
||||
|
||||
1. **Gather before drafting.** Never draft from a ticket title alone. Pull all four inputs below first; a thin gather phase produces a draft that's wrong about how the feature actually works.
|
||||
2. **Separate confirmed behavior from product intent from inference.** Code tells you what the feature does today. Linear/PRD/PRFAQ (or prior Frame/Shape output) tells you what it's meant to do and how it should be positioned. Anything you had to guess, flag explicitly rather than stating it as fact.
|
||||
3. **Follow CONTRIBUTING.md and WORD_LIST.md for voice, terminology, and formatting only, never for content accuracy.** These are a style reference, not a source of truth: rule 2's Linear+code read is what governs what the page actually says. Don't silently invent voice/structure rules either; name the nearest existing-page precedent you followed instead (see [reference/style-fallback.md](reference/style-fallback.md)).
|
||||
3. **Follow the [style guide](../../../apps/docs/style-guide/README.md) for writing conventions, including voice, terminology, formatting, page structure, and elements. Never follow it for content accuracy.** It is a style reference, not a source of truth: rule 2's Linear+code read is what governs what the page actually says. Don't invent voice or structure rules. When the guide and Google's developer documentation style guide both come up short, say so in the handoff rather than copying whatever the nearest page happens to do.
|
||||
4. **Reuse, don't duplicate.** For docs-app architecture/placement questions, use [`ask-the-docs`](../ask-the-docs/SKILL.md) and [`audit-docs-ia`](https://github.com/supabase/docs-agent-skills/blob/main/.claude/skills/audit-docs-ia/SKILL.md) rather than re-deriving that knowledge here.
|
||||
5. **Know what you're actually drafting.** Not everything that looks like "docs for a feature" is a hand-written page — see the content-type gate below before you start writing. If the ask is restructure, reorder, connective text, or clarity on an existing page (no new product story), use [`edit-the-docs`](../edit-the-docs/SKILL.md) instead.
|
||||
|
||||
@@ -26,7 +26,7 @@ Drafts net-new Supabase docs content (or product-grounded rewrites) for a featur
|
||||
|
||||
Four inputs, read in this sequence (sequence, not priority; Linear remains the product-intent source and code remains the behavior source per rule 2 and Phase 1 step 3):
|
||||
|
||||
1. **Style guide — voice/terminology reference.** Start with [`apps/docs/CONTRIBUTING.md`](../../../apps/docs/CONTRIBUTING.md) and [`apps/docs/WORD_LIST.md`](../../../apps/docs/WORD_LIST.md) for voice, structure, and terminology. If those don't cover the case, fall back to the nearest comparable existing page under `apps/docs/content/` and say explicitly: _"no dedicated style guide yet — following the precedent of `<page>`."_ See [reference/style-fallback.md](reference/style-fallback.md).
|
||||
1. **Style guide — voice/terminology reference.** Read [`style-guide/README.md`](../../../apps/docs/style-guide/README.md) and follow the step that matches what you're drafting. Check [`WORD_LIST.md`](../../../apps/docs/style-guide/WORD_LIST.md) for the terms you plan to introduce. The guide's closing section says what to consult when it's silent; follow that rather than matching a neighbouring page.
|
||||
2. **Linear — the ticket and its product context.** Linear is an internal Supabase tool: preferred when available, not required for open-source contributors. When a Linear issue is available, pull it, then its parent project/initiative description too (PRD, PRFAQ, RFC, or initiative narrative) and any PM comments. Product framing/positioning language usually lives one level up from the ticket, in the parent project or initiative description rather than the ticket body. Distinguish scope the ticket actually commits to from aspirational language in the PRD. If there is no Linear issue and no prior Frame/Shape product-intent output, stop drafting: ask internal authors for a Linear URL, otherwise hand off to [`pm-the-docs`](../pm-the-docs/SKILL.md) (Frame) and [`ask-the-docs`](../ask-the-docs/SKILL.md) when Shape/IA is unsettled. Resume only after product intent exists — never invent positioning, and never run Frame/Shape inside this Draft skill.
|
||||
3. **Code.** Read the actual implementation before writing a single behavior claim — the PRD describes intent, the code describes what shipped. Check the Linear issue/project first for a linked `supabase/supabase` PR — its diff and description are the most precise "what actually shipped" source, more precise than a general codebase read. If no PR is linked, locate the feature directly in `supabase/supabase` (or the product's own repo), and apply [`ask-the-docs`](../ask-the-docs/SKILL.md)'s reuse/minimalism lens: understand what exists before describing it. When behavior spans services (CLI, Auth, migrations, platform, …), follow [`pm-the-docs`](../pm-the-docs/SKILL.md) → [universe-lookup](../pm-the-docs/reference/universe-lookup.md) **capability gate** (universe when accessible, else OSS public search / linked repos — not `ask-the-docs`). If code and PRD disagree, the code wins for behavior claims — flag the mismatch rather than silently picking one.
|
||||
4. **Whatever else the author supplies.** Screenshots, example projects, related pages, Slack threads, a specific voice sample. Screenshots are for more than general context — use them to verify the _exact_ button/menu/field labels before writing instructional steps that reference them; a mismatched UI label is one of the easiest, most avoidable errors in a draft. Ask for these when the feature's user-facing shape is still unclear after 1–3, rather than guessing.
|
||||
@@ -49,18 +49,16 @@ When in doubt, ask `ask-the-docs` rather than guessing — this classification i
|
||||
- Place the page using existing IA precedent; for a placement call that isn't obvious, consult [`audit-docs-ia`](https://github.com/supabase/docs-agent-skills/blob/main/.claude/skills/audit-docs-ia/SKILL.md)'s nav/IA knowledge rather than guessing a nav slot.
|
||||
- **Wire it into navigation, not just onto disk.** Placement (which section) and nav enablement (whether it actually shows up) are separate — confirm the current nav-registration mechanism via `ask-the-docs`/`audit-docs-ia` rather than assuming a page is discoverable just because the file exists in the right folder.
|
||||
- Ground every behavior claim in Phase 1's code read (the linked PR when there is one); ground every "why this matters" framing in Linear/PM context or prior Frame/Shape output; mark inferred material inline (e.g. an HTML comment or a flagged line in the handoff summary) so a reviewer can find it fast.
|
||||
- **Write for timelessness.** Prefer documenting what exists now over promising future features. See [reference/common-pitfalls.md](reference/common-pitfalls.md#2-timeless-documentation).
|
||||
- **Keep it concise and avoid redundancy.** See [reference/common-pitfalls.md](reference/common-pitfalls.md#4-redundancy-and-over-explanation).
|
||||
- **Prefer paragraphs over single-item lists.** See [reference/common-pitfalls.md](reference/common-pitfalls.md#5-single-item-lists).
|
||||
- **Write timeless documentation, cut redundancy, and prefer a paragraph to a single-item list.** See [timeless documentation](../../../apps/docs/style-guide/03-page-structure.md#write-timeless-documentation), [brevity](../../../apps/docs/style-guide/01-voice-and-tone.md#brevity), and [lists](../../../apps/docs/style-guide/02-elements.md#lists).
|
||||
- **Strip internal business context before the final draft.** HTML comments flagging PRD intent, roadmap speculation, internal ticket discussions, or "gap-fill" notes must be removed from MDX before handoff. Open-source docs shouldn't expose internal planning. Flag assumptions and open questions for reviewers in the PR description instead, not in the shipped content.
|
||||
- Search [`apps/docs/WORD_LIST.md`](../../../apps/docs/WORD_LIST.md) when introducing or reviewing technical terms, UI actions, abbreviations, and potentially ambiguous language during drafting. This targeted search supplements, but does not replace, the full-file compliance check in Phase 2.5.
|
||||
- Search [`WORD_LIST.md`](../../../apps/docs/style-guide/WORD_LIST.md) when introducing or reviewing technical terms, UI actions, abbreviations, and potentially ambiguous language during drafting. Use the two-pass protocol in [Use with an AI agent](../../../apps/docs/style-guide/WORD_LIST.md#use-with-an-ai-agent) rather than reading the file end to end.
|
||||
- Reuse repeated content through `apps/docs/content/_partials/` instead of copying it. For nav wiring, partials, and file placement, see [`ask-the-docs`](../ask-the-docs/SKILL.md)'s `app-map.md` and `federated-docs.md`.
|
||||
|
||||
## Phase 2.5 — Review checklist
|
||||
|
||||
Before handing off, confirm:
|
||||
|
||||
- [ ] CONTRIBUTING.md / WORD_LIST.md followed, or precedent page named explicitly
|
||||
- [ ] Style guide followed, or the gap named explicitly in the handoff
|
||||
- [ ] Every behavior claim traces to the code read (ideally the linked PR), not just the PRD
|
||||
- [ ] Every "why it matters" / positioning line traces to Linear/PM context or prior Frame/Shape output, not invented
|
||||
- [ ] Inferred or assumed material is flagged, not stated as fact
|
||||
@@ -75,13 +73,15 @@ Before handing off, confirm:
|
||||
|
||||
### Compliance checklist
|
||||
|
||||
Re-read [`apps/docs/CONTRIBUTING.md`](../../../apps/docs/CONTRIBUTING.md) and [`apps/docs/WORD_LIST.md`](../../../apps/docs/WORD_LIST.md) in full before handoff, not just the sections searched during drafting. When a dedicated style guide lands in the repo, extend this checklist to cover it too.
|
||||
Before handoff, run both passes from [Use with an AI agent](../../../apps/docs/style-guide/WORD_LIST.md#use-with-an-ai-agent): read [Phrase groups](../../../apps/docs/style-guide/WORD_LIST.md#phrase-groups) for the literal term lists, then `grep '^### ' WORD_LIST.md` and read only the entries matching words on the page. Cover terms you didn't introduce, not just the ones you searched while drafting. Then check the draft against each numbered file in the [style guide](../../../apps/docs/style-guide/README.md).
|
||||
|
||||
- [ ] Parentheses used only for acronyms or `(Optional)`, not prose asides
|
||||
- [ ] Bold, italics, and code used only for their distinct purposes (UI labels, must-not-miss terms), not for visual emphasis alone
|
||||
- [ ] No dash-based asides where a direct sentence reads better
|
||||
- [ ] Terminology matches `WORD_LIST.md` (including any terms flagged as imprecise, not just spelling/capitalization)
|
||||
- [ ] Headings, admonitions, and links follow CONTRIBUTING.md's "Styling, formatting, and grammar" and "Components and elements" sections
|
||||
- [ ] Terminology matches [`WORD_LIST.md`](../../../apps/docs/style-guide/WORD_LIST.md) (including any terms flagged as imprecise, not just spelling/capitalization)
|
||||
- [ ] Voice, tense, and brevity follow [`01-voice-and-tone.md`](../../../apps/docs/style-guide/01-voice-and-tone.md)
|
||||
- [ ] Document type, section grouping, and chunking follow [`03-page-structure.md`](../../../apps/docs/style-guide/03-page-structure.md)
|
||||
- [ ] Admonitions, headings, links, and other components follow [`02-elements.md`](../../../apps/docs/style-guide/02-elements.md)
|
||||
|
||||
## Phase 3 — Handoff
|
||||
|
||||
@@ -95,8 +95,8 @@ This skill stops at a reviewable draft. It does not open worktrees or PRs itself
|
||||
|
||||
## Additional resources
|
||||
|
||||
- Style / terminology: [`apps/docs/CONTRIBUTING.md`](../../../apps/docs/CONTRIBUTING.md), [`apps/docs/WORD_LIST.md`](../../../apps/docs/WORD_LIST.md), [reference/style-fallback.md](reference/style-fallback.md)
|
||||
- Common pitfalls to avoid: [reference/common-pitfalls.md](reference/common-pitfalls.md)
|
||||
- Style: [`style-guide/README.md`](../../../apps/docs/style-guide/README.md) routes to the file for each level
|
||||
- Repo mechanics (nav wiring, partials, reference pipeline): [`apps/docs/CONTRIBUTING.md`](../../../apps/docs/CONTRIBUTING.md)
|
||||
- Drafting mechanics: [reference/drafting-mechanics.md](reference/drafting-mechanics.md)
|
||||
- Content-type gate detail: [reference/content-type-gate.md](reference/content-type-gate.md)
|
||||
- Existing-page restructure/clarity: [`edit-the-docs`](../edit-the-docs/SKILL.md)
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
# Common pitfalls in docs drafts
|
||||
|
||||
Patterns to watch for when drafting docs, based on review feedback. These are guidelines, not absolute rules — use judgment based on context.
|
||||
|
||||
## 1. Internal planning context in open-source docs
|
||||
|
||||
**Principle:** Supabase docs are open source. Internal planning context, unshipped features, and business intent shouldn't be visible in the public repo.
|
||||
|
||||
**What to watch for:**
|
||||
|
||||
- HTML comments referencing PRDs, roadmaps, or internal ticket discussions
|
||||
- "Gap-fill" notes about what's planned but not shipped
|
||||
- Internal product strategy or positioning discussions
|
||||
- References to any project-management or ticketing system (internal or a contributor's own tools)
|
||||
|
||||
**Where to put internal context instead:**
|
||||
|
||||
- PR description (for review-time context)
|
||||
- The team's project-management tool (for product/PM handoff)
|
||||
- Internal docs (for roadmap tracking)
|
||||
|
||||
## 2. Timeless documentation
|
||||
|
||||
**Principle:** Prefer documenting what exists now over promising future features ([Google's timeless documentation](https://developers.google.com/style/timeless-documentation)). Future promises become stale.
|
||||
|
||||
**Common patterns to watch for:**
|
||||
|
||||
- "Coming soon" / "will be available" / "once finalized"
|
||||
- "This page is a placeholder"
|
||||
- "Being rolled out gradually" without concrete eligibility criteria
|
||||
|
||||
**Better alternatives:**
|
||||
|
||||
- Document what exists today
|
||||
- Wait to publish until the feature is complete
|
||||
- If phased rollout is real, be specific: "Available to organizations on Pro and Enterprise plans"
|
||||
|
||||
**Context matters:** Changelog and roadmap content naturally references the future — this guidance applies primarily to feature documentation.
|
||||
|
||||
## 3. Placeholder pages
|
||||
|
||||
**Principle:** Generally avoid shipping pages that explicitly say "This is a placeholder" or "More details coming soon."
|
||||
|
||||
**Alternatives:**
|
||||
|
||||
- Wait to publish until content is ready
|
||||
- If navigation structure requires it, link to external resources that are complete
|
||||
- Ship minimal but useful content (what's true today) rather than promises
|
||||
|
||||
**Valid exceptions:** Navigation structure needs, federated docs where the placeholder provides context and links out, cross-references where the page existing (even minimal) provides value.
|
||||
|
||||
## 4. Redundancy and over-explanation
|
||||
|
||||
**Principle:** Avoid restating the same point in multiple ways. Prefer brevity — sometimes broad strokes help more than mechanical detail.
|
||||
|
||||
**Common patterns:**
|
||||
|
||||
- Multiple ways of saying the same thing: "It's free" + "You won't be billed" + "No charge"
|
||||
- Admonition stating a risk, then body text restating it verbatim
|
||||
- Adjacent sentences that rephrase each other
|
||||
- Over-explaining mechanical steps or implementation detail that doesn't help the reader complete the task
|
||||
|
||||
**How to catch it:** If you can remove a sentence without losing information, it's probably redundant. If a procedure works with less setup explanation, prefer the shorter path.
|
||||
|
||||
## 5. Single-item lists
|
||||
|
||||
**Principle:** Prefer paragraphs over single-item bullet lists.
|
||||
|
||||
**Why:** Single-item lists can signal incomplete content.
|
||||
|
||||
**Valid exceptions:** Layout consistency across sections, future expansion expected, or when the item needs special visual emphasis.
|
||||
|
||||
## 6. Restating admonition content
|
||||
|
||||
**Principle:** Admonitions and body text should cover distinct points, not repeat each other.
|
||||
|
||||
**What to watch for:** An admonition stating a risk/limitation, then the next paragraph restating it verbatim.
|
||||
|
||||
## Summary checklist
|
||||
|
||||
Before submitting a draft, check:
|
||||
|
||||
- [ ] No HTML comments with internal PRD/roadmap/ticket context in MDX
|
||||
- [ ] Future promises minimized where appropriate (timeless documentation)
|
||||
- [ ] Placeholder pages avoided where possible
|
||||
- [ ] No unnecessary redundancy or mechanical over-explanation
|
||||
- [ ] Single-item lists avoided unless there's a reason
|
||||
- [ ] Admonitions and body text cover distinct points
|
||||
|
||||
**Note:** These are guidelines based on review feedback, not absolute rules. Use judgment based on content type, context, and the specific documentation needs. The goal is clearer, more maintainable docs, not rigid adherence to formatting rules.
|
||||
|
||||
## Style guide consolidation
|
||||
|
||||
This content is style guidance, not skill-specific process. It belongs in a shared, human-and-agent-readable style guide rather than only inside this skill. When a dedicated style guide exists in the repo, fold this content into it and replace this file with a pointer, the same pattern [reference/style-fallback.md](style-fallback.md) already uses for CONTRIBUTING.md/WORD_LIST.md.
|
||||
@@ -1,8 +1,10 @@
|
||||
# Drafting mechanics
|
||||
|
||||
Mechanics that come up during drafting but aren't worth duplicating from
|
||||
`CONTRIBUTING.md` or `ask-the-docs`. For nav wiring, partials, and file
|
||||
placement, see `ask-the-docs`'s `app-map.md` and `federated-docs.md`.
|
||||
Mechanics that come up during drafting but aren't worth duplicating from the
|
||||
style guide or `ask-the-docs`. For style rules, see
|
||||
[`apps/docs/style-guide/`](../../../../apps/docs/style-guide/README.md). For nav
|
||||
wiring, partials, and file placement, see `ask-the-docs`'s `app-map.md` and
|
||||
`federated-docs.md`.
|
||||
|
||||
## Link paths
|
||||
|
||||
@@ -38,6 +40,6 @@ From the repository root, run `pnpm format` to apply Prettier to changed MDX
|
||||
files. This enforces repo-wide formatting rules, including lowercase SQL
|
||||
keyword casing in code samples.
|
||||
|
||||
Check terminology against [`apps/docs/WORD_LIST.md`](../../../../apps/docs/WORD_LIST.md)
|
||||
Check terminology against [`WORD_LIST.md`](../../../../apps/docs/style-guide/WORD_LIST.md)
|
||||
yourself. Treat its replacements as suggestions when context matters: rewrite the
|
||||
sentence instead of applying one that changes its technical meaning.
|
||||
@@ -1,22 +0,0 @@
|
||||
# Style guide fallback
|
||||
|
||||
There is no separate published style guide yet beyond what already lives in
|
||||
this repo. Use the public sources below, in order.
|
||||
|
||||
This fallback governs voice, formatting, and terminology only. It's not a
|
||||
source of truth for behavior or product framing (that's the Gather phase's
|
||||
Linear + code read, per SKILL.md rule 3).
|
||||
|
||||
1. Read [`apps/docs/CONTRIBUTING.md`](../../../../apps/docs/CONTRIBUTING.md)
|
||||
for authoring conventions (voice, structure, document types).
|
||||
2. Read [`apps/docs/WORD_LIST.md`](../../../../apps/docs/WORD_LIST.md) for
|
||||
preferred spelling, capitalization, and terminology.
|
||||
3. If those don't cover the case, find the nearest comparable existing page
|
||||
under `apps/docs/content/` (same product area, similar content type —
|
||||
reference vs. guide vs. quickstart) and follow its voice, heading
|
||||
structure, and code-sample conventions.
|
||||
4. State which page you followed as precedent in the handoff summary — e.g.
|
||||
"no dedicated style guide yet — following the precedent of
|
||||
`guides/storage/uploads.mdx`."
|
||||
5. When a dedicated style guide is added to the repo later, prefer it over
|
||||
precedent-matching and drop this fallback step.
|
||||
+3
-3
@@ -34,7 +34,7 @@ reviews:
|
||||
Strictly enforce event naming: [object]_[verb] in snake_case. Only approved
|
||||
verbs: opened, clicked, submitted, created, removed, updated, retrieved,
|
||||
intended, evaluated, added, enabled, disabled, copied, exposed, failed,
|
||||
converted. Properties must be camelCase for new events (match existing
|
||||
converted, completed. Properties must be camelCase for new events (match existing
|
||||
convention when adding to existing events). Flag any usage of
|
||||
useSendEventMutation. Verify @group Events and @source JSDoc tags are
|
||||
accurate. Check that new interfaces are added to the TelemetryEvent union type.
|
||||
@@ -74,8 +74,8 @@ reviews:
|
||||
Flag style, terminology, and structure issues as usual. When a page has two or
|
||||
more of them, add one comment pointing the author at the `/write-the-docs` skill
|
||||
for new content or `/edit-the-docs` for an existing page (canonical files in
|
||||
`.agents/skills/`); both apply apps/docs/CONTRIBUTING.md and
|
||||
apps/docs/WORD_LIST.md. Skip that pointer on a single issue, so it stays a
|
||||
`.agents/skills/`); both apply the docs style guide in
|
||||
apps/docs/style-guide/. Skip that pointer on a single issue, so it stays a
|
||||
signal that the author isn't using the skills rather than boilerplate.
|
||||
- path: '{apps,packages}/**/*.{tsx,jsx,css,mdx}'
|
||||
instructions: |
|
||||
|
||||
@@ -22,4 +22,7 @@
|
||||
/apps/studio/components/interfaces/Organization/Documents/ @supabase/security
|
||||
/apps/studio/pages/new/index.tsx @supabase/security
|
||||
|
||||
/apps/studio/lib/ai/ @supabase/ai
|
||||
/apps/studio/pages/api/ai/ @supabase/ai
|
||||
|
||||
/packages/shared-data/compute-disk-limits.ts @supabase/platform
|
||||
@@ -38,10 +38,9 @@ Provide a clear numbered procedure that the PR reviewer can walk through.
|
||||
1. For example, `Open the live and preview links side-by-side.`
|
||||
2. For example, `See the issue is fixed.`
|
||||
|
||||
|
||||
## Checklist
|
||||
|
||||
Check all before review:
|
||||
|
||||
- [ ] I have read [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
|
||||
- [ ] If I wrote a new docs topic or edited an existing topic, I used the `/write-the-docs` or `/edit-the-docs` skill, which references [WORD_LIST](https://github.com/supabase/supabase/blob/master/apps/docs/WORD_LIST.md) and the docs [CONTRIBUTING](https://github.com/supabase/supabase/blob/master/apps/docs/CONTRIBUTING.md) guide
|
||||
- [ ] If I wrote a new docs topic or edited an existing topic, I used the `/write-the-docs` or `/edit-the-docs` skill, which applies the docs [style guide](https://github.com/supabase/supabase/tree/master/apps/docs/style-guide)
|
||||
@@ -0,0 +1,53 @@
|
||||
name: Ingest Docs Content for Search V2
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '.github/workflows/docs-search-v2-ingest.yml'
|
||||
- 'apps/docs/content/**/*.mdx'
|
||||
- 'apps/docs/scripts/search_v2/**'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-prod
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
ingest:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
|
||||
env:
|
||||
SEARCH_V2_SUPABASE_PROJECT_ID: ${{ secrets.SEARCH_V2_SUPABASE_PROJECT_ID }}
|
||||
SEARCH_V2_SUPABASE_SECRET_KEY: ${{ secrets.SEARCH_V2_SUPABASE_SECRET_KEY }}
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
sparse-checkout: |
|
||||
apps/docs
|
||||
packages
|
||||
patches
|
||||
|
||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
name: Install pnpm
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Download dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Ingest content
|
||||
working-directory: ./apps/docs
|
||||
run: pnpm run search-v2:ingest
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
- run: pnpm --filter library build:registry
|
||||
- name: Check generated registry
|
||||
run: |
|
||||
registry_changes="$(git status --porcelain --untracked-files=all -- apps/ui-library/public/r)"
|
||||
registry_changes="$(git status --porcelain --untracked-files=all -- apps/ui-library/public/r apps/ui-library/__registry__)"
|
||||
if [ -n "$registry_changes" ]; then
|
||||
printf '%s\n' "$registry_changes"
|
||||
echo 'Run pnpm --filter library build:registry and commit the generated registry files.'
|
||||
|
||||
@@ -47,6 +47,7 @@ jobs:
|
||||
permission-pull-requests: write
|
||||
|
||||
- name: Decrease ESLint ratchet baselines and open PR
|
||||
id: decrease-baselines
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
@@ -69,6 +70,7 @@ jobs:
|
||||
|
||||
if git diff --quiet; then
|
||||
echo "No baseline updates detected."
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -81,11 +83,45 @@ jobs:
|
||||
|
||||
pr_url=$(gh pr list --state open --head "$BRANCH" --json url --jq '.[0].url // ""' 2>/dev/null || echo "")
|
||||
if [ -z "$pr_url" ]; then
|
||||
gh pr create \
|
||||
pr_url=$(gh pr create \
|
||||
--title "[bot] Decrease ESLint ratchet baselines" \
|
||||
--body "Automated weekly decrease of ESLint ratchet baselines." \
|
||||
--base "$DEFAULT_BRANCH" \
|
||||
--head "$BRANCH"
|
||||
--head "$BRANCH")
|
||||
else
|
||||
gh pr comment "$pr_url" --body "Updated ESLint ratchet baselines with the latest weekly decreases."
|
||||
fi
|
||||
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
echo "pr_url=$pr_url" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: 'Notify #team-frontend about rules that hit a zero baseline'
|
||||
if: steps.decrease-baselines.outputs.changed == 'true'
|
||||
env:
|
||||
PR_URL: ${{ steps.decrease-baselines.outputs.pr_url }}
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_DASHBOARD_WEBHOOK_URL }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
zero_rules=$(jq -r '.rules | to_entries | map(select(.value == 0) | .key) | join(", ")' apps/studio/.github/eslint-rule-baselines.json)
|
||||
|
||||
if [ -z "$zero_rules" ]; then
|
||||
echo "No rules dropped to a baseline of 0; nothing to notify."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -z "${SLACK_WEBHOOK_URL:-}" ]; then
|
||||
echo "::warning::SLACK_DASHBOARD_WEBHOOK_URL secret is not set; skipping Slack notification for zero-baseline rules: $zero_rules"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
pr_number="${PR_URL##*/}"
|
||||
text="<@U0A1BRW39PC> the weekly ratchet-baseline job just dropped these rules to 0 in <${PR_URL}|#${pr_number}>: ${zero_rules}. Can you remove them from ratchet tracking and bump their ESLint severity to \"error\"?"
|
||||
|
||||
payload=$(jq -n --arg text "$text" '{text: $text}')
|
||||
|
||||
if ! curl --fail --silent --show-error -X POST "$SLACK_WEBHOOK_URL" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "$payload"; then
|
||||
echo "::warning::Failed to post Slack notification for zero-baseline rules: $zero_rules"
|
||||
fi
|
||||
@@ -0,0 +1,60 @@
|
||||
name: Partner intake form / HubSpot sync check
|
||||
|
||||
# Deliberately its own workflow, path-filtered to only the files this check
|
||||
# actually concerns — NOT part of www-tests.yml's `pnpm test` run, which
|
||||
# gates every apps/www PR. A HubSpot-side change surfaced by the scheduled
|
||||
# www-partner-form-sync.yml bot PR (which touches only the generated
|
||||
# snapshot) should fail *this* check, not block unrelated apps/www work.
|
||||
on:
|
||||
pull_request:
|
||||
branches: ['master']
|
||||
paths:
|
||||
- 'apps/www/components/Partners/PartnerIntakeForm.fields.ts'
|
||||
- 'apps/www/components/Partners/PartnerIntakeForm.sync.test.ts'
|
||||
- 'apps/www/components/Partners/__generated__/partner-intake-form.hubspot.json'
|
||||
- 'apps/www/lib/staticFormCrm.ts'
|
||||
- 'apps/www/vitest.sync.config.ts'
|
||||
- 'apps/www/package.json'
|
||||
- '.github/workflows/www-partner-form-sync-check.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CI: true
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
sparse-checkout: |
|
||||
apps/www
|
||||
packages
|
||||
supabase
|
||||
patches
|
||||
|
||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
name: Install pnpm
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install deps
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run partner intake form / HubSpot sync check
|
||||
run: pnpm run test:partner-form-sync
|
||||
working-directory: ./apps/www
|
||||
@@ -0,0 +1,78 @@
|
||||
name: Sync partner intake form from HubSpot
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run at 00:00 UTC every Monday
|
||||
- cron: '0 0 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
sync-partner-form:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.ref }}
|
||||
sparse-checkout: |
|
||||
apps/www
|
||||
packages
|
||||
patches
|
||||
|
||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
name: Install pnpm
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install deps
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Fetch the live HubSpot form and regenerate the snapshot
|
||||
run: pnpm run sync:partner-form
|
||||
working-directory: ./apps/www
|
||||
|
||||
- name: Generate token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
with:
|
||||
client-id: ${{ vars.GH_AUTOFIX_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.GH_AUTOFIX_PRIVATE_KEY }}
|
||||
permission-pull-requests: write
|
||||
permission-contents: write
|
||||
|
||||
# Only opens/updates a PR when the regenerated snapshot actually
|
||||
# differs from what's committed — i.e. only when the live HubSpot form
|
||||
# has changed since the last sync. The PR's existence is the drift
|
||||
# alert: PartnerIntakeForm.sync.test.ts (run by the normal
|
||||
# www-tests.yml suite on this PR) fails until a human reconciles
|
||||
# PartnerIntakeForm.tsx / staticFormCrm.ts with the new snapshot.
|
||||
- name: Create pull request
|
||||
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
commit-message: 'chore(www): sync partner intake form snapshot from HubSpot'
|
||||
title: 'chore(www): partner intake form has changed in HubSpot'
|
||||
body: |
|
||||
The live "Become a Partner" HubSpot form no longer matches the
|
||||
checked-in snapshot at
|
||||
`apps/www/components/Partners/__generated__/partner-intake-form.hubspot.json`.
|
||||
|
||||
`PartnerIntakeForm.sync.test.ts` will fail on this PR until
|
||||
`apps/www/components/Partners/PartnerIntakeForm.fields.ts` and
|
||||
`apps/www/lib/staticFormCrm.ts` are updated by hand to match —
|
||||
see the comment at the top of that test for what it checks.
|
||||
branch: 'gha/auto-sync-partner-intake-form'
|
||||
base: 'master'
|
||||
add-paths: |
|
||||
apps/www/components/Partners/__generated__/partner-intake-form.hubspot.json
|
||||
@@ -4,7 +4,11 @@ on:
|
||||
pull_request:
|
||||
branches: ['master']
|
||||
paths:
|
||||
- '.github/workflows/www-tests.yml'
|
||||
- 'apps/www/**/*.ts*'
|
||||
- 'apps/www/package.json'
|
||||
- 'apps/www/turbo.jsonc'
|
||||
- 'scripts/upload-static-assets.sh'
|
||||
- 'packages/common/sentry.ts'
|
||||
- 'packages/common/sentry.test.ts'
|
||||
- 'apps/www/next.config.mjs'
|
||||
@@ -45,6 +49,7 @@ jobs:
|
||||
apps/www
|
||||
apps/docs/content/guides
|
||||
packages
|
||||
scripts
|
||||
supabase
|
||||
patches
|
||||
|
||||
|
||||
+2
-1
@@ -14,13 +14,14 @@ apps/www/public/images/*
|
||||
apps/www/public/changelog.md
|
||||
apps/www/public/changelog/*.md
|
||||
apps/www/.generated/*
|
||||
# Written by apps/www/scripts/syncPartnerIntakeForm.mjs
|
||||
apps/www/components/Partners/__generated__
|
||||
apps/docs/**/generated/*
|
||||
apps/docs/examples/*
|
||||
examples/slack-clone/nextjs-slack-clone/full-schema.sql
|
||||
# ignore files with custom js formatting
|
||||
apps/studio/public
|
||||
apps/**/.turbo
|
||||
apps/docs/CONTRIBUTING.md
|
||||
apps/docs/__generated__
|
||||
# Generated by apps/docs/internals/generate-markdown-manifest.ts
|
||||
apps/docs/lib/markdown-manifest.ts
|
||||
|
||||
@@ -60,10 +60,10 @@ Never hand-edit generated files: `packages/api-types/types/**`, `**/routeTree.ge
|
||||
|
||||
## Skills
|
||||
|
||||
The skills in `.agents/skills/` are the source of truth for conventions — load the relevant ones before working, don't guess:
|
||||
The skills in `.agents/skills/` are the source of truth for conventions. Load the relevant ones before working, don't guess. One exception: for docs **content** style, `apps/docs/style-guide/` is the source of truth and the docs skills are the process that applies it.
|
||||
|
||||
- `copywriting` — any user-facing text, anywhere in the monorepo
|
||||
- `pm-the-docs` / `write-the-docs` / `edit-the-docs` / `ask-the-docs` / `review-the-docs` — anything under `apps/docs` (see `apps/docs/CONTRIBUTING.md` for the authoring skill model)
|
||||
- `pm-the-docs` / `write-the-docs` / `edit-the-docs` / `ask-the-docs` / `review-the-docs` — anything under `apps/docs` (see `apps/docs/CONTRIBUTING.md` for the authoring skill model, and `apps/docs/style-guide/` for the content style rules they apply)
|
||||
- `telemetry-standards` — PostHog events, `packages/common/telemetry-constants.ts`
|
||||
- `dev-toolbar-review` — `packages/dev-tools`, `packages/common/posthog-client.ts`, `packages/common/feature-flags.tsx`
|
||||
- `safe-sql-execution` — any code that builds or executes SQL against user databases
|
||||
|
||||
@@ -7,10 +7,14 @@ const color = colors['default']
|
||||
|
||||
const Colors = ({
|
||||
definition,
|
||||
classes,
|
||||
}: {
|
||||
definition: 'background' | 'border' | 'text' | 'colors' | 'palletes'
|
||||
/** Optional subset of utility classes. Defaults to the full set for `definition`. */
|
||||
classes?: string[]
|
||||
}) => {
|
||||
const [copiedIndex, setCopiedIndex] = useState<number | null>(null)
|
||||
const items = classes ?? color[definition]
|
||||
|
||||
const handleCopy = async (value: string, index: number) => {
|
||||
try {
|
||||
@@ -63,7 +67,7 @@ const Colors = ({
|
||||
return (
|
||||
<>
|
||||
<Grid>
|
||||
{color[definition].map((x: string, i) => {
|
||||
{items.map((x: string, i) => {
|
||||
return (
|
||||
<GridItem
|
||||
key={i}
|
||||
|
||||
@@ -26,7 +26,9 @@ All interactive page elements should be reachable by keyboard. Native buttons, l
|
||||
|
||||
Chromium-based browsers and Firefox handle this automatically via the Tab key. Safari, by default, requires the Option key to also be held down. Enabling _Keyboard navigation_ on macOS Settings [removes this requirement](https://mayank.co/blog/safari-focus/#keyboard-navigation) but makes links non-tabbable as a result.
|
||||
|
||||
Interactive page elements should also provide visual feedback upon selection via a `focus-visible` state. We use one shared focus ring so users recognize this state instantly.
|
||||
Interactive page elements should also provide visual feedback upon selection via a `focus-visible` state. We use one shared focus ring so users recognize this state instantly. Its color follows the theme's brighter primary hue in both themes. See [Primary and brand colors](../docs/color-usage#primary-and-brand-colors).
|
||||
|
||||
An editable text field can use its visible insertion caret to show focus. This can be useful for inputs embedded within a compact control, where another ring would obscure nearby elements. Check that the caret clearly identifies the active field. Read-only fields and controls without a caret still need a visible focus indicator.
|
||||
|
||||
### Focus ring recipe
|
||||
|
||||
@@ -75,8 +77,8 @@ transition-property: color, background-color, border-color, ...
|
||||
Rules:
|
||||
|
||||
- Prefer `:focus-visible` over `:focus` so click/tap does not show a focus indicator
|
||||
- Never use `outline-none` / `outline-hidden` without a ring or outline replacement
|
||||
- Always use the shared color (`ring-ring` / `outline-ring`). Variants (primary, danger, warning) do not change focus colour
|
||||
- Never use `outline-none` / `outline-hidden` without a visible focus indicator, such as a ring, outline, or insertion caret in an editable text field
|
||||
- Always use the shared color (`ring-ring` / `outline-ring`). Variants (primary, danger, warning) do not change focus color. The shared color derives from `--primary` with enough lightness to remain visible in light mode.
|
||||
- Do not animate the focus indicator; avoid `transition-all` / `transition` on controls that show one (prefer `transition-colors`)
|
||||
- Prefer `focus-ring` / `focus-inset` over copy-pasting the class stack
|
||||
- On interactive `<tr>`s, use `focus-inset` only. `focus-ring` will look fine in some browsers and invisible in others
|
||||
|
||||
@@ -5,21 +5,58 @@ description: Colors system breakdown with best practices.
|
||||
|
||||
The shorthand utility classes below simplify our full color palette by providing sensible, contrast-checked defaults. Use them whenever possible to ensure accessible text colors and balanced background fills.
|
||||
|
||||
## Primary and brand colors
|
||||
|
||||
Primary is the theme's functional accent. Changing `--primary-hue` updates primary text, buttons, selected controls, and focus rings. Brand is the fixed Supabase palette for product identity.
|
||||
|
||||
| Role | Tokens | Follows the primary hue? |
|
||||
| ----------------- | ------------------------------------------------------------ | ------------------------ |
|
||||
| Functional accent | `--primary`, `--primary-solid`, `--primary-bright`, `--ring` | Yes |
|
||||
| Supabase identity | `brand-default`, `brand-*` | No |
|
||||
|
||||
### Primary ink
|
||||
|
||||
`--primary` is for readable UI: links, labels, radios, step dots, and other small indicators. Utilities include `text-primary`, `bg-primary`, and `border-primary`.
|
||||
|
||||
In light mode it is darkened to meet WCAG AA for normal text. In dark mode it stays bright so those controls remain visible on dark surfaces.
|
||||
|
||||
### Primary solid
|
||||
|
||||
`--primary-solid` is the deeper fill for primary buttons. In dark mode it is darker than `--primary` so buttons do not read as lime. In light mode it matches `--primary`, so ink and buttons share one green. Primary Button uses `bg-primary-solid` / `text-primary-solid-foreground`.
|
||||
|
||||
### Bright primary
|
||||
|
||||
`--primary-bright` gives focus rings, selected controls, and other interactive chrome a brighter version of the primary hue. It stays vivid when light-mode `--primary` is darkened for readable text. Use `bg-primary-bright`, `border-primary-bright`, and the shared `ring-ring` focus treatment.
|
||||
|
||||
### Brand green
|
||||
|
||||
`brand-default` remains the fixed Supabase green for explicitly branded surfaces.
|
||||
|
||||
Toggle the theme to compare the swatches below. Light mode collapses primary and primary-solid; dark mode separates bright ink from the deeper button plate:
|
||||
|
||||
<Colors
|
||||
definition={'colors'}
|
||||
classes={['bg-primary', 'bg-primary-solid', 'bg-primary-bright', 'bg-brand-default']}
|
||||
/>
|
||||
|
||||
See also [Typography](../docs/typography) (`text-primary`) and [Accessibility](../docs/accessibility#focus-management) (shared focus ring).
|
||||
|
||||
## Text
|
||||
|
||||
Use accent text colors (e.g. text-destructive, text-warning) sparingly to avoid visual overload.
|
||||
|
||||
Use `text-primary` for readable branded text such as links, labels, and statuses. On light-theme
|
||||
surfaces it meets the 4.5:1 WCAG AA requirement for normal text. Use `bg-brand-default` /
|
||||
`border-brand-default` when you need the canonical bright Supabase green as a fill or border.
|
||||
Use `text-primary` for readable branded text such as links, labels, and statuses. It meets
|
||||
WCAG AA for normal text on the surfaces used by the apps: darkened in light mode, bright in
|
||||
dark mode. Primary buttons use a separate solid plate in dark mode; focus chrome uses
|
||||
the brighter primary hue. See [Primary and brand colors](#primary-and-brand-colors).
|
||||
|
||||
<Colors definition={'text'} />
|
||||
|
||||
## Background
|
||||
|
||||
Use `bg-brand-default` when the canonical Supabase green is required as a fill. The same
|
||||
`brand-default` suffix applies to borders and other non-text utilities, such as
|
||||
`border-brand-default`.
|
||||
Use `bg-primary-bright` for selected controls that need a vivid fill, and `bg-primary`
|
||||
for smaller indicators such as radios and step dots. Use `bg-brand-default` only when
|
||||
the canonical Supabase green is required. Details in [Primary and brand colors](#primary-and-brand-colors).
|
||||
|
||||
<Colors definition={'background'} />
|
||||
|
||||
@@ -40,7 +77,7 @@ We use backgrounds in 2 different ways. In the ./www and ./docs sites, we use a
|
||||
<body className="bg-studio">{children}</body>
|
||||
```
|
||||
|
||||
### Backgrounds and Surfaces
|
||||
### Backgrounds and surfaces
|
||||
|
||||
#### `./apps/www` + `./apps/docs`
|
||||
|
||||
@@ -72,7 +109,7 @@ This is not to be confused with `Dialogs`, they require to use the same app back
|
||||
|
||||
<Colors definition={'border'} />
|
||||
|
||||
## Other Colors
|
||||
## Other colors
|
||||
|
||||
These can also be accessed with `foreground`. Like `text-foreground-light`.
|
||||
|
||||
|
||||
@@ -100,13 +100,17 @@ Used for actions that are not as important as the primary action, or for actions
|
||||
|
||||
<ComponentPreview name="button-link" />
|
||||
|
||||
### Only an icon
|
||||
### Icon-only
|
||||
|
||||
Displaying only an Icon in a button.
|
||||
Render an [icon](./icons) in a button without accompanying text. Ensure the button is accessible by:
|
||||
|
||||
<Admonition type="note" title="This feature requires more support" className="mt-3">
|
||||
We should update the button component to support this use case better.
|
||||
</Admonition>
|
||||
- Wrapping it in a [Tooltip](./tooltip) for sighted users.
|
||||
- Adding an `aria-label` prop for screen readers.
|
||||
- Setting its `aria-describedby` prop to `undefined` when the tooltip content repeats the label. Otherwise screen readers read the label twice.
|
||||
|
||||
Consider also squaring off the button container as shown in the example below. For the default `tiny` size (`h-[26px]`), use `w-6.5` so the button is square.
|
||||
|
||||
Compact icon-only buttons may also benefit from the `hit-area` utility. This increases their tap target slightly each side without changing the visual layout. See the [Table](./table#actions) component for more information.
|
||||
|
||||
<ComponentPreview name="button-icon" />
|
||||
|
||||
|
||||
@@ -132,3 +132,9 @@ You can create a responsive combobox by using the `<Popover />` on desktop and t
|
||||
### Form
|
||||
|
||||
<ComponentPreview name="combobox-form" />
|
||||
|
||||
### Create from list
|
||||
|
||||
When users can create an option while choosing one, give the action a clear verb label and place it at the bottom of the list, separated from the existing options. Keep the current selection in place while they create the new option.
|
||||
|
||||
<ComponentPreview name="combobox-create-option" peekCode wide />
|
||||
@@ -72,6 +72,12 @@ import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
<ComponentPreview name="textarea-with-button" />
|
||||
|
||||
### With addon
|
||||
|
||||
An addon places supporting content inside the same bordered field as the textarea. Wrap `InputGroupTextarea` and `InputGroupAddon` in `InputGroup`, and use `align="block-end"` to place the addon below the text.
|
||||
|
||||
<ComponentPreview name="textarea-with-addon" />
|
||||
|
||||
### Form
|
||||
|
||||
<ComponentPreview name="textarea-form" />
|
||||
@@ -89,3 +89,37 @@ import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
|
||||
### Disabled
|
||||
|
||||
<ComponentPreview name="toggle-group-disabled" />
|
||||
|
||||
## Segmented
|
||||
|
||||
A Supabase addition, not part of shadcn or Radix. A segmented control is a compact row of two to
|
||||
four mutually exclusive options sharing one continuous track, for switching view state (Data or
|
||||
Definition) or filtering a list (All, Active, Revoked). The choice takes effect immediately and is
|
||||
never saved, so reach for a [Switch](../components/switch) or
|
||||
[Radio Group](../components/radio-group) when the setting is persisted, and
|
||||
[Tabs](../components/tabs) when the options swap whole panels of content. Items paint nothing
|
||||
themselves; a single indicator slides between them, so the group always has exactly one thing
|
||||
selected. Give a filter an explicit **All** segment rather than letting deselection stand for
|
||||
"no filter".
|
||||
|
||||
<ComponentPreview name="toggle-group-segmented" />
|
||||
|
||||
<ComponentPreview name="toggle-group-segmented-filter" peekCode />
|
||||
|
||||
## Props
|
||||
|
||||
`ToggleGroup` forwards every prop to the underlying
|
||||
[Radix Toggle Group](https://www.radix-ui.com/docs/primitives/components/toggle-group#api-reference).
|
||||
These are the ones worth knowing about.
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| --------------- | --------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `type` | `'single' \| 'multiple'` | | Required by Radix. Only a `'single'` group gets the segmented indicator; a `'multiple'` group falls back to items painting their own background. |
|
||||
| `variant` | `'default' \| 'outline' \| 'segmented'` | `'default'` | Styles the root and every item, which inherit it through context. `segmented` drops the gaps and per-item borders in favor of an indicator that slides between segments, respecting `prefers-reduced-motion`. |
|
||||
| `size` | `'tiny' \| 'sm' \| 'default' \| 'lg'` | `'default'` | Item height and padding, inherited through context. `tiny` suits a dense toolbar or table footer. Segments hug their labels, so add `className="flex-1"` to each item, plus a width on the root, if you need them equal. |
|
||||
| `tone` | `'text' \| 'outline' \| 'primary'` | `'text'` | Segmented only. `text` is a flat `bg-accent` fill, `outline` adds a container border and a raised bordered indicator, `primary` fills the indicator with brand. |
|
||||
| `allowDeselect` | `boolean` | `true` | Whether clicking the active item in a `type="single"` group clears it, emitting `''`. Set `false` for a segmented control, which has no empty state. |
|
||||
| `aria-label` | `string` | | Names what is being switched or filtered. The segments name the options, not the axis. |
|
||||
|
||||
`variant` and `size` are the shadcn variant axes; `segmented`, `tone` and `allowDeselect` are the
|
||||
Supabase additions. `default` and `outline` are unchanged from shadcn.
|
||||
@@ -50,6 +50,34 @@ export function FilterDemo() {
|
||||
}
|
||||
```
|
||||
|
||||
## Variants
|
||||
|
||||
The default variant joins filters into a segmented bar. Use the pill variant to show each filter as a separate rounded chip:
|
||||
|
||||
### Segmented
|
||||
|
||||
<ComponentPreview
|
||||
name="filter-bar-segmented-demo"
|
||||
description="Filters joined into a segmented bar."
|
||||
peekCode
|
||||
showDottedGrid
|
||||
wide
|
||||
/>
|
||||
|
||||
### Pill
|
||||
|
||||
<ComponentPreview
|
||||
name="filter-bar-pill-demo"
|
||||
description="Rounded chips for each filter, as used in Studio."
|
||||
peekCode
|
||||
showDottedGrid
|
||||
wide
|
||||
/>
|
||||
|
||||
```tsx
|
||||
<FilterBar variant="pill" className="border-0 bg-transparent overflow-visible" {...props} />
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### FilterProperty
|
||||
@@ -60,6 +88,7 @@ interface FilterProperty {
|
||||
name: string
|
||||
type: 'string' | 'number' | 'date' | 'boolean'
|
||||
operators: string[]
|
||||
formatValue?: (value: FilterCondition['value']) => string
|
||||
options?: string[] | ((search?: string) => Promise<string[]> | string[])
|
||||
}
|
||||
```
|
||||
@@ -94,6 +123,7 @@ interface FilterCondition {
|
||||
| onFreeformTextChange | (text: string) => void | Callback when free-form text changes |
|
||||
| actions | FilterBarAction[]? | Optional custom actions to show in the menu |
|
||||
| isLoading | boolean? | If true, dims the bar while work is in progress |
|
||||
| variant | 'default' \| 'pill'? | Filter appearance. Defaults to 'default' |
|
||||
|
||||
## Custom actions (e.g. AI)
|
||||
|
||||
|
||||
@@ -14,4 +14,7 @@ The shorthands below are composed of core [Tailwind utility classes](../docs/tai
|
||||
| `text-code-inline` | Apply to a `code` element for inline code or similar custom inline content |
|
||||
| `text-primary` | Accessible Supabase green for readable branded text |
|
||||
|
||||
`text-primary` is independent from `bg-brand-default`, `border-brand-default`, and other non-text brand utilities. Its light-mode value has at least 4.5:1 contrast against the light surfaces used by the apps, meeting WCAG AA for normal text.
|
||||
`text-primary` meets WCAG AA for normal text on app surfaces: darkened in light mode,
|
||||
bright in dark mode. Focus rings and selected controls derive their brighter fill from
|
||||
the same primary hue. See [Primary and brand colors](../docs/color-usage#primary-and-brand-colors) for when
|
||||
to use each green.
|
||||
@@ -222,27 +222,27 @@ type ChartConfig = {
|
||||
|
||||
Line chart component for displaying time-series data.
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| ------------------- | --------------------------------------------------------------------- | ----------------------------- | ----------------------------------------------------------------- |
|
||||
| `data` | `ChartLineTick[]` | - | Array of data points with timestamp |
|
||||
| `dataKey` | `string` | - | Key in data object to plot |
|
||||
| `config` | `ChartConfig?` | - | Chart configuration for styling |
|
||||
| `onLineClick` | `(datum: ChartLineTick, tooltipData?: CategoricalChartState) => void` | - | Click handler for line points |
|
||||
| `DateTimeFormat` | `string` | `'MMM D, YYYY, hh:mma'` | Date format for tooltips and labels |
|
||||
| `isFullHeight` | `boolean` | `false` | Whether chart should take full height |
|
||||
| `className` | `string` | - | Additional CSS classes |
|
||||
| `color` | `string` | `'hsl(var(--brand-default))'` | Line color |
|
||||
| `hoverColor` | `string` | `'hsl(var(--brand-500))'` | Line color on hover |
|
||||
| `chartHighlight` | `ChartHighlight` | - | Highlight selection configuration |
|
||||
| `updateDateRange` | `(from: string, to: string) => void` | - | Callback when date range is updated via highlight |
|
||||
| `highlightActions` | `ChartHighlightAction[]` | - | Actions to show when area is highlighted |
|
||||
| `syncId` | `string` | - | ID to sync multiple charts |
|
||||
| `showHighlightArea` | `boolean` | `true` | Whether to show highlight area |
|
||||
| `cursor` | `string` | - | Cursor style (defaults to 'crosshair' if chartHighlight provided) |
|
||||
| `showGrid` | `boolean` | `false` | Whether to show grid lines |
|
||||
| `showYAxis` | `boolean` | `false` | Whether to show Y-axis |
|
||||
| `YAxisProps` | `object` | - | Additional Y-axis props (tick, tickFormatter, width, etc.) |
|
||||
| `strokeWidth` | `number` | `1.5` | Line stroke width |
|
||||
| Prop | Type | Default | Description |
|
||||
| ------------------- | --------------------------------------------------------------------- | ------------------------- | ----------------------------------------------------------------- |
|
||||
| `data` | `ChartLineTick[]` | - | Array of data points with timestamp |
|
||||
| `dataKey` | `string` | - | Key in data object to plot |
|
||||
| `config` | `ChartConfig?` | - | Chart configuration for styling |
|
||||
| `onLineClick` | `(datum: ChartLineTick, tooltipData?: CategoricalChartState) => void` | - | Click handler for line points |
|
||||
| `DateTimeFormat` | `string` | `'MMM D, YYYY, hh:mma'` | Date format for tooltips and labels |
|
||||
| `isFullHeight` | `boolean` | `false` | Whether chart should take full height |
|
||||
| `className` | `string` | - | Additional CSS classes |
|
||||
| `color` | `string` | `'var(--primary-bright)'` | Line color |
|
||||
| `hoverColor` | `string` | `'hsl(var(--brand-500))'` | Line color on hover |
|
||||
| `chartHighlight` | `ChartHighlight` | - | Highlight selection configuration |
|
||||
| `updateDateRange` | `(from: string, to: string) => void` | - | Callback when date range is updated via highlight |
|
||||
| `highlightActions` | `ChartHighlightAction[]` | - | Actions to show when area is highlighted |
|
||||
| `syncId` | `string` | - | ID to sync multiple charts |
|
||||
| `showHighlightArea` | `boolean` | `true` | Whether to show highlight area |
|
||||
| `cursor` | `string` | - | Cursor style (defaults to 'crosshair' if chartHighlight provided) |
|
||||
| `showGrid` | `boolean` | `false` | Whether to show grid lines |
|
||||
| `showYAxis` | `boolean` | `false` | Whether to show Y-axis |
|
||||
| `YAxisProps` | `object` | - | Additional Y-axis props (tick, tickFormatter, width, etc.) |
|
||||
| `strokeWidth` | `number` | `1.5` | Line stroke width |
|
||||
|
||||
#### ChartLineTick Type
|
||||
|
||||
@@ -275,26 +275,26 @@ Line chart component for displaying time-series data.
|
||||
|
||||
Bar chart component for displaying time-series data.
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| ------------------- | -------------------------------------------------------------------- | ----------------------------- | ----------------------------------------------------------------- |
|
||||
| `data` | `ChartBarTick[]` | - | Array of data points with timestamp |
|
||||
| `dataKey` | `string` | - | Key in data object to plot |
|
||||
| `config` | `ChartConfig?` | - | Chart configuration for styling |
|
||||
| `onBarClick` | `(datum: ChartBarTick, tooltipData?: CategoricalChartState) => void` | - | Click handler for bars |
|
||||
| `DateTimeFormat` | `string` | `'MMM D, YYYY, hh:mma'` | Date format for tooltips and labels |
|
||||
| `isFullHeight` | `boolean` | `false` | Whether chart should take full height |
|
||||
| `className` | `string` | - | Additional CSS classes |
|
||||
| `color` | `string` | `'hsl(var(--brand-default))'` | Bar color |
|
||||
| `hoverColor` | `string` | `'hsl(var(--brand-500))'` | Bar color on hover |
|
||||
| `chartHighlight` | `ChartHighlight` | - | Highlight selection configuration |
|
||||
| `updateDateRange` | `(from: string, to: string) => void` | - | Callback when date range is updated via highlight |
|
||||
| `highlightActions` | `ChartHighlightAction[]` | - | Actions to show when area is highlighted |
|
||||
| `syncId` | `string` | - | ID to sync multiple charts |
|
||||
| `showHighlightArea` | `boolean` | `true` | Whether to show highlight area |
|
||||
| `cursor` | `string` | - | Cursor style (defaults to 'crosshair' if chartHighlight provided) |
|
||||
| `showGrid` | `boolean` | `false` | Whether to show grid lines |
|
||||
| `showYAxis` | `boolean` | `false` | Whether to show Y-axis |
|
||||
| `YAxisProps` | `object` | - | Additional Y-axis props (tick, tickFormatter, width, etc.) |
|
||||
| Prop | Type | Default | Description |
|
||||
| ------------------- | -------------------------------------------------------------------- | ------------------------------- | ----------------------------------------------------------------- |
|
||||
| `data` | `ChartBarTick[]` | - | Array of data points with timestamp |
|
||||
| `dataKey` | `string` | - | Key in data object to plot |
|
||||
| `config` | `ChartConfig?` | - | Chart configuration for styling |
|
||||
| `onBarClick` | `(datum: ChartBarTick, tooltipData?: CategoricalChartState) => void` | - | Click handler for bars |
|
||||
| `DateTimeFormat` | `string` | `'MMM D, YYYY, hh:mma'` | Date format for tooltips and labels |
|
||||
| `isFullHeight` | `boolean` | `false` | Whether chart should take full height |
|
||||
| `className` | `string` | - | Additional CSS classes |
|
||||
| `color` | `string` | `'var(--primary-bright)'` | Bar color |
|
||||
| `hoverColor` | `string` | `'var(--primary-bright-hover)'` | Bar color on hover |
|
||||
| `chartHighlight` | `ChartHighlight` | - | Highlight selection configuration |
|
||||
| `updateDateRange` | `(from: string, to: string) => void` | - | Callback when date range is updated via highlight |
|
||||
| `highlightActions` | `ChartHighlightAction[]` | - | Actions to show when area is highlighted |
|
||||
| `syncId` | `string` | - | ID to sync multiple charts |
|
||||
| `showHighlightArea` | `boolean` | `true` | Whether to show highlight area |
|
||||
| `cursor` | `string` | - | Cursor style (defaults to 'crosshair' if chartHighlight provided) |
|
||||
| `showGrid` | `boolean` | `false` | Whether to show grid lines |
|
||||
| `showYAxis` | `boolean` | `false` | Whether to show Y-axis |
|
||||
| `YAxisProps` | `object` | - | Additional Y-axis props (tick, tickFormatter, width, etc.) |
|
||||
|
||||
#### ChartBarTick Type
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ export default function ComposedChartBasic() {
|
||||
const chartConfig = {
|
||||
standard_score: {
|
||||
label: 'Standard Score',
|
||||
color: 'hsl(var(--brand-default))',
|
||||
color: 'var(--primary-bright)',
|
||||
},
|
||||
performance: {
|
||||
label: 'Performance',
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
import { Button } from 'ui'
|
||||
import { ExternalLink } from 'lucide-react'
|
||||
import { Button, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
||||
|
||||
export default function ButtonIcon() {
|
||||
return <Button variant="outline" icon={<ChevronRight className="h-4 w-4" />}></Button>
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
icon={<ExternalLink />}
|
||||
// Match tooltip content for screen readers
|
||||
aria-label="View logs"
|
||||
// Tooltip repeats the label; clear describedby so screen readers don't hear it twice
|
||||
// Skip this if the tooltip adds information beyond the label
|
||||
aria-describedby={undefined}
|
||||
></Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>View logs</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { Checkbox } from 'ui'
|
||||
|
||||
export default function CheckboxWithText() {
|
||||
return (
|
||||
<div className="items-top flex space-x-2">
|
||||
<div className="items-start flex space-x-2">
|
||||
<Checkbox id="terms1" />
|
||||
<div className="grid gap-1.5 leading-none">
|
||||
<label
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
'use client'
|
||||
|
||||
import { Check, Plus } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
ComboboxTrigger,
|
||||
Command,
|
||||
CommandGroup,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from 'ui'
|
||||
|
||||
const buckets = ['Images', 'Exports']
|
||||
|
||||
export default function ComboboxCreateOption() {
|
||||
const [selectedBucket, setSelectedBucket] = useState('')
|
||||
const [isListOpen, setIsListOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<Popover open={isListOpen} onOpenChange={setIsListOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<ComboboxTrigger
|
||||
aria-expanded={isListOpen}
|
||||
data-state={isListOpen ? 'open' : 'closed'}
|
||||
className="w-[240px]"
|
||||
>
|
||||
{selectedBucket || 'Select a bucket'}
|
||||
</ComboboxTrigger>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[240px] p-0" align="start">
|
||||
<Command>
|
||||
<CommandList>
|
||||
<CommandGroup>
|
||||
{buckets.map((bucket) => (
|
||||
<CommandItem
|
||||
key={bucket}
|
||||
value={bucket}
|
||||
className="cursor-pointer justify-between"
|
||||
onSelect={() => {
|
||||
setSelectedBucket(bucket)
|
||||
setIsListOpen(false)
|
||||
}}
|
||||
>
|
||||
{bucket}
|
||||
{selectedBucket === bucket && <Check size={14} />}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
<CommandSeparator />
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
className="cursor-pointer"
|
||||
onSelect={() => {
|
||||
setIsListOpen(false)
|
||||
// Open the creation flow here without changing the selected bucket.
|
||||
}}
|
||||
>
|
||||
<Plus size={14} className="mr-2" />
|
||||
New bucket
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
@@ -118,19 +118,28 @@ const initialFilters: FilterGroup = {
|
||||
conditions: [],
|
||||
}
|
||||
|
||||
export default function FilterBarDemo() {
|
||||
function FilterBarExample({ variant }: { variant: 'default' | 'pill' }) {
|
||||
const [filters, setFilters] = useState<FilterGroup>(initialFilters)
|
||||
const [freeformText, setFreeformText] = useState('')
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<FilterBar
|
||||
filterProperties={filterProperties}
|
||||
freeformText={freeformText}
|
||||
onFreeformTextChange={setFreeformText}
|
||||
filters={filters}
|
||||
onFilterChange={setFilters}
|
||||
/>
|
||||
<FilterBar
|
||||
variant={variant}
|
||||
className={variant === 'pill' ? 'border-0 bg-transparent overflow-visible' : undefined}
|
||||
filterProperties={filterProperties}
|
||||
freeformText={freeformText}
|
||||
onFreeformTextChange={setFreeformText}
|
||||
filters={filters}
|
||||
onFilterChange={setFilters}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default function FilterBarDemo() {
|
||||
return (
|
||||
<div className="w-full space-y-6">
|
||||
<FilterBarExample variant="default" />
|
||||
<FilterBarExample variant="pill" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { useState } from 'react'
|
||||
import { FilterBar, type FilterGroup } from 'ui-patterns/FilterBar'
|
||||
|
||||
const filterProperties = [
|
||||
{ label: 'Name', name: 'name', type: 'string' as const, operators: ['=', '!='] },
|
||||
{
|
||||
label: 'Status',
|
||||
name: 'status',
|
||||
type: 'string' as const,
|
||||
options: ['active', 'inactive', 'pending'],
|
||||
operators: ['=', '!='],
|
||||
},
|
||||
]
|
||||
|
||||
export default function FilterBarPillDemo() {
|
||||
const [filters, setFilters] = useState<FilterGroup>({ logicalOperator: 'AND', conditions: [] })
|
||||
const [freeformText, setFreeformText] = useState('')
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<FilterBar
|
||||
variant="pill"
|
||||
className="border-0 bg-transparent overflow-visible"
|
||||
filterProperties={filterProperties}
|
||||
filters={filters}
|
||||
onFilterChange={setFilters}
|
||||
freeformText={freeformText}
|
||||
onFreeformTextChange={setFreeformText}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { useState } from 'react'
|
||||
import { FilterBar, type FilterGroup } from 'ui-patterns/FilterBar'
|
||||
|
||||
const filterProperties = [
|
||||
{ label: 'Name', name: 'name', type: 'string' as const, operators: ['=', '!='] },
|
||||
{
|
||||
label: 'Status',
|
||||
name: 'status',
|
||||
type: 'string' as const,
|
||||
options: ['active', 'inactive', 'pending'],
|
||||
operators: ['=', '!='],
|
||||
},
|
||||
]
|
||||
|
||||
export default function FilterBarSegmentedDemo() {
|
||||
const [filters, setFilters] = useState<FilterGroup>({ logicalOperator: 'AND', conditions: [] })
|
||||
const [freeformText, setFreeformText] = useState('')
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<FilterBar
|
||||
filterProperties={filterProperties}
|
||||
filters={filters}
|
||||
onFilterChange={setFilters}
|
||||
freeformText={freeformText}
|
||||
onFreeformTextChange={setFreeformText}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -154,21 +154,21 @@ const EXECUTION_TIME_CHART_CONFIG = {
|
||||
},
|
||||
max_execution_time: {
|
||||
label: 'Max Execution Time',
|
||||
color: 'hsl(var(--brand-default))',
|
||||
color: 'var(--primary-bright)',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
const CPU_TIME_CHART_CONFIG = {
|
||||
max_cpu_time_used: {
|
||||
label: 'Max CPU Time',
|
||||
color: 'hsl(var(--brand-default))',
|
||||
color: 'var(--primary-bright)',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
const MEMORY_CHART_CONFIG = {
|
||||
avg_memory_used: {
|
||||
label: 'Memory Usage',
|
||||
color: 'hsl(var(--brand-default))',
|
||||
color: 'var(--primary-bright)',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
|
||||
@@ -23,13 +23,13 @@ export default function SuccessCheckSelected() {
|
||||
className={cn(
|
||||
'relative flex w-full items-center rounded-md border px-4 py-3 text-left text-sm transition-colors',
|
||||
isSelected
|
||||
? 'border-brand-default bg-brand-200/20 pr-10 dark:bg-brand-300'
|
||||
? 'border-primary-bright bg-primary-bright/10 pr-10'
|
||||
: 'hover:border-default hover:bg-surface-200'
|
||||
)}
|
||||
>
|
||||
{option}
|
||||
{isSelected && (
|
||||
<SuccessCheck className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2" />
|
||||
<SuccessCheck className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 border-primary-bright bg-primary-bright text-black" />
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { InputGroup, InputGroupAddon, InputGroupText, InputGroupTextarea } from 'ui'
|
||||
|
||||
export default function TextareaWithAddon() {
|
||||
return (
|
||||
<InputGroup className="w-full max-w-sm">
|
||||
<InputGroupTextarea placeholder="Type your message here." rows={4} maxLength={120} />
|
||||
<InputGroupAddon align="block-end">
|
||||
<InputGroupText>120 character limit</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { ToggleGroup, ToggleGroupItem } from 'ui'
|
||||
|
||||
const SIZES = ['tiny', 'sm', 'default', 'lg'] as const
|
||||
|
||||
export default function ToggleGroupSegmentedFilter() {
|
||||
const [status, setStatus] = React.useState('all')
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{SIZES.map((size) => (
|
||||
<div key={size} className="flex items-center gap-4">
|
||||
<span className="w-16 shrink-0 text-xs text-foreground-lighter">{size}</span>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
variant="segmented"
|
||||
size={size}
|
||||
value={status}
|
||||
onValueChange={setStatus}
|
||||
allowDeselect={false}
|
||||
aria-label={`Filter keys by status (${size})`}
|
||||
>
|
||||
<ToggleGroupItem value="all">All</ToggleGroupItem>
|
||||
<ToggleGroupItem value="active">Active</ToggleGroupItem>
|
||||
<ToggleGroupItem value="revoked">Revoked</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { ToggleGroup, ToggleGroupItem } from 'ui'
|
||||
|
||||
const TONES = ['text', 'outline', 'primary'] as const
|
||||
|
||||
export default function ToggleGroupSegmented() {
|
||||
const [view, setView] = React.useState('data')
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{TONES.map((tone) => (
|
||||
<div key={tone} className="flex items-center gap-4">
|
||||
<span className="w-16 shrink-0 text-xs text-foreground-lighter">{tone}</span>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
variant="segmented"
|
||||
tone={tone}
|
||||
value={view}
|
||||
onValueChange={setView}
|
||||
allowDeselect={false}
|
||||
aria-label={`Table view (${tone})`}
|
||||
>
|
||||
<ToggleGroupItem value="data">Data</ToggleGroupItem>
|
||||
<ToggleGroupItem value="definition">Definition</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -385,6 +385,12 @@ export const examples: Registry = [
|
||||
registryDependencies: ['command'],
|
||||
files: ['example/combobox-demo.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'combobox-create-option',
|
||||
type: 'components:example',
|
||||
registryDependencies: ['command'],
|
||||
files: ['example/combobox-create-option.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'combobox-dropdown-menu',
|
||||
type: 'components:example',
|
||||
@@ -605,6 +611,18 @@ export const examples: Registry = [
|
||||
registryDependencies: ['filter-bar'],
|
||||
files: ['example/filter-bar-demo.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'filter-bar-pill-demo',
|
||||
type: 'components:example',
|
||||
registryDependencies: ['filter-bar'],
|
||||
files: ['example/filter-bar-pill-demo.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'filter-bar-segmented-demo',
|
||||
type: 'components:example',
|
||||
registryDependencies: ['filter-bar'],
|
||||
files: ['example/filter-bar-segmented-demo.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'hover-card-demo',
|
||||
type: 'components:example',
|
||||
@@ -1021,6 +1039,12 @@ export const examples: Registry = [
|
||||
registryDependencies: ['textarea', 'form'],
|
||||
files: ['example/textarea-form.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'textarea-with-addon',
|
||||
type: 'components:example',
|
||||
registryDependencies: ['textarea', 'input-group'],
|
||||
files: ['example/textarea-with-addon.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'textarea-with-button',
|
||||
type: 'components:example',
|
||||
@@ -1063,6 +1087,18 @@ export const examples: Registry = [
|
||||
registryDependencies: ['toggle-group'],
|
||||
files: ['example/toggle-group-outline.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'toggle-group-segmented',
|
||||
type: 'components:example',
|
||||
registryDependencies: ['toggle-group'],
|
||||
files: ['example/toggle-group-segmented.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'toggle-group-segmented-filter',
|
||||
type: 'components:example',
|
||||
registryDependencies: ['toggle-group'],
|
||||
files: ['example/toggle-group-segmented-filter.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'toggle-group-sm',
|
||||
type: 'components:example',
|
||||
|
||||
+14
-2
@@ -2,9 +2,21 @@
|
||||
|
||||
Next.js app router + MDX. Dev server: `pnpm dev:docs` → http://localhost:3001/docs (the bare `/` 404s).
|
||||
|
||||
## Skills — load before working
|
||||
## Style guide
|
||||
|
||||
`pm-the-docs`, `write-the-docs`, `edit-the-docs`, `ask-the-docs`, and `review-the-docs` back the docs authoring process — see `CONTRIBUTING.md` for which stage each covers. For architecture questions (MDX pipeline, GraphQL endpoint, search embeddings, federated docs, build pipeline), `ask-the-docs` has the reference notes.
|
||||
`style-guide/` holds the docs style guide as plain markdown.
|
||||
|
||||
- `style-guide/README.md` — what the guide covers, how the files are ordered, and the external references it defers to
|
||||
- `style-guide/WORD_LIST.md` — terminology; check it before drafting and again before opening a PR
|
||||
- `style-guide/01-voice-and-tone.md` — person, tense, sentence length, brevity
|
||||
- `style-guide/02-elements.md` — admonitions, code blocks, procedures, tabs, images
|
||||
- `style-guide/03-page-structure.md` — document type, section grouping, chunking
|
||||
|
||||
Load the file you need rather than the whole directory. No tool enforces the guide, so applying it is the author's job, or the skill's.
|
||||
|
||||
## Skills
|
||||
|
||||
Load these before working. `pm-the-docs`, `write-the-docs`, `edit-the-docs`, `ask-the-docs`, and `review-the-docs` back the docs authoring process. See `CONTRIBUTING.md` for which stage each covers. They apply the style guide above. For architecture questions (MDX pipeline, GraphQL endpoint, search embeddings, federated docs, build pipeline), `ask-the-docs` has the reference notes.
|
||||
|
||||
## Test requirements
|
||||
|
||||
|
||||
+23
-428
@@ -4,7 +4,7 @@ Our docs help developers to get started and keep succeeding with Supabase. We we
|
||||
|
||||
If you'd like to contribute, see our list of [recommended issues](https://github.com/supabase/supabase/issues?q=is%3Aopen+is%3Aissue+label%3Adocumentation+label%3A%22help+wanted%22). We also welcome you to open a PR or a new issue with your question.
|
||||
|
||||
Here are some general guidelines on writing docs for Supabase. If you write with an AI coding agent, these skills apply the guidelines for you:
|
||||
How to write a docs page is covered by the [style guide](./style-guide/README.md). This file covers repo mechanics. If you write with an AI coding agent, these skills apply the style guide for you:
|
||||
|
||||
- `/write-the-docs` to draft a new page.
|
||||
- `/edit-the-docs` to revise an existing page.
|
||||
@@ -13,84 +13,25 @@ Here are some general guidelines on writing docs for Supabase. If you write with
|
||||
|
||||
See [AI agent skills for docs authoring](#ai-agent-skills-for-docs-authoring) for the full set, including the skills that help you frame a page and place it in the information architecture.
|
||||
|
||||
## General principles
|
||||
## Style guide
|
||||
|
||||
Write helpful, concise, and understandable documentation. We have a global audience whose members speak different native languages.
|
||||
The [style guide](./style-guide/README.md) covers how to write a docs page: voice,
|
||||
page structure, which components to use, and terminology. Start at its
|
||||
[README](./style-guide/README.md) for what the guide covers and how the files are ordered.
|
||||
|
||||
To make docs as clear as possible:
|
||||
| File | Covers |
|
||||
| ------------------------------------------------------------------------ | -------------------------------------------------- |
|
||||
| [`style-guide/WORD_LIST.md`](./style-guide/WORD_LIST.md) | Terminology, spelling, capitalization |
|
||||
| [`style-guide/01-voice-and-tone.md`](./style-guide/01-voice-and-tone.md) | Person, tense, sentence length, brevity |
|
||||
| [`style-guide/02-elements.md`](./style-guide/02-elements.md) | Admonitions, code blocks, procedures, tabs, images |
|
||||
| [`style-guide/03-page-structure.md`](./style-guide/03-page-structure.md) | Document type, section grouping, chunking |
|
||||
|
||||
- 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. 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, or when you move between [information types](#information-types). 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.
|
||||
|
||||
## Information types
|
||||
|
||||
Separating kinds of information helps a reader reach what they came for and retain it afterward. Someone scanning for a command shouldn't have to read past a definition to find it, and someone reading to understand shouldn't have to step around instructions. Blended prose slows down both, along with an AI agent trying to answer a question from the page, and little of it sticks.
|
||||
|
||||
The [Information Mapping](https://support.informationmapping.com/hc/en-us/articles/213446789-Present-your-information-in-a-clear-and-consistent-way) method names six kinds, each answering a different reader question:
|
||||
|
||||
| Type | Answers | Present with |
|
||||
| --- | --- | --- |
|
||||
| Procedure | How do I do it? | Numbered steps, or an if/then table |
|
||||
| Process | What is happening? How does it work? | A stage-by-stage description, or a when/then table |
|
||||
| Structure | What are its parts? | A part and description table, or a labeled diagram |
|
||||
| Principle | What should I do or not do? | Text, a list, or an admonition |
|
||||
| Concept | What is it? | Text, a list, or a diagram |
|
||||
| Fact | What are the facts? | Text, a list, or a table |
|
||||
|
||||
### Recommendations
|
||||
|
||||
- **Separate a procedure, a process, a structure, or a concept**: Each usually reads better in its own section. Procedure and process get blended most often, because both answer a question about how, and a reader following steps can't act on the process sentences.
|
||||
- **Keep context out of the action path**: A concept or a process tends to work better before the procedure or after it than threaded through the steps.
|
||||
- **Let a principle or a fact ride along**: Either is often a single sentence, so it can sit in the section it qualifies rather than getting one of its own. A fact about timing fits in the step it describes, and a principle can close the concept paragraph that motivates it.
|
||||
- **Look again at a long paragraph**: Past three or four sentences, it has often picked up a second kind of information. Label each sentence and see where the labels change.
|
||||
- **Leave connective prose alone**: An introduction, a transition, an outcome, and a navigation outline describe the page rather than the product, so none of this applies to them.
|
||||
|
||||
### Examples
|
||||
|
||||
Not recommended, because one paragraph blends a concept, a procedure, and a structure:
|
||||
|
||||
```md
|
||||
Row Level Security is a Postgres feature that restricts which rows a user can read
|
||||
or write, and it's the main way to secure a table that several users share. Enable
|
||||
it by running `alter table profiles enable row level security`, which takes effect
|
||||
immediately. Be careful, because a table with Row Level Security enabled and no
|
||||
policy returns no rows to every client, so write a policy before you deploy. The
|
||||
`using` clause of a policy accepts any expression that returns a boolean.
|
||||
```
|
||||
|
||||
Recommended, with each type in the presentation that suits it:
|
||||
|
||||
```md
|
||||
## Row Level Security
|
||||
|
||||
Row Level Security restricts which rows a user can read or write. It's the main way
|
||||
to secure a table that several users share.
|
||||
|
||||
### Enable Row Level Security
|
||||
|
||||
1. Run `alter table profiles enable row level security`. The change takes effect
|
||||
immediately.
|
||||
2. Write a policy that grants the access your app needs.
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
A table with Row Level Security enabled and no policy returns no rows to every
|
||||
client. Write a policy before you deploy.
|
||||
|
||||
</Admonition>
|
||||
|
||||
### Policy reference
|
||||
|
||||
The `using` clause accepts any expression that returns a boolean.
|
||||
```
|
||||
The rest of this file covers repo mechanics: where content lives, how to add a page,
|
||||
and how the reference docs are generated.
|
||||
|
||||
## AI agent skills for docs authoring
|
||||
|
||||
Use these skills for every docs change you make with an AI coding agent: `/write-the-docs` to draft, and `/edit-the-docs` to revise an existing page. They apply this guide and the [word list](./WORD_LIST.md), so you don't have to hold either one in your head.
|
||||
Use these skills for every docs change you make with an AI coding agent: `/write-the-docs` to draft, and `/edit-the-docs` to revise an existing page. They apply the [style guide](./style-guide/README.md), so you don't have to hold it in your head.
|
||||
|
||||
Skills work in any agent that reads `.agents/skills/`, such as Claude Code, Cursor, or Codex. Invoke a skill with `/name`, for example `/write-the-docs`. The canonical files live in `.agents/skills/` (`.claude/skills` is a symlink).
|
||||
|
||||
@@ -98,118 +39,18 @@ Skills work in any agent that reads `.agents/skills/`, such as Claude Code, Curs
|
||||
|
||||
Use the [Write the docs](../../.agents/skills/pm-the-docs/reference/write-the-docs-checklist.md) checklist when product intent and code drive the change: net-new pages, or revising/restructuring existing ones.
|
||||
|
||||
| Skill | Checklist stage | Use for |
|
||||
| --- | --- | --- |
|
||||
| [`pm-the-docs`](../../.agents/skills/pm-the-docs/SKILL.md) | Frame / shape | Audience, stage, why, content type, cross-repo scope (universe when you have Supabase org access, else OSS path) |
|
||||
| [`ask-the-docs`](../../.agents/skills/ask-the-docs/SKILL.md) | Frame / shape | Docs-app architecture, IA placement, where content lives |
|
||||
| [`write-the-docs`](../../.agents/skills/write-the-docs/SKILL.md) | Draft | Draft or revise content grounded in intent and code |
|
||||
| [`test-the-docs`](../../.agents/skills/test-the-docs/SKILL.md) | Draft / self-review | Run snippets in a Docker-isolated stack; verification report |
|
||||
| [`review-the-docs`](../../.agents/skills/review-the-docs/SKILL.md) | Self-review / PR review | Checking a draft; verify a PR |
|
||||
| Skill | Checklist stage | Use for |
|
||||
| ------------------------------------------------------------------ | ----------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| [`pm-the-docs`](../../.agents/skills/pm-the-docs/SKILL.md) | Frame / shape | Audience, stage, why, content type, cross-repo scope (universe when you have Supabase org access, else OSS path) |
|
||||
| [`ask-the-docs`](../../.agents/skills/ask-the-docs/SKILL.md) | Frame / shape | Docs-app architecture, IA placement, where content lives |
|
||||
| [`write-the-docs`](../../.agents/skills/write-the-docs/SKILL.md) | Draft | Draft or revise content grounded in intent and code |
|
||||
| [`test-the-docs`](../../.agents/skills/test-the-docs/SKILL.md) | Draft / self-review | Run snippets in a Docker-isolated stack; verification report |
|
||||
| [`review-the-docs`](../../.agents/skills/review-the-docs/SKILL.md) | Self-review / PR review | Checking a draft; verify a PR |
|
||||
|
||||
### Edit existing pages
|
||||
|
||||
Use [`edit-the-docs`](../../.agents/skills/edit-the-docs/SKILL.md) for style, structure, or brevity on an existing page when you are not changing the product story.
|
||||
|
||||
## Document types
|
||||
|
||||
Supabase docs contain four types of documents. Before you start writing, think about what type of doc you need.
|
||||
|
||||
### Explainers
|
||||
|
||||
Explainers help the reader to learn a topic. They are conceptual and mostly prose-based. They can include:
|
||||
|
||||
- A description of _what_ a feature is
|
||||
- Some reasons _why_ it is useful
|
||||
- Some examples of _when_ to use it
|
||||
- A high-level explanation of _how_ it works
|
||||
|
||||
Explainers don't include:
|
||||
|
||||
- Instructions on how to use it
|
||||
|
||||
### Tutorials
|
||||
|
||||
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. 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).
|
||||
|
||||
### 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: concise steps that readers can follow in sequence.
|
||||
|
||||
A value statement makes a good opener: name what the reader can do, and why it matters to them. That's what tells a reader or an agent whether the page matches their goal.
|
||||
|
||||
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**: `Restrict access to a shared table with Row Level Security. To learn how a policy is evaluated, see [Row Level Security](...).`
|
||||
- **Not recommended**: Begin with several paragraphs about how Row Level Security works before stating what the reader can do.
|
||||
|
||||
**Mixed information types:** [Information types](#information-types) apply at the page level too. Group sections of related types together, and try to keep the procedure group unbroken so context doesn't interrupt the action path. A section serving two types can be split, with a cross-reference between the halves.
|
||||
|
||||
Classify a section by what the reader is doing in it, not by what it's about. On a page about tables every section is about tables, so subject matter tells you nothing. A reader opens a section on schemas to understand something, so it's context.
|
||||
|
||||
One order that works: a short concept opener, then procedures, then concept and process, then structure and fact.
|
||||
|
||||
```text
|
||||
## What is a table? <- concept opener
|
||||
## Creating and managing tables <- procedures
|
||||
### Creating tables
|
||||
### Securing your tables
|
||||
### Loading data
|
||||
## How tables are organized <- concept and process
|
||||
### Primary keys
|
||||
### Relationships between tables
|
||||
### Schemas
|
||||
## Reference <- structure and fact
|
||||
### Data types
|
||||
```
|
||||
|
||||
**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.
|
||||
|
||||
For example, an introduction to a long guide that mixes information types:
|
||||
|
||||
```md
|
||||
Connect your app to Postgres through a connection pooler, a direct connection, or a
|
||||
Supabase client library.
|
||||
|
||||
- [Choose a connection method](#choose-a-connection-method) compares the options and
|
||||
their trade-offs. Start here if you aren't sure which one fits your app.
|
||||
- [Connect your app](#connect-your-app) has the steps for each method.
|
||||
- [Connection parameters](#connection-parameters) lists every parameter and its
|
||||
default.
|
||||
```
|
||||
|
||||
Each link says what the reader gets from that group, so someone who already knows which method they want goes straight to the procedures.
|
||||
|
||||
**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.
|
||||
|
||||
- Group introduction: `The following sections cover each connection method in turn. Every method needs your project reference, which you find on the project settings page.`
|
||||
- Transition where the type changes: `Those are the mechanics of opening a connection. To understand why a pooled connection behaves differently under load, see [Connection pooling](...).`
|
||||
- Outcome after a procedure: `Your app now connects through the pooler. Queries that used to fail at the connection limit queue instead.`
|
||||
|
||||
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.
|
||||
|
||||
References include:
|
||||
|
||||
- Function parameters
|
||||
- Return types
|
||||
- Code samples
|
||||
- Warnings about critical errors, such as missteps that can cause data loss
|
||||
|
||||
References don't include:
|
||||
|
||||
- Explanations of the context for a feature
|
||||
- Examples of use cases
|
||||
- Multi-step instructions
|
||||
|
||||
## Repo organization
|
||||
|
||||
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.
|
||||
@@ -293,97 +134,9 @@ If you copy the same content multiple times across different files, create a **p
|
||||
|
||||
To use a partial, import it into your MDX file. You can also set up a partial to automatically import by including it in the `components` within [`apps/docs/features/docs/MdxBase.shared.tsx`](https://github.com/supabase/supabase/blob/master/apps/docs/features/docs/MdxBase.shared.tsx).
|
||||
|
||||
## Components and elements
|
||||
## Content listings
|
||||
|
||||
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 draw reader attention to an important point or an aside. They highlight important information, but get less effective if they're overused.
|
||||
|
||||
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`: 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`.
|
||||
- `note`: Highlight an important prerequisite, constraint, clarification, or optional shortcut that doesn't represent a risk. If the information is essential to completing a step, include it in the procedure instead.
|
||||
|
||||
Structure an admonition with these props and content:
|
||||
|
||||
- `title` (optional): Add a short callout title. Don't put Markdown or HTML headings inside an admonition. If the content needs a heading to structure the page, move the heading and its section outside the admonition.
|
||||
- `children`: Add rich body content such as paragraphs, lists, links, and code.
|
||||
- `actions` (optional): Add standalone calls to action so they remain separate from the body content. Keep contextual links and interactive examples in the body when they are part of the explanation.
|
||||
|
||||
```mdx
|
||||
<Admonition
|
||||
type="note"
|
||||
title="Optional title"
|
||||
actions={<Button>Continue</Button>}
|
||||
>
|
||||
|
||||
Your content here
|
||||
|
||||
</Admonition>
|
||||
```
|
||||
|
||||
### Blockquotes
|
||||
|
||||
Don't use blockquotes.
|
||||
|
||||
### Code blocks
|
||||
|
||||
Keep code lines short to avoid scrolling. For example, you can split long shell commands with `\`.
|
||||
|
||||
- **JavaScript/TypeScript**
|
||||
|
||||
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 code block by including it after the opening backticks and language specifier:
|
||||
|
||||
````md
|
||||
```ts environment.ts
|
||||
|
||||
```
|
||||
````
|
||||
|
||||
Optionally highlight lines by using `mark=${lineNumber}`.
|
||||
|
||||
````md
|
||||
```js mark=12:13
|
||||
|
||||
```
|
||||
````
|
||||
|
||||
### 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.` Bold is also the convention for an inline label that opens a paragraph or a list item, such as `**Recommended**:` or `**Navigation:**`.
|
||||
- _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.
|
||||
Overview and index pages use a single `<ContentListings id="..." />` component for curated link sections. For when to use one, see [the style guide](./style-guide/02-elements.md#content-listings). Refer to [`storage.data.ts`](data/content-listings/storage.data.ts) and [`storage.mdx`](content/guides/storage.mdx) for a full example.
|
||||
|
||||
**Prompt to add content listings:**
|
||||
|
||||
@@ -403,164 +156,6 @@ 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). Use `cl-data` for a data export with a namespaced ID. Use `cl-inline` for an MDX component.
|
||||
|
||||
|
||||
### Footnotes
|
||||
|
||||
Don't use footnotes.
|
||||
|
||||
### Graphs
|
||||
|
||||
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).
|
||||
|
||||
Sequence diagram:
|
||||
|
||||
````mdx
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant Browser
|
||||
participant Supabase
|
||||
|
||||
User->>Browser: Clicks "Sign in"
|
||||
Browser->>Supabase: Request authorization
|
||||
Supabase->>Browser: Return token
|
||||
```
|
||||
````
|
||||
|
||||
The `flowchart` keyword accepts a direction such as `LR` or `TD`:
|
||||
|
||||
````mdx
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["content/**/*.md"] -->|Contentlayer| B[MDX]
|
||||
B --> C[Rehype]
|
||||
C -->|Our Plugin| D[SVG]
|
||||
D -->|Base64| E[Embedded Images]
|
||||
```
|
||||
````
|
||||
|
||||
A few tips:
|
||||
|
||||
- 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 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.
|
||||
|
||||
### Images
|
||||
|
||||
Images are uploaded in the `apps/docs/public/img` folder.
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
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)`.
|
||||
|
||||
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
|
||||
|
||||
Use ordered lists for steps that must be taken one after the other. Use unordered lists when order doesn't matter.
|
||||
|
||||
Use Arabic numerals (`1`, `2`, `3`) for ordered lists and dashes (`-`) for unordered lists.
|
||||
|
||||
Don't nest lists more than two deep.
|
||||
|
||||
```md
|
||||
1. List item
|
||||
2. List item
|
||||
1. List item
|
||||
2. List item
|
||||
3. List item
|
||||
- List item
|
||||
- List item
|
||||
<!-- DON'T ADD ANOTHER LEVEL OF NESTING -->
|
||||
- Overly nested list item
|
||||
```
|
||||
|
||||
### Tabs
|
||||
|
||||
Use tabs to provide alternative instructions for different platforms or languages.
|
||||
|
||||
The optional `queryGroup` prop lets you link directly to a tab. For this example, use `/docs/my-page?packagemanager=npm`.
|
||||
|
||||
```
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="npm"
|
||||
queryGroup="packagemanager"
|
||||
>
|
||||
<TabPanel id="npm" label="npm">
|
||||
|
||||
// ...
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="yarn" label="Yarn">
|
||||
|
||||
// ...
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
### Videos
|
||||
|
||||
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'
|
||||
---
|
||||
```
|
||||
|
||||
## Styling, formatting, and grammar
|
||||
|
||||
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. 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/).
|
||||
|
||||
Follow the [Supabase documentation word list](./WORD_LIST.md) for preferred spelling, capitalization, and usage. No tool checks terminology, so check your own prose against the list, or let `/edit-the-docs` do it.
|
||||
|
||||
## Search
|
||||
|
||||
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.
|
||||
|
||||
@@ -57,4 +57,4 @@ for coverage and skipped rules.
|
||||
|
||||
## Contributing
|
||||
|
||||
For repo organization and style guide, see the [contributing guide](https://github.com/supabase/supabase/blob/master/apps/docs/CONTRIBUTING.md). If you write with an AI coding agent, use the `/write-the-docs` skill to draft, `/edit-the-docs` to revise an existing page, and `/review-the-docs` to self-review before you open a PR.
|
||||
For how to write a page, see the [style guide](https://github.com/supabase/supabase/tree/master/apps/docs/style-guide). For repo organization, see the [contributing guide](https://github.com/supabase/supabase/blob/master/apps/docs/CONTRIBUTING.md). If you write with an AI coding agent, use the `/write-the-docs` skill to draft, `/edit-the-docs` to revise an existing page, and `/review-the-docs` to self-review before you open a PR.
|
||||
+1
-1
@@ -20,4 +20,4 @@ It also means that we can switch to any documentation system we want. On this si
|
||||
|
||||
## Contributing
|
||||
|
||||
To contribute to docs, see the [developers' guide](https://github.com/supabase/supabase/blob/master/apps/docs/DEVELOPERS.md) and [contributing guide](https://github.com/supabase/supabase/blob/master/apps/docs/CONTRIBUTING.md). If you write with an AI coding agent, use the `/write-the-docs` skill to draft and `/edit-the-docs` to revise an existing page.
|
||||
To contribute to docs, see the [style guide](https://github.com/supabase/supabase/tree/master/apps/docs/style-guide) for how to write a page, and the [developers' guide](https://github.com/supabase/supabase/blob/master/apps/docs/DEVELOPERS.md) and [contributing guide](https://github.com/supabase/supabase/blob/master/apps/docs/CONTRIBUTING.md) for repo mechanics. If you write with an AI coding agent, use the `/write-the-docs` skill to draft and `/edit-the-docs` to revise an existing page.
|
||||
+1
-953
@@ -1,955 +1,3 @@
|
||||
# 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.
|
||||
|
||||
The `/write-the-docs` and `/edit-the-docs` agent skills apply this list as you draft.
|
||||
|
||||
Every rule here 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.`.
|
||||
|
||||
### 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_.
|
||||
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.
|
||||
|
||||
### 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. Don't use _easy_, _easily_, _quickly_,
|
||||
_simple_, or _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_).
|
||||
|
||||
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.
|
||||
|
||||
### 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.
|
||||
|
||||
## 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 instead of _leverage_.
|
||||
|
||||
### lifecycle
|
||||
|
||||
Write _lifecycle_, not _life cycle_ or _life-cycle_.
|
||||
|
||||
### login and log in
|
||||
|
||||
Don't use _login_ or _log in_ in prose. Use _sign in_. See
|
||||
[sign in and sign-in](#sign-in-and-sign-in).
|
||||
|
||||
## M
|
||||
|
||||
### marketing language
|
||||
|
||||
Describe measurable behavior instead of making promotional claims. Don't use:
|
||||
|
||||
- _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
|
||||
|
||||
Spell out zero through nine. Use numerals for 10 and greater. Use numerals
|
||||
regardless for versions, technical quantities, step and page numbers, prices, and
|
||||
percentages, and throughout a sentence that mixes a number under 10 with a larger
|
||||
one.
|
||||
|
||||
- **Recommended**: four options, 24 hours, version 3, 128 bits, step 2, 40%
|
||||
- **Not recommended**: 4 options, twenty-four hours
|
||||
|
||||
Spell out ordinals. Group digits in large numbers with commas, counting left from
|
||||
the decimal point. Write fractions as decimals where practical. Use a hyphen with
|
||||
no spaces for a range.
|
||||
|
||||
- **Recommended**: first, forty-third, 1,532,784 bytes, 0.75, 2012-2016
|
||||
- **Not recommended**: 1st, 1532784 bytes, three-quarters, 2012 - 2016
|
||||
|
||||
Omit a count of steps or items unless the count helps the reader plan. Name the
|
||||
action or link the heading rather than citing a step or section number.
|
||||
|
||||
- **Recommended**: To connect to your database:
|
||||
- **Recommended**: After you create the project, copy the project URL.
|
||||
|
||||
### 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.
|
||||
|
||||
### 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.
|
||||
|
||||
### 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.
|
||||
|
||||
### powered by
|
||||
|
||||
Don't use _powered by_. Prefer _with_, _by_, or _through_, depending on the
|
||||
relationship.
|
||||
|
||||
### prior to and subsequent to
|
||||
|
||||
Use _before_ and _after_.
|
||||
|
||||
## 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.
|
||||
|
||||
### sign in and sign-in
|
||||
|
||||
Use _sign in_, _sign out_, and _sign up_ as verbs. Use the hyphenated forms
|
||||
_sign-in_, _sign-out_, and _sign-up_ as nouns or adjectives. Match the product UI
|
||||
labels **Sign in**, **Sign out**, and **Sign up**.
|
||||
|
||||
Keep _login_, _log in_, _logout_, _log out_, and `logOut` when quoting
|
||||
third-party UI or when they are part of code, routes, URL slugs, CLI commands, or
|
||||
established feature names such as _social login_.
|
||||
|
||||
### singular they
|
||||
|
||||
Use _they_, _them_, and _their_ as gender-neutral singular pronouns. Don't use
|
||||
_s/he_, _he/she_, _(s)he_, or _him/her_.
|
||||
|
||||
### slang abbreviations
|
||||
|
||||
Don't use internet slang in documentation, such as _tl;dr_, _ymmv_, _rtfm_, _imo_,
|
||||
or _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_, not _utilize_ or _utilise_. Use _utilization_ only when referring to the
|
||||
measured proportion of a resource in use.
|
||||
|
||||
## V
|
||||
|
||||
### vague verbs
|
||||
|
||||
Describe the concrete action:
|
||||
|
||||
- _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.
|
||||
|
||||
### vCPU
|
||||
|
||||
Use _vCPU_ (plural _vCPUs_) for the CPU resources of Supabase compute sizes.
|
||||
Don't describe Supabase compute in _cores_.
|
||||
|
||||
_Core_ remains correct for hardware the reader owns or manages, such as
|
||||
self-hosting requirements or a migration VM, and in general CPU discussion.
|
||||
|
||||
- Recommended: The 16XL compute size has 64 vCPUs.
|
||||
- Recommended: Run the migration from a VM with 8 CPU cores.
|
||||
- Not recommended: The 16XL compute size has 64 cores.
|
||||
|
||||
### 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_.
|
||||
|
||||
### 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.
|
||||
|
||||
## Phrase groups
|
||||
|
||||
The alphabetical entries explain the intent behind each rule. This section collects
|
||||
the full term lists in one place, grouped by the problem they cause.
|
||||
|
||||
### Filler
|
||||
|
||||
Don't use _actually_, _easily_, _easy_, _just_, _let's_, _obviously_,
|
||||
_of course_, _please_, _quickly_, _simple_, _simply_, or _that's it_. Remove the term or state the intended meaning directly.
|
||||
_please_ is the exception: keep it when asking permission, apologizing for an
|
||||
inconvenience, or requesting an action that primarily benefits Supabase.
|
||||
|
||||
### Marketing language
|
||||
|
||||
Don't use _best in class_, _best-in-class_, _cutting edge_,
|
||||
_cutting-edge_, _effortlessly_, _game changer_, _game-changer_, _hassle free_,
|
||||
_hassle-free_, _powerful_, or _seamlessly_. Describe specific behavior or
|
||||
measurable results instead.
|
||||
|
||||
### Vague verbs
|
||||
|
||||
Use _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 none of these match the operation.
|
||||
|
||||
### Apologies
|
||||
|
||||
Don't use _oops_ or _sorry_. State what happened directly. Apologize
|
||||
only when an apology is genuinely useful to the reader.
|
||||
|
||||
### First person
|
||||
|
||||
Don't use _I_, _I'm_, _me_, _my_, or _mine_. Address the
|
||||
reader as _you_ and use an explicit noun for other actors.
|
||||
|
||||
### Gender-neutral pronouns
|
||||
|
||||
Don't use _s/he_, _he/she_, _(s)he_, or _him/her_. Use the
|
||||
singular _they_ or rewrite the sentence.
|
||||
|
||||
### Inclusive language
|
||||
|
||||
Don't use these terms:
|
||||
|
||||
- _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
|
||||
|
||||
Write _e.g._ with both periods, not _eg._ or _eg_. Replace _i.e._, _ie._, and
|
||||
_ie_ with _that is_. Prefer _for example_ and _that is_ in prose when space
|
||||
allows.
|
||||
|
||||
### Powered by
|
||||
|
||||
Don't use _powered by_. Use _with_, _by_, or _through_, depending on the
|
||||
relationship.
|
||||
|
||||
### Preferred usage
|
||||
|
||||
Use:
|
||||
|
||||
- _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
|
||||
|
||||
Don't use 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
|
||||
|
||||
Don't use _tl;dr_, _ymmv_, _rtfm_, _imo_, or _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.
|
||||
This file moved to [`style-guide/WORD_LIST.md`](./style-guide/WORD_LIST.md).
|
||||
@@ -0,0 +1,125 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const reference = vi.hoisted(() => ({
|
||||
sections: [] as Array<{ id: string; slug: string; title: string; type: string }>,
|
||||
}))
|
||||
|
||||
vi.mock('~/features/docs/Reference.generated.singleton', () => ({
|
||||
getFlattenedSections: async () => reference.sections,
|
||||
getFunctionsList: async () => [],
|
||||
getTypeSpec: async () => undefined,
|
||||
}))
|
||||
|
||||
vi.mock('~/features/docs/Reference.mdx', () => ({
|
||||
getRefMarkdown: async () => 'Reference content',
|
||||
}))
|
||||
|
||||
vi.mock('next/navigation', () => ({
|
||||
notFound: () => {
|
||||
throw new Error('notFound')
|
||||
},
|
||||
}))
|
||||
|
||||
import { GET } from './route'
|
||||
|
||||
function section(slug: string, title: string, type = 'markdown') {
|
||||
return { id: slug, slug, title, type }
|
||||
}
|
||||
|
||||
async function html(path: string) {
|
||||
const response = await GET(new Request(`https://supabase.com${path}`))
|
||||
expect(response.status).toBe(200)
|
||||
return response.text()
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
reference.sections = []
|
||||
})
|
||||
|
||||
describe('crawler reference aliases', () => {
|
||||
it('resolves unique bare JavaScript modifier and filter slugs', async () => {
|
||||
reference.sections = [
|
||||
section('using-modifiers-order', 'Order'),
|
||||
section('using-filters-or', 'Or'),
|
||||
section('using-modifiers-maybesingle', 'Maybe single'),
|
||||
]
|
||||
|
||||
for (const [requested, canonical] of [
|
||||
['order', 'using-modifiers-order'],
|
||||
['or', 'using-filters-or'],
|
||||
['maybeSingle', 'using-modifiers-maybesingle'],
|
||||
]) {
|
||||
expect(await html(`/reference/javascript/${requested}`)).toContain(
|
||||
`href="https://supabase.com/docs/reference/javascript/${canonical}"`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('resolves storage slugs and keeps an exact versioned slug', async () => {
|
||||
reference.sections = [
|
||||
section('file-buckets-createbucket', 'Create bucket'),
|
||||
section('file-buckets-upload', 'Upload'),
|
||||
section('file-buckets-createsignedurl', 'Create signed URL'),
|
||||
section('file-buckets-updatebucket', 'Update bucket'),
|
||||
section('file-buckets-listv2', 'List files v2'),
|
||||
]
|
||||
|
||||
for (const [requested, canonical] of [
|
||||
['storage-createbucket', 'file-buckets-createbucket'],
|
||||
['storage-from-upload', 'file-buckets-upload'],
|
||||
['storage-from-createsignedurl', 'file-buckets-createsignedurl'],
|
||||
['storage-updatebucket', 'file-buckets-updatebucket'],
|
||||
['file-buckets-listv2', 'file-buckets-listv2'],
|
||||
]) {
|
||||
expect(await html(`/reference/javascript/${requested}`)).toContain(
|
||||
`href="https://supabase.com/docs/reference/javascript/${canonical}"`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('resolves SDK-specific user and root aliases', async () => {
|
||||
reference.sections = [
|
||||
section('introduction', 'Introduction'),
|
||||
section('auth-admin', 'Admin'),
|
||||
section('auth-getuser', 'Get user'),
|
||||
section('auth-currentuser', 'Current user'),
|
||||
]
|
||||
|
||||
expect(await html('/reference/javascript')).toContain(
|
||||
'href="https://supabase.com/docs/reference/javascript/introduction"'
|
||||
)
|
||||
expect(await html('/reference/javascript/start')).toContain('JavaScript: Introduction')
|
||||
expect(await html('/reference/javascript/admin-api')).toContain('JavaScript: Admin')
|
||||
expect(await html('/reference/swift/get-user')).toContain(
|
||||
'href="https://supabase.com/docs/reference/swift/auth-getuser"'
|
||||
)
|
||||
expect(await html('/reference/dart/get-user')).toContain(
|
||||
'href="https://supabase.com/docs/reference/dart/auth-currentuser"'
|
||||
)
|
||||
expect(await html('/reference/kotlin')).toContain('Kotlin: Introduction')
|
||||
})
|
||||
|
||||
it('keeps exact matches ahead of aliases and rejects ambiguous bare slugs', async () => {
|
||||
reference.sections = [
|
||||
section('delete', 'Delete'),
|
||||
section('auth-passkey-list', 'Passkeys'),
|
||||
section('file-buckets-list', 'Files'),
|
||||
]
|
||||
|
||||
expect(await html('/reference/javascript/delete')).toContain('JavaScript: Delete')
|
||||
expect(await html('/reference/javascript/storage-list')).toContain('JavaScript: Files')
|
||||
await expect(html('/reference/javascript/list')).rejects.toThrow('notFound')
|
||||
})
|
||||
|
||||
it('uses canonical paths for explicit versions', async () => {
|
||||
reference.sections = [section('auth-update', 'Update')]
|
||||
|
||||
expect(await html('/reference/javascript/v1/auth-update')).toContain(
|
||||
'href="https://supabase.com/docs/reference/javascript/v1/auth-update"'
|
||||
)
|
||||
reference.sections = [section('auth-updateuser', 'Update')]
|
||||
expect(await html('/reference/javascript/v2/auth-updateuser')).toContain(
|
||||
'href="https://supabase.com/docs/reference/javascript/auth-updateuser"'
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -43,10 +43,23 @@ export async function GET(request: Request) {
|
||||
url,
|
||||
}
|
||||
})
|
||||
section = flattenedSections.find(
|
||||
(section) =>
|
||||
(section.type === 'markdown' || section.type === 'function') && section.slug === slug
|
||||
const contentSections = flattenedSections.filter(
|
||||
(section) => section.type === 'markdown' || section.type === 'function'
|
||||
)
|
||||
section = contentSections.find((section) => section.slug === slug)
|
||||
if (!section) {
|
||||
const legacySlug = slug?.toLowerCase()
|
||||
const replacement = legacyReferenceSlug(lib, version, legacySlug)
|
||||
|
||||
if (replacement) {
|
||||
section = contentSections.find((section) => section.slug?.toLowerCase() === replacement)
|
||||
} else if (legacySlug && version === 'v2' && (lib === 'javascript' || lib === 'dart')) {
|
||||
const matches = contentSections.filter((section) =>
|
||||
section.slug?.toLowerCase().endsWith(`-${legacySlug}`)
|
||||
)
|
||||
if (matches.length === 1) section = matches[0]
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (!section) {
|
||||
@@ -56,7 +69,7 @@ export async function GET(request: Request) {
|
||||
const html = htmlShell(
|
||||
lib,
|
||||
isVersion ? version : null,
|
||||
slug,
|
||||
section.slug ?? slug,
|
||||
section,
|
||||
libraryNav(sectionsWithUrl) + (await sectionDetails(lib, isVersion ? version : null, section))
|
||||
)
|
||||
@@ -66,6 +79,17 @@ export async function GET(request: Request) {
|
||||
return response
|
||||
}
|
||||
|
||||
function legacyReferenceSlug(lib: string, version: string, slug?: string) {
|
||||
if (!slug || slug === 'start') return 'introduction'
|
||||
if (lib === 'swift' && version === 'v2' && slug === 'get-user') return 'auth-getuser'
|
||||
if (version !== 'v2' || (lib !== 'javascript' && lib !== 'dart')) return undefined
|
||||
if (slug === 'admin-api') return 'auth-admin'
|
||||
if (slug === 'get-user') return lib === 'dart' ? 'auth-currentuser' : 'auth-getuser'
|
||||
if (slug.startsWith('storage-from-')) return `file-buckets-${slug.slice('storage-from-'.length)}`
|
||||
if (slug.startsWith('storage-')) return `file-buckets-${slug.slice('storage-'.length)}`
|
||||
return undefined
|
||||
}
|
||||
|
||||
function htmlShell(
|
||||
lib: string,
|
||||
version: string | null,
|
||||
@@ -74,6 +98,7 @@ function htmlShell(
|
||||
body: string
|
||||
) {
|
||||
const libraryName = REFERENCES[lib].name
|
||||
const versionPath = version && version !== REFERENCES[lib].versions[0] ? '/' + version : ''
|
||||
let title = libraryName + ': ' + (section.title ?? '')
|
||||
|
||||
return (
|
||||
@@ -84,6 +109,7 @@ function htmlShell(
|
||||
`<meta name="og:image" content="https://supabase.com/docs/img/supabase-og-image.png">` +
|
||||
`<meta name="twitter:image" content="https://supabase.com/docs/img/supabase-og-image.png">` +
|
||||
`<link rel="canonical" href="https://supabase.com/docs/reference/${lib}` +
|
||||
versionPath +
|
||||
(slug ? '/' + slug : '') +
|
||||
`">` +
|
||||
'</head>' +
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'content-type',
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { supabaseSearchV2 } from '~/scripts/search_v2/client'
|
||||
import { type NextRequest } from 'next/server'
|
||||
|
||||
import { corsHeaders } from './cors'
|
||||
|
||||
export const runtime = 'edge'
|
||||
|
||||
export async function OPTIONS() {
|
||||
return new Response(null, { headers: corsHeaders })
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const query = searchParams.get('q')?.trim()
|
||||
const limit = Number(searchParams.get('limit')) || 10
|
||||
|
||||
if (!query) {
|
||||
return Response.json({ error: 'Missing q parameter' }, { status: 400, headers: corsHeaders })
|
||||
}
|
||||
|
||||
const { data, error } = await supabaseSearchV2().rpc('search_docs', {
|
||||
query_text: query,
|
||||
match_limit: limit,
|
||||
})
|
||||
|
||||
if (error) {
|
||||
console.error('Error running docs search v2:', error)
|
||||
return Response.json({ error: error.message }, { status: 500, headers: corsHeaders })
|
||||
}
|
||||
|
||||
return Response.json(data, { headers: corsHeaders })
|
||||
}
|
||||
@@ -3096,14 +3096,13 @@ export const telemetry: NavMenuConstant = {
|
||||
name: 'Hire an agent',
|
||||
items: [
|
||||
{ name: 'Set up an agent', url: '/guides/observability/automate-with-agents' },
|
||||
{ name: 'Generalist', url: '/guides/observability/automate-with-agents/all' },
|
||||
{ name: 'Health monitor', url: '/guides/observability/automate-with-agents/health' },
|
||||
{ name: 'Security monitor', url: '/guides/observability/automate-with-agents/security' },
|
||||
{
|
||||
name: 'Performance monitor',
|
||||
url: '/guides/observability/automate-with-agents/performance',
|
||||
},
|
||||
{ name: 'Capacity monitor', url: '/guides/observability/automate-with-agents/usage' },
|
||||
{ name: 'Resource monitor', url: '/guides/observability/automate-with-agents/usage' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -7,16 +7,18 @@ interface IStep {
|
||||
}
|
||||
|
||||
interface IStepHikeCompactSubcomponents {
|
||||
Step: FC<IStep>
|
||||
Details: FC<IDetails>
|
||||
Code: FC<ICode>
|
||||
Step: FC<PropsWithChildren<IStep>>
|
||||
Details: FC<PropsWithChildren<IDetails>>
|
||||
Code: FC<PropsWithChildren<ICode>>
|
||||
}
|
||||
interface IDetails {
|
||||
title?: string
|
||||
fullWidth?: boolean
|
||||
}
|
||||
|
||||
interface ICode {}
|
||||
interface ICode {
|
||||
className?: string
|
||||
}
|
||||
|
||||
interface IStepHikeCompact {
|
||||
title: string
|
||||
@@ -65,7 +67,7 @@ const Step: FC<PropsWithChildren<IStep>> = ({ children, title, step }) => {
|
||||
className="border bg-surface-100
|
||||
border-control flex items-center justify-center rounded-full
|
||||
w-6 h-6 text-xs text-foreground font-normal font-mono
|
||||
dropshadow-sm
|
||||
drop-shadow-sm
|
||||
"
|
||||
>
|
||||
{step}
|
||||
@@ -107,7 +109,7 @@ const Details: FC<PropsWithChildren<IDetails>> = ({ children, title, fullWidth =
|
||||
)
|
||||
}
|
||||
|
||||
const Code: FC<PropsWithChildren<ICode>> = ({ children }) => {
|
||||
const Code: FC<PropsWithChildren<ICode>> = ({ children, className }) => {
|
||||
// Not `not-prose`: steps interleave labels and admonitions with their code samples, and
|
||||
// stripping prose leaves that text unstyled and flush against the samples.
|
||||
return (
|
||||
@@ -118,7 +120,8 @@ const Code: FC<PropsWithChildren<ICode>> = ({ children }) => {
|
||||
// `Step` spaces the block as a whole, so samples don't carry margins of their own...
|
||||
'[&_.shiki]:!my-0 [&_.shiki-wrapper]:!my-0',
|
||||
// ...but back-to-back samples have no prose between them to separate them.
|
||||
'[&_.shiki+.shiki]:!mt-6'
|
||||
'[&_.shiki+.shiki]:!mt-6',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
@@ -129,4 +132,4 @@ const Code: FC<PropsWithChildren<ICode>> = ({ children }) => {
|
||||
StepHikeCompact.Step = Step
|
||||
StepHikeCompact.Details = Details
|
||||
StepHikeCompact.Code = Code
|
||||
export default StepHikeCompact
|
||||
export { StepHikeCompact }
|
||||
@@ -1,37 +1,38 @@
|
||||
{/* Generated by `make -C apps/docs/spec generate.partials.access-control`. Do not hand-edit; see supabase/platform#37175 and apps/docs/spec/Makefile. */}
|
||||
|
||||
| MCP tool | Required permission |
|
||||
| --------------------------- | ----------------------------------------------------------------------------- |
|
||||
| `apply_migration` | **Migrations** (Read-write) |
|
||||
| `confirm_cost` | None (always available) |
|
||||
| `create_branch` | **Development Branches** (Read-write) or **Production Branches** (Read-write) |
|
||||
| `create_project` | **Organization Projects** (Read-write) |
|
||||
| `delete_branch` | **Production Branches** (Read-write) or **Development Branches** (Read-write) |
|
||||
| `deploy_edge_function` | **Edge Functions** (Read-write) |
|
||||
| `execute_sql` | **Database** (Read) |
|
||||
| `generate_typescript_types` | **Database** (Read) |
|
||||
| `get_advisors` | **Advisors** (Read) |
|
||||
| `get_cost` | **Organization Settings** (Read) and **Projects (account-wide)** (Read) |
|
||||
| `get_edge_function` | **Edge Functions** (Read) |
|
||||
| `get_logs` | **Logs** (Read) |
|
||||
| `get_organization` | **Organization Settings** (Read) |
|
||||
| `get_project` | **Project Settings** (Read) |
|
||||
| `get_project_url` | **Project Settings** (Read) |
|
||||
| `get_publishable_keys` | **API Keys** (Read) |
|
||||
| `get_storage_config` | **Storage Config** (Read) |
|
||||
| `list_branches` | **Development Branches** (Read) or **Production Branches** (Read) |
|
||||
| `list_edge_functions` | **Edge Functions** (Read) |
|
||||
| `list_extensions` | **Database** (Read) |
|
||||
| `list_migrations` | **Migrations** (Read) |
|
||||
| `list_organizations` | **Organizations** (Read) |
|
||||
| `list_projects` | **Projects (account-wide)** (Read) |
|
||||
| `list_storage_buckets` | **Storage** (Read) |
|
||||
| `list_tables` | **Database** (Read) |
|
||||
| `merge_branch` | **Production Branches** (Read-write) or **Development Branches** (Read-write) |
|
||||
| `pause_project` | **Project Settings** (Read-write) |
|
||||
| `query_logs` | **Logs** (Read) |
|
||||
| `rebase_branch` | **Production Branches** (Read-write) or **Development Branches** (Read-write) |
|
||||
| `reset_branch` | **Production Branches** (Read-write) or **Development Branches** (Read-write) |
|
||||
| `restore_project` | **Project Settings** (Read-write) |
|
||||
| `search_docs` | None (always available) |
|
||||
| `update_storage_config` | **Storage Config** (Read-write) |
|
||||
| MCP tool | Required permission |
|
||||
| ----------------------------- | ----------------------------------------------------------------------------- |
|
||||
| `apply_migration` | **Migrations** (Read-write) |
|
||||
| `confirm_cost` | None (always available) |
|
||||
| `create_branch` | **Development Branches** (Read-write) or **Production Branches** (Read-write) |
|
||||
| `create_edge_function_secret` | **Edge Function Secrets** (Read-write) and **Edge Function Secrets** (Read) |
|
||||
| `create_project` | **Organization Projects** (Read-write) |
|
||||
| `delete_branch` | **Production Branches** (Read-write) or **Development Branches** (Read-write) |
|
||||
| `deploy_edge_function` | **Edge Functions** (Read-write) |
|
||||
| `execute_sql` | **Database** (Read) |
|
||||
| `generate_typescript_types` | **Database** (Read) |
|
||||
| `get_advisors` | **Advisors** (Read) |
|
||||
| `get_cost` | **Organization Settings** (Read) and **Projects (account-wide)** (Read) |
|
||||
| `get_edge_function` | **Edge Functions** (Read) |
|
||||
| `get_logs` | **Logs** (Read) |
|
||||
| `get_organization` | **Organization Settings** (Read) |
|
||||
| `get_project` | **Project Settings** (Read) |
|
||||
| `get_project_url` | **Project Settings** (Read) |
|
||||
| `get_publishable_keys` | **API Keys** (Read) |
|
||||
| `get_storage_config` | **Storage Config** (Read) |
|
||||
| `list_branches` | **Development Branches** (Read) or **Production Branches** (Read) |
|
||||
| `list_edge_functions` | **Edge Functions** (Read) |
|
||||
| `list_extensions` | **Database** (Read) |
|
||||
| `list_migrations` | **Migrations** (Read) |
|
||||
| `list_organizations` | **Organizations** (Read) |
|
||||
| `list_projects` | **Projects (account-wide)** (Read) |
|
||||
| `list_storage_buckets` | **Storage** (Read) |
|
||||
| `list_tables` | **Database** (Read) |
|
||||
| `merge_branch` | **Production Branches** (Read-write) or **Development Branches** (Read-write) |
|
||||
| `pause_project` | **Project Settings** (Read-write) |
|
||||
| `query_logs` | **Logs** (Read) |
|
||||
| `rebase_branch` | **Production Branches** (Read-write) or **Development Branches** (Read-write) |
|
||||
| `reset_branch` | **Production Branches** (Read-write) or **Development Branches** (Read-write) |
|
||||
| `restore_project` | **Project Settings** (Read-write) |
|
||||
| `search_docs` | None (always available) |
|
||||
| `update_storage_config` | **Storage Config** (Read-write) |
|
||||
@@ -54,6 +54,11 @@
|
||||
| | | [Retry delivery](/docs/reference/api/v2-projects-ref-webhooks-deliveries-id-retry-post) |
|
||||
| | | [Send test event](/docs/reference/api/v2-projects-ref-webhooks-endpoints-id-test-post) |
|
||||
| | | [Update endpoint](/docs/reference/api/v2-projects-ref-webhooks-endpoints-id-patch) |
|
||||
| Notebooks | Read | [Get notebook](/docs/reference/api/v2-get-notebook) |
|
||||
| | | [List notebooks](/docs/reference/api/v2-list-notebooks) |
|
||||
| | Read-write | [Create notebook](/docs/reference/api/v2-create-notebook) |
|
||||
| | | [Delete notebook](/docs/reference/api/v2-delete-notebook) |
|
||||
| | | [Update notebook](/docs/reference/api/v2-update-notebook) |
|
||||
| **Database** | | |
|
||||
| Backups | Read | [Get backup schedule](/docs/reference/api/v1-get-backup-schedule) |
|
||||
| | | [List all backups](/docs/reference/api/v1-list-all-backups) |
|
||||
@@ -156,11 +161,11 @@
|
||||
| Storage Config | Read | [Get project config](/docs/reference/api/v2-get-project-config)[^5] |
|
||||
| | | [Get storage config](/docs/reference/api/v1-get-storage-config) |
|
||||
| | Read-write | [Update storage config](/docs/reference/api/v1-update-storage-config) |
|
||||
| Compute | Read | [Get a worker](/docs/reference/api/v2-get-a-worker) |
|
||||
| | | [List all workers](/docs/reference/api/v2-list-all-workers) |
|
||||
| | Read-write | [Create worker upload](/docs/reference/api/v2-create-worker-upload) |
|
||||
| | | [Delete a worker](/docs/reference/api/v2-delete-a-worker) |
|
||||
| | | [Deploy a worker](/docs/reference/api/v2-deploy-a-worker) |
|
||||
| Compute | Read | [Get a compute instance](/docs/reference/api/v2-get-a-compute-instance) |
|
||||
| | | [List all compute instances](/docs/reference/api/v2-list-all-compute-instances) |
|
||||
| | Read-write | [Create compute instance upload](/docs/reference/api/v2-create-compute-instance-upload) |
|
||||
| | | [Delete a compute instance](/docs/reference/api/v2-delete-a-compute-instance) |
|
||||
| | | [Deploy a compute instance](/docs/reference/api/v2-deploy-a-compute-instance) |
|
||||
| **Infrastructure and delivery** | | |
|
||||
| Development Branches | Read | [Get a branch](/docs/reference/api/v1-get-a-branch) |
|
||||
| | | [Get a branch config](/docs/reference/api/v1-get-a-branch-config) |
|
||||
|
||||
@@ -1 +1,8 @@
|
||||
Pricing details and included quotas will be published here when billing enforcement goes live.
|
||||
<Price price="0.50" /> per GB, after your plan's included quota.
|
||||
|
||||
| Plan | Included ingest | Over-usage per GB |
|
||||
| ---------- | --------------- | ------------------------------------------------------ |
|
||||
| Free | 1 GB | Not billed — see [Exceeding quotas](#exceeding-quotas) |
|
||||
| Pro | 20 GB | <Price price="0.50" /> |
|
||||
| Team | 20 GB | <Price price="0.50" /> |
|
||||
| Enterprise | 20 GB | <Price price="0.50" /> |
|
||||
@@ -1 +1,7 @@
|
||||
Pricing details and included quotas will be published here when billing enforcement goes live.
|
||||
Every organization gets a query allowance scaled to its log ingest usage, at a fixed ratio:
|
||||
|
||||
```
|
||||
Query allowance = 100 × log ingest usage
|
||||
```
|
||||
|
||||
The Free Plan includes 1 GB of ingest, so it also includes 100 GB of query allowance. Paid plans include 20 GB of ingest, so they start with 2,000 GB of query allowance. If you ingest more than your plan's included amount and pay for the overage, your allowance grows at the same 100x rate.
|
||||
@@ -155,10 +155,10 @@ allow_dynamic_registration = true
|
||||
|
||||
### Step 2: Create the MCP server
|
||||
|
||||
The fastest path is the [MCP Server block](/library/docs/headless/mcp-server) in the Supabase Library. It installs an Edge Function with the middleware already wired, a `whoami` tool, and a small tool registry to extend:
|
||||
The fastest path is the [MCP Server block](/library/docs/headless/mcp) in the Supabase Library. It installs an Edge Function with the middleware already wired, a `whoami` tool, and a small tool registry to extend:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add https://supabase.com/library/r/mcp-server.json
|
||||
npx shadcn@latest add https://supabase.com/library/r/mcp.json
|
||||
```
|
||||
|
||||
To write the function yourself, start with a table for the tools to work on. Create it with RLS so each user only sees their own rows; the `user_id` default means inserts don't need to pass it. Save this as a migration with `supabase migration new create_todos` and paste it into the generated file:
|
||||
@@ -196,8 +196,8 @@ Replace the contents of `supabase/functions/mcp/index.ts`. Compared with the pub
|
||||
import 'jsr:@supabase/functions-js/edge-runtime.d.ts'
|
||||
|
||||
import { createMcpHandler, McpServer } from 'npm:@modelcontextprotocol/server@^2.0.0'
|
||||
import { pipeline } from 'npm:@supabase/middleware@^0.5.0'
|
||||
import { withOAuthProtectedResource, withSupabase } from 'npm:@supabase/server@^1.6.0'
|
||||
import { pipeline } from 'npm:@supabase/middleware@1'
|
||||
import { withOAuthProtectedResource, withSupabase } from 'npm:@supabase/server@1'
|
||||
import { z } from 'npm:zod@^4.3.6'
|
||||
|
||||
import type { Database } from './database.types.ts'
|
||||
@@ -254,7 +254,7 @@ Deno.serve(
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
Composing `withSupabase` as a `pipeline` entry is alpha and tracks `@supabase/middleware` 0.x. The nested form, `withOAuthProtectedResource(withSupabase({ auth: 'user' }, handler))`, is stable and behaves the same. Both need `@supabase/server` 1.6.0 or later.
|
||||
The nested form, `withOAuthProtectedResource(withSupabase({ auth: 'user' }, handler))`, behaves the same. Both need `@supabase/server` 1.6.0 or later.
|
||||
|
||||
</Admonition>
|
||||
|
||||
@@ -380,7 +380,7 @@ Both examples in this guide are in the `supabase/supabase` repository, ready to
|
||||
|
||||
## Resources
|
||||
|
||||
- [MCP Server block](/library/docs/headless/mcp-server) and [Headless App block](/library/docs/tanstack/headless-app) in the Supabase Library
|
||||
- [MCP Server block](/library/docs/headless/mcp) and [Headless App block](/library/docs/tanstack/headless-app) in the Supabase Library
|
||||
- [`@supabase/server` reference](/docs/reference/server/introduction)
|
||||
- [MCP authentication with Supabase Auth](/docs/guides/auth/oauth-server/mcp-authentication)
|
||||
- [OAuth 2.1 server](/docs/guides/auth/oauth-server)
|
||||
|
||||
@@ -133,11 +133,11 @@ There are some situations where you might want to manually authenticate the MCP
|
||||
|
||||
### CI environment
|
||||
|
||||
To authenticate the MCP server in a CI environment, you can create a personal access token (PAT) with the necessary scopes and pass it as a header to the MCP server.
|
||||
To authenticate the MCP server in a CI environment, create a scoped personal access token (PAT) and pass it as a header to the MCP server.
|
||||
|
||||
1. Production projects can contain sensitive data. Before connecting one, scope the server to that project, enable [read-only mode](#configuration-options), restrict the available feature groups, and review the [security risks](#security-risks).
|
||||
|
||||
1. Navigate to your Supabase [access tokens](/dashboard/account/tokens) and generate a new token. Name the token based on its purpose, e.g. "Example App MCP CI token".
|
||||
1. Navigate to your Supabase [access tokens](/dashboard/account/tokens) and generate a new token. Name the token based on its purpose, for example "Example App MCP CI token". Scope it to the project the server connects to, and grant only the permissions your enabled tools need. See [MCP tools](/docs/guides/platform/personal-access-tokens#mcp-tools) for the permission each tool requires.
|
||||
|
||||
1. Pass the token to the `Authorization` header in your MCP server configuration. For example if you are using [Claude Code](https://docs.claude.com/en/docs/claude-code/github-actions), your MCP server configuration might look like this:
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ We can combine Hugging Face with [Supabase Storage](/storage) and [Database Webh
|
||||
- Create a `caption` column of type `text`.
|
||||
- Regenerate TypeScript types to include new `image_caption` table.
|
||||
- Deploy the function to Supabase: `supabase functions deploy huggingface-image-captioning`.
|
||||
- Create the Database Webhook in the [Supabase Dashboard](/dashboard/project/_/database/hooks) to trigger the `huggingface-image-captioning` function anytime a record is added to the `storage.objects` table.
|
||||
- Create the Database Webhook in the [Supabase Dashboard](/dashboard/project/_/integrations/webhooks/overview) to trigger the `huggingface-image-captioning` function anytime a record is added to the `storage.objects` table.
|
||||
|
||||
## Generate TypeScript types
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ Database calls (`select`, `insert`, `update`, `upsert`, `delete`, `rpc`) return
|
||||
| `details` | When `hint` and `message` aren't enough. Often contains the offending value, key, or row. |
|
||||
| `message` | As the human summary. Useful in UI strings, less useful for debugging. |
|
||||
|
||||
A full list of PostgREST error codes is in the [Error Codes reference](/guides/api/rest/postgrest-error-codes).
|
||||
A full list of PostgREST error codes is in the [Error Codes reference](/docs/guides/api/rest/postgrest-error-codes).
|
||||
|
||||
## Branch on `error.code`, not `error.message`
|
||||
|
||||
@@ -140,6 +140,6 @@ supabase.channel('room1').subscribe((status, err) => {
|
||||
|
||||
## Related
|
||||
|
||||
- [PostgREST Error Codes](/guides/api/rest/postgrest-error-codes)
|
||||
- [Automatic retries with `supabase-js`](/guides/api/automatic-retries-in-supabase-js)
|
||||
- [Securing your API](/guides/api/securing-your-api)
|
||||
- [PostgREST Error Codes](/docs/guides/api/rest/postgrest-error-codes)
|
||||
- [Automatic retries with `supabase-js`](/docs/guides/api/automatic-retries-in-supabase-js)
|
||||
- [Securing your API](/docs/guides/api/securing-your-api)
|
||||
@@ -67,6 +67,19 @@ Here's the text formatted as a proper markdown table:
|
||||
| 42501 | if authenticated 403, else 401 | insufficient privileges |
|
||||
| other | 400 | |
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
Some codes in this table are triggered by platform conditions rather than application code. Seeing them in high volume usually points to an infrastructure event rather than a bug in your queries:
|
||||
|
||||
- **25006** — The database has entered read-only mode due to disk quota. See [Read-only mode](/docs/guides/platform/database-size#read-only-mode) for causes and recovery steps.
|
||||
- **53100** — Disk is full. Supabase emits this alongside 25006 during severe disk exhaustion.
|
||||
- **53300** — Too many connections. The connection pool has reached its limit; check your [connection pool settings](/docs/guides/database/connection-management).
|
||||
- **57P03** — The database cannot accept connections, typically during a restart or failover.
|
||||
|
||||
When you see these codes in volume, check the [Database dashboard](/dashboard/project/_/observability/database) before debugging application code.
|
||||
|
||||
</Admonition>
|
||||
|
||||
## API level errors
|
||||
|
||||
### Connection errors
|
||||
|
||||
@@ -38,7 +38,7 @@ In the **sign-in flow**, the user signs in (upgrading the session to AAL1) and t
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
[TOTP MFA API](/docs/reference/javascript/auth-mfa-api) is free to use and is enabled on all Supabase projects by default.
|
||||
[TOTP MFA API](/docs/reference/javascript/auth-mfa) is free to use and is enabled on all Supabase projects by default.
|
||||
|
||||
</Admonition>
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ const supabase = createServerClient(
|
||||
)
|
||||
```
|
||||
|
||||
See the [server-side rendering guide](/guides/auth/server-side) for framework-specific setup.
|
||||
See the [server-side rendering guide](/docs/guides/auth/server-side) for framework-specific setup.
|
||||
|
||||
## `@supabase/server`
|
||||
|
||||
@@ -92,6 +92,6 @@ In a cookie-based framework you can compose the two — let `@supabase/ssr` own
|
||||
|
||||
## Next steps
|
||||
|
||||
- [Server-side rendering](/guides/auth/server-side) — set up `@supabase/ssr` for your framework.
|
||||
- [Server-side rendering](/docs/guides/auth/server-side) — set up `@supabase/ssr` for your framework.
|
||||
- [`@supabase/server` reference](/docs/reference/server/introduction) — API for header-based server auth.
|
||||
- [`supabase-js` reference](/docs/reference/javascript/introduction) — the base JavaScript client.
|
||||
@@ -637,7 +637,7 @@ Response:
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
For complete API documentation, see the [OAuth Admin API reference](/docs/reference/javascript/auth-admin-oauth-admin).
|
||||
For complete API documentation, see the [OAuth Admin API reference](/docs/reference/javascript/oauth-admin).
|
||||
|
||||
</Admonition>
|
||||
|
||||
|
||||
@@ -84,8 +84,8 @@ When building your own MCP server, integrate with Supabase Auth to authenticate
|
||||
On Supabase Edge Functions, or any runtime with a `fetch`-style handler, [`@supabase/server`](/docs/reference/server/introduction) does the OAuth plumbing for you. `withOAuthProtectedResource()` publishes the protected resource metadata and the `WWW-Authenticate` challenge that MCP clients use to find your Auth server; `withSupabase({ auth: 'user' })` verifies the token and gives your tools a client scoped to that user:
|
||||
|
||||
```ts
|
||||
import { pipeline } from 'npm:@supabase/middleware@^0.5.0'
|
||||
import { withOAuthProtectedResource, withSupabase } from 'npm:@supabase/server@^1.6.0'
|
||||
import { pipeline } from 'npm:@supabase/middleware@1'
|
||||
import { withOAuthProtectedResource, withSupabase } from 'npm:@supabase/server@1'
|
||||
|
||||
Deno.serve(
|
||||
pipeline(
|
||||
@@ -98,7 +98,7 @@ Deno.serve(
|
||||
)
|
||||
```
|
||||
|
||||
The [MCP Server block](/library/docs/headless/mcp-server) in the Supabase Library packages this as an installable Edge Function, and the [OAuth Consent block](/library/docs/nextjs/oauth-consent) provides the consent screen. See [Deploy MCP servers](/docs/guides/ai-tools/byo-mcp) for the full setup.
|
||||
The [MCP Server block](/library/docs/headless/mcp) in the Supabase Library packages this as an installable Edge Function, and the [OAuth Consent block](/library/docs/nextjs/oauth-consent) provides the consent screen. See [Deploy MCP servers](/docs/guides/ai-tools/byo-mcp) for the full setup.
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
|
||||
@@ -849,7 +849,7 @@ It's a good practice to provide a settings page where users can view all authori
|
||||
|
||||
</Admonition>
|
||||
|
||||
For complete API reference, see the [OAuth methods in supabase-js](/docs/reference/javascript/auth-admin-oauth-server).
|
||||
For complete API reference, see the [OAuth methods in supabase-js](/docs/reference/javascript/oauth-server).
|
||||
|
||||
## Next steps
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ Leaked password protection is available on the Pro Plan and above.
|
||||
|
||||
Users will need to be recently logged in to change their password without requiring reauthentication. (A user is considered recently logged in if the session was created within the last 24 hours.) If disabled, a user can change their password at any time.
|
||||
|
||||
When enabled, a `nonce` will be sent to the user and this nonce must be validated before the a password change can occur. This can be triggered with the [reauthenticate()](/docs/reference/javascript/auth-reauthentication) API call.
|
||||
When enabled, a `nonce` will be sent to the user and this nonce must be validated before the a password change can occur. This can be triggered with the [reauthenticate()](/docs/reference/javascript/auth-reauthenticate) API call.
|
||||
|
||||
```
|
||||
const { error } = await supabase.auth.reauthenticate()
|
||||
|
||||
@@ -14,7 +14,7 @@ When a JWT is issued by Supabase Auth, the key used to create its [signature](ht
|
||||
|
||||
| System | Type | Description |
|
||||
| ----------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Legacy | JWT secret | Initially Supabase was designed to use a single shared secret key to sign all JWTs. This includes <span className="whitespace-nowrap!">the `anon` and `service_role`</span> keys, all user access tokens including some [Storage pre-signed URLs](/docs/reference/javascript/storage-from-createsignedurl). **No longer recommended.** Available for backward compatibility. |
|
||||
| Legacy | JWT secret | Initially Supabase was designed to use a single shared secret key to sign all JWTs. This includes <span className="whitespace-nowrap!">the `anon` and `service_role`</span> keys, all user access tokens including some [Storage pre-signed URLs](/docs/reference/javascript/file-buckets-createsignedurl). **No longer recommended.** Available for backward compatibility. |
|
||||
| Signing keys | Asymmetric key (RSA, Elliptic Curves) | A JWT signing key based on [public-key cryptography](https://en.wikipedia.org/wiki/Public-key_cryptography) (RSA, Elliptic Curves) that follows industry best practices and significantly improves the security, reliability and performance of your applications. |
|
||||
| Signing keys | Shared secret key | A JWT signing key based on a [shared secret](https://en.wikipedia.org/wiki/HMAC). |
|
||||
|
||||
|
||||
@@ -84,6 +84,12 @@ Transaction mode does not support [prepared statements](https://postgresql.org/d
|
||||
|
||||
</Admonition>
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
Transaction mode does not support [query pipelining](https://www.postgresql.org/docs/current/libpq-pipeline-mode.html). See [Transaction mode limitations](#transaction-mode-limitations) for mode details.
|
||||
|
||||
</Admonition>
|
||||
|
||||
```txt
|
||||
postgresql://postgres.[PROJECT-REF]:[YOUR-PASSWORD]@[POOLER-HOST]:6543/postgres
|
||||
```
|
||||
@@ -168,7 +174,7 @@ For more on sizing an application-side pool, see the [Supavisor FAQ](/docs/guide
|
||||
|
||||
### Transaction mode limitations
|
||||
|
||||
[Transaction mode](#pooler-transaction-mode) returns your connection to the pool after each transaction, so anything that depends on session state doesn't survive between transactions. Three things are affected.
|
||||
[Transaction mode](#pooler-transaction-mode) returns your connection to the pool after each transaction, so anything that depends on session state doesn't survive between transactions. Four things are affected.
|
||||
|
||||
**Prepared statements** aren't supported, so turn them off. Each driver does this differently:
|
||||
|
||||
@@ -185,7 +191,15 @@ For node-postgres, Psycopg, and Rust drivers, see [Disabling prepared statements
|
||||
|
||||
**Session-level state** is lost between transactions. This covers `set` and `reset`, session-level advisory locks, `listen` and `notify`, and temporary tables. Run them inside the transaction that needs them, or use session mode or a direct connection instead.
|
||||
|
||||
Direct connections and session mode support all three, so none of this applies to either.
|
||||
**Query pipelining** isn't supported: transaction mode returns a connection to the pool as soon as it sees one reply finish, even if the client already queued more queries on it.
|
||||
|
||||
Direct connections and session mode support all four, so none of this applies to either.
|
||||
|
||||
<Admonition type='caution'>
|
||||
|
||||
`postgres.js` pipelines queries by default, so this combination can hang queries or return mismatched rows — see the [Postgres.js guide](/docs/guides/database/postgres-js) for how to avoid it.
|
||||
|
||||
</Admonition>
|
||||
|
||||
### SSL [#connecting-with-ssl]
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ Note that `pg_graphql` supports schema introspection, so you can connect any Gra
|
||||
comment on schema public is e'@graphql({"introspection": true})';
|
||||
```
|
||||
|
||||
See the [upgrade notes](/guides/platform/upgrading#upgrading-to-pg_graphql-160) for details.
|
||||
See the [upgrade notes](/docs/guides/platform/upgrading#upgrading-to-pg_graphql-160) for details.
|
||||
|
||||
## API
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ video: 'https://www.youtube.com/v/MJZCCpCYEqk'
|
||||
---
|
||||
|
||||
Postgres has built-in support for [SQL functions](https://www.postgresql.org/docs/current/sql-createfunction.html).
|
||||
These functions live inside your database, and they can be [used with the API](../../reference/javascript/rpc).
|
||||
These functions live inside your database, and you can call them from your app with [`rpc()`](../../reference/javascript/rpc).
|
||||
|
||||
## Quick demo
|
||||
|
||||
@@ -14,14 +14,15 @@ These functions live inside your database, and they can be [used with the API](.
|
||||
|
||||
## Getting started
|
||||
|
||||
Supabase provides several options for creating database functions. You can use the Dashboard or create them directly using SQL.
|
||||
We provide a SQL editor within the Dashboard, or you can [connect](../../guides/database/connecting-to-postgres) to your database
|
||||
and run the SQL queries yourself.
|
||||
Create a database function from the Dashboard, or write the SQL yourself against a
|
||||
[direct connection](../../guides/database/connecting-to-postgres).
|
||||
|
||||
1. Go to the "SQL editor" section.
|
||||
2. Click "New Query".
|
||||
3. Enter the SQL to create or replace your Database function.
|
||||
4. Click "Run" or cmd+enter (ctrl+enter).
|
||||
To use the Dashboard:
|
||||
|
||||
1. Go to the **SQL Editor** section.
|
||||
2. Click **New Query**.
|
||||
3. Enter the SQL that creates or replaces your database function.
|
||||
4. Click **Run**. You can also press `cmd+enter` or `ctrl+enter`.
|
||||
|
||||
## Basic functions [#simple-functions]
|
||||
|
||||
@@ -40,26 +41,24 @@ $$; --6
|
||||
<details>
|
||||
<summary>Show/Hide Details</summary>
|
||||
|
||||
At it's most basic a function has the following parts:
|
||||
At its most basic, a function has the following parts:
|
||||
|
||||
1. `create or replace function hello_world()`: The function declaration, where `hello_world` is the name of the function. You can use either `create` when creating a new function or `replace` when replacing an existing function. Or you can use `create or replace` together to handle either.
|
||||
2. `returns text`: The type of data that the function returns. If it returns nothing, you can `returns void`.
|
||||
1. `create or replace function hello_world()`: The function declaration, where `hello_world` is the name of the function. Use `create` for a new function, `replace` for one that exists, or `create or replace` when the function might not exist yet.
|
||||
2. `returns text`: The type of data the function returns. For a function that returns nothing, write `returns void`.
|
||||
3. `language sql`: The language used inside the function body. This can also be a procedural language: `plpgsql`, `plpython`, etc.
|
||||
4. `as $$`: The function wrapper. Anything enclosed inside the `$$` symbols will be part of the function body.
|
||||
5. `select 'hello world';`: A basic function body. The final `select` statement inside a function body will be returned if there are no statements following it.
|
||||
4. `as $$`: The function wrapper. Anything inside the `$$` symbols is part of the function body.
|
||||
5. `select 'hello world';`: A basic function body. The function returns the result of the last `select` statement in its body.
|
||||
6. `$$;`: The closing symbols of the function wrapper.
|
||||
|
||||
</details>
|
||||
|
||||
<br />
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
When naming your functions, make the name of the function unique as overloaded functions are not supported.
|
||||
Overloaded functions aren't supported. Give every function a unique name.
|
||||
|
||||
</Admonition>
|
||||
|
||||
After the Function is created, we have several ways of "executing" the function - either directly inside the database using SQL, or with one of the client libraries.
|
||||
After you create the function, you can run it inside the database with SQL, or with one of the client libraries.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -144,9 +143,9 @@ Reference: [`Rpc()`](../../reference/csharp/rpc)
|
||||
|
||||
## Returning data sets
|
||||
|
||||
Database Functions can also return data sets from [Tables](../../guides/database/tables) or Views.
|
||||
A database function can also return a data set from a [table](../../guides/database/tables) or a view.
|
||||
|
||||
For example, if we had a database with some Star Wars data inside:
|
||||
For example, take a database holding some Star Wars data:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -212,7 +211,7 @@ values
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
We could create a function which returns all the planets:
|
||||
The following function returns all the planets:
|
||||
|
||||
```sql
|
||||
create or replace function get_planets()
|
||||
@@ -223,7 +222,7 @@ as $$
|
||||
$$;
|
||||
```
|
||||
|
||||
Because this function returns a table set, we can also apply filters and selectors. For example, if we only wanted the first planet:
|
||||
Because this function returns a table set, you can apply filters and selectors to it. To get the first planet only:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -294,7 +293,7 @@ data = supabase.rpc('get_planets').eq('id', 1).execute()
|
||||
|
||||
## Passing parameters
|
||||
|
||||
Create a function to insert a new planet into the `planets` table and return the new ID. Note that this time we're using the `plpgsql` language.
|
||||
Create a function that inserts a new planet into the `planets` table and returns the new ID. This function uses the `plpgsql` language.
|
||||
|
||||
```sql
|
||||
create or replace function add_planet(name text)
|
||||
@@ -313,7 +312,7 @@ end;
|
||||
$$;
|
||||
```
|
||||
|
||||
Once again, you can execute this function either inside your database using a `select` query, or with the client libraries:
|
||||
You can run this function inside your database with a `select` query, or with the client libraries:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -413,14 +412,13 @@ await supabase.Rpc("add_planet", new Dictionary<string, object> { { "name", "Jak
|
||||
|
||||
### Database Functions vs Edge Functions
|
||||
|
||||
For data-intensive operations, use Database Functions, which are executed within your database
|
||||
and can be called remotely using the [REST and GraphQL API](../api).
|
||||
For data-intensive operations, use database functions. They run inside your database, and you can call them remotely with the [REST and GraphQL API](../api).
|
||||
|
||||
For use-cases which require low-latency, use [Edge Functions](../../guides/functions), which are globally-distributed and can be written in Typescript.
|
||||
For use cases that need low latency, use [Edge Functions](../../guides/functions). They're globally distributed and you write them in TypeScript.
|
||||
|
||||
### Security `definer` vs `invoker`
|
||||
|
||||
Postgres allows you to specify whether you want the function to be executed as the user _calling_ the function (`invoker`), or as the _creator_ of the function (`definer`). For example:
|
||||
Postgres runs a function either as the user _calling_ it (`invoker`) or as its _creator_ (`definer`). For example:
|
||||
|
||||
```sql
|
||||
create function hello_world()
|
||||
@@ -434,31 +432,31 @@ end;
|
||||
$$;
|
||||
```
|
||||
|
||||
It is best practice to use `security invoker` (which is also the default). If you ever use `security definer`, you _must_ set the `search_path`.
|
||||
If you use an empty search path (`search_path = ''`), you must explicitly state the schema for every relation in the function body (e.g. `from public.table`).
|
||||
This limits the potential damage if you allow access to schemas which the user executing the function should not have.
|
||||
Prefer `security invoker`, which is also the default. When you use `security definer`, you must set the `search_path`.
|
||||
|
||||
With an empty search path, `search_path = ''`, name the schema for every relation in the function body, such as `from public.table`. An empty search path limits the damage when the function can reach a schema you don't want the calling user to reach.
|
||||
|
||||
### Function privileges
|
||||
|
||||
By default, database functions can be executed by any role. There are two main ways to restrict this:
|
||||
By default, any role can run a database function. You can restrict execution in two ways:
|
||||
|
||||
1. On a case-by-case basis. Specifically revoke permissions for functions you want to protect. Execution needs to be revoked for both `public` and the role you're restricting:
|
||||
1. Revoke on a case-by-case basis. Revoke execute for the functions you want to protect, from both `public` and the role you're restricting:
|
||||
|
||||
```sql
|
||||
revoke execute on function public.hello_world from public;
|
||||
revoke execute on function public.hello_world from anon;
|
||||
```
|
||||
|
||||
1. Restrict function execution by default. Specifically _grant_ access when you want a function to be executable by a specific role.
|
||||
1. Restrict execution by default, then grant access to the roles that need each function.
|
||||
|
||||
To restrict all existing functions, revoke execution permissions from both `public` _and_ the role you want to restrict:
|
||||
To restrict every function that exists, revoke execute from both `public` and the role you want to restrict:
|
||||
|
||||
```sql
|
||||
revoke execute on all functions in schema public from public;
|
||||
revoke execute on all functions in schema public from anon, authenticated;
|
||||
```
|
||||
|
||||
To restrict all new functions, change the default privileges for both `public` _and_ the role you want to restrict:
|
||||
To restrict every function created later, change the default privileges for both `public` and the role you want to restrict:
|
||||
|
||||
```sql
|
||||
alter default privileges in schema public revoke execute on functions from public;
|
||||
@@ -473,7 +471,7 @@ By default, database functions can be executed by any role. There are two main w
|
||||
|
||||
### Debugging functions
|
||||
|
||||
You can add logs to help you debug functions. This is especially recommended for complex functions.
|
||||
Add logs to help you debug a function. Logs matter most in a complex function.
|
||||
|
||||
Good targets to log include:
|
||||
|
||||
@@ -482,7 +480,7 @@ Good targets to log include:
|
||||
|
||||
#### General logging
|
||||
|
||||
To create custom logs in the [Dashboard's Postgres Logs](/dashboard/project/_/logs/postgres-logs), you can use the `raise` keyword. By default, there are 3 observed severity levels:
|
||||
Use the `raise` keyword to write custom logs to the [Postgres logs](/dashboard/project/_/logs/postgres-logs) in the Dashboard. Three severity levels appear by default:
|
||||
|
||||
- `log`
|
||||
- `warning`
|
||||
@@ -533,14 +531,14 @@ $$;
|
||||
select error_if_null(null);
|
||||
```
|
||||
|
||||
Value checking is common, so Postgres provides a shorthand: the `assert` keyword. It uses the following format:
|
||||
Value checking is common, so Postgres provides the `assert` keyword as a shorthand. It takes the following format:
|
||||
|
||||
```sql
|
||||
-- throw error when condition is false
|
||||
assert <some condition>, 'message';
|
||||
```
|
||||
|
||||
Below is an example
|
||||
For example:
|
||||
|
||||
```sql
|
||||
create function assert_example(name text)
|
||||
@@ -567,7 +565,7 @@ $$;
|
||||
select assert_example('Harry Potter');
|
||||
```
|
||||
|
||||
Error messages can also be captured and modified with the `exception` keyword:
|
||||
You can also capture and modify an error message with the `exception` keyword:
|
||||
|
||||
```sql
|
||||
create function error_example()
|
||||
@@ -587,7 +585,7 @@ $$;
|
||||
|
||||
#### Advanced logging
|
||||
|
||||
For more complex functions or complicated debugging, try logging:
|
||||
For a more complex function, or for harder debugging, log the following:
|
||||
|
||||
- Formatted variables
|
||||
- Individual rows
|
||||
|
||||
@@ -6,11 +6,11 @@ description: "A storage extension for Postgres which uses Postgres's pluggable s
|
||||
|
||||
The [OrioleDB](https://www.orioledb.com/) Postgres extension provides a drop-in replacement storage engine for the default heap storage method. It is designed to improve Postgres' scalability and performance.
|
||||
|
||||
OrioleDB addresses Postgres's scalability limitations by removing bottlenecks in the shared memory cache under high concurrency. It also optimizes write-ahead-log (WAL) insertion through row-level WAL logging. These changes lead to significant improvements in the industry standard TPC-C benchmark, which approximates a real-world transactional workload. The following benchmark was performed on a c7g.metal instance and shows OrioleDB's performance outperforming the default Postgres heap method with a 3.3x speedup.
|
||||
OrioleDB addresses Postgres's scalability limitations by removing bottlenecks in the shared memory cache under high concurrency. It also optimizes write-ahead-log (WAL) insertion through row-level WAL logging. These changes lead to significant improvements in the industry standard TPC-C benchmark, which approximates a real-world transactional workload. The following benchmark was performed on a 8xlarge instance and shows OrioleDB's performance outperforming the default Postgres heap method with a 1.8x speedup.
|
||||
|
||||
<Image
|
||||
alt="TPC-C (warehouses = 500)"
|
||||
src="/docs/img/database/orioledb-tpc-c-500-warehouse.png"
|
||||
alt="Line chart of TPC-C throughput at 5120 warehouses, in transactions per minute (tpmC), from 32 to 256 connections. OrioleDB rises to about 112,800 tpmC by 128 connections and holds between about 110,000 and 112,000 from 128 connections onward. Heap stays between about 37,000 and 65,000 across the whole range."
|
||||
src="/docs/img/database/orioledb-tpc-c-5120-warehouse.png"
|
||||
className="max-w-[550px] mx-auto! border rounded-md"
|
||||
width={1000}
|
||||
height={609}
|
||||
@@ -18,7 +18,7 @@ OrioleDB addresses Postgres's scalability limitations by removing bottlenecks in
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
OrioleDB is in active development and has [certain limitations](https://www.orioledb.com/docs/usage/getting-started#current-limitations). Native B-tree indexes give the best performance, and an Index Access Method bridge provides experimental support for other index types built for heap storage, including pg_vector's HNSW indexes. In the Supabase OrioleDB image the default storage method has been updated to use OrioleDB, granting better performance out of the box.
|
||||
OrioleDB is in public beta, and projects that use it have access to the same paid features as other Supabase projects. OrioleDB has [some limitations](https://www.orioledb.com/docs/usage/getting-started#current-limitations) compared to the default heap storage engine, so review them before you choose it for a project. Native B-tree indexes give the best performance, and an Index Access Method bridge provides experimental support for other index types built for heap storage, including pg_vector's HNSW indexes. In the Supabase OrioleDB image the default storage method has been updated to use OrioleDB, granting better performance out of the box.
|
||||
|
||||
</Admonition>
|
||||
|
||||
@@ -44,20 +44,12 @@ OrioleDB implements copy-on-write checkpoints to persist data efficiently. This
|
||||
|
||||
### Creating OrioleDB project
|
||||
|
||||
You can get started with OrioleDB by enabling the extension in your Supabase dashboard.
|
||||
To get started with OrioleDB you need to [create a new Supabase project](/dashboard/new/_) and choose `OrioleDB Public Alpha` Postgres version.
|
||||
You choose OrioleDB when you create a project. You can't add OrioleDB to an existing project or remove it later.
|
||||
|
||||
<Image
|
||||
alt="Creating OrioleDB project"
|
||||
src={{
|
||||
light: '/docs/img/database/orioledb-creating-project--light.png',
|
||||
dark: '/docs/img/database/orioledb-creating-project.png',
|
||||
}}
|
||||
className="max-w-[550px] mx-auto! border rounded-md"
|
||||
|
||||
width={1376}
|
||||
height={2034}
|
||||
/>
|
||||
1. [Create a new Supabase project](/dashboard/new/_).
|
||||
1. Expand **Advanced Configuration**.
|
||||
1. Under **Postgres Type**, select **Postgres with OrioleDB**.
|
||||
1. Finish creating the project.
|
||||
|
||||
### Creating tables
|
||||
|
||||
@@ -120,10 +112,11 @@ explain select * from blog_post order by published_at desc limit 10;
|
||||
-> Index Scan Backward using blog_post_published_at on blog_post (cost=0.15..48.95 rows=320 width=120)
|
||||
|
||||
explain select * from blog_post where id = 1;
|
||||
QUERY PLAN
|
||||
──────────────────────────────────────────────────────────────────────────────────
|
||||
Index Scan using blog_post_pkey on blog_post (cost=0.15..8.17 rows=1 width=120)
|
||||
Index Cond: (id = 1)
|
||||
QUERY PLAN
|
||||
───────────────────────────────────────────────────────────────────────
|
||||
Custom Scan (o_scan) on blog_post (cost=0.15..8.17 rows=1 width=120)
|
||||
Forward index scan of: blog_post_pkey
|
||||
Conds: (id = 1)
|
||||
|
||||
explain (analyze, buffers) select * from blog_post order by published_at desc limit 10;
|
||||
QUERY PLAN
|
||||
@@ -167,7 +160,7 @@ A smaller set of OrioleDB settings has a `user` [context](/docs/guides/database/
|
||||
|
||||
| Setting | Description | Default |
|
||||
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
|
||||
| `orioledb.default_compress` | Default compression level for OrioleDB tables. Set to `-1` to disable compression, or `0` or a positive integer to trade write speed for a smaller table on disk. | `-1` |
|
||||
| `orioledb.default_compress` | Default compression level for OrioleDB tables. Set to `-1` to disable compression, or `0` to `22` to trade write speed for a smaller table on disk. | `-1` |
|
||||
| `orioledb.default_primary_compress` | Default compression level for the primary index. Accepts the same values as `orioledb.default_compress`. | `-1` |
|
||||
| `orioledb.default_toast_compress` | Default compression level for TOASTed values. Accepts the same values as `orioledb.default_compress`. | `-1` |
|
||||
| `orioledb.serializable` | How OrioleDB handles `SERIALIZABLE` transactions. `table_lock` acquires a coarse lock per touched table, `repeatable_read` silently downgrades the isolation level, and `error` rejects `SERIALIZABLE` transactions. | `table_lock` |
|
||||
|
||||
@@ -38,6 +38,14 @@ hideToc: true
|
||||
|
||||
To get your connection details, go to the [**Connect** panel](/dashboard/project/_?showConnect=true). Choose [**Transaction pooler**](/dashboard/project/_?showConnect=true&method=transaction) if you're on a platform with transient connections, such as a serverless function, and [**Session pooler**](/dashboard/project/_?showConnect=true&method=session) if you have a long-lived connection. Copy the URI and save it as the environment variable `DATABASE_URL`.
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
`postgres.js` pipelines queries by default. Combined with the [shared pooler transaction mode](/docs/guides/database/connecting-to-postgres#pooler-transaction-mode), this can hang queries or return mismatched rows. `postgres.js` has no working option to turn pipelining off directly: `max_pipeline: 0` breaks `sql.begin()` transactions instead, a known upstream [bug](https://github.com/porsager/postgres/issues/1189).
|
||||
|
||||
For more about pipelining with `postgres.js`, [contact support](/dashboard/support/new).
|
||||
|
||||
</Admonition>
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
@@ -47,7 +55,7 @@ hideToc: true
|
||||
import postgres from 'postgres'
|
||||
|
||||
const connectionString = process.env.DATABASE_URL
|
||||
const sql = postgres(connectionString)
|
||||
const sql = postgres(connectionString, { prepare: false })
|
||||
|
||||
export default sql
|
||||
```
|
||||
|
||||
@@ -47,7 +47,7 @@ Follow [Set up Pipelines](/docs/guides/database/replication/pipelines#setup-over
|
||||
|
||||
Optionally adjust [destination settings](#destination-settings) and [table partitioning and clustering](#table-partitioning-and-clustering) before creation.
|
||||
|
||||
Click **Create and start pipeline** and complete the validation and cost confirmations.
|
||||
Click **Start pipeline** to validate the destination. Review the cost estimate, then click **Create and start pipeline**.
|
||||
|
||||
Supabase Pipelines charges and Google Cloud charges are separate. BigQuery can charge for Storage Write API ingestion, storage, and the compute used to apply CDC changes. See [BigQuery CDC pricing](https://cloud.google.com/bigquery/docs/change-data-capture#CDC_pricing).
|
||||
|
||||
|
||||
@@ -10,59 +10,61 @@ sidebar_label: 'ClickHouse'
|
||||
|
||||
The ClickHouse destination is in private alpha and available only to approved organizations. [Request access](/go/supabase-pipelines-new-destinations) before following this guide.
|
||||
|
||||
Replicate Postgres changes to [ClickHouse](https://clickhouse.com/) as current-state tables or an append-only history. [Choose a table engine](#choose-a-table-engine), [prepare resources](#prepare-clickhouse-resources), then [configure the destination](#configure-clickhouse-as-a-destination).
|
||||
[ClickHouse](https://clickhouse.com/) is a database for analytics. Supabase Pipelines replicates Postgres tables to ClickHouse as either current-state tables or an append-only history of changes.
|
||||
|
||||
To replicate data to ClickHouse:
|
||||
|
||||
1. [Choose a table engine](#choose-a-table-engine) and check the [source table requirements](#source-table-requirements).
|
||||
2. [Prepare a database, user, and HTTPS endpoint](#prepare-clickhouse-resources) in ClickHouse.
|
||||
3. [Configure the ClickHouse destination](#configure-clickhouse-as-a-destination) in the Dashboard.
|
||||
4. [Query the replicated data](#query-replicated-data) in ClickHouse.
|
||||
|
||||
## Source table requirements
|
||||
|
||||
`ReplacingMergeTree` requires a source primary key. `MergeTree` can replicate insert-only tables without one. Include all primary-key columns in the publication when the table has a key.
|
||||
The source table requirements depend on the selected table engine and the operations in the Postgres publication. `ReplacingMergeTree` requires a source primary key. `MergeTree` can replicate insert-only tables without one. If a table has a primary key, include all its key columns in the publication.
|
||||
|
||||
Check the [replica-identity and array requirements](#replica-identity-and-arrays) for your source tables.
|
||||
|
||||
### Choose a table engine
|
||||
|
||||
The table engine controls how ClickHouse represents changes. It is selected for the entire destination.
|
||||
|
||||
Choose the engine before creating the pipeline. Changing **Table engine** later does not convert existing destination tables; writes fail if their engine differs from the configured one. Restore the previous setting to resume using those tables.
|
||||
The table engine determines how ClickHouse stores and queries replicated changes. Choose one engine for the entire destination:
|
||||
|
||||
| Engine | Data model and query pattern |
|
||||
| -------------------- | ------------------------------------------------------------------------------------------------ |
|
||||
| `ReplacingMergeTree` | Current-state tables. Requires a primary key. Query the generated `__current` view. |
|
||||
| `MergeTree` | Append-only CDC history. A primary key is optional for insert-only tables. Query the base table. |
|
||||
|
||||
Updating a source primary-key value removes the old key from the current-state view and writes the row under its new key. Changing the primary-key definition is a separate [schema change](#schema-change-support).
|
||||
Choose the engine before creating the pipeline. Changing **Table engine** later does not convert existing destination tables. Writes fail if their engine differs from the configured one. Restore the previous setting to resume writing to those tables.
|
||||
|
||||
With `ReplacingMergeTree`, changing a source primary-key value removes the old key from the current-state view and writes the row under its new key. Changing which columns make up the primary key is a separate [schema change](#schema-change-support).
|
||||
|
||||
## Prepare ClickHouse resources
|
||||
|
||||
Before creating the destination:
|
||||
Managed Pipelines run in AWS `eu-central-1` (Frankfurt). When creating your ClickHouse service, choose a nearby region to [reduce replication latency](/docs/guides/database/replication/pipelines#region). Then prepare these resources in ClickHouse before creating the destination:
|
||||
|
||||
1. Create or choose a ClickHouse database for the replicated tables.
|
||||
2. Create a dedicated ClickHouse user for Pipelines.
|
||||
3. Grant the user access to the target database. Pipelines must be able to:
|
||||
1. Create an empty database for the replicated tables. In ClickHouse Cloud, open your service's **SQL console** and run `create database if not exists pipelines;`, replacing `pipelines` if you prefer another name. Pipelines creates and manages the tables and, for `ReplacingMergeTree`, the current-state views. Do not create or alter these objects yourself.
|
||||
2. Create a dedicated database user for Pipelines and grant it access to that database. In ClickHouse Cloud, create this user in the **SQL console**; the organization's **Users and roles** page manages console members. The database user must be able to:
|
||||
- Query `system.databases`, `system.tables`, and `system.columns`
|
||||
- Create, alter, truncate, and drop tables
|
||||
- Create and drop views when using `ReplacingMergeTree`
|
||||
- Insert rows into managed tables
|
||||
4. Copy the database's HTTPS endpoint, including its port when required. Pipelines rejects HTTP endpoints and private or internal hostnames.
|
||||
3. Copy the public HTTPS endpoint for your ClickHouse server. In ClickHouse Cloud, click **Connect** and select **HTTPS** to find it. Include the port if your endpoint requires one. Pipelines cannot connect to HTTP endpoints or private and internal hostnames.
|
||||
|
||||
Keep the database otherwise empty. Pipelines manages the replicated tables and current-state views. Don't pre-create or manually alter those objects.
|
||||
|
||||
The default `ReplacingMergeTree` engine requires ClickHouse 23.5 or later. The `MergeTree` event-log engine does not have this minimum-version requirement.
|
||||
For `ReplacingMergeTree`, the ClickHouse server must run version 23.5 or later. `MergeTree` does not have this minimum-version requirement.
|
||||
|
||||
## Configure ClickHouse as a destination
|
||||
|
||||
Follow [Set up Pipelines](/docs/guides/database/replication/pipelines#setup-overview) and select **ClickHouse**. Enter these destination settings:
|
||||
Follow [Set up Pipelines](/docs/guides/database/replication/pipelines#setup-overview). When prompted to choose a destination, select **ClickHouse** and enter the following settings:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------- | -------------------------------------------------------------------------- |
|
||||
| **URL** | Public HTTPS endpoint, including its port when required |
|
||||
| **User** | Dedicated ClickHouse user |
|
||||
| **Password** | User's password, if required |
|
||||
| **Database** | Existing target database |
|
||||
| **Table engine** | `replacing_merge_tree` for current state or `merge_tree` for event history |
|
||||
| Field | Value |
|
||||
| ------------------ | ----------------------------------------------------------------------- |
|
||||
| **HTTPS endpoint** | Public HTTPS endpoint, including its port when required |
|
||||
| **User** | Your dedicated database user, such as `pipelines_user` |
|
||||
| **Password** | That user's password, if set |
|
||||
| **Database** | Destination database, such as `pipelines` |
|
||||
| **Table engine** | `ReplacingMergeTree` for current state or `MergeTree` for event history |
|
||||
|
||||
Click **Create and start pipeline** and complete the validation and cost confirmations.
|
||||
|
||||
Place ClickHouse near the [managed pipeline region](/docs/guides/database/replication/pipelines#region).
|
||||
Click **Start pipeline** to validate the destination. Review the cost estimate, then click **Create and start pipeline**.
|
||||
|
||||
## Query replicated data
|
||||
|
||||
@@ -79,7 +81,7 @@ Postgres schema and table names cannot start or end with `_` or contain `"` or `
|
||||
|
||||
### ReplacingMergeTree
|
||||
|
||||
`ReplacingMergeTree` is the default and is intended for current-state analytics. Pipelines:
|
||||
Use the generated `__current` view when you need the latest version of each source row. `ReplacingMergeTree` is the default engine. Pipelines:
|
||||
|
||||
- Uses the source primary key as ClickHouse's sorting and deduplication key
|
||||
- Adds an `_etl_version UInt128` ordering column
|
||||
@@ -92,16 +94,16 @@ Query the generated view for the current state:
|
||||
|
||||
```sql
|
||||
select *
|
||||
from "public_orders__current";
|
||||
from pipelines."public_orders__current";
|
||||
```
|
||||
|
||||
ClickHouse background merges combine older row versions over time. Until they do, querying the base table without `FINAL` can return multiple versions of the same source row. Use the generated `__current` view for normal current-state queries.
|
||||
ClickHouse background merges combine older row versions over time. Before a merge, the base table can contain multiple versions of the same source row. The generated view uses `FINAL` to return the current version and excludes deleted rows.
|
||||
|
||||
Pipelines does not run `OPTIMIZE ... FINAL CLEANUP`. ClickHouse operators remain responsible for any physical tombstone cleanup required by their storage-retention policy.
|
||||
|
||||
### MergeTree
|
||||
|
||||
`MergeTree` stores every replicated change as an append-only event. Pipelines adds:
|
||||
Query the base table when you need the history of inserts, updates, and deletes. `MergeTree` stores each replicated change as an append-only event. Pipelines adds:
|
||||
|
||||
- `cdc_operation`, containing `INSERT`, `UPDATE`, or `DELETE`
|
||||
- `cdc_lsn`, containing the Postgres commit LSN for the change
|
||||
@@ -111,7 +113,7 @@ The `cdc_operation`, `cdc_lsn`, and `cdc_tx_ordinal` names are reserved and can'
|
||||
|
||||
Inserts and updates append the complete new row. A primary-key value update also appends a `DELETE` for the old key. Deletes append the old row when the source uses `REPLICA IDENTITY FULL`. With primary-key identity, a delete contains the key values; other fields use `NULL` for nullable scalars or placeholders such as zero, empty strings, and empty arrays. Those placeholders are not the deleted row's original values.
|
||||
|
||||
Read the base table to analyze the event history. Order source changes by `cdc_lsn` and then `cdc_tx_ordinal`. The old-key delete and new-key update from one primary-key value change share both values, so this pair is not a unique destination-row ID.
|
||||
Order source changes by `cdc_lsn` and then `cdc_tx_ordinal`. The old-key delete and new-key update from one primary-key value change share both values, so this pair is not a unique destination-row ID.
|
||||
|
||||
### Truncates and table restarts
|
||||
|
||||
|
||||
@@ -10,13 +10,17 @@ sidebar_label: 'Snowflake'
|
||||
|
||||
The Snowflake destination is in private alpha and available only to approved organizations. [Request access](/go/supabase-pipelines-new-destinations) before following this guide.
|
||||
|
||||
[Snowflake](https://www.snowflake.com/) is a managed data platform. Supabase Pipelines writes an append-only change history for each replicated Postgres table to Snowflake.
|
||||
[Snowflake](https://www.snowflake.com/) is a managed data platform. Supabase Pipelines replicates each Postgres table to Snowflake as an append-only history of changes.
|
||||
|
||||
[Prepare resources](#prepare-snowflake-resources), [configure the destination](#configure-snowflake-as-a-destination), then [query replicated data](#query-and-materialize-current-state).
|
||||
To replicate data to Snowflake:
|
||||
|
||||
1. [Prepare a database, schema, role, service user, and key pair](#prepare-snowflake-resources) in Snowflake.
|
||||
2. [Configure the Snowflake destination](#configure-snowflake-as-a-destination) in the Dashboard.
|
||||
3. [Query or materialize the replicated data](#query-and-materialize-current-state) in Snowflake.
|
||||
|
||||
## Source table requirements
|
||||
|
||||
Required `REPLICA IDENTITY` depends on the operations enabled in the Postgres publication:
|
||||
The operations enabled in the Postgres publication determine the required `REPLICA IDENTITY`:
|
||||
|
||||
| Published operations | Required replica identity |
|
||||
| -------------------- | ---------------------------------------------------------------------------------------------------------- |
|
||||
@@ -30,13 +34,13 @@ Set full replica identity before publishing updates:
|
||||
alter table public.your_table replica identity full;
|
||||
```
|
||||
|
||||
`REPLICA IDENTITY FULL` increases WAL volume, but lets Pipelines construct complete new rows when Postgres omits unchanged out-of-line TOAST values. The setting applies only to new WAL records. If retained WAL already contains an incompatible update, restart replication for the affected table after changing the setting.
|
||||
`REPLICA IDENTITY FULL` increases WAL volume but allows Pipelines to construct complete new rows when Postgres omits unchanged out-of-line TOAST values. It applies only to new WAL records. If the retained WAL already contains an incompatible update, change the setting and then restart replication for the affected table.
|
||||
|
||||
## Prepare Snowflake resources
|
||||
|
||||
Create a dedicated Snowflake database, schema, role, and service user for Pipelines. Keep the schema otherwise empty to avoid ownership conflicts. Use unquoted identifiers for the service user and role. Pipelines converts the account and user names to uppercase during authentication.
|
||||
Before you create a pipeline, prepare a dedicated Snowflake database and an empty schema for the replicated tables. Pipelines also needs a service user and role that can create and manage those tables. Use unquoted identifiers for the service user and role so Snowflake stores them in uppercase. Pipelines also converts the account and user names to uppercase during authentication.
|
||||
|
||||
Run the following as a Snowflake administrator. Change the example names as needed:
|
||||
Run this setup as a Snowflake administrator, changing the example names as needed:
|
||||
|
||||
```sql
|
||||
create role if not exists PIPELINES_ROLE;
|
||||
@@ -55,24 +59,24 @@ grant usage on schema PIPELINES_DB.REPLICATED to role PIPELINES_ROLE;
|
||||
grant create table on schema PIPELINES_DB.REPLICATED to role PIPELINES_ROLE;
|
||||
```
|
||||
|
||||
The pipeline role must own destination tables so it can alter, truncate, or drop them. Don't pre-create destination tables under another role.
|
||||
In this example, Pipelines creates the destination tables using `PIPELINES_ROLE`. Whatever name you choose, the role must retain ownership so Pipelines can alter, truncate, or drop the tables when required. Do not create the tables in advance under another role.
|
||||
|
||||
Snowflake creates each table's managed default pipe, `<TABLE>-STREAMING`, automatically. No virtual warehouse, stage, or manually created pipe is required. See [Snowpipe Streaming access privileges](https://docs.snowflake.com/en/user-guide/snowpipe-streaming/snowpipe-streaming-access-control) for the ingestion requirements.
|
||||
Snowflake automatically creates a managed default pipe named `<TABLE>-STREAMING` for each table. You don't need to provide a virtual warehouse, stage, or pipe for ingestion. See [Snowpipe Streaming access privileges](https://docs.snowflake.com/en/user-guide/snowpipe-streaming/snowpipe-streaming-access-control) for the required permissions.
|
||||
|
||||
Use a separate role and warehouse for downstream queries and transformations.
|
||||
To keep ingestion resources separate from downstream workloads, use a different role and warehouse for queries and transformations.
|
||||
|
||||
### Keep the SQL and streaming roles aligned
|
||||
|
||||
Pipelines uses two Snowflake interfaces:
|
||||
Pipelines connects to Snowflake through separate interfaces for SQL requests and streaming. Both interfaces need to use the same role:
|
||||
|
||||
- SQL requests use the optional **Role** configured in the Dashboard. When **Role** is empty, they use the user's default role.
|
||||
- SQL requests use the optional **Role** under **Advanced settings** in the Dashboard. When **Role** is empty, they use the user's default role.
|
||||
- Snowpipe Streaming uses the user's `DEFAULT_ROLE`. It does not use the optional **Role** setting.
|
||||
|
||||
Set the pipeline role as the service user's `DEFAULT_ROLE`. Leave **Role** empty or set it to that same role. Otherwise, SQL validation can succeed while streaming fails.
|
||||
Grant the permissions above to a dedicated role such as `PIPELINES_ROLE`, then set it as the service user's `DEFAULT_ROLE`. In the Dashboard, either leave **Role** empty or enter the same role explicitly. This keeps SQL validation and streaming aligned.
|
||||
|
||||
### Generate a key pair
|
||||
|
||||
Pipelines authenticates with an RSA key pair. Snowflake requires a key of at least 2048 bits and recommends PKCS #8. Choose one of the following commands to generate `rsa_key.p8`.
|
||||
Pipelines uses an RSA key pair to authenticate with Snowflake. The key must be at least 2048 bits, and Snowflake recommends the PKCS #8 format. Run one of the following commands from the directory where you want to store the key. The command creates a private key named `rsa_key.p8` in that directory.
|
||||
|
||||
For an unencrypted private key:
|
||||
|
||||
@@ -88,19 +92,21 @@ openssl genrsa 2048 | openssl pkcs8 -topk8 -v2 des3 \
|
||||
-inform PEM -out rsa_key.p8
|
||||
```
|
||||
|
||||
Derive the public key:
|
||||
Create a public key from the private key. Snowflake uses the public key to verify connections signed with `rsa_key.p8`:
|
||||
|
||||
```bash
|
||||
openssl rsa -in rsa_key.p8 -pubout -out rsa_key.pub
|
||||
```
|
||||
|
||||
Register only the public-key body with the service user. Omit the `BEGIN PUBLIC KEY` and `END PUBLIC KEY` lines:
|
||||
Open `rsa_key.pub` and copy the text between the `BEGIN PUBLIC KEY` and `END PUBLIC KEY` lines. In the following statement, replace the `<public-key-body>` placeholder with the copied text:
|
||||
|
||||
```sql
|
||||
alter user PIPELINES_USER set rsa_public_key = '<public-key-body>';
|
||||
```
|
||||
|
||||
Keep `rsa_key.p8` and its passphrase secret. Don't commit them, paste them into logs, or send them to support. The Dashboard accepts unencrypted PKCS #1 or PKCS #8 keys, and encrypted PKCS #8 keys with a passphrase. It does not support encrypted PKCS #1 keys.
|
||||
When you configure the destination in the Dashboard, paste or upload the complete `rsa_key.p8` file into **Private key**. Preserve its original PEM header and footer. If the key is passphrase-protected, enter the passphrase into **Private key passphrase**.
|
||||
|
||||
Keep `rsa_key.p8` and its passphrase secret. The Dashboard accepts unencrypted PKCS #1 and PKCS #8 keys. It also accepts encrypted PKCS #8 keys with a passphrase, but not encrypted PKCS #1 keys.
|
||||
|
||||
See [Snowflake key-pair authentication](https://docs.snowflake.com/en/user-guide/key-pair-auth) to verify the public-key fingerprint and rotate keys with `RSA_PUBLIC_KEY_2`.
|
||||
|
||||
@@ -112,35 +118,35 @@ Run this query in Snowflake:
|
||||
select current_organization_name() || '-' || current_account_name();
|
||||
```
|
||||
|
||||
Enter the result as **Account ID**, for example `MYORG-MYACCOUNT`. Do not enter a full URL or dotted locator-and-region hostname. Account IDs can contain up to 63 characters. Legacy one-part account locators are also accepted. See [Snowflake account identifiers](https://docs.snowflake.com/en/user-guide/admin-account-identifier) for details.
|
||||
Use the result as the **Account ID**, for example `MYORG-MYACCOUNT`. The field also accepts legacy one-part account locators, but not full URLs or dotted locator-and-region hostnames. Account IDs can contain up to 63 characters. See [Snowflake account identifiers](https://docs.snowflake.com/en/user-guide/admin-account-identifier) for details.
|
||||
|
||||
## Configure Snowflake as a destination
|
||||
|
||||
Follow [Set up Pipelines](/docs/guides/database/replication/pipelines#setup-overview) and select **Snowflake**. Enter these settings:
|
||||
Follow the steps in [Set up Pipelines](/docs/guides/database/replication/pipelines#setup-overview). When prompted to choose a destination, select **Snowflake** and enter the following settings:
|
||||
|
||||
| Field | Value |
|
||||
| -------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| **Account ID** | `MYORG-MYACCOUNT`, for example; use an organization-account identifier |
|
||||
| **User** | `PIPELINES_USER`, or your unquoted service user |
|
||||
| **Database** | `PIPELINES_DB`, or your destination database |
|
||||
| **Schema** | `REPLICATED`, or your dedicated destination schema |
|
||||
| **Role** | The service user's default role name, or empty; see [role alignment](#keep-the-sql-and-streaming-roles-aligned) |
|
||||
| **Private key** | Complete PEM, including begin and end lines |
|
||||
| **Private key passphrase** | Only for an encrypted PKCS #8 key |
|
||||
|
||||
Click **Create and start pipeline** and complete the validation and cost confirmations.
|
||||
| Field | Value |
|
||||
| -------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| **Account ID** | `MYORG-MYACCOUNT`, for example; use an organization-account identifier |
|
||||
| **User** | `PIPELINES_USER`, or your unquoted service user |
|
||||
| **Database** | `PIPELINES_DB`, or your destination database |
|
||||
| **Schema** | `REPLICATED`, or your dedicated destination schema |
|
||||
| **Role** | Leave empty to use the service user's default role, or enter that same role explicitly |
|
||||
| **Private key** | Paste or upload the complete `rsa_key.p8` private-key PEM file |
|
||||
| **Private key passphrase** | Only for an encrypted PKCS #8 key |
|
||||
|
||||
Enter the database and schema identifiers exactly as stored in Snowflake. Unquoted identifiers are stored in uppercase. Choose an account near the [managed pipeline region](/docs/guides/database/replication/pipelines#region).
|
||||
|
||||
Click **Start pipeline**, then complete the validation and cost confirmations.
|
||||
|
||||
## How it works
|
||||
|
||||
Pipelines uses Snowflake's SQL REST API to validate the database and schema, create, evolve, and recreate destination tables, and apply source `TRUNCATE` operations. It sends initial and ongoing row data through Snowpipe Streaming.
|
||||
Pipelines uses Snowflake's SQL REST API to validate the database and schema. It also uses the API to create, update, and recreate destination tables and to apply source `TRUNCATE` operations. Pipelines sends initial and ongoing row data through Snowpipe Streaming.
|
||||
|
||||
Validation checks authentication, database and schema visibility, and that `QUOTED_IDENTIFIERS_IGNORE_CASE` is `FALSE`. It does not verify that the role can create or own tables or write through Snowpipe Streaming.
|
||||
|
||||
### Destination table names
|
||||
|
||||
Pipelines maps each Postgres schema and table pair to one Snowflake table name. It doubles existing underscores, joins the names with one underscore, and uppercases the result:
|
||||
Pipelines combines each Postgres schema and table name into one Snowflake table name. Existing underscores are doubled, the two names are joined with a single underscore, and the result is converted to uppercase:
|
||||
|
||||
| Postgres table | Snowflake table |
|
||||
| ---------------------- | ------------------------ |
|
||||
@@ -160,20 +166,20 @@ Each destination table contains the replicated source columns plus two `VARCHAR
|
||||
|
||||
The metadata names are reserved and can't be used by source columns. Initial-sync rows use `insert` and the shared sequence number `0000000000000000/0000000000000000`.
|
||||
|
||||
Snowflake tables are an event history, not a current-state replica:
|
||||
Snowflake tables contain an event history rather than a current-state replica:
|
||||
|
||||
- An insert appends the new row.
|
||||
- An update appends the complete new row. It does not append a before image.
|
||||
- A delete appends the complete old row for `REPLICA IDENTITY FULL`. For a primary-key or `USING INDEX` identity, it sends only the identity columns. Other columns can contain destination defaults or `NULL`; do not treat them as the deleted row's original values.
|
||||
- A source `TRUNCATE` truncates the Snowflake table, resets its streaming state, and does not append a truncate event.
|
||||
- Truncating the source table also truncates the Snowflake table and resets its streaming state. It does not append a truncate event.
|
||||
|
||||
The sequence number orders changes but is not a globally unique event ID. Snowpipe committed offsets suppress routine replay; consumers must still tolerate [duplicate processing](/docs/guides/database/replication/pipelines-faq#can-data-be-processed-more-than-once).
|
||||
The sequence number orders changes but is not a globally unique event ID. Snowpipe committed offsets suppress routine replay, but consumers must still tolerate [duplicate processing](/docs/guides/database/replication/pipelines-faq#can-data-be-processed-more-than-once).
|
||||
|
||||
A [table restart](/docs/guides/database/replication/pipelines-monitoring#restarting-tables) drops the Snowflake table and managed streaming state, erasing its history. It cannot recover past events. [Removing a table from the publication](/docs/guides/database/replication/pipelines#removing-tables-from-replication) leaves its destination history in place.
|
||||
[Restarting a table](/docs/guides/database/replication/pipelines-monitoring#restarting-tables) drops the Snowflake table and its managed streaming state, which erases the replicated history. A restart cannot recover past events. By contrast, [removing a table from the publication](/docs/guides/database/replication/pipelines#removing-tables-from-replication) leaves its destination history in place.
|
||||
|
||||
## Query replicated data [#query-and-materialize-current-state]
|
||||
|
||||
Use the replicated change history to build a current-state dataset for reports and analytics. Pipelines maintains the history table. You create and maintain the queries, views, or dynamic tables that read it.
|
||||
Use the replicated change history to build current-state datasets for reporting and analytics. Pipelines maintains the history table, while you maintain the queries, views, or dynamic tables that read from it.
|
||||
|
||||
| Approach | When to use it | Tradeoff |
|
||||
| -------------------------------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
||||
@@ -183,17 +189,17 @@ Use the replicated change history to build a current-state dataset for reports a
|
||||
|
||||
### Before you start
|
||||
|
||||
The examples use `public.orders`, replicated to `PIPELINES_DB.REPLICATED.PUBLIC_ORDERS`, with source columns `id` and `status`. Replace these names with your own. Wait for the table's initial sync to finish before treating the result as a complete replica.
|
||||
The examples use a source table named `public.orders` with the columns `id` and `status`. Pipelines replicates it to `PIPELINES_DB.REPLICATED.PUBLIC_ORDERS`. Replace these names with your own, and wait for the initial sync to finish before treating the results as a complete replica.
|
||||
|
||||
Choose a unique, non-null identity that stays the same when a row is updated. The examples use `id`. For a composite key, include every key column in `partition by`, such as `partition by "tenant_id", "id"`. Include those columns in the publication and in delete events. `REPLICA IDENTITY FULL` alone does not make rows unique.
|
||||
Choose a unique, non-null identity that does not change when a row is updated. The examples use `id`. If you use a composite key, include every key column in `partition by`, such as `partition by "tenant_id", "id"`. The publication and delete events must include the same columns. `REPLICA IDENTITY FULL` alone does not make rows unique.
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
Changing an identity column can leave the old identity in these results. Pipelines appends the new row for an update without a delete for the previous identity. Use an immutable key for this pattern.
|
||||
If an identity value changes, the old identity can remain in the results. Pipelines appends the updated row without first appending a delete for the previous identity. Use an immutable key for this pattern.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Use a separate analytics role and warehouse, with a schema outside the Pipelines-managed `REPLICATED` schema for derived objects. The examples use `ANALYTICS_ROLE`, `ANALYTICS_WH`, and `PIPELINES_DB.ANALYTICS`. Ask your Snowflake administrator to prepare these resources and grant the analytics role:
|
||||
Create derived objects in a schema outside the Pipelines-managed `REPLICATED` schema, using a separate analytics role and warehouse. The examples use `ANALYTICS_ROLE`, `ANALYTICS_WH`, and `PIPELINES_DB.ANALYTICS`. Ask your Snowflake administrator to prepare these resources and grant the analytics role:
|
||||
|
||||
- `USAGE` on the warehouse, database, and both schemas.
|
||||
- `SELECT` on the replicated table.
|
||||
@@ -217,9 +223,9 @@ qualify row_number() over (
|
||||
and "_cdc_operation" != 'delete';
|
||||
```
|
||||
|
||||
The result contains one row per identity whose latest operation is not `delete`. Ordering by the fixed-width sequence string selects the latest change. Repeated copies of the same event produce one result row. Keep the double quotes around source and metadata column names because Pipelines creates them as case-sensitive identifiers.
|
||||
The query returns one row for each identity whose latest operation is not `delete`. The fixed-width sequence string determines which change is the latest, and repeated copies of the same event collapse into one result row. Keep the double quotes around source and metadata column names because Pipelines creates them as case-sensitive identifiers.
|
||||
|
||||
Keep the delete condition in `qualify`. A `where "_cdc_operation" != 'delete'` condition would remove delete events before ranking and could bring back an older row. Snowflake's [`QUALIFY` reference](https://docs.snowflake.com/en/sql-reference/constructs/qualify) explains this evaluation order.
|
||||
Keep the delete condition inside `qualify`. Moving it to `where "_cdc_operation" != 'delete'` would remove delete events before the rows are ranked, which could bring back an older version of a deleted row. Snowflake's [`QUALIFY` reference](https://docs.snowflake.com/en/sql-reference/constructs/qualify) explains this evaluation order.
|
||||
|
||||
To reuse the query from an analytics tool, save it as a view:
|
||||
|
||||
@@ -233,11 +239,11 @@ qualify row_number() over (
|
||||
and "_cdc_operation" != 'delete';
|
||||
```
|
||||
|
||||
A regular view stores the query definition, not a separate copy of its results. Each read derives current state from the history available to that query. See Snowflake's [comparison of views and dynamic tables](https://docs.snowflake.com/en/user-guide/overview-view-mview-dts).
|
||||
A regular view stores the query definition rather than a separate copy of the results. Each read derives the current state from the history available at query time. See Snowflake's [comparison of views and dynamic tables](https://docs.snowflake.com/en/user-guide/overview-view-mview-dts).
|
||||
|
||||
### Materialize with a dynamic table
|
||||
|
||||
A dynamic table stores the query result and refreshes it as the replicated history changes. Use it when you want to query a maintained current-state dataset without defining a scheduled merge task.
|
||||
A dynamic table stores the query results and refreshes them as the replicated history changes. Use one when you need a maintained current-state dataset without maintaining a scheduled merge task.
|
||||
|
||||
1. Ask the owner of the replicated table to enable change tracking in Snowflake. This is a table setting, not a change to the replicated columns or data. Run as `PIPELINES_ROLE`, or another role that inherits ownership:
|
||||
|
||||
@@ -282,15 +288,15 @@ A dynamic table stores the query result and refreshes it as the replicated histo
|
||||
|
||||
Confirm that `refresh_mode` is `INCREMENTAL` and scheduling is running. Use [Snowflake's refresh monitoring](https://docs.snowflake.com/en/user-guide/dynamic-tables/monitoring) to check the last successful refresh and any errors. After an insert, update, or delete reaches the replicated table, the next successful refresh reflects it in `ORDERS_CURRENT`.
|
||||
|
||||
The five-minute `target_lag` is an example freshness target relative to the history in Snowflake. It is not a fixed refresh schedule or an end-to-end latency guarantee from Postgres. Pipeline replication lag and dynamic-table refresh lag both affect freshness. See Snowflake's [target lag guide](https://docs.snowflake.com/en/user-guide/dynamic-tables/target-lag).
|
||||
The five-minute `target_lag` is an example freshness target relative to the history already in Snowflake. It is neither a fixed refresh schedule nor an end-to-end latency guarantee from Postgres. Both pipeline replication lag and dynamic-table refresh lag affect freshness. See Snowflake's [target lag guide](https://docs.snowflake.com/en/user-guide/dynamic-tables/target-lag).
|
||||
|
||||
Dynamic-table refreshes consume warehouse compute, and the materialized results consume storage. These costs are additional to ingestion and querying. Start with a freshness target that meets your reporting needs and measure a representative workload. A dedicated warehouse helps isolate refresh costs. See Snowflake's [dynamic table cost guide](https://docs.snowflake.com/en/user-guide/dynamic-tables/cost).
|
||||
Refreshing a dynamic table consumes warehouse compute, while its materialized results consume storage. These costs are additional to ingestion and querying. Start with a freshness target that meets your reporting needs, then test its cost and refresh behavior with a representative workload. A dedicated warehouse can help isolate refresh costs. See Snowflake's [dynamic table cost guide](https://docs.snowflake.com/en/user-guide/dynamic-tables/cost).
|
||||
|
||||
### Use streams and tasks
|
||||
|
||||
Snowflake [streams and tasks](https://docs.snowflake.com/en/user-guide/data-pipelines-intro) can maintain a separate table with scheduled `MERGE` statements. Use this option when you need control over the update procedure or schedule. Snowflake's [SCD Type 1 examples](https://docs.snowflake.com/en/user-guide/dynamic-tables/migrate-streams-tasks#scd-type-1-upsert) compare this approach with dynamic tables.
|
||||
Snowflake [streams and tasks](https://docs.snowflake.com/en/user-guide/data-pipelines-intro) can maintain a separate table through scheduled `MERGE` statements. Use them when you need more control over the update procedure or schedule. Snowflake's [SCD Type 1 examples](https://docs.snowflake.com/en/user-guide/dynamic-tables/migrate-streams-tasks#scd-type-1-upsert) compare this approach with dynamic tables.
|
||||
|
||||
Adapt the merge to Pipelines' `"_cdc_operation"` and `"_cdc_sequence_number"` columns. A stream on the history table sees appended rows, including rows representing source updates and deletes. Your job must interpret those operations, load existing history, tolerate replay, and rebuild current state after a source truncate or pipeline table reset.
|
||||
Adapt the merge to the `"_cdc_operation"` and `"_cdc_sequence_number"` columns created by Pipelines. A stream on the history table sees every appended row, including rows that represent source updates and deletes. Your job must interpret those operations, load the existing history, tolerate replay, and rebuild the current state after a source truncate or pipeline table reset.
|
||||
|
||||
### Maintain derived objects
|
||||
|
||||
@@ -320,7 +326,7 @@ Pipelines creates Snowflake columns with these mappings:
|
||||
| `oid` | `BIGINT` |
|
||||
| Other types | `VARCHAR` |
|
||||
|
||||
Pipelines uses `VARCHAR` for character and text types, `numeric`, `time with time zone`, `interval`, `uuid`, `bytea`, bit strings, and custom or unknown types. `bytea` values are lowercase hexadecimal strings. Pipelines stores these values in serialized form, not as native Snowflake types.
|
||||
Pipelines maps character and text types, `numeric`, `time with time zone`, `interval`, `uuid`, `bytea`, bit strings, and custom or unknown types to `VARCHAR`. It serializes these values instead of storing them as native Snowflake types. For `bytea`, the serialized value is a lowercase hexadecimal string.
|
||||
|
||||
Additional limits apply:
|
||||
|
||||
@@ -336,9 +342,9 @@ Pipelines supports:
|
||||
- Adding, renaming, or dropping columns
|
||||
- Adding or removing published columns on tracked tables
|
||||
|
||||
Replicated columns remain nullable in Snowflake, and changes to existing column defaults are not propagated. Initial table creation can copy compatible literal defaults. Columns added in Postgres can copy string, numeric, or boolean literal defaults; other defaults are omitted. Postgres still supplies the source values through replication.
|
||||
Replicated columns remain nullable in Snowflake, and changes to existing column defaults are not propagated. When a table is first created, Pipelines can copy compatible literal defaults. It can also copy string, numeric, or boolean literal defaults for columns added later in Postgres. Other defaults are omitted, although Postgres still supplies the source values through replication.
|
||||
|
||||
Schema changes also affect stored history: renaming a column changes its name in old events, dropping it removes its historical values, and adding one with a default can populate older rows.
|
||||
Schema changes also affect the stored history. Renaming a column changes its name in earlier events, dropping a column removes its historical values, and adding a column with a default can populate older rows.
|
||||
|
||||
Previously excluded columns are added without defaults, leaving historical events `NULL` for those columns. Removing a published column drops its destination values; adding it again does not restore them.
|
||||
|
||||
|
||||
@@ -161,10 +161,12 @@ You need a _new_ project for staging. A project which has already been modified
|
||||
|
||||
The Supabase CLI requires a few environment variables to run in non-interactive mode.
|
||||
|
||||
- `SUPABASE_ACCESS_TOKEN` is your personal access token
|
||||
- `SUPABASE_ACCESS_TOKEN` is a [scoped personal access token](/docs/guides/platform/personal-access-tokens) limited to the projects this workflow deploys to
|
||||
- `SUPABASE_DB_PASSWORD` is your project specific database password
|
||||
- `SUPABASE_PROJECT_ID` is your project specific reference string
|
||||
|
||||
The workflows below run `supabase link`, so grant the token **Project Settings**, **API Keys**, and **API Key Secrets**, all with **Read** access.
|
||||
|
||||
We recommend adding these as [encrypted secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets) to your GitHub Actions runners.
|
||||
|
||||
Create the following files inside the `.github/workflows` directory:
|
||||
|
||||
@@ -5,7 +5,7 @@ description: 'How the Authorization and apikey request headers and the verify_jw
|
||||
subtitle: 'How the Authorization and apikey headers and the verify_jwt platform check work'
|
||||
---
|
||||
|
||||
Every request to an Edge Function passes through two layers of auth. First, a platform-level check (`verify_jwt`) runs before your code executes. Then, once the request reaches your handler, you decide what to do with the credentials the caller sent. This page is the reference for both layers. For the practical patterns built on top of them, see [Securing Edge Functions](/guides/functions/auth).
|
||||
Every request to an Edge Function passes through two layers of auth. First, a platform-level check (`verify_jwt`) runs before your code executes. Then, once the request reaches your handler, you decide what to do with the credentials the caller sent. This page is the reference for both layers. For the practical patterns built on top of them, see [Securing Edge Functions](/docs/guides/functions/auth).
|
||||
|
||||
## Understanding authorization headers
|
||||
|
||||
@@ -41,7 +41,7 @@ For migration compatibility, `verify_jwt` accepts publishable and secret keys on
|
||||
Use the `verify_jwt` flag to match how the function is called:
|
||||
|
||||
- **Leave `verify_jwt` on** for functions that are only called with a user JWT, such as functions invoked from the client through `supabase.functions.invoke`. The platform rejects unauthenticated requests before they reach your code, and your handler can trust that a valid JWT is present.
|
||||
- **Turn `verify_jwt` off** for functions that are called without an `Authorization` header, such as webhooks from external providers, or service-to-service calls that authenticate with an API key. These patterns are covered in [Securing Edge Functions](/guides/functions/auth).
|
||||
- **Turn `verify_jwt` off** for functions that are called without an `Authorization` header, such as webhooks from external providers, or service-to-service calls that authenticate with an API key. These patterns are covered in [Securing Edge Functions](/docs/guides/functions/auth).
|
||||
|
||||
Set the flag per function in `supabase/config.toml`:
|
||||
|
||||
|
||||
@@ -5,9 +5,11 @@ description: 'Authentication patterns for Supabase Edge Functions.'
|
||||
subtitle: 'Authentication patterns for Edge Functions'
|
||||
---
|
||||
|
||||
The `withSupabase` wrapper from [`@supabase/server`](https://github.com/supabase/server) verifies the caller's credentials against a declared `auth` mode and hands you a pre-configured Supabase client on `ctx`. The sections below show how to use it for each common auth scenario.
|
||||
Secure an Edge Function by declaring which credentials it accepts. The `withSupabase` wrapper from the [`@supabase/server` package](https://github.com/supabase/server) on GitHub checks each caller against the `auth` mode you set. Your handler receives a preconfigured Supabase client on `ctx`.
|
||||
|
||||
For how authorization headers and the `verify_jwt` platform check work under the hood, see [Authorization headers](/docs/guides/functions/auth-headers).
|
||||
For how authorization headers and the `verify_jwt` platform check work, see [Authorization headers](/docs/guides/functions/auth-headers).
|
||||
|
||||
The wrapper accepts four `auth` modes:
|
||||
|
||||
| Mode | Accepts |
|
||||
| --------------- | ------------------------------------------ |
|
||||
@@ -18,10 +20,10 @@ For how authorization headers and the `verify_jwt` platform check work under the
|
||||
|
||||
## Authenticated user calls
|
||||
|
||||
Functions called by signed-in users — typically through `supabase.functions.invoke` from the client — send the user's session JWT on the `Authorization` header. Keep `verify_jwt = true` (the default) so the platform validates the JWT before your handler runs, then use `auth: 'user'` to get `ctx.supabase` already scoped to the caller's RLS policies.
|
||||
When a signed-in user calls a function, the request carries the user's session JWT on the `Authorization` header. Your app usually makes that call through `supabase.functions.invoke`. The default is `verify_jwt = true`. The platform validates the JWT before your handler runs. Use `auth: 'user'` to get a `ctx.supabase` scoped to the caller's Row Level Security (RLS) policies.
|
||||
|
||||
```ts
|
||||
import { withSupabase } from 'npm:@supabase/server'
|
||||
import { withSupabase } from 'npm:@supabase/server@1'
|
||||
|
||||
export default {
|
||||
fetch: withSupabase({ auth: 'user' }, async (_req, ctx) => {
|
||||
@@ -40,10 +42,10 @@ export default {
|
||||
|
||||
## Service-to-service calls
|
||||
|
||||
Cron jobs, workers, `pg_net`, or another Edge Function make calls with a secret key on the `apikey` header rather than a user JWT. Disable `verify_jwt` and use `auth: 'secret'` to validate the key against any secret key from your [dashboard](/dashboard/project/_/settings/api-keys). You get `ctx.supabaseAdmin` for privileged work.
|
||||
Cron jobs, workers, `pg_net`, and other Edge Functions make calls with a secret key on the `apikey` header rather than a user JWT. Disable `verify_jwt` and use `auth: 'secret'`. The wrapper validates the key against any secret key in your [project's API keys](/dashboard/project/_/settings/api-keys), and gives your handler `ctx.supabaseAdmin` for privileged work.
|
||||
|
||||
```ts
|
||||
import { withSupabase } from 'npm:@supabase/server'
|
||||
import { withSupabase } from 'npm:@supabase/server@1'
|
||||
|
||||
export default {
|
||||
fetch: withSupabase({ auth: 'secret' }, async (_req, ctx) => {
|
||||
@@ -55,15 +57,15 @@ export default {
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
To accept only one specific key, use `auth: 'secret:<name>'`. For example, `auth: 'secret:automations'` only accepts the secret key you named "automations" in the [**Settings > API keys**](/dashboard/project/_/settings/api-keys) section of the Dashboard. The same syntax works for publishable keys (`auth: 'publishable:<name>'`).
|
||||
To accept only one specific key, use `auth: 'secret:<name>'`. For example, `auth: 'secret:automations'` accepts only the secret key named `automations`. To name a key, open [**Settings > API keys**](/dashboard/project/_/settings/api-keys) in the Supabase Dashboard. The same syntax works for publishable keys: `auth: 'publishable:<name>'`.
|
||||
|
||||

|
||||

|
||||
|
||||
</Admonition>
|
||||
|
||||
## Public functions
|
||||
|
||||
For a genuinely public function, like a health check, use `auth: 'none'` with `verify_jwt = false` so anonymous callers can reach the handler.
|
||||
For a genuinely public function, such as a health check, use `auth: 'none'` with `verify_jwt = false` so anonymous callers can reach the handler.
|
||||
|
||||
```toml
|
||||
[functions.health]
|
||||
@@ -71,7 +73,7 @@ verify_jwt = false
|
||||
```
|
||||
|
||||
```ts
|
||||
import { withSupabase } from 'npm:@supabase/server'
|
||||
import { withSupabase } from 'npm:@supabase/server@1'
|
||||
|
||||
export default {
|
||||
fetch: withSupabase({ auth: 'none' }, async () => {
|
||||
@@ -81,14 +83,14 @@ export default {
|
||||
}
|
||||
```
|
||||
|
||||
`auth: 'none'` skips every credential check — see the caution under [External webhooks](#external-webhooks) before using it on anything that reads or writes sensitive data.
|
||||
`auth: 'none'` accepts every caller. For a function that authenticates callers itself, see the [External webhooks](#external-webhooks) section of this page.
|
||||
|
||||
## External webhooks
|
||||
|
||||
External providers like Stripe or GitHub don't send Supabase credentials. They sign the request body with their own shared secret. Use `auth: 'none'` to skip the SDK's credential check, then verify the provider's signature inside the handler. Keep `verify_jwt = false`.
|
||||
External providers such as Stripe or GitHub don't send Supabase credentials. They sign the request body with their own shared secret. Use `auth: 'none'` to skip the wrapper's credential check, then verify the provider's signature inside the handler. Keep `verify_jwt = false`.
|
||||
|
||||
```ts
|
||||
import { withSupabase } from 'npm:@supabase/server'
|
||||
import { withSupabase } from 'npm:@supabase/server@1'
|
||||
import Stripe from 'npm:stripe'
|
||||
|
||||
const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!)
|
||||
@@ -117,24 +119,24 @@ export default {
|
||||
return new Response('bad signature', { status: 400 })
|
||||
}
|
||||
|
||||
// your business logic. ctx.supabaseAdmin available for db work
|
||||
// your business logic. ctx.supabaseAdmin available for database work
|
||||
return Response.json({ received: true })
|
||||
}),
|
||||
}
|
||||
```
|
||||
|
||||
<Admonition type="caution">
|
||||
<Admonition type="danger">
|
||||
|
||||
`auth: 'none'` disables every credential check. Your handler is fully responsible for authenticating the caller. Never use it on an endpoint that reads or writes sensitive data without verifying the caller some other way.
|
||||
`auth: 'none'` disables every credential check, so your handler is fully responsible for authenticating the caller. When the function reads or writes sensitive data, verify the caller yourself inside the handler.
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Combining modes
|
||||
|
||||
Functions that answer both users and internal callers take an array on `auth`. Modes are tried in order. The first match wins, and `ctx.authMode` tells you which matched.
|
||||
Functions that answer both users and internal callers take an array on `auth`. The wrapper tries each mode in order and uses the first one that matches. `ctx.authMode` tells you which mode matched.
|
||||
|
||||
```ts
|
||||
import { withSupabase } from 'npm:@supabase/server'
|
||||
import { withSupabase } from 'npm:@supabase/server@1'
|
||||
|
||||
export default {
|
||||
fetch: withSupabase({ auth: ['user', 'secret'] }, async (req, ctx) => {
|
||||
@@ -151,10 +153,10 @@ export default {
|
||||
|
||||
## Custom error responses
|
||||
|
||||
To shape the 401 response yourself, use `createSupabaseContext` instead of `withSupabase`. It returns a `{ data, error }` tuple so you stay in control.
|
||||
To shape the 401 response yourself, use `createSupabaseContext` instead of `withSupabase`. It returns a `{ data, error }` tuple instead of rejecting the request for you.
|
||||
|
||||
```ts
|
||||
import { createSupabaseContext } from 'npm:@supabase/server'
|
||||
import { createSupabaseContext } from 'npm:@supabase/server@1'
|
||||
|
||||
export default {
|
||||
fetch: async (req: Request) => {
|
||||
@@ -169,7 +171,7 @@ export default {
|
||||
|
||||
## Environment variables
|
||||
|
||||
`@supabase/server` reads its configuration from a standard set of environment variables. On the Supabase platform and in local development with the CLI, these are auto-provisioned.
|
||||
`@supabase/server` reads its configuration from a standard set of environment variables, which the Supabase Platform and the Supabase CLI provision for you.
|
||||
|
||||
| Variable | What it is |
|
||||
| --------------------------- | ----------------------------------------- |
|
||||
@@ -178,10 +180,10 @@ export default {
|
||||
| `SUPABASE_SECRET_KEYS` | Named secret keys as a JSON object |
|
||||
| `SUPABASE_JWKS` | JSON Web Key Set used to verify user JWTs |
|
||||
|
||||
Local development with the CLI uses a single-key setup, which the SDK also accepts as a fallback: `SUPABASE_PUBLISHABLE_KEY` and `SUPABASE_SECRET_KEY`.
|
||||
Local development with the CLI uses a single-key setup. `@supabase/server` also accepts `SUPABASE_PUBLISHABLE_KEY` and `SUPABASE_SECRET_KEY` as a fallback.
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
The same zero-config experience is available on other runtimes. Install [`@supabase/server`](https://github.com/supabase/server) in your Node.js, Bun, Cloudflare Workers, or self-hosted Deno app and set the environment variables above. See the package's [environment variables guide](https://github.com/supabase/server/blob/main/docs/environment-variables.md) for the full reference.
|
||||
`@supabase/server` configures itself the same way on other runtimes. Install it in your Node.js, Bun, Cloudflare Workers, or self-hosted Deno app, then set the same variables. For the full reference, see the [environment variables guide](https://github.com/supabase/server/blob/main/docs/environment-variables.md) in the `@supabase/server` repository.
|
||||
|
||||
</Admonition>
|
||||
@@ -174,7 +174,7 @@ export default {
|
||||
## Authentication Errors
|
||||
|
||||
These errors occur when the request contains a missing, malformed, or unsupported JWT token. Fixing them requires ensuring your requests include a valid authorization header, or disabling JWT verification for public endpoints.
|
||||
For further information, see [Authorization headers](/docs/guides/functions/auth-headers) and [Securing Edge Functions](/guides/functions/auth).
|
||||
For further information, see [Authorization headers](/docs/guides/functions/auth-headers) and [Securing Edge Functions](/docs/guides/functions/auth).
|
||||
|
||||
### UNAUTHORIZED_NO_AUTH_HEADER
|
||||
|
||||
|
||||
@@ -278,7 +278,7 @@ When deploying MCP servers:
|
||||
- **Limit tool scope**: Only expose tools that are necessary for your use case
|
||||
- **Monitor usage**: Track tool calls and monitor for unusual activity
|
||||
|
||||
For more security guidance, see the [MCP security guide](/guides/getting-started/mcp#security-risks).
|
||||
For more security guidance, see the [MCP security guide](/docs/guides/getting-started/mcp#security-risks).
|
||||
|
||||
## What's next
|
||||
|
||||
@@ -295,6 +295,6 @@ With your MCP server running on Supabase Edge Functions, you can:
|
||||
|
||||
- [mcp-lite on GitHub](https://github.com/fiberplane/mcp-lite)
|
||||
- [Model Context Protocol Spec](https://modelcontextprotocol.io/)
|
||||
- [Supabase Edge Functions Docs](/guides/functions)
|
||||
- [Supabase Edge Functions Docs](/docs/guides/functions)
|
||||
- [Deno Runtime Documentation](https://deno.land/)
|
||||
- [Fiberplane tutorial](https://blog.fiberplane.com/blog/mcp-lite-supabase-edge-functions/)
|
||||
@@ -271,7 +271,7 @@ Push notifications are an important part of any mobile app. They allow you to se
|
||||
|
||||
## Create the database webhook
|
||||
|
||||
Navigate to the [Database Webhooks settings](/dashboard/project/_/database/hooks) in your Supabase Dashboard.
|
||||
Navigate to the [Database Webhooks settings](/dashboard/project/_/integrations/webhooks/overview) in your Supabase Dashboard.
|
||||
|
||||
1. Enable and create a new hook.
|
||||
1. Conditions to fire webhook: Select the `public.notifications` table and tick the `Insert` event.
|
||||
|
||||
@@ -25,7 +25,7 @@ subtitle: "Limits applied Edge Functions in Supabase's hosted platform."
|
||||
- Enterprise: Unlimited
|
||||
- Maximum log message length: 10,000 characters
|
||||
- Log event threshold: 100 events per 10 seconds
|
||||
- Recursive/Nested Function Calling: ~5000 requests per minute [more details](/docs/guides/functions/recursive-functions)
|
||||
- Recursive/Nested Function Calling: 30 requests per trace, within a 60-second window [more details](/docs/guides/functions/recursive-functions)
|
||||
|
||||
### Secrets
|
||||
|
||||
|
||||
@@ -24,9 +24,15 @@ Inbound requests to your Edge Functions and requests to external APIs (e.g., Str
|
||||
|
||||
## Rate limit budget
|
||||
|
||||
Each request chain has a budget of at least **5,000 requests per minute**. In busier regions, this budget may be higher. All function-to-function calls within the same request chain share this budget.
|
||||
Each request chain (or _trace_) has a budget of **30 requests within a 60-second window**. All function-to-function calls that share the same trace count toward this budget.
|
||||
|
||||
For example, if Function A calls Function B, and Function B calls Function C, all three calls count toward the same budget pool.
|
||||
For example, if Function A calls Function B, and Function B calls Function C, all three calls count toward the same 30-request budget.
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
A trace is the chain of calls that starts when one Edge Function invokes another. The budget applies per trace, not per project, so two separate invocations of the same function each get their own budget.
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Handling rate limit errors
|
||||
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
---
|
||||
id: 'functions-secrets'
|
||||
title: 'Environment variables'
|
||||
description: 'Managing secrets and environment variables.'
|
||||
subtitle: 'Manage sensitive data securely across environments.'
|
||||
description: 'Set, read, and deploy Edge Function secrets.'
|
||||
subtitle: 'Store API keys and other secrets where your Edge Functions can read them.'
|
||||
---
|
||||
|
||||
Store API keys and other secrets where your Edge Functions can read them. Local development and production load them differently, so set them in both.
|
||||
Local development and production load secrets differently, so set them in both.
|
||||
|
||||
- [Local secrets](#local-secrets) and [Production secrets](#production-secrets) have the steps for each environment.
|
||||
- [Accessing environment variables](#accessing-environment-variables) shows how to read a secret from your function code. [When your function can't read a secret](#when-your-function-cant-read-a-secret) covers the local case where nothing arrives.
|
||||
- [Reference](#reference) explains which file feeds which runtime, and lists the variables Supabase injects for you.
|
||||
|
||||
## Local secrets
|
||||
|
||||
In development, Edge Functions read secrets from `supabase/functions/.env`, which is automatically loaded on `supabase start`. Create the file before you start the stack.
|
||||
Locally, Edge Functions read secrets from `supabase/functions/.env`. The local stack loads that file on `supabase start`.
|
||||
|
||||
1. Create `supabase/functions/.env` and add each secret with the value you want the function to read. A `.env.example` template isn't enough on its own, because the runtime reads the values rather than the variable names.
|
||||
1. Create `supabase/functions/.env` and add each secret with the value you want the function to read. A `.env.example` template isn't enough, because the runtime reads the values rather than the variable names.
|
||||
|
||||
```bash
|
||||
# supabase/functions/.env
|
||||
@@ -32,7 +36,7 @@ In development, Edge Functions read secrets from `supabase/functions/.env`, whic
|
||||
supabase functions new hello-world
|
||||
```
|
||||
|
||||
```tsx
|
||||
```ts
|
||||
// supabase/functions/hello-world/index.ts
|
||||
Deno.serve(() => {
|
||||
const secretKey = Deno.env.get('STRIPE_SECRET_KEY')
|
||||
@@ -46,7 +50,7 @@ In development, Edge Functions read secrets from `supabase/functions/.env`, whic
|
||||
supabase start
|
||||
```
|
||||
|
||||
5. Call the function. A `configured` of `true` means the runtime handed it the secret.
|
||||
5. Call the function. When `configured` comes back `true`, the runtime handed the secret to your function.
|
||||
|
||||
```bash
|
||||
curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/hello-world' \
|
||||
@@ -57,9 +61,71 @@ Your function now reads the secret from your local environment.
|
||||
|
||||
---
|
||||
|
||||
## Production secrets
|
||||
|
||||
Set secrets for your production Edge Functions in the Supabase Dashboard or with the Supabase CLI.
|
||||
|
||||
Creating or deleting a production secret requires the Owner or Administrator role. Developers can view secrets but not change them. See [Access control](/docs/guides/platform/access-control#edge-config-permissions) for the full matrix.
|
||||
|
||||
A secret name can't start with `SUPABASE_`. That prefix is reserved for the variables Supabase injects, and both the Dashboard and the Management API reject it.
|
||||
|
||||
### Using the Dashboard
|
||||
|
||||
1. Open [Edge Function Secrets](/dashboard/project/_/functions/secrets) in the Dashboard.
|
||||
2. Enter the **Key** and **Value** for your secret, then click **Save**.
|
||||
|
||||
<Image
|
||||
alt='The Edge Function Secrets page in the Supabase Dashboard. An Add new secrets card holds a Key field whose placeholder reads "e.g. CLIENT_KEY" and a Value field with a reveal toggle and a remove button, above an Add another button and a Save button.'
|
||||
src={{
|
||||
light: '/docs/img/edge-functions-secrets--light.jpg',
|
||||
dark: '/docs/img/edge-functions-secrets.jpg',
|
||||
}}
|
||||
width={3757}
|
||||
height={1525}
|
||||
/>
|
||||
|
||||
You can paste multiple secrets at once.
|
||||
|
||||
### Using the CLI
|
||||
|
||||
1. Create a `.env` file with the secrets you want to deploy, and add it to your `.gitignore` before you commit.
|
||||
|
||||
```bash
|
||||
# .env
|
||||
STRIPE_SECRET_KEY=sk_live_...
|
||||
```
|
||||
|
||||
2. Push every secret in the file to your remote project. The command also makes them visible in the Dashboard.
|
||||
|
||||
```bash
|
||||
supabase secrets set --env-file .env
|
||||
```
|
||||
|
||||
`supabase secrets set` also sets production secrets individually, without a `.env` file.
|
||||
|
||||
```bash
|
||||
supabase secrets set STRIPE_SECRET_KEY=sk_live_...
|
||||
```
|
||||
|
||||
List the secrets set on your remote project:
|
||||
|
||||
```bash
|
||||
supabase secrets list
|
||||
```
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
Your functions read a new secret immediately, so you don't need to redeploy.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Your deployed functions can now read the secret.
|
||||
|
||||
---
|
||||
|
||||
## Accessing environment variables
|
||||
|
||||
Access an environment variable with the `Deno.env.get` method, passing the name of the variable you want.
|
||||
Read an environment variable with `Deno.env.get`, passing the name of the variable.
|
||||
|
||||
```js
|
||||
Deno.env.get('NAME_OF_SECRET')
|
||||
@@ -67,12 +133,14 @@ Deno.env.get('NAME_OF_SECRET')
|
||||
|
||||
### In an Edge Function
|
||||
|
||||
Inside an Edge Function, the Supabase keys are already in the environment. Read them and pass them to `createClient`:
|
||||
|
||||
```ts
|
||||
import { createClient } from 'npm:@supabase/supabase-js@2'
|
||||
|
||||
const SUPABASE_PUBLISHABLE_KEYS = JSON.parse(Deno.env.get('SUPABASE_PUBLISHABLE_KEYS')!)
|
||||
|
||||
// For user-facing operations (respects RLS)
|
||||
// For user-facing operations (respects Row Level Security)
|
||||
const supabase = createClient(
|
||||
Deno.env.get('SUPABASE_URL')!,
|
||||
// To use a different API key, change 'default' to your preferred key name
|
||||
@@ -80,7 +148,7 @@ const supabase = createClient(
|
||||
)
|
||||
|
||||
const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)
|
||||
// For admin operations (bypasses RLS)
|
||||
// For admin operations (bypasses Row Level Security)
|
||||
const supabaseAdmin = createClient(
|
||||
Deno.env.get('SUPABASE_URL')!,
|
||||
// To use a different API key, change 'default' to your preferred key name
|
||||
@@ -90,7 +158,7 @@ const supabaseAdmin = createClient(
|
||||
|
||||
### In a Deno script
|
||||
|
||||
A Deno script you run yourself, outside `supabase functions serve`, doesn't read `supabase/functions/.env`. Pass the file, and grant the script access to the environment with `--allow-env`:
|
||||
A Deno script you run yourself, outside `supabase functions serve`, doesn't read `supabase/functions/.env`. Pass the file with `--env-file`, and grant the script access to environment variables with `--allow-env`:
|
||||
|
||||
```bash
|
||||
deno run --allow-env --env-file=supabase/functions/.env script.ts
|
||||
@@ -106,7 +174,7 @@ STRIPE_SECRET_KEY=sk_test_... deno run --allow-env script.ts
|
||||
|
||||
## When your function can't read a secret
|
||||
|
||||
The local runtime loads `supabase/functions/.env` when the stack starts, so a function that returns nothing for a variable usually means the value never reached it.
|
||||
A variable that comes back empty usually means the value never reached the runtime.
|
||||
|
||||
Restart the stack, or serve the function with the file passed explicitly:
|
||||
|
||||
@@ -114,83 +182,21 @@ Restart the stack, or serve the function with the file passed explicitly:
|
||||
supabase functions serve hello-world --env-file supabase/functions/.env
|
||||
```
|
||||
|
||||
To keep a separate file per environment, name your own and pass it the same way:
|
||||
|
||||
```bash
|
||||
supabase functions serve --env-file .env.local
|
||||
```
|
||||
If the value still doesn't arrive, confirm you edited the file your runtime reads.
|
||||
|
||||
---
|
||||
|
||||
## Production secrets
|
||||
## Reference
|
||||
|
||||
Set secrets for your production Edge Functions in the Dashboard or with the CLI.
|
||||
Look up which file feeds which runtime, and which variables Supabase injects for you.
|
||||
|
||||
Creating or deleting a production secret requires the Owner or Administrator role. Developers can view secrets but not change them. See [Access control](/docs/guides/platform/access-control#edge-config-permissions) for the full matrix.
|
||||
|
||||
A secret name can't start with `SUPABASE_`. That prefix is reserved for the variables Supabase injects, and both the Dashboard and the Management API reject it.
|
||||
|
||||
### Using the Dashboard
|
||||
|
||||
1. Open [Edge Function Secrets](/dashboard/project/_/functions/secrets) in the Dashboard.
|
||||
2. Enter the **Key** and **Value** for your secret, then click **Save**.
|
||||
|
||||
<Image
|
||||
alt="The Edge Function Secrets page in the Supabase Dashboard. An Add new secrets card holds a Key field hinting e.g. CLIENT_KEY and a Value field with a reveal toggle and a remove button, above an Add another button and a Save button."
|
||||
src={{
|
||||
light: '/docs/img/edge-functions-secrets--light.jpg',
|
||||
dark: '/docs/img/edge-functions-secrets.jpg',
|
||||
}}
|
||||
width={3757}
|
||||
height={1525}
|
||||
/>
|
||||
|
||||
You can paste multiple secrets at once.
|
||||
|
||||
### Using the CLI
|
||||
|
||||
Create a `.env` file with the secrets you want to deploy. Add it to your `.gitignore` before you commit.
|
||||
|
||||
```bash
|
||||
# .env
|
||||
STRIPE_SECRET_KEY=sk_live_...
|
||||
```
|
||||
|
||||
Push every secret in the file to your remote project with `supabase secrets set`, which also makes them visible in the Dashboard.
|
||||
|
||||
```bash
|
||||
supabase secrets set --env-file .env
|
||||
```
|
||||
|
||||
This command also sets production secrets individually, without a `.env` file.
|
||||
|
||||
```bash
|
||||
supabase secrets set STRIPE_SECRET_KEY=sk_live_...
|
||||
```
|
||||
|
||||
To see the secrets you have set remotely, use `supabase secrets list`.
|
||||
|
||||
```bash
|
||||
supabase secrets list
|
||||
```
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
Secrets are available in your functions immediately. You don't need to redeploy after setting them.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Your deployed functions can now read the secret.
|
||||
|
||||
---
|
||||
|
||||
## Where local values come from
|
||||
### Where local values come from
|
||||
|
||||
A project can hold more than one file that feeds local environment variables, and they aren't interchangeable:
|
||||
|
||||
- `supabase/functions/.env` is the one your Edge Functions read, loaded when the stack starts.
|
||||
- A file you name yourself, such as `.env.local`, passed to `supabase functions serve` with `--env-file`.
|
||||
- A `.env` at the root of your project is the one `config.toml` reads, through its `env()` function. See [Using secrets inside config.toml](/docs/guides/local-development/managing-config#using-secrets-inside-configtoml). A variable your function needs has to be in `supabase/functions/.env` too, even when the same value is already in the root file.
|
||||
- A file you name yourself, such as `.env.local`, is read only when you pass it to `supabase functions serve` with `--env-file`.
|
||||
- A `.env` at the root of your project is the one `config.toml` reads, through its `env()` function. See [Using secrets inside config.toml](/docs/guides/local-development/managing-config#using-secrets-inside-configtoml). A variable your function needs also has to be in `supabase/functions/.env`, even when the root file already holds the same value.
|
||||
|
||||
You can also set local values in `config.toml` itself, under `[edge_runtime.secrets]`:
|
||||
|
||||
@@ -199,25 +205,29 @@ You can also set local values in `config.toml` itself, under `[edge_runtime.secr
|
||||
STRIPE_SECRET_KEY = "env(STRIPE_SECRET_KEY)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Default secrets
|
||||
### Default secrets
|
||||
|
||||
Alongside the secrets you set yourself, Edge Functions have access to these by default:
|
||||
|
||||
- `SUPABASE_URL`: The API gateway for your Supabase project
|
||||
- `SUPABASE_DB_URL`: The URL for your Postgres database. Use it to connect directly to your database
|
||||
- `SUPABASE_PUBLISHABLE_KEYS`: The `publishable` keys JSON dictionary for your Supabase API. This is safe to use in a browser when you have Row Level Security enabled
|
||||
- `SUPABASE_SECRET_KEYS`: The `secret` keys JSON dictionary for your Supabase API. This is safe to use in Edge Functions, but **never** use it in a browser. These keys bypass Row Level Security
|
||||
- `SUPABASE_JWKS`: The JSON Web Key Set used to verify user JWTs. Same value served at `https://<project-ref>.supabase.co/auth/v1/.well-known/jwks.json`
|
||||
| Variable | Description |
|
||||
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `SUPABASE_URL` | The API gateway for your Supabase project. |
|
||||
| `SUPABASE_DB_URL` | The URL for your Postgres database. Use it to connect directly to your database. |
|
||||
| `SUPABASE_PUBLISHABLE_KEYS` | The `publishable` keys JSON dictionary for your Supabase API. Safe to use in a browser when you have Row Level Security enabled. |
|
||||
| `SUPABASE_SECRET_KEYS` | The `secret` keys JSON dictionary for your Supabase API. These keys bypass Row Level Security, so use them in Edge Functions and **never** in a browser. |
|
||||
| `SUPABASE_JWKS` | The JSON Web Key Set used to verify user JWTs. Same value served at `https://<project-ref>.supabase.co/auth/v1/.well-known/jwks.json`. |
|
||||
|
||||
Legacy keys:
|
||||
|
||||
- `SUPABASE_ANON_KEY`: The `anon` key for your Supabase API. This is safe to use in a browser when you have Row Level Security enabled
|
||||
- `SUPABASE_SERVICE_ROLE_KEY`: The `service_role` key for your Supabase API. This is safe to use in Edge Functions, but **never** use it in a browser. This key bypasses Row Level Security
|
||||
| Variable | Description |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `SUPABASE_ANON_KEY` | The `anon` key for your Supabase API. Safe to use in a browser when you have Row Level Security enabled. |
|
||||
| `SUPABASE_SERVICE_ROLE_KEY` | The `service_role` key for your Supabase API. This key bypasses Row Level Security, so use it in Edge Functions and **never** in a browser. |
|
||||
|
||||
In a hosted environment, functions have access to the following environment variables:
|
||||
In a hosted environment, functions also have access to these variables:
|
||||
|
||||
- `SB_REGION`: The region the function was invoked in
|
||||
- `SB_EXECUTION_ID`: A UUID for the function instance, or [isolate](/docs/guides/functions/architecture#4-execution-mechanics-fast-and-isolated)
|
||||
- `DENO_DEPLOYMENT_ID`: The version of the function code, formatted as `{project_ref}_{function_id}_{version}`
|
||||
| Variable | Description |
|
||||
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `SB_REGION` | The region the function was invoked in. |
|
||||
| `SB_EXECUTION_ID` | A UUID for the function instance, or [isolate](/docs/guides/functions/architecture#4-execution-mechanics-fast-and-isolated). |
|
||||
| `DENO_DEPLOYMENT_ID` | The version of the function code, formatted as `{project_ref}_{function_id}_{version}`. |
|
||||
@@ -176,7 +176,7 @@ Pass the branch's own project ref to read the keys for a preview branch. A branc
|
||||
|
||||
Use the Management API to fetch keys from your own tooling, such as a deploy script or an internal provisioning service.
|
||||
|
||||
Authenticate with a [personal access token](/dashboard/account/tokens). An OAuth application needs the `secrets:read` scope, and a fine-grained token needs the `api_gateway_keys_read` permission. Without either, the request returns 403 Forbidden.
|
||||
Authenticate with a [personal access token](/dashboard/account/tokens). An OAuth application needs the `secrets:read` scope, and a [scoped personal access token](/docs/guides/platform/personal-access-tokens) needs the **API Keys** permission with **Read** access. To reveal key values with `reveal=true`, as the example below does, the scoped token also needs **API Key Secrets** with **Read** access. Without the required scope or permissions, the request returns 403 Forbidden.
|
||||
|
||||
```bash
|
||||
export PROJECT_REF="your-project-ref"
|
||||
@@ -246,7 +246,7 @@ export const supabaseAdmin = createClient(
|
||||
Don't read the key from the environment inside an Edge Function. Use the [`@supabase/server`](/docs/guides/functions/auth) SDK instead. It verifies the caller's secret key for you and hands back a privileged client on `ctx`, so the key never appears in your code.
|
||||
|
||||
```ts supabase/functions/roster/index.ts
|
||||
import { withSupabase } from 'npm:@supabase/server'
|
||||
import { withSupabase } from 'npm:@supabase/server@1'
|
||||
|
||||
export default {
|
||||
fetch: withSupabase({ auth: 'secret' }, async (_req, ctx) => {
|
||||
|
||||
@@ -149,7 +149,7 @@ Wrap your existing `Deno.serve` handler with `withSupabase` and declare an `auth
|
||||
For a function your users call from the client, use `auth: 'user'`. The SDK validates the user's session JWT and gives you a client scoped to their Row Level Security policies.
|
||||
|
||||
```ts
|
||||
import { withSupabase } from 'npm:@supabase/server'
|
||||
import { withSupabase } from 'npm:@supabase/server@1'
|
||||
|
||||
Deno.serve(
|
||||
withSupabase({ auth: 'user' }, async (_req, ctx) => {
|
||||
@@ -162,7 +162,7 @@ Deno.serve(
|
||||
For a function called by your own backend, a worker, or `pg_net`, use `auth: 'secret'`. The SDK validates the secret key and gives you a client that bypasses Row Level Security.
|
||||
|
||||
```ts
|
||||
import { withSupabase } from 'npm:@supabase/server'
|
||||
import { withSupabase } from 'npm:@supabase/server@1'
|
||||
|
||||
Deno.serve(
|
||||
withSupabase({ auth: 'secret' }, async (_req, ctx) => {
|
||||
@@ -177,7 +177,7 @@ To accept a specific named key instead of `default`, add its name after the mode
|
||||
`withSupabase` returns a standard request handler, so you can also export it as a `fetch` handler instead of passing it to `Deno.serve`:
|
||||
|
||||
```ts
|
||||
import { withSupabase } from 'npm:@supabase/server'
|
||||
import { withSupabase } from 'npm:@supabase/server@1'
|
||||
|
||||
export default {
|
||||
fetch: withSupabase({ auth: 'user' }, async (_req, ctx) => {
|
||||
|
||||
@@ -9,10 +9,10 @@ To develop your applications using the locally running Supabase stack, you'll ne
|
||||
|
||||
A container manager compatible with Docker APIs is a prerequisite:
|
||||
|
||||
- [Docker Desktop](https://docs.docker.com/desktop/) (macOS, Windows, Linux) - preferred option
|
||||
- [OrbStack](https://orbstack.dev/) (macOS) - recommended on macOS
|
||||
- [Docker Desktop](https://docs.docker.com/desktop/) (macOS, Windows, Linux) - recommended on Windows and Linux
|
||||
- [Rancher Desktop](https://rancherdesktop.io/) (macOS, Windows, Linux)
|
||||
- [Podman](https://podman.io/) (macOS, Windows, Linux)
|
||||
- [OrbStack](https://orbstack.dev/) (macOS)
|
||||
|
||||
</Admonition>
|
||||
|
||||
|
||||
@@ -247,13 +247,14 @@ supabase stop --no-backup
|
||||
|
||||
## Running a local Supabase project
|
||||
|
||||
The most common thing you'll do with the CLI is run the full Supabase stack (Postgres, Auth, Storage, and the rest) on your own machine. That stack runs in Docker containers, so you need a container runtime installed first. Follow the official guide to install and configure [Docker Desktop](https://docs.docker.com/desktop) on your machine.
|
||||
The most common thing you'll do with the CLI is run the full Supabase stack (Postgres, Auth, Storage, and the rest) on your own machine. That stack runs in Docker containers, so you need a container runtime installed first. On Windows and Linux, follow the official guide to install and configure [Docker Desktop](https://docs.docker.com/desktop).
|
||||
|
||||
On macOS, we recommend [OrbStack](https://orbstack.dev/) instead of Docker Desktop. It's a drop-in replacement that handles extended file attributes (xattrs) on mounted volumes and container networking more reliably than Docker Desktop. It also starts faster and uses less CPU, memory, and disk, which makes a noticeable difference when running the full Supabase stack.
|
||||
|
||||
Alternately, you can use a different container tool that offers Docker compatible APIs.
|
||||
|
||||
- [Rancher Desktop](https://rancherdesktop.io/) (macOS, Windows, Linux)
|
||||
- [Podman](https://podman.io/) (macOS, Windows, Linux)
|
||||
- [OrbStack](https://orbstack.dev/) (macOS)
|
||||
- [colima](https://github.com/abiosoft/colima) (macOS)
|
||||
|
||||
With a container runtime running, go to the folder where you want to create your project and initialize it:
|
||||
|
||||
@@ -95,6 +95,41 @@ limit 100;
|
||||
|
||||
Combine predicates with `and`, `or`, and `not`. Select only the fields needed for the investigation. To correlate sources, use an identifier present in both; a shared timestamp alone does not establish that events belong to the same request.
|
||||
|
||||
## Filter by Postgres error code [#sqlstate-filtering]
|
||||
|
||||
Filter `postgres_logs` by SQLSTATE to surface errors at a specific category rather than by message text. The code lives in `log_attributes['parsed.sql_state_code']`.
|
||||
|
||||
To see which error codes are occurring across a time window:
|
||||
|
||||
```sql
|
||||
select
|
||||
log_attributes['parsed.sql_state_code'] as sqlstate,
|
||||
count() as occurrences
|
||||
from logs
|
||||
where source = 'postgres_logs'
|
||||
and log_attributes['parsed.sql_state_code'] != ''
|
||||
group by sqlstate
|
||||
order by occurrences desc
|
||||
limit 50;
|
||||
```
|
||||
|
||||
During a platform-level incident (such as the database entering read-only mode), codes like `25006` (`read_only_sql_transaction`) and `53100` (`disk_full`) will dominate the results. To focus on application errors while a platform incident is active, exclude the known platform codes:
|
||||
|
||||
```sql
|
||||
select
|
||||
timestamp,
|
||||
log_attributes['parsed.sql_state_code'] as sqlstate,
|
||||
event_message
|
||||
from logs
|
||||
where source = 'postgres_logs'
|
||||
and log_attributes['parsed.sql_state_code'] not in ('25006', '53100', '57P03')
|
||||
and log_attributes['parsed.error_severity'] = 'ERROR'
|
||||
order by timestamp desc
|
||||
limit 50;
|
||||
```
|
||||
|
||||
SQLSTATE codes starting with `25` or `53` usually indicate platform-level resource events rather than application bugs. See the [PostgREST error codes reference](/docs/guides/api/rest/postgrest-error-codes#database-level-errors) and [Database size guide](/docs/guides/platform/database-size#read-only-mode) for context.
|
||||
|
||||
## Query limits [#limit-and-result-row-limitations]
|
||||
|
||||
Use an explicit `limit` and narrow time range. The logs query surface rejects `select *` and `count(*)`; list columns and use `count()`. A result limit bounds returned rows, not the time range scanned.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
id: 'automate-with-agents'
|
||||
title: 'Hire an agent'
|
||||
subtitle: 'Run a read-only monitoring routine in your own agent harness.'
|
||||
description: 'Choose and set up a Health, Security, Performance, or Capacity monitor in Claude, Codex, or Cursor.'
|
||||
description: 'Choose and set up a Health, Security, Performance, or Resource monitor in Claude, Codex, or Cursor.'
|
||||
---
|
||||
|
||||
This guide explains how to run a Supabase monitoring agent in your own harness. Each agent is a prompt plus a schedule. It reads project data and reports findings. It does not change the project.
|
||||
@@ -16,7 +16,7 @@ Start with one monitor. Add another only when the project needs a different sour
|
||||
| [Health monitor](/docs/guides/observability/automate-with-agents/health) | API and Auth server errors, error-rate spikes, connection pressure | Hourly | You need incident detection and regular feedback loops |
|
||||
| [Security monitor](/docs/guides/observability/automate-with-agents/security) | Security Advisor findings, authentication and authorization failures | Daily | You need a regular access-control and configuration review |
|
||||
| [Performance monitor](/docs/guides/observability/automate-with-agents/performance) | Slow queries, lock waits, long-running sessions, Performance Advisor findings | Hourly | You need query and database performance checks |
|
||||
| [Capacity monitor](/docs/guides/observability/automate-with-agents/usage) | Request, error, storage, table, and connection growth | Daily | You need to identify growth before it reaches a resource limit |
|
||||
| [Resource monitor](/docs/guides/observability/automate-with-agents/usage) | Request, error, storage, table, and connection growth | Daily | You need to identify growth before it reaches a resource limit |
|
||||
|
||||
For a small project, run the most relevant routine daily or weekly and include the other categories in its prompt. Split it into specialized monitors only when you need different owners, schedules, or alert thresholds.
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
---
|
||||
id: 'automate-with-agents-all'
|
||||
title: 'Generalist'
|
||||
subtitle: 'Generalist is a read-only daily agent. It runs all four checks — health, security, performance, and usage — and reports only findings that need attention.'
|
||||
description: 'A once-daily agent that checks all signal sources and reports across health, security, performance, and usage.'
|
||||
---
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Schedule([Once per day]) --> Health[query_logs: health]
|
||||
Schedule --> Security[get_advisors: security]
|
||||
Schedule --> Performance[get_advisors + pg_stat_activity]
|
||||
Schedule --> Usage[execute_sql: sizes and growth]
|
||||
Health & Security & Performance & Usage --> Filter{Anything to report?}
|
||||
Filter -->|Yes| Report[Daily summary]
|
||||
Filter -->|No| Silent[Stay silent]
|
||||
```
|
||||
|
||||
## What it watches
|
||||
|
||||
- **Health** — API 5xx, Auth failures, error-rate spikes in the last 24 hours
|
||||
- **Security** — Security Advisor findings, authorization failure spikes
|
||||
- **Performance** — slow queries, lock waits, Performance Advisor findings
|
||||
- **Usage** — database size, connection counts, API request growth, approaching limits
|
||||
|
||||
It uses `query_logs`, `get_advisors`, and read-only `execute_sql` on project-scoped [Supabase MCP](/docs/guides/ai-tools/mcp). It does not change the project.
|
||||
|
||||
## When it watches
|
||||
|
||||
<AgentWatchSchedule id="all" />
|
||||
|
||||
## What it will output
|
||||
|
||||
Generalist reports only checks that turn up a finding. If health is clear, that section is omitted. If all checks are clear, the agent stays silent. When it does report, each section follows the same format as the specialist agent: a grouped finding, a likely cause, and a next step for a person to act on.
|
||||
|
||||
<$Partial path="monitoring_agent_output.mdx" />
|
||||
|
||||
## Set up the agent
|
||||
|
||||
<AgentSetup id="all" />
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 'automate-with-agents-usage'
|
||||
title: 'Capacity monitor'
|
||||
title: 'Resource monitor'
|
||||
subtitle: 'A read-only agent that tracks resource and request growth and estimates when a confirmed limit could be reached.'
|
||||
description: 'Daily monitoring for resource growth and approaching limits'
|
||||
---
|
||||
@@ -30,7 +30,7 @@ It uses read-only `execute_sql` and `query_logs` on project-scoped [Supabase MCP
|
||||
|
||||
## What it will output
|
||||
|
||||
Capacity monitor reports new or changed request-growth signals and resource-limit risks. When saved measurements support a forecast within 14 days, it includes the estimated date, calculation, and scaling guide. If history or a matching limit is missing, it explains what it needs instead of inventing a date. See [what triggers a capacity report](/docs/guides/observability/detecting#usage).
|
||||
Resource monitor reports new or changed request-growth signals and resource-limit risks. When saved measurements support a forecast within 14 days, it includes the estimated date, calculation, and scaling guide. If history or a matching limit is missing, it explains what it needs instead of inventing a date. See [what triggers a resource report](/docs/guides/observability/detecting#usage).
|
||||
|
||||
If a check cannot run, the agent tells you what is missing. Clear checks and unchanged findings stay quiet.
|
||||
|
||||
|
||||
@@ -48,8 +48,8 @@ Compute sizes can be changed by first selecting your project in the dashboard [h
|
||||
|
||||
className="max-w-[500px]"
|
||||
|
||||
width={2122}
|
||||
height={1302}
|
||||
width={2026}
|
||||
height={1060}
|
||||
/>
|
||||
|
||||
We charge hourly for additional compute based on your usage. Read more about [usage-based billing for compute](/docs/guides/platform/manage-your-usage/compute).
|
||||
@@ -166,5 +166,5 @@ As mentioned in the Postgres [documentation](https://postgresqlco.nf/doc/en/para
|
||||
|
||||
### Constraints
|
||||
|
||||
- You can modify disk attributes up to **four times** within a rolling 24-hour window. A new modification can be initiated as soon as the previous one completes. If you reach this limit, you will encounter throttling and must wait for the rolling 24-hour window to permit further adjustments.
|
||||
- After **any** disk attribute change, there is a cooldown period of approximately four hours before you can make further adjustments. During this time, no changes are allowed. If you encounter throttling, you’ll need to wait until the cooldown period concludes before making additional modifications.
|
||||
- You can increase disk size but cannot decrease it.
|
||||
@@ -87,9 +87,7 @@ Supabase uses network-attached storage to balance performance with scalability.
|
||||
|
||||
Projects on the Pro Plan and higher have auto-scaling disks.
|
||||
|
||||
Disk size expands automatically when the database reaches 90% of the allocated disk size. The disk is expanded to be 50% larger (for example, 8 GB -> 12 GB).
|
||||
|
||||
Auto-scaling is limited to four modifications within a rolling 24-hour window. While a new modification can be initiated immediately after the previous one completes, reaching the quota of four resizes within the current rolling 24-hour window will prevent further scaling until the window allows it. If you reach 95% disk utilization and have exhausted your modification quota, your project will enter read-only mode.
|
||||
Disk size expands automatically when the database reaches 90% of the allocated disk size. The disk is expanded to be 50% larger (for example, 8 GB -> 12 GB). Auto-scaling is limited to four modifications within a rolling 24-hour window. If you reach 95% disk utilization and have exhausted your modification quota, your project will enter read-only mode.
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
@@ -103,7 +101,7 @@ Disk size can also be manually expanded on the [Database Settings page](/dashboa
|
||||
|
||||
You may want to import a lot of data into your database which requires multiple disk expansions. for example, uploading more than 1.5x the current size of your database storage will put your database into [read-only mode](#read-only-mode). If so, it is highly recommended you increase the disk size manually on the [Database Settings page](/dashboard/project/_/database/settings).
|
||||
|
||||
Due to restrictions on the underlying cloud provider, disk modifications are limited to four operations within a rolling 24-hour window. While a new modification can be initiated as soon as the previous one completes, you will be unable to make further adjustments if you reach this rolling 24-hour limit until the rolling 24-hour window permits it.
|
||||
Due to restrictions on the underlying cloud provider, disk expansions can occur only once every four hours. During the four hour cool down window, the disk cannot be resized again.
|
||||
|
||||
</Admonition>
|
||||
|
||||
@@ -126,7 +124,24 @@ To resolve it, upgrade your plan or disable your Spend Cap to lift the restricti
|
||||
|
||||
In some cases Supabase may put your database into read-only mode to prevent your database from exceeding the billing or disk limitations.
|
||||
|
||||
In read-only mode, clients will encounter errors such as `cannot execute INSERT in a read-only transaction`. Regular operation (read-write mode) is automatically re-enabled once usage is below 95% of the disk size,
|
||||
In read-only mode, clients will encounter errors such as `cannot execute INSERT in a read-only transaction`. The Postgres SQLSTATE code for this error is **25006** (`read_only_sql_transaction`). During severe disk exhaustion, **SQLSTATE 53100** (`disk_full`) may also appear. Regular operation (read-write mode) is automatically re-enabled once usage is below 95% of the disk size.
|
||||
|
||||
While in read-only mode, all write operations are blocked — including background jobs, scheduled tasks, and internal monitoring writes. Data that those jobs write (such as disk usage snapshots) will be stale until the database returns to read-write mode.
|
||||
|
||||
To confirm that your database has entered read-only mode, query Postgres logs for the relevant SQLSTATE codes using the [Logs Explorer](/dashboard/project/_/logs/explorer) with the query source set to **Logs**:
|
||||
|
||||
```sql
|
||||
select
|
||||
timestamp,
|
||||
log_attributes['parsed.error_severity'] as severity,
|
||||
log_attributes['parsed.sql_state_code'] as sqlstate,
|
||||
event_message
|
||||
from logs
|
||||
where source = 'postgres_logs'
|
||||
and log_attributes['parsed.sql_state_code'] in ('25006', '53100', '57P03')
|
||||
order by timestamp desc
|
||||
limit 50;
|
||||
```
|
||||
|
||||
### Disabling read-only mode
|
||||
|
||||
@@ -138,7 +153,7 @@ First, change the [transaction access mode](https://www.postgresql.org/docs/curr
|
||||
set session characteristics as transaction read write;
|
||||
```
|
||||
|
||||
This allows you to delete data from within the session. After deleting data, consider running a vacuum to reclaim as much space as possible:
|
||||
This allows you to delete data from within the current session. After deleting data, consider running a vacuum to reclaim as much space as possible:
|
||||
|
||||
```sql
|
||||
vacuum;
|
||||
@@ -150,6 +165,12 @@ Once you have reclaimed space, you can run the following to disable [read-only](
|
||||
set default_transaction_read_only = 'off';
|
||||
```
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
`SET SESSION CHARACTERISTICS` applies only to the current session. Background jobs and scheduled tasks resume writes automatically once the platform exits read-only mode — no manual step is needed.
|
||||
|
||||
</Admonition>
|
||||
|
||||
### Disk size distribution
|
||||
|
||||
You can check the distribution of your disk size on your [project's Infrastructure page](/dashboard/project/_/settings/infrastructure).
|
||||
|
||||
@@ -5,24 +5,41 @@ title: 'Manage your usage'
|
||||
|
||||
Each subpage breaks down a specific usage item and details what you're charged for, how costs are calculated, and how to optimize usage and reduce costs.
|
||||
|
||||
- [Compute](/docs/guides/platform/manage-your-usage/compute)
|
||||
- [Read Replicas](/docs/guides/platform/manage-your-usage/read-replicas)
|
||||
## Database
|
||||
|
||||
- [Branching](/docs/guides/platform/manage-your-usage/branching)
|
||||
- [Egress](/docs/guides/platform/manage-your-usage/egress)
|
||||
- [Compute](/docs/guides/platform/manage-your-usage/compute)
|
||||
- [Disk IOPS](/docs/guides/platform/manage-your-usage/disk-iops)
|
||||
- [Disk Size](/docs/guides/platform/manage-your-usage/disk-size)
|
||||
- [Disk Throughput](/docs/guides/platform/manage-your-usage/disk-throughput)
|
||||
- [Disk IOPS](/docs/guides/platform/manage-your-usage/disk-iops)
|
||||
- [Monthly Active Users](/docs/guides/platform/manage-your-usage/monthly-active-users)
|
||||
- [Monthly Active Third-Party Users](/docs/guides/platform/manage-your-usage/monthly-active-users-third-party)
|
||||
- [Egress](/docs/guides/platform/manage-your-usage/egress)
|
||||
- [IPv4](/docs/guides/platform/manage-your-usage/ipv4)
|
||||
- [Pipelines](/docs/guides/platform/manage-your-usage/pipelines)
|
||||
- [Point-in-Time Recovery](/docs/guides/platform/manage-your-usage/point-in-time-recovery)
|
||||
- [Read Replicas](/docs/guides/platform/manage-your-usage/read-replicas)
|
||||
|
||||
## Auth
|
||||
|
||||
- [MFA Phone](/docs/guides/platform/manage-your-usage/advanced-mfa-phone)
|
||||
- [Monthly Active SSO Users](/docs/guides/platform/manage-your-usage/monthly-active-users-sso)
|
||||
- [Storage Size](/docs/guides/platform/manage-your-usage/storage-size)
|
||||
- [Monthly Active Third-Party Users](/docs/guides/platform/manage-your-usage/monthly-active-users-third-party)
|
||||
- [Monthly Active Users](/docs/guides/platform/manage-your-usage/monthly-active-users)
|
||||
|
||||
## Storage
|
||||
|
||||
- [Storage Image Transformations](/docs/guides/platform/manage-your-usage/storage-image-transformations)
|
||||
- [Edge Function Invocations](/docs/guides/platform/manage-your-usage/edge-function-invocations)
|
||||
- [Storage Size](/docs/guides/platform/manage-your-usage/storage-size)
|
||||
|
||||
## Realtime
|
||||
|
||||
- [Realtime Messages](/docs/guides/platform/manage-your-usage/realtime-messages)
|
||||
- [Realtime Peak Connections](/docs/guides/platform/manage-your-usage/realtime-peak-connections)
|
||||
|
||||
## Edge Functions
|
||||
|
||||
- [Edge Function Invocations](/docs/guides/platform/manage-your-usage/edge-function-invocations)
|
||||
|
||||
## Platform security, observability, and compliance
|
||||
|
||||
- [Custom Domains](/docs/guides/platform/manage-your-usage/custom-domains)
|
||||
- [Point-in-Time Recovery](/docs/guides/platform/manage-your-usage/point-in-time-recovery)
|
||||
- [IPv4](/docs/guides/platform/manage-your-usage/ipv4)
|
||||
- [MFA Phone](/docs/guides/platform/manage-your-usage/advanced-mfa-phone)
|
||||
- [Log Drains](/docs/guides/platform/manage-your-usage/log-drains)
|
||||
- [Pipelines](/docs/guides/platform/manage-your-usage/pipelines)
|
||||
- [Logs Ingest/Query and Log Drains](/docs/guides/platform/manage-your-usage/logs)
|
||||
@@ -3,19 +3,19 @@ id: 'manage-usage-logs-ingest'
|
||||
title: 'Manage Logs Ingest usage'
|
||||
---
|
||||
|
||||
<Admonition type="caution" title="Coming soon">
|
||||
<Admonition type="caution" title="Not yet billed">
|
||||
|
||||
Logs pricing is being rolled out. Pricing details and included quotas on this page are subject to change. This page will be updated when billing enforcement goes live.
|
||||
To ensure customers have ample time to prepare, we will introduce a grace period that runs through the start of 2027 before billing begins.
|
||||
|
||||
</Admonition>
|
||||
|
||||
## What you are charged for
|
||||
|
||||
You are charged for the total volume of log data that Supabase ingests across all your project's services (Postgres, API gateway, Auth, Storage, Realtime, Edge Functions, etc.) during the billing cycle, measured in GB.
|
||||
You are charged for the total volume of log data that Supabase ingests across all your project's services (Postgres, API gateway, Auth, Storage, Realtime, Edge Functions, and others) during the billing cycle.
|
||||
|
||||
## How charges are calculated
|
||||
|
||||
Logs Ingest is charged per GB of log data ingested during the billing cycle.
|
||||
Logs Ingest is charged per byte of log data ingested during the billing cycle.
|
||||
|
||||
### Usage on your invoice
|
||||
|
||||
@@ -27,25 +27,63 @@ Usage is shown as "Logs Ingest" on your invoice.
|
||||
|
||||
## Billing examples
|
||||
|
||||
Billing examples will be published here when pricing is finalized.
|
||||
Ingest is billed per byte (bytes ÷ 1,000,000,000 × $0.50), not rounded up to the next GB. These examples use fractional GB to show that precision.
|
||||
|
||||
| Plan | Monthly ingest | Included | Billable overage | Cost |
|
||||
| ---- | -------------- | -------- | ---------------- | ----------------------- |
|
||||
| Free | 0.32 GB | 1 GB | — | <Price price="0" /> |
|
||||
| Paid | 19.76 GB | 20 GB | — | <Price price="0" /> |
|
||||
| Paid | 20.24 GB | 20 GB | 0.24 GB | <Price price="0.12" /> |
|
||||
| Paid | 27.48 GB | 20 GB | 7.48 GB | <Price price="3.74" /> |
|
||||
| Paid | 41.92 GB | 20 GB | 21.92 GB | <Price price="10.96" /> |
|
||||
| Paid | 115.06 GB | 20 GB | 95.06 GB | <Price price="47.53" /> |
|
||||
|
||||
## View usage
|
||||
|
||||
You can view Logs Ingest usage on the [organization's usage page](/dashboard/org/_/usage) of the Dashboard. The page shows the usage of all projects by default. To view the usage for a specific project, select it from the dropdown. You can also select a different time period.
|
||||
|
||||
{/* TODO: Add screenshots once Studio surfaces are live */}
|
||||
|
||||
## Optimize usage
|
||||
|
||||
Every service in your Supabase project automatically generates logs — you don't write them directly. Log volume scales with your application's traffic and behavior. To reduce ingest volume:
|
||||
Every service in your Supabase project generates logs automatically. Log volume scales with your application's traffic and behavior.
|
||||
|
||||
- **Configure Postgres logging settings.** Postgres emits logs for connections, checkpoints, statements, and more — many of which can be tuned or disabled. Adjusting settings such as `log_connections`, `log_min_duration_statement`, and `log_statement` can significantly reduce Postgres log volume. See [Customizing Postgres configs](/docs/guides/database/custom-postgres-config) for the full list of configurable parameters.
|
||||
- **Reduce log-level verbosity** in your Edge Functions and server-side code (for example, `info` → `warn` in production).
|
||||
- **Audit verbose application logging in your application code.** Application-level logs forwarded to Supabase services count toward ingest.
|
||||
- **Cap log payload size.** Large structured payloads can inflate GB-billed volume.
|
||||
- **Investigate spikes.** Use the [**Logs Explorer**](/dashboard/project/_/logs-explorer) in the Dashboard to find services or endpoints producing unusually high volume.
|
||||
Reduce the volume of a log type rather than turning it off completely. Raise a threshold rather than disabling logging outright as this prevents you from having information if something goes wrong later. For example a security incident or a slow query may be completely missed.
|
||||
|
||||
{/* TODO: add MCP/skill guidance link when available */}
|
||||
You change all of the Postgres settings below through [Custom Postgres Configuration](/docs/guides/database/custom-postgres-config), either from the SQL Editor or the Supabase CLI. Some changes take effect immediately; others require a database restart. The CLI has a `--no-restart` option if you want to batch several changes together before restarting. See [Postgres log configuration](/docs/guides/database/postgres/postgres-log-config) for the full list of configurable log settings and what each one does.
|
||||
|
||||
### Start with the two biggest levers
|
||||
|
||||
Consider these options first. They are responsible for most log volume on most projects.
|
||||
|
||||
- **[`log_statement`](/docs/guides/database/postgres/postgres-log-config#logstatement)** — set to `none` if you don't manually read through raw query logs to debug your application, for example if your team relies on an AI coding assistant, an APM tool, or the Query Performance dashboard instead. Keep `all` on if you routinely read query text in the Logs Explorer to debug issues, or you have a compliance requirement to retain a full statement audit trail; `mod` is a lighter-volume option if you only need a record of data changes.
|
||||
- **[`log_min_duration_statement`](/docs/guides/database/postgres/postgres-log-config#logmindurationstatement)** — raise the threshold (for example, from a few milliseconds to 1-2 seconds) if you only care about queries that are slow enough to notice. Keep it low only while you're actively in a performance-tuning phase, and raise it back up once that investigation is done.
|
||||
- **[pgAudit](/docs/guides/database/extensions/pgaudit)**, if enabled — this extension can be very verbose independently of `log_statement`. Turn off a broad `pgaudit.log` or `pgaudit.role` configuration if you don't need a dedicated audit trail beyond what `log_statement` already gives you.
|
||||
|
||||
<Admonition type="danger" title="Statement logs can expose sensitive data">
|
||||
|
||||
Enabling `log_statement=all`, or setting `log_min_duration_statement` to a low or `0` threshold, logs the full text of statements — including plaintext values such as passwords passed as query parameters. Before enabling either, check whether your queries carry sensitive data, and confirm who can access these logs and how long they're retained.
|
||||
|
||||
</Admonition>
|
||||
|
||||
### Check these often-overlooked settings
|
||||
|
||||
- **[`log_connections` and `log_disconnections`](/docs/guides/database/postgres/postgres-log-config#logconnections)** — turn off if you don't need a record of exactly when every connection opened and closed. Keep on if you're troubleshooting connection exhaustion or spikes, or have a security requirement to log all connection activity. If this setting is generating a lot of volume, your app may not be using a connection pooler such as Supavisor, which Supabase provides by default — connecting through the pooler reduces both log volume and the risk of running out of connections.
|
||||
- **[`log_autovacuum_min_duration`](/docs/guides/database/postgres/postgres-log-config#logautovacuumminduration)** — raise the threshold, or turn it off, if you're not actively diagnosing a table-maintenance problem. Keep it on with a reasonable threshold if you've had autovacuum-related performance issues before.
|
||||
- **[`log_temp_files`](/docs/guides/database/postgres/postgres-log-config#logtempfiles)** — turn off if you're not actively tuning query performance around memory usage. Keep it on if you're investigating slow queries related to sorting or joining large amounts of data.
|
||||
- **[`log_lock_waits`](/docs/guides/database/postgres/postgres-log-config#loglockwaits)** — turn off if you're not seeing symptoms of queries hanging. Keep it on if multiple processes write to the same data concurrently and you've seen unexplained slowdowns before.
|
||||
|
||||
### Check this if your logs seem unusually large for no clear reason
|
||||
|
||||
**[`log_min_messages`](/docs/guides/database/postgres/postgres-log-config#logminmessages)** controls the verbosity of Postgres's own internal logging, separate from query activity. The default is `warning`. If this was turned up to a debug level while troubleshooting a specific issue and never turned back down, it can produce a large, ongoing volume of internal messages that aren't useful for day-to-day monitoring — check the current value and reset it if you don't recognize turning it up yourself.
|
||||
|
||||
Custom RPC functions and stored procedures are also worth reviewing. A PL/pgSQL function that calls `RAISE NOTICE` or `RAISE LOG` produces log output every time it runs, independent of any of the settings above — if a frequently-called function logs on every invocation, it can account for a large, otherwise-unexplained share of your volume.
|
||||
|
||||
### Settings you can generally leave alone
|
||||
|
||||
[`log_checkpoints`](/docs/guides/database/postgres/postgres-log-config#logcheckpoints), [`log_recovery_conflict_waits`](/docs/guides/database/postgres/postgres-log-config#logrecoveryconflictwaits), `log_replication_commands`, and [`log_startup_progress_interval`](/docs/guides/database/postgres/postgres-log-config#logstartupprogressinterval) are either low-volume by nature or only relevant if you use specific features such as replication. Unless you know you're using the related feature, these rarely contribute meaningfully to your total ingest.
|
||||
|
||||
## Checking your new usage baseline
|
||||
|
||||
After you change a setting, give it a day or two, then check your ingest usage trend in the Dashboard or your log volume in the Logs Explorer, to confirm the change had the effect you expected. Volume can be uneven day-to-day depending on traffic, so look at the trend over several days before drawing conclusions. If you're not sure which setting is responsible for your current usage, check which kind of log entries make up most of your volume in the Logs Explorer before changing anything.
|
||||
|
||||
## Exceeding Quotas
|
||||
|
||||
|
||||
@@ -3,49 +3,61 @@ id: 'manage-usage-logs-query'
|
||||
title: 'Manage Logs Query usage'
|
||||
---
|
||||
|
||||
<Admonition type="caution" title="Coming soon">
|
||||
<Admonition type="caution" title="Not yet enforced">
|
||||
|
||||
Logs pricing is being rolled out. Pricing details and included quotas on this page are subject to change. This page will be updated when billing enforcement goes live.
|
||||
The allowance and degraded-state consequences described on this page are not enforced yet. This page describes how enforcement will work once the grace period ends at the start of 2027.
|
||||
|
||||
</Admonition>
|
||||
|
||||
## What you are charged for
|
||||
|
||||
You are charged for the volume of log data scanned when you read logs via the Studio UI, the Management API, the CLI, or any other interface, measured in GB.
|
||||
Logs Query usage isn't billed directly. Instead, your organization gets a log query allowance that scales with how much log data you ingest. The allowance covers the volume of log data scanned when you read logs through the Studio UI, the Management API, the CLI, or any other interface.
|
||||
|
||||
## How charges are calculated
|
||||
|
||||
Logs Query is charged per GB of log data scanned during the billing cycle.
|
||||
|
||||
### Usage on your invoice
|
||||
|
||||
Usage is shown as "Logs Query" on your invoice.
|
||||
|
||||
## Pricing
|
||||
## How your allowance is calculated
|
||||
|
||||
<$Partial path="billing/pricing/pricing_logs_query.mdx" />
|
||||
|
||||
## Billing examples
|
||||
| Monthly ingest | Ingest overage (billed) | Query allowance |
|
||||
| -------------- | ----------------------- | --------------- |
|
||||
| 20 GB | 0 GB | 2,000 GB |
|
||||
| 21 GB | 1 GB | 2,100 GB |
|
||||
| 25 GB | 5 GB | 2,500 GB |
|
||||
| 40 GB | 20 GB | 4,000 GB |
|
||||
| 100 GB | 80 GB | 10,000 GB |
|
||||
|
||||
Billing examples will be published here when pricing is finalized.
|
||||
There's no separate per-GB price for Logs Query. Your allowance is always 100 times your ingest usage for the same month.
|
||||
|
||||
### Usage on your invoice
|
||||
|
||||
Logs Query doesn't appear as a line item on your invoice, because it isn't billed directly.
|
||||
|
||||
## View usage
|
||||
|
||||
You can view Logs Query usage on the [organization's usage page](/dashboard/org/_/usage) of the Dashboard. The page shows the usage of all projects by default. To view the usage for a specific project, select it from the dropdown. You can also select a different time period.
|
||||
|
||||
{/* TODO: Add screenshots once Studio surfaces are live */}
|
||||
|
||||
## Optimize usage
|
||||
|
||||
Logs Query usage scales directly with the time range and data volume you scan. Keep usage low by:
|
||||
Two people asking similar-sounding questions can generate very different amounts of usage, depending on how they ask. These tips help you avoid scanning more data than a question requires — they're habits, not restrictions, and none of them ask you to give up visibility you need.
|
||||
|
||||
- **Using the Logs Explorer** in the [Dashboard](/dashboard/project/_/logs-explorer) for ad-hoc queries — it surfaces the most relevant log data without over-scanning.
|
||||
- **Keeping time ranges narrow.** A 1-day window scans 7× less data than a 7-day window.
|
||||
- **Applying service and endpoint filters early** to reduce the volume scanned per query.
|
||||
- **Avoiding frequent programmatic polling.** Repeated API or CLI log queries accumulate GB rapidly. For continuous log streaming, [Log Drains](/docs/guides/platform/manage-your-usage/log-drains) are more cost-effective.
|
||||
- **Narrow your time range to what the question needs.** Query cost scales with how much data a query scans, and time range is usually the biggest factor: a 1-day window scans about 7 times less data than a 7-day window. Start with the smallest window that could contain the answer, and widen only if you don't find it. Keep a wide window when you're doing genuine historical or trend analysis on purpose — that's a real use case, only a more expensive one by nature.
|
||||
- **Filter as part of the query, not after you've pulled the data.** Add filters for source, service, status code, or project before you run a search. A broad, unfiltered query scans everything in its time range, even if you only look at a fraction of the results afterwards. Filtering after the fact doesn't reduce what was already scanned.
|
||||
- **Use the [Logs Explorer](/dashboard/project/_/logs-explorer) for ad-hoc digging, not scripted polling.** Every query scans data again — there's no caching benefit from asking the same question repeatedly. A script that polls the query endpoint on a schedule re-scans on every run, so it can use far more allowance than a person checking manually, even if it usually finds nothing new.
|
||||
- **Use [Log Drains](/docs/guides/platform/manage-your-usage/log-drains) for anything continuous.** If you need an ongoing feed of your logs — for your own monitoring stack, alerting, or archiving — repeatedly querying for what's new since you last checked is one of the most expensive ways to get it, because each check re-scans. Drains stream logs to a destination as they arrive instead.
|
||||
|
||||
{/* TODO: add MCP/skill guidance link when available */}
|
||||
After you change how you query, check your usage trend over the following few days rather than a single day. Usage varies with how much debugging or investigation you happen to do, so a single day's change isn't a reliable signal on its own.
|
||||
|
||||
## Exceeding Quotas
|
||||
## When you exceed your allowance
|
||||
|
||||
<$Partial path="billing/exceeding_usage_quotas.mdx" />
|
||||
If you scan more log data than your allowance covers in a given month, the following month enters a degraded state:
|
||||
|
||||
- Queries are rate limited to 10 per minute.
|
||||
- Log retention shrinks to 24 hours (Pro, Team, and Enterprise) or 1 hour (Free).
|
||||
|
||||
If you exceed your allowance again the next month, while still in that degraded state, logs access in the API and Studio UI will be cut off entirely for the month after. Access returns to normal at your next billing cycle.
|
||||
|
||||
### A concrete walkthrough
|
||||
|
||||
- January: You use 20 GB of ingest and 2,450 GB of query, more volume than your allowance covers.
|
||||
- February: Degraded — 10 queries/min, retention shrunk to 24 hours on paid tiers, 1 hour on the Free Plan.
|
||||
- March: If February also went over, that's two consecutive months, so this month is a full cutoff.
|
||||
- April: Billing cycle resets, access returns to normal. This allows you to make continued efforts to optimize your log query usage.
|
||||
@@ -3,24 +3,16 @@ id: 'manage-usage-logs'
|
||||
title: 'Manage Logs usage'
|
||||
---
|
||||
|
||||
<Admonition type="caution" title="Coming soon">
|
||||
Logs usage has two components, and only one of them is billed:
|
||||
|
||||
Logs pricing is being rolled out. Pricing details and included quotas on this page are subject to change. This page will be updated when billing enforcement goes live.
|
||||
- **Logs Ingest** — the total GB of log data Supabase ingests across all your project's services (Postgres, API gateway, Auth, Storage, Realtime, Edge Functions, and others) during the billing cycle. Each plan includes a free quota, and usage beyond the quota is billed per GB.
|
||||
- **Logs Query** — the total GB of log data scanned when you read logs through the Studio UI, the Management API, the CLI, or any other interface. Logs Query isn't billed. Instead, you get a query allowance scaled to your log ingest usage: 100 GB of allowance for every 1 GB you ingest. Once enforcement goes live, scanning more than your allowance in a month will lead to a degraded state — rate-limited queries and shorter retention — rather than a charge.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Logs usage is metered on two SKUs:
|
||||
|
||||
- **Logs Ingest** — the total GB of log data Supabase ingests across all your project's services (Postgres, API gateway, Auth, Storage, Realtime, Edge Functions, etc.) during the billing cycle.
|
||||
- **Logs Query** — the total GB of log data scanned when you read logs via the Studio UI, the Management API, the CLI, or any other interface.
|
||||
|
||||
Each plan includes a free quota for both. Usage beyond the quota is billed per GB. Pricing details and quotas will be published on the per-SKU pages below when billing enforcement goes live.
|
||||
|
||||
For optimization tips and billing details, see the per-SKU pages:
|
||||
For optimization tips and billing details, see:
|
||||
|
||||
- [Manage Logs Ingest usage](/docs/guides/platform/manage-your-usage/logs-ingest)
|
||||
- [Manage Logs Query usage](/docs/guides/platform/manage-your-usage/logs-query)
|
||||
|
||||
## Logs vs log drains
|
||||
|
||||
[Log Drains](/docs/guides/platform/manage-your-usage/log-drains) stream logs out of Supabase to external destinations (Datadog, Better Stack, your own S3 bucket, etc.) and are billed separately on drain hours and events. Draining logs does not replace or reduce Logs Ingest charges — ingest is metered when Supabase processes your logs, drains are metered when Supabase streams them out. These are separate billing primitives, not overlapping charges.
|
||||
[Log Drains](/docs/guides/platform/manage-your-usage/log-drains) stream logs out of Supabase to external destinations, such as Datadog, Better Stack, or your own S3 bucket, and are billed separately on drain hours and events. Draining logs does not replace or reduce Logs Ingest charges — ingest is metered when Supabase processes your logs, drains are metered when Supabase streams them out. These are separate billing primitives, not overlapping charges.
|
||||
Loaded 100 of 975 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user