Bridger Logo

Bridger Tower / Designer and Software Engineer

Use Supabase with Payload CMS

A guide to setting up Payload 3 with a Supabase backend for high-performance, serverless applications.

Using Supabase with Payload CMS 3

This guide walks you through how to integrate Supabase with Payload CMS 3. Payload 3.0 is now fully Next.js native, installing directly into your Next.js application with both the admin panel and backend living right in your app folder.


Why Supabase + Payload CMS?

  • Supabase provides a Postgres database, authentication, storage, and more.
  • Payload CMS is now the first truly Next.js-native CMS with built-in server components support, local API capabilities, and seamless integration with Next.js features.

By using them together, you can:

  1. Leverage Supabase Authentication in Payload to manage your users and roles.
  2. Use Supabase's powerful PostgreSQL database - Payload 3.0 now has stable PostgreSQL support with Vercel PostgreSQL adapters.
  3. Take advantage of Supabase Storage for files or media while Payload handles structured content with its new bulk upload capabilities.
  4. Build high-performance applications using Payload's server components and local API for the fastest possible data access.

Key Features in Payload 3.0

  1. Next.js Native Integration

    • Install directly into any Next.js app
    • Full admin panel and backend in your app folder
    • Deploy anywhere: serverless on Vercel, containers, or your preferred platform
  2. Database Flexibility

    • Stable PostgreSQL support
    • SQLite support
    • Vercel PostgreSQL database adapters
    • Powered by Drizzle ORM
  3. Performance Optimizations

    • Server components and server functions support
    • Local API for direct database access
    • Selective loading with new Select and Populate APIs
    • React compiler integration for better performance
  4. Enhanced Content Management

    • Stable Lexical rich text editor with inline and block components
    • Built-in jobs queue for task automation
    • Bulk upload support
    • Live Preview with server component support

Prerequisites

  1. Node.js (v16+)
  2. Next.js (App Router)
  3. Payload CMS v3 (installed via create-payload-app or added to existing Next.js project)
  4. Supabase Project (set up in your Supabase dashboard)

1. Set Up Your Project

  1. If starting from scratch, create a new Next.js project with Payload:

    bash
    1pnpm create payload-app

    Select the Website template to get started with server components and Tailwind CSS.

  2. If adding to an existing Next.js project:

    bash
    1pnpm add payload
  3. Set up your Supabase project:

    • Go to app.supabase.com and create a new project
    • Save your credentials:
      • Project URL (often called supabaseUrl)
      • API Key (often called supabaseKey or anonKey)

2. Initialize Your Payload CMS 3 Project

If you haven't already installed Payload, you can do so by running:

bash
1pnpm create payload-app my-payload-app

Follow the prompts to choose your configuration. Once it's set up:

bash
1cd my-payload-app
2pnpm install
3pnpm run dev

By default, your local Payload CMS runs at http://localhost:3000/admin.


3. Install the Supabase Client

Within your Payload project, install Supabase's JavaScript client:

bash
1pnpm install @supabase/supabase-js

4. Connect Supabase and Payload

Create a helper module (e.g. supabaseClient.ts or supabaseClient.js) to configure and export a Supabase client you can reuse throughout your code:

ts
1// supabaseClient.ts
2import { createClient } from "@supabase/supabase-js";
3
4const supabaseUrl = process.env.SUPABASE_URL || "";
5const supabaseKey = process.env.SUPABASE_ANON_KEY || "";
6
7export const supabase = createClient(supabaseUrl, supabaseKey);

Add the corresponding .env variables:

bash
1# .env
2SUPABASE_URL=https://xyzcompany.supabase.co
3SUPABASE_ANON_KEY=your-anon-key

Note: For production, you might want to use the service role key or a more secure environment variable. Keep your credentials safe!


5. Example: Using Supabase in Payload Hooks

A common scenario might be using Supabase whenever a new document is created or updated in Payload. For instance, you could create a user in Supabase when a new User document is created in Payload.

ts
1// payload.config.ts
2import { buildConfig } from "payload/config";
3import { supabase } from "./supabaseClient";
4
5export default buildConfig({
6 // ...rest of your Payload config
7 collections: [
8 {
9 slug: "users",
10 fields: [
11 {
12 name: "email",
13 type: "email",
14 required: true,
15 },
16 {
17 name: "password",
18 type: "text",
19 required: true,
20 },
21 ],
22 hooks: {
23 afterChange: [
24 async ({ operation, doc }) => {
25 if (operation === "create") {
26 // Create a user in Supabase
27 const { email, password } = doc;
28 const { error } = await supabase.auth.signUp({
29 email,
30 password,
31 });
32
33 if (error) {
34 console.error(
35 "Error creating user in Supabase:",
36 error.message
37 );
38 }
39 }
40 },
41 ],
42 },
43 },
44 ],
45});
  • afterChange is a Payload hook that triggers when a document is created or updated.
  • operation === 'create' ensures we only create the user in Supabase when the document is newly added.

6. Example: Using Supabase Storage in a Payload Collection

Suppose you want to leverage Supabase Storage to host images or files. You could store an image in Payload but also upload it to Supabase Storage in an afterChange hook:

ts
1// payload.config.ts
2import { supabase } from "./supabaseClient";
3
4export default buildConfig({
5 collections: [
6 {
7 slug: "media",
8 fields: [
9 {
10 name: "file",
11 type: "upload",
12 relationTo: "media-files",
13 },
14 ],
15 hooks: {
16 afterChange: [
17 async ({ doc }) => {
18 const fileData = doc?.file;
19 if (fileData) {
20 // This could be the file buffer, filename, etc.
21 const fileBuffer = fileData?.buffer;
22 const fileName = fileData?.filename || "payload-upload";
23
24 // Upload to Supabase Storage
25 const { error } = await supabase.storage
26 .from("your-bucket-name")
27 .upload(`images/${fileName}`, fileBuffer);
28
29 if (error) {
30 console.error(
31 "Error uploading file to Supabase:",
32 error.message
33 );
34 } else {
35 console.log("File uploaded successfully to Supabase!");
36 }
37 }
38 },
39 ],
40 },
41 },
42 ],
43});

Adapt this to your actual file handling strategy. Payload's upload fields can be configured to store locally or elsewhere before you sync it to Supabase.


7. Authentication Considerations

  1. Using Supabase Auth for the Payload Admin: Payload has its own auth, but you could offload user management to Supabase. In that case, you'd integrate or override Payload's auth logic with Supabase.
  2. JWT vs. Session: Payload uses JWT-based authentication. Supabase also uses JWT under the hood with additional helpers. Make sure you handle tokens securely if you're bridging the two systems.

8. Deployment Tips

  • Self-Hosting: Both Supabase and Payload can be self-hosted. Ensure your environment variables (Supabase URL, keys, etc.) are correctly set in Docker, AWS, or wherever you host.
  • Netlify / Vercel: If you host your front-end on Netlify or Vercel, keep your Payload server and Supabase project accessible from that environment.
  • Security: Use secure tokens (like the service role key) only on the server side. Never expose them in public code.

Next Steps

  • Explore Payload Plugins: You could build a dedicated Payload plugin to encapsulate your Supabase integration.
  • Fine-Tune Hooks: Payload's hooks are very flexible—use them to integrate Supabase logic exactly where you need it.
  • Consider Real-Time: Supabase provides real-time capabilities. You could push real-time updates from Supabase into your Payload front-end or use webhooks that update Payload content.

Conclusion

That's it! By combining Supabase and Payload CMS 3, you have a flexible, powerful system for data storage, user management, and file uploads. The best part is that both are straightforward once you get the hang of them.

Feel free to adapt the examples above to fit your exact use-case. Let me know if you have any questions—I'm happy to help. Keep building awesome stuff!

Happy coding!

© Bridger Tower, 2025