Full guided Supabase integration for your project. Specify framework as argument.
Guides complete Supabase integration with authentication, database setup, and nanoid identifiers. Use when adding Supabase to Next.js, Kotlin, iOS, or Flutter projects for a production-ready foundation.
/plugin marketplace add azlekov/my-claude-code/plugin install azlekov-personal@azlekov/my-claude-codeAdd Supabase to this $ARGUMENTS project with full authentication, database setup, and nanoid identifiers.
If no argument provided, detect the framework:
package.json with next → Next.jsbuild.gradle.kts with supabase-kt or Kotlin files → KotlinPackage.swift or .xcodeproj → iOSpubspec.yaml with Flutter → FlutterCheck if Supabase project exists:
# Check for existing config
ls supabase/config.toml 2>/dev/null || echo "No Supabase project found"
If no project exists:
# Initialize Supabase
npx supabase init
# Link to existing project (if user has one)
npx supabase link --project-ref <project-ref>
Create migration for nanoid:
npx supabase migration new add_nanoid_function
Add to migration file:
-- Enable pgcrypto
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- Helper function
CREATE OR REPLACE FUNCTION nanoid_optimized(
size int, alphabet text, mask int, step int
) RETURNS text LANGUAGE plpgsql VOLATILE PARALLEL SAFE AS $$
DECLARE
idBuilder text := '';
counter int := 0;
bytes bytea;
alphabetIndex int;
alphabetArray text[];
alphabetLength int := length(alphabet);
BEGIN
alphabetArray := regexp_split_to_array(alphabet, '');
LOOP
bytes := gen_random_bytes(step);
FOR counter IN 0..step - 1 LOOP
alphabetIndex := (get_byte(bytes, counter) & mask) + 1;
IF alphabetIndex <= alphabetLength THEN
idBuilder := idBuilder || alphabetArray[alphabetIndex];
IF length(idBuilder) = size THEN RETURN idBuilder; END IF;
END IF;
END LOOP;
END LOOP;
END $$;
-- Main nanoid function
CREATE OR REPLACE FUNCTION nanoid(
prefix text DEFAULT '',
size int DEFAULT 21,
alphabet text DEFAULT '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
additionalBytesFactor float DEFAULT 1.02
) RETURNS text LANGUAGE plpgsql VOLATILE PARALLEL SAFE AS $$
DECLARE
alphabetLength int := length(alphabet);
mask int := (2 << cast(floor(log(alphabetLength - 1) / log(2)) as int)) - 1;
step int := cast(ceil(additionalBytesFactor * mask * (size - length(prefix)) / alphabetLength) as int);
BEGIN
RETURN prefix || nanoid_optimized(size - length(prefix), alphabet, mask, step);
END $$;
npx supabase migration new create_profiles_table
-- Profiles table with nanoid
CREATE TABLE public.profiles (
id TEXT NOT NULL DEFAULT nanoid('usr_') PRIMARY KEY,
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
display_name TEXT,
avatar_url TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT profiles_id_format CHECK (id ~ '^usr_[0-9a-zA-Z]{17}$'),
CONSTRAINT profiles_user_id_unique UNIQUE (user_id)
);
-- Enable RLS
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
-- RLS Policies
CREATE POLICY "Users can view own profile"
ON public.profiles FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Users can update own profile"
ON public.profiles FOR UPDATE
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
-- Auto-create profile on signup
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS trigger AS $$
BEGIN
INSERT INTO public.profiles (user_id, display_name)
VALUES (new.id, new.raw_user_meta_data->>'full_name');
RETURN new;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
-- Index for user lookups
CREATE INDEX profiles_user_id_idx ON public.profiles(user_id);
npm install @supabase/supabase-js @supabase/ssr
Create .env.local:
NEXT_PUBLIC_SUPABASE_URL=your-project-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
lib/supabase/client.ts (Browser):
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
}
lib/supabase/server.ts (Server):
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export async function createClient() {
const cookieStore = await cookies()
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll()
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
} catch {
// Called from Server Component
}
},
},
}
)
}
lib/supabase/middleware.ts:
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function updateSession(request: NextRequest) {
let supabaseResponse = NextResponse.next({ request })
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll()
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value }) =>
request.cookies.set(name, value)
)
supabaseResponse = NextResponse.next({ request })
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
)
},
},
}
)
await supabase.auth.getUser()
return supabaseResponse
}
middleware.ts:
import { type NextRequest } from 'next/server'
import { updateSession } from '@/lib/supabase/middleware'
export async function middleware(request: NextRequest) {
return await updateSession(request)
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
],
}
npx supabase gen types typescript --local > src/types/database.ts
build.gradle.kts:
dependencies {
implementation(platform("io.github.jan-tennert.supabase:bom:3.1.1"))
implementation("io.github.jan-tennert.supabase:postgrest-kt")
implementation("io.github.jan-tennert.supabase:auth-kt")
implementation("io.github.jan-tennert.supabase:storage-kt")
implementation("io.ktor:ktor-client-cio:3.1.1")
}
import io.github.jan.supabase.createSupabaseClient
import io.github.jan.supabase.auth.Auth
import io.github.jan.supabase.postgrest.Postgrest
import io.github.jan.supabase.storage.Storage
object SupabaseClient {
val client = createSupabaseClient(
supabaseUrl = BuildConfig.SUPABASE_URL,
supabaseKey = BuildConfig.SUPABASE_ANON_KEY
) {
install(Auth)
install(Postgrest)
install(Storage)
}
}
class ProfileRepository(private val supabase: SupabaseClient) {
suspend fun getProfile(userId: String): Profile? {
return supabase.client.postgrest
.from("profiles")
.select()
.eq("user_id", userId)
.decodeSingleOrNull<Profile>()
}
}
In Xcode: File → Add Package Dependencies
URL: https://github.com/supabase/supabase-swift
import Supabase
let supabase = SupabaseClient(
supabaseURL: URL(string: "YOUR_SUPABASE_URL")!,
supabaseKey: "YOUR_SUPABASE_ANON_KEY"
)
@MainActor
class AuthManager: ObservableObject {
@Published var session: Session?
init() {
Task {
for await state in supabase.auth.authStateChanges {
self.session = state.session
}
}
}
}
pubspec.yaml:
dependencies:
supabase_flutter: ^2.8.3
import 'package:supabase_flutter/supabase_flutter.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(
url: 'YOUR_SUPABASE_URL',
anonKey: 'YOUR_SUPABASE_ANON_KEY',
);
runApp(const MyApp());
}
final supabase = Supabase.instance.client;
Android (android/app/src/main/AndroidManifest.xml):
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="your.app.scheme" android:host="login-callback" />
</intent-filter>
iOS (ios/Runner/Info.plist):
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>your.app.scheme</string>
</array>
</dict>
</array>
After setup, verify:
npx supabase db push to apply migrationsSELECT nanoid('test_'); in SQL Editor