## 1. Created BITESHIP_ADDON_SPEC.md ✅ - Complete plugin specification - Database schema, API endpoints - WooCommerce integration - React components - Implementation timeline ## 2. Merged Addon Documentation ✅ Created ADDON_DEVELOPMENT_GUIDE.md (single source of truth): - Merged ADDON_INJECTION_GUIDE.md + ADDON_HOOK_SYSTEM.md - Two addon types: Route Injection + Hook System - Clear examples for each type - Best practices and troubleshooting - Deleted old documents ## 3. Tax Settings ✅ Frontend (admin-spa/src/routes/Settings/Tax.tsx): - Enable/disable tax calculation toggle - Display standard/reduced/zero tax rates - Show tax options (prices include tax, based on, display) - Link to WooCommerce for advanced config - Clean, simple UI Backend (includes/Api/TaxController.php): - GET /settings/tax - Fetch tax settings - POST /settings/tax/toggle - Enable/disable taxes - Fetches rates from woocommerce_tax_rates table - Clears WooCommerce cache on update ## 4. Advanced Local Pickup - TODO Will be simple: Admin adds multiple pickup locations ## Key Decisions: ✅ Hook system = No hardcoding, zero coupling ✅ Tax settings = Simple toggle + view, advanced in WC ✅ Single addon guide = One source of truth Next: Advanced Local Pickup locations
16 KiB
WooNooW Addon Development Guide
Version: 2.0.0
Last Updated: November 9, 2025
Status: Production Ready
📋 Table of Contents
- Overview
- Addon Types
- Quick Start
- SPA Route Injection
- Hook System Integration
- Component Development
- Best Practices
- Examples
- Troubleshooting
Overview
WooNooW provides two powerful addon systems:
1. SPA Route Injection (Admin UI)
- ✅ Register custom SPA routes
- ✅ Inject navigation menu items
- ✅ Add submenu items to existing sections
- ✅ Load React components dynamically
- ✅ Full isolation and safety
2. Hook System (Functional Extension)
- ✅ Extend OrderForm, ProductForm, etc.
- ✅ Add custom fields and validation
- ✅ Inject components at specific points
- ✅ Zero coupling with core
- ✅ WordPress-style filters and actions
Both systems work together seamlessly!
Addon Types
Type A: UI-Only Addon (Route Injection)
Use when: Adding new pages/sections to admin
Example: Reports, Analytics, Custom Dashboard
// Registers routes + navigation
add_filter('woonoow/spa_routes', ...);
add_filter('woonoow/nav_tree', ...);
Type B: Functional Addon (Hook System)
Use when: Extending existing functionality
Example: Indonesia Shipping, Custom Fields, Validation
// Registers hooks
addFilter('woonoow_order_form_after_shipping', ...);
addAction('woonoow_order_created', ...);
Type C: Full-Featured Addon (Both Systems)
Use when: Complex integration needed
Example: Subscriptions, Bookings, Memberships
// Backend: Routes + Hooks
add_filter('woonoow/spa_routes', ...);
add_filter('woonoow/nav_tree', ...);
// Frontend: Hook registration
addonLoader.register({
init: () => {
addFilter('woonoow_order_form_custom_sections', ...);
}
});
Quick Start
Step 1: Create Plugin File
<?php
/**
* Plugin Name: My WooNooW Addon
* Description: Extends WooNooW functionality
* Version: 1.0.0
* Requires: WooNooW 1.0.0+
*/
// 1. Register addon
add_filter('woonoow/addon_registry', function($addons) {
$addons['my-addon'] = [
'id' => 'my-addon',
'name' => 'My Addon',
'version' => '1.0.0',
'spa_bundle' => plugin_dir_url(__FILE__) . 'dist/addon.js',
'dependencies' => ['woocommerce' => '8.0'],
];
return $addons;
});
// 2. Register routes (optional - for UI pages)
add_filter('woonoow/spa_routes', function($routes) {
$routes[] = [
'path' => '/my-addon',
'component_url' => plugin_dir_url(__FILE__) . 'dist/MyPage.js',
'capability' => 'manage_woocommerce',
'title' => 'My Addon',
];
return $routes;
});
// 3. Add navigation (optional - for UI pages)
add_filter('woonoow/nav_tree', function($tree) {
$tree[] = [
'key' => 'my-addon',
'label' => 'My Addon',
'path' => '/my-addon',
'icon' => 'puzzle',
];
return $tree;
});
Step 2: Create Frontend Integration
// admin-spa/src/index.ts
import { addonLoader, addFilter } from '@woonoow/hooks';
addonLoader.register({
id: 'my-addon',
name: 'My Addon',
version: '1.0.0',
init: () => {
// Register hooks here
addFilter('woonoow_order_form_custom_sections', (content, formData, setFormData) => {
return (
<>
{content}
<MyCustomSection data={formData} onChange={setFormData} />
</>
);
});
}
});
Step 3: Build
npm run build
Done! Your addon is now integrated.
SPA Route Injection
Register Routes
add_filter('woonoow/spa_routes', function($routes) {
$base_url = plugin_dir_url(__FILE__) . 'dist/';
$routes[] = [
'path' => '/subscriptions',
'component_url' => $base_url . 'SubscriptionsList.js',
'capability' => 'manage_woocommerce',
'title' => 'Subscriptions',
];
$routes[] = [
'path' => '/subscriptions/:id',
'component_url' => $base_url . 'SubscriptionDetail.js',
'capability' => 'manage_woocommerce',
'title' => 'Subscription Detail',
];
return $routes;
});
Add Navigation
// Main menu item
add_filter('woonoow/nav_tree', function($tree) {
$tree[] = [
'key' => 'subscriptions',
'label' => __('Subscriptions', 'my-addon'),
'path' => '/subscriptions',
'icon' => 'repeat',
'children' => [
[
'label' => __('All Subscriptions', 'my-addon'),
'mode' => 'spa',
'path' => '/subscriptions',
],
[
'label' => __('New', 'my-addon'),
'mode' => 'spa',
'path' => '/subscriptions/new',
],
],
];
return $tree;
});
// Or inject into existing section
add_filter('woonoow/nav_tree/products/children', function($children) {
$children[] = [
'label' => __('Bundles', 'my-addon'),
'mode' => 'spa',
'path' => '/products/bundles',
];
return $children;
});
Hook System Integration
Available Hooks
Order Form Hooks
// Add fields after billing address
'woonoow_order_form_after_billing'
// Add fields after shipping address
'woonoow_order_form_after_shipping'
// Add custom shipping fields
'woonoow_order_form_shipping_fields'
// Add custom sections
'woonoow_order_form_custom_sections'
// Add validation rules
'woonoow_order_form_validation'
// Modify form data before render
'woonoow_order_form_data'
Action Hooks
// Before form submission
'woonoow_order_form_submit'
// After order created
'woonoow_order_created'
// After order updated
'woonoow_order_updated'
Hook Registration Example
import { addonLoader, addFilter, addAction } from '@woonoow/hooks';
addonLoader.register({
id: 'indonesia-shipping',
name: 'Indonesia Shipping',
version: '1.0.0',
init: () => {
// Filter: Add subdistrict selector
addFilter('woonoow_order_form_after_shipping', (content, formData, setFormData) => {
return (
<>
{content}
<SubdistrictSelector
value={formData.shipping?.subdistrict_id}
onChange={(id) => setFormData({
...formData,
shipping: { ...formData.shipping, subdistrict_id: id }
})}
/>
</>
);
});
// Filter: Add validation
addFilter('woonoow_order_form_validation', (errors, formData) => {
if (!formData.shipping?.subdistrict_id) {
errors.subdistrict = 'Subdistrict is required';
}
return errors;
});
// Action: Log when order created
addAction('woonoow_order_created', (orderId, orderData) => {
console.log('Order created:', orderId);
});
}
});
Hook System Benefits
✅ Zero Coupling
// WooNooW Core has no knowledge of your addon
{applyFilters('woonoow_order_form_after_shipping', null, formData, setFormData)}
// If addon exists: Returns your component
// If addon doesn't exist: Returns null
// No import, no error!
✅ Multiple Addons Can Hook
// Addon A
addFilter('woonoow_order_form_after_shipping', (content) => {
return <>{content}<AddonAFields /></>;
});
// Addon B
addFilter('woonoow_order_form_after_shipping', (content) => {
return <>{content}<AddonBFields /></>;
});
// Both render!
✅ Type Safety
addFilter<ReactNode, [OrderFormData, SetState<OrderFormData>]>(
'woonoow_order_form_after_shipping',
(content, formData, setFormData) => {
// TypeScript knows the types!
return <MyComponent />;
}
);
Component Development
Basic Component
// dist/MyPage.tsx
import React from 'react';
export default function MyPage() {
return (
<div className="space-y-6">
<div className="rounded-lg border p-6 bg-card">
<h2 className="text-xl font-semibold mb-2">My Addon</h2>
<p className="text-sm opacity-70">Welcome!</p>
</div>
</div>
);
}
Access WooNooW APIs
// Access REST API
const api = (window as any).WNW_API;
const response = await fetch(`${api.root}my-addon/endpoint`, {
headers: { 'X-WP-Nonce': api.nonce },
});
// Access store data
const store = (window as any).WNW_STORE;
console.log('Currency:', store.currency);
// Access site info
const wnw = (window as any).wnw;
console.log('Site Title:', wnw.siteTitle);
Use WooNooW Components
import { __ } from '@/lib/i18n';
import { formatMoney } from '@/lib/currency';
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
export default function MyPage() {
return (
<Card className="p-6">
<h2>{__('My Addon', 'my-addon')}</h2>
<p>{formatMoney(1234.56)}</p>
<Button>{__('Click Me', 'my-addon')}</Button>
</Card>
);
}
Build Configuration
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
lib: {
entry: 'src/index.ts',
name: 'MyAddon',
fileName: 'addon',
formats: ['es'],
},
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM',
},
},
},
},
});
Best Practices
✅ DO:
-
Use Hook System for Functional Extensions
// ✅ Good - No hardcoding addFilter('woonoow_order_form_after_shipping', ...); -
Use Route Injection for New Pages
// ✅ Good - Separate UI add_filter('woonoow/spa_routes', ...); -
Declare Dependencies
'dependencies' => ['woocommerce' => '8.0'] -
Check Capabilities
'capability' => 'manage_woocommerce' -
Internationalize Strings
'label' => __('My Addon', 'my-addon') -
Handle Errors Gracefully
try { await api.post(...); } catch (error) { toast.error('Failed to save'); }
❌ DON'T:
-
Don't Hardcode Addon Components in Core
// ❌ Bad - Breaks if addon not installed import { SubdistrictSelector } from 'addon'; <SubdistrictSelector /> // ✅ Good - Use hooks {applyFilters('woonoow_order_form_after_shipping', null)} -
Don't Skip Capability Checks
// ❌ Bad 'capability' => '' // ✅ Good 'capability' => 'manage_woocommerce' -
Don't Modify Core Navigation
// ❌ Bad unset($tree[0]); // ✅ Good $tree[] = ['key' => 'my-addon', ...];
Examples
Example 1: Simple UI Addon (Route Injection Only)
<?php
/**
* Plugin Name: WooNooW Reports
* Description: Custom reports page
*/
add_filter('woonoow/addon_registry', function($addons) {
$addons['reports'] = [
'id' => 'reports',
'name' => 'Reports',
'version' => '1.0.0',
];
return $addons;
});
add_filter('woonoow/spa_routes', function($routes) {
$routes[] = [
'path' => '/reports',
'component_url' => plugin_dir_url(__FILE__) . 'dist/Reports.js',
'title' => 'Reports',
];
return $routes;
});
add_filter('woonoow/nav_tree', function($tree) {
$tree[] = [
'key' => 'reports',
'label' => 'Reports',
'path' => '/reports',
'icon' => 'bar-chart',
];
return $tree;
});
Example 2: Functional Addon (Hook System Only)
// Indonesia Shipping - No UI pages, just extends OrderForm
import { addonLoader, addFilter } from '@woonoow/hooks';
import { SubdistrictSelector } from './components/SubdistrictSelector';
addonLoader.register({
id: 'indonesia-shipping',
name: 'Indonesia Shipping',
version: '1.0.0',
init: () => {
addFilter('woonoow_order_form_after_shipping', (content, formData, setFormData) => {
return (
<>
{content}
<div className="border rounded-lg p-4 mt-4">
<h3 className="font-medium mb-3">📍 Shipping Destination</h3>
<SubdistrictSelector
value={formData.shipping?.subdistrict_id}
onChange={(id) => setFormData({
...formData,
shipping: { ...formData.shipping, subdistrict_id: id }
})}
/>
</div>
</>
);
});
}
});
Example 3: Full-Featured Addon (Both Systems)
<?php
/**
* Plugin Name: WooNooW Subscriptions
* Description: Subscription management
*/
// Backend: Register addon + routes
add_filter('woonoow/addon_registry', function($addons) {
$addons['subscriptions'] = [
'id' => 'subscriptions',
'name' => 'Subscriptions',
'version' => '1.0.0',
'spa_bundle' => plugin_dir_url(__FILE__) . 'dist/addon.js',
];
return $addons;
});
add_filter('woonoow/spa_routes', function($routes) {
$routes[] = [
'path' => '/subscriptions',
'component_url' => plugin_dir_url(__FILE__) . 'dist/SubscriptionsList.js',
];
return $routes;
});
add_filter('woonoow/nav_tree', function($tree) {
$tree[] = [
'key' => 'subscriptions',
'label' => 'Subscriptions',
'path' => '/subscriptions',
'icon' => 'repeat',
];
return $tree;
});
// Frontend: Hook integration
import { addonLoader, addFilter } from '@woonoow/hooks';
addonLoader.register({
id: 'subscriptions',
name: 'Subscriptions',
version: '1.0.0',
init: () => {
// Add subscription fields to order form
addFilter('woonoow_order_form_custom_sections', (content, formData, setFormData) => {
return (
<>
{content}
<SubscriptionOptions data={formData} onChange={setFormData} />
</>
);
});
// Add subscription fields to product form
addFilter('woonoow_product_form_fields', (content, formData, setFormData) => {
return (
<>
{content}
<SubscriptionSettings data={formData} onChange={setFormData} />
</>
);
});
}
});
Troubleshooting
Addon Not Appearing?
- Check dependencies are met
- Verify capability requirements
- Check browser console for errors
- Flush caches:
?flush_wnw_cache=1
Route Not Loading?
- Verify
component_urlis correct - Check file exists and is accessible
- Look for JS errors in console
- Ensure component exports
default
Hook Not Firing?
- Check hook name is correct
- Verify addon is registered
- Check
window.WNW_ADDONSin console - Ensure
init()function runs
Component Not Rendering?
- Check for React errors in console
- Verify component returns valid JSX
- Check props are passed correctly
- Test component in isolation
Support & Resources
Documentation:
ADDON_INJECTION_GUIDE.md- SPA route injection (legacy)ADDON_HOOK_SYSTEM.md- Hook system details (legacy)BITESHIP_ADDON_SPEC.md- Indonesia shipping exampleSHIPPING_ADDON_RESEARCH.md- Shipping integration patterns
Code References:
includes/Compat/AddonRegistry.php- Addon registrationincludes/Compat/RouteRegistry.php- Route managementincludes/Compat/NavigationRegistry.php- Navigation buildingadmin-spa/src/lib/hooks.ts- Hook system implementationadmin-spa/src/App.tsx- Dynamic route loading
End of Guide
Version: 2.0.0
Last Updated: November 9, 2025
Status: ✅ Production Ready
This is the single source of truth for WooNooW addon development.