Dokan turns WooCommerce into a multi-vendor marketplace. Out of the box, vendors get a flat percentage commission and that's it. Every serious marketplace I've worked on outgrows this within a quarter — they need tiered rates, category-specific splits, promotional bonuses, or vendor-class commissions.
Here's how Dokan's commission system actually works under the hood, and how to extend it without forking.
The Default Commission Flow
When an order is paid:
dokan_after_order_create_commissionfires- Dokan reads vendor's
dokan_admin_percentageanddokan_admin_percentage_typeuser meta - It calculates the admin's cut and writes a row to
wp_dokan_orders - The vendor's earning is
order_total - admin_cut
The admin percentage type can be percentage, flat, or combine (combination of percentage + flat fee).
The Filter You'll Use Most
apply_filters('dokan_get_seller_percentage', $percentage, $product_id, $vendor_id, $order_item_id);
This fires for every line item. Override it to implement custom logic:
add_filter('dokan_get_seller_percentage', function ($percentage, $product_id, $vendor_id, $order_item_id) {
// Premium vendors keep more
if (get_user_meta($vendor_id, 'is_premium_vendor', true) === 'yes') {
return 90; // 90% to vendor
}
// Promotional category gets a one-month boost
if (has_term('back-to-school', 'product_cat', $product_id) && time() < strtotime('2026-09-01')) {
return 95;
}
return $percentage;
}, 10, 4);
Per-Category Commission
A common ask: "Electronics get 10% commission, Books get 25%."
add_filter('dokan_get_seller_percentage', function ($percentage, $product_id) {
$category_rates = array(
'electronics' => 90,
'books' => 75,
'fashion' => 80,
);
$terms = wp_get_post_terms($product_id, 'product_cat', array('fields' => 'slugs'));
foreach ($terms as $slug) {
if (isset($category_rates[$slug])) {
return $category_rates[$slug];
}
}
return $percentage;
}, 10, 2);
Note: WordPress runs this filter on every product on every cart calculation. Cache aggressively if you have complex logic — wp_cache_get('dokan_pct_' . $product_id) saves ~50ms on cart pages with many items.
Subscription Commissions
Dokan Pro adds subscription products. The renewal commission is a separate filter:
add_filter('dokan_subscription_get_recurring_commission', function ($commission, $vendor_id, $order_id) {
// Apply a different rate to renewals
return $commission * 1.05; // 5% bonus on renewals
}, 10, 3);
Withdrawal Hooks
Vendor payouts go through Dokan's withdrawal system. Hook into approval to add custom validation:
add_action('dokan_withdraw_request_approved', function ($withdraw) {
// Notify the vendor's manager
$vendor_id = $withdraw->user_id;
$manager_id = get_user_meta($vendor_id, 'account_manager', true);
if ($manager_id) {
wp_mail(
get_userdata($manager_id)->user_email,
sprintf('Withdrawal approved for vendor %d', $vendor_id),
sprintf('Amount: %s', $withdraw->amount)
);
}
});
The Database Tables
For complex reporting, work directly with the schema:
-- Vendor earnings per order
SELECT seller_id, SUM(net_amount) as earnings
FROM wp_dokan_orders
WHERE order_status IN ('wc-completed', 'wc-processing')
AND DATE(created) >= '2026-04-01'
GROUP BY seller_id;
-- Pending withdrawals
SELECT user_id, SUM(amount) as pending
FROM wp_dokan_withdraw
WHERE status = 0
GROUP BY user_id;
Testing Commission Logic
I keep a Pest suite that exercises the commission filters with fixtures:
it('applies premium vendor rate', function () {
$vendor = $this->createVendor(array('is_premium_vendor' => 'yes'));
$product = $this->createProduct($vendor);
$rate = apply_filters('dokan_get_seller_percentage', 80, $product->ID, $vendor->ID, 0);
expect($rate)->toBe(90);
});
createVendor and createProduct are Pest factories I built using Dokan's vendor registration API — not the public REST one, the internal dokan_create_vendor().
What Not To Do
- Don't override
Dokan_Orders_Manager— Dokan's internals change between versions and your override breaks on upgrades. Use filters. - Don't write to
wp_dokan_ordersdirectly — go throughdokan()->orders->update()so the right hooks fire. - Don't trust
$order_item_idin old hooks — pre-Dokan-3.x, this was sometimes 0. Use the product+order combination as your stable key.
The filter API is enough for 95% of customisations. If you find yourself reaching for class extension, ask in the support forum first — there's usually a hook you missed.
Al Amin Ahamed
Senior software engineer & AI practitioner. Laravel, PHP, WordPress plugins, WooCommerce extensions.
About me →More from the blog
← Older
WordPress Plugin Security: Lessons from Two Disclosures
Newer →
Building Production Gutenberg Blocks in 2025
One email a month. No noise.
What I shipped, what I read, occasional deep dive. Unsubscribe anytime.
Check your inbox — confirmation link sent.