Convert a CSV File to an HTML Table in WordPress Without a Plugin

When a client needs to display a large data table on their WordPress site — think product specs, pricing grids, or reference data — manually building the HTML in the block editor quickly becomes impractical. PHP’s native fgetcsv() function lets you read a CSV file row by row and output clean, properly escaped HTML, with no plugin required.

Problem: A client needs to display tabular data from a CSV file on a WordPress page — re-entering it manually in the block editor is impractical, and the data is updated periodically.

Solution: Build a shortcode that reads the CSV file from the uploads directory with fgetcsv(), treats the first row as column headers, and renders a semantic <table> with <thead> and <tbody>. Pass the filename as a shortcode attribute so different pages can load different files.

The function below reads a CSV file from the WordPress uploads directory, treats the first row as column headers, renders any cell that is a valid URL as a hyperlink, and outputs a Bootstrap-compatible <table>:

<?php
/**
 * Convert a CSV file to an HTML table.
 *
 * @param string $file_url    Absolute URL to the CSV file (must be on the same server).
 * @param string $link_title  Link text used when a cell value is a URL.
 * @param string $link_class  Optional CSS class(es) for generated links.
 */
function convert_csv_to_html( $file_url, $link_title, $link_class = '' ) {
    $file_url  = esc_url( $file_url, [ 'http', 'https' ] );
    $file_path = $_SERVER['DOCUMENT_ROOT'] . wp_make_link_relative( $file_url );

    if ( ! file_exists( $file_path ) ) {
        return;
    }

    $handle = fopen( $file_path, 'r' );
    if ( $handle === false ) {
        return;
    }

    // Sanitise the class attribute once
    $class_attr = '';
    if ( ! empty( $link_class ) && preg_match( '/^[_a-z0-9 -]+$/i', $link_class ) ) {
        $class_attr = ' class="' . esc_attr( $link_class ) . '"';
    }

    $row_index = 0;
    echo '<div class="table-responsive"><table class="table">';

    while ( ( $row = fgetcsv( $handle, 1000 ) ) !== false ) {
        echo '<tr>';
        foreach ( $row as $cell ) {
            if ( $row_index === 0 ) {
                // First row: column headers
                echo '<th>' . esc_html( $cell ) . '</th>';
            } else {
                if ( filter_var( $cell, FILTER_VALIDATE_URL ) !== false ) {
                    echo '<td><a href="' . esc_url( $cell ) . '"' . $class_attr . '>'
                        . esc_html( $link_title ) . '</a></td>';
                } else {
                    echo '<td>' . esc_html( $cell ) . '</td>';
                }
            }
        }
        echo '</tr>';
        $row_index++;
    }

    fclose( $handle );
    echo '</table></div>';
}

Usage — call the function from within a page template, passing the URL to a CSV file already uploaded to the Media Library:

<?php
$csv_url = 'https://example.com/wp-content/uploads/2019/10/products.csv';
convert_csv_to_html( $csv_url, 'View PDF', 'btn btn-sm btn-outline-primary' );

NOTE: The function reads the file path via $_SERVER['DOCUMENT_ROOT'], so the CSV must be hosted on the same server. For externally hosted CSV files, use wp_remote_get() to fetch the content and write it to a temporary file before passing it to fgetcsv(). Also ensure the CSV is UTF-8 encoded to avoid character encoding issues in the rendered table.