docs: standardise next steps on overview pages with content listings (#47097)

## I have read the CONTRIBUTING.md file.

YES

## What kind of change does this PR introduce?

This PR helps standardise link sections which is useful for overview
pages that frequently use similar sections such as "Next steps", "Get
started", or "Examples".

Six high-traffic overview pages are migrated as a pilot, with a skill in
the new
[supabase/docs-agent-skills](https://github.com/supabase/docs-agent-skills)
repo to audit and convert the rest in a follow-on PR.

Refactored from an initial YAML front matter approach per review
feedback from @jeremenichelli. Now implemented as a React component and
using existing linting & Markdown export functionality.

A second round of review feedback further simplified the architecture:
the per-listing component registry was removed in favor of a single
`<ContentListings id="..." />` component backed by an ID-keyed data
lookup, the listing data moved out of `apps/docs/components/` into
`apps/docs/data/content-listings/`, the listing-specific link wrapper
was replaced with the existing `<Link>` + `<GlassPanel>` pattern from
the rest of the docs, and the headings now defer to the shared
`<Heading>` from `MdxBase.shared.tsx` (no parallel marker-to-tag
mapping, no typography overrides). Great feedback, thank you! 🙏

Relates to DOCS-1032.

## What is the current behavior?

Authors implement these sections however they wish. As a result,
overview and index pages use inconsistent patterns for orientation
links: some use hand-rolled Markdown lists, some use custom panel/grid
components, some use buttons, and some have no guidance about where to
go next at all. There is no shared component for these sections and no
analytics on those clicks.

## What is the new behavior?

Authors add orientation sections in two steps:

1. Define listing data in a `.data.ts` file under
`apps/docs/data/content-listings/` (for example, `storage.data.ts`).
Each `ContentListingGroup` has a globally-unique `id` like
`storage-get-started`.
2. Place a single `<ContentListings id="..." />` component inline in
guide MDX.

The ID is also the telemetry `listingId`, so the same value
disambiguates the section in PostHog dashboards.

Grid and list layouts, optional icons (such as
`/docs/img/icons/github-icon` with `-light.svg` variants for dark mode),
and external URLs are supported. Conditionals that use `$Show` around
inline components are also supported, for example for auth pricing.

### Usage example from "Storage" overview page

`apps/docs/data/content-listings/storage.data.ts`:

```ts
export const storageGetStarted: ContentListingGroup = {
  id: 'storage-get-started',
  heading: 'Get started',
  description: 'Choose the bucket type that fits your use case:',
  type: 'grid',
  items: [
    {
      title: 'Files buckets',
      href: '/guides/storage/quickstart',
      description:
        'Store and serve images, videos, documents, and general-purpose files with direct URL access and row-level security.',
    },
    {
      title: 'Analytics buckets',
      href: '/guides/storage/analytics/introduction',
      description:
        'Store data in Apache Iceberg tables for data lakes, logs, and ETL. Query from Postgres via foreign tables with partitioning.',
    },
    {
      title: 'Vector buckets',
      href: '/guides/storage/vector/introduction',
      description:
        'Store embeddings and run similarity search for semantic matching, AI, and RAG. Use HNSW indexing, distance metrics, and metadata filtering.',
    },
  ],
}
```

`apps/docs/content/guides/storage.mdx`:

```mdx
<ContentListings id="storage-get-started" />
```

Renders as:

<img width="689" alt="Storage Get started listing — Files, Analytics,
and Vector buckets"
src="https://github.com/user-attachments/assets/0d1b9531-962f-40ae-891e-b1e93ff1c939"
/>

<br>Exported in Markdown as:

```md
## Get started

Choose the bucket type that fits your use case:

- **[Files buckets](/docs/guides/storage/quickstart):** Store and serve images, videos, documents, and general-purpose files with direct URL access and row-level security.
- **[Analytics buckets](/docs/guides/storage/analytics/introduction):** Store data in Apache Iceberg tables for data lakes, logs, and ETL. Query from Postgres via foreign tables with partitioning.
- **[Vector buckets](/docs/guides/storage/vector/introduction):** Store embeddings and run similarity search for semantic matching, AI, and RAG. Use HNSW indexing, distance metrics, and metadata filtering.
```

Click tracking fires via PostHog (`docs_content_listing_clicked`):

```json
{
  "action": "docs_content_listing_clicked",
  "custom_properties": {
    "targetPath": "/guides/storage/quickstart",
    "linkTitle": "Files buckets",
    "groupTitle": "Get started",
    "listingId": "storage-get-started"
  }
}
```

Still finding my way around PostHog, but I verified on preview deploy
that clicking a content listing on `/docs/guides/auth` sends
`docs_content_listing_clicked` to
`https://api.supabase.green/platform/telemetry/event` and receives HTTP
201.

### Authoring experience

Three ways to add or convert content listings: copy the agent prompt
first, use snippets for manual edits, or invoke the audit skill for
batch follow-on work. Refer to `CONTRIBUTING.md` for the full authoring
guide.

#### 1. Agent prompt

Copy into Cursor or another AI assistant:

```text
Add a content listing block for [TOPIC] / [SECTION] (for example, Storage / Examples).
Follow CONTRIBUTING § Content listings in apps/docs.
- Add data to apps/docs/data/content-listings/[topic].data.ts
- Use a globally-unique kebab-case id like `[topic]-[section]`
- Place inline in the guide MDX with <ContentListings id="..." />
- Copy structure from storageGetStarted in apps/docs/data/content-listings/storage.data.ts
- Run pnpm test:local lib/content-listings.test.ts from apps/docs
```

#### 2. VS Code / Cursor snippets

Type these prefixes in the docs workspace
(`.vscode/content-listing.code-snippets`):

| Prefix | Inserts |
| ----------- | --------------------------------------------------------
|
| `cl-data` | `ContentListingGroup` export skeleton with namespaced id |
| `cl-inline` | `<ContentListings id="…" />` in guide MDX |

<img width="658" height="274" alt="image"
src="https://github.com/user-attachments/assets/5ef20954-7aee-4925-887d-79a5ae766b37"
/>

#### 3. Batch audit skill

For follow-on overview page conversion or maintenance, use the
[`audit-content-listings`](https://github.com/supabase/docs-agent-skills/blob/main/.claude/skills/audit-content-listings/SKILL.md)
skill in `docs-agent-skills` (skill, `conversion-manifest.json`, and
validation script).

Example:

```text
Use audit-content-listings. Audit getting-started.mdx, update conversion-manifest.json, then convert the next unconverted section only.
```

## Additional context

The implementation includes a presentational `<ContentListings />`
component (grid/list layouts, GlassPanel, telemetry) backed by ID-keyed
data modules, and a single markdown export handler that reads the same
`id` prop from the JSX and looks up data via the shared registry.

Key files:

- **Data:** `apps/docs/data/content-listings/` (one `.data.ts` file per
guide topic, plus `index.ts` exporting `CONTENT_LISTINGS` and
`getContentListingById`)
- **Renderer:** `apps/docs/components/ContentListings/` (single
`<ContentListings id="…" />` component); registered in
`apps/docs/features/docs/MdxBase.shared.tsx`
- **Types/helpers:** `apps/docs/lib/content-listings.schema.ts` (zod
schemas, type aliases, grid/heading/href helpers)
- **Markdown export:** `apps/docs/internals/markdown-schema/Listings.ts`
(single ID-driven handler) wired into
`apps/docs/internals/generate-guides-markdown.ts`
- **Telemetry:** `docs_content_listing_clicked` defined in
`packages/common/telemetry-constants.ts`, fired from
`ContentListings.client.tsx`
- **Authoring guide:** `apps/docs/CONTRIBUTING.md` (Components and
elements → Content listings)
- **VS Code snippets:** `.vscode/content-listing.code-snippets`
(`cl-data`, `cl-inline`)

### Before & After

#### Auth

| [Before (production)](https://supabase.com/docs/guides/auth) | [After
(preview)](https://docs-git-fork-nrichers-nikrichers-docs-1032-sta-e2a8cb-supabase.vercel.app/docs/guides/auth)
|
|
---------------------------------------------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------------------------------------------
|
| ![Auth
before](https://moijyfpvgnmgoxvwcikq.supabase.co/storage/v1/object/public/pr-proof/supabase/supabase/pr47097/auth-before-dbc93ccd.png)
| ![Auth
after](https://moijyfpvgnmgoxvwcikq.supabase.co/storage/v1/object/public/pr-proof/supabase/supabase/pr47097/auth-after-789b25a6.png)
|

#### Database overview

| [Before
(production)](https://supabase.com/docs/guides/database/overview) |
[After
(preview)](https://docs-git-fork-nrichers-nikrichers-docs-1032-sta-e2a8cb-supabase.vercel.app/docs/guides/database/overview)
|
|
--------------------------------------------------------------------------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------------------------------------------------------------------------
|
| ![Database
before](https://moijyfpvgnmgoxvwcikq.supabase.co/storage/v1/object/public/pr-proof/supabase/supabase/pr47097/database-before-0d32136a.png)
| ![Database
after](https://moijyfpvgnmgoxvwcikq.supabase.co/storage/v1/object/public/pr-proof/supabase/supabase/pr47097/database-after-22447d16.png)
|

#### Edge Functions

| [Before (production)](https://supabase.com/docs/guides/functions) |
[After
(preview)](https://docs-git-fork-nrichers-nikrichers-docs-1032-sta-e2a8cb-supabase.vercel.app/docs/guides/functions)
|
|
--------------------------------------------------------------------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------------------------------------------------------------------
|
| ![Functions
before](https://moijyfpvgnmgoxvwcikq.supabase.co/storage/v1/object/public/pr-proof/supabase/supabase/pr47097/functions-before-11319580.png)
| ![Functions
after](https://moijyfpvgnmgoxvwcikq.supabase.co/storage/v1/object/public/pr-proof/supabase/supabase/pr47097/functions-after-83268362.png)
|

#### Storage

| [Before (production)](https://supabase.com/docs/guides/storage) |
[After
(preview)](https://docs-git-fork-nrichers-nikrichers-docs-1032-sta-e2a8cb-supabase.vercel.app/docs/guides/storage)
|
|
----------------------------------------------------------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------------------------------------------------------
|
| ![Storage
before](https://moijyfpvgnmgoxvwcikq.supabase.co/storage/v1/object/public/pr-proof/supabase/supabase/pr47097/storage-before-9b4ae535.png)
| ![Storage
after](https://moijyfpvgnmgoxvwcikq.supabase.co/storage/v1/object/public/pr-proof/supabase/supabase/pr47097/storage-after-7503d664.png)
|

#### Realtime

| [Before (production)](https://supabase.com/docs/guides/realtime) |
[After
(preview)](https://docs-git-fork-nrichers-nikrichers-docs-1032-sta-e2a8cb-supabase.vercel.app/docs/guides/realtime)
|
|
-------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------
|
| ![Realtime
before](https://moijyfpvgnmgoxvwcikq.supabase.co/storage/v1/object/public/pr-proof/supabase/supabase/pr47097/realtime-before-6eb5b125.png)
| ![Realtime
after](https://moijyfpvgnmgoxvwcikq.supabase.co/storage/v1/object/public/pr-proof/supabase/supabase/pr47097/realtime-after-92ec7d74.png)
|

#### Getting Started (partial migration for demoing)

| [Before
(production)](https://supabase.com/docs/guides/getting-started) | [After
(preview)](https://docs-git-fork-nrichers-nikrichers-docs-1032-sta-e2a8cb-supabase.vercel.app/docs/guides/getting-started)
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------------------------------------------------
|
| ![Getting Started
before](https://moijyfpvgnmgoxvwcikq.supabase.co/storage/v1/object/public/pr-proof/supabase/supabase/pr47097/getting-started-before-89251d7b.png)
| ![Getting Started
after](https://moijyfpvgnmgoxvwcikq.supabase.co/storage/v1/object/public/pr-proof/supabase/supabase/pr47097/getting-started-after-3e33c6d4.png)
|

### Test plan

- [ ] Visually verify migrated pages render correctly:
- [ ] `/guides/auth` — grid "Get started", conditional pricing list,
grid "Next steps"
  - [ ] `/guides/database/overview` — get started + next steps listings
  - [ ] `/guides/getting-started` — top 3-column grid
  - [ ] `/guides/functions` — get started + example listings
  - [ ] `/guides/storage` — get started, examples, resources listings
  - [ ] `/guides/realtime` — get started, examples, resources listings
- [ ] Confirm listings render at explicit page positions
- [ ] Click a content listing link and verify
`docs_content_listing_clicked` fires in PostHog with expected properties
(the new `listingId` is the namespaced kebab-case id, e.g.
`storage-get-started`)
- [ ] Build docs and confirm `.md` alternate output includes listing
sections at component placement (e.g.
`public/markdown/guides/storage.md`)
- [ ] Run unit tests: `pnpm test:local lib/content-listings.test.ts` in
`apps/docs`

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Introduced a standardized content listings system for organizing
related guides and resources.
* Content listings now support both grid and list layouts for consistent
presentation.
  * Added click telemetry for content listing interactions.

* **Documentation**
* Updated authentication, database, functions, getting started,
realtime, and storage guide pages to use the new content listing
components.
* Improved MDX structure examples and listing markup formatting in
contributor documentation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->



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

## Release Notes

* **New Features**
* Introduced a new content listings component for displaying guide
content in list and grid layouts across documentation pages.
* Added telemetry tracking for content listing interactions to measure
user engagement.

* **Documentation**
* Updated guide pages (Authentication, Database, Functions, Storage,
Realtime, Getting Started) to use the new listings layout.
* Added contribution guidelines for creating and managing content
listings in documentation.

* **Tests**
* Added comprehensive test coverage for content listings validation,
serialization, and rendering.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Nik Richers <nik@validmind.ai>
Co-authored-by: Jeremias Menichelli <jmenichelli@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-06-29 23:57:12 +00:00
1 parent c1646f9a95
commit 635b2d6050
26 files changed
+1119 -511

No files matched your search

+2 -1
View File
@@ -112,7 +112,8 @@ next-env.d.ts
# DynamoDB Local files
.dynamodb/
.vscode
.vscode/*
!.vscode/content-listing.code-snippets
.idea
.vercel
+28
View File
@@ -0,0 +1,28 @@
{
"Content listing data export": {
"prefix": "cl-data",
"scope": "typescript",
"description": "ContentListingGroup export for overview listing blocks",
"body": [
"export const ${1:topic}${2:Section}: ContentListingGroup = {",
" id: '${3:topic-section}',",
" heading: '${4:Section heading}',",
" description: '${5:Optional intro sentence}',",
" type: '${6|grid,list|}',",
" items: [",
" {",
" title: '${7:Link title}',",
" href: '${8:/guides/...}',",
" description: '${9:Short description}',",
" },",
" ],",
"}"
]
},
"Content listing inline MDX": {
"prefix": "cl-inline",
"scope": "markdown,mdx",
"description": "Inline ContentListings component in a guide MDX file",
"body": ["<ContentListings id=\"${1:topic-section}\" />"]
}
}
+30 -3
View File
@@ -197,15 +197,42 @@ Keep code lines short to avoid scrolling. For example, you can split long shell
Optionally specify a filename for the codeblock by including it after the opening backticks and language specifier:
```md
````md
```ts environment.ts
```
````
Optionally highlight lines by using `mark=${lineNumber}`.
```md
````md
```js mark=12:13
```
````
### 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.
**Prompt to add content listings:**
```text
Add a content listing block for [TOPIC] / [SECTION] (for example, Storage / Examples).
Follow CONTRIBUTING § Content listings in apps/docs.
Copy structure from `storageGetStarted` in apps/docs/data/content-listings/storage.data.ts.
Pick a globally-unique kebab-case id like `[topic]-[section]`.
Run `pnpm test:local lib/content-listings.test.ts` from apps/docs.
```
**Manually add content listings:**
1. Add or update a `ContentListingGroup` export in [`data/content-listings/[topic].data.ts`](data/content-listings/). The `id` field must be globally unique across all listing groups (e.g. `storage-get-started`, not just `get-started`) — it is used both as the lookup key and as the telemetry `listingId`.
2. Place the component inline in guide MDX, for example `<ContentListings id="storage-get-started" />`. Use a partial only when the block is reused or gated with `$Show` at the partial level.
3. Run `pnpm test:local lib/content-listings.test.ts` from `apps/docs`.
Code snippets for manually adding content listings are available in [`.vscode/content-listing.code-snippets`](../../.vscode/content-listing.code-snippets): `cl-data` (data export with namespaced id) and `cl-inline` (MDX component).
### Footnotes
@@ -284,7 +311,7 @@ Don't nest lists more than two deep.
3. List item
- List item
- List item
<!-- DON'T ADD ANOTHER LEVEL OF NESTING -->
<!-- DON'T ADD ANOTHER LEVEL OF NESTING -->
- Overly nested list item
```
@@ -0,0 +1,116 @@
'use client'
import type { ContentListingGroup, ContentListingItem } from '~/lib/content-listings.schema'
import {
getContentListingById,
getContentListingGroupLabel,
isExternalContentListingHref,
} from '~/lib/content-listings.utils'
import { useSendTelemetryEvent } from '~/lib/telemetry'
import Link from 'next/link'
import { useCallback } from 'react'
import { GlassPanel } from 'ui-patterns/GlassPanel'
import { Heading } from 'ui/src/components/CustomHTMLElements'
const GRID_ITEM_CLASS = {
2: 'col-span-12 md:col-span-6',
3: 'col-span-12 md:col-span-4',
4: 'col-span-12 md:col-span-3',
} as const
function useContentListingClickHandler(group: ContentListingGroup) {
const sendTelemetryEvent = useSendTelemetryEvent()
const groupLabel = getContentListingGroupLabel(group)
const trackClick = useCallback(
(item: ContentListingItem) => {
sendTelemetryEvent({
action: 'docs_content_listing_clicked',
properties: {
targetPath: item.href,
linkTitle: item.title,
...(groupLabel ? { groupTitle: groupLabel } : {}),
listingId: group.id,
},
})
},
[sendTelemetryEvent, group.id, groupLabel]
)
return { trackClick }
}
function ContentListingGroupHeading({ group }: { group: ContentListingGroup }) {
if (!group.heading) return null
return <Heading tag={group.headingLevel ?? 'h2'}>{group.heading}</Heading>
}
function ContentListingsGroup({ group }: { group: ContentListingGroup }) {
const { trackClick } = useContentListingClickHandler(group)
const isGrid = group.type === 'grid'
const listClassName = isGrid ? 'grid md:grid-cols-12 gap-4' : 'list-disc pl-6 space-y-2'
const gridItemClassName = isGrid ? GRID_ITEM_CLASS[group.columns ?? 3] : undefined
// Heading stays outside `not-prose` so it inherits the surrounding MDX prose
// typography. The list itself opts out so its explicit Tailwind layout wins.
return (
<section className="space-y-4">
<ContentListingGroupHeading group={group} />
<div className="not-prose space-y-4">
{group.description && <p className="text-foreground-light">{group.description}</p>}
<ul className={listClassName}>
{group.items.map((item) => {
const external = isExternalContentListingHref(item.href)
const key = `${group.id}-${item.href}`
if (isGrid) {
return (
<li key={key} className={gridItemClassName}>
<Link
href={item.href}
passHref
className="block h-full"
onClick={() => trackClick(item)}
target={external ? '_blank' : undefined}
>
<GlassPanel
title={item.title}
icon={item.icon}
hasLightIcon={Boolean(item.icon)}
>
{item.description}
</GlassPanel>
</Link>
</li>
)
}
return (
<li key={key}>
<Link
href={item.href}
onClick={() => trackClick(item)}
target={external ? '_blank' : undefined}
>
<strong>{item.title}</strong>: {item.description}
</Link>
</li>
)
})}
</ul>
</div>
</section>
)
}
export function ContentListings({ id }: { id: string }) {
const group = getContentListingById(id)
if (!group || !group.items.length) return null
return (
<div className="my-10 space-y-10">
<ContentListingsGroup group={group} />
</div>
)
}
@@ -0,0 +1 @@
export { ContentListings } from './ContentListings.client'
+6 -10
View File
@@ -1,7 +1,7 @@
---
id: 'auth'
title: 'Auth'
description: 'Use Supabase to Authenticate and Authorize your users.'
description: 'Use Supabase to authenticate and authorize your users.'
subtitle: 'Use Supabase to authenticate and authorize your users.'
tocVideo: '6ow_jW4epf8'
---
@@ -27,19 +27,15 @@ Auth uses your project's Postgres database under the hood, storing user data and
Auth also enables access control to your database's automatically generated [REST API](/docs/guides/api). When using Supabase SDKs, your data requests are automatically sent with the user's Auth Token. The Auth Token scopes database access on a row-by-row level when used along with [RLS policies](/docs/guides/database/postgres/row-level-security).
<ContentListings id="auth-get-started" />
<$Show if="authentication:show_providers">
<$Partial path="providers.mdx" />
</$Show>
<$Show if="billing:all">
## Pricing
Charges apply to Monthly Active Users (MAU), Monthly Active Third-Party Users (Third-Party MAU), and Monthly Active SSO Users (SSO MAU) and Advanced MFA Add-ons. For a detailed breakdown of how these charges are calculated, refer to the following pages:
- [Pricing MAU](/docs/guides/platform/manage-your-usage/monthly-active-users)
- [Pricing Third-Party MAU](/docs/guides/platform/manage-your-usage/monthly-active-users-third-party)
- [Pricing SSO MAU](/docs/guides/platform/manage-your-usage/monthly-active-users-sso)
- [Advanced MFA - Phone](/docs/guides/platform/manage-your-usage/advanced-mfa-phone)
<ContentListings id="auth-pricing" />
</$Show>
<ContentListings id="auth-next-steps" />
+7 -32
View File
@@ -1,44 +1,19 @@
---
id: 'database'
title: 'Database'
description: 'Every Supabase project is a full Postgres database. Learn how to connect, manage, and secure your data.'
subtitle: 'Use Supabase to connect, manage, and secure your Postgres database.'
description: 'Use Supabase to connect, manage, and secure your Postgres database.'
sidebar_label: 'Overview'
---
Every Supabase project has a full [Postgres](https://www.postgresql.org/) database — not a Postgres abstraction.
Every Supabase project gets a full [Postgres](https://www.postgresql.org/) database, not a Postgres abstraction. This database is the foundation that Auth, Storage, Realtime, and Edge Functions are built on, and Supabase manages daily database backups and offers point-in-time recovery on paid plans.
<Admonition type="tip" label="Working with your database">
You can work with a project's database in several ways:
Work with your project's database in the following ways:
- Visually using the [**Table Editor**](/dashboard/project/_/editor) section of the Dashboard.
- With query syntax using the [**SQL Editor**](/dashboard/project/_/sql) section of the Dashboard.
- Programmatically using a variety of methods.
- Programmatically using a variety of different methods.
Read the [Connect to your database](/docs/guides/database/connecting-to-postgres#how-to-connect-to-your-postgres-databases) guide for details on connection strings and options.
<ContentListings id="database-get-started" />
</Admonition>
The database is the foundation that Auth, Storage, Realtime, and Edge Functions are built on, and Supabase manages daily database backups and offers point-in-time recovery on paid plans.
## Get started
If you're new to the database section, these are the pages to read first:
- **[Connect to your database](/docs/guides/database/connecting-to-postgres)**: Connection strings, the Supavisor connection pooler, and when to use direct, transaction, or session mode.
- **[Tables and data](/docs/guides/database/tables)**: Create tables and relationships, and edit rows from the Dashboard.
- **[Import data](/docs/guides/database/import-data)**: Load existing data from CSV files, `pg_dump`, or another Postgres database.
- **[Secure your data](/docs/guides/database/secure-data)**: Row Level Security (RLS) is how Supabase makes the database safe to query directly from the client. Read this before exposing any table to your app.
- **[Extensions](/docs/guides/database/extensions)**: Enable Postgres extensions — including `pgvector` for embeddings, `PostGIS` for geospatial data, and `pg_cron` for scheduled jobs — from the Dashboard.
- **[Run SQL commands](/dashboard/project/_/sql)**: Use the Dashboard's SQL Editor for ad-hoc queries and saved snippets.
## Going further
Once you've covered the basics, these guides help with other use cases and features:
- **[Database functions](/docs/guides/database/functions)** and **[triggers](/docs/guides/database/postgres/triggers)**: Run logic inside the database in response to inserts, updates, or deletes.
- **[Database webhooks](/docs/guides/database/webhooks)**: Send row changes to an external HTTP endpoint.
- **[Replication and read replicas](/docs/guides/database/replication)**: Stream changes to other systems or read from a geographically closer replica.
- **[Backups](/docs/guides/platform/backups)**: Daily backups on every project, with point-in-time recovery on paid plans. Backups cover the database itself; objects stored through the Storage API are not included.
- **[Query performance and optimization](/docs/guides/database/query-optimization)**: Indexes, the query planner, and tools for finding slow queries.
- **[Roles and permissions](/docs/guides/database/postgres/roles)**: The Postgres roles Supabase ships with and how to add your own.
<ContentListings id="database-next-steps" />
+13 -290
View File
@@ -1,8 +1,8 @@
---
id: 'functions'
title: 'Edge Functions'
description: 'Globally distributed TypeScript functions.'
subtitle: 'Globally distributed TypeScript functions.'
description: 'Run TypeScript functions globally at the edge.'
subtitle: 'Run TypeScript functions globally at the edge.'
sidebar_label: 'Overview'
tocVideo: 'za_loEtS4gs'
---
@@ -41,295 +41,18 @@ Edge Functions are server-side TypeScript functions, distributed globally at the
- Sending transactional emails.
- Building messaging bots for Slack, Discord, etc.
<div className="not-prose">
<Button size="medium" asChild>
<a href="/docs/guides/functions/quickstart">Get started</a>
</Button>
</div>
<ContentListings id="functions-get-started" />
## Examples
Check out the [Edge Function Examples](https://github.com/supabase/supabase/tree/master/examples/edge-functions) in our GitHub repository.
Check out [Supabase Edge Function Examples](https://github.com/supabase/supabase/tree/master/examples/edge-functions) in GitHub or try these examples:
<div className="grid md:grid-cols-12 gap-4 not-prose">
<div className="col-span-4">
<Link href="/guides/functions/auth" passHref>
<GlassPanel icon={'/docs/img/icons/github-icon'} hasLightIcon={true} title="With supabase-js">
Use the Supabase client inside your Edge Function.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link href="/guides/functions/kysely-postgres" passHref>
<GlassPanel
icon={'/docs/img/icons/github-icon'}
hasLightIcon={true}
title="Type-Safe SQL with Kysely"
>
Combining Kysely with Deno Postgres gives you a convenient developer experience for
interacting directly with your Postgres database.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link href="/guides/functions/examples/sentry-monitoring" passHref>
<GlassPanel
icon={'/docs/img/icons/github-icon'}
hasLightIcon={true}
title="Monitoring with Sentry"
>
Monitor Edge Functions with the Sentry Deno SDK.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link href="/guides/functions/cors" passHref>
<GlassPanel
icon={'/docs/img/icons/github-icon'}
hasLightIcon={true}
title="With CORS headers"
>
Send CORS headers for invoking from the browser.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link
href="https://github.com/supabase-community/expo-stripe-payments-with-supabase-functions"
passHref
>
<GlassPanel
icon={'/docs/img/icons/github-icon'}
hasLightIcon={true}
title="React Native with Stripe"
>
Full example for using Supabase and Stripe, with Expo.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link
href="https://github.com/supabase-community/flutter-stripe-payments-with-supabase-functions"
passHref
>
<GlassPanel
icon={'/docs/img/icons/github-icon'}
hasLightIcon={true}
title="Flutter with Stripe"
>
Full example for using Supabase and Stripe, with Flutter.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link
href="https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/restful-tasks/index.ts"
passHref
>
<GlassPanel
icon={'/docs/img/icons/github-icon'}
hasLightIcon={true}
title="Building a RESTful Service API"
>
Learn how to use HTTP methods and paths to build a RESTful service for managing tasks.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link
href="https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/read-storage/index.ts"
passHref
>
<GlassPanel
icon={'/docs/img/icons/github-icon'}
hasLightIcon={true}
title="Working with Supabase Storage"
>
An example on reading a file from Supabase Storage.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link href="/guides/functions/examples/og-image" passHref>
<GlassPanel
icon={'/docs/img/icons/github-icon'}
hasLightIcon={true}
title="Open Graph Image Generation"
>
Generate Open Graph images with Deno and Supabase Edge Functions.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link
href="https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/og-image-with-storage-cdn"
passHref
>
<GlassPanel
icon={'/docs/img/icons/github-icon'}
hasLightIcon={true}
title="OG Image Generation & Storage CDN Caching"
>
Cache generated images with Supabase Storage CDN.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link
href="https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/location"
passHref
>
<GlassPanel
icon={'/docs/img/icons/github-icon'}
hasLightIcon={true}
title="Get User Location"
>
Get user location data from user's IP address.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link href="/guides/functions/examples/cloudflare-turnstile" passHref>
<GlassPanel
icon={'/docs/img/icons/github-icon'}
hasLightIcon={true}
title="Cloudflare Turnstile"
>
Protecting Forms with Cloudflare Turnstile.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link href="/guides/functions/connect-to-postgres" passHref>
<GlassPanel
icon={'/docs/img/icons/github-icon'}
hasLightIcon={true}
title="Connect to Postgres"
>
Connecting to Postgres from Edge Functions.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link href="/guides/functions/examples/github-actions" passHref>
<GlassPanel icon={'/docs/img/icons/github-icon'} hasLightIcon={true} title="GitHub Actions">
Deploying Edge Functions with GitHub Actions.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link
href="https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/oak-server"
passHref
>
<GlassPanel
icon={'/docs/img/icons/github-icon'}
hasLightIcon={true}
title="Oak Server Middleware"
>
Request Routing with Oak server middleware.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link href="/guides/ai/examples/huggingface-image-captioning" passHref>
<GlassPanel icon={'/docs/img/icons/github-icon'} hasLightIcon={true} title="Hugging Face">
Access 100,000+ Machine Learning models.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link href="/guides/functions/examples/amazon-bedrock-image-generator" passHref>
<GlassPanel icon={'/docs/img/icons/github-icon'} hasLightIcon={true} title="Amazon Bedrock">
Amazon Bedrock Image Generator
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link href="/guides/ai/examples/openai" passHref>
<GlassPanel icon={'/docs/img/icons/github-icon'} hasLightIcon={true} title="OpenAI">
Using OpenAI in Edge Functions.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link href="/guides/functions/examples/stripe-webhooks" passHref>
<GlassPanel icon={'/docs/img/icons/github-icon'} hasLightIcon={true} title="Stripe Webhooks">
Handling signed Stripe Webhooks with Edge Functions.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link href="/guides/functions/examples/send-emails" passHref>
<GlassPanel icon={'/docs/img/icons/github-icon'} hasLightIcon={true} title="Send emails">
Send emails in Edge Functions with Resend.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link
href="https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/streams"
passHref
>
<GlassPanel icon={'/docs/img/icons/github-icon'} hasLightIcon={true} title="Web Stream">
Server-Sent Events in Edge Functions.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link href="/guides/functions/examples/screenshots" passHref>
<GlassPanel icon={'/docs/img/icons/github-icon'} hasLightIcon={true} title="Puppeteer">
Generate screenshots with Puppeteer.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link href="/guides/functions/examples/discord-bot" passHref>
<GlassPanel icon={'/docs/img/icons/github-icon'} hasLightIcon={true} title="Discord Bot">
Building a Slash Command Discord Bot with Edge Functions.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link href="/guides/functions/examples/telegram-bot" passHref>
<GlassPanel icon={'/docs/img/icons/github-icon'} hasLightIcon={true} title="Telegram Bot">
Building a Telegram Bot with Edge Functions.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link
href="https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/file-upload-storage"
passHref
>
<GlassPanel icon={'/docs/img/icons/github-icon'} hasLightIcon={true} title="Upload File">
Process multipart/form-data.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link href="/guides/functions/examples/upstash-redis" passHref>
<GlassPanel icon={'/docs/img/icons/github-icon'} hasLightIcon={true} title="Upstash Redis">
Build an Edge Functions Counter with Upstash Redis.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link href="/guides/functions/examples/rate-limiting" passHref>
<GlassPanel icon={'/docs/img/icons/github-icon'} hasLightIcon={true} title="Rate Limiting">
Rate Limiting Edge Functions with Upstash Redis.
</GlassPanel>
</Link>
</div>
<div className="col-span-4">
<Link href="/guides/functions/examples/slack-bot-mention" passHref>
<GlassPanel
icon={'/docs/img/icons/github-icon'}
hasLightIcon={true}
title="Slack Bot Mention Edge Function"
>
Slack Bot handling Slack mentions in Edge Function
</GlassPanel>
</Link>
</div>
</div>
<ContentListings id="functions-examples-supabase" />
<ContentListings id="functions-examples-webhooks-payments" />
<ContentListings id="functions-examples-ai-media" />
<ContentListings id="functions-examples-messaging" />
<ContentListings id="functions-examples-operations" />
+1 -30
View File
@@ -5,36 +5,7 @@ description: 'Resources for getting started with Supabase.'
hideToc: true
---
<div className="flex flex-col gap-12 my-12">
<div>
<div className="grid grid-cols-12 gap-6 not-prose">
<Link href="/guides/ai-tools" className="col-span-12 md:col-span-4" passHref>
<GlassPanel
title="Build with AI tools"
hasLightIcon={true}
background={false}
showIconBg={true}
>
Develop with Supabase AI-first using plugins, MCP, and skills.
</GlassPanel>
</Link>
<Link href="/guides/getting-started/api-keys" className="col-span-12 md:col-span-4" passHref>
<GlassPanel title="API Keys" hasLightIcon={true} background={false} showIconBg={true}>
Learn about the different API keys in Supabase and how to use them.
</GlassPanel>
</Link>
<Link href="/guides/local-development" className="col-span-12 md:col-span-4" passHref>
<GlassPanel title="Local Development" hasLightIcon={true} background={false} showIconBg={true}>
Use the Supabase CLI to develop locally and collaborate between teams.
</GlassPanel>
</Link>
</div>
</div>
</div>
<ContentListings id="getting-started-overview" />
### Use cases
+3 -55
View File
@@ -20,60 +20,8 @@ Supabase provides a globally distributed [Realtime](https://github.com/supabase/
- **Multiplayer games** - Synchronized game state and player interactions
- **Social features** - Live notifications, reactions, and user activity feeds
Check the [Getting Started](/docs/guides/realtime/getting_started) guide to get started.
<ContentListings id="realtime-get-started" />
## Examples
<ContentListings id="realtime-examples" />
<div className="grid md:grid-cols-12 gap-4 not-prose">
<div className="col-span-6">
<Link href="https://multiplayer.dev" target="_blank" passHref>
<GlassPanel title="Multiplayer.dev">
Showcase application displaying cursor movements and chat messages using Broadcast.
</GlassPanel>
</Link>
</div>
<div className="col-span-6">
<Link href="https://supabase.com/ui/docs/nextjs/realtime-chat" target="_blank" passHref>
<GlassPanel title="Chat">
Supabase UI chat component using Broadcast to send message between users.
</GlassPanel>
</Link>
</div>
<div className="col-span-6">
<Link href="https://supabase.com/ui/docs/nextjs/realtime-avatar-stack" target="_blank" passHref>
<GlassPanel title="Avatar Stack">
Supabase UI avatar stack component using Presence to track connected users.
</GlassPanel>
</Link>
</div>
<div className="col-span-6">
<Link href="https://supabase.com/ui/docs/nextjs/realtime-cursor" target="_blank" passHref>
<GlassPanel title="Realtime Cursor">
Supabase UI realtime cursor component using Broadcast to share users' cursors to build
collaborative applications.
</GlassPanel>
</Link>
</div>
</div>
## Resources
Find the source code and documentation in the Supabase GitHub repository.
<div className="grid md:grid-cols-12 gap-4 not-prose">
<div className="col-span-6">
<Link href="https://github.com/supabase/realtime" passHref>
<GlassPanel title="Supabase Realtime">View the source code.</GlassPanel>
</Link>
</div>
<div className="col-span-6">
<Link
href="https://supabase.com/blog/supabase-realtime-multiplayer-general-availability"
passHref
>
<GlassPanel title="Realtime: Multiplayer Edition">
Read more about Supabase Realtime.
</GlassPanel>
</Link>
</div>
</div>
<ContentListings id="realtime-resources" />
+3 -86
View File
@@ -17,91 +17,8 @@ Supabase Storage is a robust, scalable solution for managing files of any size w
- **Fine-grained Access Control** - Manage file permissions with row-level security and custom policies
- **Multiple Bucket Types** - Specialized storage solutions for different use cases
## Storage bucket types
<ContentListings id="storage-get-started" />
Supabase Storage offers different bucket types optimized for specific use cases:
<ContentListings id="storage-examples" />
### Files buckets
Store and serve traditional files including images, videos, documents, and general-purpose content. Ideal for user-generated content, media libraries, and asset management.
**Use cases:** Images, videos, documents, PDFs, archives
**Features:**
- Global CDN delivery
- Image optimization and transformation
- Row-level security integration
- Direct URL access for files
[Learn more about Files Buckets](/docs/guides/storage/quickstart)
### Analytics buckets
Purpose-built for storing and analyzing data in open table formats like Apache Iceberg. Perfect for time-series data, logs, and large-scale analytical workloads.
**Use cases:** Data lakes, analytics pipelines, ETL operations, historical data analysis
**Features:**
- Apache Iceberg table format support
- SQL-accessible via Postgres foreign tables
- Partitioned data organization
- Efficient data querying and transformation
[Learn more about Analytics Buckets](/docs/guides/storage/analytics/introduction)
### Vector buckets
Specialized storage for vector embeddings and similarity search operations. Designed for AI and ML applications requiring semantic search capabilities.
**Use cases:** AI-powered search, semantic similarity matching, embedding storage, RAG systems
**Features:**
- Optimized vector indexing (HNSW, Flat)
- Multiple distance metrics (cosine, euclidean, L2)
- Metadata filtering for vectors
- Similarity search queries
[Learn more about Vector Buckets](/docs/guides/storage/vector/introduction)
## Examples
Check out all of the Storage [templates and examples](https://github.com/supabase/supabase/tree/master/examples/storage) in our GitHub repository.
<div className="grid md:grid-cols-12 gap-4 not-prose">
<div className="col-span-12">
<Link
href="https://github.com/supabase/supabase/tree/master/examples/storage/resumable-upload-uppy"
passHref
>
<GlassPanel
icon={'/docs/img/icons/github-icon'}
hasLightIcon={true}
title="Resumable Uploads with Uppy"
>
Use Uppy to upload files to Supabase Storage using the TUS protocol (resumable uploads).
</GlassPanel>
</Link>
</div>
</div>
## Resources
Find the source code and documentation in the Supabase GitHub repository.
<div className="grid md:grid-cols-12 gap-4 not-prose">
<div className="col-span-6">
<Link href="https://github.com/supabase/storage-api" passHref>
<GlassPanel title="Supabase Storage API">View the source code.</GlassPanel>
</Link>
</div>
<div className="col-span-6">
<Link href="https://supabase.github.io/storage/" passHref>
<GlassPanel title="OpenAPI Spec">
See the Swagger Documentation for Supabase Storage.
</GlassPanel>
</Link>
</div>
</div>
<ContentListings id="storage-resources" />
@@ -0,0 +1,102 @@
import type { ContentListingGroup } from '~/lib/content-listings.schema'
export const authGetStarted: ContentListingGroup = {
id: 'auth-get-started',
heading: 'Get started',
description: "Start here if you're new to Supabase Auth:",
type: 'grid',
items: [
{
title: 'Auth with email and password',
href: '/guides/auth/passwords',
description: 'Sign up and sign in users with email and password.',
},
{
title: 'Server-side rendering',
href: '/guides/auth/server-side',
description: 'Create a Supabase client for SSR frameworks like Next.js and SvelteKit.',
},
{
title: 'Row Level Security',
href: '/guides/database/postgres/row-level-security',
description: 'Use RLS policies to authorize data access from the client.',
},
],
}
export const authPricing: ContentListingGroup = {
id: 'auth-pricing',
heading: 'Pricing',
description:
'Charges apply to Monthly Active Users (MAU), Monthly Active Third-Party Users (Third-Party MAU), and Monthly Active SSO Users (SSO MAU) and Advanced MFA Add-ons. For a detailed breakdown of how these charges are calculated, refer to the following pages.',
type: 'list',
items: [
{
title: 'Pricing MAU',
href: '/guides/platform/manage-your-usage/monthly-active-users',
description: 'How MAU usage is measured and billed.',
},
{
title: 'Pricing Third-Party MAU',
href: '/guides/platform/manage-your-usage/monthly-active-users-third-party',
description: 'How third-party auth MAU is measured and billed.',
},
{
title: 'Pricing SSO MAU',
href: '/guides/platform/manage-your-usage/monthly-active-users-sso',
description: 'How SSO MAU usage is measured and billed.',
},
{
title: 'Advanced MFA - Phone',
href: '/guides/platform/manage-your-usage/advanced-mfa-phone',
description: 'How Advanced MFA Phone add-on usage is measured and billed.',
},
],
}
export const authNextSteps: ContentListingGroup = {
id: 'auth-next-steps',
heading: 'Next steps',
description:
"Once you've covered the basics, these guides help with other use cases and features:",
type: 'grid',
columns: 4,
items: [
{
title: 'Email (Magic link or OTP)',
href: '/guides/auth/auth-email-passwordless',
description:
'Sign up and sign in users with a Magic Link or email OTP instead of a password.',
},
{
title: 'Enterprise SSO',
href: '/guides/auth/enterprise-sso',
description: 'Add Single Sign-On for enterprise applications with SAML 2.0.',
},
{
title: 'User sessions',
href: '/guides/auth/sessions',
description: 'Control session lifetime, refresh tokens, and multi-device sign-in behavior.',
},
{
title: 'Third-party auth',
href: '/guides/auth/third-party/overview',
description: 'Use Clerk, Auth0, Firebase Auth, Cognito, or WorkOS JWTs with Supabase APIs.',
},
{
title: 'Multi-factor authentication',
href: '/guides/auth/auth-mfa',
description: 'Add a second factor to user sign-in with TOTP or phone.',
},
{
title: 'JWTs',
href: '/guides/auth/jwts',
description: 'Understand how Supabase Auth issues and validates JWTs.',
},
{
title: 'Auth Hooks',
href: '/guides/auth/auth-hooks',
description: 'Customize Auth behavior with Postgres functions at key lifecycle points.',
},
],
}
@@ -0,0 +1,90 @@
import type { ContentListingGroup } from '~/lib/content-listings.schema'
export const databaseGetStarted: ContentListingGroup = {
id: 'database-get-started',
heading: 'Get started',
description: "If you're new to the database section, these are the pages to read first:",
type: 'grid',
columns: 2,
items: [
{
title: 'Connect to your database',
href: '/guides/database/connecting-to-postgres',
description:
'Connection strings, the Supavisor connection pooler, and when to use direct, transaction, or session mode.',
},
{
title: 'Tables and data',
href: '/guides/database/tables',
description: 'Create tables and relationships, and edit rows from the Dashboard.',
},
{
title: 'Import data',
href: '/guides/database/import-data',
description: 'Load existing data from CSV files, `pg_dump`, or another Postgres database.',
},
{
title: 'Secure your data',
href: '/guides/database/secure-data',
description:
'Row Level Security (RLS) is how Supabase makes the database safe to query directly from the client. Read this before exposing any table to your app.',
},
{
title: 'Extensions',
href: '/guides/database/extensions',
description:
'Add Postgres extensions from the Dashboard, including `pgvector` for embeddings, `PostGIS` for geospatial data, and `pg_cron` for scheduled jobs.',
},
{
title: 'Run SQL commands',
href: '/dashboard/project/_/sql',
description: "Use the Dashboard's SQL Editor for ad-hoc queries and saved snippets.",
},
],
}
export const databaseNextSteps: ContentListingGroup = {
id: 'database-next-steps',
heading: 'Next steps',
description:
"Once you've covered the basics, these guides help with other use cases and features:",
type: 'grid',
items: [
{
title: 'Database functions',
href: '/guides/database/functions',
description: 'Run logic inside the database in response to inserts, updates, or deletes.',
},
{
title: 'Triggers',
href: '/guides/database/postgres/triggers',
description: 'Run logic inside the database in response to inserts, updates, or deletes.',
},
{
title: 'Database webhooks',
href: '/guides/database/webhooks',
description: 'Send row changes to an external HTTP endpoint.',
},
{
title: 'Replication and read replicas',
href: '/guides/database/replication',
description: 'Stream changes to other systems or read from a geographically closer replica.',
},
{
title: 'Backups',
href: '/guides/platform/backups',
description:
'Daily backups on every project, with point-in-time recovery on paid plans. Backups cover the database itself; objects stored through the Storage API are not included.',
},
{
title: 'Query performance and optimization',
href: '/guides/database/query-optimization',
description: 'Indexes, the query planner, and tools for finding slow queries.',
},
{
title: 'Roles and permissions',
href: '/guides/database/postgres/roles',
description: 'The Postgres roles Supabase ships with and how to add your own.',
},
],
}
@@ -0,0 +1,211 @@
import type { ContentListingGroup } from '~/lib/content-listings.schema'
export const functionsGetStarted: ContentListingGroup = {
id: 'functions-get-started',
heading: 'Get started',
type: 'grid',
columns: 2,
items: [
{
title: 'Edge Functions quickstart',
href: '/guides/functions/quickstart',
description: 'Create, test, and deploy your first Edge Function with the Supabase CLI.',
},
],
}
export const functionsExamplesSupabase: ContentListingGroup = {
id: 'functions-examples-supabase',
heading: 'Supabase integration',
headingLevel: 'h3',
type: 'grid',
items: [
{
title: 'With supabase-js',
href: '/guides/functions/auth',
description: 'Use the Supabase client inside your Edge Function.',
},
{
title: 'Connect to Postgres',
href: '/guides/functions/connect-to-postgres',
description: 'Connect to Postgres from Edge Functions.',
},
{
title: 'Type-Safe SQL with Kysely',
href: '/guides/functions/kysely-postgres',
description:
'Combine Kysely with Deno Postgres for a convenient developer experience when interacting directly with your Postgres database.',
},
{
title: 'With CORS headers',
href: '/guides/functions/cors',
description: 'Send CORS headers for invoking from the browser.',
},
{
title: 'Building a RESTful Service API',
href: 'https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/restful-tasks/index.ts',
icon: '/docs/img/icons/github-icon',
description:
'Learn how to use HTTP methods and paths to build a RESTful service for managing tasks.',
},
{
title: 'Oak Server Middleware',
href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/oak-server',
icon: '/docs/img/icons/github-icon',
description: 'Route requests with Oak server middleware.',
},
{
title: 'Web Stream',
href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/streams',
icon: '/docs/img/icons/github-icon',
description: 'Stream Server-Sent Events from Edge Functions.',
},
{
title: 'Get User Location',
href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/location',
icon: '/docs/img/icons/github-icon',
description: "Get user location data from user's IP address.",
},
{
title: 'Working with Supabase Storage',
href: 'https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/read-storage/index.ts',
icon: '/docs/img/icons/github-icon',
description: 'Read a file from Supabase Storage.',
},
{
title: 'Upload File',
href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/file-upload-storage',
icon: '/docs/img/icons/github-icon',
description: 'Process multipart/form-data.',
},
],
}
export const functionsExamplesWebhooksPayments: ContentListingGroup = {
id: 'functions-examples-webhooks-payments',
heading: 'Webhooks & payments',
headingLevel: 'h3',
type: 'grid',
items: [
{
title: 'Stripe Webhooks',
href: '/guides/functions/examples/stripe-webhooks',
description: 'Handle signed Stripe webhooks with Edge Functions.',
},
{
title: 'React Native with Stripe',
href: 'https://github.com/supabase-community/expo-stripe-payments-with-supabase-functions',
icon: '/docs/img/icons/github-icon',
description: 'Use Supabase and Stripe in a React Native app with Expo.',
},
{
title: 'Flutter with Stripe',
href: 'https://github.com/supabase-community/flutter-stripe-payments-with-supabase-functions',
icon: '/docs/img/icons/github-icon',
description: 'Use Supabase and Stripe in a Flutter app.',
},
],
}
export const functionsExamplesAiMedia: ContentListingGroup = {
id: 'functions-examples-ai-media',
heading: 'AI & media',
headingLevel: 'h3',
type: 'grid',
items: [
{
title: 'Hugging Face',
href: '/guides/ai/examples/huggingface-image-captioning',
description: 'Access 100,000+ Machine Learning models.',
},
{
title: 'OpenAI',
href: '/guides/ai/examples/openai',
description: 'Use OpenAI in Edge Functions.',
},
{
title: 'Amazon Bedrock',
href: '/guides/functions/examples/amazon-bedrock-image-generator',
description: 'Generate images with Amazon Bedrock in Edge Functions.',
},
{
title: 'Open Graph Image Generation',
href: '/guides/functions/examples/og-image',
description: 'Generate Open Graph images with Deno and Supabase Edge Functions.',
},
{
title: 'OG Image Generation & Storage CDN Caching',
href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/og-image-with-storage-cdn',
icon: '/docs/img/icons/github-icon',
description: 'Cache generated images with Supabase Storage CDN.',
},
{
title: 'Puppeteer',
href: '/guides/functions/examples/screenshots',
description: 'Generate screenshots with Puppeteer.',
},
],
}
export const functionsExamplesMessaging: ContentListingGroup = {
id: 'functions-examples-messaging',
heading: 'Bots & email',
headingLevel: 'h3',
type: 'grid',
items: [
{
title: 'Send emails',
href: '/guides/functions/examples/send-emails',
description: 'Send emails in Edge Functions with Resend.',
},
{
title: 'Discord Bot',
href: '/guides/functions/examples/discord-bot',
description: 'Build a slash command Discord bot with Edge Functions.',
},
{
title: 'Telegram Bot',
href: '/guides/functions/examples/telegram-bot',
description: 'Build a Telegram bot with Edge Functions.',
},
{
title: 'Slack Bot Mention Edge Function',
href: '/guides/functions/examples/slack-bot-mention',
description: 'Handle Slack mentions in a Slack bot Edge Function.',
},
],
}
export const functionsExamplesOperations: ContentListingGroup = {
id: 'functions-examples-operations',
heading: 'Operations & security',
headingLevel: 'h3',
type: 'grid',
items: [
{
title: 'Monitoring with Sentry',
href: '/guides/functions/examples/sentry-monitoring',
description: 'Monitor Edge Functions with the Sentry Deno SDK.',
},
{
title: 'GitHub Actions',
href: '/guides/functions/examples/github-actions',
description: 'Deploy Edge Functions with GitHub Actions.',
},
{
title: 'Upstash Redis',
href: '/guides/functions/examples/upstash-redis',
description: 'Build an Edge Functions Counter with Upstash Redis.',
},
{
title: 'Rate Limiting',
href: '/guides/functions/examples/rate-limiting',
description: 'Rate-limit Edge Functions with Upstash Redis.',
},
{
title: 'Cloudflare Turnstile',
href: '/guides/functions/examples/cloudflare-turnstile',
description: 'Protect forms with Cloudflare Turnstile.',
},
],
}
@@ -0,0 +1,23 @@
import type { ContentListingGroup } from '~/lib/content-listings.schema'
export const gettingStartedGetStarted: ContentListingGroup = {
id: 'getting-started-overview',
type: 'grid',
items: [
{
title: 'Build with AI tools',
href: '/guides/ai-tools',
description: 'Develop with Supabase AI-first using plugins, MCP, and skills.',
},
{
title: 'API Keys',
href: '/guides/getting-started/api-keys',
description: 'Learn about the different API keys in Supabase and how to use them.',
},
{
title: 'Local Development',
href: '/guides/local-development',
description: 'Use the Supabase CLI to develop locally and collaborate between teams.',
},
],
}
+40
View File
@@ -0,0 +1,40 @@
import type { ContentListingGroup } from '~/lib/content-listings.schema'
import { authGetStarted, authNextSteps, authPricing } from './auth.data'
import { databaseGetStarted, databaseNextSteps } from './database.data'
import {
functionsExamplesAiMedia,
functionsExamplesMessaging,
functionsExamplesOperations,
functionsExamplesSupabase,
functionsExamplesWebhooksPayments,
functionsGetStarted,
} from './functions.data'
import { gettingStartedGetStarted } from './getting-started.data'
import { realtimeExamples, realtimeGetStarted, realtimeResources } from './realtime.data'
import { storageExamples, storageGetStarted, storageResources } from './storage.data'
const ALL_GROUPS: readonly ContentListingGroup[] = [
authGetStarted,
authPricing,
authNextSteps,
databaseGetStarted,
databaseNextSteps,
functionsGetStarted,
functionsExamplesSupabase,
functionsExamplesWebhooksPayments,
functionsExamplesAiMedia,
functionsExamplesMessaging,
functionsExamplesOperations,
gettingStartedGetStarted,
realtimeGetStarted,
realtimeExamples,
realtimeResources,
storageGetStarted,
storageExamples,
storageResources,
]
export const CONTENT_LISTINGS: Readonly<Record<string, ContentListingGroup>> = Object.fromEntries(
ALL_GROUPS.map((group) => [group.id, group])
)
@@ -0,0 +1,66 @@
import type { ContentListingGroup } from '~/lib/content-listings.schema'
export const realtimeGetStarted: ContentListingGroup = {
id: 'realtime-get-started',
heading: 'Get started',
type: 'grid',
columns: 2,
items: [
{
title: 'Getting Started',
href: '/guides/realtime/getting_started',
description: 'Set up Realtime in your project and send your first message.',
},
],
}
export const realtimeExamples: ContentListingGroup = {
id: 'realtime-examples',
heading: 'Examples',
type: 'grid',
columns: 2,
items: [
{
title: 'Multiplayer.dev',
href: 'https://multiplayer.dev',
description:
'Showcase application displaying cursor movements and chat messages using Broadcast.',
},
{
title: 'Chat',
href: 'https://supabase.com/ui/docs/nextjs/realtime-chat',
description: 'Supabase UI chat component using Broadcast to send message between users.',
},
{
title: 'Avatar Stack',
href: 'https://supabase.com/ui/docs/nextjs/realtime-avatar-stack',
description: 'Supabase UI avatar stack component using Presence to track connected users.',
},
{
title: 'Realtime Cursor',
href: 'https://supabase.com/ui/docs/nextjs/realtime-cursor',
description:
"Supabase UI realtime cursor component using Broadcast to share users' cursors to build collaborative applications.",
},
],
}
export const realtimeResources: ContentListingGroup = {
id: 'realtime-resources',
heading: 'Resources',
description: 'Find the source code and documentation in the Supabase GitHub repository:',
type: 'grid',
columns: 2,
items: [
{
title: 'Supabase Realtime',
href: 'https://github.com/supabase/realtime',
description: 'View the source code.',
},
{
title: 'Realtime: Multiplayer Edition',
href: 'https://supabase.com/blog/supabase-realtime-multiplayer-general-availability',
description: 'Read more about Supabase Realtime.',
},
],
}
@@ -0,0 +1,72 @@
import type { ContentListingGroup } from '~/lib/content-listings.schema'
export const storageGetStarted: ContentListingGroup = {
id: 'storage-get-started',
heading: 'Get started',
description: 'Choose the bucket type that fits your use case:',
type: 'grid',
items: [
{
title: 'Files buckets',
href: '/guides/storage/quickstart',
description:
'Store and serve images, videos, documents, and general-purpose files with direct URL access and row-level security.',
},
{
title: 'Analytics buckets',
href: '/guides/storage/analytics/introduction',
description:
'Store data in Apache Iceberg tables for data lakes, logs, and Supabase Pipelines. Query from Postgres via foreign tables with partitioning.',
},
{
title: 'Vector buckets',
href: '/guides/storage/vector/introduction',
description:
'Store embeddings and run similarity search for semantic matching, AI, and RAG. Use HNSW indexing, distance metrics, and metadata filtering.',
},
],
}
export const storageExamples: ContentListingGroup = {
id: 'storage-examples',
heading: 'Examples',
description: 'Working sample projects for common Storage integration patterns:',
type: 'grid',
columns: 2,
items: [
{
title: 'Storage templates and examples',
href: 'https://github.com/supabase/supabase/tree/master/examples/storage',
description:
'Sample projects for resumable uploads, signed upload URLs, and serving map tiles from private buckets.',
},
{
title: 'Resumable Uploads with Uppy',
href: 'https://github.com/supabase/supabase/tree/master/examples/storage/resumable-upload-uppy',
icon: '/docs/img/icons/github-icon',
description:
'Upload large files with pause-and-resume support using Uppy and the TUS protocol.',
},
],
}
export const storageResources: ContentListingGroup = {
id: 'storage-resources',
heading: 'Resources',
description: 'Source code and REST API reference for the Storage service:',
type: 'grid',
columns: 2,
items: [
{
title: 'Supabase Storage API',
href: 'https://github.com/supabase/storage-api',
description: 'Amazon S3-compatible object storage service that stores metadata in Postgres.',
},
{
title: 'OpenAPI Spec',
href: 'https://supabase.github.io/storage/',
description:
'Interactive reference for Storage REST endpoints, request parameters, and response schemas.',
},
],
}
+6 -3
View File
@@ -5,6 +5,7 @@ import AuthProviders from '~/components/AuthProviders'
import { AuthSmsProviderConfig } from '~/components/AuthSmsProviderConfig'
import ButtonCard from '~/components/ButtonCard'
import { ComputeDiskLimitsTable } from '~/components/ComputeDiskLimitsTable'
import { ContentListings } from '~/components/ContentListings'
import { Extensions } from '~/components/Extensions'
import Image, { type ImageProps } from '~/components/Image'
import { Mermaid } from '~/components/Mermaid'
@@ -26,6 +27,7 @@ import { ShowUntil } from '~/features/ui/ShowUntil'
import { TabPanel, Tabs } from '~/features/ui/Tabs'
import { ArrowDown, Check, X } from 'lucide-react'
import Link from 'next/link'
import { type ComponentPropsWithoutRef } from 'react'
import { Badge, Button } from 'ui'
import { Admonition, type AdmonitionProps } from 'ui-patterns/admonition'
import { GlassPanel } from 'ui-patterns/GlassPanel'
@@ -74,6 +76,7 @@ const components = {
CodeSampleDummy,
CodeSampleWrapper,
ComputeDiskLimitsTable,
ContentListings,
ErrorCodes,
Extensions,
GlassPanel,
@@ -100,17 +103,17 @@ const components = {
TabPanel,
InfoTooltip,
a: MdxAnchor,
h2: (props: any) => (
h2: (props: ComponentPropsWithoutRef<'h2'>) => (
<Heading tag="h2" {...props}>
{props.children}
</Heading>
),
h3: (props: any) => (
h3: (props: ComponentPropsWithoutRef<'h3'>) => (
<Heading tag="h3" {...props}>
{props.children}
</Heading>
),
h4: (props: any) => (
h4: (props: ComponentPropsWithoutRef<'h4'>) => (
<Heading tag="h4" {...props}>
{props.children}
</Heading>
@@ -17,6 +17,7 @@ import { AuthProviders } from './markdown-schema/AuthProviders'
import { ComputeDiskLimitsTable } from './markdown-schema/ComputeDiskLimitsTable'
import { ErrorCodes } from './markdown-schema/ErrorCodes'
import { Link } from './markdown-schema/Link'
import { ContentListings } from './markdown-schema/ContentListings'
import { MetricsStackCards } from './markdown-schema/MetricsStackCards'
import { NavData } from './markdown-schema/NavData'
import { Panel } from './markdown-schema/Panel'
@@ -152,6 +153,7 @@ const SCHEMA: ComponentSchema = {
...StepHike,
TabPanel,
MetricsStackCards,
ContentListings,
NavData,
SharedData,
}
@@ -178,7 +180,9 @@ async function transformBody(content: string, data: Record<string, unknown>): Pr
const header = headerParts.join('\n\n')
return header ? `${header}\n\n${body}` : body
let output = header ? `${header}\n\n${body}` : body
return output
}
async function generateOne(sourceFile: string, frontmatter: FrontmatterFormat): Promise<string> {
@@ -0,0 +1,51 @@
import { withDocsBasePath } from '~/internals/internal-links'
import type { ContentListingGroup } from '~/lib/content-listings.schema'
import { getContentListingById, isExternalContentListingHref } from '~/lib/content-listings.utils'
import { getInternalLinkBaseUrl } from '../internal-links'
const HEADING_MARKDOWN: Record<'h2' | 'h3' | 'h4', string> = {
h2: '##',
h3: '###',
h4: '####',
}
export function serializeContentListingGroupToMarkdown(
group: ContentListingGroup,
linkBaseUrl: string
): string {
const lines: string[] = []
if (group.heading) {
const level = group.headingLevel ?? 'h2'
lines.push(`${HEADING_MARKDOWN[level]} ${group.heading}`)
lines.push('')
}
if (group.description) {
lines.push(group.description)
lines.push('')
}
for (const item of group.items) {
const href = isExternalContentListingHref(item.href)
? item.href
: `${linkBaseUrl}${withDocsBasePath(item.href)}`
lines.push(`- **[${item.title}](${href}):** ${item.description}`)
}
return lines.join('\n')
}
/**
* Markdown export handler for `<ContentListings id="..." />`. Looks up the
* group by id in the same data registry the React component uses.
*/
export const ContentListings = ({ props }: { props: Record<string, unknown> }): string => {
const id = typeof props.id === 'string' ? props.id : ''
if (!id) return ''
const group = getContentListingById(id)
if (!group) return ''
return serializeContentListingGroupToMarkdown(group, getInternalLinkBaseUrl())
}
+20
View File
@@ -0,0 +1,20 @@
import { z } from 'zod'
import {
contentListingGridColumnsSchema,
contentListingGroupSchema,
contentListingGroupTypeSchema,
contentListingHeadingLevelSchema,
contentListingItemSchema,
} from './content-listings.zod.mjs'
export {
contentListingGridColumnsSchema,
contentListingGroupSchema,
contentListingGroupTypeSchema,
contentListingHeadingLevelSchema,
contentListingItemSchema,
}
export type ContentListingItem = z.infer<typeof contentListingItemSchema>
export type ContentListingGroup = z.infer<typeof contentListingGroupSchema>
+166
View File
@@ -0,0 +1,166 @@
import { storageGetStarted } from '~/data/content-listings/storage.data'
import {
ContentListings as ContentListingsMarkdownHandler,
serializeContentListingGroupToMarkdown,
} from '~/internals/markdown-schema/ContentListings'
import { isExternalContentListingHref } from '~/lib/content-listings.utils'
import { describe, expect, it } from 'vitest'
describe('isExternalContentListingHref', () => {
it('treats protocol-relative URLs as external', () => {
expect(isExternalContentListingHref('//example.com/path')).toBe(true)
})
it('treats absolute http(s) URLs as external', () => {
expect(isExternalContentListingHref('https://github.com/supabase/storage-api')).toBe(true)
})
it('treats internal guide paths as not external', () => {
expect(isExternalContentListingHref('/guides/storage/quickstart')).toBe(false)
})
})
describe('serializeContentListingGroupToMarkdown', () => {
it('renders grouped items with absolute URLs and descriptions', () => {
const markdown = serializeContentListingGroupToMarkdown(
{
id: 'get-started',
heading: 'Get started',
description: 'Read these first.',
items: [
{
title: 'Connect to your database',
href: '/guides/database/connecting-to-postgres',
description: 'Connection strings and pooler modes.',
},
],
},
'https://supabase.com'
)
expect(markdown).toContain('## Get started')
expect(markdown).toContain('Read these first.')
expect(markdown).toContain(
'**[Connect to your database](https://supabase.com/docs/guides/database/connecting-to-postgres):** Connection strings and pooler modes.'
)
})
it('preserves external hrefs in markdown export', () => {
const markdown = serializeContentListingGroupToMarkdown(
{
id: 'resources',
items: [
{
title: 'Storage API',
href: 'https://github.com/supabase/storage-api',
description: 'View the source code.',
},
],
},
'https://supabase.com'
)
expect(markdown).toContain(
'**[Storage API](https://github.com/supabase/storage-api):** View the source code.'
)
})
it('respects heading-level in markdown export', () => {
const markdown = serializeContentListingGroupToMarkdown(
{
id: 'get-started',
heading: 'Get started',
headingLevel: 'h3',
items: [
{
title: 'Connect',
href: '/guides/database/connecting-to-postgres',
description: 'Connection strings.',
},
],
},
''
)
expect(markdown).toContain('### Get started')
})
it('renders heading and description only once', () => {
const markdown = serializeContentListingGroupToMarkdown(
{
id: 'get-started',
heading: 'Get started',
description: 'Read these first.',
items: [
{
title: 'Connect',
href: '/guides/database/connecting-to-postgres',
description: 'Connection strings.',
},
],
},
''
)
expect(markdown.match(/^## Get started$/gm)).toHaveLength(1)
expect(markdown.match(/^Read these first\.$/gm)).toHaveLength(1)
expect(markdown).toBe(
'## Get started\n\nRead these first.\n\n- **[Connect](/docs/guides/database/connecting-to-postgres):** Connection strings.'
)
})
it('omits heading line when heading is not set', () => {
const markdown = serializeContentListingGroupToMarkdown(
{
id: 'get-started',
items: [
{
title: 'Connect',
href: '/guides/database/connecting-to-postgres',
description: 'Connection strings.',
},
],
},
''
)
expect(markdown).not.toMatch(/^#+\s/m)
expect(markdown).toContain('**[Connect]')
})
})
describe('ContentListings markdown handler', () => {
it('serializes the group looked up by id', () => {
const markdown = ContentListingsMarkdownHandler({ props: { id: 'storage-get-started' } })
expect(markdown).toContain('## Get started')
expect(markdown).toContain('Files buckets')
expect(markdown).toContain('/guides/storage/quickstart')
})
it('returns empty string when id is missing or unknown', () => {
expect(ContentListingsMarkdownHandler({ props: {} })).toBe('')
expect(ContentListingsMarkdownHandler({ props: { id: 'nonexistent-id' } })).toBe('')
})
it('matches direct serializeContentListingGroupToMarkdown for the same data', () => {
const handler = ContentListingsMarkdownHandler({ props: { id: 'storage-get-started' } })
const direct = serializeContentListingGroupToMarkdown(storageGetStarted, '')
expect(handler).toBe(direct)
})
})
describe('TelemetryEvent union', () => {
it('includes docs_content_listing_clicked', () => {
const event = {
action: 'docs_content_listing_clicked' as const,
properties: {
targetPath: '/guides/storage',
linkTitle: 'Storage',
},
}
const _typeCheck: import('common/telemetry-constants').TelemetryEvent = event
expect(_typeCheck.action).toBe('docs_content_listing_clicked')
})
})
+16
View File
@@ -0,0 +1,16 @@
import { CONTENT_LISTINGS } from '~/data/content-listings'
import type { ContentListingGroup } from './content-listings.schema'
/** Label for telemetry — prefers heading, falls back to id. */
export function getContentListingGroupLabel(group: ContentListingGroup): string {
return group.heading ?? group.id
}
export function isExternalContentListingHref(href: string): boolean {
return /^https?:\/\//i.test(href) || href.startsWith('//')
}
export function getContentListingById(id: string): ContentListingGroup | undefined {
return CONTENT_LISTINGS[id]
}
+24
View File
@@ -0,0 +1,24 @@
import { z } from 'zod'
export const contentListingItemSchema = z.object({
title: z.string().min(1),
href: z.string().min(1),
description: z.string().min(1),
icon: z.string().min(1).optional(),
})
export const contentListingGroupTypeSchema = z.enum(['list', 'grid'])
export const contentListingGridColumnsSchema = z.union([z.literal(2), z.literal(3), z.literal(4)])
export const contentListingHeadingLevelSchema = z.enum(['h2', 'h3', 'h4'])
export const contentListingGroupSchema = z.object({
id: z.string().min(1),
heading: z.string().min(1).optional(),
headingLevel: contentListingHeadingLevelSchema.optional(),
description: z.string().optional(),
type: contentListingGroupTypeSchema.optional(),
columns: contentListingGridColumnsSchema.optional(),
items: z.array(contentListingItemSchema).min(1),
})
+17
View File
@@ -892,6 +892,22 @@ export interface AskAiClickedEvent {
}
}
/**
* User clicked a curated orientation link from a content listings MDX component.
*
* @group Events
* @source docs
*/
export interface DocsContentListingClickedEvent {
action: 'docs_content_listing_clicked'
properties: {
targetPath: string
linkTitle: string
groupTitle?: string
listingId?: string
}
}
/**
* User clicked a recommended page card shown on a docs "not found" page.
*
@@ -3553,6 +3569,7 @@ export type TelemetryEvent =
| DocsFeedbackClickedEvent
| CopyAsMarkdownClickedEvent
| AskAiClickedEvent
| DocsContentListingClickedEvent
| Docs404RecommendationClickedEvent
| HomepageFrameworkQuickstartClickedEvent
| HomepageProductCardClickedEvent