> For the complete documentation index, see [llms.txt](https://manual.bubble.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://manual.bubble.io/help-guides/logic/workflows/events/frontend-events/custom-events.md).

# Custom events

Custom events let you reuse logic by running one workflow from another. Learn how to create them, pass parameters, return data, and trigger or schedule them.

{% hint style="info" %}
This article covers frontend custom events. For the server-side version, see [Backend custom events](/help-guides/logic/workflows/events/backend-events/backend-custom-events.md).
{% endhint %}

Most events respond to something happening in your app, like a click or a page load. Custom events are different: They only run when another workflow[^1] tells them to, using the **Trigger a custom event** action. Think of them as a set of actions with a name, which you can call from anywhere on the page. They can also accept parameters, which you can use in the actions inside them.

You can trigger custom events on the same page, or inside a reusable element[^2] placed on that page, with the **Trigger a custom event from a reusable element** action.

***

## What custom events are used for

### Avoid repeating work

Since custom events can be called from anywhere on the page, they save you from duplicating workflows.

Let's say your app handles documents. You have a *Save* option in a *File* menu, and a *Save* icon in a toolbar. Both do exactly the same thing: They save the contents of an input to the database. Instead of building two identical workflows, you can place the actions in a custom event and trigger it from both places.

### Return data

Custom events can also return data to the workflow that triggered them. For example, you can use one to:

* Check something in the database or an external service
* Make calculations
* Generate dynamic data
* Validate forms

### Stay consistent and prepare for changes

Custom events help you standardize things that happen in your app. Let's say you use a popup[^3] to show messages like *The changes have been saved.* If you show that popup through a custom event, it looks and behaves the same no matter what the message is. And if you later decide to use an [Alert element](#user-content-fn-4)[^4] instead, you only have to change one workflow.

### Control the order of operations

Custom events give you more control over the order things happen in, since the workflow that triggers one waits for it to finish. See [how custom events run](#how-custom-events-run).

**Article section:** [The order in which actions are triggered](/help-guides/logic/workflows/actions.md#the-order-in-which-actions-are-triggered)\
**Video:** [Understanding workflow execution rules](https://www.youtube.com/watch?v=IbhCrciOKHM\&t=41s)

***

## Creating a custom event

In the **Workflow** tab, [add a new workflow](/help-guides/logic/workflows/events.md#adding-an-event-to-a-workflow) and pick *Create a custom event...* from the *Custom* category.

<figure><img src="https://34394582-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M5sbzwG7CljeZdkntrL%2Fuploads%2F5antXgJDbONqPCtLlFzD%2FCleanShot%202026-10-13%20at%2014.17.29.png?alt=media&amp;token=c5e29742-ba8d-47d9-812a-ce8195a38a5d" alt="The event menu with the Custom category expanded and Create a custom event highlighted."><figcaption><p>Custom events are created from the <em>Custom</em> category.</p></figcaption></figure>

### Parameters

Parameters let you pass any kind of data to a custom event and use it in the actions inside it. Let's build an event that can change the name of any user:

{% stepper %}
{% step %}

#### Name the event

Give the event a name. The name doesn't affect how it works, but a descriptive name makes it easier to recognize later.
{% endstep %}

{% step %}

#### Add a user parameter

Click **Add a new parameter** and set its type to *User*. This is the user whose name we want to change.
{% endstep %}

{% step %}

#### Add a text parameter

Click **Add a new parameter** again and set the type to *text*. This is the new name the user will be given.

<figure><img src="https://34394582-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M5sbzwG7CljeZdkntrL%2Fuploads%2FzHHS6FCA8xvA78ZGqlhW%2Fcustom-event-properties.png?alt=media&amp;token=3e34023d-d814-452b-827d-079b4f6c8b99" alt="The properties of a custom event showing two custom parameters."><figcaption><p>The event now accepts a user and a text.</p></figcaption></figure>
{% endstep %}

{% step %}

#### Use the parameters in an action

Add a **Make changes to a thing** action. In *Thing to change*, choose `user`, which is the first parameter. Then pick the *Name* field and set it to `text`, which is the second parameter.

<figure><img src="https://34394582-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M5sbzwG7CljeZdkntrL%2Fuploads%2FPpRWIIT46mkoophZHHCW%2Fupdate-users-name.png?alt=media&amp;token=6c131e45-d38e-4c0b-aed3-4e2b35e382bc" alt="The Make changes to a thing action using parameters sent by the custom event."><figcaption><p>The <strong>Make changes to a thing</strong> action uses both parameters: <em>user</em> tells the action which user to change, and <em>text</em> holds the new name.</p></figcaption></figure>
{% endstep %}
{% endstepper %}

{% hint style="warning" %}
Make sure to use the **Make changes to a thing** action, and not **Make changes to current user**, since the parameter decides *which* user to change.
{% endhint %}

The custom event can now change the name of *any* user. By setting up custom events for key workflows, you can keep them consistent across the page.

### Return values

You can also set up a custom event to return one or more values. Let's expand the example. Instead of changing the user's name, we want to start a subscription with the [Stripe plugin](#user-content-fn-5)[^5]. That can succeed or fail, depending on the user's payment details, and we want to tell the original workflow how it went.

{% hint style="info" %}
To follow this example yourself, install the Stripe plugin from the **Plugins** tab.
{% endhint %}

{% stepper %}
{% step %}

#### Add the return values

Select the custom event to see its properties, and add two values under the *Return values* header:

<table><thead><tr><th width="200">Value name</th><th width="120">Data type</th><th>Purpose</th></tr></thead><tbody><tr><td>Subscription started?</td><td>yes/no</td><td>Returns whether the subscription was started</td></tr><tr><td>Error message</td><td>text</td><td>Returns the error from Stripe if it wasn't</td></tr></tbody></table>

<figure><img src="https://34394582-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M5sbzwG7CljeZdkntrL%2Fuploads%2F9K9zCh5OHaxza00lqb79%2Freturn-parameters.png?alt=media&amp;token=b2194e73-5d75-4db0-a2e0-b8184afad3b4" alt="A custom event with one parameter and two return values."><figcaption><p>Return values are separate from parameters.</p></figcaption></figure>

The event still has a *user* parameter, which tells it which user to subscribe. *Subscription started?* is required, since we'll return a yes or no every time. *Error message* is optional, since we only need it if something goes wrong.
{% endstep %}

{% step %}

#### Return the result if the subscription started

Add the actions that try to start the subscription:

<figure><img src="https://34394582-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M5sbzwG7CljeZdkntrL%2Fuploads%2F0KQbnB4vVzTRBAKqCoFA%2Freturn-parameter-action.png?alt=media&amp;token=8515583e-060e-4d39-878f-e341e4ed26a4" alt="A workflow showing a Stripe subscription successfully being started."><figcaption><p>The numbers match the table below.</p></figcaption></figure>

| Number        | What's set up                                                                                 |
| ------------- | --------------------------------------------------------------------------------------------- |
| 1             | The **Subscribe the user to a plan** action starts the subscription for the `user` parameter. |
| 2             | The **Return data** action sends `yes` back as the *Subscription started?* value.             |
| 3             | *Error message* stays blank, since this action only runs if the subscription started.         |
| 4             | The *Only when* field checks that Stripe returned the status *active*.                        |
| {% endstep %} |                                                                                               |

{% step %}

#### Return the result if it didn't

Add another **Return data** action for when the subscription fails:

<figure><img src="https://34394582-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M5sbzwG7CljeZdkntrL%2Fuploads%2FyHiD9CTAxrpziceRxiPP%2Freturn-data-example-2.png?alt=media&amp;token=9b6a6a15-91fe-44fb-9124-75a232747921" alt="A workflow showing a Stripe subscription failing to start."><figcaption><p>The numbers match the table below.</p></figcaption></figure>

| Number | What's set up                                                                                     |
| ------ | ------------------------------------------------------------------------------------------------- |
| 1      | This time, the action returns `no`.                                                               |
| 2      | It also returns the error message from the Stripe plugin.                                         |
| 3      | The *Only when* field checks that the status isn't *active*, which means the subscription failed. |

{% hint style="warning" %}
When a **Return data** action runs, the workflow stops, so any later steps won't trigger. It works much like the **Terminate this workflow** action.
{% endhint %}
{% endstep %}

{% step %}

#### Use the returned data

The data now goes back to the workflow that triggered the custom event. In this example, a button triggers the event and sends the current user as the parameter:

<figure><img src="https://34394582-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M5sbzwG7CljeZdkntrL%2Fuploads%2FMckWGKwfYo9RIrXtKcfb%2Freturn-user-example.png?alt=media&amp;token=50c57fe1-6e20-45d2-8393-4a46ac0febb8" alt="Triggering a custom event and sending the current user as a custom parameter."><figcaption><p>The first step triggers the custom event.</p></figcaption></figure>

The next step shows the error if there is one:

<figure><img src="https://34394582-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M5sbzwG7CljeZdkntrL%2Fuploads%2FX2k1WGBrSi3B00sJLnVz%2Ferror-message.png?alt=media&amp;token=b34d0d8e-f329-4944-a9a8-e1060c004d20" alt="An action showing an alert messages if the Stripe subscription did not succeed."><figcaption><p>The numbers match the table below.</p></figcaption></figure>

| Number           | What's set up                                                                                                               |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------- |
| 1                | The **Show message in an alert box** action shows the error. You need an **Alert** element on the page to use this action.  |
| 2                | The message uses the `Result of step 1` data source. Step 1 triggered the custom event, so this gives us the returned data. |
| 3                | The *Only when* field checks whether an error was returned.                                                                 |
| {% endstep %}    |                                                                                                                             |
| {% endstepper %} |                                                                                                                             |

A real app might do more than this, but the example shows how a custom event can run a series of actions and hand back a result.

***

## Running a custom event

### Triggering

The **Trigger a custom event** action runs a custom event right away.

<figure><img src="https://34394582-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M5sbzwG7CljeZdkntrL%2Fuploads%2FXbgwAYd5Ym7ohhtb7dmt%2FCleanShot%202026-10-13%20at%2014.40.30.png?alt=media&amp;token=9d8e85fa-05ff-4f3f-ad2a-9296e338e680" alt="The action menu showing the Trigger a custom event action."><figcaption><p>The actions for running custom events are in the <em>Custom Events</em> category.</p></figcaption></figure>

### Trigger a workflow in a reusable element

A custom event inside a reusable element can be triggered from the element's parent. For example, if you place a reusable element called *popup-message* on a page called *index*, you can trigger any custom event inside *popup-message* from *index*, and from any other page where you place the element.

{% hint style="info" %}
This only works in one direction, from the parent page to the reusable element inside it.
{% endhint %}

### Scheduling

The **Schedule a custom event** action runs a custom event after a delay, set in seconds.

{% hint style="info" %}
Scheduling a custom event is a client-side action, so it won't run if the page is closed. To schedule a workflow that runs even if the user has left, use an API workflow.

**Article series:** [The Workflow API](/help-guides/integrations/api/the-bubble-api/the-workflow-api.md)\
**Article:** [Scheduling API workflows](/help-guides/integrations/api/the-bubble-api/the-workflow-api/api-workflows/scheduling-api-workflows.md)
{% endhint %}

### Triggering when data changes

The **Trigger a custom event when data changes** action looks for a change in a specific field and triggers a custom event once when it happens. It only works while the page is open, and it triggers once for each time the action runs.

**Reference:** [Trigger a custom event when data changes](/core-resources/actions/custom.md#trigger-a-custom-event-when-data-changes)

### How custom events run

When a workflow triggers a custom event, it stops at that point, runs the custom event to the end, and then moves on to its next action. The custom event can also see what happened in the actions before it.

That's the difference between triggering a custom event and scheduling one with no delay:

| Action                                        | What the parent workflow does                                                       |
| --------------------------------------------- | ----------------------------------------------------------------------------------- |
| **Trigger a custom event**                    | Pauses and waits for the custom event to finish.                                    |
| **Schedule a custom event** with a delay of 0 | Keeps going, so the custom event can run in parallel with the rest of the workflow. |

***

## Plan custom events with Bubble AI

Custom events aren't on the [Bubble AI Agent](/help-guides/ai/bubble-ai-agent.md)'s list of [supported frontend events](/help-guides/ai/bubble-ai-agent/edit-apps-with-ai.md#generate-and-modify-frontend-workflows), and it can't build them in [backend workflows](/help-guides/ai/bubble-ai-agent/edit-apps-with-ai.md#generate-and-modify-backend-workflows). You can still ask it to explain how to set one up, or to look at your app and find places where a custom event could help.

Here are a few prompts to try:

{% code overflow="wrap" %}

```
Explain how to set up a custom event that returns data.
```

{% endcode %}

{% code overflow="wrap" %}

```
Do any workflows on this page repeat the same actions?
```

{% endcode %}

{% code overflow="wrap" %}

```
Explain what this workflow does and which steps run in order.
```

{% endcode %}

**Article:** [Edit apps with AI](/help-guides/ai/bubble-ai-agent/edit-apps-with-ai.md)

***

## FAQ: Custom events

<details>

<summary>I get the issue checker error "This workflow must end with a unconditional 'Return Data' action"</summary>

This error shows up when at least one of the workflow's return data parameters is required (not marked as optional), and the last step in the workflow isn't a **Return data** action without a condition. A required parameter means the workflow has to return something every time it runs, so Bubble needs an unconditional **Return data** action at the end.

There are two ways to fix it:

| Fix                                         | How it works                                                                                                                                               |
| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Add an unconditional **Return data** action | Add a **Return data** action with no condition as the last step of the workflow, and return the data you need.                                             |
| Make the parameter optional                 | Mark the return data parameter as optional. The workflow doesn't have to return a value anymore, so a **Return data** action with a condition is accepted. |

</details>

<details>

<summary>Can I loop a custom event?</summary>

No. Custom events can't be looped, so a custom event can't trigger itself.

If you need to run the same logic on each item in a list, use the **Schedule API workflow on a list** action instead. Set up a backend workflow that performs the action, and Bubble runs it once for each item in the list.

</details>

<details>

<summary>Can I trigger a custom event from another page?</summary>

Not directly. You can trigger custom events on the same page, or inside a reusable element placed on that page.

</details>

<details>

<summary>Will a scheduled custom event run if the user closes the page?</summary>

No. Scheduling a custom event is a client-side action, so it only runs while the page is open. Use an [API workflow](/help-guides/integrations/api/the-bubble-api/the-workflow-api/api-workflows/scheduling-api-workflows.md) if it needs to run after the user has left.

</details>

<details>

<summary>What's the difference between a custom event and a backend custom event?</summary>

A custom event is a frontend event. A [backend custom event](/help-guides/logic/workflows/events/backend-events/backend-custom-events.md) runs on Bubble's server, and you can choose whether it respects privacy rules.

</details>

[^1]: A *workflow* is the combination of an event and one or more actions.\
    **Article:** [Workflows](/help-guides/logic/workflows.md)

[^2]: A reusable element is a set of elements you build once and can place on several pages.\
    **Reference:** [Reusable elements](/core-resources/elements/reusable-elements.md)

[^3]: The *Popup* container type is a group that is displayed above all other elements and is centered on the screen regardless of the scrolling position of the page.\
    **Article:** [Popups](/help-guides/design/elements/web-app/containers/popups.md)

[^4]: The *Alert* element is used to quickly flash a text message on the screen.\
    **Reference:** [Alert](/core-resources/elements/visual-elements.md#alert)

[^5]: The Stripe plugin is a Bubble-made plugin that connects your app to the payment gateway Stripe, so you can accept payments and start subscriptions.\
    **Reference:** [The Stripe plugin](/core-resources/bubble-made-plugins/stripe.md)
