A production-ready registration system using Supabase (PostgreSQL + Auth) with vanilla HTML/CSS/JavaScript.
- Email OTP verification (Supabase Auth)
- Full name validation (English letters only, 3-50 characters)
- Duplicate prevention (case-insensitive)
- Row Level Security (RLS) enabled
- Admin CSV export
- Mobile responsive
FormForm/
├── public/
│ ├── index.html # Main registration page
│ ├── style.css # Styles
│ ├── app.js # Frontend logic
│ └── admin.html # Admin export page
├── supabase/
│ └── migrations/
│ └── 20260217_create_registrations.sql
└── README.md
# Using npm
npm install -g supabase
# Or using Scoop (Windows)
scoop bucket add supabase https://github.com/supabase/scoop-bucket.git
scoop install supabase- Go to supabase.com
- Create a new account (free tier available)
- Create a new project
- Wait for database provisioning (~2 minutes)
cd d:\mounir\FormForm
# Initialize Supabase (if not already done)
supabase init
# Login to Supabase
supabase login
# Link to your project (get project ref from dashboard URL)
# Dashboard URL: https://supabase.com/dashboard/project/YOUR_PROJECT_REF
supabase link --project-ref YOUR_PROJECT_REF# Push migrations to your Supabase database
supabase db pushThis creates:
registrationstable- Unique indexes for email and full_name (case-insensitive)
- RLS policies for authenticated users
Get your API keys from Supabase Dashboard: Settings > API
Edit public/app.js and public/admin.html:
const SUPABASE_URL = 'https://YOUR_PROJECT_REF.supabase.co';
const SUPABASE_ANON_KEY = 'eyJhbG...'; // anon/public keyGo to Authentication > Email Templates in Supabase Dashboard.
Customize the OTP email template:
<h2>Your Verification Code</h2>
<p>Enter this code to complete your registration:</p>
<h1>{{ .Token }}</h1>
<p>This code expires in 1 hour.</p>- Install "Live Server" extension in VS Code
- Right-click
public/index.html - Select "Open with Live Server"
cd public
npx servecd public
python -m http.server 5500Then open: http://localhost:5500
- Open
http://localhost:5500/admin.html - Get your Service Role Key from Supabase Dashboard: Settings > API > service_role (secret)
- Enter the key and click "Connect"
- Click "Export CSV" to download all registrations
CREATE TABLE registrations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
full_name TEXT NOT NULL,
email TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT now()
);
-- Case-insensitive unique constraints
CREATE UNIQUE INDEX idx_registrations_email_lower ON registrations (lower(email));
CREATE UNIQUE INDEX idx_registrations_full_name_lower ON registrations (lower(full_name));| Policy | Action | Who | Condition |
|---|---|---|---|
| Allow authenticated users to insert | INSERT | authenticated | Email matches JWT |
| Allow authenticated users to read own | SELECT | authenticated | Email matches JWT |
| Field | Rule |
|---|---|
| Valid email format, verified via OTP | |
| Full Name | ^[A-Za-z ]{3,50}$ - English letters only |
The app handles:
- Duplicate email registration
- Duplicate name registration
- Invalid OTP codes
- Expired OTP codes
- Rate limiting
- Session expiration
- Expected users: ~200 max
- Supabase free tier is sufficient
- No backend server required
- Check spam folder
- Verify SMTP settings in Supabase Dashboard
- Check rate limits (Supabase limits OTP emails)
- Email or name already exists (case-insensitive)
- User will see a clear error message
- Ensure user is authenticated
- Check that email in request matches session email
- RLS enabled on registrations table
- Only authenticated users can insert
- Users can only insert their own email
- Unique constraints prevent duplicates
- Email verified via OTP before form access
- Service role key used only for admin export
- Anon key safe to expose (limited by RLS)
MIT