WP Manifestindependent plugin directory
manifest / integrations / leagueapps-wp

LeagueApps for WordPress

Publish LeagueApps Teams and Divisions on your own WordPress site. Read-only, one-way, and it never touches member contact details.

by garrettbuilds · github.com/garrettbuilds/leagueapps-wp · website

0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/garrettbuilds/leagueapps-wp/archive/refs/heads/main.zip

Publish your LeagueApps Teams and Divisions on your own site. Read-only, one-way, and it never touches member contact details.

Built for one LGBTQ+ softball league and a tournament they host, then generalised so it does not assume their vocabulary, their division model, or their league structure. If something in here still does, that is a bug — please open an issue.

What it does

  • Reads registrations from your LeagueApps Site and derives a team list from them
  • Groups teams by division, handling both LeagueApps division models
  • Caches the result in your own database, so visitors never wait on an API call
  • Renders through a Gutenberg block, or a shortcode for any other builder
  • Refuses to publish anything it cannot verify

What it deliberately does not do

  • Write to LeagueApps. There is no write method in the client, and adding one would mean adding it visibly to an interface rather than passing a different string to a general function.
  • Read member data. The member endpoint is absent from the endpoint map, so no code path reaches it.
  • Publish contact details. Registrations carry email, phone, address, date of birth, gender and payment status. All of it is dropped at the parse boundary. No setting turns any of it on.
  • Show schedules or standings. Those endpoints do not exist. See docs/LEAGUEAPPS-API.md.
  • Take over a page. It creates drafts and offers blocks. It does not rewrite anything you already published.
  • Delete your data when you deactivate it, or when you delete it, unless you switched that on first. See docs/DATA.md.

Requirements

  • WordPress 6.4+, PHP 8.1+
  • WP-CLI, and server cron or a host cron panel
  • A LeagueApps API key, one per Site

There are two LeagueApps APIs and they are not interchangeable. The public lane (api.leagueapps.io, la-api-key header) returns teams and locations with no personal data in them and is what you want. The private export lane (admin.leagueapps.io, OAuth2 JWT with a .p12) returns registrations, which carry names, emails, addresses, dates of birth and payment status — everything this plugin then throws away to find a team name.

Prefer the public lane. See docs/LEAGUEAPPS-API.md, including the header name, which cannot be worked out by trial and error because every wrong guess returns the same error as no key at all.

Neither lane has schedules or standings. Those endpoints do not exist.

Install

One entry per LeagueApps Site, because a key issued for one Site is refused by another:

// wp-config.php
define( 'LAWP_CREDENTIALS', array(
    1000001 => array( 'client_id' => 'key-name', 'cert_path' => '/var/www/example.com/private/tournament.p12' ),
    1234 => array( 'client_id' => 'key-name', 'cert_path' => '/var/www/example.com/private/league.p12' ),
) );

Never in the database: a key in wp_options is in every export, every backup and every migration.

The .p12 belongs above your web root but inside your site folder, mode 0600, owned by the user your cron job runs as — which is usually not the user PHP-FPM runs as. Not /opt: hosts commonly deny the site user there, and the resulting "not readable" is baffling because the file's own permissions look correct.

wp plugin activate leagueapps-wp
wp leagueapps health
wp leagueapps discover --site=1234

Discovery tells you how your Site is organised before you configure anything. Run it first. It is the difference between mapping your divisions and guessing them.

The admin screen

LeagueApps in the sidebar. Written for whoever inherits the site, not for whoever installed it.

It leads with whether the connection works, then for each event: how many teams are published, which divisions are configured but empty, what is on the public page, and when it last synced.

Three buttons:

Check for changes Reads LeagueApps and reports exactly what would change. Writes nothing.
Update the page now Applies it.
A team is missing The one that earns its place.

"A team is missing"

The question every administrator asks, and the one a plugin usually cannot answer: it is in LeagueApps, why is it not on the page?

It reads live, changes nothing, and lists every team the source offered with what happened to it and what to do:

Dallas Vengeance   Registration status is SPOT_PENDING, not Spot Reserved
                   The registration is unfinished in LeagueApps. Confirm the spot there.

Each row says what to DO. A reason code alone sends somebody back to whoever installed the plugin, which is the situation this screen exists to end.

Teams in finished programs are left out. The first version reported every team the Site had ever had - several hundred rows from completed 2018 seasons - and the one team genuinely missing was lost among them.

Sync

Dry run is the default and writes nothing:

wp leagueapps sync --event=summer-classic-2026

Applying takes two deliberate flags:

wp leagueapps sync --event=summer-classic-2026 \
  --mode=apply --confirm=apply-teams-summer-classic-2026

One cron entry; the plugin decides what is due:

*/5 * * * * flock -n /var/lock/leagueapps-sync.lock \
  /usr/local/bin/wp --path=/var/www/example.com/htdocs \
  leagueapps run-due-syncs --quiet >> /var/log/leagueapps-sync.log 2>&1

Safety rules that will stop a sync

These exist because each one describes a way a public page can become wrong.

Rule What it prevents
A read must finish A timeout emptying the page, because absent rows look like withdrawn teams
Mass-removal circuit breaker The wrong program silently replacing 84 teams with 8
Unknown divisions are held A team published in a division nobody assigned it to
Source identity check Last season's ids quietly syncing last season's teams
Denylist assertion A private field ever reaching the database
Plan staleness Applying a plan reviewed before lunch

Docs

BLOCK.md The Teams block and shortcode, and what the editor offers
DIVISIONS.md Both division models, and why the map is configuration
DRY-RUN.md What a dry run proves, and the exit codes
CONFIGURATION.md Wizard, blocks, and which setting lives where
DATA.md What is stored, caching, deactivation and uninstall
TESTING.md The suite, and the two dead guards mutation testing found
EDGE-CASES.md Real cases found in live data
LEAGUEAPPS-API.md Auth, endpoints, and where the docs are wrong

Tests

composer install && composer test

191 tests, no WordPress, no database, no network.

Licence

GPL-2.0-or-later.