Action
An action is a one-off button that calls an endpoint — standalone on a page, or attached to a table row.
@page.action("Delete Book", method="delete", description="Remove a book by ID")
async def delete_book(
session: AsyncSessionDep, book_id: int = Query(...)
) -> spec.Action:
book = await session.get(models.Book, book_id)
if book:
await session.delete(book)
await session.commit()
return {"message": f"Deleted book #{book_id}"}Decorator
page.action(
name: str,
*,
method: spec.HttpMethod = "post",
description: str | None = None,
is_hidden: bool = False,
icon: spec.Icon | None = None,
color: spec.Color | None = None,
)The decorated function is registered at <method> /<page-id>/action/<action-id>, using whichever HTTP method you pass — get, post (the default), put, patch, delete, or head.
Return value
class ActionResponse(TypedDict):
icon: NotRequired[Icon]
color: NotRequired[Color]
toast: NotRequired[str]
table: NotRequired[dict | object]
message: NotRequired[str]
type Action = ActionResponse | None | strSame shape as a form's response: return None for a silent success, a string as shorthand for message, or a full ActionResponse dict with toast/message/table for richer feedback.
@page.action("Ping Service", method="get", icon="activity", color="green")
async def ping_service(target: str = Query("api")) -> spec.Action:
return {
"toast": f"{target} responded in 12ms",
"message": f"Pinged '{target}' — reachable",
}Parameters by HTTP method
Actions take parameters the same way any FastAPI endpoint does — Query for get/delete, a pydantic model or Body(...) for post/put/patch, Form(...) fields when appropriate:
@page.action("Rotate API Key", method="patch", icon="key", color="amber")
async def rotate_api_key(
key_name: str = Form(..., description="Key to rotate"),
expires_in_days: int = Form(30, description="Validity period for the new key"),
) -> spec.Action: ...Attaching an action to a table row
Give an action is_hidden=True when its only purpose is to be triggered from a row, then reference it from that row's __actions__ with reference_action:
from openadmin.fastapi import reference_action
@page.action("Delete Author", is_hidden=True)
async def delete_author(id: str) -> spec.Action:
...
return {"toast": f"User with id {id} deleted"}
@page.table("All Authors")
async def get_all_authors(...) -> spec.Table:
return {
"data": [
{
"id": author.id,
"name": ...,
"__actions__": [
{
"label": "Delete this user",
"action": reference_action(delete_author),
"query": {"id": author.id},
"color": "red",
"icon": "trash",
},
],
}
for author in authors
],
}reference_action reads the ID that the @page.action(...) decorator stamped onto delete_author, so delete_author must already be decorated before it's referenced this way — a plain top-to-bottom ordering requirement, not special behavior. See Implementing a Table for the full recipe.