mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 01:15:03 +03:00
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) | | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | |  |  | #### 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) | | -------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | |  |  | #### 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) | | -------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | |  |  | #### 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) | | ---------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | |  |  | #### 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) | | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | |  |  | #### 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) | | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | |  |  | ### 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:
26 files changed
+1119
-511
No files matched your search
+2
-1
@@ -112,7 +112,8 @@ next-env.d.ts
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
.vscode
|
||||
.vscode/*
|
||||
!.vscode/content-listing.code-snippets
|
||||
.idea
|
||||
.vercel
|
||||
|
||||
|
||||
+28
@@ -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}\" />"]
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
@@ -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" />
|
||||
@@ -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" />
|
||||
@@ -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" />
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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" />
|
||||
@@ -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.',
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -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.',
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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),
|
||||
})
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user