Install
Upload it under Plugins → Add New Plugin → Upload Plugin, then activate.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/amirrad98/intramurals/releases/download/v1.0.2/leagueflow-1.0.2.zipLeagueFlow is a classic WordPress plugin for running multi-sport leagues with teams, players, fixtures, derived standings, and knockout brackets. It uses WordPress-native admin screens, metaboxes, taxonomies, dynamic Gutenberg blocks, and shortcodes.
GitHub releases and WordPress updates
Requires WordPress 6.5+ and PHP 8.1+. Download the attached
leagueflow-X.Y.Z.zip from GitHub Releases.
Install it through Plugins → Add New → Upload Plugin. The package contains the
stable leagueflow/ directory. Use this asset rather than GitHub's generated source
archives, which contain development tooling and have a different directory name.
Sites running 1.0.1 or earlier must upload the updater-enabled 1.0.2 release once and replace the existing installation. While LeagueFlow is active, later stable releases appear through WordPress's normal plugin update checks. Dashboard → Updates → Check again requests a check; successful release metadata is cached for five minutes. A site does not need a GitHub token. Administrators choose when to install updates or enable WordPress's automatic updates. Updates replace plugin files; they do not uninstall LeagueFlow, migrate websites or replace league data.
The updater uses the native Update URI hooks and the public release asset
releases/latest/download/latest.json. It accepts stable versions only and verifies
the downloaded ZIP against the specific version's manifest SHA-256. Failed checks
are cached for one minute; an unavailable or invalid feed supplies no update. Keep
the repository public and keep published assets immutable. Inactive plugins do not
load their update hooks; activate LeagueFlow before checking for GitHub updates.
The approach follows the deterministic packaging and verified-download design in Zoer Connect, with assets served directly from LeagueFlow's existing repository. No separate feed repository or deploy key is needed. HTTPS and checksums verify transport/integrity; they are not independent publisher signatures.
Build and release
php tests/github-updater.php
python3 -m unittest discover -s tests -p 'test_*.py' -v
python3 scripts/build.py
Outputs in ignored dist/ are a deterministic plugin ZIP, .zip.sha256 checksum
and latest.json manifest. Packaging includes the plugin entry point, uninstall
file, existing plugin documentation and runtime directories. It excludes Git,
workflows, audit documents, scripts and tests; unsafe runtime entries fail.
For a new stable release:
- Update the plugin header and
LEAGUEFLOW_VERSIONto the same newX.Y.Z. - Add
releases/X.Y.Z.mdrelease notes, run the local checks, commit and push. - Wait for branch CI: PHP 8.1–8.4, package/publication tests, native security regressions and upgrade integration on disposable WordPress 6.5 and current stable sites.
- Create an annotated matching tag (
git tag -a vX.Y.Z -m 'LeagueFlow X.Y.Z') and push it (git push origin vX.Y.Z).
The tag workflow repeats all checks before publishing. It creates a draft, verifies all three assets and then makes the release public. Reruns compare bytes and can finish incomplete drafts; they never overwrite different existing assets. An older release cannot displace a newer latest version. Prerelease/non-semantic tags fail validation and do not publish. Never move a published tag or replace its ZIP; increment the version instead. Branch and pull-request builds upload test artifacts but do not publish releases. No additional GitHub secrets are required.
Native integration sets LEAGUEFLOW_TEST_PACKAGE to the built ZIP and runs
wp eval-file tests/wordpress-security.php, followed by
wp eval-file tests/wordpress-update.php on an isolated
http://leagueflow.test installation with MySQL, PHP 8.2, WP-CLI, zip, mysqli
and the built plugin activated. Controlled HTTP fixtures exercise real WordPress
update hooks and Plugin_Upgrader: both checksum rejection without file changes
and a successful next-version installation preserving active status and stored
data. Local isolated tests supplement this integration; they do not certify every
host, theme or league workflow.
Architecture
lf_team,lf_player, andlf_matchare shared custom post types used across all sports.lf_sportis the core taxonomy that assigns every team, player, and match to a sport module.lf_league_levelassigns teams, players, matches, and calendar events to league levels such as Recreational and Competitive inside each sport.lf_competitionandlf_seasonare taxonomies shared by teams and matches.Sports_Managerdefines built-in sports, enables or disables sport modules, creates sport-specific admin menus, and exposes sport-specific match fields and icons.- Standings are calculated from finished match scores and are never entered manually.
- Knockout brackets are built from match meta (
lf_is_knockout,lf_round_label,lf_round_order,lf_next_match_id,lf_next_match_slot). - Frontend blocks and shortcodes use the same server-side renderer for consistent output.
- LeagueFlow ships portable public templates for team pages, match pages, and match archives. Classic themes use the bundled PHP wrappers, and block themes receive plugin-registered block templates unless the active theme or Site Editor provides a more specific override.
Folder Structure
leagueflow/
├── assets/
│ ├── css/
│ └── js/
├── blocks/
│ ├── knockout-bracket/
│ ├── league-table/
│ ├── match-card/
│ ├── match-list/
│ ├── team-list/
│ └── team-roster/
├── includes/
│ ├── class-activator.php
│ ├── class-admin.php
│ ├── class-assets.php
│ ├── class-blocks.php
│ ├── class-knockout-service.php
│ ├── class-plugin.php
│ ├── class-post-types.php
│ ├── class-renderer.php
│ ├── class-rest-controller.php
│ ├── class-seeder.php
│ ├── class-settings.php
│ ├── class-shortcodes.php
│ ├── class-sports-manager.php
│ ├── class-standings-service.php
│ ├── class-taxonomies.php
│ ├── class-template-loader.php
│ └── helpers.php
├── templates/
├── SPORTS.md
├── leagueflow.php
├── README.md
└── uninstall.php
Installation
- Copy the
leagueflowfolder intowp-content/plugins/. - Activate LeagueFlow in WordPress admin.
- Visit
LeagueFlow > Sportsand enable the sports you want to support. - Review
LeagueFlow > Settingsfor points, display defaults, and slug settings. - Create competitions and seasons from the LeagueFlow admin menu.
Data Model
- Sport: taxonomy term used to modularize one shared data model across soccer, basketball, football, baseball, hockey, volleyball, cricket, rugby, and future sport modules.
- League Level: taxonomy term used with Sport to split each sport into levels such as Recreational and Competitive.
- Competition: taxonomy term.
- Season: taxonomy term.
- Team: post with short name, city, coach, founded year, description, and featured image as logo.
- Player: post with team assignment, jersey number, position, age, nationality, captain flag, and featured image as photo.
- Match: post with fixture data, scores, status, sport-specific notes, and optional knockout settings.
- Field Availability: saved admin availability windows that describe when a field or venue can host matches. Matches can link back to the availability window used by the scheduling assistant.
Admin Usage
Sports Setup
- Start at
LeagueFlow > Sports. - Enable the sports you want active in the plugin.
- Each enabled sport gets its own left-hand WordPress admin menu with a sport-specific icon, overview page, and filtered team, player, match, standings, and bracket views.
- Disabled sports stay out of the menu so one install can be tailored to the sports you actually run.
Teams
- Add teams from
LeagueFlow > Teams. - Sport-specific menus also provide sport-scoped team screens and
Add Teamlinks. - Assign each team to a league level from the team details box.
- Use the main content editor for the team description.
- Use the featured image for the team logo.
Players
- Add players from
LeagueFlow > Players. - Sport-specific menus also provide sport-scoped player screens and
Add Playerlinks. - Assign each player to a team from the metabox.
- Mark captains with the captain checkbox.
- Administrators can link an eligible subscriber or portal account, or provision
a new account using the player's valid, unused email. New users receive a
WordPress password-setup link. Linking keeps existing passwords unchanged.
Account controls require
leagueflow_manage_player_accounts,edit_usersandpromote_users; provisioning also requirescreate_users. Privileged users and your own account cannot be linked as player identities.
Matches
- Add fixtures from
LeagueFlow > Matches. - Sport-specific menus also provide sport-scoped match screens and
Add Matchlinks. - Assign competition and season terms in the sidebar.
- Match sport is inherited from the selected teams so cross-sport fixtures are blocked.
- Match league level is inherited from the selected teams so recreational and competitive fixtures remain separate.
- Use the Scheduling Assistant in the match details box to auto-fill date/time and venue from field availability, or edit those fields manually to override the assistant.
- Mark a fixture as
finishedto have it counted in the league table. - For knockout fixtures, enable
Knockout Match, define the round label and order, and optionally choose the next match and slot. - The match events box changes by sport. Soccer can store scorers and cards, basketball can store quarter scores and foul summaries, baseball can store inning lines, and so on.
Field Availability
- Open
LeagueFlow > Field Availability, or the sport-specificField Availabilitypage, to define recurring weekday or one-off date windows for each site field. - Availability rules include field/venue name, sport scope, start and end times, match length, and optional buffer time.
- Use
Auto Schedule Matchesto fill missing match dates, venues, or both for a sport, competition, season, date range, or specific date. - Existing matches and calendar events at the same venue are treated as conflicts, and teams are not placed into overlapping match slots.
- Manual match edits remain the override path unless the overwrite option is intentionally selected.
- Global field configuration requires
leagueflow_manage_field_availability. Bulk scheduling requiresleagueflow_manage_scheduleand permission to edit every affected match; overwrite also requiresleagueflow_overwrite_schedule. Fixture generation requiresleagueflow_manage_fixturesand readable teams. Users without WordPress publication permission generate drafts.
These management capabilities are granted to administrators automatically on upgrade. Explicitly grant them when delegating to custom roles. Field availability permission authorizes changes across the whole site, including sport menu pages.
League Levels
- Manage level terms from
LeagueFlow > League Levels. - Recreational and Competitive levels are created automatically.
- Use the level selector in standings, bracket, block, shortcode, and REST contexts to filter a sport into one level.
Competitions and Seasons
- Manage competition terms from
LeagueFlow > Competitions. - Manage season terms from
LeagueFlow > Seasons. - Assign a sport to each competition and season so sport-specific pages, blocks, and filters stay consistent.
- Apply them to teams and matches so blocks, archives, standings, and brackets can filter correctly.
Standings
- Visit
LeagueFlow > Standingsto preview calculated tables in admin. - Points are controlled by plugin settings.
Knockout Brackets
- Visit
LeagueFlow > Knockout Bracketsto preview the visual bracket. - Winners can advance automatically into the next match when a knockout fixture is marked
finished. - If a match is decided by penalties or a ruling, use
Winner Override.
Gutenberg Blocks
League TableSport StandingsTeam ListTeam RosterMatch ListMatch CardKnockout Bracket
All blocks are dynamic server-side blocks and inherit the classic frontend styling from the plugin. League table, team list, match list, calendar, and knockout bracket blocks support sport and league_level attributes so editors can target a specific sport module and level. Sport Standings displays enabled sports split by league level as expandable standings panels.
Shortcodes
[league_table competition="spring-league" season="2026-spring" sport="soccer" league_level="recreational"][sport_standings competition="spring-league" season="2026-spring"][sport_standings sports="soccer,volleyball"][team_roster team="123"][match_list competition="spring-league" season="2026-spring" sport="soccer" league_level="competitive"][knockout_bracket competition="2026-cup" season="2026-spring" sport="soccer" league_level="competitive"][team_list competition="spring-league" season="2026-spring" sport="soccer" league_level="recreational"][match_card match="456"]
Shortcode attributes accept IDs or slugs where appropriate.
REST API
Public read-only endpoints are available under:
/wp-json/leagueflow/v1/standings/wp-json/leagueflow/v1/matches/wp-json/leagueflow/v1/teams/wp-json/leagueflow/v1/bracket/wp-json/leagueflow/v1/league-levels/wp-json/leagueflow/v1/events/wp-json/leagueflow/v1/calendar/events
Supported query parameters include sport, league_level, competition, season, status, team, limit, and include_knockout.
Matches use limit and page; calendar endpoints use per_page and page.
The default is 20 records per page and the maximum is 100. Zero/negative limits
use the default; oversized limits use the maximum. Calendar responses include
total, pages and pagination; filter metadata describes the requested page.
Date ranges (start_date, end_date), status, team, type and taxonomy filters
apply before database pagination. Calendar search includes titles, descriptions,
venues, teams and taxonomy labels. Match archives have page navigation.
Anonymous general collections omit fixtures referencing private or protected
teams, including when a password cookie exists. An explicit team filter verifies
that team's WordPress password cookie and includes its fixtures once unlocked;
the team's unlocked profile and recent matches remain available. Staff retain
private-team access through WordPress's normal permissions.
Embedded match lists and [match_calendar] also default to 20 and are capped at
100 records through their limit attribute. They no longer load all historical
records; use date/taxonomy scopes or the paginated REST endpoints for larger feeds.
Both POST /calendar/events and POST /events/create-calendar-event require
WordPress event creation/editing permission. Users who can publish default to
published events; other authors default to draft. Explicit publication, scheduled
publication and private status require their corresponding WordPress permissions.
Existing terms require assignment permission, and new terms additionally require
taxonomy management permission.
Demo Data
Use LeagueFlow > Settings > Generate Demo Data to create:
- Sample competitions and a season
- Recreational and Competitive league levels
- Four teams
- Five players per team
- League fixtures
- Cup semifinals and a final with automatic winner advancement
- Example frontend pages using sport-aware shortcodes
Local Testing Notes
- Activate the plugin locally.
- Generate demo data from settings.
- Visit the generated pages:
League TableFixtures & ResultsCup Bracket
- Edit a finished match score and confirm the standings update.
- Edit a knockout match, mark it finished, and confirm the winner advances into the configured next match.
- Insert LeagueFlow blocks in the block editor, select a sport, and compare frontend output.
- Review SPORTS.md for the built-in sport model notes and research references that shaped the modular field design.
Security and Standards
- Uses capability checks and nonces for admin actions.
- Sanitizes and escapes data in admin and frontend output.
- Translation-ready with the
leagueflowtext domain. - Avoids external frameworks and uses WordPress-native APIs.
Releases
1 release. Each count is every asset in that release; expand a row for the breakdown.