Local preview mode — sample VillageServer data is shown so every dashboard view can be reviewed without Supabase.Changes reset when this page reloads.

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.

Public contentcampaigns, posts, photos, affiliates
Tracked visitslatest analytics window
Kit requestscommunities asking for access
Support interestlocation and ministry preferences

Quick actions

Jump directly to the work you want to do.

Launch readiness

The core administrative pieces now have a home.

01
Supabase schema readyContent, affiliates, analytics, and requests
02
Field guides publishedRaspberry Pi, storage, power, and satellite
03
Pamphlet library livePrintable device-transfer instructions

New campaign

No campaigns yet. Create one above.

New post

No posts yet. Write a field update above.

Add photo by URL

No photos yet.

New affiliate

No affiliates added through the admin yet.
Loading traffic summary…

Visits by Source

Loading sources…

Visits by Medium

Loading media…

Ad Campaigns

Loading campaigns…

Recent Visits (last 200)

Loading recent visits…

Interests by Country

Loading locations…

Interests by Initiative

Loading preferences…

All Submissions

Loading submissions…

Requests by Country

Loading countries…

Requests by Ad Source

Loading sources…

All Requests

Loading 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.

01 · SOURCE

A link is clicked

An ad, email, social post, search result, or another website sends someone to VillageServer.

02 · BROWSER

Context arrives

The URL may contain UTM tags or a click ID. The browser may also provide a referrer URL.

03 · DESTINATION

Our endpoint receives it

The page sends those fields and its destination path to /api/track?type=visit.

04 · REPORT

Supabase stores a row

The admin dashboard groups rows by source, medium, campaign, destination, and date.

QUESTION 01Basics

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.

QUESTION 02Attribution

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-pilot

The dashboard reads utm_source, utm_medium, and utm_campaign exactly as they appear in that URL.

QUESTION 03Click IDs

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.

QUESTION 04Referrers

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.

QUESTION 05Stored fields

What exactly is saved for a page visit?

FieldMeaningExample
site_hostPublic VillageServer domain; both domains feed one streamvillageserver.org
pathVillageServer destination page/donate
referrerPrevious page when suppliedfacebook.com/…
utm_sourcePlatform or publisherfacebook
utm_mediumTraffic typepaid-social
utm_campaignCampaign labelkenya-pilot
fbclid / ttclidPlatform click clueOpaque identifier
created_atTime Supabase created the rowUTC timestamp
QUESTION 06Privacy

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
QUESTION 07Troubleshooting

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.

QUESTION 08Reading reports

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.

⚠ Connect Supabase to enable content management

  1. Create a free project at supabase.com
  2. Open the complete schema file, copy it, and run it once in the Supabase SQL editor
  3. Go to Project Settings → API and copy your Project URL and service_role key
  4. In Vercel → your project → Settings → Environment Variables, add:
    • SUPABASE_URL = your project URL
    • SUPABASE_SERVICE_KEY = your service role key
    • ADMIN_PASSWORD = a strong password for this admin panel
  5. 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);