Skip to content

AppML Events — Handling Custom Business Logic with Event Handlers

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about AppML Events. We cover key concepts, practical examples, and best practices to help you master this topic.

AppML events let you inject custom logic at key points in the data lifecycle. You can validate data beyond model constraints, trigger notifications, update related records, and integrate with external systems.

What You'll Learn

You will write event handlers for before-save, after-save, before-delete, and after-delete events, and use them for custom validation, data transformation, and side effects.

Why It Matters

Real applications need more than standard CRUD. Events let you add business rules without leaving AppML. You avoid writing separate API endpoints while keeping your custom logic organized and maintainable.

Real-World Use

DodaZIP uses an after-save event on the compression profile model to automatically deploy configuration changes to production servers. The event handler triggers a webhook that restarts the compression service.

flowchart LR
    A[User Saves Record] --> B[Before Save Events]
    B --> C[Custom Validation]
    B --> D[Data Transformation]
    C --> E{Valid?}
    E -->|No| F[Reject with Error]
    E -->|Yes| G[Save to Database]
    G --> H[After Save Events]
    H --> I[Send Notification]
    H --> J[Update Related Records]
    H --> K[Trigger Webhook]
    style A fill:#1e293b,color:#fff
    style G fill:#0f172a,color:#fff

Event Types

AppML fires events at specific points during the request lifecycle.

Before-save events fire before the record is written to the database. Use them for custom validation and data modification. After-save events fire after the record is saved. Use them for notifications, logging, and side effects.

Before-delete events fire before a record is deleted. After-delete events fire after deletion.

Writing a Before-Save Handler

Create a handler file in the events directory of your AppML project.

// events/products.js
module.exports = {
  beforeSave: function(data, context) {
    if (data.price < 0) {
      throw new Error('Price cannot be negative');
    }
    data.slug = data.name.toLowerCase().replace(/\s+/g, '-');
    data.updated_at = new Date().toISOString();
    return data;
  }
};

Expected output: Before saving a product, the handler validates the price, generates a URL slug from the name, and sets the updated timestamp.

Register the handler in the model:

<table name="products">
  <event type="before-save" handler="events/products.js"/>
  ...
</table>

Writing an After-Save Handler

Send notifications or update related data after a record is saved.

// events/orders.js
module.exports = {
  afterSave: function(data, context) {
    const db = context.database;
    db.query(
      'UPDATE customers SET last_order_date = ? WHERE id = ?',
      [data.order_date, data.customer_id]
    );
    context.notification.send({
      to: 'admin@example.com',
      subject: 'New order placed',
      body: `Order #${data.id} for $${data.total}`
    });
    return true;
  }
};

Expected output: After an order is saved, the customer's last_order_date is updated and an admin notification email is sent.

Data Transformation in Events

Transform data before it reaches the database.

// events/users.js
module.exports = {
  beforeSave: function(data) {
    if (data.password) {
      const crypto = require('crypto');
      const salt = crypto.randomBytes(16).toString('hex');
      const hash = crypto.pbkdf2Sync(data.password, salt, 1000, 64, 'sha512').toString('hex');
      data.password_hash = hash;
      data.password_salt = salt;
      delete data.password;
    }
    if (data.email) {
      data.email = data.email.toLowerCase().trim();
    }
    return data;
  }
};

Expected output: Before saving a user, the password is hashed with a salt, and the email is normalized to lowercase.

Conditional Event Execution

Execute events only under specific conditions.

// events/inventory.js
module.exports = {
  beforeSave: function(data) {
    if (data.quantity < data.reorder_point) {
      context.trigger('reorder', {
        product_id: data.id,
        quantity: data.reorder_quantity
      });
    }
    return data;
  },

  reorder: function(params) {
    const http = require('http');
    http.post('https://supplier-api.example.com/reorder', params);
  }
};

Expected output: When inventory quantity drops below the reorder point during a save operation, an automatic reorder request is sent to the supplier API.

Chaining Multiple Events

Multiple handlers can run on the same event. They execute in the order they are registered.

<table name="orders">
  <event type="after-save" handler="events/logging.js"/>
  <event type="after-save" handler="events/notifications.js"/>
  <event type="after-save" handler="events/inventory-update.js"/>
</table>

Expected output: After saving an order, the log entry is written first, then the notification is sent, then inventory is updated.

Common Mistakes

  1. Throwing unhandled errors in events: An error in a before-save handler prevents the save from completing. Use try-catch blocks and return meaningful error messages.

  2. Making synchronous HTTP calls in events: Network requests in before-save handlers block the save operation. Use after-save events for external calls.

  3. Forgetting to return data from before-save handlers: If you do not return the modified data object, the save proceeds with unmodified data.

  4. Creating infinite loops: An after-save handler that updates the same table triggers another after-save event. Use condition checks to prevent Recursion.

  5. Not handling async operations properly: Event handlers run synchronously by default. Use callbacks or promises correctly to avoid race conditions.

Practice Questions

  1. What is the difference between before-save and after-save events?

Before-save fires before the database write and can modify or reject data. After-save fires after the write and is used for side effects.

  1. How do you reject a save operation from an event handler?

Throw an error with a descriptive message. The save is canceled and the error is displayed to the user.

  1. Can you have multiple handlers for the same event on one table?

Yes. Multiple event elements with different handlers execute in the order they are defined.

  1. What context properties are available in event handlers?

The context object provides access to the database, notification system, request data, and configuration.

  1. Why should HTTP calls go in after-save handlers instead of before-save?

Network calls are slow and can fail. Before-save handlers should be fast because they block the save operation.

Challenge

Create an event handler for a project management model. When a task status changes to completed, automatically update the project completion percentage and send a notification to the project manager. Prevent recursion by checking if the percentage already matches.

Frequently Asked Questions

Can event handlers access the current user's session?

Yes. The context object includes user information from the current session, including user ID, role, and permissions.

Are event handlers written in JavaScript or PHP?

AppML supports both. JavaScript handlers run on Node.js environments. PHP handlers run on PHP environments. Use the appropriate syntax for your runtime.

Can I test event handlers without triggering them through the UI?

Yes. AppML provides a CLI command to test event handlers with mock data. Run appml test-event products beforeSave with sample JSON data.

Do events work with XML and JSON data sources?

Yes. Events are data-source agnostic. The same event system works with databases, XML files, and JSON APIs.

What happens if an after-save event fails?

The record is already saved. The error is logged, and the user sees a warning. The after-save failure does not roll back the save.

Mini Project

Build an order processing system with events. Create a before-save validator that checks inventory availability, an after-save handler that sends order confirmation, and another after-save handler that updates inventory quantities. Test with sufficient and insufficient stock.

What's Next

Continue to AppML Filters to learn advanced filtering techniques for complex data queries.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro