A private, invite-only family tree web app you can self-host. Explore an interactive family tree, view member profiles, and manage your family's history — completely under your own control, independent of Ancestry or any subscription service.
Built with: Next.js · Supabase · Tailwind CSS · Vercel
- Interactive visual tree (desktop) + accordion directory (mobile)
- Member profiles — photos, birth/death dates, location, fun facts
- Magic-link login — no passwords required
- Admin dashboard — add people, manage relationships, invite members
- Family map — plot all members on an interactive map (OpenStreetMap, free)
- Find Connection — see how any two people are related (BFS traversal)
- GEDCOM export/import — compatible with Ancestry, FamilySearch, Gramps
- Family data collection form — shareable link for members to submit their own info
- Email notifications — contact form + login alerts via Resend
- PDF/print export — full tree or individual branches
git clone https://github.com/your-username/open-family-tree.git
cd open-family-tree
npm install- Create a free project at supabase.com
- Run the SQL in Database Setup below
- Copy your Project URL and anon key from Project Settings → API
- Create a free account at resend.com
- Copy your API key
cp .env.example .env.localFill in .env.local with your values (see Environment Variables).
Replace public/crest.svg with your own image, or upload it anywhere and set NEXT_PUBLIC_CREST_URL to that URL.
npm run devOpen http://localhost:3000.
- Push your repo to GitHub
- Import it at vercel.com/new
- Add all environment variables in Vercel → Settings → Environment Variables
- Deploy
Copy .env.example to .env.local and fill in your values:
| Variable | Example | Purpose |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL |
https://abc.supabase.co |
Supabase project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
eyJ... |
Supabase anon key |
SUPABASE_SERVICE_ROLE_KEY |
eyJ... |
Supabase service role key (server-side only) |
RESEND_API_KEY |
re_... |
Resend API key for email |
NEXT_PUBLIC_FAMILY_NAME |
Smith Family Tree |
App title shown everywhere |
NEXT_PUBLIC_FAMILY_SHORT_NAME |
Smith |
Short name for descriptions |
NEXT_PUBLIC_ROOT_PRIORITY_NAMES |
John Smith,Jane Smith |
Full names that appear first in the tree (comma-separated) |
NEXT_PUBLIC_ROOT_FAMILY_NAME |
Smith |
Primary family surname — shown before other branches |
NEXT_PUBLIC_CREST_URL |
/crest.svg |
Path or URL to your family crest/logo |
ADMIN_EMAIL |
admin@example.com |
Where contact form and login notifications go |
Run these in the Supabase SQL Editor (Database → SQL Editor):
CREATE TABLE people (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
first_name text NOT NULL,
last_name text NOT NULL,
date_of_birth text,
dob_precision text,
date_of_death text,
dod_precision text,
is_deceased boolean NOT NULL DEFAULT false,
address text,
lat double precision,
lng double precision,
fun_fact text,
photo_url text,
gender text,
birth_order integer,
created_by text,
created_at timestamptz DEFAULT now()
);
CREATE TABLE relationships (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
person_id uuid REFERENCES people(id) ON DELETE CASCADE,
related_person_id uuid REFERENCES people(id) ON DELETE CASCADE,
relationship_type text NOT NULL
);
CREATE TABLE user_profiles (
id uuid PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
email text,
role text NOT NULL DEFAULT 'member',
person_id uuid REFERENCES people(id),
created_at timestamptz DEFAULT now()
);
CREATE TABLE app_settings (
key text PRIMARY KEY,
value text NOT NULL
);
CREATE TABLE collection_links (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
token text UNIQUE NOT NULL,
active boolean NOT NULL DEFAULT true,
created_at timestamptz DEFAULT now()
);
CREATE TABLE family_collections (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
token text NOT NULL,
submitted_by_name text,
status text NOT NULL DEFAULT 'pending',
data jsonb,
created_at timestamptz DEFAULT now()
);ALTER TABLE people ENABLE ROW LEVEL SECURITY;
ALTER TABLE relationships ENABLE ROW LEVEL SECURITY;
ALTER TABLE user_profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE app_settings ENABLE ROW LEVEL SECURITY;
CREATE POLICY "auth_read_people" ON people FOR SELECT TO authenticated USING (true);
CREATE POLICY "auth_read_relationships" ON relationships FOR SELECT TO authenticated USING (true);
CREATE POLICY "auth_read_profiles" ON user_profiles FOR SELECT TO authenticated USING (true);
CREATE POLICY "auth_insert_own_profile" ON user_profiles FOR INSERT TO authenticated WITH CHECK (auth.uid() = id);
CREATE POLICY "auth_update_own_profile" ON user_profiles FOR UPDATE TO authenticated USING (auth.uid() = id);
CREATE POLICY "admin_update_any_profile" ON user_profiles FOR UPDATE TO authenticated
USING ((SELECT role FROM user_profiles WHERE id = auth.uid()) = 'admin');
CREATE POLICY "auth_read_settings" ON app_settings FOR SELECT TO authenticated USING (true);
CREATE POLICY "admin_write_settings" ON app_settings FOR ALL TO authenticated
USING ((SELECT role FROM user_profiles WHERE id = auth.uid()) = 'admin')
WITH CHECK ((SELECT role FROM user_profiles WHERE id = auth.uid()) = 'admin');INSERT INTO storage.buckets (id, name, public) VALUES ('photos', 'photos', true);
CREATE POLICY "auth_upload_photos" ON storage.objects FOR INSERT TO authenticated
WITH CHECK (bucket_id = 'photos');
CREATE POLICY "public_read_photos" ON storage.objects FOR SELECT TO public
USING (bucket_id = 'photos');- Open your deployed app and enter your email on the login page
- Click the magic link in your email
- You are automatically made admin — the first person to sign in gets admin role
- Go to Admin → Members to invite family members by email
| Layer | Technology |
|---|---|
| Framework | Next.js (App Router) + TypeScript |
| Database | Supabase (PostgreSQL) |
| Auth | Supabase Auth (magic links) |
| Storage | Supabase Storage (photos) |
| Resend API | |
| Maps | Leaflet 1.9 + OpenStreetMap (free, no API key needed) |
| Geocoding | Nominatim / OpenStreetMap (free, no API key needed) |
| Styling | Tailwind CSS |
| Hosting | Vercel (free tier sufficient for a family) |
Total infrastructure cost: $0 on free tiers for a family-scale deployment.
MIT