Sports club match schedule table layout

Project research.
I have been asked if I can help a local children’s sports club to setup a system on their website to publish a schedule of matches and display it in a grid. I have started experimenting with custom post types but I’m not sure if I am going down the wrong path. I currently have a custom post type for matches and a custom post type for teams. They would like a grid as follows that will be updated from the custom post types but I am searching for the best way to do this.
U10 team U11 Team U12 team U13 team U14 team
30/05/2025 away home away away away
31/05/2025 home home away
01/06/2025 home away home
02/06/2025 away away home
20/06/2025 away

My sandbox is setup so the user adds a match and selects a date form a date picker for the match date but I will need to setup a looper to do something like a SELECT DISTINCT in sql
The match CPT is also linked to the Teams CPT using ACF pro relationship filed
I have been wondering if match date should actually be a custom taxonomy so each match date will need to be added to a match date taxonomy before a user can add a match to that day.

Any thoughts on this are much appreciated.

I did query this with Claude and it came back with the following and most notably that a match date taxonomy is a bad idea!!! I thought i would post this a people are interested in how AI is responding

CPT + ACF Relationship — You’re on the right track
Using a Match CPT linked to a Teams CPT via an ACF relationship field is a solid foundation. Don’t abandon that path.


The Taxonomy Question for Match Dates
This is the most interesting architectural decision. Here’s the honest breakdown:
Using a custom taxonomy for dates — probably not ideal, for a few reasons:
• Dates are inherently a data type, not a classification. Taxonomies are designed for things like “category” or “location”, not calendar dates
• You’d end up with a messy growing list of taxonomy terms like 30-05-2025, 31-05-2025 etc.
• Querying and sorting them chronologically becomes awkward since taxonomy terms are alphabetical by default
• Adding a new match date means an admin has to first create the taxonomy term, which adds friction and room for error
Stick with a date picker field (ACF date field) — this is the right call because:
• Dates sort and filter naturally in WP_Query using meta_query and orderby => meta_value
• No pre-administration needed before adding a match
• Your SELECT DISTINCT equivalent is achievable via a custom query


The DISTINCT Date Problem in WordPress
This is where it gets interesting. You have a few options:
Option 1 — Custom SQL query (most direct)
php
global $wpdb;
$dates = $wpdb->get_col("
SELECT DISTINCT meta_value
FROM {$wpdb->postmeta}
WHERE meta_key = ‘match_date’
ORDER BY meta_value ASC
");
This gives you your unique dates to loop over as the row headers in your grid.
Option 2 — WP_Query with grouping in Twig/PHP Run a query for all matches, then group the results by date in PHP/Twig before rendering. Less efficient but keeps you within standard WP_Query.


The Grid Logic
The grid you’ve described is essentially a pivot table — dates on one axis, teams on the other. That’s hard to do purely with a standard looper. The approach I’d suggest:

  1. Get your distinct dates (the rows)
  2. Get all your teams (the columns) — from your Teams CPT
  3. For each date, loop through each team and check if a match exists for that combination, and if so whether it’s home or away
    In pseudo-code:
    for each date:
    for each team:
    find match where match_date = date AND related_team = team
    output “home” / “away” / empty cell

Cornerstone / Twig Consideration
The pivot table logic is complex enough that a pure looper stack in Cornerstone will struggle with this. I’d suggest:
• Build a custom PHP function (in your theme’s functions.php or a plugin) that returns the fully structured grid data as a nested array
• Expose it to Twig via a Timber filter or a custom shortcode/block
• Then your Cornerstone template just renders what PHP has already structured
This keeps the heavy logic out of the builder and makes the template clean and maintainable.


Summary Recommendation
Decision Recommendation
Match date field type ACF Date Picker (not taxonomy)
Team linking ACF Relationship (keep as-is)
Distinct dates Custom SQL or PHP grouping
Grid rendering PHP builds the data structure, Twig renders it
Looper use Fine for simpler lists, but the pivot grid needs custom code

Hey @The_Capture_Factory,

The recommendations provided are architecturally correct and represent the best path forward for your setup.

Below is a detailed analysis of your concerns, along with a concrete architectural plan for how you can implement this clean, high-performance hybrid solution directly within the Pro framework (without losing the ability to use the Cornerstone builder).

Please just note that this involves custom coding which is beyond the scope of our theme support. If you need further help on this, you can subscribe to our One support or One Total Care if you need implementation.


Concern 1: Structuring the Date Data (ACF Date Picker vs. Custom Taxonomy)

Verdict: Stick to the ACF Date Picker. Avoid using custom taxonomies for specific calendar dates.

Why ACF Date Picker is the Correct Choice:

  1. Chronological Querying & Sorting: Dates are linear, continuous mathematical values. ACF stores the date picker value as a standard string formatted as YYYYMMDD (or similar) in the wp_postmeta table. This allows WordPress to easily run meta queries using standard comparison operators (e.g., > or < today’s date) to filter upcoming or past matches.
  2. Simplified Administration: A date picker offers a clean calendar dropdown UI in the WordPress admin panel, preventing human input errors (such as typos in date strings).
  3. Performance: While meta queries on massive datasets can be slow, a local sports club’s match database is typically small (hundreds or thousands of entries), meaning the query overhead is negligible.

Why Custom Taxonomies for Dates are an Anti-Pattern:

  1. Classification vs. Chronology: Taxonomies (wp_terms) are designed for classification (e.g., “Spring Season 2026”, “Under-12 Division”), not for specific chronological days.
  2. Complex Range Queries: You cannot natively perform mathematical greater-than/less-than queries on taxonomy terms since they are treated as unique string slugs. Querying “matches in the next two weeks” becomes incredibly convoluted.
  3. Database Bloat: Creating a new taxonomy term for every single calendar day unnecessarily bloats the wp_terms and wp_term_taxonomy tables, rendering terms non-reusable and defeating the structural purpose of a taxonomy.

Concern 2: Building the Grid (Looper Logic & Matrix Pivot-Table)

Verdict: Do not use standard Cornerstone Loopers out of the box. Instead, use a Cornerstone Custom Looper Provider (via PHP Hook) to fetch and structure your data efficiently, then loop through it visually inside Cornerstone.

Why Standard Cornerstone GUI Loopers Fall Short Here:

A match schedule grid is a pivot table (matrix) where you plot Dates (Rows) against Teams (Columns). To build this purely with standard Cornerstone loopers, you would need:

  1. An outer looper to fetch distinct match dates.
  2. An inner looper to fetch teams.
  3. A third conditional check or query in each cell to see if a match exists between that team and that date.

This creates a severe $N \times M$ query problem (executing separate database queries for every single cell in the grid). For 20 dates and 10 teams, this results in 200+ database queries per page load, which will severely degrade page performance unless you setup caching.


The Recommended Solution: A Hybrid “Best of Both Worlds” Approach

You do not need to discard Cornerstone and write raw HTML/PHP templates. Instead, you can write a high-performance PHP/SQL function to fetch and structure the schedule matrix, and expose that matrix to Cornerstone using a Custom Looper Provider.

Step 1: Create a Custom Looper Provider in functions.php

You can register a custom looper in your child theme’s functions.php using Pro’s native cs_looper_custom_{action} hook. This function queries the database once using custom SQL or an optimized WP_Query, groups the matches by date and team, and returns a clean, structured array to Cornerstone.

Here is a conceptual example of how this is implemented:

// Register the custom looper provider in functions.php
add_filter( 'cs_looper_custom_match_schedule_matrix', function( $result, $params ) {
    
    // 1. Fetch all teams (to form columns)
    $teams = get_posts([
        'post_type'      => 'team',
        'posts_per_page' => -1,
        'fields'         => 'ids', // highly optimized
    ]);

    // 2. Fetch all matches with their ACF date and team relationships
    $matches = get_posts([
        'post_type'      => 'match',
        'posts_per_page' => -1,
        'meta_key'       => 'match_date',
        'orderby'        => 'meta_value',
        'order'          => 'ASC',
    ]);

    $matrix = [];

    // 3. Process matches into a structured matrix grouped by date
    foreach ( $matches as $match ) {
        $date = get_field( 'match_date', $match->ID ); // YYYYMMDD
        $home_team = get_field( 'home_team', $match->ID ); // Relationship field return (Post ID / Object)
        $away_team = get_field( 'away_team', $match->ID );
        
        if ( ! isset( $matrix[$date] ) ) {
            $matrix[$date] = [
                'formatted_date' => date_i18n( 'F j, Y', strtotime( $date ) ),
                'raw_date'       => $date,
                'team_matches'   => [],
            ];
        }

        // Map which teams play on this date, and whether they are Home/Away
        if ( $home_team ) {
            $matrix[$date]['team_matches'][$home_team] = [
                'status'    => 'Home',
                'opponent'  => get_the_title( $away_team ),
                'match_id'  => $match->ID
            ];
        }
        if ( $away_team ) {
            $matrix[$date]['team_matches'][$away_team] = [
                'status'    => 'Away',
                'opponent'  => get_the_title( $home_team ),
                'match_id'  => $match->ID
            ];
        }
    }

    // Convert associative array to indexed list for the Looper
    return array_values( $matrix );
}, 10, 2 );

Step 2: Use the Custom Looper in Cornerstone

Once registered, you can configure your grid layout visually inside the Cornerstone builder:

  1. Outer Row Looper (Dates):

    • Select your Row or Section element.
    • Enable Looper Provider and set the Type to Custom.
    • Set the Custom Hook name to match_schedule_matrix.
    • Enable Looper Consumer on the columns or repeated grid rows to loop through each distinct date.
    • You can print the row header using: {{dc:looper:field key="formatted_date"}}.
  2. Inner Column Looper (Teams) or Individual Cells:

    • Since you have all the teams in columns, you can use standard Cornerstone Looper Providers to loop through the “Teams” CPT to render the column cells.
    • In each team’s cell, you can access the pre-loaded data instantly from memory without querying the database again. You can write a tiny PHP helper function / custom shortcode to pull the status:
      // Example shortcode: [get_match_status team_id="{{dc:post:id}}"]
      add_shortcode( 'get_match_status', function( $atts ) {
          // Access the current item from the parent date looper
          $current_date_data = cs_looper_manager()->get_current_data(); 
          $team_id = $atts['team_id'];
          
          if ( isset( $current_date_data['team_matches'][$team_id] ) ) {
              $match_info = $current_date_data['team_matches'][$team_id];
              return sprintf( '%s vs %s', $match_info['status'], $match_info['opponent'] );
          }
          
          return '—'; // No match scheduled for this team on this date
      });
      

Thank you @christian for taking the time to read and reply to my post. I will be having a good go at the challenge myself first as i enjoy creating solutions but as i’m not a massively experienced developer i will probably use the services of your One Support service to get the project over the line.
Thanks again for your invaluable input and advice and taking the time to respond.

Kind regards
Richard

You are most welcome, Richard.

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.