> ## 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.

# Creating Event Subscribers

> Learn how to create event subscribers to react to system events in EverShop

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

<Steps>
  ### Identify the Event

  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:

  ```bash theme={null}
  mkdir -p extensions/my-extension/src/subscribers/product_created
  ```

  ### Create the Handler

  Create a JavaScript/TypeScript file with your handler:

  ```javascript title="src/subscribers/product_created/consoleLog.js" theme={null}
  export default function consoleLog(data) {
    console.log('Product Created:', data);
  }
  ```

  <Note>
    The filename can be anything descriptive. Multiple files in the same directory will all execute for the event.
  </Note>

  ### Build and Test

  Run the build command:

  ```bash theme={null}
  npm run build
  ```

  Now when a product is created, your subscriber will execute.
</Steps>

## Real-World Example

Here's the actual subscriber from the sample extension:

```javascript title="extensions/sample/src/subscribers/product_created/consoleLog.js" theme={null}
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_created` | New product created | Product object         |
| `product_updated` | Product modified    | Updated product object |
| `product_deleted` | Product removed     | Product ID             |

### Order Events

| Event Name        | When Triggered       | Data Received         |
| ----------------- | -------------------- | --------------------- |
| `order_placed`    | New order created    | Order object          |
| `order_updated`   | Order status changed | Updated order object  |
| `order_cancelled` | Order cancelled      | Order object          |
| `order_shipped`   | Order shipped        | Order + shipping info |

### Customer Events

| Event Name            | When Triggered           | Data Received           |
| --------------------- | ------------------------ | ----------------------- |
| `customer_registered` | New customer account     | Customer object         |
| `customer_updated`    | Customer profile changed | Updated customer object |
| `customer_logged_in`  | Customer login           | Customer object         |

## Advanced Examples

### Send Email on Order

```javascript title="src/subscribers/order_placed/sendConfirmationEmail.js" theme={null}
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

```javascript title="src/subscribers/order_placed/updateInventory.js" theme={null}
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

```typescript title="src/subscribers/product_updated/logChange.ts" theme={null}
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

```javascript title="src/subscribers/order_placed/notifyWarehouse.js" theme={null}
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

```javascript title="src/subscribers/customer_registered/addToSegment.js" theme={null}
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.

<Warning>
  Subscribers execute asynchronously and in no guaranteed order. Don't rely on execution sequence.
</Warning>

## Async/Await Support

Subscribers fully support async operations:

```javascript theme={null}
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:

```javascript theme={null}
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
  }
}
```

<Tip>
  **Best Practice**: Log errors but don't throw them. Throwing errors can prevent other subscribers from executing.
</Tip>

## Data Access Patterns

### Destructure Event Data

```javascript theme={null}
export default function mySubscriber(data) {
  const { product, user, metadata } = data;
  
  // Use destructured data
  console.log(`${user.name} created product: ${product.name}`);
}
```

### Validate Data

```javascript theme={null}
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:

```javascript theme={null}
// 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

```javascript theme={null}
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

<Tip>
  **Keep Subscribers Focused**: Each subscriber should do one thing well. Create multiple subscribers instead of one complex one.
</Tip>

* **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

```javascript theme={null}
// 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

```javascript theme={null}
// 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

```javascript theme={null}
// 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

<CardGroup cols={2}>
  <Card title="Cron Jobs" icon="clock" href="/guides/cron-jobs">
    Schedule background tasks
  </Card>

  <Card title="API Endpoints" icon="globe" href="/guides/api-endpoints">
    Create custom API endpoints
  </Card>
</CardGroup>
