Skip to content
Person by person

Know who is on the roster, and who is eligible to be there

A player record is more than a name and a jersey number. BallOBall holds the person: the teams they sit on, the age bracket they qualify for, the certificates and documents filed against them, and the game log that fills in as matches get scored. Build a roster by hand, paste in a whole squad, or send an invite and let the player supply the rest.

Nobody checks the birth date until somebody protests

The squad list is a spreadsheet. The birth certificates are in a shared drive somebody set up two seasons ago. The coaching certificates are a stack of photocopies, and the medical form is still in an email. None of it is joined up, so the checks only happen after something goes wrong — a protest lodged the morning after a match, a parent asking who cleared the assistant coach, an under-14 who has been playing six weeks in the under-12 division because the birth date was never to hand. Then the club secretary moves on and takes half of it with them. The point of a player record is that the check happens when the player is added, not when someone complains.

How it works

Four steps, no configuration

  1. Build the roster

    Add players one at a time with a jersey number, a position and a captain, or paste rows and upload a CSV for a whole squad. The parse is previewed with per-row errors before anything is written. Positions are validated against the league's sport, and a duplicate jersey number within a team is guarded against.

  2. Invite the person behind the name

    Email an invite to a player or a coach, or share the league's short join code for people to redeem. Invite links expire after 14 days, and a pending one can be revoked or regenerated. The roster does not wait on them — a player added by a coach exists straight away.

  3. Check they are allowed to be there

    Divisions and pools carry minimum and maximum age bounds, and an out-of-age player is refused both when an admin adds them and when someone registers, provided the birth date is on file. Certificates upload against the person, a compliance list shows who is expiring, and household documents sit in a vault where files are served only through short-lived signed links.

  4. Let the record build itself

    Every scored match feeds that player's game log and season line — opponent, result, and the stats their sport publishes, with home and away splits. Reading those back — game logs, stat lines, leaderboards and side-by-side comparison — is the paid Leaderboards module, which a league admin switches on per league.

Capabilities

What sits behind a player's name

Everything below is built and running today. Unbadged items are included on every plan; a badge means the capability needs a module added to your plan, a higher plan tier, or a piece of setup finished first.

12 of these are included on every plan. One group needs the Leaderboards module.

  • Player profiles and roster management

    Coaches and league admins can add, edit and remove players on a team roster, set jersey numbers, positions and a captain, with positions validated against the league's sport.

    Roster writes require coach-of-team, league-admin, or a club role grant carrying LEAGUE_MANAGE_ROSTER. Rosters lock ahead of upcoming matches. Jersey numbers are unique per team. Archived leagues are read-only.

  • Bulk roster import by paste or CSV

    Admins can paste rows or upload a CSV of players, preview the parsed result with per-row errors before committing, and see a per-row success/failure report with a retry for the failures.

    Parsing and validation happen in the browser and rows are submitted one HTTP request at a time — there is no server-side batch endpoint, so a large import is a sequence of individual creates.

  • Player and coach invitations by email plus league join codes

    Admins can invite people to a league or team by email, revoke or regenerate a pending invite, and share a short league join code that people can redeem to join; invitation links expire after 14 days.

    Invite emails are delivered through a third-party transactional email provider configured per environment; the invite row is still created if the email fails to send. Join codes can be rotated or set to a custom value by an admin.

  • Age-based division eligibility enforcement

    Divisions and pools can carry minimum and maximum age bounds, and the platform blocks adding an out-of-age player to a team in that division — both when an admin adds them to a roster and when someone registers.

    Requires a birth date on the player; league admins and above can apply an explicit eligibility override.

  • Organization people directory with scoped access grants

    Admins get one screen listing everyone in the organization together with every access grant they hold and what each grant's scope actually covers.

    The screen is read-only by design — the grant and revoke APIs are live, but there is no mutation UI on this page yet, so changes are made through other surfaces.

  • Role-based access control with scoped grants

    BallOBall ships a fixed set of roles — platform admin, platform support, league admin, coach, scorekeeper, referee, player, fan and a scan-only gate agent — and league admins can grant additional roles to members, including grants scoped to a single sport, league or team.

    Roles are a fixed enum; organizations cannot define their own top-level roles. Sensitive listings (minors' ID documents, referee 1099 earnings, background-check results) require an organization-wide grant, because narrower club-scoped grants intentionally pass the coarse role check used for navigation gates — those endpoints call a separate tenant-wide guard that rejects a club-, league-, team- or sport-scoped grant.

  • Player game log and season stat line on public player pages

    Leaderboards module

    A player page shows that player's season stat line and a per-game log with opponent, result and sport-specific per-game stats, with overall/home/away splits.

    Returns an empty stat line unless the league has the LEADERBOARDS module enabled (backend/src/handlers/playerStatsHandler.ts:317-319). Minutes in the game log are an explicit estimate derived from which periods a player recorded an event in, flagged minutesEstimated on every row until SUBSTITUTION reconciliation lands, and rendered with a leading ≈.

  • Player leaderboards ranked by sport-specific metrics

    Leaderboards module

    Leagues can publish leaderboards that rank players by the metrics their sport defines — points and rebounds for basketball, runs and wickets for cricket, goals and assists for soccer.

    The LEADERBOARDS feature module ships DISABLED by default (infrastructure/lambdas/db-init/platform-features.sql:1355, :1697) — a league admin must turn it on before any player numbers are served; until then the endpoint returns an empty list. A single leaderboard ranks by ONE metric at a time, capped at 100 rows and defaulting to 25; an omitted or unsupported metric falls back to that sport's first published metric.

  • Side-by-side compare for players or teams

    Leaderboards module

    Two to four players, or two to four teams, can be compared side by side on their season stat lines, with home/away split selection.

    Hard-limited to between 2 and 4 entities per comparison (backend/src/handlers/standingsHandler.ts:219-220). Signed-in per-sport section only (/s/:slug/compare); no public fan route. Numbers are withheld when the LEADERBOARDS module is off.

  • CSV export from stats and standings screens

    Standings, leaderboards, the stat grid, comparisons, box scores, player game logs and advanced-stats tables each have a CSV export button.

    CSV is the only export format — there is no PDF or Excel export anywhere in this domain. Exports are generated in the browser from the rows currently on screen (frontend/src/lib/csv.ts:13); there is no server-side export endpoint and no scheduled or emailed export.

  • Certification and compliance tracking with expiry alerts

    Coaches, referees and volunteers can upload certifications, admins verify or reject them, and a compliance dashboard lists who across the organization is expiring soon or already expired.

    Available to league admins and platform staff. Verification is a human admin decision on an uploaded document — nothing is checked against an issuing body.

  • Secure document vault for household and member records

    Families and organization admins can store residency, medical, insurance and photo-ID documents against a household: the storage key is generated server-side and bound to the uploader with a signed token, the file is never given a public URL, and retrieval happens only through a short-lived signed link.

    Access is limited to a guardian of that household, an admin of the household's organization, and platform staff. The underlying bucket uses S3-managed encryption with public access blocked; there is no customer-managed key option.

  • Guardian links and minor-messaging policy

    Guardians can be linked to and verified against a minor player, team messaging is checked against that policy before a message is stored, and admins get a review queue of policy violations they can resolve.

    The policy currently LOGS violations rather than blocking them — the service's stated v1 contract always returns allowed, so no message is actually refused, and no notification is sent to the guardian when a violation fires. Hard-blocking is described in the code as v2 work behind a per-tenant flag that does not exist yet.

  • Concussion return-to-play protocol

    A graduated multi-step return-to-play workflow lets staff file an incident, advance or set back a player through the stages, defer or clear them, and see which players on a team are currently in protocol.

    Gated behind the MODULE_CONCUSSION_PROTOCOL feature module, which is seeded disabled by default and must be enabled per league. A player can always read their own incident history regardless.

  • Data-subject rights: account deletion, data export and erasure

    A user can delete their own account from within the app, and a minor's data can be exported or erased by the player, a verified guardian, an organization admin or platform staff — deliberately not behind any feature toggle.

    Self-deletion is refused for staff accounts and for anyone who still owns an organization; ownership must be transferred first. Whole-organization export and user anonymization are platform-staff actions, not self-serve.

Questions

Before you ask

Do players have to sign up before we can put them on a roster?

No. A coach or league admin adds the player directly — name, jersey number, position — and the record exists from that moment. Email invites and the league join code are how a person gets their own sign-in, not how they get onto the team sheet. A pending invite can be revoked or regenerated by an admin at any point.

A player turns out for two teams, then moves clubs next season. Is that one record or several?

Several roster entries, one person — provided they have an account. An entry belongs to a team, because the jersey number, the position and the availability flag all belong to that team; a player on two squads has an entry on each, and next season's squad is a new entry again. What joins them is the user account. Once a player claims theirs through an email invite or the league join code, the entries they own link back to a single person and the game log rolls up across teams and seasons. A player added by name alone, who never claims an account, has no thread joining those entries — so sending the invite is what turns a set of unrelated squad lines into a history.

Why is a player's stats page empty?

Player stat lines, game logs, leaderboards and comparisons belong to the paid Leaderboards module, which ships disabled. A league admin turns it on per league; until then the endpoint returns an empty stat line and the public stats tab comes back empty for the same reason. Per-match box scores are included in the core plan and do not depend on it. One detail: minutes in the game log are an estimate derived from which periods the player recorded an event in, and are flagged as an estimate rather than presented as a recorded figure.

Does BallOBall run background checks on our coaches?

The Checkr and NCSI adapters run in stub mode in production. No request reaches either provider, and a check's status advances through a canned state machine rather than a real result — so nothing in that queue is evidence a person was actually screened. The workflow around the adapters is built end to end — initiate, track, provider callbacks, and a one-year background-check certification issued automatically when a check clears — and it is waiting on a live provider connection. What is live today is certification tracking: coaches, referees and volunteers upload a document, an admin verifies or rejects it, and a compliance dashboard lists who is expiring soon or already expired. That verification is a human decision on an uploaded file — nothing is checked against the issuing body.

How does it handle minors and guardians?

A guardian can be linked to and verified against a minor player. Household records — residency, medical, insurance, photo ID — live in a vault where the storage key is generated server-side and the file is served only through short-lived signed links. Team messages are checked against the minor-messaging policy, and here is the limit: that check records violations into an admin review queue rather than refusing the message, and the guardian is not notified. Treat it as evidence and review, not as a block.

A parent asks us to delete their child's record. What happens?

A minor's data can be exported or erased by the player, a verified guardian, an organisation admin or platform staff, and that path is deliberately not behind any feature toggle. Any user can also delete their own account from inside the app — except staff accounts, and anyone who still owns an organisation, who has to transfer ownership first. A whole-organisation export is a BallOBall platform-staff action rather than something an admin runs themselves.

Bring your first roster across

Public leagues need no account to open.