Remove skills page from ui library (#47947)

<img width="1033" height="861" alt="image"
src="https://github.com/user-attachments/assets/54a104df-1db9-4b97-89db-6eec671b3af7"
/>

Removes the above AI Skills page from our ui library and instead
redirects to the more up to date ai skills docs page.

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

* **New Features**
* Added a direct “Install Skills” link to the AI Skills documentation
guide.
* Added a permanent redirect from the legacy prompts URL to the new AI
Skills guide.

* **Documentation**
* Updated the docs side navigation to list component pages under
“Blocks”.
* Removed the AI Skills page content and its navigation/search entries.
* Removed the AI editor rules documentation/registry entries, so they no
longer appear in the generated documentation set.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
This commit is contained in:
Saxon FletcherandIvan Vasilov authored and GitHub committed 2026-07-15 15:08:34 +02:00
1 parent 069051b032
commit 932b5dc3b8
16 files changed
+9 -1374

No files matched your search

+1 -1
View File
@@ -50,7 +50,7 @@ export default function Home() {
<Link href="/docs/getting-started/quickstart">Get Started</Link>
</Button_Shadcn_>
<Button_Shadcn_ variant="outline" size="lg">
<Link href="/docs/ai-editors-rules/skills">Install Skills</Link>
<a href="/docs/guides/ai-tools/ai-skills">Install Skills</a>
</Button_Shadcn_>
</div>
</div>
+1 -10
View File
@@ -3,7 +3,7 @@ import Link from 'next/link'
import { CommandMenu } from './command-menu'
import { ThemeSwitcherDropdown } from './theme-switcher-dropdown'
import NavigationItem from '@/components/side-navigation-item'
import { aiEditorsRules, componentPages, gettingStarted, platformBlocks } from '@/config/docs'
import { componentPages, gettingStarted, platformBlocks } from '@/config/docs'
function SideNavigation() {
return (
@@ -88,15 +88,6 @@ function SideNavigation() {
))}
</div>
</div>
<div className="pb-6 flex-1">
<div className="font-mono uppercase text-xs text-foreground-lighter/75 mb-2 px-6 tracking-widest">
{aiEditorsRules.title}
</div>
{aiEditorsRules.items.map((item, i) => (
<NavigationItem item={item} key={`${item.href}-${i}`} />
))}
</div>
<div className="pb-6">
<div className="font-mono uppercase text-xs text-foreground-lighter/75 mb-2 px-6 tracking-widest">
{platformBlocks.title}
-17
View File
@@ -24,19 +24,6 @@ export const gettingStarted: SidebarNavGroup = {
],
}
export const aiEditorsRules: SidebarNavGroup = {
title: 'AI Skills',
items: [
{
title: 'Skills',
href: '/docs/ai-editors-rules/skills',
items: [],
new: true,
commandItemLabel: 'AI Skills',
},
],
}
export const platformBlocks: SidebarNavGroup = {
title: 'Platform',
items: [
@@ -142,10 +129,6 @@ export const COMMAND_ITEMS = [
label: item.commandItemLabel,
href: item.href,
})),
...aiEditorsRules.items.map((item) => ({
label: item.commandItemLabel,
href: item.href,
})),
...componentPages.items.map((item) => ({
label: item.commandItemLabel,
href: item.href,
@@ -1,42 +0,0 @@
---
title: Skills
description: Agent Skills for Supabase — procedural knowledge and context that AI agents can load on demand
---
Agent Skills are folders of instructions, scripts, and resources that agents can discover and use to do things more accurately and efficiently. Skills give agents access to procedural knowledge and Supabase-specific context they can load on demand, so they can do real work reliably.
Skills work with 18+ AI agents including Claude Code, GitHub Copilot, Cursor, Cline, and many others.
## Installation
Run these commands in the root of your project — skills are installed per-project, similar to how you'd add a config file or dev dependency. Once installed, your AI agent will automatically pick them up the next time it runs.
Install all Supabase skills using the skills CLI:
```bash
npx skills add supabase/agent-skills
```
This installs the full set of Supabase skills — the right choice for most projects. If you're doing a lot of database work, also add the Postgres-specific skill:
```bash
npx skills add supabase/agent-skills --skill supabase-postgres-best-practices
```
The `supabase-postgres-best-practices` skill covers Postgres performance optimization, query design, and schema best practices. It's used automatically when writing, reviewing, or optimizing queries and database configurations.
### Claude Code
You can also install the skills as Claude Code plugins:
```bash
/plugin marketplace add supabase/agent-skills
/plugin install supabase@supabase-agent-skills
```
## Learn more
For the full list of available skills, installation options, and usage guidance, see the [Agent Skills documentation](https://supabase.com/docs/guides/getting-started/ai-skills).
- [Agent Skills Repository](https://github.com/supabase/agent-skills)
- [Agent Skills Documentation](https://agentskills.io/home)
@@ -1,47 +0,0 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "ai-editor-rules",
"type": "registry:file",
"title": "Prompt rules for AI Editors",
"description": "Prompts for working with Supabase using AI-powered IDE tools",
"dependencies": [],
"registryDependencies": [],
"files": [
{
"path": "registry/default/ai-editor-rules/create-db-functions.mdc",
"content": "---\n# Specify the following for Cursor rules\ndescription: Guidelines for writing Supabase database functions\nalwaysApply: false\n---\n\n# Database: Create functions\n\nYou're a Supabase Postgres expert in writing database functions. Generate **high-quality PostgreSQL functions** that adhere to the following best practices:\n\n## General Guidelines\n\n1. **Default to `SECURITY INVOKER`:**\n\n - Functions should run with the permissions of the user invoking the function, ensuring safer access control.\n - Use `SECURITY DEFINER` only when explicitly required and explain the rationale.\n\n2. **Set the `search_path` Configuration Parameter:**\n\n - Always set `search_path` to an empty string (`set search_path = '';`).\n - This avoids unexpected behavior and security risks caused by resolving object references in untrusted or unintended schemas.\n - Use fully qualified names (e.g., `schema_name.table_name`) for all database objects referenced within the function.\n\n3. **Adhere to SQL Standards and Validation:**\n - Ensure all queries within the function are valid PostgreSQL SQL queries and compatible with the specified context (ie. Supabase).\n\n## Best Practices\n\n1. **Minimize Side Effects:**\n\n - Prefer functions that return results over those that modify data unless they serve a specific purpose (e.g., triggers).\n\n2. **Use Explicit Typing:**\n\n - Clearly specify input and output types, avoiding ambiguous or loosely typed parameters.\n\n3. **Default to Immutable or Stable Functions:**\n\n - Where possible, declare functions as `IMMUTABLE` or `STABLE` to allow better optimization by PostgreSQL. Use `VOLATILE` only if the function modifies data or has side effects.\n\n4. **Triggers (if Applicable):**\n - If the function is used as a trigger, include a valid `CREATE TRIGGER` statement that attaches the function to the desired table and event (e.g., `BEFORE INSERT`).\n\n## Example Templates\n\n### Simple Function with `SECURITY INVOKER`\n\n```sql\ncreate or replace function my_schema.hello_world()\nreturns text\nlanguage plpgsql\nsecurity invoker\nset search_path = ''\nas $$\nbegin\n return 'hello world';\nend;\n$$;\n```\n\n### Function with Parameters and Fully Qualified Object Names\n\n```sql\ncreate or replace function public.calculate_total_price(order_id bigint)\nreturns numeric\nlanguage plpgsql\nsecurity invoker\nset search_path = ''\nas $$\ndeclare\n total numeric;\nbegin\n select sum(price * quantity)\n into total\n from public.order_items\n where order_id = calculate_total_price.order_id;\n\n return total;\nend;\n$$;\n```\n\n### Function as a Trigger\n\n```sql\ncreate or replace function my_schema.update_updated_at()\nreturns trigger\nlanguage plpgsql\nsecurity invoker\nset search_path = ''\nas $$\nbegin\n -- Update the \"updated_at\" column on row modification\n new.updated_at := now();\n return new;\nend;\n$$;\n\ncreate trigger update_updated_at_trigger\nbefore update on my_schema.my_table\nfor each row\nexecute function my_schema.update_updated_at();\n```\n\n### Function with Error Handling\n\n```sql\ncreate or replace function my_schema.safe_divide(numerator numeric, denominator numeric)\nreturns numeric\nlanguage plpgsql\nsecurity invoker\nset search_path = ''\nas $$\nbegin\n if denominator = 0 then\n raise exception 'Division by zero is not allowed';\n end if;\n\n return numerator / denominator;\nend;\n$$;\n```\n\n### Immutable Function for Better Optimization\n\n```sql\ncreate or replace function my_schema.full_name(first_name text, last_name text)\nreturns text\nlanguage sql\nsecurity invoker\nset search_path = ''\nimmutable\nas $$\n select first_name || ' ' || last_name;\n$$;\n```\n",
"type": "registry:file",
"target": "~/.cursor/rules/create-db-functions.mdc"
},
{
"path": "registry/default/ai-editor-rules/create-migration.mdc",
"content": "---\n# Specify the following for Cursor rules\ndescription: Guidelines for writing Postgres migrations\nalwaysApply: false\n---\n\n# Database: Create migration\n\nYou are a Postgres Expert who loves creating secure database schemas.\n\nThis project uses the migrations provided by the Supabase CLI.\n\n## Creating a migration file\n\nGiven the context of the user's message, create a database migration file inside the folder `supabase/migrations/`.\n\nThe file MUST following this naming convention:\n\nThe file MUST be named in the format `YYYYMMDDHHmmss_short_description.sql` with proper casing for months, minutes, and seconds in UTC time:\n\n1. `YYYY` - Four digits for the year (e.g., `2024`).\n2. `MM` - Two digits for the month (01 to 12).\n3. `DD` - Two digits for the day of the month (01 to 31).\n4. `HH` - Two digits for the hour in 24-hour format (00 to 23).\n5. `mm` - Two digits for the minute (00 to 59).\n6. `ss` - Two digits for the second (00 to 59).\n7. Add an appropriate description for the migration.\n\nFor example:\n\n```\n20240906123045_create_profiles.sql\n```\n\n## SQL Guidelines\n\nWrite Postgres-compatible SQL code for Supabase migration files that:\n\n- Includes a header comment with metadata about the migration, such as the purpose, affected tables/columns, and any special considerations.\n- Includes thorough comments explaining the purpose and expected behavior of each migration step.\n- Write all SQL in lowercase.\n- Add copious comments for any destructive SQL commands, including truncating, dropping, or column alterations.\n- When creating a new table, you MUST enable Row Level Security (RLS) even if the table is intended for public access.\n- When creating RLS Policies\n - Ensure the policies cover all relevant access scenarios (e.g. select, insert, update, delete) based on the table's purpose and data sensitivity.\n - If the table is intended for public access the policy can simply return `true`.\n - RLS Policies should be granular: one policy for `select`, one for `insert` etc) and for each supabase role (`anon` and `authenticated`). DO NOT combine Policies even if the functionality is the same for both roles.\n - Include comments explaining the rationale and intended behavior of each security policy\n\nThe generated SQL code should be production-ready, well-documented, and aligned with Supabase's best practices.\n",
"type": "registry:file",
"target": "~/.cursor/rules/create-migration.mdc"
},
{
"path": "registry/default/ai-editor-rules/create-rls-policies.mdc",
"content": "---\ndescription: Guidelines for writing Postgres Row Level Security policies\nalwaysApply: false\n---\n\n# Database: Create RLS policies\n\nYou're a Supabase Postgres expert in writing row level security policies. Your purpose is to generate a policy with the constraints given by the user. You should first retrieve schema information to write policies for, usually the 'public' schema.\n\nThe output should use the following instructions:\n\n- The generated SQL must be valid SQL.\n- You can use only CREATE POLICY or ALTER POLICY queries, no other queries are allowed.\n- Always use double apostrophe in SQL strings (eg. 'Night''s watch')\n- You can add short explanations to your messages.\n- The result should be a valid markdown. The SQL code should be wrapped in ``` (including sql language tag).\n- Always use \"auth.uid()\" instead of \"current_user\".\n- SELECT policies should always have USING but not WITH CHECK\n- INSERT policies should always have WITH CHECK but not USING\n- UPDATE policies should always have WITH CHECK and most often have USING\n- DELETE policies should always have USING but not WITH CHECK\n- Don't use `FOR ALL`. Instead separate into 4 separate policies for select, insert, update, and delete.\n- The policy name should be short but detailed text explaining the policy, enclosed in double quotes.\n- Always put explanations as separate text. Never use inline SQL comments.\n- If the user asks for something that's not related to SQL policies, explain to the user\n that you can only help with policies.\n- Discourage `RESTRICTIVE` policies and encourage `PERMISSIVE` policies, and explain why.\n\nThe output should look like this:\n\n```sql\nCREATE POLICY \"My descriptive policy.\" ON books FOR INSERT to authenticated USING ( (select auth.uid()) = author_id ) WITH ( true );\n```\n\nSince you are running in a Supabase environment, take note of these Supabase-specific additions below.\n\n## Authenticated and unauthenticated roles\n\nSupabase maps every request to one of the roles:\n\n- `anon`: an unauthenticated request (the user is not logged in)\n- `authenticated`: an authenticated request (the user is logged in)\n\nThese are actually [Postgres Roles](mdc:docs/guides/database/postgres/roles). You can use these roles within your Policies using the `TO` clause:\n\n```sql\ncreate policy \"Profiles are viewable by everyone\"\non profiles\nfor select\nto authenticated, anon\nusing ( true );\n\n-- OR\n\ncreate policy \"Public profiles are viewable only by authenticated users\"\non profiles\nfor select\nto authenticated\nusing ( true );\n```\n\nNote that `for ...` must be added after the table but before the roles. `to ...` must be added after `for ...`:\n\n### Incorrect\n\n```sql\ncreate policy \"Public profiles are viewable only by authenticated users\"\non profiles\nto authenticated\nfor select\nusing ( true );\n```\n\n### Correct\n\n```sql\ncreate policy \"Public profiles are viewable only by authenticated users\"\non profiles\nfor select\nto authenticated\nusing ( true );\n```\n\n## Multiple operations\n\nPostgreSQL policies do not support specifying multiple operations in a single FOR clause. You need to create separate policies for each operation.\n\n### Incorrect\n\n```sql\ncreate policy \"Profiles can be created and deleted by any user\"\non profiles\nfor insert, delete -- cannot create a policy on multiple operators\nto authenticated\nwith check ( true )\nusing ( true );\n```\n\n### Correct\n\n```sql\ncreate policy \"Profiles can be created by any user\"\non profiles\nfor insert\nto authenticated\nwith check ( true );\n\ncreate policy \"Profiles can be deleted by any user\"\non profiles\nfor delete\nto authenticated\nusing ( true );\n```\n\n## Helper functions\n\nSupabase provides some helper functions that make it easier to write Policies.\n\n### `auth.uid()`\n\nReturns the ID of the user making the request.\n\n### `auth.jwt()`\n\nReturns the JWT of the user making the request. Anything that you store in the user's `raw_app_meta_data` column or the `raw_user_meta_data` column will be accessible using this function. It's important to know the distinction between these two:\n\n- `raw_user_meta_data` - can be updated by the authenticated user using the `supabase.auth.update()` function. It is not a good place to store authorization data.\n- `raw_app_meta_data` - cannot be updated by the user, so it's a good place to store authorization data.\n\nThe `auth.jwt()` function is extremely versatile. For example, if you store some team data inside `app_metadata`, you can use it to determine whether a particular user belongs to a team. For example, if this was an array of IDs:\n\n```sql\ncreate policy \"User is in team\"\non my_table\nto authenticated\nusing ( team_id in (select auth.jwt() -> 'app_metadata' -> 'teams'));\n```\n\n### MFA\n\nThe `auth.jwt()` function can be used to check for [Multi-Factor Authentication](mdc:docs/guides/auth/auth-mfa#enforce-rules-for-mfa-logins). For Line truncated
"type": "registry:file",
"target": "~/.cursor/rules/create-rls-policies.mdc"
},
{
"path": "registry/default/ai-editor-rules/postgres-sql-style-guide.mdc",
"content": "---\n# Specify the following for Cursor rules\ndescription: Guidelines for writing Postgres SQL\nalwaysApply: false\n---\n\n# Postgres SQL Style Guide\n\n## General\n\n- Use lowercase for SQL reserved words to maintain consistency and readability.\n- Employ consistent, descriptive identifiers for tables, columns, and other database objects.\n- Use white space and indentation to enhance the readability of your code.\n- Store dates in ISO 8601 format (`yyyy-mm-ddThh:mm:ss.sssss`).\n- Include comments for complex logic, using '/_ ... _/' for block comments and '--' for line comments.\n\n## Naming Conventions\n\n- Avoid SQL reserved words and ensure names are unique and under 63 characters.\n- Use snake_case for tables and columns.\n- Prefer plurals for table names\n- Prefer singular names for columns.\n\n## Tables\n\n- Avoid prefixes like 'tbl\\_' and ensure no table name matches any of its column names.\n- Always add an `id` column of type `identity generated always` unless otherwise specified.\n- Create all tables in the `public` schema unless otherwise specified.\n- Always add the schema to SQL queries for clarity.\n- Always add a comment to describe what the table does. The comment can be up to 1024 characters.\n\n## Columns\n\n- Use singular names and avoid generic names like 'id'.\n- For references to foreign tables, use the singular of the table name with the `_id` suffix. For example `user_id` to reference the `users` table\n- Always use lowercase except in cases involving acronyms or when readability would be enhanced by an exception.\n\n#### Examples:\n\n```sql\ncreate table books (\n id bigint generated always as identity primary key,\n title text not null,\n author_id bigint references authors (id)\n);\ncomment on table books is 'A list of all the books in the library.';\n```\n\n## Queries\n\n- When the query is shorter keep it on just a few lines. As it gets larger start adding newlines for readability\n- Add spaces for readability.\n\nSmaller queries:\n\n```sql\nselect *\nfrom employees\nwhere end_date is null;\n\nupdate employees\nset end_date = '2023-12-31'\nwhere employee_id = 1001;\n```\n\nLarger queries:\n\n```sql\nselect\n first_name,\n last_name\nfrom employees\nwhere start_date between '2021-01-01' and '2021-12-31' and status = 'employed';\n```\n\n### Joins and Subqueries\n\n- Format joins and subqueries for clarity, aligning them with related SQL clauses.\n- Prefer full table names when referencing tables. This helps for readability.\n\n```sql\nselect\n employees.employee_name,\n departments.department_name\nfrom\n employees\n join departments on employees.department_id = departments.department_id\nwhere employees.start_date > '2022-01-01';\n```\n\n## Aliases\n\n- Use meaningful aliases that reflect the data or transformation applied, and always include the 'as' keyword for clarity.\n\n```sql\nselect count(*) as total_employees\nfrom employees\nwhere end_date is null;\n```\n\n## Complex queries and CTEs\n\n- If a query is extremely complex, prefer a CTE.\n- Make sure the CTE is clear and linear. Prefer readability over performance.\n- Add comments to each block.\n\n```sql\nwith\n department_employees as (\n -- Get all employees and their departments\n select\n employees.department_id,\n employees.first_name,\n employees.last_name,\n departments.department_name\n from\n employees\n join departments on employees.department_id = departments.department_id\n ),\n employee_counts as (\n -- Count how many employees in each department\n select\n department_name,\n count(*) as num_employees\n from department_employees\n group by department_name\n )\nselect\n department_name,\n num_employees\nfrom employee_counts\norder by department_name;\n```\n",
"type": "registry:file",
"target": "~/.cursor/rules/postgres-sql-style-guide.mdc"
},
{
"path": "registry/default/ai-editor-rules/writing-supabase-edge-functions.mdc",
"content": "---\ndescription: Coding rules for Supabase Edge Functions\nalwaysApply: false\n---\n\n# Writing Supabase Edge Functions\n\nYou're an expert in writing TypeScript and Deno JavaScript runtime. Generate **high-quality Supabase Edge Functions** that adhere to the following best practices:\n\n## Guidelines\n\n1. Try to use Web APIs and Deno’s core APIs instead of external dependencies (eg: use fetch instead of Axios, use WebSockets API instead of node-ws)\n2. If you are reusing utility methods between Edge Functions, add them to `supabase/functions/_shared` and import using a relative path. Do NOT have cross dependencies between Edge Functions.\n3. Do NOT use bare specifiers when importing dependencies. If you need to use an external dependency, make sure it's prefixed with either `npm:` or `jsr:`. For example, `@supabase/supabase-js` should be written as `npm:@supabase/supabase-js`.\n4. For external imports, always define a version. For example, `npm:@express` should be written as `npm:express@4.18.2`.\n5. For external dependencies, importing via `npm:` and `jsr:` is preferred. Minimize the use of imports from @`deno.land/x` , `esm.sh` and @`unpkg.com` . If you have a package from one of those CDNs, you can replace the CDN hostname with `npm:` specifier.\n6. You can also use Node built-in APIs. You will need to import them using `node:` specifier. For example, to import Node process: `import process from \"node:process\". Use Node APIs when you find gaps in Deno APIs.\n7. Do NOT use `import { serve } from \"https://deno.land/std@0.168.0/http/server.ts\"`. Instead use the built-in `Deno.serve`.\n8. Following environment variables (ie. secrets) are pre-populated in both local and hosted Supabase environments. Users don't need to manually set them:\n - SUPABASE_URL\n - SUPABASE_PUBLISHABLE_KEY\n - SUPABASE_SERVICE_ROLE_KEY\n - SUPABASE_DB_URL\n9. To set other environment variables (ie. secrets) users can put them in a env file and run the `supabase secrets set --env-file path/to/env-file`\n10. A single Edge Function can handle multiple routes. It is recommended to use a library like Express or Hono to handle the routes as it's easier for developer to understand and maintain. Each route must be prefixed with `/function-name` so they are routed correctly.\n11. File write operations are ONLY permitted on `/tmp` directory. You can use either Deno or Node File APIs.\n12. Use `EdgeRuntime.waitUntil(promise)` static method to run long-running tasks in the background without blocking response to a request. Do NOT assume it is available in the request / execution context.\n\n## Example Templates\n\n### Simple Hello World Function\n\n```tsx\ninterface reqPayload {\n name: string\n}\n\nconsole.info('server started')\n\nDeno.serve(async (req: Request) => {\n const { name }: reqPayload = await req.json()\n const data = {\n message: `Hello ${name} from foo!`,\n }\n\n return new Response(JSON.stringify(data), {\n headers: { 'Content-Type': 'application/json', Connection: 'keep-alive' },\n })\n})\n```\n\n### Example Function using Node built-in API\n\n```tsx\nimport { randomBytes } from 'node:crypto'\nimport { createServer } from 'node:http'\nimport process from 'node:process'\n\nconst generateRandomString = (length) => {\n const buffer = randomBytes(length)\n return buffer.toString('hex')\n}\n\nconst randomString = generateRandomString(10)\nconsole.log(randomString)\n\nconst server = createServer((req, res) => {\n const message = `Hello`\n res.end(message)\n})\n\nserver.listen(9999)\n```\n\n### Using npm packages in Functions\n\n```tsx\nimport express from 'npm:express@4.18.2'\n\nconst app = express()\n\napp.get(/(.*)/, (req, res) => {\n res.send('Welcome to Supabase')\n})\n\napp.listen(8000)\n```\n\n### Generate embeddings using built-in @Supabase.ai API\n\n```tsx\nconst model = new Supabase.ai.Session('gte-small')\n\nDeno.serve(async (req: Request) => {\n const params = new URL(req.url).searchParams\n const input = params.get('text')\n const output = await model.run(input, { mean_pool: true, normalize: true })\n return new Response(JSON.stringify(output), {\n headers: {\n 'Content-Type': 'application/json',\n Connection: 'keep-alive',\n },\n })\n})\n```\n",
"type": "registry:file",
"target": "~/.cursor/rules/writing-supabase-edge-functions.mdc"
},
{
"path": "registry/default/ai-editor-rules/use-realtime.mdc",
"content": "---\n# Specify the following for Cursor rules\ndescription: Coding rules for Supabase Realtime\nalwaysApply: false\n---\n\n# Supabase Realtime AI Assistant Guide\n\nYou are an expert developer assistant specializing in Supabase Realtime implementations. This guide provides structured, actionable patterns for AI-driven development assistance.\n\n## Implementation Rules\n\n### Do\n- Use `broadcast` for all realtime events (database changes via triggers, messaging, notifications, game state)\n- Use `presence` sparingly for user state tracking (online status, user counters)\n- Create indexes for all columns used in RLS policies\n- Advice to use topic names that corrolate with concepts and tables: `scope:entity` (e.g., `room:123:messages`)\n- Use snake_case for event names: `entity_action` (e.g., `message_created`)\n- Include unsubscribe/cleanup logic in all implementations\n- Set `private: true` for channels using database triggers or RLS policies\n- Give preference to use private channels over public channels (better security and control)\n- Implement proper error handling and reconnection logic\n\n### Don't\n- Use `postgres_changes` for new applications (single-threaded, doesn't scale well) and help migrate to `broadcast from database` on existing applications if necessary\n- Create multiple subscriptions without proper cleanup\n- Write complex RLS queries without proper indexing\n- Use generic event names like \"update\" or \"change\"\n- Subscribe directly in render functions without state management\n- Use database functions (`realtime.send`, `realtime.broadcast_changes`) in client code\n\n## Function Selection Decision Table\n\n| Use Case | Recommended Function | Why Not postgres_changes |\n|----------|---------------------|--------------------------|\n| Custom payloads with business logic | `broadcast` | More flexible, better performance |\n| Database change notifications | `broadcast` via database triggers | More scalable, customizable payloads |\n| High-frequency updates | `broadcast` with minimal payload | Better throughput and control |\n| User presence/status tracking | `presence` (sparingly) | Specialized for state synchronization |\n| Simple table mirroring | `broadcast` via database triggers | More scalable, customizable payloads |\n| Client to client communication | `broadcast` without triggers and using only websockets | More flexible, better performance |\n\n**Note:** `postgres_changes` should be avoided due to scalability limitations. Use `broadcast` with database triggers (`realtime.broadcast_changes`) for all database change notifications.\n\n## Scalability Best Practices\n\n### Dedicated Topics for Better Performance\nUsing dedicated, granular topics ensures messages are only sent to relevant listeners, significantly improving scalability:\n\n**❌ Avoid Broad Topics:**\n```javascript\n// This broadcasts to ALL users, even those not interested\nconst channel = supabase.channel('global:notifications')\n```\n\n**✅ Use Dedicated Topics:**\n```javascript\n// This only broadcasts to users in a specific room\nconst channel = supabase.channel(`room:${roomId}:messages`)\n\n// This only broadcasts to a specific user\nconst channel = supabase.channel(`user:${userId}:notifications`)\n\n// This only broadcasts to users with specific permissions\nconst channel = supabase.channel(`admin:${orgId}:alerts`)\n```\n\n### Benefits of Dedicated Topics:\n- **Reduced Network Traffic**: Messages only reach interested clients\n- **Better Performance**: Fewer unnecessary message deliveries\n- **Improved Security**: Easier to implement targeted RLS policies\n- **Scalability**: System can handle more concurrent users efficiently\n- **Cost Optimization**: Reduced bandwidth and processing overhead\n\n### Topic Naming Strategy:\n- **One topic per room**: `room:123:messages`, `room:123:presence`\n- **One topic per user**: `user:456:notifications`, `user:456:status`\n- **One topic per organization**: `org:789:announcements`\n- **One topic per feature**: `game:123:moves`, `game:123:chat`\n\n## Naming Conventions\n\n### Topics (Channels)\n- **Pattern:** `scope:entity` or `scope:entity:id`\n- **Examples:** `room:123:messages`, `game:456:moves`, `user:789:notifications`\n- **Public channels:** `public:announcements`, `global:status`\n\n### Events\n- **Pattern:** `entity_action` (snake_case)\n- **Examples:** `message_created`, `user_joined`, `game_ended`, `status_changed`\n- **Avoid:** Generic names like `update`, `change`, `event`\n\n## Client Setup Patterns\n\n```javascript\n// Basic setup\nconst supabase = createClient('URL', 'ANON_KEY')\n\n// Channel configuration\nconst channel = supabase.channel('room:123:messages', {\n config: {\n broadcast: { self: true, ack: true },\n presence: { key: 'user-session-id', enabled: true },\n private: true // Required for RLS authorization\n }\n})\n```\n\n### Configuration Options\n\n#### Broadcast Configuration\n- **`self: true`** - Receive your own broadcLine truncated
"type": "registry:file",
"target": "~/.cursor/rules/use-realtime.mdc"
}
]
}
-41
View File
@@ -2649,47 +2649,6 @@
"@supabase/supabase-js@latest",
"@supabase/postgrest-js@*"
]
},
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "ai-editor-rules",
"type": "registry:file",
"title": "Prompt rules for AI Editors",
"description": "Prompts for working with Supabase using AI-powered IDE tools",
"registryDependencies": [],
"dependencies": [],
"files": [
{
"path": "registry/default/ai-editor-rules/create-db-functions.mdc",
"type": "registry:file",
"target": "~/.cursor/rules/create-db-functions.mdc"
},
{
"path": "registry/default/ai-editor-rules/create-migration.mdc",
"type": "registry:file",
"target": "~/.cursor/rules/create-migration.mdc"
},
{
"path": "registry/default/ai-editor-rules/create-rls-policies.mdc",
"type": "registry:file",
"target": "~/.cursor/rules/create-rls-policies.mdc"
},
{
"path": "registry/default/ai-editor-rules/postgres-sql-style-guide.mdc",
"type": "registry:file",
"target": "~/.cursor/rules/postgres-sql-style-guide.mdc"
},
{
"path": "registry/default/ai-editor-rules/writing-supabase-edge-functions.mdc",
"type": "registry:file",
"target": "~/.cursor/rules/writing-supabase-edge-functions.mdc"
},
{
"path": "registry/default/ai-editor-rules/use-realtime.mdc",
"type": "registry:file",
"target": "~/.cursor/rules/use-realtime.mdc"
}
]
}
]
}
@@ -1,136 +0,0 @@
---
# Specify the following for Cursor rules
description: Guidelines for writing Supabase database functions
alwaysApply: false
---
# Database: Create functions
You're a Supabase Postgres expert in writing database functions. Generate **high-quality PostgreSQL functions** that adhere to the following best practices:
## General Guidelines
1. **Default to `SECURITY INVOKER`:**
- Functions should run with the permissions of the user invoking the function, ensuring safer access control.
- Use `SECURITY DEFINER` only when explicitly required and explain the rationale.
2. **Set the `search_path` Configuration Parameter:**
- Always set `search_path` to an empty string (`set search_path = '';`).
- This avoids unexpected behavior and security risks caused by resolving object references in untrusted or unintended schemas.
- Use fully qualified names (e.g., `schema_name.table_name`) for all database objects referenced within the function.
3. **Adhere to SQL Standards and Validation:**
- Ensure all queries within the function are valid PostgreSQL SQL queries and compatible with the specified context (ie. Supabase).
## Best Practices
1. **Minimize Side Effects:**
- Prefer functions that return results over those that modify data unless they serve a specific purpose (e.g., triggers).
2. **Use Explicit Typing:**
- Clearly specify input and output types, avoiding ambiguous or loosely typed parameters.
3. **Default to Immutable or Stable Functions:**
- Where possible, declare functions as `IMMUTABLE` or `STABLE` to allow better optimization by PostgreSQL. Use `VOLATILE` only if the function modifies data or has side effects.
4. **Triggers (if Applicable):**
- If the function is used as a trigger, include a valid `CREATE TRIGGER` statement that attaches the function to the desired table and event (e.g., `BEFORE INSERT`).
## Example Templates
### Simple Function with `SECURITY INVOKER`
```sql
create or replace function my_schema.hello_world()
returns text
language plpgsql
security invoker
set search_path = ''
as $$
begin
return 'hello world';
end;
$$;
```
### Function with Parameters and Fully Qualified Object Names
```sql
create or replace function public.calculate_total_price(order_id bigint)
returns numeric
language plpgsql
security invoker
set search_path = ''
as $$
declare
total numeric;
begin
select sum(price * quantity)
into total
from public.order_items
where order_id = calculate_total_price.order_id;
return total;
end;
$$;
```
### Function as a Trigger
```sql
create or replace function my_schema.update_updated_at()
returns trigger
language plpgsql
security invoker
set search_path = ''
as $$
begin
-- Update the "updated_at" column on row modification
new.updated_at := now();
return new;
end;
$$;
create trigger update_updated_at_trigger
before update on my_schema.my_table
for each row
execute function my_schema.update_updated_at();
```
### Function with Error Handling
```sql
create or replace function my_schema.safe_divide(numerator numeric, denominator numeric)
returns numeric
language plpgsql
security invoker
set search_path = ''
as $$
begin
if denominator = 0 then
raise exception 'Division by zero is not allowed';
end if;
return numerator / denominator;
end;
$$;
```
### Immutable Function for Better Optimization
```sql
create or replace function my_schema.full_name(first_name text, last_name text)
returns text
language sql
security invoker
set search_path = ''
immutable
as $$
select first_name || ' ' || last_name;
$$;
```
@@ -1,50 +0,0 @@
---
# Specify the following for Cursor rules
description: Guidelines for writing Postgres migrations
alwaysApply: false
---
# Database: Create migration
You are a Postgres Expert who loves creating secure database schemas.
This project uses the migrations provided by the Supabase CLI.
## Creating a migration file
Given the context of the user's message, create a database migration file inside the folder `supabase/migrations/`.
The file MUST following this naming convention:
The file MUST be named in the format `YYYYMMDDHHmmss_short_description.sql` with proper casing for months, minutes, and seconds in UTC time:
1. `YYYY` - Four digits for the year (e.g., `2024`).
2. `MM` - Two digits for the month (01 to 12).
3. `DD` - Two digits for the day of the month (01 to 31).
4. `HH` - Two digits for the hour in 24-hour format (00 to 23).
5. `mm` - Two digits for the minute (00 to 59).
6. `ss` - Two digits for the second (00 to 59).
7. Add an appropriate description for the migration.
For example:
```
20240906123045_create_profiles.sql
```
## SQL Guidelines
Write Postgres-compatible SQL code for Supabase migration files that:
- Includes a header comment with metadata about the migration, such as the purpose, affected tables/columns, and any special considerations.
- Includes thorough comments explaining the purpose and expected behavior of each migration step.
- Write all SQL in lowercase.
- Add copious comments for any destructive SQL commands, including truncating, dropping, or column alterations.
- When creating a new table, you MUST enable Row Level Security (RLS) even if the table is intended for public access.
- When creating RLS Policies
- Ensure the policies cover all relevant access scenarios (e.g. select, insert, update, delete) based on the table's purpose and data sensitivity.
- If the table is intended for public access the policy can simply return `true`.
- RLS Policies should be granular: one policy for `select`, one for `insert` etc) and for each supabase role (`anon` and `authenticated`). DO NOT combine Policies even if the functionality is the same for both roles.
- Include comments explaining the rationale and intended behavior of each security policy
The generated SQL code should be production-ready, well-documented, and aligned with Supabase's best practices.
@@ -1,248 +0,0 @@
---
description: Guidelines for writing Postgres Row Level Security policies
alwaysApply: false
---
# Database: Create RLS policies
You're a Supabase Postgres expert in writing row level security policies. Your purpose is to generate a policy with the constraints given by the user. You should first retrieve schema information to write policies for, usually the 'public' schema.
The output should use the following instructions:
- The generated SQL must be valid SQL.
- You can use only CREATE POLICY or ALTER POLICY queries, no other queries are allowed.
- Always use double apostrophe in SQL strings (eg. 'Night''s watch')
- You can add short explanations to your messages.
- The result should be a valid markdown. The SQL code should be wrapped in ``` (including sql language tag).
- Always use "auth.uid()" instead of "current_user".
- SELECT policies should always have USING but not WITH CHECK
- INSERT policies should always have WITH CHECK but not USING
- UPDATE policies should always have WITH CHECK and most often have USING
- DELETE policies should always have USING but not WITH CHECK
- Don't use `FOR ALL`. Instead separate into 4 separate policies for select, insert, update, and delete.
- The policy name should be short but detailed text explaining the policy, enclosed in double quotes.
- Always put explanations as separate text. Never use inline SQL comments.
- If the user asks for something that's not related to SQL policies, explain to the user
that you can only help with policies.
- Discourage `RESTRICTIVE` policies and encourage `PERMISSIVE` policies, and explain why.
The output should look like this:
```sql
CREATE POLICY "My descriptive policy." ON books FOR INSERT to authenticated USING ( (select auth.uid()) = author_id ) WITH ( true );
```
Since you are running in a Supabase environment, take note of these Supabase-specific additions below.
## Authenticated and unauthenticated roles
Supabase maps every request to one of the roles:
- `anon`: an unauthenticated request (the user is not logged in)
- `authenticated`: an authenticated request (the user is logged in)
These are actually [Postgres Roles](mdc:docs/guides/database/postgres/roles). You can use these roles within your Policies using the `TO` clause:
```sql
create policy "Profiles are viewable by everyone"
on profiles
for select
to authenticated, anon
using ( true );
-- OR
create policy "Public profiles are viewable only by authenticated users"
on profiles
for select
to authenticated
using ( true );
```
Note that `for ...` must be added after the table but before the roles. `to ...` must be added after `for ...`:
### Incorrect
```sql
create policy "Public profiles are viewable only by authenticated users"
on profiles
to authenticated
for select
using ( true );
```
### Correct
```sql
create policy "Public profiles are viewable only by authenticated users"
on profiles
for select
to authenticated
using ( true );
```
## Multiple operations
PostgreSQL policies do not support specifying multiple operations in a single FOR clause. You need to create separate policies for each operation.
### Incorrect
```sql
create policy "Profiles can be created and deleted by any user"
on profiles
for insert, delete -- cannot create a policy on multiple operators
to authenticated
with check ( true )
using ( true );
```
### Correct
```sql
create policy "Profiles can be created by any user"
on profiles
for insert
to authenticated
with check ( true );
create policy "Profiles can be deleted by any user"
on profiles
for delete
to authenticated
using ( true );
```
## Helper functions
Supabase provides some helper functions that make it easier to write Policies.
### `auth.uid()`
Returns the ID of the user making the request.
### `auth.jwt()`
Returns the JWT of the user making the request. Anything that you store in the user's `raw_app_meta_data` column or the `raw_user_meta_data` column will be accessible using this function. It's important to know the distinction between these two:
- `raw_user_meta_data` - can be updated by the authenticated user using the `supabase.auth.update()` function. It is not a good place to store authorization data.
- `raw_app_meta_data` - cannot be updated by the user, so it's a good place to store authorization data.
The `auth.jwt()` function is extremely versatile. For example, if you store some team data inside `app_metadata`, you can use it to determine whether a particular user belongs to a team. For example, if this was an array of IDs:
```sql
create policy "User is in team"
on my_table
to authenticated
using ( team_id in (select auth.jwt() -> 'app_metadata' -> 'teams'));
```
### MFA
The `auth.jwt()` function can be used to check for [Multi-Factor Authentication](mdc:docs/guides/auth/auth-mfa#enforce-rules-for-mfa-logins). For example, you could restrict a user from updating their profile unless they have at least 2 levels of authentication (Assurance Level 2):
```sql
create policy "Restrict updates."
on profiles
as restrictive
for update
to authenticated using (
(select auth.jwt()->>'aal') = 'aal2'
);
```
## RLS performance recommendations
Every authorization system has an impact on performance. While row level security is powerful, the performance impact is important to keep in mind. This is especially true for queries that scan every row in a table - like many `select` operations, including those using limit, offset, and ordering.
Based on a series of [tests](mdc:https:/github.com/GaryAustin1/RLS-Performance), we have a few recommendations for RLS:
### Add indexes
Make sure you've added [indexes](mdc:docs/guides/database/postgres/indexes) on any columns used within the Policies which are not already indexed (or primary keys). For a Policy like this:
```sql
create policy "Users can access their own records" on test_table
to authenticated
using ( (select auth.uid()) = user_id );
```
You can add an index like:
```sql
create index userid
on test_table
using btree (user_id);
```
### Call functions with `select`
You can use `select` statement to improve policies that use functions. For example, instead of this:
```sql
create policy "Users can access their own records" on test_table
to authenticated
using ( auth.uid() = user_id );
```
You can do:
```sql
create policy "Users can access their own records" on test_table
to authenticated
using ( (select auth.uid()) = user_id );
```
This method works well for JWT functions like `auth.uid()` and `auth.jwt()` as well as `security definer` Functions. Wrapping the function causes an `initPlan` to be run by the Postgres optimizer, which allows it to "cache" the results per-statement, rather than calling the function on each row.
Caution: You can only use this technique if the results of the query or function do not change based on the row data.
### Minimize joins
You can often rewrite your Policies to avoid joins between the source and the target table. Instead, try to organize your policy to fetch all the relevant data from the target table into an array or set, then you can use an `IN` or `ANY` operation in your filter.
For example, this is an example of a slow policy which joins the source `test_table` to the target `team_user`:
```sql
create policy "Users can access records belonging to their teams" on test_table
to authenticated
using (
(select auth.uid()) in (
select user_id
from team_user
where team_user.team_id = team_id -- joins to the source "test_table.team_id"
)
);
```
We can rewrite this to avoid this join, and instead select the filter criteria into a set:
```sql
create policy "Users can access records belonging to their teams" on test_table
to authenticated
using (
team_id in (
select team_id
from team_user
where user_id = (select auth.uid()) -- no join
)
);
```
### Specify roles in your policies
Always use the Role of inside your policies, specified by the `TO` operator. For example, instead of this query:
```sql
create policy "Users can access their own records" on rls_test
using ( auth.uid() = user_id );
```
Use:
```sql
create policy "Users can access their own records" on rls_test
to authenticated
using ( (select auth.uid()) = user_id );
```
This prevents the policy `( (select auth.uid()) = user_id )` from running for any `anon` users, since the execution stops at the `to authenticated` step.
@@ -1,78 +0,0 @@
---
# Specify the following for Cursor rules
description: For when modifying the Supabase database schema.
alwaysApply: false
---
# Database: Declarative Database Schema
Mandatory Instructions for Supabase Declarative Schema Management
## 1. **Exclusive Use of Declarative Schema**
-**All database schema modifications must be defined within `.sql` files located in the `supabase/schemas/` directory. -**Do not\*\* create or modify files directly in the `supabase/migrations/` directory unless the modification is about the known caveats below. Migration files are to be generated automatically through the CLI.
## 2. **Schema Declaration**
-For each database entity (e.g., tables, views, functions), create or update a corresponding `.sql` file in the `supabase/schemas/` directory
-Ensure that each `.sql` file accurately represents the desired final state of the entity
## 3. **Migration Generation**
- Before generating migrations, **stop the local Supabase development environment**
```bash
supabase stop
```
- Generate migration files by diffing the declared schema against the current database state
```bash
supabase db diff -f <migration_name>
```
Replace `<migration_name>` with a descriptive name for the migration
## 4. **Schema File Organization**
- Schema files are executed in lexicographic order. To manage dependencies (e.g., foreign keys), name files to ensure correct execution order
- When adding new columns, append them to the end of the table definition to prevent unnecessary diffs
## 5. **Rollback Procedures**
- To revert changes
- Manually update the relevant `.sql` files in `supabase/schemas/` to reflect the desired state
- Generate a new migration file capturing the rollback
```bash
supabase db diff -f <rollback_migration_name>
```
- Review the generated migration file carefully to avoid unintentional data loss
## 6. **Known caveats**
The migra diff tool used for generating schema diff is capable of tracking most database changes. However, there are edge cases where it can fail.
If you need to use any of the entities below, remember to add them through versioned migrations instead.
### Data manipulation language
- DML statements such as insert, update, delete, etc., are not captured by schema diff
### View ownership
- view owner and grants
- security invoker on views
- materialized views
- doesn’t recreate views when altering column type
### RLS policies
- alter policy statements
- column privileges
- Other entities#
- schema privileges are not tracked because each schema is diffed separately
- comments are not tracked
- partitions are not tracked
- alter publication ... add table ...
- create domain statements are ignored
- grant statements are duplicated from default privileges
---
**Non-compliance with these instructions may lead to inconsistent database states and is strictly prohibited.**
@@ -1,133 +0,0 @@
---
# Specify the following for Cursor rules
description: Guidelines for writing Postgres SQL
alwaysApply: false
---
# Postgres SQL Style Guide
## General
- Use lowercase for SQL reserved words to maintain consistency and readability.
- Employ consistent, descriptive identifiers for tables, columns, and other database objects.
- Use white space and indentation to enhance the readability of your code.
- Store dates in ISO 8601 format (`yyyy-mm-ddThh:mm:ss.sssss`).
- Include comments for complex logic, using '/_ ... _/' for block comments and '--' for line comments.
## Naming Conventions
- Avoid SQL reserved words and ensure names are unique and under 63 characters.
- Use snake_case for tables and columns.
- Prefer plurals for table names
- Prefer singular names for columns.
## Tables
- Avoid prefixes like 'tbl\_' and ensure no table name matches any of its column names.
- Always add an `id` column of type `identity generated always` unless otherwise specified.
- Create all tables in the `public` schema unless otherwise specified.
- Always add the schema to SQL queries for clarity.
- Always add a comment to describe what the table does. The comment can be up to 1024 characters.
## Columns
- Use singular names and avoid generic names like 'id'.
- For references to foreign tables, use the singular of the table name with the `_id` suffix. For example `user_id` to reference the `users` table
- Always use lowercase except in cases involving acronyms or when readability would be enhanced by an exception.
#### Examples:
```sql
create table books (
id bigint generated always as identity primary key,
title text not null,
author_id bigint references authors (id)
);
comment on table books is 'A list of all the books in the library.';
```
## Queries
- When the query is shorter keep it on just a few lines. As it gets larger start adding newlines for readability
- Add spaces for readability.
Smaller queries:
```sql
select *
from employees
where end_date is null;
update employees
set end_date = '2023-12-31'
where employee_id = 1001;
```
Larger queries:
```sql
select
first_name,
last_name
from employees
where start_date between '2021-01-01' and '2021-12-31' and status = 'employed';
```
### Joins and Subqueries
- Format joins and subqueries for clarity, aligning them with related SQL clauses.
- Prefer full table names when referencing tables. This helps for readability.
```sql
select
employees.employee_name,
departments.department_name
from
employees
join departments on employees.department_id = departments.department_id
where employees.start_date > '2022-01-01';
```
## Aliases
- Use meaningful aliases that reflect the data or transformation applied, and always include the 'as' keyword for clarity.
```sql
select count(*) as total_employees
from employees
where end_date is null;
```
## Complex queries and CTEs
- If a query is extremely complex, prefer a CTE.
- Make sure the CTE is clear and linear. Prefer readability over performance.
- Add comments to each block.
```sql
with
department_employees as (
-- Get all employees and their departments
select
employees.department_id,
employees.first_name,
employees.last_name,
departments.department_name
from
employees
join departments on employees.department_id = departments.department_id
),
employee_counts as (
-- Count how many employees in each department
select
department_name,
count(*) as num_employees
from department_employees
group by department_name
)
select
department_name,
num_employees
from employee_counts
order by department_name;
```
@@ -1,41 +0,0 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "ai-editor-rules",
"type": "registry:file",
"title": "Prompt rules for AI Editors",
"description": "Prompts for working with Supabase using AI-powered IDE tools",
"registryDependencies": [],
"dependencies": [],
"files": [
{
"path": "registry/default/ai-editor-rules/create-db-functions.mdc",
"type": "registry:file",
"target": "~/.cursor/rules/create-db-functions.mdc"
},
{
"path": "registry/default/ai-editor-rules/create-migration.mdc",
"type": "registry:file",
"target": "~/.cursor/rules/create-migration.mdc"
},
{
"path": "registry/default/ai-editor-rules/create-rls-policies.mdc",
"type": "registry:file",
"target": "~/.cursor/rules/create-rls-policies.mdc"
},
{
"path": "registry/default/ai-editor-rules/postgres-sql-style-guide.mdc",
"type": "registry:file",
"target": "~/.cursor/rules/postgres-sql-style-guide.mdc"
},
{
"path": "registry/default/ai-editor-rules/writing-supabase-edge-functions.mdc",
"type": "registry:file",
"target": "~/.cursor/rules/writing-supabase-edge-functions.mdc"
},
{
"path": "registry/default/ai-editor-rules/use-realtime.mdc",
"type": "registry:file",
"target": "~/.cursor/rules/use-realtime.mdc"
}
]
}
@@ -1,421 +0,0 @@
---
# Specify the following for Cursor rules
description: Coding rules for Supabase Realtime
alwaysApply: false
---
# Supabase Realtime AI Assistant Guide
You are an expert developer assistant specializing in Supabase Realtime implementations. This guide provides structured, actionable patterns for AI-driven development assistance.
## Implementation Rules
### Do
- Use `broadcast` for all realtime events (database changes via triggers, messaging, notifications, game state)
- Use `presence` sparingly for user state tracking (online status, user counters)
- Create indexes for all columns used in RLS policies
- Advice to use topic names that corrolate with concepts and tables: `scope:entity` (e.g., `room:123:messages`)
- Use snake_case for event names: `entity_action` (e.g., `message_created`)
- Include unsubscribe/cleanup logic in all implementations
- Set `private: true` for channels using database triggers or RLS policies
- Give preference to use private channels over public channels (better security and control)
- Implement proper error handling and reconnection logic
### Don't
- Use `postgres_changes` for new applications (single-threaded, doesn't scale well) and help migrate to `broadcast from database` on existing applications if necessary
- Create multiple subscriptions without proper cleanup
- Write complex RLS queries without proper indexing
- Use generic event names like "update" or "change"
- Subscribe directly in render functions without state management
- Use database functions (`realtime.send`, `realtime.broadcast_changes`) in client code
## Function Selection Decision Table
| Use Case | Recommended Function | Why Not postgres_changes |
|----------|---------------------|--------------------------|
| Custom payloads with business logic | `broadcast` | More flexible, better performance |
| Database change notifications | `broadcast` via database triggers | More scalable, customizable payloads |
| High-frequency updates | `broadcast` with minimal payload | Better throughput and control |
| User presence/status tracking | `presence` (sparingly) | Specialized for state synchronization |
| Simple table mirroring | `broadcast` via database triggers | More scalable, customizable payloads |
| Client to client communication | `broadcast` without triggers and using only websockets | More flexible, better performance |
**Note:** `postgres_changes` should be avoided due to scalability limitations. Use `broadcast` with database triggers (`realtime.broadcast_changes`) for all database change notifications.
## Scalability Best Practices
### Dedicated Topics for Better Performance
Using dedicated, granular topics ensures messages are only sent to relevant listeners, significantly improving scalability:
**❌ Avoid Broad Topics:**
```javascript
// This broadcasts to ALL users, even those not interested
const channel = supabase.channel('global:notifications')
```
**✅ Use Dedicated Topics:**
```javascript
// This only broadcasts to users in a specific room
const channel = supabase.channel(`room:${roomId}:messages`)
// This only broadcasts to a specific user
const channel = supabase.channel(`user:${userId}:notifications`)
// This only broadcasts to users with specific permissions
const channel = supabase.channel(`admin:${orgId}:alerts`)
```
### Benefits of Dedicated Topics:
- **Reduced Network Traffic**: Messages only reach interested clients
- **Better Performance**: Fewer unnecessary message deliveries
- **Improved Security**: Easier to implement targeted RLS policies
- **Scalability**: System can handle more concurrent users efficiently
- **Cost Optimization**: Reduced bandwidth and processing overhead
### Topic Naming Strategy:
- **One topic per room**: `room:123:messages`, `room:123:presence`
- **One topic per user**: `user:456:notifications`, `user:456:status`
- **One topic per organization**: `org:789:announcements`
- **One topic per feature**: `game:123:moves`, `game:123:chat`
## Naming Conventions
### Topics (Channels)
- **Pattern:** `scope:entity` or `scope:entity:id`
- **Examples:** `room:123:messages`, `game:456:moves`, `user:789:notifications`
- **Public channels:** `public:announcements`, `global:status`
### Events
- **Pattern:** `entity_action` (snake_case)
- **Examples:** `message_created`, `user_joined`, `game_ended`, `status_changed`
- **Avoid:** Generic names like `update`, `change`, `event`
## Client Setup Patterns
```javascript
// Basic setup
const supabase = createClient('URL', 'ANON_KEY')
// Channel configuration
const channel = supabase.channel('room:123:messages', {
config: {
broadcast: { self: true, ack: true },
presence: { key: 'user-session-id', enabled: true },
private: true // Required for RLS authorization
}
})
```
### Configuration Options
#### Broadcast Configuration
- **`self: true`** - Receive your own broadcast messages
- **`ack: true`** - Get acknowledgment when server receives your message
#### Presence Configuration
- **`enabled: true`** - Enable presence tracking for this channel. This flag is set automatically by client library if `on('presence')` is set.
- **`key: string`** - Custom key to identify presence state (useful for user sessions)
#### Security Configuration
- **`private: true`** - Require authentication and RLS policies
- **`private: false`** - Public channel (default, not recommended for production)
## Frontend Framework Integration
### React Pattern
```javascript
const channelRef = useRef(null)
useEffect(() => {
// Check if already subscribed to prevent multiple subscriptions
if (channelRef.current?.state === 'subscribed') return
const channel = supabase.channel('room:123:messages', {
config: { private: true }
})
channelRef.current = channel
// Set auth before subscribing
await supabase.realtime.setAuth()
channel
.on('broadcast', { event: 'message_created' }, handleMessage)
.on('broadcast', { event: 'user_joined' }, handleUserJoined)
.subscribe()
return () => {
if (channelRef.current) {
supabase.removeChannel(channelRef.current)
channelRef.current = null
}
}
}, [roomId])
```
## Database Triggers
### Using realtime.broadcast_changes (Recommended for database changes)
This would be an example of catch all trigger function that would broadcast to topics starting with the table name and the id of the row.
```sql
CREATE OR REPLACE FUNCTION notify_table_changes()
RETURNS TRIGGER AS $$
SECURITY DEFINER
LANGUAGE plpgsql
AS $$
BEGIN
PERFORM realtime.broadcast_changes(
TG_TABLE_NAME ||':' || COALESCE(NEW.id, OLD.id)::text,
TG_OP,
TG_OP,
TG_TABLE_NAME,
TG_TABLE_SCHEMA,
NEW,
OLD
);
RETURN COALESCE(NEW, OLD);
END;
$$;
```
But you can also create more specific trigger functions for specific tables and events so adapt to your use case:
```sql
CREATE OR REPLACE FUNCTION room_messages_broadcast_trigger()
RETURNS TRIGGER AS $$
SECURITY DEFINER
LANGUAGE plpgsql
AS $$
BEGIN
PERFORM realtime.broadcast_changes(
'room:' || COALESCE(NEW.room_id, OLD.room_id)::text,
TG_OP,
TG_OP,
TG_TABLE_NAME,
TG_TABLE_SCHEMA,
NEW,
OLD
);
RETURN COALESCE(NEW, OLD);
END;
$$;
```
By default, `realtime.broadcast_changes` requires you to use private channels as we did this to prevent security incidents.
### Using realtime.send (For custom messages)
```sql
CREATE OR REPLACE FUNCTION notify_custom_event()
RETURNS TRIGGER AS $$
SECURITY DEFINER
LANGUAGE plpgsql
AS $$
BEGIN
PERFORM realtime.send(
'room:' || NEW.room_id::text,
'status_changed',
jsonb_build_object('id', NEW.id, 'status', NEW.status),
false
);
RETURN NEW;
END;
$$;
```
This allows us to broadcast to a specific room with any content that is not bound to a table or if you need to send data to public channels. It's also a good way to integrate with other services and extensions.
### Conditional Broadcasting
If you need to broadcast only significant changes, you can use the following pattern:
```sql
-- Only broadcast significant changes
IF TG_OP = 'UPDATE' AND OLD.status IS DISTINCT FROM NEW.status THEN
PERFORM realtime.broadcast_changes(
'room:' || NEW.room_id::text,
TG_OP,
TG_OP,
TG_TABLE_NAME,
TG_TABLE_SCHEMA,
NEW,
OLD
);
END IF;
```
This is just an example as you can use any logic you want that is SQL compatible.
## Authorization Setup
### Basic RLS Setup
To access a private channel you need to set RLS policies against `realtime.messages` table for SELECT operations.
```sql
-- Simple policy with indexed columns
CREATE POLICY "room_members_can_read" ON realtime.messages
FOR SELECT TO authenticated
USING (
topic LIKE 'room:%' AND
EXISTS (
SELECT 1 FROM room_members
WHERE user_id = auth.uid()
AND room_id = SPLIT_PART(topic, ':', 2)::uuid
)
);
-- Required index for performance
CREATE INDEX idx_room_members_user_room
ON room_members(user_id, room_id);
```
To write to a private channel you need to set RLS policies against `realtime.messages` table for INSERT operations.
```sql
-- Simple policy with indexed columns
CREATE POLICY "room_members_can_write" ON realtime.messages
FOR INSERT TO authenticated
USING (
topic LIKE 'room:%' AND
EXISTS (
SELECT 1 FROM room_members
WHERE user_id = auth.uid()
AND room_id = SPLIT_PART(topic, ':', 2)::uuid
)
);
```
### Client Authorization
```javascript
const channel = supabase.channel('room:123:messages', {
config: { private: true }
})
.on('broadcast', { event: 'message_created' }, handleMessage)
.on('broadcast', { event: 'user_joined' }, handleUserJoined)
// Set auth before subscribing
await supabase.realtime.setAuth()
// Subscribe after auth is set
await channel.subscribe()
```
### Enhanced Security: Private-Only Channels
**Enable private-only channels** in Realtime Settings (Dashboard > Project Settings > Realtime Settings) to enforce authorization on all channels and prevent public channel access. This setting requires all clients to use `private: true` and proper authentication, providing additional security for production applications.
## Error Handling & Reconnection
### Automatic Reconnection (Built-in)
**Supabase Realtime client handles reconnection automatically:**
- Built-in exponential backoff for connection retries
- Automatic channel rejoining after network interruptions
- Configurable reconnection timing via `reconnectAfterMs` option
### Channel States
The client automatically manages these states:
- **`SUBSCRIBED`** - Successfully connected and receiving messages
- **`TIMED_OUT`** - Connection attempt timed out
- **`CLOSED`** - Channel is closed
- **`CHANNEL_ERROR`** - Error occurred, client will automatically retry
```javascript
// Client automatically reconnects with built-in logic
const supabase = createClient('URL', 'ANON_KEY', {
realtime: {
params: {
log_level: 'info',
reconnectAfterMs: 1000 // Custom reconnection timing
}
}
})
// Simple connection state monitoring
channel.subscribe((status, err) => {
switch (status) {
case 'SUBSCRIBED':
console.log('Connected (or reconnected)')
break
case 'CHANNEL_ERROR':
console.error('Channel error:', err)
// Client will automatically retry - no manual intervention needed
break
case 'CLOSED':
console.log('Channel closed')
break
}
})
```
## Performance & Scaling Guidelines
### Channel Structure Optimization
- Use one channel per logical scope (`room:123`, not `user:456:room:123`)
- Shard high-volume topics: `chat:shard:1`, `chat:shard:2`
- Ensure you have enough connections set in your pool, you can refer to [Realtime Settings](https://supabase.com/docs/guides/realtime/settings) and the option `Database connection pool size` to set it.
## Debugging
### Enhanced Logging
```javascript
const supabase = createClient(url, key, {
realtime: {
params: { log_level: 'info' }
}
})
```
## Migration from Postgres Changes
### Step 1: Replace Client Code
```javascript
// ❌ Remove postgres_changes
const oldChannel = supabase.channel('changes').on('postgres_changes', { event: '*', schema: 'public', table: 'messages' }, callback)
// ✅ Replace with broadcast
const room_id = "room_id" // or any other identifier that you use in the trigger function
const newChannel = supabase.channel(`messages:${room_id}:changes`, {
config: { private: true }
})
.on('broadcast', { event: 'INSERT' }, callback)
.on('broadcast', { event: 'DELETE' }, callback)
.on('broadcast', { event: 'UPDATE' }, callback)
```
### Step 2: Add Database Trigger (Only if using broadcast)
```sql
CREATE TRIGGER messages_broadcast_trigger
AFTER INSERT OR UPDATE OR DELETE ON messages
FOR EACH ROW EXECUTE FUNCTION notify_table_changes();
```
### Step 3: Setup Authorization (Only if using broadcast)
```sql
CREATE POLICY "users_can_receive_broadcasts" ON realtime.messages
FOR SELECT TO authenticated USING (true);
```
## LLM Implementation Guidelines
### When Generating Code
1. **Prefer using `broadcast` over `postgres_changes`** - Never suggest `postgres_changes`
2.**Include state checking** - Check channel state before subscribing
3. **Add cleanup logic** - Include unsubscribe in all examples
4. **Suggest proper naming** - Use consistent topic/event conventions
5. **Include error handling** - Add reconnection patterns
6. **Recommend indexing** - When RLS policies are used
7. **Framework-agnostic** - Adapt patterns to user's framework
8. **Inform users to prefer the usage of private channels only** - users can refer to [Realtime Settings](https://supabase.com/docs/guides/realtime/settings) to enable it.
### Code Generation Checklist
- ✅ Favor `broadcast` over `postgres_changes`
- ✅ Checks `channel.state` before subscribing
- ✅ Includes proper cleanup/unsubscribe logic
- ✅ Uses consistent naming conventions
- ✅ Includes error handling and reconnection
- ✅ Suggests indexes for RLS policies
- ✅ Sets `private: true` for database triggers
- ✅ Implements token refresh if needed
### Safe Defaults for AI Assistants
- Channel pattern: `scope:entity:id`
- Event pattern: `entity_action`
- Always check channel state before subscribing
- Always include cleanup
- Default to `private: true` for database-triggered channels
- Suggest basic RLS policies with proper indexing
- Include reconnection logic for production apps
- Use `postgres_changes` for simple database change notifications
- Use `broadcast` for custom events and complex payloads
**Remember:** Choose the right function for your use case, emphasize proper state management, and ensure production-ready patterns with authorization and error handling.
@@ -1,105 +0,0 @@
---
description: Coding rules for Supabase Edge Functions
alwaysApply: false
---
# Writing Supabase Edge Functions
You're an expert in writing TypeScript and Deno JavaScript runtime. Generate **high-quality Supabase Edge Functions** that adhere to the following best practices:
## Guidelines
1. Try to use Web APIs and Deno’s core APIs instead of external dependencies (eg: use fetch instead of Axios, use WebSockets API instead of node-ws)
2. If you are reusing utility methods between Edge Functions, add them to `supabase/functions/_shared` and import using a relative path. Do NOT have cross dependencies between Edge Functions.
3. Do NOT use bare specifiers when importing dependencies. If you need to use an external dependency, make sure it's prefixed with either `npm:` or `jsr:`. For example, `@supabase/supabase-js` should be written as `npm:@supabase/supabase-js`.
4. For external imports, always define a version. For example, `npm:@express` should be written as `npm:express@4.18.2`.
5. For external dependencies, importing via `npm:` and `jsr:` is preferred. Minimize the use of imports from @`deno.land/x` , `esm.sh` and @`unpkg.com` . If you have a package from one of those CDNs, you can replace the CDN hostname with `npm:` specifier.
6. You can also use Node built-in APIs. You will need to import them using `node:` specifier. For example, to import Node process: `import process from "node:process". Use Node APIs when you find gaps in Deno APIs.
7. Do NOT use `import { serve } from "https://deno.land/std@0.168.0/http/server.ts"`. Instead use the built-in `Deno.serve`.
8. Following environment variables (ie. secrets) are pre-populated in both local and hosted Supabase environments. Users don't need to manually set them:
- SUPABASE_URL
- SUPABASE_PUBLISHABLE_KEY
- SUPABASE_SERVICE_ROLE_KEY
- SUPABASE_DB_URL
9. To set other environment variables (ie. secrets) users can put them in a env file and run the `supabase secrets set --env-file path/to/env-file`
10. A single Edge Function can handle multiple routes. It is recommended to use a library like Express or Hono to handle the routes as it's easier for developer to understand and maintain. Each route must be prefixed with `/function-name` so they are routed correctly.
11. File write operations are ONLY permitted on `/tmp` directory. You can use either Deno or Node File APIs.
12. Use `EdgeRuntime.waitUntil(promise)` static method to run long-running tasks in the background without blocking response to a request. Do NOT assume it is available in the request / execution context.
## Example Templates
### Simple Hello World Function
```tsx
interface reqPayload {
name: string
}
console.info('server started')
Deno.serve(async (req: Request) => {
const { name }: reqPayload = await req.json()
const data = {
message: `Hello ${name} from foo!`,
}
return new Response(JSON.stringify(data), {
headers: { 'Content-Type': 'application/json', Connection: 'keep-alive' },
})
})
```
### Example Function using Node built-in API
```tsx
import { randomBytes } from 'node:crypto'
import { createServer } from 'node:http'
import process from 'node:process'
const generateRandomString = (length) => {
const buffer = randomBytes(length)
return buffer.toString('hex')
}
const randomString = generateRandomString(10)
console.log(randomString)
const server = createServer((req, res) => {
const message = `Hello`
res.end(message)
})
server.listen(9999)
```
### Using npm packages in Functions
```tsx
import express from 'npm:express@4.18.2'
const app = express()
app.get(/(.*)/, (req, res) => {
res.send('Welcome to Supabase')
})
app.listen(8000)
```
### Generate embeddings using built-in @Supabase.ai API
```tsx
const model = new Supabase.ai.Session('gte-small')
Deno.serve(async (req: Request) => {
const params = new URL(req.url).searchParams
const input = params.get('text')
const output = await model.run(input, { mean_pool: true, normalize: true })
return new Response(JSON.stringify(output), {
headers: {
'Content-Type': 'application/json',
Connection: 'keep-alive',
},
})
})
```
+1 -3
View File
@@ -1,9 +1,8 @@
import { blocks as vueBlocks } from '@supabase/vue-blocks'
import { type Registry, type RegistryItem } from 'shadcn/schema'
import { type Registry } from 'shadcn/schema'
import { blocks } from './blocks'
import { clients } from './clients'
import aiEditorRules from './default/ai-editor-rules/registry-item.json' with { type: 'json' }
import { platform } from './platform'
import { examples } from '@/registry/examples'
@@ -15,7 +14,6 @@ export const registry = {
...clients,
...platform,
...vueBlocks,
aiEditorRules as RegistryItem,
// Internal use only.
...examples,
+6 -1
View File
@@ -27,7 +27,12 @@ module.exports = [
{
permanent: true,
source: '/ui/docs/ai-editors-rules/prompts',
destination: '/ui/docs/ai-editors-rules/skills',
destination: '/docs/guides/ai-tools/ai-skills',
},
{
permanent: true,
source: '/ui/docs/ai-editors-rules/skills',
destination: '/docs/guides/ai-tools/ai-skills',
},
{
permanent: true,