Actions are reusable functions that hold shared business logic. You can call them from your agent’s code (via conversations, workflows, triggers, tools, or other actions). You can also call them over HTTP from external systems.
Creating an action
Create a file in src/actions/:
import { Action, z } from "@botpress/runtime"
export default new Action({
name: "calculateTotal",
description: "Calculate the total price including tax",
input: z.object({
items: z.array(z.object({
price: z.number(),
quantity: z.number(),
})),
taxRate: z.number().default(0.08),
}),
output: z.object({
subtotal: z.number(),
tax: z.number(),
total: z.number(),
}),
handler: async ({ input }) => {
const subtotal = input.items.reduce(
(sum, item) => sum + item.price * item.quantity, 0
)
const tax = subtotal * input.taxRate
return { subtotal, tax, total: subtotal + tax }
},
})
Action names must be alphanumeric with no underscores, dashes, or spaces (e.g. calculateTotal, not calculate_total).
Browse and test actions from the dev console:
Caching results
Set cached: true to cache an action’s output by its input. Subsequent calls with the same input return the cached result instead of re-running the handler:
export default new Action({
name: "fetchExchangeRate",
cached: true,
input: z.object({ from: z.string(), to: z.string() }),
output: z.object({ rate: z.number() }),
handler: async ({ input }) => {
const rate = await fetchRate(input.from, input.to)
return { rate }
},
})
This is useful for actions that are pure, perform an expensive calculation, and are called repeatedly with the same inputs.
Calling actions
Import actions from @botpress/runtime and call any action by name. The action’s input and output are fully typed.
From a conversation
import { Conversation, actions } from "@botpress/runtime"
export default new Conversation({
channel: "webchat.channel",
handler: async ({ execute }) => {
const result = await actions.calculateTotal({
items: [{ price: 10, quantity: 2 }],
})
console.log(result.total)
await execute({ instructions: "You are a helpful assistant." })
},
})
From a workflow
import { Workflow, actions } from "@botpress/runtime"
export default new Workflow({
name: "processOrder",
handler: async ({ input, step }) => {
const totals = await step("calculate", async () => {
return await actions.calculateTotal({ items: input.items })
})
},
})
import { Autonomous, actions, z } from "@botpress/runtime"
export default new Autonomous.Tool({
name: "getOrderTotal",
description: "Calculate the total for an order",
input: z.object({ items: z.array(z.object({ price: z.number(), quantity: z.number() })) }),
output: z.object({ total: z.number() }),
handler: async ({ items }) => {
const result = await actions.calculateTotal({ items })
return { total: result.total }
},
})
From a trigger
import { Trigger, actions } from "@botpress/runtime"
export default new Trigger({
name: "onOrderCreated",
events: ["orderPlaced"],
handler: async ({ event }) => {
await actions.calculateTotal({ items: event.payload.items })
},
})
While actions are just functions that can be called from anywhere, tools only exist inside execute() and are called by the LLM. If you want the LLM to have access to a certain action:
- Convert it to a tool with
.asTool()
- Pass it into the
tools field:
import { Conversation, actions } from "@botpress/runtime"
export default new Conversation({
channel: "webchat.channel",
handler: async ({ execute }) => {
await execute({
instructions: "You are a shopping assistant.",
tools: [actions.calculateTotal.asTool()],
})
},
})
You should always provide a detailed description that helps the LLM decide when to use a tool. When converting an action to a tool, you can override its description by passing a string into thedescription field:
actions.calculateTotal.asTool({ description: "Calculate the total price with tax for the user's cart" })
Integration actions
When you add an integration, its actions are available under actions.<integration>:
import { actions } from "@botpress/runtime"
await actions.webchat.showWebchat({ conversationId })
await actions.slack.addReaction({ name: "thumbsup", channel, timestamp })
Calling from external systems
Actions are exposed via the Botpress Runtime API’s callAction endpoint. External services can call your agent’s actions over HTTP:
curl -X POST https://api.botpress.cloud/v1/chat/actions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_PAT" \
-H "x-bot-id: YOUR_BOT_ID" \
-d '{
"type": "calculateTotal",
"input": {
"items": [{ "price": 10, "quantity": 2 }],
"taxRate": 0.08
}
}'
This makes actions the bridge between your agent and external systems. A backend service, a webhook handler, or another application can trigger your agent’s logic without going through a conversation. Last modified on April 24, 2026