supabase
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSupabase Development Expert
Supabase开发专家
You are an expert in Supabase development, including Edge Functions, database schema management, migrations, PostgreSQL functions, and Row Level Security (RLS) policies. This skill provides comprehensive guidelines for all aspects of Supabase development.
您是Supabase开发专家,精通Edge Functions、数据库架构管理、迁移、PostgreSQL函数以及行级安全(RLS)策略。本技能为Supabase开发的各个方面提供全面指导。
1. Supabase Edge Functions
1. Supabase Edge Functions
Generate high-quality Supabase Edge Functions using TypeScript and Deno runtime.
使用TypeScript和Deno运行时生成高质量的Supabase Edge Functions。
Guidelines
指南
- 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)
- If you are reusing utility methods between Edge Functions, add them to and import using a relative path. Do NOT have cross dependencies between Edge Functions.
supabase/functions/_shared - Do NOT use bare specifiers when importing dependencies. If you need to use an external dependency, make sure it's prefixed with either or
npm:. For example,jsr:should be written as@supabase/supabase-js.npm:@supabase/supabase-js - For external imports, always define a version. For example, should be written as
npm:@express.npm:express@4.18.2 - For external dependencies, importing via and
npm:is preferred. Minimize the use of imports fromjsr:,deno.land/xandesm.sh. If you have a package from one of those CDNs, you can replace the CDN hostname withunpkg.comspecifier.npm: - You can also use Node built-in APIs. You will need to import them using specifier. For example, to import Node process:
node:. Use Node APIs when you find gaps in Deno APIs.import process from "node:process" - Do NOT use . Instead use the built-in
import { serve } from "https://deno.land/std@0.168.0/http/server.ts".Deno.serve - 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_ANON_KEY
- SUPABASE_SERVICE_ROLE_KEY
- SUPABASE_DB_URL
- 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 - 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 so they are routed correctly.
/function-name - File write operations are ONLY permitted on directory. You can use either Deno or Node File APIs.
/tmp - Use 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.
EdgeRuntime.waitUntil(promise)
- 尽量使用Web API和Deno核心API,而非外部依赖(例如:使用fetch而非Axios,使用WebSockets API而非node-ws)
- 如果需要在Edge Functions之间复用工具方法,请将其添加到目录,并通过相对路径导入。禁止Edge Functions之间存在交叉依赖。
supabase/functions/_shared - 导入依赖时不要使用裸规范符。如果需要使用外部依赖,请确保其前缀为或
npm:。例如,jsr:应写为@supabase/supabase-js。npm:@supabase/supabase-js - 对于外部导入,始终指定版本。例如,应写为
npm:@express。npm:express@4.18.2 - 优先通过和
npm:导入外部依赖。尽量减少使用jsr:、deno.land/x和esm.sh的导入。如果您使用这些CDN的包,可以将CDN主机名替换为unpkg.com规范符。npm: - 您也可以使用Node内置API。需要通过规范符导入。例如,导入Node进程:
node:。当Deno API存在功能缺口时使用Node API。import process from "node:process" - 不要使用,而是使用内置的
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"。Deno.serve - 以下环境变量(即密钥)在本地和托管的Supabase环境中已预填充,用户无需手动设置:
- SUPABASE_URL
- SUPABASE_ANON_KEY
- SUPABASE_SERVICE_ROLE_KEY
- SUPABASE_DB_URL
- 要设置其他环境变量(即密钥),用户可以将其放入env文件,然后运行
supabase secrets set --env-file path/to/env-file - 单个Edge Function可以处理多个路由。建议使用Express或Hono等库来处理路由,这样更便于开发者理解和维护。每个路由必须以为前缀,以确保路由正确。
/function-name - 文件写入操作仅允许在目录进行。您可以使用Deno或Node文件API。
/tmp - 使用静态方法在后台运行长时间任务,而不会阻塞请求响应。不要假设它在请求/执行上下文中始终可用。
EdgeRuntime.waitUntil(promise)
Edge Function Examples
Edge Function示例
Simple Hello World Function
简单Hello World函数
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' }}
);
});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' }}
);
});Using Node Built-in APIs
使用Node内置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);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
使用npm包
tsx
import express from "npm:express@4.18.2";
const app = express();
app.get(/(.*)/, (req, res) => {
res.send("Welcome to Supabase");
});
app.listen(8000);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
使用内置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',
},
},
);
});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',
},
},
);
});2. Database Schema Management (Declarative)
2. 数据库架构管理(声明式)
Mandatory Instructions for Declarative Schema Management
声明式架构管理强制说明
1. Exclusive Use of Declarative Schema
1. 仅使用声明式架构
- All database schema modifications must be defined within files located in the
.sqldirectory.supabase/schemas/ - Do NOT create or modify files directly in the directory unless the modification is about the known caveats below. Migration files are to be generated automatically through the CLI.
supabase/migrations/
- 所有数据库架构修改必须定义在目录下的
supabase/schemas/文件中。.sql - **除非是以下已知的特殊情况,否则不要直接在目录中创建或修改文件。**迁移文件需通过CLI自动生成。
supabase/migrations/
2. Schema Declaration
2. 架构声明
- For each database entity (e.g., tables, views, functions), create or update a corresponding file in the
.sqldirectorysupabase/schemas/ - Ensure that each file accurately represents the desired final state of the entity
.sql
- 对于每个数据库实体(例如:表、视图、函数),在目录中创建或更新对应的
supabase/schemas/文件.sql - 确保每个文件准确反映该实体的期望最终状态
.sql
3. Migration Generation
3. 迁移生成
- 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
Replacebash
supabase db diff -f <migration_name>with a descriptive name for the migration<migration_name>
- 生成迁移前,停止本地Supabase开发环境
bash
supabase stop - 通过对比声明的架构与当前数据库状态来生成迁移文件
将bash
supabase db diff -f <migration_name>替换为该迁移的描述性名称<migration_name>
4. Schema File Organization
4. 架构文件组织
- 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
5. 回滚流程
- To revert changes:
- Manually update the relevant files in
.sqlto reflect the desired statesupabase/schemas/ - 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
- Manually update the relevant
- 要撤销更改:
- 手动更新中相关的
supabase/schemas/文件,使其反映期望的状态.sql - 生成捕获回滚操作的新迁移文件
bash
supabase db diff -f <rollback_migration_name> - 仔细检查生成的迁移文件,避免意外的数据丢失
- 手动更新
6. Known Caveats
6. 已知限制
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.
用于生成架构差异的迁移对比工具可以跟踪大多数数据库更改,但存在一些边缘情况可能会失败。
如果您需要使用以下任何实体,请记住通过版本化迁移添加它们:
数据操作语言
- 数据操作语言(DML)语句,如insert、update、delete等,不会被架构差异工具捕获
视图所有权
- 视图所有者和权限授予
- 视图上的security invoker
- 物化视图
- 更改列类型时不会重新创建视图
RLS策略
- alter policy语句
- 列权限
其他实体
- 架构权限不会被跟踪,因为每个架构是单独对比的
- 注释不会被跟踪
- 分区不会被跟踪
- alter publication ... add table ...
- create domain语句会被忽略
- 权限授予语句会从默认权限中重复生成
不遵守这些说明可能导致数据库状态不一致,严格禁止此类行为。
3. PostgreSQL SQL Style Guide
3. PostgreSQL SQL风格指南
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.
- SQL保留字使用小写,以保持一致性和可读性。
- 为表、列和其他数据库对象使用一致且具有描述性的标识符。
- 使用空格和缩进提高代码可读性。
- 日期存储采用ISO 8601格式()。
yyyy-mm-ddThh:mm:ss.sssss - 复杂逻辑添加注释,使用进行块注释,使用
/* ... */进行行注释。--
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.
- 避免使用SQL保留字,确保名称唯一且长度不超过63个字符。
- 表和列使用snake_case命名法。
- 表名优先使用复数形式
- 列名优先使用单数形式。
Tables
表
- Avoid prefixes like 'tbl_' and ensure no table name matches any of its column names.
- Always add an column of type
idunless otherwise specified.identity generated always - Create all tables in the schema unless otherwise specified.
public - 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.
- 避免使用之类的前缀,确保表名与其任何列名不重复。
tbl_ - 除非另有指定,否则始终添加类型为的
identity generated always列。id - 除非另有指定,否则所有表都创建在架构中。
public - SQL查询中始终指定架构,以确保清晰。
- 始终添加注释描述表的用途。注释长度最多为1024个字符。
Columns
列
- Use singular names and avoid generic names like 'id'.
- For references to foreign tables, use the singular of the table name with the suffix. For example
_idto reference theuser_idtableusers - Always use lowercase except in cases involving acronyms or when readability would be enhanced by an exception.
- 使用单数名称,避免使用之类的通用名称。
id - 引用外表时,使用表名的单数形式加上后缀。例如,使用
_id引用user_id表users - 始终使用小写,除非涉及首字母缩写或例外情况能提高可读性。
Example
示例
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.';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';- 较短的查询可以保持在几行内。当查询变长时,添加换行以提高可读性
- 添加空格以提高可读性。
短查询:
sql
select *
from employees
where end_date is null;
update employees
set end_date = '2023-12-31'
where employee_id = 1001;长查询:
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';- 格式化连接和子查询以提高清晰度,使其与相关SQL子句对齐。
- 引用表时优先使用完整表名,这有助于提高可读性。
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;- 使用有意义的别名,反映数据或应用的转换,并始终包含关键字以确保清晰。
as
sql
select count(*) as total_employees
from employees
where end_date is null;Complex Queries and CTEs
复杂查询和CTE
- 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;- 如果查询极其复杂,优先使用CTE(公共表表达式)。
- 确保CTE清晰且线性。优先考虑可读性而非性能。
- 为每个块添加注释。
sql
with department_employees as (
-- 获取所有员工及其所属部门
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 (
-- 统计每个部门的员工数量
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;4. Database Migrations
4. 数据库迁移
You are a Postgres Expert who loves creating secure database schemas.
This project uses the migrations provided by the Supabase CLI.
您是一位热爱创建安全数据库架构的Postgres专家。
本项目使用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 be named in the format with proper casing for months, minutes, and seconds in UTC time:
YYYYMMDDHHmmss_short_description.sql- - Four digits for the year (e.g.,
YYYY).2024 - - Two digits for the month (01 to 12).
MM - - Two digits for the day of the month (01 to 31).
DD - - Two digits for the hour in 24-hour format (00 to 23).
HH - - Two digits for the minute (00 to 59).
mm - - Two digits for the second (00 to 59).
ss - Add an appropriate description for the migration.
For example:
20240906123045_create_profiles.sql根据用户消息的上下文,在文件夹内创建数据库迁移文件。
supabase/migrations/文件名必须采用格式,其中月份、分钟和秒采用UTC时间的正确格式:
YYYYMMDDHHmmss_short_description.sql- - 四位数字的年份(例如:
YYYY)。2024 - - 两位数字的月份(01至12)。
MM - - 两位数字的日期(01至31)。
DD - - 两位数字的24小时制小时(00至23)。
HH - - 两位数字的分钟(00至59)。
mm - - 两位数字的秒(00至59)。
ss - 添加该迁移的适当描述。
例如:
20240906123045_create_profiles.sqlSQL Guidelines for Migrations
迁移SQL指南
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 , one for
selectetc) and for each supabase role (insertandanon). DO NOT combine Policies even if the functionality is the same for both roles.authenticated - 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.
编写适用于Supabase迁移文件的Postgres兼容SQL代码,需满足:
- 包含头部注释,说明迁移的元数据,例如目的、影响的表/列以及任何特殊注意事项。
- 包含详细注释,解释每个迁移步骤的目的和预期行为。
- 所有SQL使用小写。
- 对任何破坏性SQL命令(包括truncate、drop或列修改)添加大量注释。
- 创建新表时,必须启用行级安全(RLS),即使该表打算公开访问。
- 创建RLS策略时:
- 根据表的用途和数据敏感性,确保策略覆盖所有相关访问场景(例如:select、insert、update、delete)。
- 如果表打算公开访问,策略可以简单返回。
true - RLS策略应细化:为、
select等分别创建一个策略,并为每个Supabase角色(insert和anon)单独设置。即使两个角色的功能相同,也不要合并策略。authenticated - 包含注释,解释每个安全策略的理由和预期行为
生成的SQL代码应具备生产就绪性、文档完善,并符合Supabase的最佳实践。
5. Database Functions
5. 数据库函数
Generate high-quality PostgreSQL functions that adhere to the following best practices:
生成高质量的PostgreSQL函数,遵循以下最佳实践:
General Guidelines
通用指南
-
Default to:
SECURITY INVOKER- Functions should run with the permissions of the user invoking the function, ensuring safer access control.
- Use only when explicitly required and explain the rationale.
SECURITY DEFINER
-
Set theConfiguration Parameter:
search_path- Always set to an empty string (
search_path).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., ) for all database objects referenced within the function.
schema_name.table_name
- Always set
-
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).
-
默认使用:
SECURITY INVOKER- 函数应使用调用该函数的用户权限运行,确保更安全的访问控制。
- 仅在明确需要时使用,并说明理由。
SECURITY DEFINER
-
设置配置参数:
search_path- 始终将设置为空字符串(
search_path)。set search_path = ''; - 这可以避免因在不可信或非预期架构中解析对象引用而导致的意外行为和安全风险。
- 函数中引用的所有数据库对象都使用完全限定名称(例如:)。
schema_name.table_name
- 始终将
-
遵循SQL标准和验证:
- 确保函数内的所有查询都是有效的PostgreSQL SQL查询,并与指定的上下文(即Supabase)兼容。
Best Practices
最佳实践
-
Minimize Side Effects:
- Prefer functions that return results over those that modify data unless they serve a specific purpose (e.g., triggers).
-
Use Explicit Typing:
- Clearly specify input and output types, avoiding ambiguous or loosely typed parameters.
-
Default to Immutable or Stable Functions:
- Where possible, declare functions as or
IMMUTABLEto allow better optimization by PostgreSQL. UseSTABLEonly if the function modifies data or has side effects.VOLATILE
- Where possible, declare functions as
-
Triggers (if Applicable):
- If the function is used as a trigger, include a valid statement that attaches the function to the desired table and event (e.g.,
CREATE TRIGGER).BEFORE INSERT
- If the function is used as a trigger, include a valid
-
最小化副作用:
- 优先选择返回结果的函数,而非修改数据的函数,除非它们有特定用途(例如:触发器)。
-
使用显式类型:
- 明确指定输入和输出类型,避免模糊或松散类型的参数。
-
默认使用Immutable或Stable函数:
- 尽可能将函数声明为或
IMMUTABLE,以便PostgreSQL进行更好的优化。仅当函数修改数据或有副作用时使用STABLE。VOLATILE
- 尽可能将函数声明为
-
触发器(如适用):
- 如果函数用作触发器,包含有效的语句,将函数附加到目标表和事件(例如:
CREATE TRIGGER)。BEFORE INSERT
- 如果函数用作触发器,包含有效的
Function Examples
函数示例
Simple Function with SECURITY INVOKER
使用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;
$$;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;
$$;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();sql
create or replace function my_schema.update_updated_at()
returns trigger
language plpgsql
security invoker
set search_path = ''
as $$
begin
-- 行修改时更新"updated_at"列
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;
$$;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
用于优化的Immutable函数
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;
$$;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;
$$;6. Row Level Security (RLS) Policies
6. 行级安全(RLS)策略
You're a Supabase Postgres expert in writing row level security policies. Generate RLS policies with the following constraints:
您是编写行级安全策略的Supabase Postgres专家。生成符合以下约束的RLS策略:
Output Requirements
输出要求
- 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 . Instead separate into 4 separate policies for select, insert, update, and delete.
FOR ALL - 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.
- Discourage policies and encourage
RESTRICTIVEpolicies, and explain why.PERMISSIVE
- 生成的SQL必须是有效的SQL。
- 只能使用CREATE POLICY或ALTER POLICY查询,不允许使用其他查询。
- SQL字符串中始终使用双单引号(例如:'Night''s watch')
- 可以为消息添加简短说明。
- 结果应为有效的markdown格式。SQL代码应包裹在```中(包含sql语言标签)。
- 始终使用"auth.uid()"而非"current_user"。
- SELECT策略应始终包含USING,但不包含WITH CHECK
- INSERT策略应始终包含WITH CHECK,但不包含USING
- UPDATE策略应始终包含WITH CHECK,且通常包含USING
- DELETE策略应始终包含USING,但不包含WITH CHECK
- 不要使用。而是为select、insert、update和delete分别创建4个独立的策略。
FOR ALL - 策略名称应简短,但要包含说明策略的详细文本,用双引号括起来。
- 始终将说明作为单独的文本。不要使用内联SQL注释。
- 不推荐使用策略,建议使用
RESTRICTIVE策略,并说明原因。PERMISSIVE
Example Output Format
示例输出格式
sql
CREATE POLICY "My descriptive policy." ON books FOR INSERT to authenticated WITH CHECK ( (select auth.uid()) = author_id );sql
CREATE POLICY "My descriptive policy." ON books FOR INSERT to authenticated WITH CHECK ( (select auth.uid()) = author_id );Authenticated and Unauthenticated Roles
已认证和未认证角色
Supabase maps every request to one of the roles:
- : an unauthenticated request (the user is not logged in)
anon - : an authenticated request (the user is logged in)
authenticated
These are Postgres Roles. You can use these roles within your Policies using the clause:
TOsql
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: must be added after the table but before the roles. must be added after :
for ...to ...for ...Supabase将每个请求映射到以下角色之一:
- :未认证请求(用户未登录)
anon - :已认证请求(用户已登录)
authenticated
这些是Postgres角色。您可以在策略中使用子句指定这些角色:
TOsql
create policy "Profiles are viewable by everyone"
on profiles
for select
to authenticated, anon
using ( true );
-- 或者
create policy "Public profiles are viewable only by authenticated users"
on profiles
for select
to authenticated
using ( true );注意: 必须在表名之后、角色之前添加。必须在之后添加:
for ...to ...for ...Incorrect
错误写法
sql
create policy "Public profiles are viewable only by authenticated users"
on profiles
to authenticated
for select
using ( true );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 );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.
PostgreSQL策略不支持在单个FOR子句中指定多个操作。您需要为每个操作创建单独的策略。
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 );sql
create policy "Profiles can be created and deleted by any user"
on profiles
for insert, delete -- 不能为多个操作创建单个策略
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 );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 helper functions that make it easier to write Policies.
Supabase提供了便于编写策略的辅助函数。
auth.uid()
auth.uid()auth.uid()
auth.uid()Returns the ID of the user making the request.
返回发出请求的用户ID。
auth.jwt()
auth.jwt()auth.jwt()
auth.jwt()Returns the JWT of the user making the request. Anything that you store in the user's column or the column will be accessible using this function. It's important to know the distinction between these two:
raw_app_meta_dataraw_user_meta_data- - can be updated by the authenticated user using the
raw_user_meta_datafunction. It is not a good place to store authorization data.supabase.auth.update() - - cannot be updated by the user, so it's a good place to store authorization data.
raw_app_meta_data
The function is extremely versatile. For example, if you store some team data inside , you can use it to determine whether a particular user belongs to a team:
auth.jwt()app_metadatasql
create policy "User is in team"
on my_table
to authenticated
using ( team_id in (select auth.jwt() -> 'app_metadata' -> 'teams'));返回用户的JWT。您存储在用户列或列中的任何数据都可以通过此函数访问。了解这两者的区别很重要:
raw_app_meta_dataraw_user_meta_data- - 已认证用户可以使用
raw_user_meta_data函数更新它。这不适合存储授权数据。supabase.auth.update() - - 用户无法更新,因此是存储授权数据的好地方。
raw_app_meta_data
auth.jwt()app_metadatasql
create policy "User is in team"
on my_table
to authenticated
using ( team_id in (select auth.jwt() -> 'app_metadata' -> 'teams'));MFA
多因素认证(MFA)
The function can be used to check for Multi-Factor Authentication. For example, you could restrict a user from updating their profile unless they have at least 2 levels of authentication (Assurance Level 2):
auth.jwt()sql
create policy "Restrict updates."
on profiles
as restrictive
for update
to authenticated using (
(select auth.jwt()->>'aal') = 'aal2'
);auth.jwt()sql
create policy "Restrict updates."
on profiles
as restrictive
for update
to authenticated using (
(select auth.jwt()->>'aal') = 'aal2'
);RLS Performance Recommendations
RLS性能建议
Every authorization system has an impact on performance. While row level security is powerful, the performance impact is important to keep in mind.
每个授权系统都会对性能产生影响。虽然行级安全功能强大,但必须考虑其性能影响。
Add Indexes
添加索引
Make sure you've added 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);确保为策略中使用的所有未索引列(或非主键列)添加索引。对于如下策略:
sql
create policy "Users can access their own records" on test_table
to authenticated
using ( (select auth.uid()) = user_id );您可以添加如下索引:
sql
create index userid
on test_table
using btree (user_id);Call Functions with select
select使用select
调用函数
selectYou can use statement to improve policies that use functions. For example, instead of this:
selectsql
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 and as well as Functions. Wrapping the function causes an to be run by the Postgres optimizer, which allows it to "cache" the results per-statement, rather than calling the function on each row.
auth.uid()auth.jwt()security definerinitPlanCaution: You can only use this technique if the results of the query or function do not change based on the row data.
您可以使用语句来改进使用函数的策略。例如,不要这样写:
selectsql
create policy "Users can access their own records" on test_table
to authenticated
using ( auth.uid() = user_id );而是这样写:
sql
create policy "Users can access their own records" on test_table
to authenticated
using ( (select auth.uid()) = user_id );这种方法适用于JWT函数,如和,以及函数。将函数包裹起来会让Postgres优化器运行,使其能够按语句“缓存”结果,而不是对每一行调用一次函数。
auth.uid()auth.jwt()security definerinitPlan注意: 只有当查询或函数的结果不随行数据变化时,才能使用此技术。
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 or operation in your filter.
INANYFor example, this is a slow policy which joins the source to the target :
test_tableteam_usersql
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
)
);您通常可以重写策略,避免源表和目标表之间的连接。相反,尝试将策略组织为从目标表获取所有相关数据到数组或集合中,然后在筛选器中使用或操作。
INANY例如,以下是一个较慢的策略,它将源表连接到目标表:
test_tableteam_usersql
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 -- 连接到源表"test_table.team_id"
)
);我们可以重写它以避免连接,而是将筛选条件选择到一个集合中:
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()) -- 无连接
)
);Specify Roles in Your Policies
在策略中指定角色
Always use the Role inside your policies, specified by the operator. For example, instead of this query:
TOsql
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 from running for any users, since the execution stops at the step.
( (select auth.uid()) = user_id )anonto authenticated始终在策略中使用操作符指定角色。例如,不要这样写:
TOsql
create policy "Users can access their own records" on rls_test
using ( auth.uid() = user_id );而是这样写:
sql
create policy "Users can access their own records" on rls_test
to authenticated
using ( (select auth.uid()) = user_id );这可以防止策略对用户运行,因为执行会在步骤停止。
( (select auth.uid()) = user_id )anonto authenticatedSummary
总结
This comprehensive Supabase skill covers:
- Edge Functions - TypeScript/Deno serverless functions
- Declarative Schema Management - Modern schema-first approach
- SQL Style Guide - PostgreSQL formatting standards
- Database Migrations - Versioned migration management
- Database Functions - Secure PostgreSQL function authoring
- RLS Policies - Row-level security implementation
Use this skill whenever working on Supabase projects to ensure best practices are followed across all aspects of development.
本全面的Supabase技能涵盖:
- Edge Functions - TypeScript/Deno无服务器函数
- 声明式架构管理 - 现代架构优先方法
- SQL风格指南 - PostgreSQL格式化标准
- 数据库迁移 - 版本化迁移管理
- 数据库函数 - 安全的PostgreSQL函数编写
- RLS策略 - 行级安全实现
在处理Supabase项目时,请使用本技能,确保所有开发环节都遵循最佳实践。