Stop relying on generic error messages. Learn to implement custom exception handling, create a logging utility, and improve your plugin's stability and UX.
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.
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:
Exception class to break the execution flow when an unrecoverable state is reached.debug.log) so you can reconstruct the failure later.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:
PHPnamespace 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.
Logging is the "black box" of your plugin. We need a service that handles formatting and writing to the WordPress debug log.
PHPnamespace 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.
Let’s advance our Knowledge Base project by updating our KnowledgeBaseModel to handle database errors safely.
PHPnamespace 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:
PHPtry { $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'); }
Service/Logger.php file in your plugin.Exceptions/DatabaseException.php file.DatabaseException if the query fails.try/catch block and display the error using add_settings_error.catch block. If you catch an exception, you must either log it or re-throw it. Silently ignoring errors makes debugging impossible.$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.debug.log. Reserve logs for actual failures or critical state changes.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.
Master WordPress security by implementing capability checks. Learn to use current_user_can to restrict admin features and enforce proper access control.
Error Handling and Logging