Skip to main content

Plugin Actions

Actions are plugin-contributed operations that appear in the UI alongside Resources, Notes, and Groups, collecting user input through typed parameters and running synchronously or asynchronously against specific entity types or content types.

Registering an Action

Register actions during init() using mah.action(table):

function init()
mah.action({
id = "colorize",
label = "Colorize Image",
entity = "resource",
placement = {"detail", "card"},
filters = {
content_types = {"image/jpeg", "image/png"}
},
params = {
{ name = "style", type = "select", label = "Style", options = {"realistic", "artistic"}, default = "realistic" },
{ name = "intensity", type = "number", label = "Intensity", min = 1, max = 100, default = 50 }
},
async = true,
confirm = "This will process the image. Continue?",
handler = function(ctx)
local resource = mah.db.get_resource(ctx.entity_id)
-- process the resource...
mah.job_progress(ctx.job_id, 50, "Processing...")
-- ...
mah.job_complete(ctx.job_id, { message = "Done" })
end
})
end

Registration Fields

FieldTypeRequiredDefaultDescription
idstringYes--Unique ID within the plugin (normalized to lowercase)
labelstringYes--Display label in the UI
entitystringYes--Target entity: "resource", "note", or "group"
handlerfunctionYes--Lua function called when the action runs
descriptionstringNo""Optional description
iconstringNo""Optional icon identifier
placementtableNo{"detail"}Where to show: "detail", "card", "bulk"
filterstableNomatch allContent-type, category, or note-type filters
paramstableNononeUser input parameter definitions
asyncbooleanNofalseRun asynchronously via the job system
confirmstringNo""Confirmation message shown before execution
bulk_maxnumberNo0Maximum entities for bulk execution (0 = unlimited)

Registering a duplicate id within the same plugin raises a Lua error.

Action Parameters

Parameters define the input fields shown to the user before the action runs.

FieldTypeRequiredDescription
namestringYesParameter key passed to the handler
typestringYes"text", "textarea", "number", "select", "boolean", "hidden"
labelstringYesDisplay label
requiredbooleanNoWhether the field must be filled
defaultanyNoDefault value
optionstableNoChoices for "select" type
minnumberNoMinimum value for "number" type
maxnumberNoMaximum value for "number" type
stepnumberNoStep increment for "number" type

Action Filters

Filters control which entities see the action. Empty filters match everything.

filters = {
content_types = {"image/jpeg", "image/png", "image/webp"}, -- Resource content types
category_ids = {5, 12}, -- Group Category IDs
note_type_ids = {3} -- Note Type IDs
}
FilterEntityDescription
content_typesResourceMatch Resources with these MIME types
category_idsGroupMatch Groups with these Category IDs
note_type_idsNoteMatch Notes with these Note Type IDs

If a filter is set but the entity lacks the filtered field, the action does not match.

Placement

PlacementLocation
detailEntity detail page (single entity)
cardEntity card in list views (single entity)
bulkBulk action bar (multiple selected entities)

Synchronous Execution

Sync actions (the default) run within a single request-response cycle. The handler receives a context table and returns a result table.

Timeout: 5 seconds.

mah.action({
id = "tag-by-type",
label = "Auto-Tag by Type",
entity = "resource",
handler = function(ctx)
local resource = mah.db.get_resource(ctx.entity_id)
-- do something quick...
return { success = true, message = "Tagged" }
end
})

Handler Context (Sync)

FieldTypeDescription
entity_idnumberID of the target entity
paramstableUser-supplied parameter values
settingstablePlugin settings

ActionResult

FieldTypeDescription
successbooleanWhether the action succeeded
messagestringMessage displayed to the user
redirectstringOptional URL to redirect to after completion
datatableOptional additional data

Asynchronous Execution

Async actions (async = true) run in a background goroutine via the job system. The API returns immediately with a job_id.

Timeout: 5 minutes. Max concurrent: 3 async actions across all plugins.

mah.action({
id = "process-video",
label = "Process Video",
entity = "resource",
async = true,
handler = function(ctx)
mah.job_progress(ctx.job_id, 10, "Downloading...")
-- long-running work...
mah.job_progress(ctx.job_id, 50, "Processing...")
-- more work...
mah.job_complete(ctx.job_id, { message = "Video processed" })
end
})

Handler Context (Async)

Same as sync, plus:

FieldTypeDescription
job_idstringJob ID for progress reporting

Job Progress Control

FunctionDescription
mah.job_progress(job_id, percent, message)Report progress (0-100). SSE updates throttled to 200ms.
mah.job_complete(job_id, result_table)Mark job as completed. Sets progress to 100.
mah.job_fail(job_id, error_message)Mark job as failed.

If the handler returns without calling mah.job_complete or mah.job_fail, the return value is parsed as an ActionResult and the job is updated accordingly.

Abort

Call mah.abort(reason) from any handler to abort the action:

handler = function(ctx)
local resource = mah.db.get_resource(ctx.entity_id)
if not resource then
mah.abort("Resource not found")
end
-- ...
end

This returns { success = false, message = reason }.

API Endpoints

List Available Actions

GET /v1/plugin/actions
ParameterTypeDescription
entitystringRequired: "resource", "note", or "group"
content_typestringOptional: filter by content type
category_iduintOptional: filter by Category ID
note_type_iduintOptional: filter by Note Type ID
curl "http://localhost:8181/v1/plugin/actions?entity=resource&content_type=image/jpeg"

Run an Action

POST /v1/jobs/action/run
Content-Type: application/json
{
"plugin": "image-processor",
"action": "colorize",
"entity_ids": [42],
"params": { "style": "realistic", "intensity": 75 }
}
  • Sync actions: Returns 200 OK with ActionResult
  • Async actions: Returns 202 Accepted with { "job_id": "abc123..." }
  • Bulk (multiple entity_ids): Returns { "results": [...] } for sync actions or { "job_ids": [...] } for async actions. Respects bulk_max.

Get Action Job Status

GET /v1/jobs/action/job?id={jobId}
curl "http://localhost:8181/v1/jobs/action/job?id=abc123def456"

Returns the current job state including status, progress, message, and result.

  • Plugin System -- plugin installation, configuration, and lifecycle
  • Plugin Hooks -- hook registration, injections, custom pages, and menus
  • Job System -- unified job listing, SSE events, and cleanup behavior
  • Plugin Lua API -- full Lua API reference for the mah module