CRUD Order of Operations

Understanding the precise sequence of events during a data operation is crucial for debugging, extending functionality, and building reliable automations. This document outlines the step-by-step process the system follows for every Create, Update, and Delete action on a record.


Core Concepts

The system's logic is enhanced by two types of customizable actions that run during a CRUD operation: Record Triggers and Record Trigger Automations.

  • Record Triggers: These are Python scripts (.py files) that you write. They provide maximum flexibility for complex logic, calculations, or integrations that require custom code.
  • Record Trigger Automations: These are no-code/low-code workflows built directly in the UI. They are perfect for creating validation rules, field updates, and other process automations without writing any code.

Both Triggers and Automations run at specific points in the transaction, defined by a context. The primary contexts are:

  • Before: Runs before the record is saved to the database. This is your chance to validate data, modify the record's values, or prevent the operation from completing by raising an error.
  • After: Runs immediately after the record is successfully saved to the database. This is ideal for performing actions on related records, sending notifications, or initiating other processes that depend on the original record being committed.
  • Async: Runs in the background after the main transaction is complete and the response has been sent to the user. This is best for long-running processes, callouts to external systems, or any task that shouldn't slow down the user's experience.

Create Operation Flow

When a new record is created, the system follows a precise sequence to ensure data integrity, apply business logic, and execute any configured automations.

graph TD subgraph Initialization A["Start: Receive Create Request"] --> B{"Get Schema & Model"}; end subgraph "Validation & Permissions" B --> C{"Permission Checks"}; C --> D["System & Hardcoded Validations"]; end subgraph "Data Preparation" D --> E["Assign Defaults: ID, Dates, Owner"]; E --> F["Handle & Resolve Lookups"]; F --> G["Format Field Values"]; end subgraph "Before Triggers" G --> H["1. Execute 'Before Create' Record Triggers (Code)"]; H --> I["2. Execute 'Before Create' Automations (UI)"]; end subgraph "Database Operation" I --> J(("Save to Database")); end subgraph "After Triggers" J --> K["3. Execute 'After Create' Record Triggers (Code)"]; K --> L["4. Execute 'After Create' Automations (UI)"]; end subgraph "Post-Processing" L --> M["System Tasks: Cache, Git Sync, etc."]; end subgraph "Async Triggers" M --> N["5. Queue 'Async Create' Triggers & Automations"]; end N --> Z["End: Return Record"];

Create Operation Steps

  1. Initialization: The system receives the incoming data and loads the corresponding object schema and data model.
  2. Permission Checks: It verifies that the current user has the necessary object-level and field-level permissions to create the record.
  3. System Validation: It runs internal validations (record_system_validations) and any hardcoded checks (hard_record_checks) specific to the object type.
  4. Data Hydration: The system enriches the data by:
    • Assigning a new UUID (id).
    • Setting default values for created_date, modified_date, created_by, and modified_by.
    • Setting the owner if ownership is enabled for the object.
  5. Lookup Resolution: It processes any lookup fields, resolving record IDs or names into structured Lookup objects.
  6. Field Formatting: Individual fields are validated and formatted based on their data type (e.g., phone, datetime, address).
  7. Execute "Before Create" Triggers: It runs all active Record Triggers (Python code) for the 'Before Create' context, in their specified order. The data can be modified by these scripts.
  8. Execute "Before Create" Automations: It runs all active Record Trigger Automations (UI workflows) for the 'Before Create' context. The data can also be modified here.
  9. Database Insert: The final, validated, and modified record is inserted into the database.
  10. Execute "After Create" Triggers: It runs all 'After Create' Record Triggers. These triggers receive the saved data but cannot modify it further. They are used for side effects.
  11. Execute "After Create" Automations: It runs all 'After Create' Record Trigger Automations.
  12. Post-Processing: The system performs final tasks like invalidating caches, syncing records to Git if enabled, and updating role hierarchies.
  13. Queue "Async" Operations: All 'Async Create' Record Triggers and Automations are added to a background queue to be processed separately.
  14. Return Response: The newly created record is returned to the client.

Update Operation Flow

Updating a record follows a similar path to creation, but with the critical addition of fetching the original record data to enable comparisons and preserve existing values.

graph TD subgraph Initialization A["Start: Receive Update Request"] --> B{"Get Schema & Model"}; end subgraph "Validation & Permissions" B --> C["Fetch Existing Record (Old Data)"]; C --> D{"Permission Checks"}; D --> E["System & Hardcoded Validations"]; end subgraph "Data Preparation" E --> F["Merge New & Old Data"]; F --> G["Handle & Resolve Lookups"]; G --> H["Format & Validate Field Values"]; end subgraph "Before Triggers" H --> I["1. Execute 'Before Update' Record Triggers (Code)"]; I --> J["2. Execute 'Before Update' Automations (UI)"]; end subgraph "Database Operation" J --> K(("Save to Database")); end subgraph "After Triggers" K --> L["3. Execute 'After Update' Record Triggers (Code)"]; L --> M["4. Execute 'After Update' Automations (UI)"]; end subgraph "Post-Processing" M --> N["System Tasks: Cache, Git Sync, etc."]; end subgraph "Async Triggers" N --> O["5. Queue 'Async Update' Triggers & Automations"]; end O --> Z["End: Return Record"];

Update Operation Steps

  1. Initialization: The system loads the object schema and data model based on the request.
  2. Fetch Old Data: The current state of the record is retrieved from the database. This old_data is available to all subsequent triggers and automations.
  3. Permission Checks: It verifies object-level and field-level permissions for the update. The field-level check specifically compares old_data to the incoming new_data to ensure the user is allowed to make the requested changes.
  4. System Validation: It runs internal and hardcoded validations.
  5. Data Merging: The system merges the incoming data with the old data. Any fields not included in the update request retain their original values. The modified_date and modified_by fields are updated.
  6. Lookup Resolution & Formatting: Lookups are resolved and fields are formatted, just as in the create process.
  7. Execute "Before Update" Triggers: Runs all 'Before Update' Record Triggers. These scripts receive both new_data and old_data and can modify the new_data payload before it's saved.
  8. Execute "Before Update" Automations: Runs all 'Before Update' Record Trigger Automations.
  9. Database Update: The final version of the record is updated in the database.
  10. Execute "After Update" Triggers: Runs all 'After Update' Record Triggers for side effects.
  11. Execute "After Update" Automations: Runs all 'After Update' Record Trigger Automations.
  12. Post-Processing: The system performs tasks like cache invalidation and Git sync.
  13. Queue "Async" Operations: All 'Async Update' Record Triggers and Automations are queued for background processing.
  14. Return Response: The updated record is returned to the client.

Delete Operation Flow

The delete operation is the most straightforward but still allows for intervention and post-processing through triggers and automations.

graph TD subgraph Initialization A["Start: Receive Delete Request"] --> B{"Get Schema & Model"}; end subgraph "Validation & Permissions" B --> C["Fetch Existing Record (Old Data)"]; C --> D{"Permission Checks"}; D --> E["System & Hardcoded Validations"]; end subgraph "Before Triggers" E --> F["1. Execute 'Before Delete' Record Triggers (Code)"]; F --> G["2. Execute 'Before Delete' Automations (UI)"]; end subgraph "Database Operation" G --> H(("Delete from Database")); end subgraph "After Triggers" H --> I["3. Execute 'After Delete' Record Triggers (Code)"]; I --> J["4. Execute 'After Delete' Automations (UI)"]; end subgraph "Post-Processing" J --> K["System Tasks: Cache, Git Sync, etc."]; end subgraph "Async Triggers" K --> L["5. Queue 'Async Delete' Triggers & Automations"]; end L --> Z["End: Return Success"];

Delete Operation Steps

  1. Initialization: The system loads the relevant object schema and data model.
  2. Fetch Record: The record slated for deletion is retrieved from the database. This data is then available to triggers and automations as old_data.
  3. Permission Checks: It verifies that the user has permission to delete the record.
  4. System Validation: It runs any relevant hardcoded checks.
  5. Execute "Before Delete" Triggers: Runs all 'Before Delete' Record Triggers. These scripts can prevent the deletion by raising an error.
  6. Execute "Before Delete" Automations: Runs all 'Before Delete' Record Trigger Automations.
  7. Database Deletion: The record is permanently removed from the database.
  8. Execute "After Delete" Triggers: Runs all 'After Delete' Record Triggers. These are commonly used to clean up related data, log the deletion, or notify other systems.
  9. Execute "After Delete" Automations: Runs all 'After Delete' Record Trigger Automations.
  10. Post-Processing: The system performs final tasks like cache invalidation and Git sync.
  11. Queue "Async" Operations: All 'Async Delete' Record Triggers and Automations are queued for background processing.
  12. Return Response: A success status is returned to the client.