Custom Actions
KraftAdmin custom actions allow you to add application-specific operations to your administration panel.
For example, an Order resource might need actions such as:
- Approve an order
- Cancel an order
- Refund an order
- Ship an order
- Send an invoice
- Mark an order as completed
Instead of putting this logic inside the admin framework, you define the operation in your own application service and expose it to KraftAdmin using @KraftAdminCustomAction.
@KraftAdminCustomAction(
entityClass = Order.class,
name = "approve-order",
label = "Approve",
icon = KraftIcon.ICON_WALLET,
variant = ActionVariant.SUCCESS,
confirmMessage = "Are you sure you want to approve this order?"
)
public KraftActionResponse approve(KraftActionContext context) {
Order order = context.entity();
orderService.approve(order);
return KraftActionResponse
.ok("Order approved.")
.build();
} KraftAdmin discovers annotated methods during startup and makes them available as actions in the administration panel.
Basic Action
A custom action requires:
- An entity
- A unique name
- A label
- A method that performs the operation
@KraftAdminCustomAction(
entityClass = Order.class,
name = "approve-order",
label = "Approve"
)
public KraftActionResponse approve(KraftActionContext context) {
Order order = context.entity();
orderService.approve(order);
return KraftActionResponse
.ok("Order approved.")
.build();
} The action will be displayed as an action for the Order resource.
Annotation Configuration
@KraftAdminCustomAction provides configuration for the action’s identity, appearance, behavior, input, permissions, and placement.
@KraftAdminCustomAction(
entityClass = Order.class,
name = "approve-order",
label = "Approve",
icon = KraftIcon.ICON_CHECK,
variant = ActionVariant.SUCCESS,
target = ActionTarget.ROW,
bulk = false,
requiresSelection = true,
confirmMessage = "Approve this order?",
hideAfterExecution = false,
refresh = true,
order = 0,
permission = "orders.approve",
group = "Order Management"
) entityClass
entityClass = Order.class Specifies the entity or resource that the action applies to.
@KraftAdminCustomAction(
entityClass = Order.class,
name = "approve-order",
label = "Approve"
) This action will be associated with the Order resource.
The action method can then access the selected entity through:
Order order = context.entity(); Default
Nothing.class In most cases, you should explicitly specify the entity class.
name
name = "approve-order" The unique identifier of the action.
Use a stable, descriptive name.
name = "approve-order" name = "refund-order" name = "send-invoice" The name is used internally to identify the action when the frontend invokes it.
Recommendations
Use lowercase kebab-case:
approve-order
refund-order
send-invoice
mark-as-completed Avoid using display text as the action name:
Approve Order The name should remain stable even if the visible label changes.
label
label = "Approve" The text displayed to the administrator.
label = "Approve" The user sees:
Approve The label can be changed without changing the action’s internal identifier.
For example:
name = "approve-order"
label = "Approve Order" Appearance
icon
icon = KraftIcon.ICON_WALLET Specifies the icon displayed alongside the action.
KraftAdmin provides predefined icons through KraftIcon.
icon = KraftIcon.ICON_TRASH icon = KraftIcon.ICON_SHIP icon = KraftIcon.ICON_SHOPPING_CART You can also provide an icon identifier directly when supported by your configured icon set.
icon = "custom-icon" If no icon is specified, KraftAdmin uses the default action icon.
variant
variant = ActionVariant.SUCCESS Controls the visual appearance of the action.
Available variants include:
| Variant | Intended use |
|---|---|
PRIMARY | Main or general-purpose actions |
SECONDARY | Less prominent actions |
SUCCESS | Positive actions |
WARNING | Actions requiring caution |
DANGER | Destructive or irreversible actions |
Examples:
variant = ActionVariant.PRIMARY variant = ActionVariant.SUCCESS variant = ActionVariant.WARNING variant = ActionVariant.DANGER For example, an approval action is a good candidate for SUCCESS:
@KraftAdminCustomAction(
entityClass = Order.class,
name = "approve-order",
label = "Approve",
variant = ActionVariant.SUCCESS
) A cancellation or deletion action may use DANGER:
@KraftAdminCustomAction(
entityClass = Order.class,
name = "cancel-order",
label = "Cancel",
variant = ActionVariant.DANGER
) Action Placement
target
target = ActionTarget.ROW Controls where the action is displayed in the administration interface.
For example, an action may appear:
- On an individual resource row
- In a toolbar
- In another supported action location
The default target is:
ActionTarget.ROW Example:
@KraftAdminCustomAction(
entityClass = Order.class,
name = "refund-order",
label = "Refund",
target = ActionTarget.ROW
) Use the target that matches how the operation is intended to be used.
Selection and Bulk Actions
requiresSelection
requiresSelection = true Specifies whether the action requires one or more selected resources.
The default is:
true For an action that operates on a specific order:
requiresSelection = true The selected entity can then be accessed through:
Order order = context.entity(); An action that does not require a selected entity can use:
requiresSelection = false This is useful for actions that operate independently of a specific resource.
bulk
bulk = true Allows the action to operate on multiple selected records.
For example:
@KraftAdminCustomAction(
entityClass = Order.class,
name = "approve-orders",
label = "Approve Selected",
bulk = true
)
public KraftActionResponse approveOrders(KraftActionContext context) {
// Process selected orders
return KraftActionResponse
.ok("Orders approved.")
.build();
} Use bulk = true for operations such as:
- Approving multiple records
- Exporting selected records
- Archiving records
- Sending notifications
- Changing the status of multiple records
Use:
bulk = false for actions intended for a single record.
The default is:
false Confirmation Dialogs
confirmMessage
confirmMessage =
"Are you sure you want to approve this order?" Displays a confirmation dialog before the action is executed.
Example:
@KraftAdminCustomAction(
entityClass = Order.class,
name = "refund-order",
label = "Refund",
confirmMessage =
"Are you sure you want to issue a refund for this order?"
) The action is not executed until the administrator confirms.
This is especially useful for:
- Deleting records
- Cancelling orders
- Issuing refunds
- Sending notifications
- Performing irreversible operations
If the value is empty:
confirmMessage = "" no confirmation message is displayed.
Action Input
Custom actions can optionally display a form before execution.
This is useful when an action needs additional information.
For example, cancelling an order may require a reason.
@KraftAdminCustomAction(
entityClass = Order.class,
name = "cancel-order",
label = "Cancel",
input = CancellationRequest.class,
inputTitle = "Cancel Order",
inputDescription = "Please provide a cancellation reason.",
submitLabel = "Cancel Order",
cancelLabel = "Close"
)
public KraftActionResponse cancel(KraftActionContext context) {
Order order = context.entity();
CancellationRequest request = context.input();
order.setCancellationReason(
request.getMessage()
);
orderService.cancel(order);
return KraftActionResponse
.ok("Order cancelled.")
.build();
} Defining an Action Input
The input class is a normal class whose fields are described using @KraftAdminField.
@Data
@NoArgsConstructor
public class CancellationRequest {
@KraftAdminField(
label = "Cancellation Message",
inputType = FormInputType.WYSIWYG,
wysiwygConfig = @RichTextConfig(
placeholder = "Enter detailed reason",
toolbarProfile = ToolbarProfile.STANDARD
)
)
private String message;
} KraftAdmin uses the field metadata to generate the form shown to the administrator.
The submitted form is then available through:
CancellationRequest request =
context.input(); input
input = CancellationRequest.class Specifies the class used to generate the action input form.
input = CancellationRequest.class If no input class is provided:
input = Nothing.class the action does not display an input form.
inputTitle
inputTitle = "Cancel Order" The title displayed at the top of the action input dialog.
Example:
inputTitle = "Cancel Order" inputDescription
inputDescription =
"Please provide a cancellation reason." Additional information displayed below the dialog title.
Use this to explain what information the administrator needs to provide.
submitLabel
submitLabel = "Cancel Order" Controls the label of the form submission button.
Default:
Submit Example:
submitLabel = "Confirm Cancellation" cancelLabel
cancelLabel = "Close" Controls the label of the button used to close the input dialog.
Default:
Cancel Example:
cancelLabel = "Go Back" Action Results
Custom action methods return a KraftActionResponse.
public KraftActionResponse approve(
KraftActionContext context
) {
// Perform operation
return KraftActionResponse
.ok("Order approved.")
.build();
} For successful operations:
return KraftActionResponse
.ok("Order approved.")
.build(); For failed operations:
return KraftActionResponse
.fail("Cannot approve a cancelled order.")
.build(); For example:
if ("CANCELLED".equals(order.getStatus())) {
return KraftActionResponse
.fail(
"Cannot approve a cancelled order."
)
.build();
} The response is sent back to the administration interface, where KraftAdmin can display the result to the user.
hideAfterExecution
hideAfterExecution = true Removes or hides the action after it has successfully executed.
This can be useful for one-way state transitions.
For example:
Pending Order
│
▼
Approve
│
▼
Approved Order After the order is approved, the Approve action may no longer be useful.
@KraftAdminCustomAction(
entityClass = Order.class,
name = "approve-order",
label = "Approve",
hideAfterExecution = true
) The default is:
false refresh
refresh = true Controls whether the current page is refreshed after the action completes successfully.
The default is:
true This is useful when the action changes the entity displayed on the page.
For example, after approving an order:
Before:
Status: PENDING
Action:
Approve
After:
Status: APPROVED Refreshing the page allows the updated state and available actions to be displayed immediately.
You can disable the refresh behavior:
refresh = false when the page does not need to be reloaded after execution.
order
order = 10 Controls the display order of actions.
Lower values appear first.
order = 0 appears before:
order = 10 which appears before:
order = 20 Example:
@KraftAdminCustomAction(
entityClass = Order.class,
name = "approve-order",
label = "Approve",
order = 1
) @KraftAdminCustomAction(
entityClass = Order.class,
name = "refund-order",
label = "Refund",
order = 20
) The default order is:
0 permission
permission = "orders.approve" Specifies an optional permission required to execute the action.
Example:
@KraftAdminCustomAction(
entityClass = Order.class,
name = "approve-order",
label = "Approve",
permission = "orders.approve"
) The permission value is application-defined.
This allows an application to control access to individual actions separately from general resource access.
For example:
User:
Can view orders
Can edit orders
Cannot approve orders The approve-order action can require:
permission = "orders.approve" group
group = "Order Management" Groups related actions together in the user interface.
For example:
group = "Order Management" Actions such as:
- Approve
- Cancel
- Refund
- Ship
can be placed into the same group.
This is useful when a resource has many custom actions.
Complete Example
The following example defines several actions for an Order entity.
@Component
@Slf4j
public class OrderActions {
private final OrderService orderService;
public OrderActions(OrderService orderService) {
this.orderService = orderService;
}
@KraftAdminCustomAction(
entityClass = Order.class,
name = "approve-order",
label = "Approve",
icon = KraftIcon.ICON_WALLET,
variant = ActionVariant.SUCCESS,
confirmMessage =
"Are you sure you want to approve this order?"
)
public KraftActionResponse approve(
KraftActionContext context
) {
Order order = context.entity();
if ("CANCELLED".equals(order.getStatus())) {
return KraftActionResponse
.fail(
"Cannot approve a cancelled order."
)
.build();
}
orderService.approve(order);
return KraftActionResponse
.ok("Order approved.")
.build();
}
@KraftAdminCustomAction(
entityClass = Order.class,
name = "cancel-order",
label = "Cancel",
icon = KraftIcon.ICON_TRASH,
variant = ActionVariant.DANGER,
confirmMessage =
"Are you sure you want to cancel this order?",
input = CancellationRequest.class,
inputTitle = "Cancel Order",
inputDescription =
"Please provide a cancellation reason.",
submitLabel = "Cancel Order",
cancelLabel = "Close"
)
public KraftActionResponse cancel(
KraftActionContext context
) {
Order order = context.entity();
CancellationRequest request =
context.input();
order.setCancellationReason(
request.getMessage()
);
orderService.cancel(order);
return KraftActionResponse
.ok("Order cancelled.")
.build();
}
@KraftAdminCustomAction(
entityClass = Order.class,
name = "refund-order",
label = "Refund",
icon = KraftIcon.ICON_SHOPPING_CART,
confirmMessage =
"Are you sure you want to issue a refund?"
)
public KraftActionResponse refund(
KraftActionContext context
) {
Order order = context.entity();
orderService.refund(order);
return KraftActionResponse
.ok("Refund completed.")
.build();
}
@KraftAdminCustomAction(
entityClass = Order.class,
name = "ship-order",
label = "Ship",
icon = KraftIcon.ICON_SHIP,
variant = ActionVariant.PRIMARY,
confirmMessage =
"Are you sure you want to ship this order?"
)
public KraftActionResponse shipOrder(
KraftActionContext context
) {
Order order = context.entity();
orderService.shipOrder(order);
return KraftActionResponse
.ok("Order shipped.")
.build();
}
} Action Context
Every custom action receives a KraftActionContext.
The context provides information about the action execution.
For a single-record action:
Order order = context.entity(); For an action with input:
CancellationRequest request =
context.input(); This allows your action method to combine:
- The selected entity
- Additional form input
- The action execution context
without requiring the action method to manually parse HTTP requests.
Recommended Structure
A good custom action should usually:
- Receive the
KraftActionContext. - Retrieve the selected entity.
- Validate whether the operation is allowed.
- Call your application’s service layer.
- Return a
KraftActionResponse.
public KraftActionResponse approve(
KraftActionContext context
) {
Order order = context.entity();
if (!order.canBeApproved()) {
return KraftActionResponse
.fail("This order cannot be approved.")
.build();
}
orderService.approve(order);
return KraftActionResponse
.ok("Order approved.")
.build();
} Keep business logic in your application’s services and domain model. The custom action should primarily connect the KraftAdmin interface to that existing business logic.