Skip to content

Commit e34510b

Browse files
mrprkrclaude
andcommitted
docs: use @supabase/server in Supabase database operations guide
Replace manual jsonwebtoken signing and createClient with @supabase/server core primitives: verifyCredentials + createContextClient for the user-scoped insert example, and createAdminClient for the admin example. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JGxYWHGbdiQ1BwXWY92dJ4
1 parent c3b0a24 commit e34510b

1 file changed

Lines changed: 63 additions & 39 deletions

File tree

‎docs/guides/examples/supabase-database-operations.mdx‎

Lines changed: 63 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -13,53 +13,68 @@ This is a basic task which inserts a new row into a table from a Trigger.dev tas
1313

1414
### Key features
1515

16-
- Shows how to set up a Supabase client using the `@supabase/supabase-js` library
16+
- Shows how to verify a user's Supabase access token using the [`@supabase/server`](https://github.com/supabase/server) package
17+
- Shows how to create a user-scoped Supabase client, so your [Row Level Security (RLS) policies](https://supabase.com/docs/guides/database/postgres/row-level-security) apply
1718
- Shows how to add a new row to a table using `insert`
1819

1920
### Prerequisites
2021

2122
- A [Supabase account](https://supabase.com/dashboard/) and a project set up
23+
- Your project uses the new [API keys](https://supabase.com/docs/guides/api/api-keys) (`sb_publishable_...` / `sb_secret_...`) and [JWT signing keys](https://supabase.com/docs/guides/auth/signing-keys). `@supabase/server` does not accept legacy `anon` / `service_role` keys or HS256-signed JWTs.
2224
- In your Supabase project, create a table called `user_subscriptions`.
2325
- In your `user_subscriptions` table, create a new column:
2426
- `user_id`, with the data type: `text`
27+
- An RLS policy on `user_subscriptions` that allows authenticated users to insert their own row, for example `with check (auth.uid()::text = user_id)`
28+
29+
### Installation
30+
31+
Install `@supabase/server` and its `@supabase/supabase-js` peer dependency:
32+
33+
```bash
34+
npm install @supabase/server @supabase/supabase-js
35+
```
36+
37+
### Environment variables
38+
39+
Add these to your project's [environment variables](/deploy-environment-variables) in the Trigger.dev dashboard:
40+
41+
- `SUPABASE_URL`: your project URL, e.g. `https://<project-ref>.supabase.co`
42+
- `SUPABASE_PUBLISHABLE_KEY`: your publishable key (`sb_publishable_...`)
43+
44+
The JWKS used to verify user tokens is derived automatically from `SUPABASE_URL`.
2545

2646
### Task code
2747

48+
Your app triggers this task with the signed-in user's access token (for example `session.access_token` from `supabase.auth.getSession()`). The task verifies the token, then queries the database as that user.
49+
2850
```ts trigger/supabase-database-insert.ts
29-
import { createClient } from "@supabase/supabase-js";
30-
import { task } from "@trigger.dev/sdk";
31-
import jwt from "jsonwebtoken";
51+
import { AbortTaskRunError, task } from "@trigger.dev/sdk";
52+
import { createContextClient, verifyCredentials } from "@supabase/server/core";
3253
// Generate the Typescript types using the Supabase CLI: https://supabase.com/docs/guides/api/rest/generating-types
3354
import { Database } from "database.types";
3455

3556
export const supabaseDatabaseInsert = task({
3657
id: "add-new-user",
37-
run: async (payload: { userId: string }) => {
38-
const { userId } = payload;
58+
run: async (payload: { accessToken: string }) => {
59+
// Verify the user's access token against your project's JWKS
60+
const { data: auth, error: authError } = await verifyCredentials(
61+
{ token: payload.accessToken, apikey: null },
62+
{ auth: "user" }
63+
);
3964

40-
// Get JWT secret from env vars
41-
const jwtSecret = process.env.SUPABASE_JWT_SECRET;
42-
if (!jwtSecret) {
43-
throw new Error("SUPABASE_JWT_SECRET is not defined in environment variables");
65+
// An invalid or expired token won't succeed on retry, so abort the run
66+
if (authError) {
67+
throw new AbortTaskRunError(`Invalid access token: ${authError.message}`);
4468
}
4569

46-
// Create JWT token for the user
47-
const token = jwt.sign({ sub: userId }, jwtSecret, { expiresIn: "1h" });
48-
49-
// Initialize Supabase client with JWT
50-
const supabase = createClient<Database>(
51-
process.env.SUPABASE_URL as string,
52-
process.env.SUPABASE_ANON_KEY as string,
53-
{
54-
global: {
55-
headers: {
56-
Authorization: `Bearer ${token}`,
57-
},
58-
},
59-
}
60-
);
70+
const userId = auth.userClaims!.id;
71+
72+
// Create a Supabase client scoped to the user, so RLS policies apply
73+
const supabase = createContextClient<Database>({
74+
auth: { token: auth.token },
75+
});
6176

62-
// Insert a new row into the user_subscriptions table with the provided userId
77+
// Insert a new row into the user_subscriptions table for the verified user
6378
const { error } = await supabase.from("user_subscriptions").insert({
6479
user_id: userId,
6580
});
@@ -76,6 +91,12 @@ export const supabaseDatabaseInsert = task({
7691
});
7792
```
7893

94+
<Note>
95+
Supabase access tokens are short-lived (1 hour by default). If a run is delayed or retried after
96+
the token has expired, verification will fail. For long-running or delayed work, use the admin
97+
client shown in the next example and pass the user ID in the payload instead.
98+
</Note>
99+
79100
<SupabaseAuthInfo />
80101

81102
### Testing your task
@@ -84,11 +105,11 @@ To test this task in the [Trigger.dev dashboard](https://cloud.trigger.dev), you
84105

85106
```json
86107
{
87-
"userId": "user_12345"
108+
"accessToken": "<a signed-in user's Supabase access token>"
88109
}
89110
```
90111

91-
If the task completes successfully, you will see a new row in your `user_subscriptions` table with the `user_id` set to `user_12345`.
112+
If the task completes successfully, you will see a new row in your `user_subscriptions` table with the `user_id` set to that user's ID.
92113

93114
## Update a user's subscription on a table in a Supabase database
94115

@@ -98,7 +119,7 @@ This type of task is useful for managing user subscriptions, updating user detai
98119

99120
### Key features
100121

101-
- Shows how to set up a Supabase client using the `@supabase/supabase-js` library
122+
- Shows how to create an admin Supabase client using the [`@supabase/server`](https://github.com/supabase/server) package
102123
- Adds a new row to the table if the user doesn't exist using `insert`
103124
- Checks if the user already has a plan, and if they do updates the existing row using `update`
104125
- Demonstrates how to use [AbortTaskRunError](https://trigger.dev/docs/errors-retrying#using-aborttaskrunerror) to stop the task run without retrying if an invalid plan type is provided
@@ -113,25 +134,24 @@ This type of task is useful for managing user subscriptions, updating user detai
113134
- `plan`, with the data type: `text`
114135
- `updated_at`, with the data type: `timestamptz`
115136

137+
### Environment variables
138+
139+
Add these to your project's [environment variables](/deploy-environment-variables) in the Trigger.dev dashboard:
140+
141+
- `SUPABASE_URL`: your project URL, e.g. `https://<project-ref>.supabase.co`
142+
- `SUPABASE_SECRET_KEY`: your secret key (`sb_secret_...`)
143+
116144
### Task code
117145

118146
```ts trigger/supabase-update-user-subscription.ts
119-
import { createClient } from "@supabase/supabase-js";
120147
import { AbortTaskRunError, task } from "@trigger.dev/sdk";
148+
import { createAdminClient } from "@supabase/server/core";
121149
// Generate the Typescript types using the Supabase CLI: https://supabase.com/docs/guides/api/rest/generating-types
122150
import { Database } from "database.types";
123151

124152
// Define the allowed plan types
125153
type PlanType = "hobby" | "pro" | "enterprise";
126154

127-
// Create a single Supabase client for interacting with your database
128-
// 'Database' supplies the type definitions to supabase-js
129-
const supabase = createClient<Database>(
130-
// These details can be found in your Supabase project settings under `API`
131-
process.env.SUPABASE_PROJECT_URL as string, // e.g. https://abc123.supabase.co - replace 'abc123' with your project ID
132-
process.env.SUPABASE_SERVICE_ROLE_KEY as string // Your service role secret key
133-
);
134-
135155
export const supabaseUpdateUserSubscription = task({
136156
id: "update-user-subscription",
137157
run: async (payload: { userId: string; newPlan: PlanType }) => {
@@ -144,6 +164,10 @@ export const supabaseUpdateUserSubscription = task({
144164
);
145165
}
146166

167+
// Create an admin Supabase client using SUPABASE_URL and SUPABASE_SECRET_KEY
168+
// 'Database' supplies the type definitions to supabase-js
169+
const supabase = createAdminClient<Database>();
170+
147171
// Query the user_subscriptions table to check if the user already has a subscription
148172
const { data: existingSubscriptions } = await supabase
149173
.from("user_subscriptions")
@@ -186,7 +210,7 @@ export const supabaseUpdateUserSubscription = task({
186210
```
187211

188212
<Note>
189-
This task uses your service role secret key to bypass Row Level Security. There are different ways
213+
This task uses your secret key to bypass Row Level Security. There are different ways
190214
of configuring your [RLS
191215
policies](https://supabase.com/docs/guides/database/postgres/row-level-security), so always make
192216
sure you have the correct permissions set up for your project.

0 commit comments

Comments
 (0)