--- title: Run --- An action can do one of the following things: - Execute a function on the server (most common) - If block is followed by a streamable variable, stream the variable on the client. Otherwise, execute a function on the server. - Execute a function on the client - Display a custom embed bubble ## Server function The most common action is to execute a function on the server. This is done by simply declaring that function in the action block. Example: ```ts export const sendMessage = createAction({ // ... run: { server: async ({ credentials: { apiKey }, options: { botId, message, responseMapping, threadId }, variables, logs, }) => { const res: ChatNodeResponse = await got .post(apiBaseUrl + botId, { headers: { Authorization: `Bearer ${apiKey}`, }, json: { message, chat_session_id: isEmpty(threadId) ? undefined : threadId, }, }) .json() if (res.error) logs.add({ status: 'error', description: res.error, }) if (res.error) logs.responseMapping?.forEach((mapping) => { if (!mapping.variableId) return const item = mapping.item ?? 'Message' if (item === 'Message') variables.set(mapping.variableId, res.message) if (item === 'Thread ID') variables.set(mapping.variableId, res.chat_session_id) }) }, }, }) ``` As you can see the function takes `credentials`, `options`, `variables` and `logs` as arguments. The `credentials` are the credentials that the user has entered in the credentials block. The `options` are the options that the user has entered in the options block. The `variables` object contains helper to save and get variables if necessary. The `logs` allows you to log anything during the function execution. These logs will be displayed as toast in the preview mode or in the Results tab on production. ## Server function + stream If your block can stream a message (like OpenAI), you can implement a stream object to handle it. ```ts export const createChatCompletion = createAction({ // ... run: { server: async (params) => { //... }, stream: { getStreamVariableId: (options) => options.responseMapping?.find( (res) => res.item === 'Message content' || !res.item )?.variableId, run: async ({ credentials: { apiKey }, options, variables }) => { const config = { apiKey, baseURL: options.baseUrl, defaultHeaders: { 'api-key': apiKey, }, defaultQuery: options.apiVersion ? { 'api-version': options.apiVersion, } : undefined, } satisfies ClientOptions const openai = new OpenAI(config) const response = await openai.chat.completions.create({ model: options.model ?? defaultOpenAIOptions.model, temperature: options.temperature ? Number(options.temperature) : undefined, stream: true, messages: parseChatCompletionMessages({ options, variables }), }) return OpenAIStream(response) }, }, }, }) ``` The `getStreamVariableId` function defines which variable should be streamed. The `run` works the same as the [server function](./run#server-function). It needs to return `Promise | undefined>`. ## Client function If you want to execute a function on the client instead of the server, you can use the `client` object. This makes your block only compatible with the Web runtime. It won't work on WhatsApp for example, the block will simply be skipped. Example: ```ts export const shoutName = createAction({ // ... run: { web: ({ options }) => { return { args: { name: options.name ?? null, }, content: `alert('Hello ' + name)`, } }, }, }) ``` The web function needs to return an object with `args` and `content`. `args` is an object with arguments that are passed to the `content` context. Note that the arguments can't be `undefined`. If you want to pass an not defined argument, you need to pass `null` instead. `content` is the code that will be executed on the client. It can call the arguments passed in `args`. ## Display embed bubble If you want to display a custom embed bubble, you can use the `displayEmbedBubble` object. See [Cal.com block](https://github.com/baptisteArno/typebot.io/blob/main/packages/forge/blocks/calCom/actions/bookEvent.ts) as an example. Example: ```ts export const bookEvent = createAction({ // ... run: { web: { displayEmbedBubble: { parseInitFunction: ({ options }) => { if (!options.link) throw new Error('Missing link') const baseUrl = options.baseUrl ?? defaultBaseUrl const link = options.link?.startsWith('http') ? options.link.replace(/http.+:\/\/[^\/]+\//, '') : options.link return { args: { baseUrl, link: link ?? '', name: options.name ?? null, email: options.email ?? null, layout: parseLayoutAttr(options.layout), }, content: `(function (C, A, L) { let p = function (a, ar) { a.q.push(ar); }; let d = C.document; C.Cal = C.Cal || function () { let cal = C.Cal; let ar = arguments; if (!cal.loaded) { cal.ns = {}; cal.q = cal.q || []; d.head.appendChild(d.createElement("script")).src = A; cal.loaded = true; } if (ar[0] === L) { const api = function () { p(api, arguments); }; const namespace = ar[1]; api.q = api.q || []; typeof namespace === "string" ? (cal.ns[namespace] = api) && p(api, ar) : p(cal, ar); return; } p(cal, ar); }; })(window, baseUrl + "/embed/embed.js", "init"); Cal("init", { origin: baseUrl }); Cal("inline", { elementOrSelector: typebotElement, calLink: link, layout, config: { name: name ?? undefined, email: email ?? undefined, } }); Cal("ui", {"hideEventTypeDetails":false,layout});`, } }, }, waitForEvent: { getSaveVariableId: ({ saveBookedDateInVariableId }) => saveBookedDateInVariableId, parseFunction: () => { return { args: {}, content: `Cal("on", { action: "bookingSuccessful", callback: (e) => { continueFlow(e.detail.data.date) } })`, } }, }, }, }, }) ``` The `displayEmbedBubble` accepts a `parseInitFunction` function. This function needs to return the same object as the [`web` function](./run#client-function). The function content can use the `typebotElement` variable to get the DOM element where the block is rendered. Optionally you can also define a `waitForEvent` object. This object accepts a `getSaveVariableId` function that returns the variable id where the event data should be saved. It also accepts a `parseFunction` function that returns the same object as the [`web` function](./run#client-function). The function content can use the `continueFlow` function to continue the flow with the event data.