Smithery Logo
MCPsSkillsDocsPricing
Login
NewFlame, an assistant that learns and improves. Available onTelegramSlack
    neversight

    supabase-react-best-practices

    neversight/supabase-react-best-practices
    Productivity
    2

    About

    SKILL.md

    Install

    • Telegram
      Telegram
    • Slack
      Slack
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    • Download skill
    ├─
    ├─
    └─
    Smithery Logo

    Give agents more agency

    Resources

    DocumentationPrivacy PolicySystem Status

    Company

    PricingAboutBlog

    Connect

    © 2026 Smithery. All rights reserved.

    About

    Comprehensive guide for building production-ready React applications with Supabase, TypeScript, and TanStack Query...

    SKILL.md

    Supabase + React Best Practices

    Expert guide for building scalable, performant React applications with Supabase.

    When to Use This Skill

    • Setting up new Supabase + React project
    • Implementing authentication flows
    • Creating data fetching hooks with TanStack Query
    • Adding real-time subscriptions
    • Optimizing database queries
    • Setting up TypeScript types
    • Debugging Supabase integration issues

    Quick Start Checklist

    // ✅ Essential Setup
    - [ ] Install: @supabase/supabase-js @tanstack/react-query
    - [ ] Generate TypeScript types from schema
    - [ ] Create typed Supabase client
    - [ ] Set up TanStack Query provider
    - [ ] Configure environment variables
    - [ ] Set up auth context/provider
    

    1. Project Structure (Best Practice)

    src/
    ├── integrations/
    │   └── supabase/
    │       ├── client.ts          # Supabase client singleton
    │       ├── types.ts           # Generated DB types
    │       └── auth.tsx           # Auth context provider
    ├── features/
    │   └── [feature]/
    │       ├── hooks/
    │       │   ├── use[Feature].ts       # Query hooks
    │       │   └── use[Feature]Mutations.ts  # Mutation hooks
    │       ├── components/
    │       └── types.ts
    └── lib/
        └── queryClient.ts         # TanStack Query config
    

    Why This Structure:

    • Clear separation of concerns
    • Reusable hooks across features
    • Type safety throughout
    • Easy to test and maintain

    2. TypeScript Setup (CRITICAL)

    Generate Types from Database

    # Generate types (run after every migration)
    npx supabase gen types typescript --project-id YOUR_PROJECT_ID > src/integrations/supabase/types.ts
    
    # Or use local
    npx supabase gen types typescript --local > src/integrations/supabase/types.ts
    

    Create Typed Client

    File: src/integrations/supabase/client.ts

    import { createClient } from '@supabase/supabase-js';
    import type { Database } from './types';
    
    const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
    const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
    
    if (!supabaseUrl || !supabaseAnonKey) {
      throw new Error('Missing Supabase environment variables');
    }
    
    // ✅ Typed Supabase client
    export const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey, {
      auth: {
        persistSession: true,
        autoRefreshToken: true,
        detectSessionInUrl: true
      }
    });
    
    // Export types for convenience
    export type Tables<T extends keyof Database['public']['Tables']> =
      Database['public']['Tables'][T]['Row'];
    export type Enums<T extends keyof Database['public']['Enums']> =
      Database['public']['Enums'][T];
    

    Benefits:

    • Full IntelliSense for all tables
    • Type-safe queries
    • Catch errors at compile time
    • Better refactoring support

    3. Authentication Patterns

    Pattern 1: Auth Context Provider (Recommended)

    File: src/integrations/supabase/auth.tsx

    import { createContext, useContext, useEffect, useState } from 'react';
    import { User, Session } from '@supabase/supabase-js';
    import { supabase } from './client';
    
    interface AuthContext {
      user: User | null;
      session: Session | null;
      loading: boolean;
      signIn: (email: string, password: string) => Promise<void>;
      signOut: () => Promise<void>;
    }
    
    const AuthContext = createContext<AuthContext | undefined>(undefined);
    
    export function AuthProvider({ children }: { children: React.ReactNode }) {
      const [user, setUser] = useState<User | null>(null);
      const [session, setSession] = useState<Session | null>(null);
      const [loading, setLoading] = useState(true);
    
      useEffect(() => {
        // Get initial session
        supabase.auth.getSession().then(({ data: { session } }) => {
          setSession(session);
          setUser(session?.user ?? null);
          setLoading(false);
        });
    
        // Listen for auth changes
        const {
          data: { subscription },
        } = supabase.auth.onAuthStateChange((_event, session) => {
          setSession(session);
          setUser(session?.user ?? null);
          setLoading(false);
        });
    
        return () => subscription.unsubscribe();
      }, []);
    
      const signIn = async (email: string, password: string) => {
        const { error } = await supabase.auth.signInWithPassword({
          email,
          password,
        });
        if (error) throw error;
      };
    
      const signOut = async () => {
        const { error } = await supabase.auth.signOut();
        if (error) throw error;
      };
    
      return (
        <AuthContext.Provider value={{ user, session, loading, signIn, signOut }}>
          {children}
        </AuthContext.Provider>
      );
    }
    
    export const useAuth = () => {
      const context = useContext(AuthContext);
      if (!context) {
        throw new Error('useAuth must be used within AuthProvider');
      }
      return context;
    };
    

    Usage:

    // App.tsx
    import { AuthProvider } from '@/integrations/supabase/auth';
    
    function App() {
      return (
        <AuthProvider>
          <RouterProvider router={router} />
        </AuthProvider>
      );
    }
    
    // In components
    function Profile() {
      const { user, loading } = useAuth();
    
      if (loading) return <Spinner />;
      if (!user) return <Navigate to="/login" />;
    
      return <div>Welcome {user.email}</div>;
    }
    

    Pattern 2: Protected Routes

    import { Navigate, Outlet } from 'react-router-dom';
    import { useAuth } from '@/integrations/supabase/auth';
    
    export function ProtectedRoute() {
      const { user, loading } = useAuth();
    
      if (loading) {
        return <div>Loading...</div>;
      }
    
      return user ? <Outlet /> : <Navigate to="/login" replace />;
    }
    
    // In router config
    <Route element={<ProtectedRoute />}>
      <Route path="/dashboard" element={<Dashboard />} />
      <Route path="/profile" element={<Profile />} />
    </Route>
    

    4. Data Fetching with TanStack Query

    Setup Query Client

    File: src/lib/queryClient.ts

    import { QueryClient } from '@tanstack/react-query';
    
    export const queryClient = new QueryClient({
      defaultOptions: {
        queries: {
          staleTime: 5 * 60 * 1000, // 5 minutes
          cacheTime: 10 * 60 * 1000, // 10 minutes
          retry: 1,
          refetchOnWindowFocus: false,
        },
      },
    });
    

    Query Hooks Pattern

    File: src/features/events/hooks/useEvents.ts

    import { useQuery } from '@tanstack/react-query';
    import { supabase } from '@/integrations/supabase/client';
    import type { Tables } from '@/integrations/supabase/client';
    
    type Event = Tables<'events'>;
    
    /**
     * Fetch all published events
     *
     * @example
     * const { data, isLoading, error } = useEvents();
     */
    export function useEvents() {
      return useQuery({
        queryKey: ['events'],
        queryFn: async () => {
          const { data, error } = await supabase
            .from('events')
            .select('*')
            .eq('status', 'published')
            .order('start_at', { ascending: true });
    
          if (error) throw error;
          return data as Event[];
        },
        staleTime: 5 * 60 * 1000,
      });
    }
    
    /**
     * Fetch single event by ID
     */
    export function useEvent(id: string) {
      return useQuery({
        queryKey: ['event', id],
        queryFn: async () => {
          const { data, error } = await supabase
            .from('events')
            .select('*, venue:venues(*)')
            .eq('id', id)
            .single();
    
          if (error) throw error;
          return data;
        },
        enabled: !!id, // Only run if ID provided
        staleTime: 5 * 60 * 1000,
      });
    }
    

    Mutation Hooks Pattern

    File: src/features/events/hooks/useEventMutations.ts

    import { useMutation, useQueryClient } from '@tanstack/react-query';
    import { supabase } from '@/integrations/supabase/client';
    import type { Tables } from '@/integrations/supabase/client';
    
    type EventInsert = Tables<'events'>['Insert'];
    type EventUpdate = Tables<'events'>['Update'];
    
    export function useCreateEvent() {
      const queryClient = useQueryClient();
    
      return useMutation({
        mutationFn: async (event: EventInsert) => {
          const { data, error } = await supabase
            .from('events')
            .insert(event)
            .select()
            .single();
    
          if (error) throw error;
          return data;
        },
        onSuccess: () => {
          // Invalidate queries to refetch
          queryClient.invalidateQueries({ queryKey: ['events'] });
        },
      });
    }
    
    export function useUpdateEvent() {
      const queryClient = useQueryClient();
    
      return useMutation({
        mutationFn: async ({ id, ...updates }: { id: string } & EventUpdate) => {
          const { data, error } = await supabase
            .from('events')
            .update(updates)
            .eq('id', id)
            .select()
            .single();
    
          if (error) throw error;
          return data;
        },
        onSuccess: (data) => {
          // Invalidate list and single item
          queryClient.invalidateQueries({ queryKey: ['events'] });
          queryClient.invalidateQueries({ queryKey: ['event', data.id] });
        },
      });
    }
    
    export function useDeleteEvent() {
      const queryClient = useQueryClient();
    
      return useMutation({
        mutationFn: async (id: string) => {
          const { error } = await supabase
            .from('events')
            .delete()
            .eq('id', id);
    
          if (error) throw error;
        },
        onSuccess: () => {
          queryClient.invalidateQueries({ queryKey: ['events'] });
        },
      });
    }
    

    Usage in Components:

    function EventForm() {
      const createEvent = useCreateEvent();
      const updateEvent = useUpdateEvent();
    
      const handleSubmit = async (values) => {
        try {
          if (editMode) {
            await updateEvent.mutateAsync({ id: eventId, ...values });
            toast.success('Event updated!');
          } else {
            await createEvent.mutateAsync(values);
            toast.success('Event created!');
          }
        } catch (error) {
          toast.error(error.message);
        }
      };
    
      return <form onSubmit={handleSubmit}>...</form>;
    }
    

    5. Real-Time Subscriptions

    Pattern: Real-Time Hook

    import { useEffect } from 'react';
    import { useQueryClient } from '@tanstack/react-query';
    import { supabase } from '@/integrations/supabase/client';
    
    export function useEventsSubscription() {
      const queryClient = useQueryClient();
    
      useEffect(() => {
        const channel = supabase
          .channel('events-changes')
          .on(
            'postgres_changes',
            {
              event: '*',
              schema: 'public',
              table: 'events',
            },
            (payload) => {
              console.log('Event change:', payload);
    
              // Invalidate queries to refetch
              queryClient.invalidateQueries({ queryKey: ['events'] });
    
              // Or update cache directly (optimistic)
              if (payload.eventType === 'INSERT') {
                queryClient.setQueryData(['events'], (old: any[]) => [
                  ...(old || []),
                  payload.new,
                ]);
              }
            }
          )
          .subscribe();
    
        return () => {
          supabase.removeChannel(channel);
        };
      }, [queryClient]);
    }
    
    // Usage
    function EventsList() {
      const { data: events, isLoading } = useEvents();
      useEventsSubscription(); // Auto-refetch on changes
    
      return <div>{events?.map(...)}</div>;
    }
    

    6. Advanced Query Patterns

    Filtering with Parameters

    export function useEvents(filters?: {
      status?: string;
      type?: string;
      search?: string;
    }) {
      return useQuery({
        queryKey: ['events', filters],
        queryFn: async () => {
          let query = supabase
            .from('events')
            .select('*');
    
          if (filters?.status) {
            query = query.eq('status', filters.status);
          }
    
          if (filters?.type) {
            query = query.eq('type', filters.type);
          }
    
          if (filters?.search) {
            query = query.ilike('name', `%${filters.search}%`);
          }
    
          const { data, error } = await query.order('start_at', { ascending: true });
    
          if (error) throw error;
          return data;
        },
        enabled: true,
      });
    }
    

    Pagination

    export function useEventsPaginated(page: number, pageSize: number = 10) {
      return useQuery({
        queryKey: ['events', 'paginated', page, pageSize],
        queryFn: async () => {
          const from = page * pageSize;
          const to = from + pageSize - 1;
    
          const { data, error, count } = await supabase
            .from('events')
            .select('*', { count: 'exact' })
            .range(from, to)
            .order('start_at', { ascending: false });
    
          if (error) throw error;
    
          return {
            events: data,
            total: count || 0,
            totalPages: Math.ceil((count || 0) / pageSize),
          };
        },
      });
    }
    

    Complex Joins

    export function useEventWithDetails(id: string) {
      return useQuery({
        queryKey: ['event', 'details', id],
        queryFn: async () => {
          const { data, error } = await supabase
            .from('events')
            .select(`
              *,
              venue:venues(*),
              organizer:profiles(id, full_name, avatar_url),
              tickets:ticket_tiers(*),
              bookings:orders(
                id,
                status,
                total_amount,
                attendee:attendees(*)
              )
            `)
            .eq('id', id)
            .single();
    
          if (error) throw error;
          return data;
        },
        enabled: !!id,
      });
    }
    

    7. Row Level Security (RLS) Best Practices

    Understanding RLS in React

    Problem: Your React app uses the anon key, which has limited permissions.

    Solution: RLS policies control what data users can access based on auth.uid().

    Testing RLS Policies

    // Helper to test if user can access data
    export async function testRLS() {
      const { data: { user } } = await supabase.auth.getUser();
    
      console.log('Current user:', user?.id);
    
      // Test read access
      const { data, error } = await supabase
        .from('events')
        .select('*');
    
      if (error) {
        console.error('RLS blocking access:', error);
      } else {
        console.log('Accessible events:', data);
      }
    }
    

    Common RLS Patterns for React

    -- Allow public read, authenticated insert
    CREATE POLICY "Public events are viewable by everyone"
      ON events FOR SELECT
      USING (visibility = 'public');
    
    CREATE POLICY "Users can insert their own events"
      ON events FOR INSERT
      WITH CHECK (auth.uid() = organizer_id);
    
    CREATE POLICY "Users can update their own events"
      ON events FOR UPDATE
      USING (auth.uid() = organizer_id);
    

    Handling RLS Errors in React

    export function useEvents() {
      return useQuery({
        queryKey: ['events'],
        queryFn: async () => {
          const { data, error } = await supabase
            .from('events')
            .select('*');
    
          // RLS will return error if blocked
          if (error) {
            if (error.code === 'PGRST301') {
              throw new Error('You do not have permission to view these events');
            }
            throw error;
          }
    
          return data;
        },
        retry: false, // Don't retry RLS errors
      });
    }
    

    8. Error Handling Patterns

    Global Error Boundary

    import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
    import { toast } from 'sonner';
    
    const queryClient = new QueryClient({
      defaultOptions: {
        queries: {
          onError: (error: any) => {
            // Handle RLS errors
            if (error.code === 'PGRST301') {
              toast.error('Access denied. Please check your permissions.');
              return;
            }
    
            // Handle auth errors
            if (error.message?.includes('JWT')) {
              toast.error('Session expired. Please log in again.');
              // Optionally redirect to login
              return;
            }
    
            // Generic error
            toast.error(error.message || 'Something went wrong');
          },
        },
        mutations: {
          onError: (error: any) => {
            toast.error(error.message || 'Failed to save changes');
          },
        },
      },
    });
    

    Component-Level Error Handling

    function EventsList() {
      const { data, isLoading, error } = useEvents();
    
      if (isLoading) {
        return <Skeleton count={5} />;
      }
    
      if (error) {
        return (
          <Alert variant="destructive">
            <AlertTitle>Error loading events</AlertTitle>
            <AlertDescription>{error.message}</AlertDescription>
          </Alert>
        );
      }
    
      if (!data || data.length === 0) {
        return (
          <EmptyState
            title="No events yet"
            description="Get started by creating your first event"
            action={<Button>Create Event</Button>}
          />
        );
      }
    
      return <div>{data.map(...)}</div>;
    }
    

    9. Performance Optimization

    1. Selective Field Fetching

    // ❌ Bad: Fetching all fields
    const { data } = await supabase.from('events').select('*');
    
    // ✅ Good: Only fetch what you need
    const { data } = await supabase
      .from('events')
      .select('id, name, start_at, price_cents');
    

    2. Query Key Strategies

    // Hierarchical query keys for better caching
    const queryKeys = {
      all: ['events'] as const,
      lists: () => [...queryKeys.all, 'list'] as const,
      list: (filters: string) => [...queryKeys.lists(), filters] as const,
      details: () => [...queryKeys.all, 'detail'] as const,
      detail: (id: string) => [...queryKeys.details(), id] as const,
    };
    
    // Usage
    useQuery({ queryKey: queryKeys.list('published') });
    useQuery({ queryKey: queryKeys.detail(eventId) });
    
    // Invalidate all events
    queryClient.invalidateQueries({ queryKey: queryKeys.all });
    
    // Invalidate only lists
    queryClient.invalidateQueries({ queryKey: queryKeys.lists() });
    

    3. Optimistic Updates

    export function useUpdateEvent() {
      const queryClient = useQueryClient();
    
      return useMutation({
        mutationFn: async ({ id, ...updates }) => {
          const { data, error } = await supabase
            .from('events')
            .update(updates)
            .eq('id', id)
            .select()
            .single();
    
          if (error) throw error;
          return data;
        },
        onMutate: async ({ id, ...updates }) => {
          // Cancel outgoing refetches
          await queryClient.cancelQueries({ queryKey: ['event', id] });
    
          // Snapshot previous value
          const previousEvent = queryClient.getQueryData(['event', id]);
    
          // Optimistically update
          queryClient.setQueryData(['event', id], (old: any) => ({
            ...old,
            ...updates,
          }));
    
          return { previousEvent };
        },
        onError: (err, variables, context) => {
          // Rollback on error
          if (context?.previousEvent) {
            queryClient.setQueryData(
              ['event', variables.id],
              context.previousEvent
            );
          }
        },
        onSettled: (data, error, variables) => {
          // Always refetch after error or success
          queryClient.invalidateQueries({ queryKey: ['event', variables.id] });
        },
      });
    }
    

    4. Prefetching for Better UX

    function EventCard({ event }) {
      const queryClient = useQueryClient();
    
      const prefetchEvent = () => {
        queryClient.prefetchQuery({
          queryKey: ['event', event.id],
          queryFn: () => fetchEventDetails(event.id),
        });
      };
    
      return (
        <Card onMouseEnter={prefetchEvent}>
          <Link to={`/events/${event.id}`}>{event.name}</Link>
        </Card>
      );
    }
    

    10. Security Best Practices

    1. Never Expose Service Role Key

    // ❌ NEVER do this in client-side code
    const supabase = createClient(url, SERVICE_ROLE_KEY);
    
    // ✅ Always use anon key in React
    const supabase = createClient(url, ANON_KEY);
    

    2. Use RLS, Not Client-Side Checks

    // ❌ Bad: Client-side authorization
    function deleteEvent(id: string) {
      if (user.role !== 'admin') {
        toast.error('Not authorized');
        return;
      }
      // Delete event
    }
    
    // ✅ Good: Let RLS handle it
    function deleteEvent(id: string) {
      // RLS policy will block if user isn't authorized
      supabase.from('events').delete().eq('id', id);
    }
    

    3. Validate Input Data

    import { z } from 'zod';
    
    const eventSchema = z.object({
      name: z.string().min(3).max(100),
      description: z.string().max(5000),
      price_cents: z.number().int().positive(),
      start_at: z.string().datetime(),
    });
    
    export function useCreateEvent() {
      return useMutation({
        mutationFn: async (event: unknown) => {
          // Validate before sending to Supabase
          const validated = eventSchema.parse(event);
    
          const { data, error } = await supabase
            .from('events')
            .insert(validated)
            .select()
            .single();
    
          if (error) throw error;
          return data;
        },
      });
    }
    

    4. Sanitize User Input

    // Prevent SQL injection in text search
    export function useSearchEvents(query: string) {
      const sanitized = query.replace(/[%_]/g, '\\$&');
    
      return useQuery({
        queryKey: ['events', 'search', sanitized],
        queryFn: async () => {
          const { data, error } = await supabase
            .from('events')
            .select('*')
            .ilike('name', `%${sanitized}%`);
    
          if (error) throw error;
          return data;
        },
      });
    }
    

    11. Testing Patterns

    Mock Supabase Client

    File: src/__tests__/mocks/supabase.ts

    import { vi } from 'vitest';
    
    export const mockSupabase = {
      from: vi.fn(() => ({
        select: vi.fn(() => ({
          eq: vi.fn(() => ({
            single: vi.fn(() => Promise.resolve({ data: null, error: null })),
          })),
        })),
        insert: vi.fn(() => ({
          select: vi.fn(() => ({
            single: vi.fn(() => Promise.resolve({ data: null, error: null })),
          })),
        })),
      })),
      auth: {
        getSession: vi.fn(() => Promise.resolve({ data: { session: null }, error: null })),
        signInWithPassword: vi.fn(),
        signOut: vi.fn(),
      },
    };
    

    Test Hooks

    import { renderHook, waitFor } from '@testing-library/react';
    import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
    import { useEvents } from '../useEvents';
    
    const createWrapper = () => {
      const queryClient = new QueryClient({
        defaultOptions: { queries: { retry: false } },
      });
    
      return ({ children }) => (
        <QueryClientProvider client={queryClient}>
          {children}
        </QueryClientProvider>
      );
    };
    
    describe('useEvents', () => {
      it('fetches events successfully', async () => {
        const { result } = renderHook(() => useEvents(), {
          wrapper: createWrapper(),
        });
    
        await waitFor(() => expect(result.current.isSuccess).toBe(true));
    
        expect(result.current.data).toHaveLength(4);
      });
    });
    

    12. Common Pitfalls & Solutions

    Pitfall 1: Not Handling Loading States

    // ❌ Bad: Flash of wrong content
    function EventsList() {
      const { data } = useEvents();
      return <div>{data?.map(...)}</div>;
    }
    
    // ✅ Good: Proper loading state
    function EventsList() {
      const { data, isLoading } = useEvents();
    
      if (isLoading) {
        return <Skeleton count={5} />;
      }
    
      return <div>{data?.map(...)}</div>;
    }
    

    Pitfall 2: Missing Query Key Dependencies

    // ❌ Bad: Filters don't refetch
    const { data } = useQuery({
      queryKey: ['events'],
      queryFn: () => fetchEvents(filters),
    });
    
    // ✅ Good: Include dependencies
    const { data } = useQuery({
      queryKey: ['events', filters],
      queryFn: () => fetchEvents(filters),
    });
    

    Pitfall 3: Not Invalidating After Mutations

    // ❌ Bad: Cache not updated
    const createEvent = useMutation({
      mutationFn: (event) => supabase.from('events').insert(event),
    });
    
    // ✅ Good: Invalidate cache
    const createEvent = useMutation({
      mutationFn: (event) => supabase.from('events').insert(event),
      onSuccess: () => {
        queryClient.invalidateQueries({ queryKey: ['events'] });
      },
    });
    

    Pitfall 4: Forgetting to Unsubscribe

    // ❌ Bad: Memory leak
    useEffect(() => {
      const channel = supabase.channel('events');
      channel.subscribe();
      // Missing cleanup!
    }, []);
    
    // ✅ Good: Cleanup subscription
    useEffect(() => {
      const channel = supabase.channel('events');
      channel.subscribe();
    
      return () => {
        supabase.removeChannel(channel);
      };
    }, []);
    

    13. Debugging Tips

    Enable Query Devtools

    import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
    
    function App() {
      return (
        <QueryClientProvider client={queryClient}>
          <RouterProvider router={router} />
          <ReactQueryDevtools initialIsOpen={false} />
        </QueryClientProvider>
      );
    }
    

    Log Supabase Queries

    // Add to client.ts for debugging
    export const supabase = createClient<Database>(url, key, {
      auth: { /* ... */ },
      global: {
        headers: {
          'x-my-custom-header': 'debug-mode'
        }
      }
    });
    
    // Log all queries
    const originalFrom = supabase.from.bind(supabase);
    supabase.from = (table: string) => {
      console.log(`[Supabase] Querying table: ${table}`);
      return originalFrom(table);
    };
    

    Check Network Tab

    • Open DevTools → Network tab
    • Filter by "supabase.co"
    • Check request/response for errors
    • Verify RLS headers (x-client-info)

    Resources

    See /resources folder for:

    • query-patterns.ts - Complete TanStack Query examples
    • auth-patterns.tsx - Auth provider templates
    • rls-examples.sql - Common RLS policies
    • testing-setup.ts - Test configuration
    • migration-guide.md - Upgrading from Auth Helpers

    Quick Reference Commands

    # Generate types
    npx supabase gen types typescript --project-id <id> > src/integrations/supabase/types.ts
    
    # Start local Supabase
    npx supabase start
    
    # Create migration
    npx supabase migration new <name>
    
    # Apply migrations
    npx supabase db push
    
    # Reset database
    npx supabase db reset
    

    Last Updated: 2025-10-19 Maintained By: EventOS Team Questions? Check resources/ folder for detailed examples

    Recommended Servers
    MantleKit Launch Planner
    MantleKit Launch Planner
    ThinAir Data
    ThinAir Data
    Supabase
    Supabase
    Repository
    neversight/skills_feed