Back to Blog
Lesson 47 of the WordPress Plugin Development: Foundations (PHP & MVC) course
WordPressJune 25, 20264 min read

Error Handling and Logging: Building Robust WordPress Plugins

Stop relying on generic error messages. Learn to implement custom exception handling, create a logging utility, and improve your plugin's stability and UX.

WordPressPHPError HandlingLoggingExceptionsPlugin Developmentplugin-development

Previously in this course, we covered Handling Large Datasets: Performance & Scaling for WordPress Plugins to ensure your plugin remains responsive as your database grows. While performance is critical, stability is the foundation of user trust. This lesson adds a layer of professional resilience by teaching you how to catch, log, and communicate errors gracefully rather than letting your plugin fail silently or crash the site.

Error Handling from First Principles

In WordPress, developers often default to if/else checks that return false on failure. While functional, this approach lacks context. When something goes wrong—like a failed database write or an invalid API response—you need to know why it happened, where it occurred, and what state the application was in.

Professional error handling revolves around three pillars:

  1. Exceptions: Using PHP’s Exception class to break the execution flow when an unrecoverable state is reached.
  2. Logging: Writing those exceptions to a persistent, searchable storage (like debug.log) so you can reconstruct the failure later.
  3. UX: Translating technical failure into actionable feedback for your users, ensuring they aren't left staring at a blank screen or a cryptic fatal error.

Implementing Custom Exception Handling

Instead of throwing generic Exception objects, we define custom exceptions. This allows us to catch specific types of errors (e.g., DatabaseException vs. ValidationException) and handle them accordingly.

In your includes/Exceptions directory, start by creating a base class:

PHP
namespace KnowledgeBase\Exceptions;

class KnowledgeBaseException extends \Exception {}
class DatabaseException extends KnowledgeBaseException {}

Now, when you perform an operation in your model, you can throw these specific errors. This is much cleaner than returning false and hoping the controller checks for it.

Creating a Logging Utility

Logging is the "black box" of your plugin. We need a service that handles formatting and writing to the WordPress debug log.

PHP
namespace KnowledgeBase\Services;

class Logger {
    public static function log(string $message, string $level = 'ERROR'): void {
        if (!defined('WP_DEBUG') || !WP_DEBUG) {
            return;
        }
        
        $timestamp = date('Y-m-d H:i:s');
        error_log("[$timestamp] [KnowledgeBase] [$level] $message");
    }
}

By centralizing this in a Logger service, you can easily toggle logging levels or swap the storage destination (e.g., to an external service like Sentry) in the future without touching your business logic.

Worked Example: The Robust Save Operation

Let’s advance our Knowledge Base project by updating our KnowledgeBaseModel to handle database errors safely.

PHP
namespace KnowledgeBase\Models;

use KnowledgeBase\Exceptions\DatabaseException;
use KnowledgeBase\Services\Logger;

class KnowledgeBaseModel {
    public function saveArticle(array $data) {
        global $wpdb;
        
        $result = $wpdb->insert("{$wpdb->prefix}kb_articles", $data);
        
        if ($result === false) {
            Logger::log("Failed to insert article: " . $wpdb->last_error);
            throw new DatabaseException("Could not save article to database.");
        }
        
        return $result;
    }
}

In your AdminController, you can now gracefully handle this:

PHP
try {
    $this->model->saveArticle($data);
    add_settings_error('kb_messages', 'kb_saved', 'Article saved successfully.', 'updated');
} catch (DatabaseException $e) {
    add_settings_error('kb_messages', 'kb_error', 'Failed to save: ' . $e->getMessage(), 'error');
}

Hands-on Exercise

  1. Create a Service/Logger.php file in your plugin.
  2. Create a Exceptions/DatabaseException.php file.
  3. Refactor one of your existing database operations (from Secure CRUD Operations: Mastering wpdb for WordPress Development) to throw a DatabaseException if the query fails.
  4. Wrap the calling controller method in a try/catch block and display the error using add_settings_error.

Common Pitfalls

  • Swallowing Exceptions: Never use an empty catch block. If you catch an exception, you must either log it or re-throw it. Silently ignoring errors makes debugging impossible.
  • Exposing Sensitive Data: Never output $wpdb->last_error directly to the user. It can contain table names or query structures that aid attackers. Log the technical error internally, but show the user a generic "Something went wrong" message.
  • Over-Logging: Logging every trivial event will bloat your debug.log. Reserve logs for actual failures or critical state changes.

Recap

We've moved from simple error checking to a professional architecture. By utilizing exceptions, we separate the "happy path" from error recovery. Our logging utility provides the visibility we need to maintain the plugin, and by integrating with add_settings_error, we provide a UX that guides users through problems rather than confusing them. Like the TypeScript Result Pattern, the goal here is explicitness: make your code's potential failure points obvious and managed.

Up next: We will begin the process of preparing your plugin for the public repository by exploring advanced deployment strategies and versioning.

Similar Posts