npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dasheck0/supabase-saas-domain

v1.0.4

Published

Opinionated TypeScript framework for building multi-tenant SaaS applications with Supabase. Includes database setup, React Query integration, Zustand state management, and complete tenant isolation.

Downloads

141

Readme

@dasheck0/supabase-saas-domain

An opinionated TypeScript framework for building multi-tenant SaaS applications with Supabase. This package provides a complete domain layer with authentication, tenant management, profile handling, storage, and row-level security (RLS) patterns.

🌟 Philosophy

This is an opinionated framework that makes specific architectural decisions for you:

  • Multi-tenant by design: Every data operation is tenant-scoped
  • RLS-first security: Database-level security with proper tenant isolation
  • React Query integration: Optimistic updates, caching, and synchronization
  • Zustand state management: Minimal, persistent client state
  • TypeScript-native: Full type safety across the stack

🚀 Quick Start

Installation

Requirements:

  • Node.js >=20.0.0
  • npm >=10.0.0
npm install @dasheck0/supabase-saas-domain @tanstack/react-query zustand

Database Setup

The framework requires a specific database schema with multi-tenant support, RLS policies, and storage configuration. You have several options to set this up:

Option 1: CLI Tool (Recommended)

# Install the package
npm install @dasheck0/supabase-saas-domain

# Run the setup with your Supabase credentials
npx supabase-saas-setup run --url YOUR_SUPABASE_URL --key YOUR_SERVICE_ROLE_KEY --verbose

# Or use environment variables
export SUPABASE_URL=your_supabase_url
export SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
npx supabase-saas-setup quick

Option 2: Programmatic Setup

const { setupSaasDatabase } = require('@dasheck0/supabase-saas-domain');
const { createClient } = require('@supabase/supabase-js');

const supabaseClient = createClient(
  'your-supabase-url',
  'your-service-role-key',
  { auth: { autoRefreshToken: false, persistSession: false } }
);

const result = await setupSaasDatabase({
  supabaseClient,
  verbose: true
});

if (result.success) {
  console.log('✅ Database setup complete!');
} else {
  console.error('❌ Setup failed:', result.errors);
}

Option 3: Manual Migration

  1. Copy all migration files from /supabase/migrations/ in the package
  2. Apply them to your Supabase project using the Supabase CLI or Dashboard
  3. Create a storage bucket named 'uploads' with public access

What Gets Created

  • Tables: profiles, tenants, tenant_memberships with proper relationships
  • RLS Policies: Tenant-scoped security for all data operations
  • Storage Bucket: 'uploads' bucket with RLS policies for file management
  • Database Functions: Utility functions for tenant management and SQL execution
  • Triggers: Automatic profile and tenant creation on user signup
  • Migration Tracking: schema_migrations table to prevent duplicate setups

Note: The setup tool uses embedded SQL migrations (not file-based) making the package completely self-contained. This means you don't need to manually copy migration files - everything is bundled with the npm package.

Basic Setup

import { SaasProvider } from '@dasheck0/supabase-saas-domain';
import { createClient } from '@supabase/supabase-js';

const supabaseClient = createClient(
  'your-supabase-url',
  'your-supabase-anon-key'
);

function App() {
  return (
    <SaasProvider supabaseClient={supabaseClient}>
      <YourApp />
    </SaasProvider>
  );
}

Authentication Sync

The framework requires you to sync authentication state:

import { useSaasUtils } from '@dasheck0/supabase-saas-domain';

function AuthSync({ user }: { user: any }) {
  const { setAuthentication, getUserId } = useSaasUtils();

  useEffect(() => {
    const currentUserId = getUserId();
    const newUserId = user?.id || null;
    
    if (currentUserId !== newUserId) {
      if (newUserId) {
        setAuthentication(newUserId, null);
      } else {
        setAuthentication(null, null);
      }
    }
  }, [user?.id, setAuthentication, getUserId]);

  return null;
}

🏗 Core Concepts

1. Multi-Tenant Architecture

The framework enforces a tenant-first approach:

import { useSaasTenantHooks, useTenantSwitcher } from '@dasheck0/supabase-saas-domain';

function TenantManager() {
  const { useTenants, useCreateTenant } = useSaasTenantHooks();
  const { currentTenant, switchTenant } = useTenantSwitcher();
  
  const { data: tenants } = useTenants();
  const createTenant = useCreateTenant();

  const handleCreateTenant = async () => {
    const newTenant = await createTenant.mutateAsync({
      name: 'My Company',
      description: 'Company workspace'
    });
    switchTenant(newTenant);
  };

  return (
    <div>
      <h2>Current: {currentTenant?.name}</h2>
      {tenants?.map(tenant => (
        <button key={tenant.id} onClick={() => switchTenant(tenant)}>
          {tenant.name}
        </button>
      ))}
    </div>
  );
}

2. Profile Management

User profiles with tenant-scoped visibility:

import { useSaasProfileHooks } from '@dasheck0/supabase-saas-domain';

function ProfileManager() {
  const { useProfiles, useCreateProfile, useUpdateProfile } = useSaasProfileHooks();
  
  const { data: profiles } = useProfiles(); // Only profiles visible to current user
  const createProfile = useCreateProfile();
  const updateProfile = useUpdateProfile();

  return (
    <div>
      {profiles?.map(profile => (
        <div key={profile.id}>
          {profile.firstName} {profile.lastName}
          <img src={profile.imageUrl} alt="Avatar" />
        </div>
      ))}
    </div>
  );
}

3. Storage with Tenant Isolation

Built-in file storage with automatic tenant scoping:

import { useSaasStorageHooks, ImageUpload } from '@dasheck0/supabase-saas-domain';

function AvatarUpload({ userId }: { userId: string }) {
  const { useUploadAvatar } = useSaasStorageHooks();
  const { currentTenant } = useTenantSwitcher();
  const uploadAvatar = useUploadAvatar();

  const handleUpload = async (file: File) => {
    if (currentTenant) {
      const result = await uploadAvatar.mutateAsync({
        userId,
        tenantId: currentTenant.id,
        file
      });
      console.log('Uploaded:', result.signedUrl);
    }
  };

  return (
    <ImageUpload
      onImageSelected={handleUpload}
      placeholder="Upload avatar"
    />
  );
}

4. Membership Management

Handle tenant memberships with role-based access:

import { useSaasMembershipHooks } from '@dasheck0/supabase-saas-domain';

function MembershipManager({ tenantId }: { tenantId: string }) {
  const { useTenantMemberships, useCreateTenantMembership } = useSaasMembershipHooks();
  
  const { data: memberships } = useTenantMemberships(tenantId, true); // eager load profiles
  const createMembership = useCreateTenantMembership();

  const inviteUser = async (email: string) => {
    await createMembership.mutateAsync({
      tenantId,
      userId: email, // In real app, resolve email to userId
      role: 'member'
    });
  };

  return (
    <div>
      <h3>Team Members</h3>
      {memberships?.map(membership => (
        <div key={membership.id}>
          {membership.profile?.firstName} - {membership.role}
        </div>
      ))}
    </div>
  );
}

🛠 Core Hooks API

Tenant Hooks

  • useTenants() - List accessible tenants
  • useCreateTenant() - Create new tenant
  • useUpdateTenant() - Update tenant details
  • useDeleteTenant() - Delete tenant
  • useTenantSwitcher() - Switch between tenants

Profile Hooks

  • useProfiles() - List visible profiles
  • useCreateProfile() - Create user profile
  • useUpdateProfile() - Update profile
  • useDeleteProfile() - Delete profile

Membership Hooks

  • useTenantMemberships(tenantId) - List tenant members
  • useUserMemberships(userId) - List user's memberships
  • useCreateTenantMembership() - Add member to tenant
  • useUpdateTenantMembership() - Update member role
  • useDeleteTenantMembership() - Remove member

Storage Hooks

  • useUploadAvatar() - Upload profile pictures
  • useUploadTenantImage() - Upload tenant logos
  • useSignedUrl() - Get signed URLs for files
  • useDeleteFile() - Delete stored files

Utility Hooks

  • useAuthState() - Current authentication state
  • useSaasUtils() - Framework utilities

🔒 Security Model

Row-Level Security (RLS)

The framework enforces security at the database level:

-- Users can only see profiles of people in their shared tenants
CREATE POLICY "profiles_shared_tenants" ON public.profiles
  FOR SELECT USING (
    EXISTS (
      SELECT 1 FROM public.tenant_memberships tm1
      JOIN public.tenant_memberships tm2 ON tm1.tenant_id = tm2.tenant_id
      WHERE tm1.user_id = auth.uid()
      AND tm2.user_id = profiles.user_id
    )
  );

Tenant Isolation

All data operations are automatically scoped to accessible tenants:

  • Storage: Files uploaded to {tenantId}/category/{userId}/filename
  • Queries: Filtered by tenant membership
  • Mutations: Validated against tenant permissions

📁 Database Schema

The framework expects these core tables:

-- Core Tables
profiles (id, user_id, first_name, last_name, image_url, ...)
tenants (id, name, description, image_url, personal, ...)  
tenant_memberships (id, tenant_id, user_id, role, ...)

-- Storage Bucket
uploads (tenant-scoped file storage)

🎨 UI Components

ImageUpload Component

import { ImageUpload } from '@dasheck0/supabase-saas-domain';

<ImageUpload
  currentImageUrl={profile.imageUrl}
  onImageSelected={(file) => handleUpload(file)}
  onImageCleared={() => setImageUrl('')}
  placeholder="Upload profile picture"
/>

Built-in Components

  • ImageUpload - Drag & drop file upload with preview
  • SaasProvider - Framework provider component

⚙️ Configuration

Required Props

interface SaasProviderProps {
  supabaseClient: SupabaseClient; // Your Supabase client instance
  children: React.ReactNode;
}

Environment Setup

  1. Supabase Project: Set up with authentication enabled
  2. Database Migrations: Run provided migrations for schema
  3. RLS Policies: Apply security policies for tenant isolation
  4. Storage Bucket: Create 'uploads' bucket for file storage
  5. Authentication: Configure Supabase Auth providers

� Troubleshooting

Setup Issues

"Cannot find module 'commander'" during CLI setup

# Install dependencies if using the CLI directly
npm install -g @dasheck0/supabase-saas-domain

"SQL execution failed" during migration

  • Ensure you're using the service role key (not anon key)
  • Check that your Supabase project has the necessary permissions
  • Verify your Supabase URL is correct

"Multiple GoTrueClient instances detected"

  • The framework uses peerDependencies to avoid this
  • Ensure you're passing your app's Supabase client to SaasProvider
  • Don't create multiple Supabase clients in your app

"userId is null" in hooks

  • Make sure you're using the AuthSync component
  • Verify that authentication state is being set properly
  • Check that your user is actually authenticated

RLS Policy Issues

"new row violates row-level security policy"

  • Ensure the user has a profile record (auto-created on signup)
  • Verify the user is a member of the tenant they're trying to access
  • Check that tenant memberships are properly configured

Storage upload failures

  • Confirm the 'uploads' bucket exists and has proper RLS policies
  • Verify the user is authenticated and has tenant membership
  • Check file size limits and allowed file types

Performance Tips

  • Use the enabled option in hooks to prevent unnecessary queries
  • Implement proper loading states for better UX
  • Consider pagination for large datasets
  • Use optimistic updates for better perceived performance

�🚀 Development

Package Development

npm install
npm run build        # Build TypeScript
npm run dev          # Watch mode
npm run test         # Run tests

Example App

cd examples/react-admin
npm install
npm run dev          # Start example app

📦 Package Structure

src/
├── api/              # Data layer (Supabase queries)
├── components/       # React components  
├── hooks/            # React Query hooks
├── models/           # TypeScript models
├── stores/           # Zustand stores
├── types/            # Type definitions
└── index.ts          # Main exports

examples/
└── react-admin/      # Full example application

supabase/
└── migrations/       # Database migrations

🤝 Contributing

This is an opinionated framework with specific architectural decisions. Contributions should align with:

  • Multi-tenant architecture patterns
  • RLS-first security model
  • React Query + Zustand state management
  • TypeScript-first development

📄 License

MIT © dasheck0


Note: This is an opinionated framework that makes architectural decisions for you. It's designed for teams building multi-tenant SaaS applications who want a complete, secure foundation without reinventing tenant isolation, security patterns, and data management.