Skip to main content

Introduction

The Supabase data provider enables you to build Refine applications with Supabase, an open source Firebase alternative built on PostgreSQL. It provides full support for CRUD operations, real-time subscriptions, and authentication.

Installation

Install the Supabase data provider package:
npm install @refinedev/supabase

Setup

1

Create Supabase project

First, create a Supabase project at supabase.com and get your API credentials from the project settings.
2

Create Supabase client

Create a Supabase client with your project credentials:
import { createClient } from "@refinedev/supabase";

const supabaseClient = createClient(
  "YOUR_SUPABASE_URL",
  "YOUR_SUPABASE_KEY"
);
3

Configure data provider

Configure the Refine app with the Supabase data provider:
import { Refine } from "@refinedev/core";
import { dataProvider, liveProvider, createClient } from "@refinedev/supabase";

const supabaseClient = createClient(
  "YOUR_SUPABASE_URL",
  "YOUR_SUPABASE_KEY"
);

const App = () => {
  return (
    <Refine
      dataProvider={dataProvider(supabaseClient)}
      liveProvider={liveProvider(supabaseClient)}
      options={{ liveMode: "auto" }}
    >
      {/* Your app content */}
    </Refine>
  );
};

Basic Usage

Get List

Fetch a list of records from a Supabase table:
import { useList } from "@refinedev/core";

const { data } = useList({
  resource: "posts",
  pagination: {
    current: 1,
    pageSize: 10,
  },
  sorters: [
    {
      field: "created_at",
      order: "desc",
    },
  ],
  filters: [
    {
      field: "status",
      operator: "eq",
      value: "published",
    },
  ],
});

Get One

Fetch a single record by ID:
import { useOne } from "@refinedev/core";

const { data } = useOne({
  resource: "posts",
  id: "1",
});

Create

Create a new record:
import { useCreate } from "@refinedev/core";

const { mutate } = useCreate();

const handleSubmit = (values) => {
  mutate({
    resource: "posts",
    values: {
      title: values.title,
      content: values.content,
      status: "draft",
    },
  });
};

Update

Update an existing record:
import { useUpdate } from "@refinedev/core";

const { mutate } = useUpdate();

const handleUpdate = (id, values) => {
  mutate({
    resource: "posts",
    id,
    values: {
      title: values.title,
      content: values.content,
    },
  });
};

Delete

Delete a record:
import { useDelete } from "@refinedev/core";

const { mutate } = useDelete();

const handleDelete = (id) => {
  mutate({
    resource: "posts",
    id,
  });
};

Real-time Updates

The Supabase data provider supports real-time updates through Supabase’s subscription feature:
import { Refine } from "@refinedev/core";
import { dataProvider, liveProvider, createClient } from "@refinedev/supabase";

const supabaseClient = createClient(
  "YOUR_SUPABASE_URL",
  "YOUR_SUPABASE_KEY"
);

const App = () => {
  return (
    <Refine
      dataProvider={dataProvider(supabaseClient)}
      liveProvider={liveProvider(supabaseClient)}
      options={{
        liveMode: "auto", // Enable automatic real-time updates
      }}
    >
      {/* Your app content */}
    </Refine>
  );
};
With liveMode: "auto", your lists will automatically update when data changes in Supabase.

Authentication

The Supabase data provider works seamlessly with Supabase authentication:
import { Refine } from "@refinedev/core";
import { dataProvider, createClient } from "@refinedev/supabase";

const supabaseClient = createClient(
  "YOUR_SUPABASE_URL",
  "YOUR_SUPABASE_KEY"
);

const authProvider = {
  login: async ({ email, password }) => {
    const { data, error } = await supabaseClient.auth.signInWithPassword({
      email,
      password,
    });

    if (error) {
      return {
        success: false,
        error,
      };
    }

    return {
      success: true,
      redirectTo: "/",
    };
  },
  logout: async () => {
    const { error } = await supabaseClient.auth.signOut();

    if (error) {
      return {
        success: false,
        error,
      };
    }

    return {
      success: true,
      redirectTo: "/login",
    };
  },
  check: async () => {
    const { data } = await supabaseClient.auth.getSession();
    const { session } = data;

    if (!session) {
      return {
        authenticated: false,
        redirectTo: "/login",
      };
    }

    return {
      authenticated: true,
    };
  },
  getIdentity: async () => {
    const { data } = await supabaseClient.auth.getUser();

    if (data?.user) {
      return {
        id: data.user.id,
        email: data.user.email,
      };
    }

    return null;
  },
  onError: async (error) => {
    console.error(error);
    return { error };
  },
};

const App = () => {
  return (
    <Refine
      dataProvider={dataProvider(supabaseClient)}
      authProvider={authProvider}
    >
      {/* Your app content */}
    </Refine>
  );
};

Advanced Queries

Filtering

Supabase supports various filter operators:
import { useList } from "@refinedev/core";

const { data } = useList({
  resource: "posts",
  filters: [
    {
      field: "title",
      operator: "contains",
      value: "tutorial",
    },
    {
      field: "views",
      operator: "gte",
      value: 100,
    },
    {
      field: "status",
      operator: "in",
      value: ["published", "featured"],
    },
  ],
});

Joining Tables

Use the select meta property to join related tables:
import { useList } from "@refinedev/core";

const { data } = useList({
  resource: "posts",
  meta: {
    select: "*, author:users(*), categories(*)",
  },
});

Custom Queries

Execute custom Supabase queries using the meta.select property:
import { useList } from "@refinedev/core";

const { data } = useList({
  resource: "posts",
  meta: {
    select: "id, title, content, created_at",
  },
  filters: [
    {
      field: "published_at",
      operator: "gte",
      value: "2024-01-01",
    },
  ],
});

Storage Integration

Work with Supabase Storage for file uploads:
import { useState } from "react";
import { createClient } from "@refinedev/supabase";

const supabaseClient = createClient(
  "YOUR_SUPABASE_URL",
  "YOUR_SUPABASE_KEY"
);

const FileUpload = () => {
  const [uploading, setUploading] = useState(false);

  const handleUpload = async (file: File) => {
    try {
      setUploading(true);

      const fileExt = file.name.split(".").pop();
      const fileName = `${Math.random()}.${fileExt}`;
      const filePath = `${fileName}`;

      const { error: uploadError } = await supabaseClient.storage
        .from("avatars")
        .upload(filePath, file);

      if (uploadError) {
        throw uploadError;
      }

      const { data } = supabaseClient.storage
        .from("avatars")
        .getPublicUrl(filePath);

      return data.publicUrl;
    } catch (error) {
      console.error("Error uploading file:", error);
    } finally {
      setUploading(false);
    }
  };

  return (
    <input
      type="file"
      onChange={(e) => {
        const file = e.target.files?.[0];
        if (file) {
          handleUpload(file);
        }
      }}
      disabled={uploading}
    />
  );
};

Row Level Security (RLS)

Supabase’s Row Level Security policies are automatically respected by the data provider. Configure RLS policies in your Supabase dashboard:
-- Example: Users can only see their own posts
CREATE POLICY "Users can view their own posts"
ON posts
FOR SELECT
USING (auth.uid() = user_id);

-- Example: Users can update their own posts
CREATE POLICY "Users can update their own posts"
ON posts
FOR UPDATE
USING (auth.uid() = user_id);

Complete Example

import { Refine } from "@refinedev/core";
import { dataProvider, liveProvider, createClient } from "@refinedev/supabase";
import routerProvider from "@refinedev/react-router";
import { BrowserRouter } from "react-router";

const supabaseClient = createClient(
  process.env.REACT_APP_SUPABASE_URL!,
  process.env.REACT_APP_SUPABASE_KEY!
);

const authProvider = {
  login: async ({ email, password }) => {
    const { data, error } = await supabaseClient.auth.signInWithPassword({
      email,
      password,
    });

    if (error) {
      return { success: false, error };
    }

    return { success: true, redirectTo: "/" };
  },
  logout: async () => {
    const { error } = await supabaseClient.auth.signOut();

    if (error) {
      return { success: false, error };
    }

    return { success: true, redirectTo: "/login" };
  },
  check: async () => {
    const { data } = await supabaseClient.auth.getSession();
    const { session } = data;

    if (!session) {
      return { authenticated: false, redirectTo: "/login" };
    }

    return { authenticated: true };
  },
  getIdentity: async () => {
    const { data } = await supabaseClient.auth.getUser();

    if (data?.user) {
      return {
        id: data.user.id,
        email: data.user.email,
      };
    }

    return null;
  },
  onError: async (error) => {
    console.error(error);
    return { error };
  },
};

const App = () => {
  return (
    <BrowserRouter>
      <Refine
        dataProvider={dataProvider(supabaseClient)}
        liveProvider={liveProvider(supabaseClient)}
        authProvider={authProvider}
        routerProvider={routerProvider}
        options={{ liveMode: "auto" }}
        resources={[
          {
            name: "posts",
            list: "/posts",
            create: "/posts/create",
            edit: "/posts/edit/:id",
            show: "/posts/show/:id",
          },
        ]}
      >
        {/* Your routes and pages */}
      </Refine>
    </BrowserRouter>
  );
};

export default App;

Supported Operators

The Supabase data provider supports the following filter operators:
  • eq: Equals
  • ne: Not equals
  • lt: Less than
  • lte: Less than or equal
  • gt: Greater than
  • gte: Greater than or equal
  • in: In array
  • nin: Not in array
  • contains: Contains (text search)
  • containss: Contains (case-insensitive)
  • null: Is null
  • nnull: Is not null

Next Steps

Data Providers Overview

Learn about other data providers

Supabase Documentation

Explore Supabase features

Build docs developers (and LLMs) love