Documentation Index Fetch the complete documentation index at: https://mintlify.com/Antony-Figueroa/my-evershop-app/llms.txt
Use this file to discover all available pages before exploring further.
Event subscribers allow your extensions to react to system events like product creation, order placement, customer registration, and more. They’re perfect for implementing custom business logic, sending notifications, logging, and integrating with external services.
Overview
Event subscribers in EverShop:
React to system events without modifying core code
Execute asynchronously to avoid blocking main operations
Can be chained - multiple subscribers can listen to the same event
Are easy to test and maintain
Support both sync and async operations
How Events Work
When an action occurs in EverShop (e.g., a product is created), the system emits an event. Subscribers listening to that event are automatically executed.
Event Emitted → Subscribers Executed → Custom Logic Runs
Directory Structure
Event subscribers are organized by event name:
extensions/[extension-name]/
└── src/
└── subscribers/
└── [event_name]/
├── handler1.js
├── handler2.js
└── handler3.js
The directory name must match the event name exactly.
Creating Your First Subscriber
Determine which event you want to listen to (e.g., product_created, order_placed).
Create the Subscriber Directory
Create a directory matching the event name:
mkdir -p extensions/my-extension/src/subscribers/product_created
Create a JavaScript/TypeScript file with your handler:
export default function consoleLog ( data ) {
console . log ( 'Product Created:' , data );
}
The filename can be anything descriptive. Multiple files in the same directory will all execute for the event.
Now when a product is created, your subscriber will execute.
Real-World Example
Here’s the actual subscriber from the sample extension:
extensions/sample/src/subscribers/product_created/consoleLog.js
export default function consoleLog ( data ) {
console . log ( 'Product Created:' , data );
}
This simple subscriber logs product data to the console whenever a new product is created.
Common Events
Here are common events you can subscribe to:
Product Events
Event Name When Triggered Data Received product_createdNew product created Product object product_updatedProduct modified Updated product object product_deletedProduct removed Product ID
Order Events
Event Name When Triggered Data Received order_placedNew order created Order object order_updatedOrder status changed Updated order object order_cancelledOrder cancelled Order object order_shippedOrder shipped Order + shipping info
Customer Events
Event Name When Triggered Data Received customer_registeredNew customer account Customer object customer_updatedCustomer profile changed Updated customer object customer_logged_inCustomer login Customer object
Advanced Examples
Send Email on Order
src/subscribers/order_placed/sendConfirmationEmail.js
export default async function sendConfirmationEmail ( data ) {
const { order , customer } = data ;
try {
// Send email using your email service
await emailService . send ({
to: customer . email ,
subject: `Order Confirmation # ${ order . orderNumber } ` ,
template: 'order-confirmation' ,
data: {
customerName: customer . fullName ,
orderNumber: order . orderNumber ,
orderTotal: order . total ,
items: order . items
}
});
console . log ( `Confirmation email sent to ${ customer . email } ` );
} catch ( error ) {
console . error ( 'Failed to send confirmation email:' , error );
}
}
Update Inventory
src/subscribers/order_placed/updateInventory.js
export default async function updateInventory ( data ) {
const { order } = data ;
try {
for ( const item of order . items ) {
// Update inventory in external system
await inventoryService . decreaseStock ({
productId: item . productId ,
quantity: item . quantity ,
warehouseId: item . warehouseId
});
console . log ( `Decreased stock for product ${ item . productId } by ${ item . quantity } ` );
}
} catch ( error ) {
console . error ( 'Failed to update inventory:' , error );
// Consider implementing retry logic or alerting
}
}
Log to Database
src/subscribers/product_updated/logChange.ts
import { pool } from '@evershop/evershop/lib/postgres/connection' ;
export default async function logChange ( data ) {
const { product , previousData , userId } = data ;
try {
await pool . query (
`INSERT INTO product_audit_log
(product_id, user_id, changes, created_at)
VALUES ($1, $2, $3, NOW())` ,
[
product . productId ,
userId ,
JSON . stringify ({
before: previousData ,
after: product
})
]
);
console . log ( `Logged changes to product ${ product . productId } ` );
} catch ( error ) {
console . error ( 'Failed to log product changes:' , error );
}
}
Webhook Integration
src/subscribers/order_placed/notifyWarehouse.js
import fetch from 'node-fetch' ;
export default async function notifyWarehouse ( data ) {
const { order } = data ;
try {
const response = await fetch ( 'https://warehouse-api.example.com/orders' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'Authorization' : `Bearer ${ process . env . WAREHOUSE_API_KEY } `
},
body: JSON . stringify ({
orderNumber: order . orderNumber ,
items: order . items . map ( item => ({
sku: item . sku ,
quantity: item . quantity ,
productName: item . productName
})),
shippingAddress: order . shippingAddress ,
priority: order . shippingMethod === 'express' ? 'high' : 'normal'
})
});
if ( ! response . ok ) {
throw new Error ( `Warehouse API returned ${ response . status } ` );
}
console . log ( `Order ${ order . orderNumber } sent to warehouse` );
} catch ( error ) {
console . error ( 'Failed to notify warehouse:' , error );
// Consider implementing a retry queue
}
}
Customer Segmentation
src/subscribers/customer_registered/addToSegment.js
export default async function addToSegment ( data ) {
const { customer } = data ;
try {
// Determine customer segment based on data
let segment = 'regular' ;
if ( customer . email . endsWith ( '.edu' )) {
segment = 'student' ;
} else if ( customer . companyName ) {
segment = 'business' ;
}
// Save segment to database or CRM
await crmService . updateCustomer ( customer . customerId , {
segment ,
registrationDate: new Date (),
source: customer . source || 'website'
});
console . log ( `Customer ${ customer . email } added to ${ segment } segment` );
} catch ( error ) {
console . error ( 'Failed to segment customer:' , error );
}
}
Multiple Subscribers per Event
You can have multiple subscribers for the same event:
src/subscribers/order_placed/
├── sendConfirmationEmail.js # Send email to customer
├── notifyWarehouse.js # Alert warehouse
├── updateInventory.js # Decrease stock
├── logToDatabase.js # Audit log
└── sendSlackNotification.js # Alert team
All subscribers will execute when the event is triggered.
Subscribers execute asynchronously and in no guaranteed order. Don’t rely on execution sequence.
Async/Await Support
Subscribers fully support async operations:
export default async function mySubscriber ( data ) {
// Await asynchronous operations
await sendEmail ( data );
await updateDatabase ( data );
await callExternalAPI ( data );
}
Error Handling
Always implement proper error handling:
export default async function mySubscriber ( data ) {
try {
// Your logic here
await someAsyncOperation ( data );
} catch ( error ) {
console . error ( 'Subscriber error:' , error );
// Log to error tracking service
errorTracker . log ( error , { context: 'order_placed subscriber' , data });
// Don't throw - let other subscribers continue
}
}
Best Practice : Log errors but don’t throw them. Throwing errors can prevent other subscribers from executing.
Data Access Patterns
Destructure Event Data
export default function mySubscriber ( data ) {
const { product , user , metadata } = data ;
// Use destructured data
console . log ( ` ${ user . name } created product: ${ product . name } ` );
}
Validate Data
export default function mySubscriber ( data ) {
if ( ! data || ! data . order ) {
console . error ( 'Invalid event data received' );
return ;
}
const { order } = data ;
// Continue with valid data
}
Testing Subscribers
Manual Testing
Trigger events manually in development:
// In your code or test file
import { emit } from '@evershop/evershop/lib/event' ;
// Emit an event
await emit ( 'product_created' , {
product: {
productId: 123 ,
name: 'Test Product' ,
price: 29.99
},
user: {
userId: 1 ,
email: 'admin@example.com'
}
});
Unit Testing
import mySubscriber from './mySubscriber' ;
test ( 'subscriber handles product_created event' , async () => {
const mockData = {
product: { productId: 123 , name: 'Test' },
user: { userId: 1 , email: 'test@example.com' }
};
// Test the subscriber
await mySubscriber ( mockData );
// Assert expected behavior
expect ( emailService . send ). toHaveBeenCalledWith (
expect . objectContaining ({ to: 'test@example.com' })
);
});
Best Practices
Keep Subscribers Focused : Each subscriber should do one thing well. Create multiple subscribers instead of one complex one.
Error Handling - Always wrap logic in try-catch blocks
Logging - Log both successes and failures for debugging
Async Operations - Use async/await for cleaner code
Validation - Validate event data before processing
Idempotency - Design subscribers to handle duplicate events
Performance - Avoid blocking operations; use queues for heavy tasks
Testing - Write unit tests for business logic
Common Use Cases
Email Notifications
// Welcome email
src / subscribers / customer_registered / sendWelcomeEmail . js
// Order confirmation
src / subscribers / order_placed / sendOrderConfirmation . js
// Shipping notification
src / subscribers / order_shipped / sendShippingNotification . js
External Integrations
// Sync to CRM
src / subscribers / customer_registered / syncToCRM . js
// Update ERP
src / subscribers / order_placed / syncToERP . js
// Analytics tracking
src / subscribers / order_placed / trackInAnalytics . js
Internal Operations
// Audit logging
src / subscribers / product_updated / auditLog . js
// Cache invalidation
src / subscribers / product_updated / invalidateCache . js
// Search index update
src / subscribers / product_created / updateSearchIndex . js
Troubleshooting
Subscriber Not Executing
Verify directory name matches event name exactly
Check subscriber exports a default function
Run npm run build to rebuild
Check extension is enabled in config/default.json
Verify the event is actually being emitted
Errors in Subscriber
Add try-catch blocks to identify errors
Check console logs for error messages
Verify event data structure matches expectations
Test subscriber in isolation
Move heavy operations to background jobs
Implement queuing for external API calls
Use caching where appropriate
Profile subscriber execution time
Next Steps
Cron Jobs Schedule background tasks
API Endpoints Create custom API endpoints