VillageServer operations
Everything the initiative needs in one place.
Manage public content, see where interest is coming from, review kit requests, and keep the next field deployment moving.
Quick actions
Jump directly to the work you want to do.
Launch readiness
The core administrative pieces now have a home.
Campaigns
New campaign
Blog & Updates
New post
Photos
Add photo by URL
Affiliates
New affiliate
Traffic & Ad Sources
Combined live stream · villageserver.com + villageserver.org
Visits by Source
Visits by Medium
Ad Campaigns
Recent Visits (last 200)
Donation Interests
Interests by Country
Interests by Initiative
All Submissions
Kit Availability Requests
Requests by Country
Requests by Ad Source
All Requests
Admin knowledge base · Traffic attribution
How VillageServer knows where a visit came from
A plain-language guide to the data path behind Traffic & Sources—what enters the system, where it goes, and what the dashboard can honestly conclude.
A link is clicked
An ad, email, social post, search result, or another website sends someone to VillageServer.
Context arrives
The URL may contain UTM tags or a click ID. The browser may also provide a referrer URL.
Our endpoint receives it
The page sends those fields and its destination path to /api/track?type=visit.
Supabase stores a row
The admin dashboard groups rows by source, medium, campaign, destination, and date.
What are “source” and “destination”?
Source means the channel that led someone here—for example Facebook, TikTok, Google, email, or a referring website. Destination means the VillageServer page they opened, such as / or /donate.
They are marketing attribution labels, not packet-level network addresses.
How does it identify an ad campaign?
The strongest signal is a tagged link created for the campaign:
villageserver.org/?utm_source=facebook&utm_medium=paid-social&utm_campaign=kenya-pilotThe dashboard reads utm_source, utm_medium, and utm_campaign exactly as they appear in that URL.
What are fbclid and ttclid?
Facebook and TikTok may append a unique click identifier when someone follows an ad. If a visit has no explicit UTM source, the dashboard uses fbclid as a Facebook clue and ttclid as a TikTok clue.
A click ID indicates the platform path; it does not expose the person's social account or packet contents.
What does the referrer tell us?
document.referrer can identify the previous webpage when the browser and that website permit it. Privacy settings, apps, HTTPS rules, and redirects may remove or shorten it.
A blank referrer is labeled direct/unknown; it does not prove that the visitor typed the address manually.
What exactly is saved for a page visit?
| Field | Meaning | Example |
|---|---|---|
site_host | Public VillageServer domain; both domains feed one stream | villageserver.org |
path | VillageServer destination page | /donate |
referrer | Previous page when supplied | facebook.com/… |
utm_source | Platform or publisher | facebook |
utm_medium | Traffic type | paid-social |
utm_campaign | Campaign label | kenya-pilot |
fbclid / ttclid | Platform click clue | Opaque identifier |
created_at | Time Supabase created the row | UTC timestamp |
What can and can’t the tracker see?
- Tagged campaign values
- VillageServer page path
- Allowed referrer value
- Submission fields a person enters
- Network packet payloads
- Browsing history
- Social passwords or accounts
- Files on the visitor's device
Why does traffic appear as Direct / Unknown?
- The link had no UTM tags.
- The browser or app removed the referrer.
- A redirect stripped query parameters.
- The visitor opened a bookmark or copied link.
- An ad platform did not append its click ID.
Use consistent tagged campaign links and test the final landing URL before publishing an ad.
What conclusions are safe to make?
The dashboard can say, “This many tracked visits arrived with the source label Facebook,” or “This form was submitted after a Kenya campaign link.” It cannot prove that every untagged visitor was direct, that one person equals one visit, or that a marketing channel caused a donation.
Treat these reports as directional attribution. Compare them with campaign-platform reports and confirmed donations before making budget decisions.
Setup guide
⚠ Connect Supabase to enable content management
- Create a free project at supabase.com
- Open the complete schema file, copy it, and run it once in the Supabase SQL editor
- Go to Project Settings → API and copy your Project URL and service_role key
- In Vercel → your project → Settings → Environment Variables, add:
SUPABASE_URL= your project URLSUPABASE_SERVICE_KEY= your service role keyADMIN_PASSWORD= a strong password for this admin panel
- Redeploy the site once env vars are set
Complete Supabase schema
The canonical schema is idempotent, enables row-level security, seeds the current affiliates, and includes every content, analytics, donation-interest, and kit-request table.
Open copy-paste schema ↗create table campaigns (
id uuid default gen_random_uuid() primary key,
slug text unique not null,
name text not null,
description text,
story text,
goal_amount numeric default 0,
raised_amount numeric default 0,
bibles_funded integer default 0,
bibles_needed integer default 0,
end_date date,
zeffy_url text,
image_url text,
active boolean default false,
created_at timestamptz default now()
);
create table posts (
id uuid default gen_random_uuid() primary key,
title text not null,
body text not null,
image_url text,
author text default 'VillageServer Initiative',
published boolean default false,
published_at timestamptz default now(),
created_at timestamptz default now()
);
create table photos (
id uuid default gen_random_uuid() primary key,
url text not null,
caption text,
alt text,
category text default 'field',
created_at timestamptz default now()
);
create table affiliates (
id uuid default gen_random_uuid() primary key,
name text not null,
website_url text,
description text,
logo_url text,
active boolean default true,
sort_order integer default 0,
created_at timestamptz default now()
);
create table page_visits (
id uuid default gen_random_uuid() primary key,
site_host text,
path text,
referrer text,
utm_source text,
utm_medium text,
utm_campaign text,
utm_content text,
utm_term text,
fbclid text,
ttclid text,
created_at timestamptz default now()
);
create table donation_interests (
id uuid default gen_random_uuid() primary key,
name text,
email text,
country text not null,
initiative text,
practical_need text,
utm_source text,
utm_medium text,
utm_campaign text,
created_at timestamptz default now()
);
create table availability_requests (
id uuid default gen_random_uuid() primary key,
country text not null,
region text,
name text,
email text,
organization text,
message text,
requested_items jsonb not null default '[]'::jsonb,
utm_source text,
utm_medium text,
utm_campaign text,
created_at timestamptz default now()
);
-- Allow public reads
alter table campaigns enable row level security;
alter table posts enable row level security;
alter table photos enable row level security;
alter table affiliates enable row level security;
alter table page_visits enable row level security;
alter table page_visits add column if not exists site_host text;
alter table donation_interests enable row level security;
alter table availability_requests enable row level security;
alter table availability_requests add column if not exists requested_items jsonb not null default '[]'::jsonb;
create policy "Public reads" on campaigns for select using (true);
create policy "Public reads published" on posts for select using (published = true);
create policy "Public reads photos" on photos for select using (true);
create policy "Public reads active affiliates" on affiliates for select using (active = true);
-- page_visits: public insert (tracking), admin select via service key
create policy "Public insert visits" on page_visits for insert with check (true);
-- donation_interests: public insert, admin select via service key
create policy "Public insert interests" on donation_interests for insert with check (true);
-- availability_requests: public insert, admin select via service key
create policy "Public insert availability" on availability_requests for insert with check (true);