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.
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.
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.
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.
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.
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.
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.
/leagues/{slug}/ SEO pageInheritance 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.
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.
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}/.
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.
__BYE__ to seed an odd-team-count division — the generator handles itPick 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).
One weekly preset auto-schedules every game. You enter:
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.
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.
Eggbeater supports six league formats. Each division can use a different format; common pairings include round-robin → single-elim playoff bracket (regular season → playoffs).
Every team plays every other team once (or twice, double round-robin). N×(N-1)/2 games per cycle. Best for 4–10 teams.
Bracket of 4 / 8 / 16. Losers out. Optional 3rd-place game + 5th–8th placement bracket. Perfect end-of-season playoff.
Winners bracket + losers bracket. Champions decided in a grand final. ~2N–1 games. Forgiving for one-loss teams.
Teams paired each round against opponents with similar records. No team is eliminated. Great for medium-large fields (16+).
Teams split into rotating pods (small round-robins) each week. Different opponents every week. Good for casual rec leagues.
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.
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.
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.
Either paste a Google Sheets URL (publicly viewable) or paste rows directly from your spreadsheet. Eggbeater detects the header row automatically.
Match each detected column to an Eggbeater field:
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.
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.
Standings recompute automatically every time a game finalizes. Each team row tracks:
The points policy lives in your league config. Edit the wizard to change:
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.
For fair-start leagues, you can hide public standings until enough games are played. Two policy options, set in the wizard:
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.
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.
On the Leagues tab, expand the Participating Clubs card to list every club involved. For each entry:
?join=CLUB_ID deep links; visiting clubs use this to claim their team's rostersPaste rows in bulk via the "Bulk paste" details panel: Pacific Waves, pacific-waves-wpc one per line.
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.
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:
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:
eggbeater.app/tournament.html?code=ABC123 — anyone with the link sees live standings + schedule + scores. No account required.eggbeater.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.
The Publishing card shows three quick-share actions once published:
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.
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 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.
The Scorer Display card lets you toggle which optional stats appear in the scorer:
Changes take effect when you next publish the league.
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.
Logos resolve by team name. The Logo Registry has two scopes:
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.
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.
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.
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.
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.
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.
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.
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.
When your league season ends, archive it. The Publishing card's 📦 Archive this league button:
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:
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.
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.