• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Building custom automated PDF financial reports and invoices for WooCommerce using dompdf library

Building custom automated PDF financial reports and invoices for WooCommerce using dompdf library

Setting Up the Development Environment

Before we dive into code, ensure you have a local WordPress development environment set up. This typically involves:

  • A local web server (e.g., Apache, Nginx)
  • PHP (version 7.4+ recommended for modern features)
  • MySQL or MariaDB
  • Composer for PHP dependency management
  • A WooCommerce installation (latest stable version)

We’ll be creating a custom WordPress plugin. The simplest way to start is by creating a new directory within your WordPress installation’s wp-content/plugins/ directory. Let’s name our plugin directory woocommerce-pdf-reports.

Inside this directory, create a main plugin file, e.g., woocommerce-pdf-reports.php. This file will contain the plugin header and our core logic.

Integrating dompdf for PDF Generation

The dompdf library is a robust PHP library that can render HTML and CSS into PDF documents. We’ll use Composer to manage its installation.

Navigate to your plugin directory in your terminal and run:

cd wp-content/plugins/woocommerce-pdf-reports
composer require dompdf/dompdf

This command will create a vendor directory containing dompdf and its dependencies, along with a composer.json and composer.lock file. We need to include the Composer autoloader in our plugin.

Plugin Header and Autoloader Inclusion

Open woocommerce-pdf-reports.php and add the standard plugin header, followed by the Composer autoloader inclusion.

/**
 * Plugin Name: WooCommerce PDF Reports
 * Plugin URI: https://example.com/plugins/woocommerce-pdf-reports/
 * Description: Generates custom PDF financial reports and invoices for WooCommerce.
 * Version: 1.0.0
 * Author: Your Name
 * Author URI: https://example.com/
 * License: GPL2
 */

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

// Include Composer autoloader.
if ( file_exists( __DIR__ . '/vendor/autoload.php' ) ) {
    require_once __DIR__ . '/vendor/autoload.php';
} else {
    // Handle error: Composer dependencies not installed.
    // In a production environment, you might want to display a more user-friendly error or prompt for installation.
    error_log( 'WooCommerce PDF Reports: Composer dependencies not found. Please run "composer install".' );
    return;
}

// Use the dompdf namespace.
use Dompdf\Dompdf;
use Dompdf\Options;

// Rest of the plugin code will go here.

Generating a Simple PDF Report

Let’s create a function that generates a basic PDF report. We’ll hook into a WooCommerce action or create a custom endpoint for demonstration. For simplicity, we’ll create a shortcode that triggers PDF generation.

First, define the HTML content for your report. This can be dynamic, pulling data from WooCommerce orders, products, or other sources.

/**
 * Generates a sample PDF report.
 */
function wppr_generate_sample_report() {
    // Get WooCommerce orders (example: last 5 completed orders)
    $orders = wc_get_orders( array(
        'limit' => 5,
        'status' => 'completed',
        'orderby' => 'date',
        'order' => 'DESC',
    ) );

    $html = '<!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>WooCommerce PDF Report</title>
        <style>
            body { font-family: sans-serif; }
            h1 { color: #333; }
            table { width: 100%; border-collapse: collapse; margin-top: 20px; }
            th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
            th { background-color: #f2f2f2; }
        </style>
    </head>
    <body>
        <h1>Sales Report - ' . date('Y-m-d') . '</h1>
        <p>This report summarizes recent completed orders.</p>
        <table>
            <thead>
                <tr>
                    <th>Order ID</th>
                    <th>Customer</th>
                    <th>Date</th>
                    <th>Total</th>
                </tr>
            </thead>
            <tbody>';

    if ( ! empty( $orders ) ) {
        foreach ( $orders as $order ) {
            $customer_name = $order->get_billing_first_name() . ' ' . $order->get_billing_last_name();
            $order_date = $order->get_date_created()->date( 'Y-m-d H:i:s' );
            $order_total = wc_price( $order->get_total() );

            $html .= '<tr>
                <td>' . $order->get_id() . '</td>
                <td>' . esc_html( $customer_name ) . '</td>
                <td>' . esc_html( $order_date ) . '</td>
                <td>' . $order_total . '</td>
            </tr>';
        }
    } else {
        $html .= '<tr><td colspan="4">No completed orders found.</td></tr>';
    }

    $html .= '</tbody>
        </table>
    </body>
    </html>';

    // Instantiate Dompdf with options.
    $options = new Options();
    $options->set( 'isHtml5ParserEnabled', true );
    $options->set( 'isRemoteEnabled', true ); // For loading external CSS/images if needed.

    $dompdf = new Dompdf( $options );

    // Load HTML content.
    $dompdf->loadHtml( $html );

    // (Optional) Set paper size and orientation.
    $dompdf->setPaper( 'A4', 'portrait' );

    // Render the HTML as PDF.
    $dompdf->render();

    // Output the generated PDF (inline display).
    $dompdf->stream( "sales-report-" . date('Y-m-d') . ".pdf", array( "Attachment" => false ) );
}

Now, let’s create a shortcode to trigger this function. Add this to your woocommerce-pdf-reports.php file:

/**
 * Shortcode to trigger PDF report generation.
 */
function wppr_generate_report_shortcode() {
    // Ensure WooCommerce is active.
    if ( ! class_exists( 'WooCommerce' ) ) {
        return '<p>WooCommerce is not active.</p>';
    }

    // Call the PDF generation function.
    wppr_generate_sample_report();

    // The stream() method exits, so this return is technically unreachable
    // but good practice for shortcode functions that might not generate output.
    return '';
}
add_shortcode( 'generate_wc_report', 'wppr_generate_report_shortcode' );

After activating the plugin, you can add the shortcode [generate_wc_report] to any WordPress page or post. Visiting that page will trigger the PDF generation and download the report.

Generating Invoices

Generating invoices requires a more structured approach, often involving specific order data and a predefined template. WooCommerce has built-in invoice functionality, but for customisation, we can leverage dompdf.

We’ll create a function that takes an order ID and generates a PDF invoice for it. This function could be hooked into the WooCommerce order processing flow or be accessible via an admin link.

/**
 * Generates a PDF invoice for a given order ID.
 *
 * @param int $order_id The ID of the WooCommerce order.
 */
function wppr_generate_invoice( $order_id ) {
    if ( ! $order_id || ! class_exists( 'WooCommerce' ) ) {
        return false;
    }

    $order = wc_get_order( $order_id );

    if ( ! $order ) {
        return false;
    }

    // Invoice HTML structure.
    $html = '<!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>Invoice #' . $order_id . '</title>
        <style>
            body { font-family: sans-serif; line-height: 1.6; }
            .invoice-box {
                max-width: 800px;
                margin: auto;
                padding: 30px;
                border: 1px solid #eee;
                box-shadow: 0 0 10px rgba(0, 0, 0, .15);
                font-size: 16px;
                line-height: 24px;
                color: #555;
            }
            .invoice-box table {
                width: 100%;
                line-height: inherit;
                text-align: left;
                border-collapse: collapse;
            }
            .invoice-box table td {
                padding: 5px;
                vertical-align: top;
            }
            .invoice-box table tr td:nth-child(2) {
                text-align: right;
            }
            .invoice-box table tr.top table td {
                padding-bottom: 20px;
            }
            .invoice-box table tr.top table td.title {
                font-size: 45px;
                line-height: 45px;
                color: #333;
            }
            .invoice-box table tr.information table td {
                padding-bottom: 40px;
            }
            .invoice-box table tr.heading td {
                background: #eee;
                border-bottom: 1px solid #ddd;
                font-weight: bold;
            }
            .invoice-box table tr.details td {
                padding-bottom: 20px;
            }
            .invoice-box table tr.item td {
                border-bottom: 1px solid #eee;
            }
            .invoice-box table tr.item.last td {
                border-bottom: none;
            }
            .invoice-box table tr.total td {
                border-top: 2px solid #eee;
                font-weight: bold;
            }
            @media only screen and (max-width: 600px) {
                .invoice-box table tr.top table td {
                    width: 100%;
                    display: block;
                    text-align: center;
                }
                .invoice-box table tr.information table td {
                    width: 100%;
                    display: block;
                    text-align: center;
                }
            }
            .company-details { text-align: right; }
            .company-details p { margin: 0; }
        </style>
    </head>
    <body>
        <div class="invoice-box">
            <table cellpadding="0" cellspacing="0">
                <tr class="top">
                    <td colspan="2">
                        <table>
                            <tr>
                                <td class="title">
                                    <!-- Your Logo Here -->
                                    <img src="https://via.placeholder.com/150x75" alt="Company Logo" style="width:100%; max-width:300px;">
                                </td>
                                <td class="company-details">
                                    <strong>Your Company Name</strong><br />
                                    Your Street Address<br />
                                    Your City, Postal Code<br />
                                    Your Email<br />
                                    Your Phone
                                </td>
                            </tr>
                        </table>
                    </td>
                </tr>
                <tr class="information">
                    <td colspan="2">
                        <table>
                            <tr>
                                <td>
                                    <strong>Billed To:</strong><br />
                                    ' . esc_html( $order->get_billing_first_name() . ' ' . $order->get_billing_last_name() ) . '<br />
                                    ' . esc_html( $order->get_billing_address_1() ) . '<br />
                                    ' . ( $order->get_billing_address_2() ? esc_html( $order->get_billing_address_2() ) . '<br />' : '' ) . '
                                    ' . esc_html( $order->get_billing_city() ) . ', ' . esc_html( $order->get_billing_postcode() ) . '<br />
                                    ' . esc_html( $order->get_billing_email() ) . '
                                </td>
                                <td style="text-align: right;">
                                    <strong>Invoice #:</strong> ' . $order_id . '<br />
                                    <strong>Date:</strong> ' . $order->get_date_created()->date( 'Y-m-d' ) . '<br />
                                    <strong>Due Date:</strong> ' . $order->get_date_paid() ? $order->get_date_paid()->date( 'Y-m-d' ) : 'N/A' . '
                                </td>
                            </tr>
                        </table>
                    </td>
                </tr>
                <tr class="heading">
                    <td>Item</td>
                    <td style="text-align: right;">Price</td>
                </tr>';

    // Order Items
    foreach ( $order->get_items() as $item_id => $item ) {
        $product_name = $item->get_name();
        $item_price = wc_price( $item->get_total() ); // Total price for this item line

        $html .= '<tr class="item">
            <td>' . esc_html( $product_name ) . '</td>
            <td style="text-align: right;">' . $item_price . '</td>
        </tr>';
    }

    // Subtotal, Shipping, Taxes, Total
    $html .= '<tr class="total">
        <td></td>
        <td style="text-align: right;">
            <strong>Subtotal:</strong> ' . wc_price( $order->get_subtotal() ) . '<br />';

    // Display shipping if applicable
    if ( $order->get_shipping_total() > 0 ) {
        $html .= '<strong>Shipping:</strong> ' . wc_price( $order->get_shipping_total() ) . '<br />';
    }

    // Display taxes if applicable
    if ( $order->get_total_tax() > 0 ) {
        $html .= '<strong>Tax:</strong> ' . wc_price( $order->get_total_tax() ) . '<br />';
    }

    $html .= '<strong>Total:</strong> ' . wc_price( $order->get_total() ) . '
        </td>
    </tr>
    </table>
    <p style="text-align: center; margin-top: 30px;">Thank you for your business!</p>
    </div>
    </body>
    </html>';

    // Instantiate Dompdf.
    $options = new Options();
    $options->set( 'isHtml5ParserEnabled', true );
    $options->set( 'isRemoteEnabled', true ); // For loading external CSS/images if needed.

    $dompdf = new Dompdf( $options );
    $dompdf->loadHtml( $html );
    $dompdf->setPaper( 'A4', 'portrait' );
    $dompdf->render();

    // Output the generated PDF (as an attachment).
    $dompdf->stream( "invoice-" . $order_id . "-" . date('Y-m-d') . ".pdf", array( "Attachment" => true ) );

    return true;
}

To make this accessible, you could add a link to the order details page in the WordPress admin. For example, you can hook into the woocommerce_admin_order_actions filter:

/**
 * Add a custom action button to the WooCommerce order list.
 */
function wppr_add_custom_order_action( $actions, $order ) {
    // Check if the order is valid and if WooCommerce is active.
    if ( ! $order || ! class_exists( 'WooCommerce' ) ) {
        return $actions;
    }

    // Generate the URL for the invoice generation.
    // We'll use a nonce for security and pass the order ID.
    $invoice_url = wp_nonce_url( admin_url( 'admin-ajax.php?action=generate_wc_invoice&order_id=' . $order->get_id() ), 'generate_wc_invoice_nonce' );

    // Add the button to the actions array.
    $actions['generate_invoice'] = array(
        'url'    => $invoice_url,
        'icon'   => 'dashicons-download', // WordPress Dashicons
        'label'  => __( 'Download Invoice', 'woocommerce-pdf-reports' ),
    );

    return $actions;
}
add_filter( 'woocommerce_admin_order_actions', 'wppr_add_custom_order_action', 10, 2 );

/**
 * Handle the AJAX request for invoice generation.
 */
function wppr_handle_ajax_invoice_generation() {
    // Verify nonce for security.
    if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'generate_wc_invoice_nonce' ) ) {
        wp_die( __( 'Security check failed.', 'woocommerce-pdf-reports' ) );
    }

    // Check if order ID is set.
    if ( ! isset( $_GET['order_id'] ) || ! absint( $_GET['order_id'] ) ) {
        wp_die( __( 'Invalid order ID.', 'woocommerce-pdf-reports' ) );
    }

    $order_id = absint( $_GET['order_id'] );

    // Call the invoice generation function.
    if ( wppr_generate_invoice( $order_id ) ) {
        // The stream() method in wppr_generate_invoice handles the output and exits.
        // No further action needed here.
    } else {
        wp_die( __( 'Failed to generate invoice.', 'woocommerce-pdf-reports' ) );
    }
}
add_action( 'wp_ajax_generate_wc_invoice', 'wppr_handle_ajax_invoice_generation' );
// For non-logged-in users, though typically admin actions are for logged-in users.
// add_action( 'wp_ajax_nopriv_generate_wc_invoice', 'wppr_handle_ajax_invoice_generation' );

Now, when you view the WooCommerce Orders list in the admin area, you’ll see a “Download Invoice” button for each order. Clicking it will generate and download the PDF invoice.

Customization and Advanced Features

The provided examples are basic. For production-ready solutions, consider:

  • Templating Engine: Instead of raw HTML strings, use a templating engine (like Twig, though not directly supported by dompdf without extra work, or simply more structured PHP functions) for cleaner HTML generation.
  • CSS Styling: Embed complex CSS for professional-looking documents. Ensure your CSS is compatible with dompdf‘s rendering capabilities. External stylesheets can be loaded if isRemoteEnabled is true, but it’s often better to embed styles directly or use a local CSS file.
  • Dynamic Data: Fetch and display more detailed order information, product meta, customer history, etc.
  • Error Handling: Implement robust error handling for PDF generation failures, file permissions, etc.
  • Internationalization: Use WordPress’s translation functions (__(), _e()) for all user-facing strings.
  • Configuration Options: Allow users to customize company details, logo, invoice templates, and report parameters via the WordPress settings API.
  • Background Processing: For very large reports or many invoices, consider using background job queues (e.g., WP-Cron with a queue plugin, or external services) to avoid timeouts.
  • Security: Always sanitize inputs and use nonces for any actions triggered via URLs or AJAX.

dompdf offers extensive options for controlling PDF output, including font embedding, image handling, and page breaks. Refer to the dompdf GitHub repository for more advanced configurations and troubleshooting.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Reducing database query bloat in Sage Roots modern environments layouts using custom lazy loaders
  • Performance Optimization: Tuning PHP-FPM and opcache pools for high-concurrency Firebase Realtime DB handlers
  • Reducing Largest Contentful Paint (LCP) by optimizing custom script enqueuing structures in legacy plugins
  • How to implement native Redis caching layers for high-volume custom taxonomy queries in Carbon Fields custom wrappers
  • Building secure B2B pricing grids with custom REST API Controllers endpoints and role overrides

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (658)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (872)
  • PHP (5)
  • PHP Development (48)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • Server (23)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (182)
  • WordPress Plugin Development (197)
  • WordPress Plugin Development (330)
  • WordPress Theme Development (357)

Recent Posts

  • Reducing database query bloat in Sage Roots modern environments layouts using custom lazy loaders
  • Performance Optimization: Tuning PHP-FPM and opcache pools for high-concurrency Firebase Realtime DB handlers
  • Reducing Largest Contentful Paint (LCP) by optimizing custom script enqueuing structures in legacy plugins

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (872)
  • Debugging & Troubleshooting (658)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • Business & Monetization (390)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala