Handling Conflict Resolution: Optimistic Locking in WordPress
Prevent data overwrites and ensure data integrity in your WordPress plugins by implementing optimistic locking, handling concurrency, and improving UX.
Previously in this course, we built robust custom hooks in creating-custom-hooks-for-api-logic-in-wordpress-plugins to simplify our data layer. Now, we're tackling a critical production-level challenge: what happens when two admins try to edit the same knowledge base article at the same time? Without a strategy, the last person to hit "Save" silently overwrites the previous user’s changes.
Understanding Concurrency and Data Integrity
In a distributed environment like the WordPress admin, concurrency issues arise when the gap between "reading" data and "writing" data is long enough for another process to intervene. To maintain data integrity, we must ensure that a user is only modifying the version of the data they actually saw on their screen.
We achieve this via optimistic locking. Instead of locking the database row (which can lead to performance bottlenecks), we assume conflicts are rare. We attach a version number (or timestamp) to our records. When a user submits an update, the system compares the version they retrieved with the current version in the database. If they don't match, the update is rejected, preventing accidental data loss.
Implementing Optimistic Locking in the REST API
To implement this, we need to track a version column in our database. Every time an entry is updated, we increment this value.
1. The Database Schema Update
First, ensure your custom table has a version column (integer, default 1).
2. The REST API Update Logic
When updating your Knowledge Base entry, your API endpoint must accept the expected version and verify it.
PHP#6A9955">// Inside your REST API update callback public function update_item( $request ) { $id = $request['id']; $expected_version = (int) $request['version']; global $wpdb; $table = $wpdb->prefix . 'kb_entries'; #6A9955">// Fetch the current state from the DB $current_entry = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table WHERE id = %d", $id ) ); #6A9955">// CONCURRENCY CHECK if ( (int) $current_entry->version !== $expected_version ) { return new WP_Error( 'kb_conflict', 'The data has been modified by another user. Please refresh and try again.', array( 'status' => 409 ) ); } #6A9955">// Perform the update and increment version $wpdb->update( $table, [ 'content' => $request['content'], 'version' => $current_entry->version + 1 ], [ 'id' => $id ] ); return rest_ensure_response( [ 'success' => true ] ); }
Improving UX with Conflict Notifications
When the API returns a 409 Conflict status, your React application must handle it gracefully. Instead of a generic error, provide a way for the user to reload the data without losing their current work if possible, or at least inform them clearly.
In your React component, use the api-fetch response to catch the error:
JAVASCRIPTconst handleSave = async (data) => { try { await apiFetch({ path: CE9178">`/kb/v1/entries/${data.id}`, method: CE9178">'POST', data: { ...data, version: currentVersion } }); } catch (error) { if (error.code === CE9178">'kb_conflict') { // UX: Alert the user and offer a refresh alert("This entry has changed! Please reload to see the latest version."); } } };
Hands-on Exercise
- Modify your database: Add a
versioncolumn to yourkb_entriestable if you haven't already. - Update the API: Implement the version check in your
POSTorPUTendpoint as shown above. - Test the flow:
- Open two browser tabs with the same edit screen.
- Save in Tab A (incrementing the version).
- Attempt to save in Tab B (which still has the old version number).
- Verify that your API returns a
409error and your UI displays the notification.
Common Pitfalls
- Ignoring the Status Code: Always use
409 Conflictfor concurrency errors. This is the semantic HTTP standard and allows your frontend to distinguish between validation errors (400) and concurrency conflicts. - Stale Data in Redux: If you are using the WordPress Data store (as discussed in handling-complex-state-dependencies-in-wordpress-data-stores), ensure your store refreshes the
versionafter a successful save. If the store remains stale, the user will trigger a conflict on their very next save attempt. - Forgetting to Increment: It's easy to update the content but forget to increment the version number in your SQL query. Always perform both atomically.
Recap
Optimistic locking is a lightweight, high-performance way to handle concurrency. By validating version numbers against the database at the moment of submission, we protect data integrity. When conflicts occur, surfacing the issue clearly through the UI ensures a professional and reliable user experience.
Up next: We will build a Modal Confirmation System to add a final layer of safety to destructive actions.
Work with me

Headless WordPress + Next.js Frontend Development
Keep WordPress for content, get a lightning-fast Next.js frontend. The best of both worlds — familiar editing, modern speed.

Custom WordPress Plugin Development
Custom WordPress & WooCommerce plugins built to standard — by the developer behind a plugin with 5,000+ active installs and a SaaS with 10,000+ users.