League BuilderBETA

Water Polo League Builder — Full Guide

Run an entire water polo season — round-robin, single-elim, Swiss, weekly pods, multi-club. From the setup wizard to publishing your /leagues/ SEO page, everything in one place.

1

Getting Started

What is League Builder?

League Builder is the season-management module of Eggbeater. It takes a tournament platform — the live spectator pages, the volunteer scorer, the bracket sheet sync, the host cockpit — and stretches it across a multi-week season.

One weekly preset auto-schedules every game. Live standings update as scores finalize. Spectator notifications announce Week N · vs Opponent, not just "next game." When the season ends, archive it and mint a fresh code for next year.

🧪

League Builder is in private beta. The first beta seasons launch with the Hydres clubs (Laval, Big Splash). The Tournament platform underneath is production-validated; the season-specific surfaces (Leagues tab, Sheet write-back, /leagues/ SEO pages) are gathering real-world data right now.

Subscribe to a league SKU

Pick Club League ($99/season) for an internal/rec league using only your club's teams, or Multi-Club League ($249/season) for a regional season with visiting clubs. See Which SKU? below for the decision guide.

Open the Leagues tab

Sign in at eggbeater.app/admin.html. The Leagues nav item is in the Tournament group; it shows a 👑 crown when your club is entitled, 🔒 lock otherwise.

Set up your league

Tap Set up league →. The wizard collects league name, season label, divisions, teams, format, and points policy. Auto-schedule or import from a Google Sheet — both paths are covered in detail below.

Publish

Hit Publish League. A 6-character share code goes live with a public spectator URL. Visiting clubs import via the code; parents follow via the URL. Auto-generated /leagues/{slug}/ SEO page.

2

Which SKU?

Two league SKUs, one decision

If every team in the league belongs to your club, Club League is the right pick. The moment a team from another club joins, the league becomes a multi-club event — visiting clubs' parents need the public spectator page, and standings cross club boundaries. That's Multi-Club League.

Club League · BETA

$99
per season · single club
  • Round-robin, single-elim, double-elim, Swiss, weekly pods, pools-to-bracket
  • Auto-schedule from one weekly preset
  • Live standings + configurable tiebreakers
  • Per-team .ics download, deep-linked standings
  • 3rd-place + 5th–8th placement brackets
  • Hide-standings-until-week-N rules
  • Public /leagues/{slug}/ SEO page

Multi-Club League · BETA

$249
per season · multi-club
  • Everything in Club League
  • Multi-club participating teams + cross-club standings
  • Visiting clubs' parents get the public spectator page free
  • Google Sheets import with reusable column templates
  • Score write-back to your sheet with live status panel
  • Per-division event calendars (Saturdays vs Sundays etc.)
💡

Inheritance rule: Multi-Club League also unlocks every Club League feature — you don't need both subscriptions. The admin paywall checks pass for either SKU on single-club gates; only Multi-Club League holders pass the multi-club gate.

3

Setup Wizard

Launching the wizard

On the Leagues tab, click Set up league → at the top of the page. The wizard is a single scrolling form covering five sections: League identity, Divisions & teams, Format, Schedule preset, and Points & tiebreakers. Save and close at any point — your draft is preserved locally.

League identity

Enter a league name (e.g., "Hydres Spring League"), an optional season label ("Spring 2026"), and a league ID (lowercase slug — autogenerated from the name). The league ID becomes part of the public URL: eggbeater.app/leagues/{slug}/.

Divisions & teams

Add one or more divisions. Each division has a name (e.g., "Division 1", "U16 Girls") and a list of teams. Paste team names one-per-line, or import from a sheet later.

  • Up to 16 teams per division (round-robin scales quadratically)
  • Add __BYE__ to seed an odd-team-count division — the generator handles it
  • Teams can be reordered within a division for snake-draft seeding

Format

Pick one format per division. See League Formats below for a full breakdown. The most common are round-robin (every team plays every other team once) and Swiss (teams paired round-by-round based on standings).

Schedule preset

One weekly preset auto-schedules every game. You enter:

  • First game date — Eggbeater calculates the rest
  • Day of week per division (e.g., Division 1 plays Saturdays, U16 Sundays)
  • Game slots — start times, court names, and game length
  • Min rest between same-team games (in hours) so back-to-back games don't get scheduled

Points & tiebreakers

Default points policy: W=3, T=1, L=0, with optional shootout adjustments (SO-win = 2, SO-loss = 1). Default tiebreaker chain: points → wins → head-to-head → GA → GF → goal differential (capped) → name. Drag any tiebreaker up or down to reorder; tap a checkbox to disable.

Generate & save

Tap Generate Schedule. Every game materializes into your imported schedule with weekIndex stamps, team IDs, division IDs, and round labels. Review the schedule on the Leagues tab; if it looks right, tap Publish League.

📝

Re-opening the wizard later (the Edit league button) restores everything you entered — your divisions, teams, format, schedule preset, and tiebreakers are all preserved on the league config.

4

League Formats

Pick the format that matches your season

Eggbeater supports six league formats. Each division can use a different format; common pairings include round-robin → single-elim playoff bracket (regular season → playoffs).

Round-Robin

Every team plays every other team once (or twice, double round-robin). N×(N-1)/2 games per cycle. Best for 4–10 teams.

Single-Elim

Bracket of 4 / 8 / 16. Losers out. Optional 3rd-place game + 5th–8th placement bracket. Perfect end-of-season playoff.

Double-Elim

Winners bracket + losers bracket. Champions decided in a grand final. ~2N–1 games. Forgiving for one-loss teams.

Swiss

Teams paired each round against opponents with similar records. No team is eliminated. Great for medium-large fields (16+).

Weekly Pods

Teams split into rotating pods (small round-robins) each week. Different opponents every week. Good for casual rec leagues.

Pools → Bracket

Pool play seeds a playoff bracket. Eggbeater generates the bracket from final pool standings; the Build Playoff Bracket button mints it on demand.

🌀

Swiss leagues: When the regular round is finished, click 🌀 Generate Next Round on the Leagues tab. Eggbeater pairs teams with similar records and avoids rematches when possible. Run as many rounds as you've planned in the preset.

5

Importing from a Google Sheet

When to use the import wizard

If your league schedule already exists in a Google Sheet (or Excel/CSV), skip the setup wizard's auto-scheduler and import it instead. The wizard maps your columns to Eggbeater's game fields and saves a reusable column template so re-importing next season is a one-click affair.

Open the import wizard

On the Leagues tab, tap 📥 Import from Sheet (visible in the setup card and the League Wizard card). The wizard opens as a modal over the page.

Paste the sheet URL or CSV

Either paste a Google Sheets URL (publicly viewable) or paste rows directly from your spreadsheet. Eggbeater detects the header row automatically.

Map columns

Match each detected column to an Eggbeater field:

  • Date — required (ISO format, or M/D/YYYY)
  • Time — start time (24h or 12h with AM/PM)
  • Court / Pool / Venue — location label
  • Team 1 / Team 2 — required, must match teams defined in your league
  • Division — optional, defaults to the only division if you have one
  • Week / Round — optional but recommended for reminder copy

Preview & save

The wizard shows a preview of every parsed game. Spot any team-name typos (the preview flags unknown teams in red). If a row looks wrong, fix the source sheet and re-fetch — your column mapping is preserved.

Save the column template

Tap Save mapping as template and give it a name (e.g., "Hydres Spring Format"). Next season, importing the same sheet shape applies the template with one click.

⚠️

Team name matching is strict. "Pacific Waves" and "Pacific waves" are different teams. Use the team-rename feature in the setup wizard if your sheet's names differ slightly from your roster, or fix them at the source before importing.

6

Standings & Tiebreakers

How standings are computed

Standings recompute automatically every time a game finalizes. Each team row tracks:

GPGames played (final scores only — in-progress doesn't count)
W / L / TWins, losses, ties (regulation)
SO W / SO LShootout wins/losses (when enabled in your points policy)
GF / GA / GDGoals for, goals against, goal differential
PtsCalculated from your points policy. Default: W=3, T=1, L=0; SO-win=2, SO-loss=1.

Configurable points policy

The points policy lives in your league config. Edit the wizard to change:

  • Regulation — Win, Tie, Loss point values
  • Shootouts — separate values for SO-win and SO-loss (useful when ties aren't allowed)
  • Forfeits — point values for forfeit-win and forfeit-loss
  • Bonus points — for goal-differential bonuses, blowout caps, etc.

Drag-orderable tiebreakers

The default chain is pts → wins → head-to-head → GA → GF → GD-capped → name. In the wizard, drag any tiebreaker up or down to reprioritize; tap the checkbox to disable.

Head-to-head only applies when exactly two teams are tied — it uses results of games strictly between them. GD-capped means goal differentials beyond a configurable cap (default ±10) are clamped, to avoid blowouts skewing the tiebreaker.

When two teams are still tied after the full chain, the final fallback is name (alphabetical) — predictable but never decisive in practice.

7

Standings Visibility Rules

Hide standings until you're ready

For fair-start leagues, you can hide public standings until enough games are played. Two policy options, set in the wizard:

Hide until week NStandings hidden until week N has at least one finalized game. Default: 0 (always show).
Hide until X games playedStandings hidden until X total games are finalized across the division. Useful when seasons start unevenly.
👁️

What spectators see when hidden: A polite card explaining the policy — e.g., "Standings open after week 3." The host's Leagues tab still shows the live data; only the public spectator page respects the rule.

8

Multi-Club Leagues

How multi-club works

A multi-club league has teams from more than one club. The host club publishes the league and owns the package. Visiting clubs import the schedule using the 6-character share code, just like a tournament.

Visiting clubs see their own team's games in their admin panel, can manage their roster, and submit scores — all of which sync back to the host's standings in real time.

Participating Clubs panel

On the Leagues tab, expand the Participating Clubs card to list every club involved. For each entry:

  • Display name — shows on the public spectator page and the printable participant onesheeter
  • Club ID — the slug used in ?join=CLUB_ID deep links; visiting clubs use this to claim their team's rosters

Paste rows in bulk via the "Bulk paste" details panel: Pacific Waves, pacific-waves-wpc one per line.

Free spectator access for visiting clubs

Under Multi-Club League, every parent of every visiting club gets the public spectator page free under your umbrella — no separate Parent subscription needed. They open app.eggbeater.app/?join=<your-league-code> and see standings + their team's schedule.

9

The Leagues Dashboard

One cockpit for the whole season

The Leagues tab is a full command center mirroring the Director tab's surfaces but customized for league framing. Every card you need is on one screen:

Card-by-card walkthrough

Run a LeagueSetup card with the wizard CTAs (Set up / Edit league, Import from Sheet, Build Playoff Bracket, /leagues/ entry, Sheets export)
Live DashboardReal-time game count, live scores, pending finals, conflicts across every division. Same engine as the Director tab.
Live Game ConsolePer-game status, score override, one-click finalization for stuck games. 30-second auto-refresh.
League WizardSheet import + manual setup CTAs surfaced as a discoverable card.
StandingsOne card per division. 6-column overview: rank, team, GP, W-L-T, GD, Pts. Respects visibility rules.
PublishingShare code, public URL, copy link, archive when the season ends.
Scorer DisplayPer-league stat toggles (assist, attempt, steal, turnover, block, exclusion, 5m goal, GK save) + cap color convention.
Logo RegistryUniversal + club-override logo URLs keyed by team name. Powers spectator cards, notifications, and Live Activities.
League RostersBulk-paste rosters for every participating club's teams. Required columns: Team, Cap #, First Name, Last Name. Optional: Division.
Participating ClubsMulti-club roster — display names + club IDs for deep-linked spectator access.
Past LeaguesArchived seasons. Frozen standings, final game data, original share code preserved.
10

Publishing Your League

What "publish" does

Hitting Publish League on the Publishing card writes your league package to the Eggbeater Worker, mints a 6-character share code (or reuses an existing one), and exposes:

  • Public spectator URLeggbeater.app/tournament.html?code=ABC123 — anyone with the link sees live standings + schedule + scores. No account required.
  • Share code — 6 characters, copy-paste-able. Visiting clubs use it to import the schedule into their own admin.
  • SEO landing pageeggbeater.app/leagues/{slug}/ — auto-generated, Google-indexable, fed from your league name + season + divisions.
🌐

/leagues/{slug}/ generation: After publish, the marketing site picks up your league on its next static-site rebuild. The /leagues/ index page lists every published league; each league has its own page with division standings, season dates, and host club info.

Sharing the league

The Publishing card shows three quick-share actions once published:

  • 📋 Copy Code — for visiting club admins to import
  • 📋 Copy Link — for parents / spectators
  • ↗ Open — open the public spectator page in a new tab to verify it looks right

Rollback: If you publish something with a typo or wrong dates, the ↩ Rollback button reverts the most recent publish. Edit your league config locally, then publish again.

11

Live Scoring & the Game Console

Volunteer scoring at the desk

Each league game is scored using the standard Eggbeater Tournament Scorer — volunteers tap the action (goal / assist / steal / kickout / etc.), tap the player, and the scoreboard updates within 5 seconds on every spectator's device. The same scorer works for tournaments and league games — there's nothing league-specific to learn at the desk.

A scoring password is required to enter scores; set it on the Publishing card. Anyone who knows the password can score; anyone who knows the share code can spectate.

The Live Game Console

The Live Game Console on the Leagues tab is the host's fallback when something goes sideways. Real-time grid of every league game in the next ~6 hours; one-click finalization for stuck games (a scorer who forgot to tap Final), score override for desk corrections, lock/unlock for adjudicated results.

Scorer Display — which stats to track

The Scorer Display card lets you toggle which optional stats appear in the scorer:

  • Assist · Attempt (missed shot) · Steal · Turnover · Block · Exclusion · 5m Goal · GK Save
  • Goal, Sprint Won, and Timeout are always shown — they're core to the scorer
  • Cap Color Convention toggle — show Dark/White cap badges + button styling (Hydres convention: team1=White, team2=Dark)

Changes take effect when you next publish the league.

12

Rosters & Logos

League Rosters card

Bulk-paste every participating team's roster once and Eggbeater stores them on the league package. Expected columns: Team, Cap #, First Name, Last Name; optional Division. Per-team imports auto-save the selected roster before deploy.

When the parent of a player on a visiting team opens the public spectator page, their cap number shows up in box scores automatically — the host club doesn't need to manually link players to teams.

Logo Registry

Logos resolve by team name. The Logo Registry has two scopes:

  • Universal — global logo registry shared across every Eggbeater club. Useful when participating clubs already have logos registered (most do).
  • Club overrides — your league's local overrides. Use these for ad-hoc team names that aren't in the global registry.

Columns: Team, Logo URL; optional Aliases, Division. Bulk paste or upload a CSV.

🖼️

Where logos show up: Spectator game cards, push notification thumbnails, Live Activity score widgets, and the printable participant onesheeter — all driven from this one registry.

13

Sheet Write-Back

What write-back does

If your league lives in a Google Sheet (a master schedule, a shared score sheet, a parent-facing standings tab), Eggbeater can POST score updates back to it as they happen. No more manually copying scores from the app to the sheet.

Deploy the Apps Script template

Open the Apps Script editor in your league's Google Sheet (Extensions → Apps Script) and paste the Eggbeater write-back template (available in the Publishing card after first publish). Save and deploy as a web app, allowing anonymous access.

Paste the webhook URL

Copy the deployed URL and paste it into the Apps Script Score Write-Back Webhook field at the top of the Director / Leagues import section. Optional shared secret for verification.

Watch scores flow

As scorers finalize games, the Worker POSTs each result to your Apps Script. The script writes back into your sheet — typically the row whose sourceSheet metadata Eggbeater preserved on import.

Status panel

The Sheet Write-Back card shows a live status panel below the URL fields: last POST timestamp, success / failure indicators, retry counts. If a write fails (auth, quota, sheet renamed), the panel surfaces the error.

Fastest setup: Deploy the Apps Script as a web app and paste the URL. Leave the secret blank for the first run; add a shared secret later once you've verified scores are flowing.

14

Live Activities & Push Reminders

League-aware reminder copy

When a parent subscribes to push notifications for a league team, Eggbeater sends two reminders before each game (60 minutes and 10 minutes out). The body of each reminder includes the week context:

⏰ U16 Girls in 1hr
Week 3 · vs Pacific Waves at 10:00 · Pool 2

Tournament games omit the "Week N · " prefix — only leagues get it.

Lock-screen Live Activities (iPhone)

When a parent taps "Follow Live" on a league game, the iPhone Live Activity header shows the league chrome:

HYDRES SPRING LEAGUE · WEEK 3

Score, quarter clock, last event — all the standard Live Activity affordances, with league framing in the header instead of "EGGBEATER WATER POLO".

📱

Subscriber matching by team ID: Eggbeater matches push subscribers by stable team ID first, then falls back to team name. If you rename a team mid-season (e.g., "Pacific Waves" → "Waves WPC"), existing subscribers still get notifications.

15

Season Archive

Wrap up a season

When your league season ends, archive it. The Publishing card's 📦 Archive this league button:

  • Freezes final standings, every game's box score, every roster
  • Moves the league from "live" to the Past Leagues card on your Leagues tab
  • Stamps the public spectator page as "season complete" so parents know it's wrapped
  • Preserves the share code permanently — visiting clubs can still look up their season history

Starting next season

After archiving, your Leagues tab returns to the empty-state setup card. Click Set up league → to mint a fresh code for the next season. You can:

  • Start from scratch — new divisions, new teams, new schedule
  • Clone from archive — reuse last season's structure (divisions, teams, format, points policy, tiebreakers) with scores cleared. The "Past Leagues" card has a clone action on each archived entry.
📦

Why archive instead of just renaming? Archiving preserves the historical record at the worker (not just locally) and frees up the share code's TTL so a fresh publish gets a fresh 30-day window. Visiting clubs and spectators see the league move to their History tab cleanly.

16

Tips & FAQ

Common questions

Can a club run both a tournament and a league at the same time?

Yes. Tournament Host + Club League / Multi-Club League stack. The Director tab manages tournaments; the Leagues tab manages league seasons. They publish to separate share codes and don't interfere.

What happens to subscribers when I archive a league?

Existing subscribers (push notifications, follow-live, etc.) are preserved on the worker for the share code's natural TTL (~30 days after publish expiration). After that, they age out automatically.

Can I switch a league from round-robin to Swiss mid-season?

Not recommended — you'd be changing the format definition while games are in flight. If you must, edit the league config, publish, then accept that already-played games will be re-assigned to the new format's structure. Better path: finish the current format, archive, start the next season fresh.

Do I need both Club League and Multi-Club League?

No — Multi-Club League inherits every Club League feature. Pick the SKU that matches your league shape today; you can upgrade later via your RevenueCat subscription if a club joins mid-season.

Can spectators see live scores during the season?

Yes — every league game updates the public spectator page within 5 seconds of a scoring action. Standings recompute on every finalization. No refresh required.

What about the iPhone widget / Apple Watch / Live Activities?

Everything that works for tournament games works for league games. The Apple Watch app shows your team's next league game, and Live Activities on iPhone include league + week chrome (see Live Activities & Push).

Next step: Print the One-Page Quick Start for your participating clubs and the volunteer scorers. It covers the 6 steps from setup to publish on a single sheet of paper.