Skip to main content

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

1
Identify the Event
2
Determine which event you want to listen to (e.g., product_created, order_placed).
3
Create the Subscriber Directory
4
Create a directory matching the event name:
5
mkdir -p extensions/my-extension/src/subscribers/product_created
6
Create the Handler
7
Create a JavaScript/TypeScript file with your handler:
8
export default function consoleLog(data) {
  console.log('Product Created:', data);
}
9
The filename can be anything descriptive. Multiple files in the same directory will all execute for the event.
10
Build and Test
11
Run the build command:
12
npm run build
13
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 NameWhen TriggeredData Received
product_createdNew product createdProduct object
product_updatedProduct modifiedUpdated product object
product_deletedProduct removedProduct ID

Order Events

Event NameWhen TriggeredData Received
order_placedNew order createdOrder object
order_updatedOrder status changedUpdated order object
order_cancelledOrder cancelledOrder object
order_shippedOrder shippedOrder + shipping info

Customer Events

Event NameWhen TriggeredData Received
customer_registeredNew customer accountCustomer object
customer_updatedCustomer profile changedUpdated customer object
customer_logged_inCustomer loginCustomer 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

  1. Verify directory name matches event name exactly
  2. Check subscriber exports a default function
  3. Run npm run build to rebuild
  4. Check extension is enabled in config/default.json
  5. Verify the event is actually being emitted

Errors in Subscriber

  1. Add try-catch blocks to identify errors
  2. Check console logs for error messages
  3. Verify event data structure matches expectations
  4. Test subscriber in isolation

Performance Issues

  1. Move heavy operations to background jobs
  2. Implement queuing for external API calls
  3. Use caching where appropriate
  4. Profile subscriber execution time

Next Steps

Cron Jobs

Schedule background tasks

API Endpoints

Create custom API endpoints

Build docs developers (and LLMs) love