Back to Abp

Interceptors

docs/en/low-code/interceptors.md

10.5.09.3 KB
Original Source
json
//[doc-seo]
{
    "Description": "Add custom business logic to dynamic entity CRUD operations using Interceptors in the ABP Low-Code System. Validate, transform, and react to data changes with JavaScript."
}

Interceptors

Preview: Interceptors and their JavaScript context are preview extension points. Script context members, validation behavior, and lifecycle hooks may change before general availability.

Use designer actions and descriptor metadata for standard low-code behavior first. Interceptors are an advanced extension point for adding JavaScript lifecycle logic when the generated CRUD flow needs validation, transformation, or replacement behavior.

Interceptors allow you to run custom JavaScript code before, after, or instead of Create, Update, and Delete operations on dynamic entities.

Interceptor Types

CommandTypeWhen Executed
CreatePreBefore entity creation — validation, default values
CreatePostAfter entity creation — notifications, related data
CreateReplaceInstead of entity creation — must return the new entity's Id (see below)
UpdatePreBefore entity update — validation, authorization
UpdatePostAfter entity update — sync, notifications
UpdateReplaceInstead of entity update — no return value needed
DeletePreBefore entity deletion — dependency checks
DeletePostAfter entity deletion — cleanup
DeleteReplaceInstead of entity deletion — no return value needed

Defining Interceptors in JSON Descriptors

The designer stores entity interceptors in the entity interceptors array:

json
{
  "name": "LowCodeDemo.Customers.Customer",
  "interceptors": [
    {
      "commandName": "Create",
      "type": "Pre",
      "javascript": "if (!args.getValue('Name')) {\n  globalError = 'Name is required.';\n}"
    }
  ]
}

Interceptor Descriptor

FieldTypeDescription
commandNamestring"Create", "Update", or "Delete"
typestring"Pre", "Post", or "Replace"
javascriptstringJavaScript code to execute

Defining Interceptors with Attributes

Use the [DynamicEntityCommandInterceptor] attribute on a C# class:

csharp
[DynamicEntity]
[DynamicEntityCommandInterceptor(
    "Create",
    InterceptorType.Pre,
    "if(!context.commandArgs.data['Name']) { globalError = 'Name is required!'; }"
)]
[DynamicEntityCommandInterceptor(
    "Create",
    InterceptorType.Post,
    "context.log('Entity created: ' + context.commandArgs.entityId);"
)]
public class Organization
{
    public string Name { get; set; }
}

The Name parameter must be one of: "Create", "Update", or "Delete". The InterceptorType can be Pre, Post, or Replace. When Replace is used, the default database operation is completely skipped and only your JavaScript handler executes. Multiple interceptors can be added to the same class (AllowMultiple = true).

Defining Interceptors with Fluent API

Use the Interceptors list on an EntityDescriptor to add interceptors programmatically in startup configuration:

csharp
AbpDynamicEntityConfig.EntityConfigurations.Configure(
    "MyApp.Organizations.Organization",
    entity =>
    {
        entity.Interceptors.Add(new CommandInterceptorDescriptor("Create")
        {
            Type = InterceptorType.Pre,
            Javascript = "if(!context.commandArgs.data['Name']) { globalError = 'Name is required!'; }"
        });

        entity.Interceptors.Add(new CommandInterceptorDescriptor("Delete")
        {
            Type = InterceptorType.Post,
            Javascript = "context.log('Deleted: ' + context.commandArgs.entityId);"
        });
    }
);

See Attributes & Fluent API for more details on Fluent API configuration.

JavaScript Context

Inside interceptor scripts, you have access to:

context.commandArgs

Property / MethodTypeDescription
dataobjectEntity data dictionary (for Create/Update)
entityIdstringEntity ID (for Update/Delete)
commandNamestringCommand name ("Create", "Update", or "Delete")
entityNamestringFull entity name
getValue(name)functionGet a property value
setValue(name, value)functionSet a property value (Pre-interceptors only)
hasValue(name)functionCheck if a property exists in the data
removeValue(name)functionRemove a property from the data

context.currentUser

Property / MethodTypeDescription
isAuthenticatedboolWhether user is logged in
idstringUser ID
userNamestringUsername
emailstringEmail address
namestringFirst name
surNamestringLast name
phoneNumberstringPhone number
phoneNumberVerifiedboolWhether phone is verified
emailVerifiedboolWhether email is verified
tenantIdstringTenant ID (for multi-tenant apps)
rolesstring[]User's role names
isInRole(roleName)functionCheck if user has a specific role

context.emailSender

Property / MethodDescription
isAvailableWhether the email sender is configured and available
sendAsync(to, subject, body)Send a plain-text email
sendAsync(from, to, subject, body)Send a plain-text email with an explicit sender
sendHtmlAsync(to, subject, htmlBody)Send an HTML email
sendHtmlAsync(from, to, subject, htmlBody)Send an HTML email with an explicit sender
queueAsync(to, subject, body)Queue a plain-text email
queueAsync(from, to, subject, body)Queue a plain-text email with an explicit sender
queueHtmlAsync(to, subject, htmlBody)Queue an HTML email
queueHtmlAsync(from, to, subject, htmlBody)Queue an HTML email with an explicit sender

Logging

MethodDescription
context.log(message)Log an informational message
context.logWarning(message)Log a warning message
context.logError(message)Log an error message

Use these methods instead of console.log (which is blocked in the sandbox).

db (Database API)

Full access to the Scripting API for querying and mutating data.

Interceptors can also use common scripting services such as user, tenant, auth, settings, features, config, http, events, jobs, blob, encryption, textTemplating, files, images, and attachments when they are enabled by the scripting capability profile.

globalError

Set this variable to a string to abort the operation and return an error:

javascript
globalError = 'Cannot delete this entity!';

Examples

Pre-Create: Validation

json
{
  "commandName": "Create",
  "type": "Pre",
  "javascript": "if(!context.commandArgs.data['Name']) {\n  globalError = 'Organization name is required!';\n}"
}

Post-Create: Email Notification

json
{
  "commandName": "Create",
  "type": "Post",
  "javascript": "if(context.currentUser.isAuthenticated && context.emailSender) {\n  await context.emailSender.sendAsync(\n    context.currentUser.email,\n    'New Order Created',\n    'Order total: $' + context.commandArgs.data['TotalAmount']\n  );\n}"
}

Pre-Update: Role-Based Authorization

json
{
  "commandName": "Update",
  "type": "Pre",
  "javascript": "if(context.commandArgs.data['IsDelivered']) {\n  if(!context.currentUser.roles.includes('admin')) {\n    globalError = 'Only administrators can mark orders as delivered!';\n  }\n}"
}

Pre-Delete: Business Rule Check

json
{
  "commandName": "Delete",
  "type": "Pre",
  "javascript": "var project = await db.get('LowCodeDemo.Projects.Project', context.commandArgs.entityId);\nif(project.Budget > 100000) {\n  globalError = 'Cannot delete high-budget projects!';\n}"
}

Pre-Update: Negative Value Check

json
{
  "commandName": "Update",
  "type": "Pre",
  "javascript": "if(context.commandArgs.data['Quantity'] < 0) {\n  globalError = 'Quantity cannot be negative!';\n}"
}

Replace-Create: Custom Insert Logic

When you need to completely replace the default create operation with custom logic:

json
{
  "commandName": "Create",
  "type": "Replace",
  "javascript": "var data = context.commandArgs.data;\ndata['Code'] = 'PRD-' + Date.now();\nvar result = await db.insert('LowCodeDemo.Products.Product', data);\ncontext.log('Product created with custom code: ' + data['Code']);\nreturn result.Id;"
}

Important: Replace-Create interceptors must return the new entity's Id (Guid). The system uses this value to fetch and return the created entity. Use return result.Id; after db.insert(...).

Replace-Update and Replace-Delete interceptors do not need to return a value.

Pre-Update: Self-Reference Check

json
{
  "commandName": "Update",
  "type": "Pre",
  "javascript": "if(context.commandArgs.data.ParentCategoryId === context.commandArgs.entityId) {\n  globalError = 'A category cannot be its own parent!';\n}"
}

See Also