# Introduction

Welcome to the Bubble Docs.

You’re reading the Bubble Manual, which has everything you need to know to build apps using Bubble. This documentation covers everything from basic concepts to advanced features and includes step-by-step tutorials, troubleshooting tips, and insights into best practices.

If you haven't already, start by [setting up a free account and start building](https://bubble.io/login?mode=signup\&utm_medium=bubble-docs\&utm_content=introduction).

Bubble lets you turn your ideas into apps by combining the power of AI and visual development. Whether you're building your first app or you're an experienced developer, Bubble's AI-powered tools and intuitive visual editor make it possible to quickly generate, refine, and launch your vision—no coding required.

<figure><img src="/files/SoVKZjbXRJx8tnaKz1o7" alt=""><figcaption></figcaption></figure>

B​ubble lets you turn your ideas into fully functional, scalable apps by combining the power of AI and visual development. Whether you're building your first app or you're an experienced developer, Bubble's AI-powered tools and intuitive visual editor make it possible to quickly generate, refine, and launch your vision —no coding required.

## Build your app using AI

Bubble's advanced AI Agent empowers you to create a fully functional app using simple prompts. Describe your idea in your own words, and watch as Bubble AI generates a personalized app structure, ready for you to customize. Confirm your features and add any the AI missed, then generate the app. This initial app will be fully functional with a built-in database and workflows — providing a strong starting point, dramatically reducing the time and effort needed to bring your idea to life.

## Refine and customize visually

After your AI-generated app is ready, Bubble's visual editor lets you fine-tune and expand your app exactly the way you want. Design your user interface, add custom events and workflows, and seamlessly integrate third-party services using plugins and the built-in API Connector. With Bubble, visual development means you have the freedom to iterate quickly, safely, and confidently.

## One platform, limitless possibilities

Bubble's flexibility lets you create any type of app — ranging from simple MVPs to sophisticated, enterprise-grade applications. With a shared database, workflows, and infrastructure, Bubble supports creating seamless experiences across web, iOS, and Android, all from a single, unified editor.

{% hint style="info" %}
To get quickly started with native mobile app development, check out our Quick Start Guide below:

Article: [Mobile app quick start guide](/help-guides/getting-started/building-for.../native-ios-and-android/mobile-app-quick-start-guide)
{% endhint %}

## Learning and building with Bubble

Bubble's docs are designed to help you quickly master the essentials and continue as a valuable reference throughout your development journey. Whether you prefer a structured walkthrough or simply browsing topics that interest you, the Bubble Manual covers foundational concepts, terminology, best practices, and step-by-step how-to guides.

\
If you're new to Bubble, start by checking out our Getting Started section. Learn the fundamentals, explore how AI and visual development come together, and discover just how quickly you can build powerful, professional apps.

{% hint style="info" %}
You can read more about [Bubble's mission](https://bubble.io/blog/mission/) and our [The Bubble Origin Story](https://bubble.io/blog/about-bubble/) in our dedicated blog articles.
{% endhint %}

## Using the Bubble Docs

### Sections

The Bubble documentation is divided into two sections:

<table data-card-size="large" data-view="cards"><thead><tr><th align="center"></th><th align="center"></th><th></th><th></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td align="center"></td><td align="center"><a href="/pages/AYIBSvMjCU3bl91RDeaX"><mark style="color:blue;"><strong>The User Manual</strong></mark></a></td><td><ul><li>Long-form articles for learning</li><li>In-depth explanation of tools and concepts</li><li>Organized by subject</li><li>Can be read from start to finish to learn Bubble</li></ul></td><td></td><td></td></tr><tr><td align="center"></td><td align="center"><a href="/pages/leuRYFJ440LkmhNLqABb"><mark style="color:blue;"><strong>The Core Reference</strong></mark></a></td><td><ul><li>Short, technical documentation</li><li>Covers all properties, settings and technical details</li><li>Organized by feature</li><li>Can be consulted as needed for specific information</li></ul></td><td></td><td></td></tr></tbody></table>

### Annotations

To help you get quickly up to speed with the terminology used in Bubble and in web app development in general, we have provided explanatory notes on select words and phrases. You will find these annotations marked with a dotted underline [such as this](#user-content-fn-1)[^1].

<figure><img src="/files/WkdhK0ndp1IYO8fH1OIu" alt=""><figcaption><p>Clicking a phrase underlined with a dotted line reveals an annotation, and sometimes a link to learn more about the subject.</p></figcaption></figure>

## Other ways to learn

The user manual and core reference is just one part of Bubble's documentation. We also offer other ways to learn in the form of videos, interactive tutorials, demos, blog posts and forum posts. Look for the header *Other ways to learn* in articles to find links to other relevant resources you may find helpful.

<figure><img src="/files/wnjm5ERjKW8Ilclcv8qC" alt=""><figcaption><p>Many articles offer multiple ways to learn a given topic. Look for the <em>Other ways to learn</em> header at the bottom of the article.</p></figcaption></figure>

## Get started now

<details>

<summary><a href="/pages/-MTgSWffMJwg9aDf7m2i"><mark style="color:blue;">New? Start here:</mark></a> find the learning resources you need to master Bubble</summary>

This section will give you an overview over the resources you have at your disposal to start your learning journey.

Article:[ New? Start here](/new-start-here)

</details>

[^1]: This is an annotation.


# New? Start Here

## One platform for both web and iOS/Android

Bubble supports building apps for both web and iOS/Android. The power of Bubble’s infrastructure is that you don’t need to choose one—you can build for both, using a shared database, workflows, and backend logic.

<figure><img src="/files/j4TYoXE7JFmLOnfSVwhD" alt=""><figcaption><p>With Bubble, your web app and native mobile app are built on the same platform—sharing a single database and backend logic to work seamlessly together.</p></figcaption></figure>

All of this happens in one editor, with no need to maintain separate codebases or integrations. This unified approach makes it easy to support multiple platforms while keeping your design, data and logic consistent across your entire app.

| Feature                                           | Web app             | Native mobile app | Wrapper           |
| ------------------------------------------------- | ------------------- | ----------------- | ----------------- |
| Can be installed from an app store                | ❌                   | ✅                 | ✅                 |
| Push notifications                                | ❌                   | ✅                 | ⚠️ (manual setup) |
| On-device camera access                           | ❌                   | ✅                 | ⚠️ (manual setup) |
| On-device photo library access                    | ⚠️ (as file upload) | ✅ (native access) | ⚠️ (manual setup) |
| Can open in a mobile browser without installation | ✅                   | ❌                 | ✅                 |
| Database access                                   | ✅                   | ✅                 | ✅                 |
| Backend workflow access                           | ✅                   | ✅                 | ✅                 |
| API access                                        | ✅                   | ✅                 | ✅                 |
| Access to styles                                  | ✅                   | ✅                 | ✅                 |

## Welcome to Bubble!

Before you start exploring, you can [sign up for your free Bubble account here](https://bubble.io/login?mode=signup\&utm_medium=bubble-docs\&utm_content=new-start-here).

## Choosing your platform

{% hint style="warning" %}
The native mobile app editor is currently in **beta**, meaning that you may experience issues or limitations while building or testing your app. Features may change, and not all functionality is final. We recommend thorough testing before publishing, and welcome feedback to help improve the experience.
{% endhint %}

With Bubble, you can build for web, mobile or both at the same time. The guides below give you an introduction to each path:

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Building for web</strong></td><td><a href="/pages/qjpk9D8O5tZqZnOI1t6M">/pages/qjpk9D8O5tZqZnOI1t6M</a></td></tr><tr><td><strong>Building for mobile</strong></td><td><a href="/pages/EVWy0mSe4tjmyf8ejh8Y">/pages/EVWy0mSe4tjmyf8ejh8Y</a></td></tr></tbody></table>

{% hint style="info" %}
To get quickly started with native mobile app development, check out our Quick Start Guide below:

Article: [Mobile app quick start guide](/help-guides/getting-started/building-for.../native-ios-and-android/mobile-app-quick-start-guide)
{% endhint %}

## Your learning and building journey

Bubble guides you step-by-step from idea to fully functional app, combining the power of AI with intuitive visual development. Here's what your learning and building process looks like:

### 1. Start with your idea

Think about the app you want to build. Consider your audience, the core features you need, and the problems you're trying to solve.

* [Planning your app](/help-guides/getting-started/building-your-first-app)

### 2. Turn your idea into an AI prompt

Using Bubble's AI app generator, describe your vision in simple terms. Clearly outline the key functionalities and style you envision.

* [Prompt guide](/help-guides/ai/bubble-ai-agent/generate-apps-with-ai#prompt-guide)

### 3. Refine the blueprint

Bubble’s AI interprets your prompt and shows you a preview. You can explore this initial interpretation, refine your instructions, and ensure it aligns closely with your vision.

* [Previewing and refining your app blueprint](/help-guides/ai/bubble-ai-agent/generate-apps-with-ai#what-is-a-user-story)

### 4. Generate and preview your app

Bubble automatically creates your app based on your refined prompt. The AI builds your design, workflows, database structure, and even populates your database with relevant sample data — ready for you to immediately test.

* [Previewing your app](/help-guides/getting-started/navigating-the-bubble-editor/previewing-your-app)

### 5. Tweak and expand with visual development

Your generated app is a robust starting point. Using Bubble’s visual editor, you can adjust, customize, and expand every aspect of your app — no coding required.

* [The Bubble Editor](/help-guides/getting-started/navigating-the-bubble-editor)

### 6. Launch and iterate

Once you’re satisfied, Bubble helps you easily deploy your app to the web, iOS, and Android. Gather user feedback, iterate quickly, and continually improve your app over time.

* [Launching your app](/help-guides/publishing-your-app/deploying-your-app)

Bubble makes the journey from idea to reality faster, more intuitive, and accessible for everyone

<br>

## Learning resources

### The User Manual

The User Manual will take you through all the different concepts, how-to's, terminology and best practices needed to learn Bubble. You can read it from beginning to end, or pick the chapters that interest you the most.

If you are new to building in Bubble, we recommend heading over to the Getting started section.

<details>

<summary><mark style="color:blue;">Getting started:</mark> the introductory guide to building apps in Bubble</summary>

Our introductory guide will not only take you through the Bubble basics, but will help you set your strategy for planning and building your app in general. We cover topics such as:

* What is Bubble
* Building your first app
  * Planning your features and versions
  * Database structure
  * Design and UX
* Creating and managing apps
* The Bubble editor and how it works
* Setting up a custom domain and DNS
* Application settings

Article series: [Getting started](/help-guides/getting-started)

</details>

### More learning resources

You can learn Bubble in several ways, including:

<details>

<summary><a href="/pages/-MUAa98iR2btsyGwTAef">The Glossary:</a> Covering terms and phrases used in Bubble</summary>

The glossary contains widely used words and phrases used in Bubble, along with a short description of what it means. Most entries link to articles in the User Manual and/or the Core Reference where you can learn more about the relevant concept.

Article: [The glossary](/the-glossary)

</details>

<details>

<summary><a href="https://bubble.io/academy"><mark style="color:blue;">The Bubble Academy</mark>:</a> <em>Video courses and lessons</em></summary>

The Bubble Academy is a large and growing collection of video courses and lessons (some of them interactice) that tackle subjects ranging from beginner to advanced.

Page: [Bubble Academy](https://bubble.io/academy)\
Page: [Video lesson search page](https://bubble.io/videos)

</details>

<details>

<summary><a href="https://www.youtube.com/@BubbleIO"><mark style="color:blue;">Youtube channel</mark>:</a> lessons (short and long) as well as community-created content</summary>

Our Youtube channel, which includes lessons both short and long. There's also an ever-growing collection of community-generated content on Youtube.

External page: [Youtube channel](https://www.youtube.com/@BubbleIO)

</details>

<details>

<summary><a href="https://bubble.io/bootcamps#!"><mark style="color:blue;">Bootcamps:</mark></a> live group courses</summary>

Bootcamps lets you get expert advice from experienced Bubblers, group discussions and feedback in your app. There are a range of different bootcamps available that cover different parts of your learning journey.

Some bootcamps are set up so that you can learn while you are developing your app and get valuable feedback and live problem-solving.

Page:[ Bubble bootcamps](https://bubble.io/bootcamps#!)

</details>

### Getting help

If you need help during your learning or building journey, the resources below can help you out with everything from account management to specific problem solving:

<details>

<summary><mark style="color:blue;">The Bubble Success Team</mark> – Bubble – <em>experienced Customer Support agents</em></summary>

The Bubble Success Team is a group of dedicated Bubble employees that answer questions and solve problems for thousands of users every month.

Page: [Contact the Success team](https://bubble.io/support/contact)

</details>

<details>

<summary><mark style="color:blue;">The Bubble Forum</mark> – Community – <em>questions, discussions and news</em></summary>

Connect with over 2 million community members, get answers to your questions, and discover how to build better with Bubble. This is also where we publish major news and discuss features with the community.

Here are a few things you should do to increase your chances of getting an answer:

1. Start with a clear question, so others know what you're trying to solve.
2. Describe what you have already tried; that will help others narrow down possible answers.
3. Share your app as an open app, so that others can access it. If your app is sensitive and private, use some screenshots to show what you have done so far.

Page: [The Bubble Forum](https://forum.bubble.io/)

</details>

<details>

<summary><mark style="color:blue;">Bubble Experts</mark> – Community – connect with experienced coaches, agencies, and freelancers</summary>

Our Bubble Experts are comprised of curated network of community pros ready to help you build, troubleshoot, or launch.

Page: [Bubble experts](https://bubble.io/experts)

</details>


# What is Bubble?

## What is Bubble?

{% embed url="<https://www.youtube.com/watch?v=IwiZC4dcLUk&list=PLoNVJrdvQQYmCRxMIlZnvOA9MamB6dvPh>" %}

Rather than specializing in one or two aspects of web development (like designing landing pages or building databases), Bubble’s full-stack visual programming interface does it all. You can customize your UX with a drag-and-drop editor, build out logic, manage databases, and more. Plus, you can integrate with anything via plugins and API. In fact, it has all the tools you need to build a site like Facebook or Airbnb. [Sign up for your free account to start building here](https://bubble.io/login?mode=signup\&utm_medium=bubble-docs\&utm_content=what-is-bubble).

Here are a few key features you’ll encounter as you dive in:

#### AI builder

Bubble includes an AI app builder that can generate a complete app from a single prompt. Based on your input, Bubble creates a blueprint that you can refine until you're satisfied. Once ready, Bubble will generate the full app for you.

You can read more about this in the [AI section](/help-guides/ai).

#### Visual programming editor

Bubble’s main interface — called the editor — allows you to build your app by pointing and clicking instead of writing code. It’s where you design the page, structure the database, and build workflows that respond to user actions. As you build, you’ll see the results on the screen, and you can preview your app with a single click.

You can dive deeper in the sections on [designing your app](/help-guides/design) and [creating workflows and logic](/help-guides/logic).

#### Database management

Building and maintaining a database is a complex task, so Bubble automates every aspect of it. As soon as you create an app, its developer and live databases will be ready to use. We’ll perform continuous point-in-time backups with easy restoration, and industry-standard security and privacy management tools are built in.

You can set up and manage all of this in just a few clicks, even if you have no prior database experience. Read more in the [database section](/help-guides/data/the-database).

#### User management and security

Bubble's built-in user management tools enable you to let users sign up for and log in to your app securely. All our user management functionality aligns with industry standards for security and privacy while giving you the flexibility you need to set up workflows and methods that suit your app.

You can read more in the [user account management section](/help-guides/data/user-accounts). If you work with a team of developers, you can also learn how to set up isolated working environments in the version control section.

#### Third-party integration

Most modern app development requires you to your app to third-party systems, from other apps to service providers like weather reports and generative AI models. Using one of our community-made plugins or Bubble's own [API connector](/help-guides/integrations/api/the-api-connector), [app connector](/help-guides/integrations/bubble-app-connector), or [SQL connector](/help-guides/integrations/sql-database-connector), you can set up connections to other systems quickly and securely.

#### Scalability

From the platform to our pricing plans, Bubble was designed to scale with your app as it grows The Bubble engine and database is hosted securely on Amazon AWS, the world's largest network of internet servers and services, and file assets are spread out across the Cloudflare Content Delivery Network. This combination gives you the power to scale quickly and confidently.

You can read more about Bubble’s tools for scaling in the [performance and scaling section](/help-guides/maintaining-an-application/performance-and-scaling).

## Building for web and native mobile

With Bubble, you can build both web apps (which run in a browser) and native mobile apps (which can be installed from the Apple App Store or Google Play Store). You don’t have to choose one over the other—Bubble supports both, and you can build them in the same editor. They share the same database, user authentication, and workflows, so your data and logic work seamlessly across both platforms.

You can read more about this in our [Building for web and Android/iOS section.](https://github.com/bubblegroup/bubble-manual/blob/main/help-guides/getting-started/building-for...)

### We can’t wait to see what you build

Bubble exists to enable anyone from first-time entrepreneurs to enterprise-level companies to take an idea from concept to fully functional, scalable reality — faster and cheaper than you could with just code.

So head over to the [Building your first app](/help-guides/getting-started/building-your-first-app) section, and let’s dive in.


# The Glossary

This list highlights select Bubble-specific terms you might encounter in the user manual and core reference. It focuses on terms with unique meanings within Bubble.

Many items on the list provide links to articles and core reference materials, offering deeper insights into the concepts and practical applications of the associated keyword.

{% hint style="info" %}
Use the search feature in your browser to quickly find the term or phrase that you are looking for. Search is usually activated with *CTRL+F* or *⌘ + F*.
{% endhint %}

<details>

<summary>Action</summary>

* An action is a part of a workflow
* It is a step that a Bubble workflow takes, i.e. the different kinds of things that workflows can do
* Examples include sending an email, logging the user in, showing an element, hiding an element, etc.

#### Learn more

Article series: [Workflows](/help-guides/logic/workflows)\
Article: [Actions](/help-guides/logic/workflows/actions)

Core reference: [List of all actions](/core-resources/actions)

</details>

<details>

<summary>Alert</summary>

* The alert is an element type in Bubble that you can add to the page
* It can be set to be displayed at any given place on the page, or stick to the top of the page.
* It's not visible to the user until you trigger it using the *Show message in alert box* action.
* It's disappears a short (customizable) time after it is displayed.
* It's useful for showing a quick warning, error, or confirmation message.

</details>

<details>

<summary>API</summary>

* API is an umbrella term for allowing two apps or systems to exchange data
* Bubble can accept inbound API requests and send outbound requests:
  * **Outbound**: made via the [API Connector plugin](#api-connector-plugin)
  * **Inbound**: made via the [Bubble API](#bubble-api)

Learn more:

* Article series: [API](/help-guides/integrations/api)

</details>

<details>

<summary>API Connector (plugin)</summary>

* The API Connector is a Bubble-built plugin that lets you set up outbound API connections with external services
* Can set up the API calls to be used as data sources or [workflow actions](#user-content-fn-1)[^1]
* Can be installed via the plugins library (Plugins > Add Plugins)

#### Learn more

Article series: [The API Connector](/help-guides/integrations/api/the-api-connector)\
Article: [API Connector security](/help-guides/security/api-security/api-connector-security)

Core reference: [API Connector properties](/core-resources/bubble-made-plugins/api-connector)

</details>

<details>

<summary>API workflows</summary>

* A type of backend workflow which can be triggered via another workflow anywhere in the app or via an API call
* These are defined in the "Backend workflows" page, which shows up in the page selector dropdown in the topnav, once the feature is [turned on in Settings > API](/help-guides/integrations/api/the-bubble-api/the-workflow-api/api-workflows#activating-and-accessing-the-backend-workflow-editor)
* These can be scheduled to run at a later time
* If the app's Workflow API is turned on, then an API workflow can also be initiated by an external API call to the app

#### Learn more

Article series: [The Bubble API](/help-guides/integrations/api/the-bubble-api)\
Article series: [The Workflow API](/help-guides/integrations/api/the-bubble-api/the-workflow-api)

Core reference: [API workflow properties](/core-resources/api/the-bubble-api/the-workflow-api)

</details>

<details>

<summary>App connector (plugin)</summary>

* A feature that lets you connect two Bubble apps so that they interact with one another more seamlessly (as opposed to connecting the two strictly via APIs as if one was a third party)
* Can be installed via the [plugins](#plugin) library (Plugins > Add Plugins)
* To learn more, see [section in the Reference](/core-resources/bubble-made-plugins/bubble-app-connector)

#### Learn more

Article: [The app connector plugin](/help-guides/integrations/bubble-app-connector)

Core reference: [Bubble app connector properties](/core-resources/bubble-made-plugins/bubble-app-connector)

</details>

<details>

<summary>Atom</summary>

* Each block of a dynamic expression is known as an atom
* Atoms can be:
  * A [data source](#data-source)
  * An [operator](#operator)
  * A [comparison](#comparison)

</details>

<details>

<summary>Backend workflow</summary>

* *Backend workflows* is the umbrella term for any type of workflow you can create in the backend editor.
* This is a category of workflow that runs independently of any page of your app - they run in the "backend"

#### Learn more

* Article: [The back-end versus the front-end](/help-guides/logic/the-frontend-and-backend)
* Article section: [Bubble API terminology: Backend workflows](/help-guides/integrations/api/the-bubble-api/bubble-api-terminology#whats-the-difference-between-backend-workflows-and-api-workflows)
* Article series: [The Bubble API](/help-guides/integrations/api/the-bubble-api)

</details>

<details>

<summary>Branch</summary>

* A branch is an independent iteration of your application that can be developed in isolation.
* You can see the creation of a branch as splitting your app into two copies, kind of like two cells dividing. The cells are genetically identical clones at first, but can keep evolving independently of each other.
* This is useful if you have different developers/teams that are working on specific features in your app: they can work completely independently without disturbing each other's work
* Branches are a part of the [version control](#version-control) feature

#### Learn more

Article series: [Version control](/help-guides/maintaining-an-application/version-control)

</details>

<details>

<summary>Bubble API</summary>

* The Bubble API is the umbrella term for Bubble's API features
  * The [Workflow API](#workflow-api)
    * Allows external apps and systems to execute workflows in your app
  * The [Data API](#data-api)
    * Gives external apps and systems secure access to your database
* The Bubble API offers security in two different ways
  * **Authentication**
    * The process of determining *who* is trying to access the resource
  * **Privacy rules**
    * Conditions applied to data in your database that determines whether the authenticated client has access to search for, read, change or delete data of a specific type
* The Bubble API is for *inbound* API calls (calls made *to* your app from the outside) as opposed to outbound calls that are made with the [API connector plugin](#app-connector-plugin))

Using the Bubble API is among Bubble's most advanced features, but it also offers vast possibilities for integration with other online platforms.

</details>

<details>

<summary>Cell</summary>

* Each individual row or column in a repeating group or table element is called a *cell*.
* Each cell's data source automatically represents the cell's index in the list of loaded data (for example, if you load a list of *users*, each cell will have one user as its data source)
* Each cell has an index number, starting with 1 and increasing sequentially

#### Learn more

Article: [Repeating groups](/help-guides/design/elements/web-app/containers/repeating-groups)\
Article: [Finding data](/help-guides/data/the-database/finding-data)

Core reference: [Repeating group properties](/core-resources/elements/containers#repeating-group)

</details>

<details>

<summary>Collaborator / Collaboration</summary>

* Any Bubble user that you invite to edit your app is called a collaborator
* You can control the access level of each collaborator in the *Collaboration* setting

#### Learn more

Article: [Collaboration](/help-guides/maintaining-an-application/collaboration)

</details>

<details>

<summary>Comparison</summary>

* A comparison is a part of a [dynamic expression](#dynamic-expression)
  * Each part of a dynamic expression is known as an [atom](#atom)
* It's used to compare two values, such as:
  * Checking whether to users have the same name
  * Checking whether the number 4 is bigger than the number 5
  * Checking whether the creator of a [database thing](#thing-database) is the current user
* A comparison will return either a *yes* or *no*
  * If you come from a traditional programming background, this would be the same as *true* or *false*

#### Learn more

Article: [Dynamic expressions](/help-guides/logic/dynamic-expressions)

</details>

<details>

<summary>Current user</summary>

* The current user is a [data source](#data-source) which returns the [database thing](#thing-database) of the currently logged-in [user](#user)
  * If no user is logged in, it will return a temporary user profile that you can still save data to
* Using the current user data source, you can return data such as *Current user's email* in a [dynamic expression](#dynamic-expression)

#### Learn more

* Article: [User accounts](/help-guides/data/user-accounts)

</details>

<details>

<summary>Constraint / search constraint</summary>

* A search constraint narrows down the results of a database search by specifying certain conditions that the data must meet
* For example, you can search for all users called "Jane Doe" by setting up a constraint that specifies that the field called *name* should contain the text *Jane Doe*.
* You can add as many constraints as you want to a search
* Adding more constraints can lead to a faster search, since it helps Bubble rule out things quicker

#### Learn more

Article: [Finding data](/help-guides/data/the-database/finding-data)

Core reference: [Search](/core-resources/data/search)

</details>

<details>

<summary>Custom event</summary>

* A custom event is a workflow that can be triggered by other workflows using the *Trigger a custom event* or *Schedule a custom event* actions.
* You can customize the parameters that are passed to the custom event, and set them as optional or required as needed.
* You can customize return values that can be passed back to the original workflow that triggered the custom event. They can be optional or required as needed.

#### Learn more

Article: [Custom events](/help-guides/logic/workflows/events/frontend-events/custom-events)

Core reference: [Custom event properties](/core-resources/events/custom-events)

</details>

<details>

<summary>Custom state</summary>

* A custom state is a way to store temporary variables on an element that can be accessed from anywhere on the same page and during the same session
* Custom states are reset when the page is refreshed, meaning they don't store data permanently
* They can hold any kind of data like text, numbers, dates or custom data types and can be read, changed and reset as needed using [actions](#action)
* Custom states can hold a default value

#### Learn more

Article: [Custom states](/help-guides/data/temporary-data/custom-states)

</details>

<details>

<summary>Data API</summary>

* The Data API is the part of the Bubble API that lets you invite other apps and systems to read and make changes in your app's database securely
* Allows querying of data, as well as creating, updating and deleting
* You can secure the Data API using authentication and privacy rules
* Must be enabled in *Settings - API*

#### Learn more

Article series: [The Bubble API](/help-guides/integrations/api/the-bubble-api) (the Data API is a part of the Bubble API)\
Article series: [The Data API](/help-guides/integrations/api/the-bubble-api/the-data-api)

Core reference: [The Data API](/core-resources/api/the-bubble-api/the-data-api) (includes a [list of all request types](/core-resources/api/the-bubble-api/the-data-api/data-api-requests))

</details>

<details>

<summary>Data source</summary>

* A data source is any source from which Bubble can pull data, such as:
  * A database search
  * An outbound API request
  * The current user, current date/time and current geographical location

#### Learn more

Article series: [Data](/help-guides/data)

Core reference: [List of data sources](/core-resources/data/data-sources)

</details>

<details>

<summary>Database</summary>

* The database is where you store dynamic data in your app
  * It can be created by you, or added by your app's users
* Every app has its own two databases;
  * Development
  * Live
* They two are completely independent, so that you can use one for testing and one for live users
* You can create [custom data types](#data-type) in your app to suit your own needs. For example, you might want to create data types like *projects, blog posts, tasks* or *blog posts*.
  * To each of these data types, you can add [*fields*](#field-field-type) that contain data
* Bubble is designed to allow you to set up and configure your database with no prior knowledge, but the underlying technology is PostgreSQL

#### Learn more

Article series: [The database](/help-guides/data/the-database)

</details>

<details>

<summary>Deploying</summary>

* Deploying your Bubble app means pushing the changes you've made in the development environment to the live environment.
* This allows users to see and interact with the latest version of your app.
* Think of it as publishing or updating your app for the public.

#### Learn more

Article: [Deploying your app](/help-guides/publishing-your-app/deploying-your-app)\
Article series: [Version control](/help-guides/maintaining-an-application/version-control) (working with different app branches in isolation and deploying those changes)

</details>

<details>

<summary>Do a search for</summary>

* *Do a search for* is a [data source](#data-source) in a [dynamic expression](#dynamic-expression), that performs a database search
* You can add [constraints](#constraint-search-constraint) to each search to narrow down the results

#### Learn more

Article: [Finding data](/help-guides/data/the-database/finding-data)

Core reference: [Search](/core-resources/data/search)

</details>

<details>

<summary>Data type</summary>

* A data type is any *type* of record that you set up your database, such as users, products, blog posts, tasks or whatever your app needs
* The *user* data type is built-in and cannot be deleted, but you can add as many fields as you need to it
* Data types apart from the built-in *user* type are known as *custom data types*

#### Learn more

Article series: [Data](/help-guides/data)

</details>

<details>

<summary>Debugger</summary>

* Bubble comes with a set of built-in debugging tools
* They let you debug workflows and elements in run-time (while you preview your app)
* The debugger appears automatically at the bottom of the screen when you preview your app

#### Learn more

* Article: [Debugging your app](/help-guides/maintaining-an-application/testing-an-application)
* Article: [Previewing your app](/help-guides/getting-started/navigating-the-bubble-editor/previewing-your-app)

</details>

<details>

<summary>Dynamic expression</summary>

* Dynamic expressions are like "live" formulas that update in real-time based on user input, database updates and other changes in your app.
* Dynamic expressions consist of three different building blocks called *atoms*:
  * [Data source](#data-source): any source of data
  * Operators: functions or actions that can be performed on the data source, such as counting, sorting and calculating
  * Comparisons: compare two compatible values, such as two numbers, data types or strings of text and get a yes/no result

#### Learn more

Article: [Dynamic expressions](/help-guides/logic/dynamic-expressions)

Core reference: [list of data sources](/core-resources/data/data-sources)\
Core reference: [list of operators and comparisons](/core-resources/data/operations-and-comparisons)

</details>

<details>

<summary>Edit mode</summary>

* When you are editing your app in the Bubble editor, as opposed to *Run mode.*

#### Learn more

* Article: [The Bubble editor](/help-guides/getting-started/navigating-the-bubble-editor)

</details>

<details>

<summary>Editor, the / Bubble editor</summary>

* The tool you use to edit your Bubble app
* Consists of the tabs (click for relevant articles)
  * [Design](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/design-tab)
  * [Workflow](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/workflow-tab)
  * [Data](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/data-tab)
  * [Styles](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/styles-tab)
  * [Plugins](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/plugins-tab)
  * [Settings](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/settings-tab)
  * [Logs](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/logs-tab)

#### Learn more

Article: [The Bubble editor](/help-guides/getting-started/navigating-the-bubble-editor)

</details>

<details>

<summary>Element</summary>

* Elements are the objects you place on the page when you design your app, such as text, buttons, images, icons and calendars
* Everything visible on a Bubble page is an element
* They are generally self-contained pieces of content that a user can see and potentially interact with

#### Learn more

Article series: [Design](/help-guides/design)\
Article series: [Elements](/help-guides/design/elements)

</details>

<details>

<summary>Environments (Development/Live)</summary>

* All deployed Bubble application consist of two different environments:
  * The **Development environment** allows you to develop and preview your app exactly as it will look when deployed. When testing changes in a branch in the Development environment, the Development database will get populated with test data, which you can view by going to the Data tab and toggling to the Development database.
  * The **Live environment** contains your live app, which is read-only. When users interact with your live app, the Live database will get populated with live data, which you can view by going to the Data tab and toggling to the Live database by clicking Switch to Live database.
* Environments are part of the [version control](#version-control) feature

#### Learn more

Article: [Previewing your app](/help-guides/getting-started/navigating-the-bubble-editor/previewing-your-app)\
Article series: [Version control](/help-guides/maintaining-an-application/version-control)

</details>

<details>

<summary>Event</summary>

* Events are the triggers that start a workflow
* Examples include:
  * An element being clicked
  * Certain data changing
  * A input form's value changing
  * A condition is true

Article series: [Workflows](/help-guides/logic/workflows)\
Article: [Events](/help-guides/logic/workflows/events)

Core reference: [list of all events](/core-resources/events)

</details>

<details>

<summary>Field / field type</summary>

* A field is the actual place in a [data type](#data-type) where the data is stored, and a data type can have many fields
* For example, on the built-in *user* data type, you might have the fields:
  * First name (text)
  * Last name (text)
  * Address (address)
* Some fields are built in:
  * Created date
  * Modified date
  * Unique ID
  * Slug
  * Email (only on the *user* data type)
* Fields can also contain a *list* of a specific type, such as a list of numbers, texts or custom data types

#### Learn more

Article series: [Data](/help-guides/data)\
Article: [Data types and fields](/help-guides/data/the-database/data-types-and-fields)

</details>

<details>

<summary>Group</summary>

* A group is a container element, used to contain other elements
* You can load data into a group, and refer to that data on the elements within it
  * For example, you can load a user into a group, and then show the users email using a text element and the [dynamic expression](#dynamic-expression) *Parent group's user's email*.
* Groups are also a way to control [responsive behavior](#responsive-design) in your app
* You can show and hide groups to navigate within the same page
  * You can collapse their width and height when hidden to set up single-page-application

#### Learn more

Article: [The Group element](/help-guides/design/elements/web-app/containers/groups)

Core reference: [List of container properties](/core-resources/elements/containers)

</details>

<details>

<summary>Login</summary>

* In this context, we mean logging into your *app* and not to your Bubble account
* Bubble features a built-in, secure login system that lets users [create an account](#user) and log in using their preferred credentials
* Using [plugins](#plugin), you can also allow users to log in using a third-party service like Google or Facebook
* Before users can log in, they must [sign up](#signing-up)

#### Learn more

* Article: [User accounts](/help-guides/data/user-accounts)

</details>

<details>

<summary>Logs / Server logs</summary>

* Every server-side action performed in a Bubble app is logged and can be viewed in the *Logs* tab in the Bubble editor
* You can use this for debugging your app by auditing the operations that have taken place at a specific time or by a specific user

#### Learn more

Article: [Server logs](/help-guides/maintaining-an-application/testing-an-application/using-server-logs)

</details>

<details>

<summary>Operator</summary>

* An operator is part of a [dynamic expression](#dynamic-expression)
* Operators are used to manipulate or aggregate data from a [data source](#data-source). For example:
  * Turning a string of text into UPPERCASE
  * Counting the number of characters in a string of text
  * Calculating the the number of results of a search
* Operators can be chained together
* Bubble will show relevant operators as you build your expression; or example, the *:number of characters* operator will be visible when you are working with text, but not when you are working numbers

#### Learn more

Article: [Dynamic expressions](/help-guides/logic/dynamic-expressions)

Core reference: [list of operators](/core-resources/data/operations-and-comparisons)

</details>

<details>

<summary>Option set</summary>

* An option set is a static collection of options that can contain multiple fields
* It behaves in much the same way as data types, but are not dynamic
  * This means they cannot be changed without re-deploying your app
  * It also means they cannot be updated by your users: only by a Bubble developer with access to the [Bubble editor](#editor-the-bubble-editor)
* They become part of your app's source code
  * This means they load faster than the database, and remains cached on the user's device
  * It also means they should not contain any sensitive data, since they are downloaded in plaintext to every device that accesses ay page in your app
* Option sets are great for storing data that doesn't change frequently, such as a list of:
  * Colors
  * Countries/states
  * Menu options
  * Dropdown options

#### Learn more

Article: [Option sets](/help-guides/data/static-data/option-sets)

</details>

<details>

<summary>Plugin</summary>

* Plugins are extensions that you can install to add features, elements or integrates third-party services.
* The plugin store features thousands of plugins.
  * Some are made by Bubble, while most are made by third parties
  * Some are free, while others require a one-time payment or subscription to use
* Plugins are installed and paid per app, not per Bubble account
* Agencies using the agency plan can install and use all plugins for free while the app is in development.

#### Learn more

Article: [Plugins](/help-guides/integrations/using-plugins)

</details>

<details>

<summary>Privacy / privacy rules</summary>

* Privacy refers to the protection of user data and information, ensuring it's accessed and shared only by the right user(s)
* Privacy and security are closely intertwined, but are not the same thing: privacy is a policy, while security is what maintains that policy
* Bubble uses privacy rules to protect data in the database from ever leaving the server if the user is not authorized to access it
* Privacy rules also protect data accessed through the Bubble API
* Privacy rules are applied to each data type as a condition (i.e. "If this user is logged in, they can access the data)

#### Learn more

Article: [Protecting data with privacy rules](/help-guides/data/the-database/protecting-data-with-privacy-rules)\
Article series: [Bubble security](/help-guides/security)

Core reference: [Privacy rules settings](/core-resources/data/privacy)

</details>

<details>

<summary>Property editor</summary>

* The property editor is the part of the Design tab that appears when you double-click on an element or click it in the element tree
* It's where you control the properties of that element
  * Different element types have different properties
* It can be made to always be visible by toggling *View > Lock Property Editor*
* This is also where you control an element's responsive properties

#### Learn more

Article: [The property editor](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/design-tab/the-property-editor)\
Article series: [The Bubble editor](/help-guides/getting-started/navigating-the-bubble-editor)

Core reference: [Element properties](/core-resources/elements/shared-properties)\
Core reference: [Responsive properties](/core-resources/elements/responsive-properties)

</details>

<details>

<summary>Repeating group</summary>

* A repeating group is a container element that lets you display a list of things from a data source
* This can be any type of data: [things](#thing-database) from the database, a list of texts, the results of an [API call](#app-connector-plugin) or any other array of data
* Repeating groups can be set up to be displayed horizontally, vertically and as a masonry grid
* You can place any kind of [element](#element) (like text and images) inside each cell of the repeating group, which can load data from the thing in that cell

#### Learn more

Article: [Repeating groups](/help-guides/design/elements/web-app/containers/repeating-groups)\
Article: [Finding data](/help-guides/data/the-database/finding-data)

Core reference: [Repeating group properties](/core-resources/elements/containers#repeating-group)

</details>

<details>

<summary>Responsive design</summary>

* Responsive design means to design your app to adjust correctly to different screen sizes and resolutions
* The goal of responsive design is to allow one page to be equally useful and visually pleasing on different devices such as computers, tablets and mobile phones
* Bubble has an advanced responsive engine that allows you to set up pixel-perfect design and responsive rules to control the behavior on different screens

#### Learn more

Article series: [Responsive design](/help-guides/design/responsive-design)

Core reference: [List of responsive properties](/core-resources/elements/responsive-properties)

</details>

<details>

<summary>Run as</summary>

* The *Run as* feature lets you preview your app as a specific user
  * This is useful to debug issues: you can see the app exactly as the user experiencing an issue does
* It's available in both preview mode and your live app
* You will find it in the *Data > App Data > "All Users"* table. It's a small text link next to the user data in the table.

#### Learn more

Article section: [Previewing your app / Run as](/help-guides/getting-started/navigating-the-bubble-editor/previewing-your-app#run-as-a-specific-user)

</details>

<details>

<summary>Run-mode</summary>

* When your app is actually running and you have users engaging with it, i.e. when you're running your app, as opposed to editing it ([edit mode](#edit-mode))
* When you click *Preview* in the editor, Bubble opens up the app in run mode, just as if you had visited the URL of your app
* Both the "Development" version and the "Live" version of your app have run modes, i.e. if you are editing your app on the Development version and click "Preview", you will enter run mode of the Development version

#### Learn more

Article: [Previewing your app](/help-guides/getting-started/navigating-the-bubble-editor/previewing-your-app)

</details>

<details>

<summary>SEO</summary>

* SEO, or Search Engine Optimization, is the practice of optimizing your pages to rank highly in search engines such as Google and Bing
* It's only needed for pages that you want users to find in search engines, and not for locked pages (pages requiring login for example)
* SEO is generally done in two places in Bubble:
  * The overall app settings
  * Each individual page

#### Learn more

The article series below goes into both the theoretical part of SEO and how to set it up in Bubble.

* Article series: [SEO](#seo)

</details>

<details>

<summary>Signing up</summary>

* In this context, we mean signing up an account in your *app*, not signing up to a Bubble account
* Bubble features a built-in, secure sign-up system that lets users create an account and [log in](#login) using their preferred credentials
* Using [plugins](#plugin), you can also allow users to sign up using a third-party service like Google or Facebook

#### Learn more

* Article: [User accounts](/help-guides/data/user-accounts)

</details>

<details>

<summary>SPA (Single-Page Application)</summary>

* A Single-Page Application (often abbreviated to SPA) is a way to set up your app's navigation as a single page, as opposed to navigating between pages
* This is done by hiding and showing elements on the page
  * This is predominantly done using the group element, and collapsing its width and height when hidden to make room for another group
  * This happens instantaneously, making the switch between groups unnoticeable for the user

#### Learn more

Article series: [Navigation](/help-guides/logic/navigation)\
Article: [SPAs](/help-guides/logic/navigation/single-page-applications-spa)

</details>

<details>

<summary>Static data</summary>

* Static data in Bubble means data in your app that needs your app to be re-deplyed to be updated
* This includes
  * Option sets
  * App texts (translations strings)
  * Element data that's not the result of a dynamic expression (such as text in a text element)
* Static data like the ones mentioned above become part of your app's JavaScript code files, and should not contain any sensitive information

#### Learn more

* Article series: [Static data](/help-guides/data/static-data)

</details>

<details>

<summary>Style</summary>

* Styles are collections of styling properties (colors, borders, fonts, etc) that can be saved, named and applied to multiple elements
  * Styles are bound to one element type (i.e. styles for buttons, styles for text)
* When you create an app, a default set of styles are automatically generated
* Styles help keep the look and feel of your app consistent and speeds up development
  * They also help you make updates to multiple elements at once across pages. Any change you make to a style is automatically applied

#### Learn more

Article: [Styles](/help-guides/design/variables-and-styles/styles)

</details>

<details>

<summary>Template</summary>

* A pre-built app that you can use as a starting point for creating your own app
* Can only be used when starting a brand new app, i.e. cannot be applied retroactively
* Usually includes a combination of pages, elements, styles, workflows, etc
* There's a large collection of community-developed templates available (see link below)
  * Some templates are available at no cost, while others require a one-time payment per application

#### Learn more

Page: [Templates](https://bubble.io/templates)

Article: [Using templates](/help-guides/design/using-a-template)

</details>

<details>

<summary>Thing (database)</summary>

* A database thing is a single record of any data type in your database
* For example, one user who signs up in your app is one database thing

#### Learn more

Article series: [Data](/help-guides/data)

Core reference: [List of data sources](/core-resources/data/data-sources)

</details>

<details>

<summary>Unique ID</summary>

* Every database thing in Bubble is automatically assigned a unique ID
* I'ts a 32-character string that follows the following format:
  * 1675853365035x879057409457629600
* You cannot change or delete a unique ID
* If you have a SQL database background, the unique ID is the *primary key* of database records in Bubble

#### Learn more

Article series: [Data](/help-guides/data)

</details>

<details>

<summary>URL parameter</summary>

* A URL parameter is a way to pass and read data using the URL in the browser
* It follows a [key-value format](#user-content-fn-2)[^2] and starts with a "?" after the main URL.
  * For example, in "my-bubble-app.com/page?navigation=user-profile", "navigation=user-profile" is the URL parameter
* URL parameters can be used to pass any kind of data, including custom [data types](#data-type) (by passing the [unique ID](#unique-id) of the thing you want to identify)
  * The upside of using URL parameters is that users can use the *back* button in their browser to return to the previous URL if needed, meaning that they can use the back button in a [single-page application](#spa-single-page-application)
  * You set URL parameters using the go to page action, and the URL parameter is instantly applied without having to reload the page (if you are going to the same page the user is currently on).

#### Learn more

Article: [URL parameters](/help-guides/data/temporary-data/url-parameters)\
Article series: [Navigation](/help-guides/logic/navigation)

Core reference: [To to page](https://manual.bubble.io/pages/-MTujrgFLiHe7MfwSBOF#go-to-page-...)

</details>

<details>

<summary>User</summary>

* Users are a built-in data type in Bubble
* They are also the only data type that come with some specific things:
  * A built-in email field
  * An authentication system (allowing users to securely [sign up](#signing-up) and [log into](#login) your app)
* You can set up [privacy rules](#privacy-privacy-rules) that govern what database data a user has access to
* If a user has not yet signed up, Bubble automatically creates a temporary user that you can save data to. That data is transferred to the user when they sign up on the same device.

#### Learn more

* Article: [User accounts](/help-guides/data/user-accounts)

</details>

<details>

<summary>Version control</summary>

* Version control is a set of features that allows you to set up separate [branches](#branch) in the [Bubble editor](#editor-the-bubble-editor)
  * These branches allow team members to work on separate features in isolation
  * Changes that you make in one branch will not be visible in other branches before they are synced

#### Learn more

Article series: [Version control](/help-guides/maintaining-an-application/version-control)

</details>

<details>

<summary>Version-test</summary>

* The version-test is the preview version of your app
* You can see *version-test* in the URL when you preview your app:
  * <https://my-bubble-application.bubbleapps.io/**version-test**/page>
* The development environment has a separate, independent database from the live environment to help you test your app's features without affecting live data
* If you remove *version-test* from the URL, you will be taken to the live app instead (if your app has been deployed)

#### Learn more

Article: [Previewing your app](/help-guides/getting-started/navigating-the-bubble-editor/previewing-your-app)

</details>

<details>

<summary>Workflow</summary>

* A workflow is the combination of an [event](#event) (such as a button-click) and one or more [actions](#action)
* Workflows either belong to a specific page (and thus only run when a user is on that page) or is an API Workflow that can be run or scheduled server-side, or with an API call
* Workflows are what makes your app react to user interaction, including:
  * Making changes in the database
  * Hiding/showing elements
  * Navigating to another page or external site
  * Sending emails

#### Learn more

Article series: [Workflows](/help-guides/logic/workflows)

Core reference: [List of events](/core-resources/events)\
Core reference: [List of actions](/core-resources/actions)

</details>

<details>

<summary>Workflow API</summary>

* The Workflow API is a part of the Bubble API, and allows external apps and systems to trigger API workflows by making an API call to your app
* The Workflow API must be enabled in *Settings > API*
* The Workflow API lets you set up [API workflows](#api-workflows)

#### Learn more

Article series: [The Bubble API](/help-guides/integrations/api/the-bubble-api)\
Article series: [The Workflow API](/help-guides/integrations/api/the-bubble-api/the-workflow-api)

Core reference: [API workflow properties](/core-resources/api/the-bubble-api/the-workflow-api)

</details>

[^1]: A *workflow* is the combination of an event (trigger) and one or more *actions*. The actions determine the tasks that Bubble should perform when the workflow is triggered.\ <br>

    Glossary: [Action](#action)

[^2]: A *key-value format* pairs a unique identifier (the "key") with a specific piece of data (the "value").

    For instance, in the pair "color=blue", "color" is the key and "blue" is the value.\ <br>

    Article: [URL parameters](/help-guides/data/temporary-data/url-parameters)


# Getting started

How to use the Bubble Manual efficiently

The **Bubble user manual** is our long-form collection of articles that teaches you Bubble from start to finish. We've designed the manual so that you can read it from A-Z or pick the categories and specific articles that you need.

Instead of delving into each individual setting and property, we explore broader topics such as design, data and logic, and we even touch on subjects not directly tied to Bubble, aiming to foster an overall understanding of web applications at large.

{% hint style="info" %}
If you are looking for more concise, technical documentation for each of Bubble's features, you may be interested in checking out the [core reference](/core-resources/using-the-core-reference) section of the docs.\
\
In [New? Start here section](/new-start-here) we also[ list other resources](/new-start-here#learning-bubble) such as videos, user community and interactive tutorials.
{% endhint %}

## How to read the user manual

The user manual is designed like a guidebook, allowing you to either read it from beginning to end or select specific sections based on what you want to learn more about. If you have just getting started with Bubble, we recommend you check out each section and get to know the how the manual is structured.

This way, if you don't want to read every section in full now, you can return to any subject for a thorough overview when you need it.

## Manual sections

### Getting started

This section focuses on the basics of Bubble and is meant for users who are new to the platform. Here we'll explore the basic stuff such as creating an app, navigating the editor and changing the general application settings.

If you have played around with Bubble for a little while and know about the basic stuff already, you can use the navigation menu on the left to find the categories that interest you the most.

<figure><img src="/files/wkVBOyPzMF10k93aLUg1" alt=""><figcaption></figcaption></figure>

<details>

<summary><a href="/pages/9RCVasZdyLkM9y4EDWKV">Building your first app</a></summary>

This section takes a look at the theory and methods around planning your first app. We take a look at how you can break your app idea into features and versions, how you can plan out your database structure and finally how you make it ready for users with a user interface.

If you've never built an app before, we recommend starting here.

Article series: [Building your first app](/help-guides/getting-started/building-your-first-app)

</details>

<details>

<summary><a href="/pages/nQzG9RbssSW5tR7ij3DO">Creating and managing apps</a></summary>

Your Bubble account can hold as many applications as you want, and this section explores how you can organize your apps.

Article: [Creating and managing apps](/help-guides/getting-started/creating-and-managing-projects)

</details>

<details>

<summary><a href="/pages/TUZ0h06DHb0Ai3VKLGmN">Navigating the Bubble editor</a></summary>

Bubble contains all the tools you need to create awesome web applications, all wrapped into one editor. Learn how to navigate it in this article.

Article: [Navigating the Bubble editor](/help-guides/getting-started/navigating-the-bubble-editor)

</details>

<details>

<summary><a href="/pages/Sfb2EVgX6WfgYIibQCNa">Custom domain and DNS</a></summary>

Your Bubble app will automatically be assigned a unique URL when it is created, such as `https://my-bubble-application.bubbleapps.io`.

As you get close to launch, you may want to move it to your own domain, such as `https://www.my-bubble-application.com`.

This article explores how to connect your app to a custom domain and set up the correct DNS[^1] settings.

Article: [Custom domain and DNS](broken://pages/Sfb2EVgX6WfgYIibQCNa)

</details>

<details>

<summary><a href="/pages/bR8EH9jF6UdAZCRVr9Ci">Application settings</a></summary>

Each Bubble app comes with a set of general settings. This article goes through each different category of settings.

Article series: [Application settings](broken://pages/bR8EH9jF6UdAZCRVr9Ci)

</details>

### Design

Bubble is a visual tools that lets you design your app by dragging and dropping elements such as text, buttons, links, tables and even maps on a page. This section covers how you design your app and helpful tools such as styles[^2], [color/font variables](#user-content-fn-3)[^3] and the [responsive engine](#user-content-fn-4)[^4].

<figure><img src="/files/QxZ2UhWaQ6RQZU9yF090" alt=""><figcaption><p>Bubble lets you set up flexible, responsive design and save them in styles that can be applied every in seconds.</p></figcaption></figure>

<details>

<summary><a href="/pages/gfDGqXYljiG9Nd79xGsX">Elements</a></summary>

Elements are the things that you place on a page to display data and let your users interact with your app. Whether you want to create an elaborate presentation page with images and animations or a simple signup form, it's all done by combining different elements in a way that your users find useful and visually pleasing.

Bubble offers a lot of different elements serving different purposes and this article series covers them all.

Article series: [Elements](/help-guides/design/elements)

</details>

<details>

<summary><a href="/pages/kI37kzGcNwIZcS3B5hnN">Styling</a></summary>

Each element that you add to your page has a default *styling* applied to it. In short, styling refers to what your elements look like, and this is done by changing styling settings such as:

* Background color, gradient or image
* Border
* Shadow
* Font
* Transitions

Bubble also features ways to save styling settings to help you maintain and update how your app looks efficiently.

Article series: [Styling](/help-guides/design/variables-and-styles)

</details>

<details>

<summary><a href="/pages/bxQHKBdFeewZeW69BIEx">Responsive design</a></summary>

Responsive design is a method that gives your users a great experience no matter what kind of device they are using to access your app.\
\
Bubble features a *responsive engine* that lets you control every aspect of your app's responsiveness in the same visual editor that you use to build your pages. It uses a grid-based layout to change the appearance of the page depending on the screen size and orientation of the device.

Article series: [Responsive design](/help-guides/design/responsive-design)

</details>

<details>

<summary><a href="/pages/diMQVVnPUnq4Zdzh8nXO">The component library</a></summary>

The component library is a collection of pre-built User Interface (UI) components that can be dragged and dropped onto your page to help you build beautiful interfaces faster. These UI components are fully responsive and are made up of containers, visual elements, and form inputs that can be individually customized once added to your page. Each component is a fully independent unit, but can be connected to each other or other parts of your app by adding workflows and data.

Article: [The component library](/help-guides/design/the-component-library)

</details>

<details>

<summary><a href="/pages/-MTk5Hnu_r7T9Ri-JMCI">Importing from Figma</a></summary>

Many users have a background from Figma. Bubble has a tool for importing your Figma design into Bubble, maintaining style attrbiutes such as colors, fonts, shadows, etc. This article covers how the import process works.

Article: [Importing from Figma](/help-guides/design/importing-from-figma)

</details>

### Data

Data forms the foundation of any application. Indeed, creating an app that doesn't handle data in some way is unusual. Be it data produced by your users, added by you, or imported from external applications or services, the capability to collect, secure, and present information essentially drives the existence of most apps.

<figure><img src="/files/S3Zumr5hD0JikAchY06C" alt=""><figcaption><p>Bubble's database editor lets you create, assign fields to and connect data types with no database experience.</p></figcaption></figure>

<details>

<summary><a href="/pages/gLu18TiwIkcqZ22AyY0G">The database</a></summary>

The database manages all the dynamic data that both you and your users have added. You can create, modify, view, and delete data as needed and run bulk operations to make major updates.

Data in the database is stored securely on Bubble's servers.

Article series: [The database](/help-guides/data/the-database)

</details>

<details>

<summary><a href="/pages/Yl9KHGZqHF3obLdBpalf">Files</a></summary>

Bubble offers integrated tools within the editor and your app for handling file and image uploads and storage.

Article: [Files](/help-guides/data/files)

</details>

<details>

<summary><a href="/pages/VqGXFhButs1yvnvydd5E">Static data</a></summary>

Static data in Bubble is defined as data that requires your app to be re-deployed to update and cannot be changed by your application's users (unlike dynamic and temporary data).

This is useful for saving data that you need to re-use in your app, but that doesn't change very often, such as strings of text and options in a list.

Article series: [Static data](/help-guides/data/static-data)

</details>

<details>

<summary><a href="/pages/ZYVKwvJ4UR8INiEo6CVk">Temporary data</a></summary>

Sometimes you'll need to store some information temporarily in your application. They can be considered *variables* and are not in any permanent storage like the database, but live for as long as they are needed.

This is useful for holding some information while the user is still one the same page and for navigation.

Article series: [Temporary data](/help-guides/data/temporary-data)

</details>

<details>

<summary><a href="/pages/laQnSk5xZE94vvU54pjm">User accounts</a></summary>

Bubble has a built-in feature to handle user accounts. In other words, your users can create and log in to their account purely with built-in tools that are secure, efficient and easy to set up.

Article series: [User accounts](/help-guides/data/user-accounts)

</details>

### Logic

The *Logic* section covers how to make Bubble *do* stuff: by combining workflows and expressions, you can set Bubble up to perform tasks for your users ranging from very simple to highly complex.

<figure><img src="/files/5ldQs5MrO7irFADCa9Xw" alt=""><figcaption><p>Bubble's workflow editor lets you drag and drop actions that happen whenever an event takes place, such as a button click.</p></figcaption></figure>

<details>

<summary><a href="/pages/gEmgcqoza1M3mgHUhriC">The frontend and the backend</a></summary>

To understand Bubble, is useful to understand the difference between things happening on the page, and things happening on Bubble's server. This article takes a theoretical look at how the two are different – and how they work together constantly to make your app work.

Article: [The frontend and the backend](/help-guides/logic/the-frontend-and-backend)

</details>

<details>

<summary><a href="/pages/SQnm2X2Fop4KzWmR4Mlb">Workflows</a></summary>

Workflows in Bubble are sequences of actions that automate your app's operations. They trigger in response to events such as a button click or page load, allowing you to create dynamic and interactive applications. In short, they are what makes your app *do* something.

Article series: [Workflows](/help-guides/logic/workflows)

</details>

<details>

<summary><a href="/pages/qHBHly6uKTtCCrtvOPiS">Dynamic expressions</a></summary>

Dynamic expressions are like "live" formulas that update in real-time based on user input, database updates and other changes in your app. They can fetch data, change and combine texts, calculate numbers and dates and a lot of other things.

Article: [Dynamic expressions](/help-guides/logic/dynamic-expressions)

</details>

<details>

<summary><a href="/pages/unztFotwv8VfccqlzABb">Conditions</a></summary>

Conditions in Bubble are the rules you set to make your application react to different scenarios. They define the "if this, then that" logic in your app, controlling how elements behave, look or function based on different variables, like user inputs or data changes.

Article: [Conditions](/help-guides/logic/conditions)

</details>

<details>

<summary><a href="/pages/BcMBsNhBTtMDfgSolC2r">Navigation</a></summary>

Navigation in Bubble is the way you manage the flow between different pages or parts of your app. It involves creating links, setting up redirects, or loading different groups or popups on a single page.

Article series: [Navigation](/help-guides/logic/navigation)

</details>

### Maintenance

Every app requires some testing and maintenance, and Bubble has many different tools for staying on top of it, as well as advanced collaboration features for teams.

<details>

<summary><a href="/pages/-MTkIy-K-DG2cISyoTOc">Collaborators</a></summary>

One of Bubble's big strengths is the ability to add up to 40 collaborators to your projects. Collaborators are registered Bubble users that you can give access to the editor in a specific app so that you can work on it together.

You can also set up independent iterations of your app (called *branches*) that allow your collaborators to concentrate on specific features and changes, maintaining their work separate from other ongoing projects. See [*Version control*](#version-control) below.

Article: [Collaborators](/help-guides/maintaining-an-application/collaboration)

</details>

<details>

<summary><a href="/pages/06P6y2ELgA1sBiRrXCwv">Version control</a></summary>

Bubble's version control system lets you divide the development of your project into independent parts, so that you and other editors with access to the project can iterate on one part of the app without impacting other parts. This helps you simultaneously progress multiple streams of work and stay on top of the changes made as each goes from development to testing to deployment.

Article series: [Version control](/help-guides/maintaining-an-application/version-control)

</details>

<details>

<summary><a href="/pages/-MTkIwWoz9rJgDsRLpdN">Commenting</a></summary>

Every part of Bubble has a built-in commenting system that lets you add notes to it. For example, you can use commenting to describe what a particular workflow, element or data type is for. This helps you remember important details in your app, and makes it simpler to collaborate with other developers.

Article: [Commenting](/help-guides/maintaining-an-application/commenting)

</details>

<details>

<summary><a href="/pages/xCKerH28ZYNhKkiEvlMh">Database maintenance</a></summary>

Bubble offers multiple tools for maintaining a healthy, secure and performant database. This article series takes a look at how you copy database versions, maintain backups and run bulk operations.

Article series: [Database maintenance](/help-guides/maintaining-an-application/database-maintenance)

</details>

<details>

<summary><a href="/pages/-MTkR8OttoIETBdnjMDe">Performance</a></summary>

As your app scales, it's important to stick to some best practices to make sure it keeps performing well. This article series explores useful tips, as well as looking at some of the technical soft and hard limits in Bubble's different features.

Article series: [Performance](/help-guides/maintaining-an-application/performance-and-scaling)

</details>

<details>

<summary><a href="/pages/-MUUJN6HhjaypyafDea8">SEO</a></summary>

SEO (or Search Engine Optimization) is the art of setting up a web page in a way that makes search engines give it a high ranking in its search results. In this section, we'll explore whether this is important for your app, and different ways to prepare your app for the search engine algorithms.

Article series: [Search Engine Optimization (SEO)](/help-guides/maintaining-an-application/seo)

</details>

<details>

<summary><a href="/pages/-MTg81RZPUegkcpad4Ss">Testing and Debugging</a></summary>

Every app needs to be tested and sometimes debugged both before it's published, whenever you add new features and to find the root cause of any issues that come up in the meantime. Bubble gives you tools to preview, test and debug your app isolated from the live version.

Article series: [Testing and debugging](/help-guides/maintaining-an-application/testing-an-application)

</details>

<details>

<summary><a href="/pages/-MTWRsY-MosFj_k0lrty">API workflow Scheduler</a></summary>

API Workflows are scheduled in a log where they can later be cancelled before they run if needed. Bubble's API Workflow Scheduler lets you show all the scheduled workflows and pause or cancel them.

Article:[ API workflow scheduler](/help-guides/maintaining-an-application/scheduler)

</details>

### Integrations

Among Bubble's most powerful features is its set of tools to connect your app to other apps and systems.

<details>

<summary><a href="/pages/smc0W1QK1tO7PFIFAhwD">API</a></summary>

By using what's called an API connection, your application can fetch data and execute commands in external software systems and vice versa.

Article series: [API](/help-guides/integrations/api)

</details>

<details>

<summary><a href="/pages/-MSIbm776F_yhvs4D1oX">Plugins</a></summary>

Bubble's plugin store contains thousands of plugins that extend the platforms core feature set. This can add new elements to your page, connect to external services, do complex calculations and a lot more.

Article series: [Plugins](/help-guides/integrations/using-plugins)

</details>

<details>

<summary><a href="/pages/HzkD4hY7qhbTFgAew8d9">SQL database connector</a></summary>

Sometimes you'll need to connect to an external database to make the most of your Bubble app. The SQL Database Connector Plugin is a handy tool that allows you to connect to databases like:

* Postgres
* MySQL
* Microsoft SQL

By running SQL queries from within Bubble, you can access information or trigger actions.

Article: [SQL database connector](/help-guides/integrations/sql-database-connector)

</details>

<details>

<summary><a href="/pages/yepHTamRHYOWZWpo6kdZ">Bubble app connector</a></summary>

Bubble can connect to most other apps by using the [API Connector](#user-content-fn-5)[^5] and/or the [SQL Connector](#user-content-fn-6)[^6]. Sometimes you may want to connect to another Bubble application, and we have made that option simple to set up using the plugin *Bubble App Connector plugin.*

Article: [Bubble app connector](/help-guides/integrations/bubble-app-connector)

</details>

### Infrastructure

<details>

<summary><a href="/pages/is1W2J1Bh7vPsG47VZO9">Security</a></summary>

Many apps will be handling sensitive information on behalf of its users. Our article series on Security shows you how you keep data safe, set up secure connections with external systems and avoid both malicious attacks and accidental data leaks.

Article series: [Security](/help-guides/security)

</details>

<details>

<summary><a href="/pages/-MUUJOSDo470wMy7eV2A">Sub-apps</a></summary>

The sub-app feature sets up a relationship between a “main app” and one or more “sub applications” and makes it easier to push any changes from the main app to its sub apps, while all main and sub apps have their own database. This is especially useful for certain ideas that involve setting up different (sub/)domains for different clients, which is common in SaaS applications.

Article: [Sub-apps](/help-guides/optimizing-an-application/sub-apps)

</details>

<details>

<summary><a href="/pages/-MUUJKlJtBmPhrbqluXI">Bubble Release Tiers</a></summary>

The Bubble engine is evolving all the time - on a typical workday, we'll have multiple code rollouts representing any combination of bug fixes, infrastructure improvements, new features, and more. In this article, we'll look at the different options available for applying these updates.

Article:[ Bubble release tiers](/help-guides/optimizing-an-application/bubble-release-tiers)

</details>

<details>

<summary><a href="/pages/n4UwuzLKUnMbpooPXC2X">Hosting and scaling</a></summary>

Bubble is not only a no-code platform, but a complete hosting solution that automatically scales as needed. In this article series we'll cover what exactly is included and how the system is designed to scale seamlessly.

Article series: [Hosting and scaling](/help-guides/optimizing-an-application/hosting-and-scaling)

</details>

<details>

<summary><a href="/pages/Zx7i8rIekigFyOdABHj9">Compliance</a></summary>

When you're developing on Bubble, it's essential to get familiar with different compliance frameworks. They're not just a checklist; they're about building trust, ensuring your users' privacy, and meeting all the necessary legal requirements.

In this article series, you'll learn about some of the top compliance frameworks and how they fit into your app's development on Bubble.

Article series: [Compliance](/help-guides/optimizing-an-application/compliance)

</details>

### Bubble for Enterprise

<details>

<summary><a href="/pages/djgPGL4Ddhqxhz1OORFE">Bubble for Enterprise</a></summary>

Bubble's Enterprise plan is specifically designed to meet the demands and compliance requirements of large organizations. Beyond the standard features, Enterprise plan clients benefit from enhanced capabilities like centralized management for both users and apps, a dedicated support team, and the flexibility to choose their hosting location. Furthermore, our commitment to stringent security measures — from [SOC 2 Type II](#user-content-fn-7)[^7] security compliance to advanced DDoS[^8] protection — means that business operations remain robust and protected. Plus, with flexible payment options like invoicing or ACH[^9], Bubble aligns with the financial workflows of large-scale organizations.

Dive in to understand how our enterprise solutions can enable your organization's digital transformation.

Article series: [Bubble for Enterprise](/help-guides/bubble-for-enterprise)

</details>

## Additional resources and getting help

We have a lot of different resources available to help you on your learning journey – everything from video lessons, live coaches and bootcamps. We list the different resources in the introductory article below:

Article section: [Additional learning resources and getting help](/new-start-here#more-learning-resources)

[^1]: DNS (Domain Name System) is a system that translates human-readable domain names (like bubble.io) into IP addresses (like 192.168.0.1) that computers use to identify each other on the internet.\
    \
    This is how a user's browser knows how to communicate with the Bubble server.\
    \
    Article: [Custom domain and DNS](broken://pages/Sfb2EVgX6WfgYIibQCNa)

[^2]: Bubble lets you set up overarching *Styles* for elements within your app to streamline the design process and promote consistency. By modifying a single style, you can effortlessly restyle all related elements, making it easy to set up and manage a cohesive look and feel in your app.\
    \
    Article: [Styles](/help-guides/design/variables-and-styles/styles)

[^3]: *Style variables* are used to store specific colors and fonts that can then be applied to elements and styles throughout your app. This ensures that your use of fonts and colors remains consistent and is easy to switch out later if needed: for example, replacing a color variable will update *all* styles that use this variable instantly.\
    \
    Article: [Color variables](/help-guides/design/variables-and-styles/color-variables)\
    Article: [Font variables](/help-guides/design/variables-and-styles/font-variables)

[^4]: Responsive design is a method that gives your users a great experience no matter what kind of device they are using to access your app.\
    \
    Bubble features a *responsive engine* that lets you control every aspect of your app's responsiveness in the same visual editor that you use to build your pages. It uses a grid-based layout to change the appearance of the page depending on the screen size and orientation of the device.\
    \
    Article: [Responsive design](/help-guides/design/responsive-design)

[^5]: The *API Connector* is used to make outgoing connections to external applications and use it as a data source or trigger actions.

    Article: [The API Connector](/help-guides/integrations/api/the-api-connector)

[^6]: The SQL Database Connector plugin lets you connect with external SQL databases.\
    \
    Article: [The SQL Database Connector](/help-guides/integrations/sql-database-connector)

[^7]: *SOC 2 Type II* is a compliance framework that evaluates a service organization's controls for security over time. It provides an attestation report on the effectiveness of these controls.\
    \
    Article: [Soc 2 Type II](/help-guides/optimizing-an-application/compliance/soc-2-type-ii)

[^8]: A *Distributed Denial of Service (DDoS)* attack floods a website or network with excessive traffic, making it unavailable to legitimate users. It aims to disrupt service rather than steal information.

[^9]: Automated Clearing House (ACH) is an electronic payment system that facilitates bank-to-bank transfers in the U.S.


# Building for...

A native mobile app and a web app are not fundamentally different. They both offer the user a visual interface built on top of a foundation of workflows and a database. The primary difference lies in how they are delivered to the user.

A web app runs within a browser, relying on HTML, CSS, and JavaScript, and is accessed via a URL. A native mobile app, on the other hand, runs directly on a mobile device's operating system, allowing for deeper integration with device-specific features such as cameras, push notifications, and biometric authentication.

If you come from a non-technical background, and especially if you have no experience developing apps for Android and iOS, the difference between a web app and a native mobile app might not be entirely clear.

Before diving into how to build native mobile apps, let’s first explore the key differences between these two types.

### Web applications

As we've explored, a web app is a set of code files made up of HTML, JavaScript, and CSS, which all modern web browsers can interpret, display, and interact with. In short, this means a web app operates within a web browser and cannot run independently of it.

There are many advantages to this approach. Although web browsers are developed by different companies and can have slight variations in behavior, they are all designed to maintain full compatibility with a standard set of programming languages like HTML, CSS, and JavaScript. This ensures that web apps created on platforms like Bubble can run consistently across a wide range of devices and operating systems, whether on a desktop, tablet, or smartphone.

Additionally, because web browsers are universally available and regularly updated to meet evolving standards, you don’t need to worry about platform-specific requirements. Your app can be accessed by anyone with a browser, without the need for users to download or install anything. This makes it easier to reach a broader audience while maintaining consistent functionality across various environments.

This approach also simplifies deployment and updates since changes to your app are immediately available to all users without requiring them to manually update anything on their end.

Bubble includes an advanced responsive editor that lets you design pages that automatically adjust to different devices and screen sizes. This ensures that your app’s pages look great and function smoothly whether viewed on a large desktop monitor or a small mobile screen. By adapting layouts and element sizes based on the user’s device, you can deliver a consistent, optimized experience across all platforms.

This approach makes a responsively designed Bubble app just as available on a mobile device as on a desktop computer, but from the perspective of a mobile device user, it comes with a few limitations that a native mobile app does not.

### Native mobile applications

Unlike web applications that run inside a browser, native mobile applications are specifically designed to run directly on a mobile device’s operating system, such as Android or iOS. This allows them to fully integrate with the [device’s hardware and software features, such as GPS, camera, and push notifications](#user-content-fn-1)[^1].

Native apps offer a few key advantages. They can be installed directly from the Apple App Store or Android Play Store without relying on a third-party service, and they don’t rely on a web browser to function. They can leverage platform-specific features and design standards, giving users an experience that feels more integrated with their device. For example, native mobile apps can take full advantage of gestures, system-level navigation, and native UI components that users are already familiar with.

The two methods of development are by no means mutually exclusive; in fact, many of today’s most popular web applications offer both a web version that users can access through any browser on any device, and a native mobile app specifically built to be downloaded in one or both app stores and leverage the features of a mobile device.

You can get started with mobile development using the resources below:

Article series: [Building for native iOS and Android](/help-guides/getting-started/building-for.../native-ios-and-android)\
Article: [Mobile app quick start guide](/help-guides/getting-started/building-for.../native-ios-and-android/mobile-app-quick-start-guide)

#### Cross-platform compatibility

Building and maintaining native apps traditionally requires building separate versions for each platform (iOS and Android), which can be time-consuming and costly. Bubble’s Native Mobile App Editor aims to bridge that gap, allowing you to build native apps using Bubble’s visual tools, while maintaining the ability to deploy your app to both major platforms.

## Learn more about building for web and native mobile

To learn more about the differences between the two, keep reading this article series:

{% content-ref url="/pages/qjpk9D8O5tZqZnOI1t6M" %}
[Web](/help-guides/getting-started/building-for.../web)
{% endcontent-ref %}

{% content-ref url="/pages/EVWy0mSe4tjmyf8ejh8Y" %}
[Native iOS and Android](/help-guides/getting-started/building-for.../native-ios-and-android)
{% endcontent-ref %}

### Web wrappers and PWAs

There are various third-party and custom code solutions that allow you to publish a Bubble app to one or both app stores. Many of these options, including plugins and services, enable access to on-device features like biometrics[^2], file storage, the camera[^3], and contacts[^4]. These solutions work by “wrapping” your web app in a native container, making it distributable through app stores while still relying on web technologies. Essentially, web wrappers function like a “full-screen browser,” masking the fact that the app is running on web technology in the background.

Progressive Web Apps (PWAs) are another alternative that lets users add your app to their home screen, but PWAs have more limited access to native device features compared to fully native apps.

Both of these solutions have been around for years and currently power many successful Bubble apps in the app stores. However, they come with certain limitations. While they can offer access to native features and provide a way to distribute your app on iOS and Android, they may not fully match the performance, seamless user experience, or deeper device integration that a fully native app can provide. They also rely on third-party solutions (services and/or plugins) and/or custom code to work.

[^1]: These features are known as on-device resources. Read more in the core reference section below:\
    \
    Reference: [On-device resources](/core-resources/on-device-resources)

[^2]: In this context, *biometrics* refers to allowing users to log in using their device's biometric methods, such as fingerprint or face recognition.

[^3]: The *camera* in this scenario means access to the on-device camera of a phone or tablet.

[^4]: *Contacts* in this scenario means to have access to the contact list (or phone book) stored on the device.


# Web

A web app is an application that runs in a web browser. Unlike native mobile apps, which are installed directly onto a user’s device via an app store, a web app lives online—accessible by visiting a URL. This includes everything from productivity tools like Google Docs, to social platforms like Facebook, to AI interfaces like ChatGPT.

You don’t need to install a web app to use it. You simply open a browser (like Chrome, Safari, or Firefox), enter the web address, and sign in. Updates happen automatically, and the experience remains consistent across platforms, as long as the browser supports the features used by the app.

## How is a web app different from a native mobile app?

Native mobile apps are designed to run directly on a device’s operating system (iOS or Android). A web app, on the other hand, runs inside a browser and relies on an internet connection to access its content and features.

Here are some of the main differences:

### **Installation**

* **Web app**: No installation required. Accessed by URL.
* **Native mobile app**: Installed from the App Store or Google Play Store.

### **Platform integration**

* **Web app**: Limited access to device hardware (e.g., camera, GPS), usually via browser APIs.
* **Native mobile app**: Deep integration with device features, like push notifications, local storage, Bluetooth, and offline access.

### **Distribution**

* **Web app**: Instantly accessible—share a link, and users can open it right away.
* **Native mobile app**: Requires submission and approval from app stores. Users must download and install the app before using it.

### **Updates**

* **Web app**: Updated centrally. Users always access the latest version.
* **Native mobile app**: Users may need to update manually or wait for updates to be pushed via the app store.

## Building a web app

When you build a web app with Bubble, there's no need to write code. Instead, Bubble uses a combination of AI and visual tools to let you define how your app looks, how it works (with workflows, conditions, and dynamic expressions), and how it stores information using a built-in database.

Behind the scenes, Bubble automatically generates the code that makes your app run: HTML for structure, CSS for styling, and JavaScript for interactivity. This output is fully compatible with modern browsers like Chrome, Safari, Edge, and Firefox, so your app works seamlessly across devices.

This output is what allows your app to run in a browser. Because it uses standard web technologies, your app works across all modern browsers—like Chrome, Safari, Edge, Firefox, and others—without requiring any adjustments.

The generated code is also set up to communicate directly with Bubble’s servers. This connection is what powers everything from your app’s database to user authentication, APIs, and encryption. Bubble manages all of these backend systems for you, so you don’t have to worry about setting up infrastructure or maintaining servers.

This is what makes building with Bubble so efficient: while you focus on the interface and logic, Bubble handles everything technical under the hood, and it's all compatible with all modern browsers.

### Can't I just run my web app on a mobile device?

Sure you can! But with one caveat: the user has to access the app through the browser on their mobile device. That's not to say there's anything wrong about that: you probably already use many web apps through your phone's browser already, but they come with some restrictions:

* Some browser-based APIs (like location or camera access) are available, but with restrictions.
* You won’t be able to send push notifications or use offline features unless you're using advanced patterns like Progressive Web Apps (PWAs).
* The app runs in the browser window, which may include the browser’s interface elements (e.g., the address bar or back button).

### When is a web app the right choice?

A web app is often the best fit when:

* You want to build and iterate quickly without going through app store approval processes.
* Your audience is spread across devices and platforms.
* You want users to access your app via a link—instantly and without installation.
* Your app doesn’t need deep access to device hardware (like Bluetooth or offline file access).

### Can I build both?

Yes—and Bubble is designed specifically to support that. You can build your web app and native mobile app in the same editor, with both versions sharing the same database and backend workflow logic.

That’s one of the things that makes Bubble so powerful: in traditional development, you’d need to build two separate apps and connect them through complex API calls and data handling. With Bubble, features like user authentication, database access, and workflows are already integrated and shared across platforms.

You’re free to start with a web app, a mobile app, or focus on just one—there’s no need to decide upfront. If you’ve built a successful web app, you can extend it into a native mobile app at any time. Bubble is built to support both, and you can make that decision at any point in time.

If you want to learn more about native mobile apps, keep reading our [Building a native iOS and Android app in Bubble section](/help-guides/getting-started/building-for.../native-ios-and-android).


# Native iOS and Android

{% hint style="warning" %}
The native mobile app editor is currently in **beta**, meaning that you may experience issues or limitations while building or testing your app. Features may change, and not all functionality is final. We recommend thorough testing before publishing, and welcome feedback to help improve the experience.
{% endhint %}

{% hint style="warning" %}
**Android compatibility:** Bubble supports Android version 12 and later. Apps may not run reliably on earlier versions.
{% endhint %}

## Navigating

Web apps and native mobile apps are built within the same environment and project in Bubble. Rather than thinking of them as separate projects, they are just different tools within the same Bubble editor, used to develop both the web and native versions of your app seamlessly.

However, web apps and native mobile apps come with some differences in methods and terminology, and we've divided this introductory guide into three sections:

### Understanding the native mobile tools

In this section, we’ll explore how Bubble's native mobile app tools integrates with the same environment used for web app development. Rather than being a completely separate editor, it offers features that allow you to design and deploy native mobile apps alongside your web app in a unified workspace and with a shared backend and database.

We’ll highlight both its current capabilities and what you can expect as the feature continues to evolve.

Article: [What is a native mobile app?](/help-guides/getting-started/building-for.../native-ios-and-android/what-is-a-native-mobile-app)

### How is native app development and web app development different?

In this section of the documentation, we'll explore the differences between web apps and native mobile apps, delving into the core principles of each to understand how they are used in development and how they function differently both in the editor and in the final app.

Article: [Native mobile app development and web app development](/help-guides/getting-started/building-for.../native-ios-and-android/native-mobile-vs.-web-development)\
Article: [Differences in native and web elements](/help-guides/getting-started/building-for.../native-ios-and-android/differences-in-native-and-web-elements)

### Terminology

The native mobile app editor introduces some new terminology. We recommend reviewing the table of terms and definitions before diving into the rest of the documentation, and encourage you to refer back to it whenever needed for clarification as you explore the editor’s features.

Article: [Native mobile app editor terminology](/help-guides/getting-started/building-for.../native-ios-and-android/native-mobile-app-terminology)


# Mobile app quick start guide

Building a native mobile app with Bubble opens the door to new ways of designing, structuring, and delivering experiences — all while using the same powerful visual development tools you're already familiar with. This guide walks you through each step of the journey, from setting up your app’s interface to launching it on the App Store and Google Play.

## Getting started quickly

### 1 – Your frontend: Mobile building blocks

**If you’ve already built a web app in Bubble**, building for mobile will have some similarities and differences Your backend setup—things like your database, workflows, conditions, and logic—can stay exactly the same. But to take advantage of what makes native mobile apps unique, you will have to adapt the the frontend. Mobile apps follow different design patterns and user expectations, so the interface will need to be reimagined using mobile-specific building blocks like mobile views, mobile elements, and native navigation. The layout is more constrained, the interaction patterns are different, and performance expectations are higher—especially on smaller screens and devices.

**If you're starting fresh with mobile**, you'll learn how to build native mobile apps without any code. We recommend starting with our [Getting Started with Mobile series on YouTube](https://www.youtube.com/@BubbleIO/videos) to get a clear overview from Matt Neary, but you can also use this Quick Start guide to learn more.

#### Mobile views:

Instead of web “pages,” mobile apps have “views.” In your **app interface manager**, add and manage views like:

* [Tab views](/help-guides/design/elements/ios-and-android-app/the-view#tab) for primary navigation
* [Stack views](/help-guides/logic/workflows/actions#stack) for a smooth "back" button experience
* [Modal views](/help-guides/logic/workflows/actions#modal) for focused tasks like forms or settings

#### Mobile elements:

Swap out common web elements with native mobile elements:

* [Sheets](/help-guides/design/elements/ios-and-android-app/containers#sheets) instead of popups
* [Short lists](/help-guides/design/elements/ios-and-android-app/containers#short-list) and selectable lists instead of repeating groups

Need inspiration? Check out platforms like [Mobbin](https://www.mobbin.com/) to see how leading mobile apps structure\
their layouts.

### 2 - Adding native mobile features

A key difference between web and native mobile apps is that native apps can interact directly with the device they’re installed on. This gives you access to features like the camera, photo library, location services, and push notifications.

With Bubble, you're not limited to wrapping a web app. You can build a true native experience that takes full advantage of mobile capabilities and feels integrated, responsive, and intuitive to users.

#### Dropdowns:

* [**Bottom sheets**](/help-guides/design/elements/ios-and-android-app/containers#sheets) – These are mobile-native elements that slide up from the bottom of the screen and feel familiar to mobile users. To use one, add the **Sheet** element from the visual elements panel. Sheets can contain other elements just like a regular group.
* [**Modals with selectable lists**](/help-guides/design/elements/ios-and-android-app/input-forms#selectable-lists) – For cases where users need to pick from a list (like choosing a category or filter), you can add a **Sheet** or **Modal** element, then place a **Selectable List** inside it. This creates a mobile-friendly alternative to dropdowns that’s easier to use on smaller screens.

Both approaches give you more control over the design and behavior of the selection experience—especially helpful on mobile where space and interaction patterns are different from the web.

#### Swipe actions:

[Swipe actions](/help-guides/design/elements/ios-and-android-app/containers#list-item-swipe-actions) let users swipe left or right on list items to trigger actions like deleting or archiving — a familiar mobile interaction pattern.

Swipe actions are available for **Vertical** and **Section Lists**, and each list item can include up to **three swipe actions** on each side:

* **Leading** (left) actions
* **Trailing** (right) actions

To add swipe actions:

1. Add a Vertical or Section List to your view.
2. Select the **List Item** template.\
   On the canvas, hover over the list item and click the **leading** or **trailing** swipe area to edit it.
3. Drag visual elements (icons, labels, etc.) into the swipe frame to represent the action.
4. In the workflow editor, use the **When swipe action is tapped** event to define the behavior (e.g., delete a task or mark it complete).
5. Optionally, you can enable **full swipe to trigger** — letting the user trigger the action by swiping all the way across.

Swipe actions come with built-in animation and default behavior, making them quick to set up and easy to use. Layout controls are intentionally limited to ensure consistency and avoid clutter.

#### Device capabilities:

Native mobile apps can access on-device features like the camera, photo library, location services, and push notifications, helping you create more interactive, native-feeling experiences.

* [**Camera an photo library**](/help-guides/logic/device-resources/camera-photo-library): Use the *Take photo* and *Pick photo* actions in workflows to let users capture or upload images. Permission prompts are triggered automatically and can be customized in the *Language* settings.
* [**Location services**:](/help-guides/logic/device-resources/location-services) Use the *Get current location* action or *Current geographic position* data source to access the device's location. You can also check permission status using *Has granted location permission*.
* [**Push notifications**:](/help-guides/logic/workflows/actions#push-notifications) To send push notifications, you must first request permission using the Request push notification permission action. Then, use Send push notification to deliver messages to the user's device.You can also configure a **tap destination** on the Send push notification action to route users to a specific view — with optional parameters, overlays, and navigation context — when they tap the notification. Read more in [this article](/help-guides/logic/workflows/actions#sending-push-notifications).
* [**Deep links/universal links:**](/help-guides/logic/workflows/actions) use the Create a mobile deep/universal link workflow action to generate a shareable URL that opens your app to a specific view. Universal links are standard https\:// links on your app's domain, so they can be safely included in emails, SMS messages, and social posts; deep links use your app's custom scheme. Links use the same navigation model as push notification tap destinations — you configure a destination view, navigation type (stack or modal), overlay, base view, and view parameters. See [universal links](https://manual.bubble.io/help-guides/publishing-your-app/native-mobile-app/global-native-mobile-settings#universal-links) for setup.

#### Embedding web views:

You can embed web pages inside mobile views using the [**WebView** element](/help-guides/design/elements/ios-and-android-app/visual-native-app-elements#webview). This is useful if you’ve already built parts of your app for the web and want to reuse them without recreating everything from scratch. Simply add a WebView element to your mobile view and point it to the relevant page in your app.

Keep in mind:

* WebViews can only display pages from your own Bubble app — external URLs are not supported.
* Users can’t navigate to other pages inside the WebView, so make sure all needed content is on the same page.
* Native features like location, camera, or push notifications will not work inside a WebView. Use native mobile components if you need access to those.

### 3 – Adding mobile logic: Workflows

Once your mobile layout is ready, it's time to bring it to life with workflows[^1].

#### Frontend workflows:

Instant interactions that run on the user's device, such as:

* Tapping a button
* [Swiping](/help-guides/design/elements/ios-and-android-app/containers#list-item-swipe-actions) on a list item
* Requesting permissions[^2] (e.g., camera or location)

#### Backend workflows:

Behind-the-scenes processes shared between web and mobile, including:

* [Saving form submissions](#user-content-fn-3)[^3]
* [Triggering database updates](#user-content-fn-4)[^4]
* Integrating with a [third-party API](#user-content-fn-5)[^5]

### 4 - Testing your app

Testing is crucial to ensure your app performs smoothly across devices and scenarios.

#### [Web preview](/help-guides/previewing-your-app/previewing-a-mobile-app#web-preview):

Great for checking basic layouts and workflows, but it won't simulate mobile-only features like push notifications.

#### [BubbleGo (iOS and Android)](/help-guides/previewing-your-app/previewing-a-mobile-app#bubblego):

Download BubbleGo to preview your app on real devices — including full mobile functionality\
like camera access and swipe actions.

When you're ready to test with broader audiences, use Apple TestFlight or Google's internal\
testing tools. Make sure your Apple Developer and Google Play Console accounts are set up\
early — approvals can take time.

### 5 - Publishing to app stores

{% hint style="info" %}
Publishing to the app store involves several steps that take place outside of the Bubble platform, and the process can be a bit complex. To make things easier, we’ve created a detailed guide that walks you through each part of the process.

Article series: [Publishing your native mobile app](/help-guides/publishing-your-app/native-mobile-app)
{% endhint %}

Once you've tested thoroughly, it's time to share your app with the world!

#### Prepare your global mobile settings:

Fill out required settings like app names, icons, app schemes, and platform-specific metadata.

#### Follow our publishing guides:

* [Publish to the App Store (iOS)](/help-guides/publishing-your-app/native-mobile-app/ios-app-store)
* [Publish to the Play Store (Android)](/help-guides/publishing-your-app/native-mobile-app/google-play-store)

#### Plan for platform requirements:

Each store has its own guidelines and review processes, so make sure you meet their\
standards before submission.

When your app is live, celebrate by sharing it with the [Bubble community](https://forum.bubble.io/)!

### 6 - Updating your app after launch

#### [OTA (Over-the-Air) Updates](/help-guides/publishing-your-app/native-mobile-app#how-ota-over-the-air-updates-work):

Push minor changes - like text edits or design tweaks — without needing App Store or Play\
Store re-approval.

#### [New builds:](/help-guides/publishing-your-app/native-mobile-app#what-is-a-live-version)

Major updates, like adding new features, require a new build submission and app store approval.

Quick tip: Bubble supports multiple live versions, allowing you to update new users while\
letting existing users continue using previous versions if needed.

### 7 - Getting discovered: App Store Optimization (ASO)

After publishing, your next goal is to help users find your app.

#### Keywords:

65% of downloads come from search. Use strong keywords in your app's title and description.

#### Visuals:

Your app icon and screenshots are often your first impression — make them count.

#### Metadata and descriptions:

Highlight your app's biggest benefits right away and back them up with real numbers or examples when possible.

Building and launching a native mobile app with Bubble puts you in control of the entire process — from idea to live product — all within a single visual development platform. By following the steps in this guide, you'll not only create a mobile app that looks and feels polished, but also one that scales alongside your ambitions.

You’re building something incredible. Let’s bring it to life 🚀

[^1]: *Workflows* are the combination of an event (trigger) and one or more actions. This is how your app reacts to user interaction.

    Article series: [Workflows](/help-guides/logic/workflows)

[^2]: On-device resources like using the camera or current location will often require a permission given by the user.

    Article section: [Requesting permission to send push notifications](/help-guides/logic/workflows/actions#requesting-permission-to-send-push-notifications)

[^3]: Saving data to the database is done by creating new things, or by making changes to an existing thing.

    Reference: [Database actions](/core-resources/actions/data-things)

[^4]: *Database trigger events* let you trigger a workflow whenever a specific change happens in the database.\
    \
    Article: [Database trigger events](/help-guides/logic/workflows/events/backend-events/database-trigger-events)

[^5]: You can connect to third-party apps and systems with outbound or incoming connections.

    Articles series: [Integrations](/help-guides/integrations)


# What is a native mobile app?

A native mobile app is downloaded from the Apple App Store or the Google Play Store, and runs directly on the user’s device – without the need to open it in a web browser or rely on a “wrapper.” It is usually more performant, has native mobile interactions and accesses device hardware/software. [Learn more in the next section](/help-guides/getting-started/building-for.../native-ios-and-android/native-mobile-vs.-web-development).

### **How is the process different from building web apps?**

In essence – it isn't. You can use the same Bubble editor to build for web and mobile.

The tools for building the native mobile version of your app are built to work seamlessly with your existing Bubble projects. This means you don’t have to choose between building a web app or a native mobile app—you can add a mobile app to an existing web project and share the same database and backend workflows. Any changes made to the database by the web or mobile app will be instantly reflected across both platforms.

Since both apps exist within the same project, you manage them through the same Bubble Editor. The mobile app has its own views and workflows, similar to how web apps are managed today. While backend workflows can be shared between the web and mobile apps, you also have the flexibility to create workflows specific to one app without impacting the other.

### How can I adapt my web app to mobile app?

Before you start building your mobile app, it’s important to understand the differences in the design process. Historically, Bubble apps are built for web browsers and follow design principles suited for browser-based environments. Native mobile apps, however, are built using the design principles and components native to Android and iOS operating systems.

Because of these differences, you'll want to redesign your mobile interface to take advantage of all the native mobile elements, components, gestures, interactions and more. This means that any existing pages in your web app will need to be rebuilt specifically for the mobile app. While you can copy and paste certain elements and workflows from a web page to a mobile view, we recommend taking the time to rebuild these pages. Doing so allows you to fully leverage the native performance, snappiness, and platform-specific look and feel offered by the Native Mobile App Editor. [Learn more in the Building section.](broken://pages/EUOX7GzVotdvUxRxcGxs)

### How much does it cost?

**Current (Private Beta) Pricing:** Bubble's native mobile app builder is currently in private beta. During this phase, building, hosting, and deploying native mobile apps is free if you have a paid web app (deploying mobile apps to the app stores will require a paid web plan since mobile apps require a live backend).

**Future Pricing:** In the future, there will be an additional cost if you want to build a web app and mobile app in the same project. We will share more details about the different plans, and you will receive ample notice before any pricing changes take effect.

**Workload Usage**: Since mobile and web apps share the same backend and database, workload usage for your project will be cumulative across both platforms. This means the workload generated by your web and mobile applications is combined. In the future, additional filters in the Logs tab will be introduced to help you differentiate and track the relative workload usage and traffic from your web and mobile apps separately.


# Native mobile vs. web development

A native mobile app and a web app are not fundamentally different. They both offer the user a visual interface built on top of a foundation of workflows and a database. The primary difference lies in how they are delivered to the user.

A web app runs within a browser, relying on HTML, CSS, and JavaScript, and is accessed via a URL. A native mobile app, on the other hand, runs directly on a mobile device's operating system, allowing for deeper integration with device-specific features such as cameras, push notifications, and biometric authentication.

If you come from a non-technical background, and especially if you have no experience developing apps for Android and iOS, the difference between a web app and a native mobile app might not be entirely clear.

Before diving into how to build native mobile apps, let’s first explore the key differences between these two types.

### Web applications

As we've explored, a web app is a set of code files made up of HTML, JavaScript, and CSS, which all modern web browsers can interpret, display, and interact with. In short, this means a web app operates within a web browser and cannot run independently of it.

There are many advantages to this approach. Although web browsers are developed by different companies and can have slight variations in behavior, they are all designed to maintain full compatibility with a standard set of programming languages like HTML, CSS, and JavaScript. This ensures that web apps created on platforms like Bubble can run consistently across a wide range of devices and operating systems, whether on a desktop, tablet, or smartphone.

Additionally, because web browsers are universally available and regularly updated to meet evolving standards, you don’t need to worry about platform-specific requirements. Your app can be accessed by anyone with a browser, without the need for users to download or install anything. This makes it easier to reach a broader audience while maintaining consistent functionality across various environments.

This approach also simplifies deployment and updates since changes to your app are immediately available to all users without requiring them to manually update anything on their end.

Bubble includes an advanced responsive editor that lets you design pages that automatically adjust to different devices and screen sizes. This ensures that your app’s pages look great and function smoothly whether viewed on a large desktop monitor or a small mobile screen. By adapting layouts and element sizes based on the user’s device, you can deliver a consistent, optimized experience across all platforms.

This approach makes a responsively designed Bubble app just as available on a mobile device as on a desktop computer, but from the perspective of a mobile device user, it comes with a few limitations that a native mobile app does not.

### Web wrappers and PWAs

There are various third-party and custom code solutions that allow you to publish a Bubble app to one or both app stores. Many of these options, including plugins and services, enable access to on-device features like biometrics[^1], file storage, the camera[^2], and contacts[^3]. These solutions work by “wrapping” your web app in a native container, making it distributable through app stores while still relying on web technologies. Essentially, web wrappers function like a “full-screen browser,” masking the fact that the app is running on web technology in the background.

Progressive Web Apps (PWAs) are another alternative that lets users add your app to their home screen, but PWAs have more limited access to native device features compared to fully native apps.

Both of these solutions have been around for years and currently power many successful Bubble apps in the app stores. However, they come with certain limitations. While they can offer access to native features and provide a way to distribute your app on iOS and Android, they may not fully match the performance, seamless user experience, or deeper device integration that a fully native app can provide. They also rely on third-party solutions (services and/or plugins) and/or custom code to work.

### Native mobile applications

Unlike web applications that run inside a browser, native mobile applications are specifically designed to run directly on a mobile device’s operating system, such as Android or iOS. This allows them to fully integrate with the device’s hardware and software features, such as GPS, camera, and push notifications.

Native apps offer a few key advantages. They can be installed directly from the Apple App Store or Android Play Store without relying on a third-party service, and they don’t rely on a web browser to function. They can leverage platform-specific features and design standards, giving users an experience that feels more integrated with their device. For example, native mobile apps can take full advantage of gestures, system-level navigation, and native UI components that users are already familiar with.

The two methods of development are by no means mutually exclusive; in fact, many of today’s most popular web applications offer both a web version that users can access through any browser on any device, and a native mobile app specifically built to be downloaded in one or both app stores and leverage the features of a mobile device.

#### Cross-platform compatibility

Building and maintaining native apps traditionally requires building separate versions for each platform (iOS and Android), which can be time-consuming and costly. Bubble’s Native Mobile App Editor aims to bridge that gap, allowing you to build native apps using Bubble’s visual tools, while maintaining the ability to deploy your app to both major platforms.

## FAQ: Native mobile vs web development

<details>

<summary>Why isn't the repeating group available in native mobile apps?</summary>

The repeating group is a web-specific element. Native mobile apps handle lists differently — each operating system has its own native list components, which Bubble uses instead. This maximizes both compatibility and performance, as list rendering is handled natively by the OS.

</details>

<details>

<summary>Why does my app need to ask permission to send push notifications and access device features?</summary>

Operating systems require apps to request explicit user consent before accessing device features like the camera, location, or push notifications. This is enforced at the OS level — it's not a Bubble limitation. Users can grant or revoke these permissions at any time through their device settings.

</details>

<details>

<summary>Why do I need to submit my app to the App Store or Google Play?</summary>

Apple and Google require all native apps to go through their review process before they can be distributed to users. This is a platform requirement, not a Bubble limitation. Bubble simplifies this process by handling the build and packaging for you.

</details>

<details>

<summary>Can I use the same database for both my web and mobile app?</summary>

Yes. Bubble's backend is shared across your web and native mobile apps, so both use the same database, and data types. Changes made in either platform are immediately reflected in both.

</details>

<details>

<summary>Why do elements in my mobile app look different from my web app?</summary>

Native mobile apps use OS-specific UI components and layout patterns that differ from web conventions.

</details>

<details>

<summary>Can I use plugins in my native mobile app?</summary>

Plugins can be used in native mobile apps, but they aren't cross-platform compatible. Many of Bubble's own plugins work on mobile, but third-party web plugins need to be rebuilt to support it. Use the *Mobile* filter in the plugin sidebar to show only plugins that support native mobile.

</details>

<details>

<summary>Why does my app need to be rebuilt and resubmitted for some updates</summary>

Minor updates, such as text changes and UI tweaks, can be pushed via over-the-air updates without resubmission. However, changes that affect the app's native configuration, such as adding new device permissions, require a new build and app store submission.

</details>

<details>

<summary>Can I test my mobile app without submitting it to the app stores?</summary>

Yes. You can test your app on a real device using BubbleGo during development, and use TestFlight for iOS beta testing before submitting to the App Store.

</details>

<details>

<summary>Why are some web elements not available in native mobile?</summary>

Many web elements rely on browser APIs and web technologies that don't exist in a native mobile environment. Bubble provides native equivalents where possible, but not every web element has a direct mobile counterpart.

</details>

[^1]: In this context, *biometrics* refers to allowing users to log in using their device's biometric methods, such as fingerprint or face recognition.

[^2]: The *camera* in this scenario means access to the on-device camera of a phone or tablet.

[^3]: *Contacts* in this scenario means to have access to the contact list (or phone book) stored on the device.


# Differences in native and web elements

## Views

#### Views compared to pages

Having built web apps with Bubble, you’re likely familiar with the concept of pages: each page is a distinct entity, and everything you design and deploy is contained within these individual pages, with each loading as a separate resource.

Views work a bit differently. While they still act as the top parent element for all nested content, they are not standalone resources that require reloading when switching between them. Instead, views are part of a unified structure within your app, allowing users to navigate between them seamlessly without the need for reloading each time.

#### Views compared to groups

Again, if you’re familiar with the Bubble web app tools, especially if you have experience building single-page applications, you’re likely accustomed to setting up groups and showing or hiding them (often by collapsing their height/width) to let users seamlessly navigate different sections within a single page.

Views share similarities with this approach, but they are more closely aligned with mobile app development principles. Rather than relying on groups within a single page, views represent distinct sections or screens of your app while still being part of a fluid and cohesive navigation flow. This structure makes views a more natural fit for mobile app patterns like stack navigation, tab navigation, and swipe gestures, offering a smoother and more intuitive user experience on mobile devices.

Lastly, collapsible groups are available in the native mobile app editor as well, meaning the two are not in any way mutually exclusive, but used for different purposes.

### Navigation types

Views differ from pages and groups in that the action used to navigate to a new view determines how that view behaves. In other words, the settings in the *Go to view* action influence how the user interacts with and navigates within the view.

* **Stack navigation** follows a hierarchy where views are layered on top of one another as the user navigates deeper into the app, much like individual cards being stacked in a deck.\
  \
  Each new view is "pushed" onto the stack, and users can "pop" back to previous views using a back button, either physical or on-screen. This is often used in scenarios where users navigate into more detailed content, such as moving from a list of items to a detailed view of a specific item. The key feature is that stack navigation maintains the history of previous views, allowing for smooth back-and-forth navigation.<br>
* **Modal views** are used for temporary, self-contained interactions that are separate from the primary navigation flow. When a modal is opened, it typically slides up from the bottom of the screen or appears over the existing content, blocking interaction with the background until it is dismissed.\
  \
  Modals are commonly used for actions like filling out forms, confirming choices, or displaying brief contextual information. Unlike stack navigation, modals don’t contribute to the app’s main navigation history and are usually dismissed rather than navigated away from.

## Sheets

### Sheets compared to floating groups

When building a Bubble web app, you may have used floating groups as a way to place content at the top, bottom or side of the screen, often on top of other elements. Sheets offer similar functionality, but with some key differences:

* **Positioning and behavior:** Sheets slide in and out as temporary overlays, while floating groups are static and anchored to a specific part of the screen.
* **Use cases:** Sheets are used to display temporary or contextual information in a way that’s anchored to mobile design principles, while floating groups are better suited for persistent elements like navigation menus, headers, or footers that remain in view as the user interacts with the page.
* **Interaction:** Sheets usually involve a dynamic transition (e.g., sliding up/down). Floating groups can be set up to imitate this behavior, but their default behavior is to be fixed in place without such transitions<br>

### Sheets compared to popups

Sheets also share some similarities with popups (though popups are not available in the native mobile app editor). However, sheets shouldn’t be viewed as a direct replacement. As mentioned earlier, sheets can cover part or all of the screen and can blur the content underneath, much like popups. In this way, you can think of sheets as a more mobile-optimized version of popups that aligns better with design patterns on iOS and Android. Let’s look at some of the differences between the two:

* **Positioning and behavior:** Popups typically appear centered on the screen and overlay the entire page, dimming the background, whereas sheets slide in from the bottom and occupy a portion of the screen while keeping the rest of the content visible.
* **Use cases:** Popups are ideal for grabbing the user’s full attention, such as displaying alerts, confirmation dialogs, or forms. Sheets, on the other hand, are better suited for displaying contextual information or options without fully interrupting the user’s flow, staying anchored to the bottom of the screen in a more mobile-friendly way.
* **Interaction:** Popups usually fade in or appear with a custom animation, often requiring the user to click outside the popup or press a button to close it. Sheets involve a sliding transition (e.g., sliding up/down) and can often be swiped away by the user, mimicking native mobile app behavior.

Sheets are often used for displaying contextual data and actions when you don’t want to change views or block the entire screen. For the user, a sheet appears as a layer on top of the content they’re currently viewing, allowing them to interact with it while keeping the underlying content accessible. The ability to slide the sheet to adjust its size or hide it mimics the feel of a real-world object.

### Lists

Lists in the native mobile app editor are a way to show lists or records, comparable to repeating groups in a Bubble web app. Still, the two serve some key differences.<br>

* **Element versus property:** In a Bubble web app, lists are typically managed by elements like repeating groups or tables. In the native mobile app editor, however, lists can be a property of an element, such as a view or sheet. This means a single element can hold either a single item or a list, depending on its settings, whereas in the web editor, lists are usually managed by dedicated elements like repeating groups or tables..\
  \
  Vertical lists can also be added as an element, similarly to setting up a vertically-scrolling repeating group in the web app editor.<br>
* **List behavior:** lists can also be automatically divided into groupings. The mechanics differ from using a nested repeating group, and behaves more like like the [*Group by*](#user-content-fn-1)[^1] operator (although the two are not exactly the same).

### Dropdowns:

While dropdowns (select menus) may seem like an obvious choice for presenting multiple options on web apps, they often create a poor user experience on mobile apps. Here's why:

1. **Hidden options:** Dropdowns hide available choices until tapped, making it difficult for users to see all options at a glance.
2. **Multi-step process:** Selecting an option requires multiple actions: tapping to open, scrolling to find the desired option, selecting it, and closing the dropdown.
3. **Scrolling difficulties:** Long lists (e.g., country selectors) can be cumbersome to navigate, especially without keyboard search on mobile.
4. **Small tap targets:** Dropdown areas and individual options can be small, leading to accidental selections.
5. **Lack of context:** Users can't easily compare options or see relationships between choices.

#### Recommended alternatives

Instead of dropdowns, consider these more user-friendly UI patterns. Opting for these alternatives can enhance the mobile experience by reducing taps and lowering cognitive load, making navigation more intuitive and efficient for your users.

1. **Switches:** For binary (on/off) choices.
2. **Radio buttons or segmented controls:** For a small number of mutually exclusive options (2-5).
3. **Steppers:** For incrementing/decrementing numeric values.
4. **Sliders:** For selecting from a range of values.
5. **Auto-complete Text Fields:** For large sets of options where users can type to filter (e.g., country selection).
6. **Bottom sheets or action sheets:** For presenting a list of options in a larger, more scannable format.
7. **Buttons:** For a small set of distinct actions.
8. **Prioritized lists:** Show the most common options upfront, with an "Other" option to access less frequent choices.

[^1]: Group by groups a list of items into chunks of related entries, and computes summaries of each group.\
    \
    Reference: [Operators and comparisons](/core-resources/data/operations-and-comparisons) | [...group by](/core-resources/data/operations-and-comparisons#group-by)


# Payments in mobile apps

{% hint style="warning" %}
**Note:**

The information in this article is provided as a general overview. Platform policies change over time, and the authoritative sources are the official documentation from each platform:

* [App Store Review Guidelines](https://developer.apple.com/app-store/review/guidelines/) — Apple
* [Google Play Payments policy](https://support.google.com/googleplay/android-developer/answer/9858738) — Google

Always refer to these documents for the most up-to-date requirements before publishing your app.
{% endhint %}

Payments in native mobile apps work differently from payments on the web. While a web app can integrate with any payment processor and charge users directly, native mobile apps are subject to platform rules set by Apple and Google. Understanding these rules is essential when planning monetization for your mobile app.

#### Why mobile payments are different

Apple's App Store and Google Play have strict policies on how apps can charge users for digital goods and services. Both platforms require that purchases of digital content, features, or subscriptions go through their own in-app purchase systems — Apple's StoreKit and Google's Play Billing. These systems handle the transaction, take a commission, and distribute the remainder to the developer.

This rule is enforced at the platform level. Submitting an app that bypasses these systems to charge users for digital goods will typically result in rejection during the review process.

#### When in-app purchases are required

In-app purchases are required for any digital goods or services consumed within the app. This includes:

* Subscriptions to premium content or features
* Unlocking app features or removing ads
* Virtual currency or in-game items
* Digital content such as articles, videos, or audio

The platform takes a commission on these transactions — typically 15% to 30%, depending on the developer's agreement and revenue thresholds.

#### When in-app purchases are not required

Apple and Google allow external payment methods for certain categories of goods and services. These include:

* Physical goods and services (such as ecommerce purchases or ride bookings)
* Services consumed outside the app (such as a meal delivered or a ride taken)
* Business-to-business transactions
* Donations to nonprofit organizations

For these categories, you can use any payment processor, including Stripe, PayPal, or others integrated through Bubble's API connector or plugins.

#### In-app purchases in Bubble

Bubble currently supports subscriptions through in-app purchases. This means you can offer recurring subscription products in your native mobile app and have them processed through Apple's or Google's billing systems. You can read more about in-app purchases in the article series below:

Article series: [In-app purchases](/help-guides/getting-started/building-for.../native-ios-and-android/in-app-purchases)

#### Choosing a payment approach

When planning payments in your native mobile app, consider what you're charging for:

* If you're selling **digital subscriptions**, use Bubble's in-app purchase support.
* If you're selling **physical goods or services consumed outside the app**, you can use external payment processors like Stripe.
* If your app combines both — for example, an ecommerce store with a premium subscription tier — you may need to use both approaches, with each transaction routed through the appropriate system.

#### Platform commission and pricing

{% hint style="info" %}
The rates below are provided as a general overview and may change over time. Always refer to the official platform documentation for current rates and terms.
{% endhint %}

Apple and Google take a commission on all in-app purchases, which affects your pricing strategy. The standard rates are:

* 30% on most purchases in the first year of a subscription
* 15% on subscription renewals after the first year
* Reduced rates for developers earning under a certain revenue threshold (currently $1 million per year)

Factor these commissions into your subscription pricing to ensure your revenue targets are met after the platform's cut.

#### Testing in-app purchases

Both Apple and Google provide sandbox environments for testing in-app purchases without charging real money. Use these environments to verify that your purchase flows, subscription renewals, and cancellation handling work correctly before submitting your app for review.

## FAQ: Payments in mobile apps

<details>

<summary>Why can't I just use Stripe to charge for subscriptions in my mobile app?</summary>

Apple and Google require all purchases of digital goods and services, including subscriptions to digital content or features, to go through their own billing systems. Using an external payment processor like Stripe for these purchases will typically result in app store rejection.

</details>

<details>

<summary>Can I use Stripe at all in my mobile app?</summary>

Stripe for mobile is not officially supported by Bubble as of now.

Technically, you can set up payments with any provider, but only for physical goods and services consumed outside the app. Examples include eCommerce purchases, food delivery, ride bookings, and event tickets.

</details>

<details>

<summary>Can I offer the same subscription on web and mobile?</summary>

Yes, but you'll need to handle each platform's billing system separately. Web subscriptions can use any payment processor, while mobile subscriptions must go through Apple's or Google's billing systems. Most apps maintain a single user account that recognizes subscriptions purchased on either platform.

</details>

<details>

<summary>Can I link to my website to let users subscribe there instead?</summary>

Both Apple and Google have strict rules about steering users to external payment methods. The rules vary by region and have been changing in response to recent legal developments. Refer to the official platform documentation for current guidance.

</details>

<details>

<summary>What if my app is rejected for payment-related reasons?</summary>

The most common reasons for rejection are using external payment methods for digital goods, or failing to use the platform's in-app purchase system where required. Review the platform's payment guidelines carefully and update your app to comply before resubmitting.

</details>


# In-app purchases

## Introduction

Bubble’s In-App Purchases (IAP) feature lets you monetize native mobile apps using the billing systems provided by Apple and Google.

{% hint style="warning" %}
**Bubble currently supports subscriptions only.** One-time digital purchases, such as consumable tokens or credits, are currently not supported.
{% endhint %}

{% hint style="warning" %}
While Bubble automates and simplifies much of the setup, **in-app purchases are governed by Apple App Store and Google Play policies**. Before submitting an app for review, you should review and follow the guidelines set by each platform to ensure a smooth approval process.
{% endhint %}

## What is Bubble’s in-app purchases feature?

Bubble’s IAP feature is a native, cross-platform integration with Apple StoreKit and Google Play Billing. It allows you to offer subscriptions in a Bubble native mobile app using each platform’s in-app purchase system.

With this feature, you define your subscription model in Bubble—including subscription groups, subscription tiers, and billing variants, and connect these to corresponding subscription products in the Apple and Google developer consoles. Bubble subscription objects act as a shared layer between Apple’s and Google’s billing systems, allowing you to support cross-platform subscription flows without building separate logic for each platform.

{% hint style="warning" %}
While Bubble provides the tooling to create and manage subscription flows, **all transactions are processed by Apple’s and Google’s billing systems**, in the same way that web payments are processed by providers such as Stripe.
{% endhint %}

### Do I need to use in-app purchases?

Apple and Google define when in-app purchases are required and when alternative payment methods are allowed.

{% hint style="danger" %}
IAP rules can be complex and may change over time, so the official documentation of each respective platform should always be treated as the source of truth.

The [guidelines](#you-must-use-in-app-purchases-if-you-sell) below provide general direction.
{% endhint %}

<details>

<summary>Official documentation for Apple and Android</summary>

#### Apple

Apple's IAP article describes how their in-app purchases work, rules for the platform and best practices.

External page: [In-app purchase | Apple Developer Documentation](https://developer.apple.com/design/human-interface-guidelines/in-app-purchase)

#### Android

Android's landing page for IAP explains how their payment system works, along with an extensive FAQ.

External page: [Understanding Google Play’s Payments policy | Play Console Help](https://support.google.com/googleplay/android-developer/answer/10281818?hl=en)

</details>

#### You must use in-app purchases if you sell

* **Digital content consumed within your app**, including premium features, subscriptions, virtual goods, additional content, or app-only functionality
* **Digital services delivered through your app**, such as ongoing access to features, cloud storage, or digital tools

#### You may use other payment methods if you sell

* **Physical goods or services**, such as shipped products, food orders, ride-sharing services, or hotel bookings
* **Real-world services**, including appointments, classes, or professional services performed outside the app
* **Digital goods primarily consumed outside the app**, such as content accessed across multiple platforms
* **Multi-platform SaaS products**, where the mobile app acts as a companion to a web-based service. See more [below](#multi-platform-saas-considerations).

### Multi-platform SaaS considerations

If you’re building a SaaS product with both web and mobile apps, there are additional considerations:

* Users can subscribe through your website using Stripe or another payment processor
* The mobile app can provide access to users who already have an active subscription
* **Important:** If the mobile app allows users to purchase, upgrade, or manage subscriptions within the app, in-app purchases must be used
* **Important:** Mobile apps cannot include links or calls to action that direct users to external payment pages

For example, a project management app may allow existing web subscribers to use the mobile app freely, but any new subscription initiated from the mobile app must be purchased through in-app purchases.

### When in doubt

If you’re selling digital content or features that users primarily access through your mobile app, in-app purchases are typically required. Apps that attempt to bypass Apple or Google’s billing systems for digital goods are likely to be rejected during app review.

### Subscription support

Bubble’s in-app purchases feature supports subscriptions only. One-time digital purchases, such as consumable tokens or credits, are not supported by this feature.

### Scope of the in-app purchases feature

Bubble’s IAP feature includes the components needed to set up a compliant subscription experience in a native mobile app. It can be divided into four main areas.

#### Initial setup

* A guided setup flow to enable in-app purchases for your app on each app store.
* A guided setup flow to connect your app backend to Apple and Google billing server notifications.

#### Product setup

* A guided setup flow to define your subscription model in Bubble and link it to Apple and Google subscription products.

Your subscription model consists of the following concepts:

* **Subscription group:** A collection of related subscription tiers
* **Subscription tier:** Defines the level of access a user has to app features
* **Billing variant:** Defines the billing frequency and price for a subscription tier

#### Workflows and data updates

* A **Subscription Purchases** data type that acts as the source of truth for a user’s subscription status and is automatically updated when Apple or Google send billing notifications
* New operators that help define subscription logic and interfaces, such as checking whether a user is subscribed to a specific tier
* Workflow actions for initiating subscription purchases and managing existing plans
* Backend workflow events that allow you to define custom behavior when server-side billing notifications are received from Apple or Google

#### Testing in development

Apple and Google provide sandbox environments for testing in-app purchases through TestFlight[^1] or platform-specific testing tracks. These environments reflect real billing behavior without actually charging, but typically require a new build or [OTA update](#user-content-fn-2)[^2] after changes.

Bubble also provides simulated testing tools that allow you to test purchases, upgrades, downgrades, and cancellations in the development database. These simulations trigger the same backend workflow events as real purchases, without making calls to Apple or Google, making it easier to test subscription flows in BubbleGo[^3] and Web Preview.

[^1]: TestFlight is Apple’s testing platform that lets you distribute iOS apps to testers before releasing them on the App Store.

[^2]: Article section: How OTA (Over-the-Air) updates work

[^3]: BubbleGo is Bubble’s companion mobile app that lets you run and test native mobile apps built with Bubble during development.

    Article section: [BubbleGo](/help-guides/previewing-your-app/previewing-a-mobile-app#bubblego)


# IAP on Apple devices

## Enabling in-app purchases

To enable in-app purchases for Apple, fill in the required fields in the *Settings* tab.

### Private key

The private key must be generated in App Store Connect.

{% stepper %}
{% step %}

### Navigate to the right section

Go to *App Store Connect → Users and Access → Integrations → In-App Purchase*.
{% endstep %}

{% step %}

### Create a new key

Click the *+ symbol* to create a new key.
{% endstep %}

{% step %}

### Download key file

Download the key file and upload it in the Settings tab.<br>
{% endstep %}
{% endstepper %}

<figure><img src="/files/Entbit8YVRgWsHi65wIO" alt=""><figcaption></figcaption></figure>

If you’re part of an Apple Enterprise developer account, a specific permission level is required to create this key. If you don’t have access, contact your company administrator.

### Key ID

Copy the Key ID generated in the previous step and paste it into the **Key ID** field in the Settings tab.

### App Apple ID

The App Apple ID must also be generated in App Store Connect.

{% stepper %}
{% step %}

### Navigate to the right section

Go to *App Store Connect → Apps → Distribution → General → App Information → General Information*.
{% endstep %}

{% step %}

### Copy the Apple ID

Copy the *Apple ID* and paste it into the *Settings* tab.

<figure><img src="/files/hlWidkMGto6NxDW2FD48" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Confirm

Once all fields are filled in, click *Validate* to confirm that everything is connected correctly.
{% endstep %}
{% endstepper %}

## Handling billing notification events

Because in-app purchases are completed through Apple’s interface, your app needs a way to detect when a user performs actions such as subscribing to or canceling a plan. Apple StoreKit sends server-side billing notifications whenever these events occur. This step ensures your app’s backend is correctly connected to Apple’s billing system.

Click *Connect billing notification events*. This automatically registers the correct endpoint URL for your app with Apple StoreKit.

It can take up to five minutes for Apple to update and recognize the new endpoint.

If you encounter an error, confirm that your API key has admin access. See [this article](/help-guides/publishing-your-app/native-mobile-app/ios-app-store#id-1-create-a-new-api-key) for instructions.

## Testing setup

{% stepper %}
{% step %}

### Send test notification

Click *Send test notification*. Make sure at least five minutes have passed since connecting the billing notification events.
{% endstep %}

{% step %}

### Confirm

Check that the notification has been successfully received.
{% endstep %}
{% endstepper %}

If an error occurs, check *App Store Connect → Apps → App Information → App Store Server Notifications*.


# IAP on Android devices

## Enabling in-app purchases

Start a build after enabling in-app purchases by clicking the button in this section.

Once the build is complete (this can take up to 30 minutes), go to *Google Play Console → App → Test and release → Testing → Internal testing*. Save the draft release that was automatically created when the build file was sent to the Play Console.

If you received a build success email but don’t see a draft release with the new build file, manually create a draft release and attach the build file provided in the email.

Click the button to verify that everything is properly connected.

### Handling billing notification events

Because in-app purchases are completed through Google’s interface, your app needs a way to detect when a user performs actions such as subscribing to or canceling a plan. Google Play Billing sends server-side notifications whenever these events occur. This step ensures your app’s backend is properly connected to Google Play Billing.

Click the link to open your Google Cloud permissions and add the following permission levels:

* *Pub/Sub-Admin*
* *Service Account User*

The *Manage permissions* option is located in the three-dot actions menu.

<figure><img src="/files/4WwuUlod8vPdujZNOv2d" alt=""><figcaption></figcaption></figure>

After adding the permissions, wait about 10 seconds. Then return to Bubble and click the button to automatically create a Pub/Sub configuration.

If this doesn’t work, make sure billing is enabled for your Google Cloud project and that the Pub/Sub API is enabled (step 3 in Google’s Pub/Sub documentation).

### Testing setup

{% stepper %}
{% step %}

### Copy the new pub/sub name

Then go to Google Play Console > Monetize with Play > Monetization setup > Enable notifications.
{% endstep %}

{% step %}

### Check the box

Check the box to *Enable real-time notifications* and paste in the *Topic Name.*

<figure><img src="/files/pDVAh9TcKlXHY4OcMiJE" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Send a test notification

Still in the Google Play Console: *Click Send a test notification.*
{% endstep %}

{% step %}

### Confirm

Return to Bubble and ensure a test notification has been successfully received.
{% endstep %}
{% endstepper %}

If you encounter an error, check *Google Play Console → Products → Pub/Sub* to find the Google in-app purchase subscription that was created.


# Setting up subscriptions

## Setting up subscription products

This guide describes a *Bubble-first* approach to setting up in-app purchases (IAP). By defining your subscription structure in Bubble before configuring Apple and Google, you establish a single source of truth and avoid working with two different billing models at the same time.

If you already have subscriptions defined in Apple or Google, you can still create and link the corresponding Bubble subscription objects afterward.

### Why use a Bubble-first approach

Without this approach, you would need to learn Apple’s and Google’s subscription structures simultaneously and then manually map them together. By defining your business model in Bubble first, you create a clear blueprint that can be translated consistently to both platforms.

<figure><img src="/files/s96MsJFqpLbFbjBKKfFH" alt=""><figcaption></figcaption></figure>

### Prerequisites

Before you begin, make sure the following are in place:

* An active Apple Developer account (if supporting iOS)
* An active Google Play Developer account (if supporting Android)
* A Google Cloud project for this app (if supporting Android)
* Your Bubble app is connected to the Apple App Store and/or Google Play Store in app settings
* In-app purchases are enabled for each connected store in mobile settings
* Your Bubble project is on a trial or paid mobile plan

### Phase 1: the Bubble blueprint

Before creating any products in Apple or Google, define your subscription model in the Bubble editor. This acts as a clear checklist for creating matching store products later.

#### Create a subscription group

A subscription group is a logical container for related plans. Most apps only need one group (for example, *Premium membership*), with multiple tiers inside it such as *Basic*, *Pro*, and *Pro Plus*.

A user can only have one active subscription within a subscription group at a time. They can upgrade or downgrade within the group, but can’t subscribe to multiple plans in the same group simultaneously.

If you plan to offer unrelated subscriptions that users may want at the same time, use multiple subscription groups. For example, a *Cooking* subscription and a *Games* subscription would typically belong to separate groups.

The subscription group name is a display name and doesn’t need to match exactly across platforms.

#### Define and rank subscription tiers

Within a subscription group, define your subscription tiers and their billing variants. For example:

```
Subscription Group: "Premium Membership"
  ├─ Subscription: "Basic" (Rank 1)
  │   ├─ Variant: Monthly ($9.99)
  │   └─ Variant: Annual ($99.99)
  ├─ Subscription: "Pro" (Rank 2)
  │   ├─ Variant: Monthly ($19.99)
  │   └─ Variant: Annual ($199.99)
  └─ Subscription: "Pro Plus" (Rank 3)
      ├─ Variant: Monthly ($29.99)
      └─ Variant: Annual ($299.99)
```

Tier rank is defined by dragging subscriptions in the list:

* **Top** = lowest tier
* **Bottom** = highest tier

Bubble uses this order to determine whether a change is an upgrade or a downgrade, which affects proration and billing behavior.

On Apple, Apple’s own rules take precedence if there’s a conflict.\
On Google, Bubble sends proration instructions that match Apple’s behavior for consistency.

You can change tier order even after billing variants are marked as ready for sale. This allows you to insert new tiers later as your product evolves.

{% hint style="warning" %}
Bubble doesn’t validate whether your ranking aligns with your pricing. Make sure the order makes sense for your business model.
{% endhint %}

#### Add billing variants

For each subscription tier, add billing variants such as monthly or annual plans.

Variants are tied to their subscription tier and can’t be moved between tiers. To change tiers, the variant must be deleted and recreated.

### Phase 2: creating products in the stores

Once your Bubble blueprint is complete, create matching products in the Apple and Google developer consoles.

#### Apple StoreKit (App Store Connect)

Apple uses a flat subscription structure. Each combination of tier and billing variant is its own subscription. While Apple supports subscription groups, it doesn’t have a native concept of billing variants.

For example, a single Bubble subscription group maps to multiple Apple subscriptions, such as

Apple Subscriptions (Flat Structure):

Subscription Group: "Premium Membership"

```
Apple Subscriptions (Flat Structure):
Subscription Group: "Premium Membership"
├─ basic_monthly (Rank 1)
├─ basic_annual (Rank 1)
├─ pro_monthly (Rank 2)
├─ pro_annual (Rank 2)
├─ proplus_monthly (Rank 3)
└─ proplus_annual (Rank 3)
```

{% stepper %}
{% step %}

#### Navigate to subscriptions

Go to *App Store Connect → App → Distribution → Monetization → Subscriptions*.
{% endstep %}

{% step %}

#### Create a subscription group

Create a subscription group that matches your Bubble group concept. If you only have one group, a generic name like *Premium plans* is usually sufficient.
{% endstep %}

{% step %}

#### Create subscriptions

Create one Apple subscription for each billing variant defined in Bubble.
{% endstep %}

{% step %}

#### Set reference names and IDs

Assign a reference name and ID to each subscription. The reference name is shown in the Bubble editor, while the ID is used for linking and can’t be changed later.
{% endstep %}

{% step %}

#### Adjust subscription order

Arrange subscriptions so billing variants of the same tier are on the same level. Apple allows multiple subscriptions at the same level by dragging them on top of each other.
{% endstep %}
{% endstepper %}

Prices, availability, and localization can be configured later.

#### Google Play Billing (Google Play Console)

Google uses a nested structure with subscriptions and base plans, which more closely matches Bubble’s model. Google doesn’t support subscription groups.

**Example:**

```
Google Subscriptions (Nested Structure):
├─ Subscription: "Basic"
│   ├─ Base Plan: "Monthly"
│   └─ Base Plan: "Annual"
├─ Subscription: "Pro"
│   ├─ Base Plan: "Monthly"
│   └─ Base Plan: "Annual"
└─ Subscription: "Pro Plus"
    ├─ Base Plan: "Monthly"
    └─ Base Plan: "Annual"
```

**Steps**

{% stepper %}
{% step %}

#### Navigate to subscriptions

Go to *Google Play Console → App → Monetize with Play → Products → Subscriptions*.
{% endstep %}

{% step %}

#### Create subscriptions

Create one Google subscription for each Bubble subscription tier.
{% endstep %}

{% step %}

#### Create base plans

Inside each subscription, create base plans for each billing variant (for example, Monthly and Annual).
{% endstep %}

{% step %}

#### Set pricing and availability

Define pricing and availability for each base plan. These settings can be adjusted before going live.
{% endstep %}
{% endstepper %}

Google subscriptions have both a name and a product ID. The name is shown in the editor, while the ID is used for linking and can’t be changed later.

{% hint style="info" %}
Products can remain pending or inactive when linked later—they don’t need to be fully activated yet.
{% endhint %}

### Phase 3: linking subscriptions in Bubble

After creating products in the stores, return to Bubble to connect them. Click *Refresh data* to load the latest store configurations.

#### Map store IDs

{% stepper %}
{% step %}

#### Map Apple subscription group

If Apple is connected, map the Apple subscription group ID using the dropdown at the top of the subscription group card.
{% endstep %}

{% step %}

#### Link billing variants

For each billing variant, select the corresponding Apple product ID and the matching Google subscription and base plan IDs. If you recently changed products in the stores, click *Refresh data* again before selecting IDs.
{% endstep %}
{% endstepper %}

### Testing your setup

#### Development testing

You can test subscription flows using Web Preview and BubbleGo. These tests simulate purchases without contacting Apple or Google and create dummy subscription records in the development database only.

This allows you to test:

* Purchase flows
* Conditional logic based on subscriptions
* Active, canceled, and other subscription states
* Backend workflows triggered by billing notification events

This does not allow you to test:

* The native Apple and Google payment sheet experience
* Managing subscriptions through Apple or Google account settings

{% hint style="info" %}
**Note:** Dummy subscription records will not expire automatically. To test what happens when a subscription ends, you should expecility change the state of the dummy subscription in the simulated payment sheet.
{% endhint %}

#### Store sandbox testing

After validating your flows and logic in development, you can test with platform sandbox environments:

* **Apple**: TestFlight with sandbox accounts
* **Google**: Play Console testing tracks (internal, closed, or open testing)

These environments allow you to test real purchase flows without real charges before publishing to live. Sandbox testing still creates subscription entitlement records in the live database, but they are marked as *sandbox*.


# Getting ready for Production

## Preparing for submission and managing plans

{% hint style="info" %}
To understand the terminology used in this article, we have a IAP glossary at the bottom of the page.
{% endhint %}

This phase covers everything required to submit an app with subscriptions to the Apple App Store and Google Play. At this point, you should already have:

* Created and configured billing variants in Bubble
* Linked those variants to Apple subscriptions and Google base plans
* Tested purchases using sandbox or test accounts

This phase focuses on preparing your Apple and Google store listings for review. Missing any of the steps below can prevent subscriptions from appearing during submission or result in your app being rejected.

### Confirm Bubble is ready for production

Before working in the Apple or Google dashboards, confirm the following in Bubble.

{% stepper %}
{% step %}

#### Double check billing variants

Double check that all billing variants are present. While these can be modified later, its best to catch any inconsistencies before going live.
{% endstep %}

{% step %}

#### Verify store links

Each billing variant is linked to:

* An Apple Subscription ID
* A Google Subscription and Base Plan (if launching on Android)
  {% endstep %}

{% step %}

#### Prepare a new build

Confirm you’re ready to generate a new native build for submission.
{% endstep %}
{% endstepper %}

If your Bubble app is connected to both the Apple App Store and Google Play Store, every variant must include IDs for both platforms. If any billing variant is missing a required link, Bubble will surface the issue in the issue checker.

If you make changes to subscription configuration after this point, you may need to generate a new build before final submission.

### Apple App Store pre-submission checklist

Apple reviews subscriptions together with your app version. All required subscription setup must be completed in App Store Connect before submitting the app.

{% hint style="warning" %}
**Family Sharing is not supported** for subscriptions in this setup. Make sure this setting is turned off before submitting Apple subscription plans for review.
{% endhint %}

Complete the required Apple readiness steps in App Store Connect before proceeding.

### Google Play pre-submission checklist

All required subscription setup must also be completed in Google Play Console before submission.

Complete the required Google readiness steps in Google Play Console before proceeding.

### Final production check

You’re ready to submit when all of the following are true:

* All "listed" Bubble variants are linked to a plan
* No draft submissions are blocking selection
* A new native build has been generated and uploaded to App Store Connect and Google Play Console
* Apple subscriptions are selectable on the app build version
* Google subscriptions are active
* App review notes and screenshots are complete

### Managing plans by unlisting

As your business evolves, you may need to retire or stop offering certain plans.

#### When to unlist

Because an active plan may have existing subscribers, *one should be careful when deleting billing variants.* Instead, consider un-listing a billing variant that was previously tied to active subscribers.

#### What unlisting does

In Bubble:

* The plan is hidden from pricing pages that reference active subscriptions
* Bubble continues processing renewals through billing notifications
* Existing subscribers remain on the plan

In the app stores:

* Unlisting a plan in Bubble does not automatically unlist it in Apple or Google
* You must manually deprecate or remove the plan in both App Store Connect and Google Play Console to prevent new users from subscribing

#### Existing subscriber behavior

Users with active subscriptions to unlisted plans:

* Can remain on their current unlisted plan
* Can upgrade or downgrade to other active plans within the same subscription group
* Can’t re-subscribe to the unlisted plan if they cancel or the subscription expires, assuming the plan has been properly unlisted in Apple and Google

### Summary checklist

#### Before you start

* Define subscription groups, subscriptions, and billing variants in Bubble
* Arrange subscription tiers in the correct order
* Create matching products in Apple and Google consoles
* Map Apple and Google IDs in Bubble

#### Before going live

* Ensure both Apple and Google IDs are mapped if using both platforms
* Activate plans in App Store Connect and Google Play Console
* Generate a new native build and submit for review

#### You’re ready when

* All required Apple and Google IDs are mapped
* Plans are active in the store consoles
* The app has been approved by Apple and Google

A common pitfall is creating store products before defining the subscription structure in Bubble. This often leads to additional rework when aligning Apple, Google, and Bubble configurations.

<details>

<summary>Glossary</summary>

* **Subscription group:** A collection of subscription tiers where a user can hold only one active subscription at a time
* **Subscription tier:** A service level within a group, such as Basic, Pro, or Pro Plus
* **Billing variant:** A billing frequency for a subscription tier, such as Monthly or Annual
* **Draft:** A billing variant that exists in Bubble but isn’t available for purchase
* **Ready for Sale:** A billing variant that’s locked, linked to store IDs, and available for purchase
* **Unlisted:** A Ready for Sale variant that’s hidden from purchase while continuing to serve existing subscribers

</details>


# Apple IAP checklist

### Apple App Store subscription readiness checklist

This section walks through the required steps to prepare subscriptions for review in the Apple App Store. Apple reviews subscriptions together with your app version, and missing any of the steps below can prevent subscriptions from appearing or cause your submission to be rejected.

#### 1. Account and app readiness

In *App Store Connect → Agreements, Tax, and Banking*:

* The *Paid Apps Agreement* is accepted
* Tax information is completed
* Banking information is completed

In *Apps → Your App*:

* At least one iOS build has been uploaded and finished processing

{% hint style="warning" %}
Apple will **not** **review** subscriptions **if any paid-app agreement is incomplete**.
{% endhint %}

#### 2. Subscription group setup (required)

Path: *Monetization → Subscriptions*

For each subscription group:

* A subscription group exists
* At least one localization has been added (for example, *English (U.S.)*)
* The localization includes:
  * A subscription group display name
  * An app name display option
* The localization has been saved

**This step is easy to miss**. If a subscription group has no localization, all subscriptions in the group will remain in *Missing Metadata* and cannot be submitted.

#### 3. Subscription status check

Path: *Monetization → Subscriptions → Subscription Group → Subscription*

For each subscription:

* The subscription status shows *Ready to Submit*

If the status shows *Missing Metadata* or *Prepare for Submission*, the subscription cannot yet be attached to an app version.

#### 4. Pricing and localization requirements

On the subscription detail page:

**Pricing**

* At least one price has been added under *Subscription Prices*
* Apple automatically calculates prices in other currencies based on current exchange rates

**Localization**

* At least one localization exists (for example, *English (U.S.)*)
* Display name is filled in
* Description is filled in

You don’t need to localize subscriptions for every country where your app is available. A single language is sufficient.

#### 5. App review information (required for the first subscription)

On the subscription page, scroll to *App Review Information*.

**Provide the following:**

* A screenshot showing where the subscription appears in your app (for example, a paywall or upgrade screen)
* Review notes explaining how Apple reviewers can find the subscription

**Screenshot requirements:**

* Can be taken from a simulator
* Doesn’t need to be marketing-polished
* Must clearly show:
  * Subscription name
  * Price
  * Billing period

Example review note:\
“To view the subscription, open the app and tap ‘Upgrade to Premium’ from the Settings screen.”

#### 6. Remove existing draft submissions

Path: *Apps → Your App → App Store → Draft Submissions*

Remove any draft submission that were created before subscriptions were fully ready.

{% hint style="warning" %}
Old draft submissions can prevent subscriptions from being selectable later, even if everything else is configured correctly.
{% endhint %}

#### 7. Attach subscriptions to the app version

Path: *Apps → Your App → App Store → iOS App → App Version*

Confirm the following:

* A build is selected
* The *In-App Purchases and Subscriptions* section is visible
* The subscription can be selected and added
* The app version is saved

If the section is visible but not clickable, it usually means:

* The subscription group localization is incomplete, or
* An old draft submission still exists

#### 8. Final Apple review checks

Before submitting, confirm that:

* The subscription is reachable in the app UI
* Price and billing period are clearly displayed
* *Restore Purchases* works correctly
* App Review Notes clearly explain how to reach the paywall

#### 9. Submit for review

Submit the app version for review and confirm that the subscription is included. Apple reviews the app and subscription together.

***

### Common Apple App Store issues and fixes

Apple’s subscription review process includes several non-obvious requirements and UI behaviors. If something doesn’t work even though it appears correctly configured, check the scenarios below.

#### Subscriptions don’t appear on the app version page

**Symptoms**

* The *In-App Purchases and Subscriptions* section is visible
* The subscription picker is empty or disabled
* Or the section doesn’t appear at all

**Most common causes**

* The subscription group has no localization
* The subscription status is not *Ready to Submit*
* An old draft submission exists

**How to fix**

* Go to *Monetization → Subscriptions*
* Open the subscription group and confirm at least one localization exists
* Confirm the subscription status is *Ready to Submit*
* Go to *Apps → Your App → App Store → Draft Submissions* and remove any drafts
* Reload the app version page

#### Subscription status shows “Missing Metadata”

**Symptoms**

* The subscription shows *Missing Metadata*
* All visible fields appear completed

**Most common causes**

* Missing subscription group localization
* No price added under *Subscription Prices*
* Missing app review information

**How to fix**

* Add a localization to the subscription group
* Add at least one price
* Provide:
  * A paywall screenshot
  * Review notes explaining how to find the subscription in the app

Apple requires both subscription-level metadata and group-level metadata.

#### Everything shows “Prepare for Submission”

**What this means**

*Prepare for Submission* doesn’t indicate missing data. It means the item hasn’t yet been included in a submission.

**Common trap**

* A draft app submission was created before subscriptions were fully ready

**How to fix**

* Remove any existing draft submissions
* Reload the app version page
* Re-select the build and subscription

This often unlocks the subscription picker immediately.

#### The “In-App Purchases and Subscriptions” section is missing

**Symptoms**

* No in-app purchases section appears on the app version page

**Common causes**

* No build is selected
* The build was generated before in-app purchases were enabled
* Apple hasn’t finished processing the build

**How to fix**

* Ensure a build is uploaded and selected
* Generate a new build from Bubble if needed
* Wait a few minutes and refresh App Store Connect

#### Changes don’t seem to take effect

**Symptoms**

* Metadata was updated, but the UI didn’t change
* Status updates are delayed

**What’s happening**

App Store Connect can take several minutes to propagate changes.

**What to do**

* Wait 5–10 minutes
* Refresh the page
* Log out and back in if necessary

#### Triggering Initiate Purchase doesn't do anything on TestFlight

**Symptoms**

* Initiate Purchase workflow is working on web preview / BubbleGo but not TestFlight
* Triggering the workflow either does nothing or shows a "SKU Not Found" error

**What's Happening**

Apple doesn't recognize the plan you are trying to subscribe to due to missing App Store Connect or Subscription requirements

**What to do**

* Ensure App Store Connect requirements have been filled out - notably Bank Information and Paid App Agreement in *App Store Connect > Business > Agreements*
* Ensure your Subscriptions are not missing any metadata in App Store Connect


# Android IAP checklist

### Google Play subscription readiness checklist

This section outlines the required steps to prepare subscriptions for review and sale on Google Play. Google reviews subscriptions as part of the app review process, and missing any of the steps below can prevent subscriptions from appearing or cause purchases to fail.

#### 1. Account and app readiness

Confirm the following before proceeding:

* Your Google Play Developer account is active
* A payments profile has been created
* The app exists in Google Play Console
* At least one Android build has been uploaded *after* in-app purchases were enabled
  * An Internal, Closed, or Production track build is sufficient

#### 2. Subscription exists in Google Play

Path: *Monetize → Products → Subscriptions*

For each subscription you intend to sell:

* The subscription exists in Google Play
* At least one base plan has been created for the subscription

#### 3. Configure and activate a base plan (required)

Each subscription must have at least one active base plan in order to be reviewed or purchased.

Path: *Monetize → Products → Subscriptions → (Your subscription) → Base plans*

For each base plan you intend to launch:

* Billing period is set (for example, monthly or yearly)
* Price is set
* Countries or regions are selected
* The base plan has been saved
* The base plan status is *Active*

A subscription with only *Draft* base plans can’t be purchased and may not be reviewed correctly during app submission.

**Base plan statuses**

Base plan statuses are conceptually similar to Bubble billing variants:

* *Draft*\
  The base plan is incomplete or has never been activated.
* *Active*\
  The base plan is ready to be sold and reviewed with the app.
* *Inactive*\
  The base plan exists but isn’t currently available for purchase.

#### 4. Subscription details check

On the subscription details page, confirm that:

* The name and description are filled in
* Benefits accurately describe what the subscription unlocks in the app
* There is no misleading pricing, trial, or promotional language

#### 5. Submit to Google Play

Submit the app to a Closed testing track or to Production.

At the time of submission:

* Subscription products are active
* The app build includes the subscription configuration

Google reviews subscriptions as part of the app review process.

***

### Common Google Play issues

If subscriptions don’t appear or purchases fail during testing, check the following:

* The base plan status is *Active*
* Pricing is set for the relevant countries
* The app build was uploaded after subscriptions were configured


# Workflow and language

## In-app purchases data and workflows

This section explains the data sources, operators, database tables, and workflow actions that become available when you enable in-app purchases (IAP). These tools let you implement subscription logic using the same patterns you already use elsewhere in Bubble.

### Subscription purchases (system table)

Bubble automatically creates and manages a **Subscription Purchases** data table. This table serves as the source of truth for a user’s subscription state.

#### What this table represents

Each record represents a single subscription entitlement for a user. Records are created and updated based on billing notifications sent by Apple and Google. You can think of this table as a read-only system log that reflects the current state of a user’s subscription.

#### Key characteristics

* Records are created and updated automatically when Apple or Google sends billing events, such as purchases, renewals, cancellations, or expirations
* Records can’t be created, modified, or deleted manually
* Updates occur near real time based on server-side notifications

#### Available fields

| Field name                   | Type            | Description                                                                                                                                                       |
| ---------------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Original Purchase Identifier | text            | Stable identifier that groups all renewals and lifecycle events for the same subscription. Useful for tracking a subscription across renewals and status changes. |
| Purchase Identifier          | text            | Unique identifier for a specific purchase or renewal event from the store.                                                                                        |
| Status                       | text            | Current lifecycle status of the subscription (e.g. active, ending, expired, paused, on hold). Exact values are store-normalized.                                  |
| Is Active                    | yes / no        | Whether the subscription is currently considered active. This is the safest field to use for quick checks.                                                        |
| User                         | User            | The Bubble user who owns this subscription.                                                                                                                       |
| Environment                  | text            | Indicates whether the subscription originated from a sandbox/test or live/production environment.                                                                 |
| Store                        | text            | The store that manages the subscription (apple or google).                                                                                                        |
| Bubble Billing Variant       | Billing Variant | The Bubble billing variant associated with this subscription entitlement.                                                                                         |
| Store Product Identifier     | text            | The product identifier used by the App Store or Play Store.                                                                                                       |
| Google Base Plan ID          | text            | The base plan identifier used by Google Play (Android only).                                                                                                      |
| Google Latest Order ID       | text            | The most recent Google Play order ID associated with this subscription.                                                                                           |
| Expiry or Renewal Date       | date            | The date when the subscription will next renew or expire, depending on its current status.                                                                        |
| Creator                      | User            | The user that created this record                                                                                                                                 |
| Created Date                 | date            | When this subscription record was first created.                                                                                                                  |
| Modified Date                | date            | When the subscription record was last updated by a billing event.                                                                                                 |
| Slug                         | text            | Custom name for the record                                                                                                                                        |

{% hint style="info" %}
Use this table primarily for visibility and debugging. For gating features and building app logic, prefer the operators and data sources described below rather than querying this table directly.
{% endhint %}

### Core IAP Types

These types are introduced by the IAP feature and are used consistently across data sources, operators, workflows, and component properties/messages.

#### Subscription Group

A logical grouping of related subscription tiers. A user can only have one active subscription within a subscription group at a time.

#### Subscription Tier

A subscription tier within a subscription group (for example: Basic, Pro, Pro Plus). This represents the “level of access” a user gets with their plan.

#### Billing Variant

A specific billing configuration for a subscription tier, such as Monthly or Annual.

### Data sources

Bubble exposes high-level data sources that surface a user’s subscription state safely and consistently. Click the core reference link for more information about each data source.

## Has granted location permission

Returns yes or no depending on the user’s permission status. See more in the core reference entry for [on-device resources](/core-resources/on-device-resources).

## In-app purchases

Bubble adds native data sources that expose subscription products and a user’s subscription state in a safe, high-level way. These data sources are designed to support common subscription UI and logic without requiring direct access to system tables.

Bubble exposes high-level data sources that surface a user’s subscription state safely and consistently.

### **All Subscription Groups**

Returns a list of all subscription groups defined for the app.

Use this when you want to:

* Build a pricing page that lists all subscription offerings
* Iterate through groups and display tiers/variants inside each group

### **Subscription Item**

Returns a single IAP object (Subscription Group, Subscription Tier, or Billing Variant). This is similar to selecting a single option in an Option Set.

Use this when you want to:

* Display details for a specific plan or billing option
* Pass a specific billing variant into a workflow action

### **Current user’s active subscriptions**

Returns a list of Subscription Purchases for the current user that are currently active.

This data source is backed by the Subscription Purchases system table and automatically filters to records where the subscription is active.

Returns

* A list of Subscription Purchase records

Common use cases

* Displaying all subscriptions a user currently has
* Building account or billing overview pages
* Debugging or inspecting subscription state

Notes

* This returns Subscription Purchases, not subscription groups or tiers
* For access control and UI gating, prefer the subscription operators described below

### **Current user’s subscription in…**

Returns the active Subscription Purchase for the current user within a specific Subscription Group.

Arguments

* Subscription Group (required)

Returns

* Returns the active subscription purchase for the given group, if one exists
* Returns empty if the user has no active subscription in that group
* By design, a user can only have one active subscription per subscription group

Common use cases

* Showing plan details for an active subscription within a specific group without having to dynamically filter the list

### Subscription Operators

## Subscription operators

Operators are the recommended way to gate features, content, and UI based on subscription state. They abstract away raw purchase data and reflect the current, normalized subscription status.

Operators are the recommended way to gate features, content, and UI based on subscription state. They abstract away raw purchase data and reflect the current, normalized subscription status.

### User is subscribed to…

Checks whether the user is currently subscribed to the selected option. This operator can be scoped to be at the group, tier, or billing variant level depending on your needs.

**Arguments**

* Subscription Group – returns yes/no if the user has any active subscription in the group
* Subscription Tier – returns yes/no if the user is subscribed to that specific tier
* At least Subscription Tier – returns yes/no if the user is subscribed to that tier or higher, based on tier order in the subscription group
* Billing Variant – returns yes/no if the user is subscribed to a specific billing variant

Common use cases

* Gating access to premium features
* Showing or hiding UI elements based on plan level
* Checking eligibility for upgrades or downgrades

Best practices

* Prefer group- or tier-level checks for access control
* Use billing-variant checks for messaging or analytics, not core gating

### Workflow actions

#### Backend workflow events (server notifications)

Bubble exposes backend workflow events that fire when Apple or Google sends billing notifications.

## **Initiate subscription purchase**

Starts a native in-app purchase flow for a selected billing variant.

Key behaviors:

* Opens the Apple or Google payment sheet
* Handles receipt validation automatically
* Updates the Subscription Purchases table when the purchase completes

You can select a billing variant directly or pass one dynamically, such as from a repeating group.

A typical flow looks like this:

* The user taps an *Upgrade* or *Subscribe* button
* The workflow runs *Initiate subscription purchase*
* Bubble hands off to Apple or Google
* The subscription state updates automatically

{% hint style="warning" %}
**Important:** A User must be logged into an account on your app in order to initiate a purchase as a Subscription Purchase record must be linked to a User. Hide all billing actions until a user has logged in.
{% endhint %}

## **Manage subscription**

Redirects the user to the platform-native subscription management screen.

Use this action to allow users to:

* Cancel a subscription
* Change billing frequency
* Upgrade or downgrade within a subscription group

Apple and Google control the management UI. Bubble provides the entry point, not the management controls themselves.

{% hint style="warning" %}
**Note:** Users can also upgrade or downgrade by initiating a purchase on a different plan for upgrades, downgrades, and cross-grades.
{% endhint %}

## Restore purchases

Restore purchases lets users who have previously made an in-app purchase restore that purchase on a new device or after reinstalling the app, without being charged again.

This action is required by both Apple and Google and must be available to users in your app. Failing to include it may result in your app being rejected during app review.


# Native mobile app terminology

While the Native Mobile App Editor is similar to building a regular Bubble app in many ways, there are some new terms and concepts introduced specifically for mobile development. Some of these terms may be familiar, but we recommend reviewing them to ensure you’re aligned with the exact definitions we’ll be using throughout the documentation.

<table><thead><tr><th width="240">Term</th><th>Definition</th></tr></thead><tbody><tr><td>App scheme</td><td>This setting defines a custom identifier for your app, used primarily in scenarios where external services need to redirect users back to the app after completing an action. This is commonly required for callbacks from OAuth providers, payment gateways, or other integrations that require redirection to the app.</td></tr><tr><td>Bubble web app / web app</td><td>This is the term we use to describe the “regular” Bubble web app editor, as opposed to this native mobile app editor.</td></tr><tr><td>Cross-platform</td><td>Cross platform means a native mobile app that is built once, but works on both iOS and Android devices out of the box. Cross-platform in this context does not include web.</td></tr><tr><td>Gesture</td><td>A gesture is similar to a click in web, but includes things like taps, long press, swipes, pans, and pinches. Users use gestures to interact with a mobile app’s UI and trigger workflows.</td></tr><tr><td>Global component</td><td>Global component in this doc refers to a component, such as the tab bar, that behaves similar to a reusable element in the sense that it is available on every view, but editing it changes the component across your app. Global components are generally system components offered by Bubble, rather than as something the user builds from scratch.</td></tr><tr><td>“Mobile friendly” app</td><td>A mobile friendly, or responsive, app is a web application that is opened in a user’s mobile browser. This type of app has been designed to look and perform well on smaller or larger screen sizes.</td></tr><tr><td>Native mobile app</td><td>A native mobile app is an app that can generally only be downloaded from the Google Play or Apple app store and runs native Swift code on Apple devices and Kotlin code on Android devices. This means it has full access to device hardware, software, and gestures.</td></tr><tr><td>Native mobile app editor</td><td>This is the term we use to describe the part of the Bubble editor where you edit your native mobile app, as opposed to the above Bubble web app.</td></tr><tr><td>Progressive web app</td><td>A progressive web app (PWA) is a responsive web application that has been wrapped in a service that allows it to run on a mobile device from the home screen rather than a user’s browser. While iOS and Android platforms are supporting more functionality these days, they are generally limited in what device hardware and software they have access to as opposed to native mobile apps. Note: Android supports far more functionality on PWAs than Apple does.</td></tr><tr><td>Section list</td><td>A section list is like a vertical list, but it groups your list items by some property on your data source. For example, your contacts app is a section list because it groups your contacts by the first letter of the contacts name.</td></tr><tr><td>Safe area</td><td>Newer devices have notches, floating islands, curved bezels, etc. which must be accounted for when designing your app. Safe areas are areas on your phone that could be impacted by these device idiosyncrasies.</td></tr><tr><td>View</td><td>Similar to a page web. This is the highest level “container” and represents what the user will see on their screen when on a particular view</td></tr></tbody></table>


# Building your first app

Starting your journey in app development can feel like learning a new language, especially with the intricate jargon and technical terminologies used in traditional programming. Bubble uses intuitive and self-explanatory terminology like *things*, *workflows*, and *conditions*, to remove the barriers of complex coding languages, allowing you to focus on bringing your ideas to life.

In this article series we'll explore the strategy for building your first app, and cover the basic Bubble concepts to get you up to speed quickly.

## What does it mean to build an app?

If you are new to app development, it may not be entirely clear what exactly it entails to build an app. That's not strange at all – after all, in traditional programming, an app is built by a *team* of people where each member specializes in one key discipline such as design, coding and database management. Bubble, on the other hand, is built to make you able to do that all on your own.

That doesn't mean that you *can't* work with a team – you most certainly can – but it means that our tools are designed so that you don't have to. Bubble provides the flexibility and resources for both solo developers and teams to bring their app visions to life.

Most apps are about collecting information, and then manipulating and presenting it in a way that's solves problems for its users. While apps can seem extremely different on the outside, they are mostly all the same thing: a useful design on top of a database.

* A **social network** collects data about users (name, profile pic, age, interests, posts) and then loads that from the database to show it to other users. A *like* on Facebook or X may *feel* social, but it's all simply data that's presented in a social way.
* A **CRM** collects data about clients, vendors and contacts, and then lets its users pull that data up whenever needed. Maybe it also crunches some data into statistics.
* A **food ordering app** collects data about restaurants, menu items, drivers, customers and orders and uses that data to automate the ordering process.

You get the point – while these are all very different categories, they are in essence doing the same thing: at the bottom is a database that users fill up with data, and on top of that is a design that makes the data useful. Many apps simply take real-life things we were already doing, like talking about our interests and ordering food, and automates them.

So, to build an app, you need to:

* Set up your **database** to store information
* Design a **user interface** that intuitively directs users to add, modify, delete, view, and analyze data effectively
* Link your design to **workflows**, ensuring that the app responds to user interaction

Most applications are built in an incremental way: they are designed to solve a simple problem (like remembering a list of clients), and then its developer progressively new helpful features are added.

## Core terminology

As you embark on your app-building journey, you'll come across some specific terms that will become the building blocks of your Bubble experience. Knowing the terms and phrases that are frequently used will also help you communicate with other Bubble users in the [forum](https://forum.bubble.io/).

{% hint style="info" %}
For a more complete list of Bubble terminonology, you can also check out our glossary.

Article: [Glossary](/the-glossary)
{% endhint %}

Let's demystify these terms to ensure you have a smooth start. In each of the expandable boxes below, we'll explore one facet of app building and go over the terminology used in each one.

<details>

<summary><mark style="color:blue;">Design:</mark> how your app looks</summary>

We'll start by exploring **elements**. This is any visual item or component you'll place on your Bubble page. Think buttons, texts, inputs, and so on. It's what makes up your app's interface your app's interface.

Elements can have **styles** associated. This lets you set up design attributes (color, border, shadow, font, etc) in one place and apply it to multiple elements.

Bubble comes with a lot of built-in elements, but you can also add new types of elements with [plugins](#plugins-enhancing-bubbles-capabilites).

**Learn more:**

* Article series: [Design](/help-guides/design)
* Article: [Design and UX resources](/help-guides/getting-started/building-your-first-app/design-and-ux)
* Page: [Bubble plugins](https://bubble.io/plugins)

</details>

<details>

<summary><mark style="color:blue;">Workflows:</mark> making your app do stuff</summary>

As you design, you'll be introducing interactions. Here's where **Workflow** comes into play. Imagine you want something to happen when a button is clicked - that's a workflow. It's a sequence of automated steps or actions initiated by an **event**.

The event is the specific trigger for your workflow. A button being clicked, a dropdown being changed, or even a page loading can all be events that kickstart a workflow.

Each step in a workflow is known as an **action**. Actions can make changes in the database, navigate to another page, hide/show something on the page and a wide range of other things.

**Learn more:**

* Article series: [Workflows](/help-guides/logic/workflows)

</details>

<details>

<summary><mark style="color:blue;">Conditions:</mark> if this, then that</summary>

Often, you'll want actions to happen only under certain circumstances. Enter **conditions**. This is the "if this, then that" of Bubble. Conditions dictate when specific actions or visual changes should take place based on criteria you set. For example, clicking a button takes you to another page, but *only* if the current user is logged in.

Conditions can also be placed on elements to control their appearance. For example, a button is only visible if the current user is logged in.

Conditions are built using **dynamic expressions**. Dynamic expressions are like "live" formulas that update in real-time based on user input, database updates and other changes in your app.

**Learn more:**

* Article: [Dynamic expressions](/help-guides/logic/dynamic-expressions)
* Article: [Conditions](/help-guides/logic/conditions)

</details>

<details>

<summary><mark style="color:blue;">The database:</mark> managing data</summary>

As you populate your app with data, you'll be dealing with **Things**. A 'Thing' is just an individual piece of data in Bubble. Think of it as an entry or record in the database, such as a specific user.

Each Thing belongs to a **Data Type**, which is like its category or table. For instance, if you're building a blogging app, *Blog Post* could be a data type. If you're building a task management app, both *Task* and *Project* can be data types.

Within these data types, there are **Fields**, which are attributes or properties. Using the Blog Post example, *Title* and *Content* could be fields.

To protect your data, you use **privacy rules** to define who can access or modify what data. It's your one-stop control center for data protection.

**Learn more:**

* Article series: [The database](/help-guides/data/the-database)
* Article: [Protecting data with privacy rules](/help-guides/data/the-database/protecting-data-with-privacy-rules)

</details>

<details>

<summary><mark style="color:blue;">Previewing:</mark> testing your app before users get access</summary>

Bubble gives you two distinct environments: Test and Live

**Test environment**: This is your sandbox, a safe space where you can experiment, iterate, and make changes without affecting your actual users. Any data you use or create here won't touch the live environment. It's an ideal place for debugging and trying new features.

**Live environment**: This is the real deal. The data here is what your actual users will interact with. Once you're satisfied with the changes in your test environment, you can **deploy** them to the live environment, ensuring that your users always experience a polished and tested version of your app.

Each environment has its own separate database. This distinction ensures that your test experiments won't accidentally overwrite or corrupt the real user data you've gathered.

If you need more advanced branching capabilities to work on features in isolation, you can also use our version control feature.

**Learn more:**

* Article series: [Version control](/help-guides/maintaining-an-application/version-control)

</details>

<details>

<summary><mark style="color:blue;">Reusable elements:</mark> avoid repeating work</summary>

As you build, you might create a component that you want to use repeatedly. That's a **Reusable Element**. It could be a navigation bar, footer, or any component you don't want to rebuild from scratch every time.

**Learn more:**

* Article: [Reusable elements](/help-guides/design/elements/web-app/reusable-elements)

</details>

<details>

<summary><mark style="color:blue;">Plugins:</mark> enhancing Bubble's capabilities</summary>

Now, there will be instances when you want to enhance Bubble's capabilities. That's where **Plugins** come in. They're like add-ons, enhancing functionality or allowing integrations.

There is a collection of Bubble-built plugins, and a plugin store with thousands of user-created plugins.

* Article: [Plugins](/help-guides/integrations/using-plugins)
* Page: [Bubble plugins](https://bubble.io/plugins)

</details>

<details>

<summary><mark style="color:blue;">API:</mark> connecting your app to other apps</summary>

You'll likely come across the term **API**, an acronym for Application Programming Interface. Think of it as a language that allows your app to communicate and exchange information with other applications. This capability unlocks a treasure trove of possibilities: from integrating real-time weather updates, baseball stats, and demographic information, to scheduling events in a user's Google Calendar or posting updates on Twitter.

Bubble comes with three different API tools:

* The **Bubble API** comes with two tools for handling *inbound* API calls:
  * The **Data API** lets you invite other apps to read and write in your app's database
  * The **Workflow API** lets you invite other apps to run workflows in your app
* The **API Connector** is a plugin that lets you connect to external apps and services to make *outbound* calls

**Learn more:**

* Article series: [API](/help-guides/integrations/api)
  * Article series: [The Bubble API](/help-guides/integrations/api/the-bubble-api)
  * Article series: [The API Connector](/help-guides/integrations/api/the-api-connector)
* Article: [Bubble API terminology](/help-guides/integrations/api/the-bubble-api/bubble-api-terminology)

</details>

That's our whirlwind introduction to Bubble's core terminology! If it feels like a lot to take in at once, don't worry: as you start building, these terms will become second nature. They are consistently used around the editor to familiarize you with them as you go through your learning journey.

## Planning your first app

Many users who try out Bubble for the first time already have an idea of what they want to build. If you have never built an app before, the process of planning it out might seem daunting, but Bubble's intuitive design and workflow mechanisms are designed to guide beginners through the creation process seamlessly.

Starting with a clear vision is essential. Break down your idea into core functionalities and the interactions you expect users to have with your app. Sketching out a basic wireframe on paper or using a digital tool can help you visualize the layout and user journey. Even if it's just rough boxes and arrows, it'll give you a roadmap to follow.

Remember, the key to a successful app isn't just in its functionality, but also in its user experience. Consider the end-user at every stage of development.

In the next section, we'll have a look at how you can go about deciding what [features to include in your app](/help-guides/getting-started/building-your-first-app/planning-features).

## Other ways to learn

<details>

<summary>Video lessons</summary>

The playlist below gives an introduction to how Bubble works and how to build your first app:

* Playlist: [Build your first Bubble app](https://www.youtube.com/watch?v=SHbY8eoe8Gw\&list=PLoNVJrdvQQYlT3e3qur1LDgP8Rcs9msAm\&pp=iAQB)

</details>


# Planning features

Before you start building, you need to decide what kind of features your app needs. Most app ideas start out as a general vision of what the app is supposed to do. You then take that vision and breaks it into the features needed to make that happen. When you've decided on the features you need, you can start planning how those features should work in an actual interface.

In this article, we'll cover:

* [What a features is](#what-is-a-feature)
* [How to plan what features you want to include in the first version of your app](#planning-features-for-your-mvp)
* [Setting up user journeys to decide how a specific feature should work](#the-user-journey)

## What is a feature?

A **feature** is essentially a distinct function or capability that an app offers. It's what allows users to perform certain tasks or achieve specific outcomes within an app. They can broadly be categorized into two types: *general features* and *specific features*.

### General features

These are foundational features commonly found in a majority of apps across different genres. They form the basic infrastructure, facilitating the core user interactions.

Typical examples include:

* **User sign-up/sign-in**: allow users to create an account and log into the app
* **Reset password:** let users reset their password without you as the developer having to intervene
* **Notifications**: alert users about updates, messages, or other important information
* **Search bar**: let users search for content

General features are what makes your app *work*, but they're not what sets it apart. When you plan your app, it's a good idea to list these features as well, to make sure you have the full picture of the project ahead.

### Specific features

Specific features are unique to the nature and purpose of the app. They are linked closely with the app's core idea. For instance:

* For a food delivery app, a specific feature might be the ability for users to explore a restaurant's menu and add items to a cart
* In a language learning app, a custom vocabulary quiz tailored to a user's learning history could be a specific feature.
* For a budgeting app, a feature that projects future savings based on current spending habits might be included.

The specific features are what sets your app apart from other apps: the reason it's being developed in the first place. If your app exists to solve a problem, the app's features are *how* the problem will be solved.

Next, let's look at how you can prioritize features by thinking of your app in *versions*.

## Think in versions

It's useful to think of your app in versions, so that you know what exactly you are building. It's very easy to come up with new ideas as you work, and get sidetracked from your initial plan.

By planning out versions, you can stay focused on delivering a core set of features first, and then progressively enhance and expand your app based on feedback and actual user needs.

For example, you could think about your versions as follows:

**1. MVP (Minimum Viable Product):** This is the most basic version of your app, with only the essential features needed to make it functional. The goal here is to get something to market quickly to test the concept, gather feedback, and identify potential improvements and even pivots[^1].

**2. Version 1.0:** Once you've validated the idea with your MVP, it's time to refine. This version will have improved UX/UI, and possibly some new features based on the feedback from your MVP. You'll also address any significant bugs or problems identified during the MVP phase.

**3. Version 2.0 (and beyond):** As your user base grows and you collect more feedback, you'll start to identify more areas for enhancement and expansion. This could involve adding new features, improving existing ones, or expanding into new markets or platforms.

Remember, it's tempting to want to add every feature you can think of from the get-go, but restraint can help you get your app to the market on schedule. Every time a good idea comes up, you can of course take note of it, and plan it into a future version.

Furthermore, taking a step-by-step approach allows you to adapt and pivot based on real-world feedback, rather than assumptions.

Users will often use your app in ways you hadn't anticipated or express needs you hadn't considered. By listening to them and iterating on your product, you increase the chances of your app's success in the long run.

## Planning features for your MVP

The first version of an app is often referred to as the Minimum Viable Product (MVP). The goal is to create something functional that effectively addresses the primary need or problem you've identified, without getting bogged down in secondary features or details.

Here's how you can approach this:

1. **Identify the core problem:** What primary problem does your app aim to solve? Understanding this will help you prioritize the features that are absolutely necessary for your MVP.
2. **User stories:** Draft user stories to envision how users will interact with your app. For example: "As a user, I want to be able to create new contacts so that I can keep track of my clients."
3. **Prioritize features:** List all the features you envision for your app and then rank them based on their importance to the core problem. Focus on the top priorities for your MVP.
4. **Sketch or wireframe:** Use sketches or wireframes to map out the user journey and interface. This visual aid will help you understand how the features integrate and flow together.
5. **Feedback loop:** Share your prioritized feature list and wireframes with potential users, stakeholders, or teammates. Gather feedback and adjust your plan accordingly.
6. **Avoid** [**feature creep**](#user-content-fn-2)[^2]**:** It's tempting to add more features as you plan, but stay focused on the primary goal. Additional features can always be added in later versions once the MVP has been tested and validated.
7. **Technical feasibility:** If you are unsure whether Bubble is the right platform for your project, you can get in touch with our [Success team](https://bubble.io/contact) or ask other users in our active [forum](https://forum.bubble.io/). Describe your idea and you can quickly get feedback on whether it's feasible.
8. **Plan for feedback collection:** As the purpose of the MVP is to test and validate your idea, have mechanisms in place (like feedback forms, analytics tools, etc.) to gather user feedback once it's launched.

Even if Bubble is a very efficient tool to build in, don't be afraid to leave out features that are not absolutely needed to get your idea validated. You can always add those in later.

## The user journey

A user journey represents the series of steps or interactions a user undertakes to achieve a particular goal within your app or website. By mapping out these journeys, you can make sure that you have a plan for how users will reach a specific goal step-by-step.

When designing user journeys, start by envisioning a specific persona. *Who* is the individual embarking on this journey? Understanding this user's characteristics can guide you in tailoring the experience to suit their needs. For instance, while some users might be tech-savvy, others could benefit from more detailed instructions and a prominent call-to-action[^3].

### Sign-up user journey

For example, you could set up a user journey for signing up to your app. This process can be a critical, as it often represents a user's first interaction with your app.

1. **User persona**:
   * **Name**: Sarah
   * **Age**: 28
   * **Tech-savviness**: Moderate
   * **Goal**: To sign up for the app quickly and without hassles to explore its features.
2. **Entry point**:

   Sarah hears about the app in social media and decides to try it out
3. **Opening the app**:

   Sarah opens the app and is greeted with a welcome screen showcasing the app's main features.
4. **Call to action**:

   After the intro slides, Sarah sees two prominent buttons: "Sign Up" and "Log In."
5. **Sign up option**:

   Sarah clicks on "Sign Up" and is presented with options:

   * "[Sign Up with Email](#user-content-fn-4)[^4],"
   * "[Sign Up with Google](#user-content-fn-5)[^5],"
   * "[Sign Up with Facebook](#user-content-fn-6)[^6]."
6. **Choosing sign-up method**:

   Wanting to keep things simple, Sarah chooses "Sign Up with Google."
7. **Permissions**:

   A prompt asks Sarah to allow the app to access her Google account info. She confirms.
8. **Additional details**:

   The app asks Sarah for a few more details to enhance her experience: her interests and preferences related to the app's features.
9. **Confirmation and feedback**:

   Once she provides the details, Sarah gets a success message: "Thank you for signing up, Sarah! Let's get started."
10. **Profile setup** (optional step):\
    Sarah is then prompted to complete her profile by adding a profile picture and other optional details.
11. **First use experience**:\
    After the onboarding, Sarah is directed to the app's main dashboard or home screen to start exploring.
12. **Follow-up email**:\
    Five minutes later, Sarah receives a welcome email reiterating the app's features and providing resources like FAQ and customer support links.

Throughout this journey, keep the following things in mind:

* Ensure that each step is intuitive and not overwhelming.
* Provide clear instructions and feedback.
* If needed, make it easy to ask for help.
* Keep the number of steps minimal, asking only for necessary information to ensure a high signup rate

While many sign-up processes look very similar, you don't need to see this as a blueprint: you are free to add or remove steps that you think make sense for your app. The idea is to know what the process should look like *before* you start building. This approach ensures you don't overlook crucial steps, making your development process as efficient as possible.

You can set up the user journey in steps, like we did in the example, but you can also use apps like [Miro](https://miro.com/), [Apple Freeform](https://www.apple.com/newsroom/2022/12/apple-launches-freeform-a-powerful-new-app-designed-for-creative-collaboration/) or [Lucid](https://lucid.app/) to set it up more visually. Sometimes, good old pen and paper works best.

In the next section, we'll explore how you can think about the [data that you want to manage in your app](/help-guides/getting-started/building-your-first-app/database-structure).

[^1]: A *pivot* refers to a fundamental change in strategy, made in response to feedback. This can be to test a new hypothesis about the product, business model, or engine of growth.

[^2]: *Feature creep* refers to the excessive addition of new features to an app, leading to unnecessary complexity, and causing delays in its release.

[^3]: A call-to-action is a visual indication on the screen that encourages the user to do something.

    For example, you can place a brightly colored button with the text “SIGN UP” in a prominent place on the page to encourage users to sign up.

[^4]: Bubble has a built-in system for signing up new users using their email address.

    Article: [User accounts](/help-guides/data/user-accounts)

[^5]: Bubble has a plugin for signing up new users using their Google account.<br>

    Page: [Let users sign in with Google](https://bubble.io/integration/google-sign-in#!)

[^6]: Bubble has a built-in system for signing up new users using their Facebook account.

    Page: [Let your users Log in with Facebook](https://bubble.io/integration/facebook#!)


# Database structure

Now that you have decided on the features you want in your app, it's time to start thinking about how you structure your data. We won't go into the *technical* details on how to set that up just yet, but keep focusing on the planning stage.

{% hint style="info" %}
We also have a long list of guides that go into detail on how to plan a database structure for a specific app category (like a project management app, marketplace app or blog). You can use this as inspiration for your project and learn how experienced Bubble developers think:

Article series: [Database structure by app type](/help-guides/data/the-database/database-structure-by-app-type)
{% endhint %}

## Visualizing your data

When you plan your database structure, you should take notes. There are a range of different ways to do this, and there's really no "best practice". You should use whatever method makes sense to you.

For many users, simply noting things down with pen and paper or on a whiteboard is the most efficient way, at least for the first planning stage. You can also consider apps that focus on whiteboarding and diagrams, such as [Miro](https://miro.com/) and [Lucid.app](https://lucid.app/).

<figure><img src="/files/kScEEuSsYD4ApSlfBQVK" alt=""><figcaption><p>Planning your database structure doesn't need fancy tools: use whatever you're comfortable with. Pen and paper works just fine.</p></figcaption></figure>

Don't worry about taking notes in the "right" way: your goal is simply to get an understanding of what kind of data you want to store before you start building.

## The data types

<details>

<summary>Transitioning from SQL Databases? Here's what to know in Bubble.</summary>

In traditional databases, relationships between tables are commonly maintained through primary and foreign keys that act as unique identifiers. However, when learning Bubble, you'll notice that it's handled a bit differently.

Bubble simplifies database relations with its intuitive design. Rather than juggling keys, Bubble allows for direct linking of data types. This not only streamlines data management but also offers a more user-friendly and visual experience for those who don't have a background in database design. You can read more about this in the article section below.

Article section: [How the Bubble database is different from traditional databases](/help-guides/data/the-database#how-the-bubble-database-is-different-from-traditional-databases)

If you are interested in learning more about the underlying technology that powers database, we also have a section that covers this:

Article section: [Technical information about the Bubble database](/help-guides/data/the-database#technical-information-about-the-bubble-database)

As you map out your database, you can still think of Bubble as a traditional relational database and plan your data types and relationships accordingly.

</details>

The [data types](#user-content-fn-1)[^1] make up the overarching types of data that you want to store in your app. Let's look at some examples to illustrate:

**Social media app** (see our detailed guide [here](/help-guides/data/the-database/database-structure-by-app-type/social-network-apps))

* Users
* Posts
* Interests

**Project management app** (see our detailed guide [here](/help-guides/data/the-database/database-structure-by-app-type/project-management-apps))

* Users
* Projects
* Tasks

**eCommerce app** (see our detailed guide [here](/help-guides/data/the-database/database-structure-by-app-type/online-store-ecommerce-apps))

* Users
* Products

These are of course not exhaustive lists, but serve as examples to show *what* a data type is. Think about the vision you have for your app, and try to plan out what kind of data you need to store to make it a helpful tool.

### The fields

The [*fields*](#user-content-fn-2)[^2] are the data that you store in each data type. Fields can consist of different types of data, such as text, numbers, files, images and dates.

Let's repeat the examples from above and add some fields to those data types:

**Social media app** (see our detailed guide [here](/help-guides/data/the-database/database-structure-by-app-type/social-network-apps))

* Users
  * Name (text)
  * Date of birth (date)
  * Profile pic (image)
* Posts
  * Header
  * Content
* Interests
  * Name

**Project management app** (see our detailed guide [here](/help-guides/data/the-database/database-structure-by-app-type/project-management-apps))

* Users
  * Name (text)
  * Profile pic (image)
* Projects
  * Project name (text)
  * Project description (text)
* Tasks
  * Task name (text)
  * Task description (text)
  * Deadline (date)

**eCommerce app** (see our detailed guide [here](/help-guides/data/the-database/database-structure-by-app-type/online-store-ecommerce-apps))

* Users
  * Name (text)
  * Address (address)
* Products
  * Header (text)
  * Description (text)
  * Image (image)
  * Price (number)
* Shopping cart

### Relationships

Bubble's database is what's called a *relational database.* Relational simply means that different kinds of data can be *connected* somehow. For example, in a social media app, a post is *connected* to a user – that is, it "belongs" to the user that posted it.

This method is used in a range of different ways to structure how different types of data are related to each other. Relationships are just another field that's added to the data type. In the social media example, we could add a field to the *Post* data type, and that field is of type *User*. We can call that field whatever we want, such as *Owner*.

A relationship can contain *one* thing (i.e. a Post is connected to one owner), or a *list of things* (i.e. a Shopping cart contains a list of Products).

Let's again go over the earlier examples and see where it makes sense to connect data. We've marked the relationship fields in <mark style="background-color:green;">green</mark>.

**Social media app** (see our detailed guide [here](/help-guides/data/the-database/database-structure-by-app-type/social-network-apps))

* Users
  * Name (text)
  * Date of birth (date)
  * Profile pic (image)
  * <mark style="background-color:green;">Interests</mark> (list of Interests)
* Posts
  * Header
  * Content
  * <mark style="background-color:green;">Owner</mark> (User)
* Interests
  * Name

**Project management app** (see our detailed guide [here](/help-guides/data/the-database/database-structure-by-app-type/project-management-apps))

* Users
  * Name (text)
  * Profile pic (image)
* Projects
  * Project name (text)
  * Project description (text)
  * <mark style="background-color:green;">Owner</mark> (User)
  * <mark style="background-color:green;">Tasks</mark> (list of Tasks)
* Tasks
  * Task name (text)
  * Task description (text)
  * Deadline (date)
  * <mark style="background-color:green;">Owner</mark> (User)
  * <mark style="background-color:green;">Project</mark> (Project)

**eCommerce app** (see our detailed guide [here](/help-guides/data/the-database/database-structure-by-app-type/online-store-ecommerce-apps))

* Users
  * Name (text)
  * Address (address)
* Products
  * Header (text)
  * Description (text)
  * Image (image)
  * Price (number)
* Shopping cart
  * <mark style="background-color:green;">Owner</mark> (User)
  * <mark style="background-color:green;">Products</mark> (list of Products)

Don't worry about the technical side of this just yet – we'll cover that in detail in our [article series about the database](/help-guides/data/the-database). Don't see this is a blueprint for setting up your app either – these are just simplified examples to get you into the right mindset:

* **Data types** are the overarching types of data in your app, such as users, tasks, products and blog posts
  * **Fields** contain the actual data stored in these types, such as name, phone number, description and image
* Data types can be **connected through relationships**, such as a shopping cart to an owner. These are just another field saved on the data type.

Keep in mind we are still in the planning stage. Planning out what kind of data you need your app to store will help you get a better understanding of how you can fulfil the vision that made you want to make an app in the first place.

Remember what we said in the first part of this section: most apps consist of a database at the bottom and a user interface on top of it. Now that we've covered the data, let's move on to the [Design and UX](/help-guides/getting-started/building-your-first-app/design-and-ux) of your app.

[^1]: The database consists of different *data types* that you as the developer plan and set up. Data types are like containers that hold specific types of information and each data type is made up of fields where that information is stored.\ <br>

    Article: [Data types](/help-guides/data/the-database/data-types-and-fields)

[^2]: Each data type consist of *fields* that hold different kinds of information. These fields are formatted in specific ways and can only hold the type of information that they are set up to contain.\ <br>

    To illustrate, think of a phone book: a *Contact* has different fields for name, date of birth and email address and the format of these fields remains constant: name and email address will always contain text and date of birth will always contain a date.

    Article section: [Fields](/help-guides/data/the-database/data-types-and-fields#fields)


# Design and UX

Unless you come from a design background, it can be difficult to know where to start. Luckily, there is an abundance of design resources both within the Bubble community and on the web in general.

Let's first quickly cover what exactly we *mean* by design.

## What is app design?

The short and easy answer is of course: design is what your app looks like. Let's break that down into some more detail.

The design process of an app can be categorized into two main disciplines:

### UI and UX

The **user interface** (often called UI) is the overall look of an app, including page layout, colors, typography, icons, images and other elements. Designing a user interface is about making the app look visually pleasing and aligned with the brand identity.

**User experience** design focuses on how the user perceives the app. Is it easy to navigate and use? Is it logical in its structure? Does it solve problems efficiently? UX design is rooted in understanding and optimizing the user's journey, ensuring that the interface is intuitive, efficient, and user-friendly.

So from this, we can draw up a few points that you can focus on in your design process:

* An app should look good and have a consistent design to represent your brand
* It should be easy to understand, navigate and use

Design is of course a wide and professional discipline. If you don't come from a design background, we recommend studying the work of great designers to get ideas and inspiration for your own work.

### Finding inspiration

Creating software that is easy to use and useful is both an art and a science. Have you given any thought as to why some apps feel like a natural extension of your brain, while others are frustratingly clunky? Great software feels intuitive, anticipates user needs, and delivers a delightful experience that keeps users coming back.

Behind every seamless app or platform is a meticulous combination of design thinking, understanding of user behavior, and technical prowess. Drawing inspiration from these standout pieces of software can guide your own app-building journey.

Study them, not just as a user, but as a creator. Dive deep into their user interface (UI), their user experience (UX), and the problems they solve. Ask yourself, why does a particular feature resonate with you?

Many app ideas will belong to one or more software categories. For example, if you are writing software to handle internal projects, inventory or HR in your company you can find a lot of inspiration in already existing software doing similar things.

Sometimes, great inspiration passes us by because we don't really reflect on *what* it is that makes it good. You will likely find things in these apps that you really like, and other things that you think could be improved – make it a habit to keep an eye out for clever solutions and good user experiences and ask yourself *why* it's good.

## Design resources

### Bubble resources

Bubble has a very active and helpful community that offers plenty of resources to get you started on your app's design.

#### Templates

We have a large and growing catalogue of templates in our template store. Templates are not only about design, but often offer fully functioning apps that you can use as a springboard for your own project. Some templates focus on multi-purpose apps, while others serve specific niches, such as eCommerce, task management and landing pages.

Page: [Template store](https://bubble.io/templates)

#### Component library

The Component Library is a collection of pre-built User Interface (UI) components that can be dragged and dropped onto your page to help you build beautiful interfaces faster. These UI components are fully responsive and are made up of containers, visual elements, and form inputs that can be individually customized once added to your page.

Page: [The Component Library](/help-guides/design/the-component-library)\
Video: [Introducing the Component Library](https://www.youtube.com/watch?v=5dgmchu7S6o)

#### Showcase

Bubble hosts millions of apps, and you can easily find inspiration in our [Showcase](https://bubble.io/showcase). This is where we publish customer success stories.

Page: [Bubble showcase](https://bubble.io/showcase)

### Third-party resources

From pre-designed templates to user interface guidelines, and from color palette generators to typography recommendations, there are tools and platforms dedicated to helping.

**1. Design templates:** Websites like [Dribbble](https://dribbble.com/), [Envato elements](https://elements.envato.com/) or [Behance](https://www.behance.net/) showcase design works from professionals worldwide. These can serve as inspiration or even a starting point for your app’s interface. Some platforms also offer downloadable UI kits to give you a jumpstart.

**2. Color tools:** Websites like [Colormind](http://colormind.io/), [Adobe Color](https://color.adobe.com/create/color-wheel), or [Coolors](https://coolors.co/) can help you generate color schemes that look harmonious and pleasing to the eye. Understand the psychology of colors and pick a palette that aligns with your app's mood and purpose.

**3. Typography:** Tools like [Google Fonts](https://fonts.google.com/) or [FontPair](https://www.fontpair.co/) can help you pick the perfect font combinations for readability and aesthetics. Remember, typography plays a crucial role in user experience, so ensure your text is legible across devices.

**4. Icon libraries:** Need more icons than the built-in ones? Websites like [FontAwesome](https://fontawesome.com/), [Iconfinder](https://www.iconfinder.com/) offer vast libraries of icons that you can use to enhance your app's design. We also have plugins in the plugin store that offer more icons.

**5. Mockup and prototyping tools:** Platforms such as [Figma](https://www.figma.com/) or [Sketch](https://www.sketch.com/) allow you to create detailed mockups and interactive prototypes of your app. This helps visualize the end product. Bubble has a [tool for importing designs from Figma](/help-guides/design/importing-from-figma).

**6. Design guidelines:** For beginners, sticking to guidelines can be helpful. [Material Design](https://m3.material.io/) (by Google) and [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/) (by Apple) provide principles and recommendations to create a consistent and intuitive user experience.

**7. Feedback platforms:** Websites like [UserTesting](https://www.usertesting.com/) or [Maze](https://maze.co/) can help you gather feedback on your designs from real users. Feedback at this stage can save countless hours of rework after development.

Lastly, remember that design isn’t just about aesthetics. Good design solves problems. It's about creating an intuitive, seamless experience for your users. As you dive deeper into design resources, keep the user's needs and behaviors at the forefront of your decisions. With the right tools and a user-centric mindset, you can craft a design that not only looks good but also feels right to your target audience.

## The design process

When your ideas are in place and after you've found the inspiration and resources needed to bring it to life, it's time to think about how to transfer that design to the screen. There are various methods to do this, and no right or wrong way. Decide for yourself what you're most comfortable with, and focus on getting to the finish line.

### The tools

#### Pen and paper/post-its

Again, there is nothing wrong with using pen and paper during the first part of designing your app. Many users enjoy the freedom of not having to learn any new tools and the speed at which you can draw something directly on the page – and the feeling of throwing away stuff you no longer need.

<figure><img src="/files/3Ad0ZIViOkDYXcZkfbxM" alt=""><figcaption><p>Planning your app's design doesn't have to start on the screen – a sheet of paper, post-it notes and whiteboards are a great place to start sketching quickly.</p></figcaption></figure>

Pen and paper is great for visualizing different parts of the user journey too. For example, when a user signs up, what fields do you need? How should that form look? How many fields are too many?

#### Mockups/prototypes

Another method is to use mockup and prototyping tools (as suggested in point 5 in the above list). These are essentially visual tools that let you draw your ideas on the screen more in the way that you intend for them to look in the final app. Using a mockup tool like Figma can be highly useful as it offers real-time collaboration, cloud storage and the design can be imported directly into Bubble.

Your choice between using a prototyping tool or diving straight into Bubble largely hinges on your personal preferences and familiarity with such platforms. If you're wary of adding another layer of software mastery to your plate, you might choose to dive directly into Bubble's design environment instead.

#### Designing in Bubble

Bubble comes with a visual WYSIWYG[^1] editor that's designed to help you place elements directly on a canvas and see the results immediately, similar to popular prototyping tools.

<figure><img src="/files/8xGeiWxs4tJAwW9UIfHR" alt=""><figcaption><p>Bubble's WYSIWYG editor makes it easy to see results immediately. With the instant <a href="/pages/hysMxLLZjzev2BTT1WTz">app preview</a>, you can see exactly what the app will look like to your users.</p></figcaption></figure>

If you choose this approach when you plan your design, we strongly recommend reading through the [Design](/help-guides/design) section of the manual to learn how Bubble's tools work. From there, we also link to video lessons that can get you quickly into the mindset of designing in Bubble.

### The UI kit

To keep your design process efficient, it's often a good idea to think about a UI kit. This is essentially a collection of elements that have consistent style attributes, which can include buttons, icons, form inputs, typography, and other elements.

If you want to set up and maintain a consistent design, we recommend going through this process first. Not only does it make it easier to be consistent in your design choices, but it can really speed up the design process overall, since you don't need to spend time on each individual element.

<figure><img src="/files/Y930SEzyuNc7draBfT7V" alt=""><figcaption><p>Setting up a UI kit can be as simple as placing and styling a few elements on a page, but it can make your design process a lot more efficient and consistent.</p></figcaption></figure>

The idea is to think about the different elements that you will use in your app, and then set up their design in one place. This can be a page, or a reusable element for example. Whenever you need a specific button, you simply copy/paste it from that page to use it elsewhere.

You decide how detailed and advanced you want to be in your UI kit. Below we suggest a few different levels of complexity – if this is your first app, you may choose to only focus on the basics for example.

Think about all the design attributes that should consistently apply to an element, such as its color, border, shadow, width, height and font/font size.

#### Start with the basics

Focus on the fundamental components first:

* **Buttons**: These come in various sizes and states. Design for primary, secondary, and tertiary actions. A "Cancel" button is often less prominent than an "OK" button for example.
* **Input Fields**: Think about text fields, dropdowns, checkboxes, radio buttons, and sliders.
* **Navigation**: Design navigation bars, sidebars, and tab bars.
* **Headers and body text:** text elements that are correctly formatted for headers and paragraphs

#### Expand to complex components

Once you have the basic components, it's time to tackle more complex ones:

* **Cards**: Commonly used to display snippets of information in an organized manner (such as the products in an eCommerce store)
* **Modals**: Pop-ups that grab the user's attention (alerts, confirmations, etc)
* **Tables**: Vital for displaying data. Consider pagination, sorting, and filters.

#### Interactivity and states

Users will interact with your interface, so account for various states:

* **Hover**: How does an element look when a user [hovers over it](#user-content-fn-2)[^2]?
* **Active**: What happens when an element is clicked[^3]?
* **Disabled**: Ensure users can recognize non-clickable[^4] or non-interactable components (i.e. a disabled button or input form)

#### Save styles and style variables

When you are happy with your UI kit, you can save the styling on the different elements as [*styles*](#user-content-fn-5)[^5]. This saves their styling attributes in a style that you can name, and then apply to other elements of the same time. We recommend that you set up styles *after* you have finished your UI kit, so that you can review your overall design first.

[Font variables](#user-content-fn-6)[^6] and [color variables](#user-content-fn-7)[^7] can also help you keep track of your app's design.

#### Add more elements as needed

Whenever you need a new type of element, make a habit of adding it to your UI kit, so that you keep an updated "catalogue" of elements.

{% hint style="warning" %}
If you create your UI kit on a page, remember to remove that page before you deploy your app to live users (or they page will be accessible online). If you want to keep the UI kit without it being available to your end-users, you can place it inside of a reusable element instead.

Article: [Reusable elements](/help-guides/design/elements/web-app/reusable-elements)
{% endhint %}

### Building the page

When you are happy with your UI kit, you can start building your actual pages. Because you have invested some time to set up your initial colors, fonts and elements, you'll have a better feeling of how the app's overall look should be.

In the next section of the guide, we'll look into how you [Create and manage apps in Bubbl](/help-guides/getting-started/creating-and-managing-projects)e, before moving onto covering the [Bubble editor](/help-guides/getting-started/navigating-the-bubble-editor), its tabs, sections and tools and settings.

If you want to read more about how to use Bubble's design tools, you can continue directly to the [Design article series](/help-guides/design).

[^1]: *WYSIWYG* stands for "What You See Is What You Get."

    In the context of the Bubble editor, it means that the layout and elements you arrange and design in the editor will look and behave similarly when previewed or live. Essentially, it allows you to visually build and see the app as your users will experience it.

[^2]: This is set up using the *Element is hovered* conditional expression. You can save this to a Style to apply it to multiple elements.

    Article: [Conditions](/help-guides/logic/conditions)<br>

    Article: [Styles](/help-guides/design/variables-and-styles/styles)

[^3]: This is set up using the *Element is pressed* conditional expression. You can save this to a Style to apply it to multiple elements.

    Article: [Conditions](/help-guides/logic/conditions)<br>

    Article: [Styles](/help-guides/design/variables-and-styles/styles)

[^4]: This is set up using the *Element isn't clickable* conditional expression. You can save this to a Style to apply it to multiple elements.

    Article: [Conditions](/help-guides/logic/conditions)<br>

    Article: [Styles](/help-guides/design/variables-and-styles/styles)

[^5]: Bubble lets you set up overarching Styles for elements within your app to streamline the design process and promote consistency. By modifying a single style, you can effortlessly restyle all related elements, making it easy to set up and manage a cohesive look and feel in your app.\ <br>

    Article: [Styles](/help-guides/design/variables-and-styles/styles)

[^6]: The *font variables* feature in the Styles tab enables you to establish a collection of fonts that you can apply throughout your app.

    Making changes in a Font variable automatically applies those changes to every Style and element connected to that variable.

    Article: [Font variables](/help-guides/design/variables-and-styles/font-variables)

[^7]: The *color variables* feature in the Styles tab enables you to establish a palette of colors that you can apply throughout your app.<br>

    Making changes in a Color variable automatically applies those changes to every Style and element connected to that variable.

    Article: [Color variables](/help-guides/design/variables-and-styles/color-variables)


# eCommerce and payments

{% hint style="warning" %}
**Disclaimer:** Note that while Stripe is mentioned in this document as an example of a payment platform, this should not be interpreted as an endorsement or recommendation of Stripe's services. The information provided here does not constitute legal advice. As an app builder, it is your responsibility to select a payment platform that meets your specific needs. You are also responsible for ensuring compliance with the terms and conditions of the chosen payment platform.

We encourage you to conduct thorough research and, if necessary, consult with a legal professional to make an informed decision.
{% endhint %}

In this Article series, we delve into handling payments within your Bubble app. These articles are geared towards providing insights into the planning and deeper comprehension of payment systems, rather than delving into the technical aspects of payment setup. We’ll look into subjects like how your app will generate revenue in the first place, popular payment models, and strategies for implementing those models effectively

This article series uses Stripe in all examples, as it is the official plugin with the most extensive range of features accessible. Stripe also comes with detailed and developer-friendly documentation, and you will find links to relevant articles at the bottom of each article.

## How will your app generate revenue?

Deciding how to implement payments is not simply a matter of answering yes or no. All commercial apps generate revenue somehow, and many do so through payments. While the method can vary greatly, they all have one thing in common: they need users who are willing to pay for the services or products your app provides – and they need a sufficient number of such users to sustain the business.

Effective payment integration allows you to monetize your products or services directly. By providing products, services or compelling content behind paywalls, you can incentivize users to become paying customers, and unlock a steady stream of revenue. This is all straightforward – any for-profit enterprise revolves around the fundamental premise of generating more revenue than expenses. However, as we'll explore in this section, there are many ways to achieve this objective.

Think about it – what exactly are your customers paying for? There are different models for how to accept payments, but first, let’s try to answer that question. Put yourself in the chair of your user. Are they paying for:

* features that your app gives them?
* products or services, sold by your company, through your app?
* products or services, sold by other people, through your app?
* membership in a community?
* products or services offered by another app or website, through affiliate marketing?
* appointment for classes, bootcamps, coaching or other services?
* virtual goods?
* charity donations?

Some apps even generate revenue without direct payment from end-users, but instead move the transaction to a third party. For example, companies like Google and Meta create a major part of their revenue from marketing tools directing tailored messages to the users of the app. The users get free access, while Google/Meta get paid instead by businesses. Some companies outsource their revenue generation to a platform outside of their app, such as accepting donations and membership from service providers such as BuyMeACoffe and Patreon.

By knowing exactly how you will monetize your app, it will not only be easier to pick a payment provider and needed features, but you will also be prepared to design your app in such a way as to cater for these payments. For example, as an extension of the actual payment technology, you can start thinking about relevant details like a shopping cart feature, subscription plans (such as Freemium, Basic and Pro), delivery charges, third-party payments and taxes.

When you have decided on if and how your app should generate revenue, we can start looking at different alternatives for how direct payments can be accepted.

## Planning payment features

First, let's look at what we mean we talk about different *payment models*. Below, we're listing the three different models we'll be covering in this series, along with a wide range of examples to clarify what kinds of apps we mean:

### One-time purchases

The most basic kind of payment is a simple one-time purchase. This doesn't mean that each customer can only ever pay once, but that each payment is handled as an isolated event.

Examples include:

<table><thead><tr><th width="245">Purchase Type</th><th>Examples</th></tr></thead><tbody><tr><td>Products</td><td>Amazon, Etsy, Best Buy</td></tr><tr><td>E-books</td><td>Kindle Store, Barnes &#x26; Noble Nook</td></tr><tr><td>Pay-per-view events</td><td>UFC Pay-Per-View, WWE Network</td></tr><tr><td>Online courses</td><td>Coursera, Udemy (individual courses), MasterClass (single classes)</td></tr><tr><td>Mobile App Purchases</td><td>Premium app upgrades in App Store or Google Play Store</td></tr><tr><td>Digital Movie Purchases</td><td>Google Play Movies, Vudu</td></tr><tr><td>Donations</td><td>Buymeacoffee</td></tr></tbody></table>

### Subscriptions

Subscription payments are repeated on a given frequency, such as monthly or yearly.

Examples include:

<table><thead><tr><th width="320">Purchase type</th><th>Examples</th></tr></thead><tbody><tr><td>Streaming services</td><td>Netflix, HBO, Hulu, Disney+</td></tr><tr><td>Music streaming</td><td>Spotify, Apple Music</td></tr><tr><td>Software as a Service (SaaS)</td><td>Adobe Creative Cloud, Microsoft 365</td></tr><tr><td>E-book/magazine subscriptions</td><td>Amazon Kindle Unlimited, Scribd</td></tr><tr><td>Online learning platforms</td><td>LinkedIn Learning, Skillshare</td></tr><tr><td>Fitness and wellness apps</td><td>Peloton, Calm</td></tr><tr><td>Cloud storage and backup services</td><td>Dropbox, Google Drive</td></tr><tr><td>Meal kit delivery services</td><td>Blue Apron, HelloFresh</td></tr><tr><td>Gaming subscription services</td><td>PlayStation Plus, Xbox Game Pass</td></tr><tr><td>News and media subscriptions</td><td>The New York Times, The Washington Post</td></tr></tbody></table>

### Marketplaces

Marketplace apps are apps that rely on two parties: a buyer and seller. They can sell products or services, but what they have in common is that a portion of the generated revenue is handed off to a seller, creator or service provider.

Examples include:

| Marketplace type       | Examples                |
| ---------------------- | ----------------------- |
| Online retail          | eBay, Etsy              |
| Ride-sharing services  | Uber, Lyft, Grab        |
| Food delivery          | DoorDash, Grubhub, Grab |
| Freelance services     | Upwork, Fiverr          |
| Accommodation rentals  | Airbnb, VRBO            |
| Peer-to-peer selling   | Craigslist              |
| Digital goods exchange | Steam, Envato Market    |
| Artisan and handmade   | Etsy, ArtFire           |
| Event ticketing        | StubHub, Ticketmaster   |
| Book resale            | AbeBooks, BookFinder    |

As the app examples show, these three models support a wide array of different apps. Many of them may seem very different at first glance, but they share commonalities in our they are structured and arrange for payment and payouts.

Take some time to consider how you plan for your app to accept payments. Throughout the article, we'll used methods that rely heavily on Stripe's services, so first, let's cover how to determine whether Stripe is the right choice for your app in the first place.

## Choosing a payment provider

### Why are payment providers needed?

A payment provider is a service or platform that facilitates monetary transactions between your app and its users. They are crucial for handling the complexities of online payments, ensuring both security and efficiency.

Let's explore some of the reasons why they are needed:

1. **Security**: They provide robust security measures, protecting sensitive financial information through encryption and compliance with standards like PCI DSS.
2. **User trust**: A reputable payment provider builds user confidence, assuring them that their financial transactions are secure.
3. **Global accessibility**: Many providers support multiple currencies and payment methods, which is essential for reaching a global audience.
4. **Technical support**: Payment providers offer comprehensive technical support, easing the burden on developers to manage payment processing intricacies.
5. **Compliance and regulations**: They navigate the legal landscape of online payments, staying compliant with regional and international laws, thus reducing legal risks for your app.
6. **Efficiency**: They streamline the payment process with features like recurring billing and instant payments, enhancing user experience.

Realistically, setting up your own system for accepting payments is not a feasible project; there's a reason even the biggest companies in the world rely on third parties to handle monetary transactions. That being said, it's worth noting that it's only the actual *transaction* that is handled by the payment providers. That is, the actual collection of payment card information and communication with the card companies and banks.

How much you charge, and how you structure things like cost calculations, shopping carts and user interface is still entirely up to you.

### How does an online transaction through a payment gateway work?

Let's also explore what an actual transaction looks like, using the payment provider Stripe as an example. A transaction in this context is the exchange of money between two parties: your app (and company) and a customer.

When a payment transaction is processed through a service like Stripe, several steps occur behind the scenes to ensure the transaction is secure, authorized, and successful. Here's what typically happens:

1. **Initiation of transaction**: When your app requests a payment from an end-user, your app sends a request to Stripe. This request, even if you are using a plugin, is sent as an encrypted [API call](#user-content-fn-1)[^1].
2. **Tokenization**: Stripe converts the sensitive payment details into a unique token. This process, known as tokenization, ensures that the actual card details are not exposed during the transaction process.
3. **Authorization request**: Stripe sends this token along with the transaction details to the relevant card network (e.g., Visa, MasterCard) and the issuing bank for authorization. This step confirms that the card is valid and has enough funds or credit available.
4. **Bank's response**: The issuing bank checks the card's validity, fund availability, and runs fraud detection algorithms. Based on these checks, the bank approves or declines the transaction and sends this response back to Stripe.
5. **Completion of transaction**: If approved, Stripe informs the app of the successful transaction. The bank then moves to transfer the funds, a process known as settlement, which may take a few days.
6. **Funds transfer**: Once settled, the funds are transferred from the customer's bank account to the merchant's Stripe account, and then to the merchant's bank account based on their payout settings.
7. **Transaction record**: Stripe maintains a record of the transaction for future reference.

Throughout this process, security and compliance with payment industry standards are maintained to protect against fraud and ensure a secure transaction environment.

### Geographical availability

Before you can choose a payment provider, you need to ensure that the provider is available in the region where you want to accept payments. Even if a payment gateway is marketed as global, doesn't necessarily mean that it's available in every country and with any currency.

It makes sense to establish which providers are available to you early in the process. This way you can get to know the relevant payment provider's features and see how that affects your app. For example, Stripe offers a subscription feature and has a dedicated Bubble plugin, while a more regional provider may not offer the same convenient features.

### Features

Payment providers like Paypal and Stripe each have suites of features that they offer in addition to simply handling payments. These features, that include things like subscriptions, automated marketplace seller payouts and invoices may or may not be relevant to your app.

Keep in mind as you shop around for a payment provider that just because one provider offers a specific feature, it doesn't mean that all of them do. After having established what type of payments you want your app to facilitate, you can use these requirements to see which providers offer those capabilities.

### Regulations

Before selecting a payment provider, it's important to understand the regulatory landscape governing financial transactions in your target market. Various regulations, such as [Strong Customer Authentication (SCA)](#user-content-fn-2)[^2] in Europe, impose requirements on how payments are processed and authenticated (Stripe Checkout handles SCA automatically in the majority of cases).

Similar to geographical constraints, not all payment providers may support compliance with these regulations or operate in every jurisdiction where your app intends to accept payments. Therefore, you should research and verify whether a payment provider supports the regulatory requirements of your target regions.

### Ease and cost of implementing

In the majority of cases, there are a few different ways to implement a payment gateway:

#### Official plugins

The easiest and quickest way to get a payment solution set up in Bubble is to use plugins. Bubble has multiple official plugins for major providers like Stripe, PayPal, and Braintree. The official plugins typically have the most widely used payment features built in, but lesser used features or brand new features may not have been implemented. If you decide to use a plugin, remember to check which features it gives access to.

**Community plugins**

The Bubble community also has a wide range of different payment plugins. Some of these connect to payment gateways that are not supported by official plugins, while others connect to the same gateway but offers a collection of additional features. Keep in mind that community-made plugins depend on the developer for support and maintenance.

#### The API Connector

Bubble's API Connector plugin is a robust tool for connecting to external APIs, including payment gateways. If you want to connect to a provider not listed in the plugin store, or you want to access features not supported by a plugin, the API Connector offers a secure way to set this up. This requires a bit more know-how than using a plugin, but offers more flexibility.

### Offering multiple providers

Some apps have multiple payment providers, to offer their users the freedom to choose their preferred or available provider. For example, implementing both traditional card payments and PayPal may have a positive effect on the signup-rate.

While this approach can make sense, keep in mind the following:

* For each provider you choose to implement, you add to your app's development and maintenance time
  * This can also branch out into areas of your business that you may not have planned for, such as increasing the accounting work and complicating compliance
* If you rely on specific features offered by a payment gateway, such as Stripe subscriptions, it can be tricky or impossible to implement the same feature set to users that pay with a different gateway.
  * For example, combining subscriptions managed in Stripe with another payment gateway can require workarounds
* Different gateways can have different cost structures – remember to take this into account when you calculate prices and costs
  * For example, payment providers may have different transaction fees. Some take a percentage, others a set amount, and many take a percentage as well as an amount.

This is not meant to discourage a decision to implement more than one gateway, but to help you make an informed decision. You may also be interested in checking out solutions that already offer multiple ways to pay, such as Braintree[^3].

## Shopping carts

Many eCommerce solutions have a shopping cart, that allows customers to add items to a cart, and check out all items at once. This is not required, but an additional feature that you can consider. In the next article in this series, we'll look at some of the planning that goes into building a shopping cart features:

Article: [Shopping cart](/help-guides/getting-started/building-your-first-app/ecommerce-and-payments/shopping-cart)

## FAQ: Payments

#### Is a payment token an encrypted version of the card details?

No, it's not accurate to say that a token is an encrypted version of the card details. Tokenization and encryption are different processes. A token is a unique identifier that represents the card details but does not contain or reveal the original data. It's more like a reference code that maps back to the sensitive data through a secure system. Unlike encrypted data, which can be decrypted back to its original form, a token alone cannot be reverse-engineered to reveal the original card details. This makes tokenization a secure method for handling sensitive information in transactions.

#### What is the difference between a payment provider like Stripe, and a card issuer like Visa or Mastercard?

Payment providers and card issuers serve different roles in the payment process:

*Payment provider (Stripe):*

* Role: Stripe is a *payment processor* or *gateway*. It acts as a mediator between merchants (like your app) and the financial institutions involved in a payment transaction.
* Functionality: Stripe handles the technical aspects of processing online payments, including security (like tokenization), authorization requests, and facilitating the transfer of funds.
* Services: Apart from processing payments, Stripe offers a range of financial services including handling subscriptions, storing customer payment information securely, and managing different payment methods.

*Card issuer (Visa or Mastercard):*

* Role: Visa and Mastercard are card networks and issuers. They provide the infrastructure that allows their credit and debit cards to be used for payments worldwide.
* Functionality: They set the terms and conditions for their cards, process transactions made with their cards across their networks, and provide the link between cardholders' banks and merchants' banks.
* Responsibilities: These companies are responsible for authorizing and settling credit and debit card transactions, setting interchange fees, and ensuring security and fraud prevention measures on their network.

#### Who pays the transaction fee? Me or my app's end-users?

Most app owners view transaction fees as a necessary cost of doing business, and typically, users are not aware of the fees retained by the payment gateway. Generally, payment providers calculate these fees based on the total transaction amount. In marketplace apps, it's not unusual for sellers registered on the platform to bear the transaction fees, but this is not a requirement from the payment gateway.

Regardless of the policy adopted, the payment gateway usually will not display these fees to the end-user during the transaction process. Instead, the fees are often detailed in the merchant's agreement with the payment provider and reflected in the backend accounting.

Whichever policy you choose, it's important to understand and account for these fees in your app's pricing structure and revenue model.

#### What is PCI DSS?

As a reminder, the Bubble user manual is **not** a source of legal advice, and you should always consult a legal professional for up-to-date and correct information for your particular project.

The Payment Card Industry Data Security Standard (PCI DSS) is a set of security standards designed to ensure that all companies that accept, process, store, or transmit credit card information maintain a secure environment. Established by major credit card companies, PCI DSS aims to protect cardholder data from fraud and security breaches.

This standard comprises a comprehensive set of requirements for enhancing payment account data security. These requirements include measures for network architecture, software design, and other protective processes. Organizations are required to adhere to these standards to safeguard sensitive cardholder information.

PCI DSS is not a single-time compliance effort but an ongoing process. It involves regular monitoring and testing of systems to ensure they meet security requirements. Compliance with PCI DSS is mandatory for all entities dealing with credit card transactions to minimize the risk of data breaches and enhance consumer trust in payment systems. Stripe offers a more in-depth guide about PCI DSS that you can find below.

External page: [A guide to PCI compliance | Stripe Docs](https://stripe.com/en-no/guides/pci-compliance)

#### How do I make my app PCI DSS compliant?

As a reminder, the Bubble user manual is **not** a source of legal advice, and you should always consult a legal professional for up-to-date and correct information for your particular project.

In short, Bubble is not designed for PCI DSS compliance, and as such, you should never process or store cardholder data like card number, expiry date and security in your Bubble app. One of the major reasons for using a payment gateway in the first place, is to outsource the compliance requirements to a third party that specializes in handling cardholder data securely. By using a solution like Stripe Checkout, users are redirected to a secure payment page hosted by Stripe, meaning that your app is not involved in processing the cardholder data in any way.

The PCI DSS (Payment Card Industry Data Security Standard) standard pertains exclusively to entities involved in **storing, processing, or transmitting cardholder data within the payment card industry**. Payment gateways handle the secure processing of payment information, which can substantially alleviate the compliance burden.

Your app may still have PCI DSS compliance obligations, depending on how it interacts with payment card information. If your application transmits or processes cardholder data in any form, it must adhere to PCI DSS requirements. The level of compliance required varies based on the volume and manner of transactions your app handles. Please consult a legal professional if you are in doubt.

For more comprehensive information on PCI DSS compliance, we recommend consulting Stripe's detailed guide. However, remember that this guide should be used in conjunction with the official PCI DSS guidelines. Depending on your specific scenario, you may also want to seek professional advice to ensure complete compliance.

External page: [A guide to PCI compliance | Stripe Docs](https://stripe.com/en-no/guides/pci-compliance)

## Other ways to learn

<details>

<summary>Articles (APIs)</summary>

* [The Bubble API](/help-guides/integrations/api/the-bubble-api) - how to send up API workflows that can receive webhooks from Stripe
* [The API Connector](/help-guides/integrations/api/the-api-connector) - to set up additional Stripe API calls not supported by the official Stripe plugin, you can use the API Connector

</details>

<details>

<summary>Article (database structure)</summary>

* [Database structure guides](/help-guides/data/the-database/database-structure-by-app-type) (article series that offers a lot of tips on exactly how to structure a particular app type, such as an eCommerce store)
  * [eCommerce apps](/help-guides/data/the-database/database-structure-by-app-type/online-store-ecommerce-apps) (direct link to eCommerce app database structure article)

</details>

<details>

<summary>Core reference</summary>

[The Stripe plugin](/core-resources/bubble-made-plugins/stripe)

</details>

<details>

<summary>External documentation</summary>

[Stripe docs](https://stripe.com/docs)

* [Stripe Checkout](https://stripe.com/docs/payments/checkout) (Stripe's one-time payment feature)
* [Stripe Billing](https://stripe.com/docs/billing/subscriptions/overview) (Stripe's subscription feature)
* [Stripe Connect](https://stripe.com/docs/connect) (Stripe's marketplace feature)
* [Invoices](https://docs.stripe.com/api/invoices) (How invoices work in Stripe)<br>
* [Webhooks](https://stripe.com/docs/webhooks) (sending data back to your app from Stripe upon an event, such as a completed transaction)<br>
* [A guide to PCI compliance | Stripe Docs](https://stripe.com/en-no/guides/pci-compliance) (comprehensive guide on PCI DSS)

</details>

[^1]: An *API call* is a request sent to access/modify data or initiate a specific action in a server. It's like asking a question or making a request between two apps/systems in a specific language, which the API understands and responds to.

    In this context, an API call is made from your app to the Stripe server.

    Article series: [APIs](/help-guides/integrations/api)

[^2]: *SCA (Strong Customer Authentication)* regulation is a requirement within the European Union aimed at enhancing the security of online payments and reducing fraud.

    It mandates that customers authenticate themselves using two or more factors when making electronic transactions.\
    \
    External page: [SCA | Stripe Docs](https://stripe.com/en-no/guides/strong-customer-authentication)

[^3]: Braintree, owned by PayPal, offers traditional card payments as well as additional solutions such as PayPal, Venmo, Google Pay and Apple Pay.

    External page: [Braintree](https://www.braintreepayments.com/)


# Shopping cart

Many apps that accept payments will use the metaphorical approach of a *shopping cart*. As that phrase suggests, this allows your users to add multiple items to a cart, and then check out all of the items at once in one single transaction that includes both the items and any related costs such as shipping.

{% hint style="info" %}
This article explores the use of a shopping cart feature in your app, focusing primarily on planning considerations rather than providing a step-by-step implementation guide. If you're seeking a practical example illustrating how an eCommerce shopping cart can be structured, we recommend exploring our database structure guide.

Article series: [Database structures by app type](/help-guides/data/the-database/database-structure-by-app-type) | [Online store](/help-guides/data/the-database/database-structure-by-app-type/online-store-ecommerce-apps)
{% endhint %}

## Defining a shopping cart

Shopping carts, like many app features, are easier to implement if you decide and plan ahead. The reason for this is that your database, pages and workflows will be structured in a different way if you decide to add a shopping cart feature, as opposed to only allowing the purchase of one item at a time.

The shopping cart is already a well-established and useful metaphor, so we'll stick to that. Let's look at what exactly it is from a structural perspective:

<figure><img src="/files/JQWPfA6OyWygjtyRQD1J" alt="" width="375"><figcaption><p>A shopping cart is simply a metaphorical container for all the items a user wants to check out at the same time.</p></figcaption></figure>

That's pretty simple! But even with a simple thing like this, it's useful to give some quick thought as to what exactly it is we are talking about. An online shopping cart is very much akin to the real-life experience of adding items to a cart, almost to the point of no longer being a metaphor:

1. A **customer browses the store**, often looking at products sorted by different categories
2. They **add items** to a shopping cart, and that shopping cart moves along with them to the checkout
3. They **pay for all items** at once
4. The next time they show up, an empty shopping cart is waiting for them

Let's use this scenario, which anyone in the world can recognize, and draw out a few facts that help us think about how the value an actual shopping cart adds in real life:

1. The shopping cart is a **convenience**, making the shopping experience easier. It **doesn't hold any value in itself** – users are paying for the items within, not the cart itself.
2. It helps the user **maintain a list of products**, without having to remember them or write them down
3. Everyone agrees that the products are **reserved** for that customer as long as it is in their cart. This is sometimes true in the online world as well.
4. It **frees up time for the employees** in the store – they don't have to fetch and reserve the items for the customer.

An online cart is simply a digital version of that. It helps your users collect items and bring them to checkout, sometimes reserving the product(s) within in the process.

As a shop owner, you are using automation to allow customers to handle their order. You don't need to take orders by email, confirm the inventory, add the items to an invoice or anything else: in essence, you are letting the user handle most of the data entry.

<figure><img src="/files/jxVSQCqLb4KS3PyzggrC" alt=""><figcaption><p>Adding a shopping cart to your app mirrors its physical brick-and-mortar counterpart closely, almost to the point where it stops being a metaphor.</p></figcaption></figure>

For the customer, an additional upside of the digital cart, is that it offers an organized table of information that aids them in the purchase decision:

* The **list of items**
* The **total number of items**
* The **total sum** to pay
* The **total** **shipping cost**

Of course, depending on the nature of your store, more or less information can be made available. The point is that the shopping cart is not only a container, but a good way to organize and aggregate information for both the customer and the seller.

Now, that we have agreed what a shopping cart is, and the value it brings to the buyer and seller, we can look at it from a more technical and structural perspective.

## Deciding whether to implement a shopping cart

A shopping cart feature can have many benefits, but it will also add to the development time and complexity of your app.

Here are some key considerations when deciding whether to implement a shopping cart feature:

**Complexity of products/services**: Consider the complexity of the products or services your app offers. If users typically purchase multiple items or services in a single transaction, a shopping cart can streamline the checkout process by allowing them to add items as they browse and complete the purchase all at once. On the other hand, if your app primarily offers simple, single-item purchases, a shopping cart may be unnecessary and a direct checkout process could suffice.

**User expectations**: Think about user expectations and industry standards. Many users are accustomed to shopping cart functionality from e-commerce websites and may expect a similar experience in your app if they are purchasing multiple items. Providing a familiar and convenient shopping experience can enhance user satisfaction and encourage repeat purchases.

**Order size and frequency**: Evaluate the typical order size and frequency of purchases in your app. If users tend to make large or frequent purchases, a shopping cart can help them organize and manage their orders more efficiently. However, if purchases are usually small or infrequent, a direct checkout process without a shopping cart may be more suitable.

**Cost for users**: Sometimes, a shopping cart feature can make the final cost lower for the user, and may be considered more fair from the user's point of view. For example, shipping two or more items together may be more cost-efficient than shipping them separately. If the lack of a shopping cart negatively affects users in this way, a shopping cart feature may be a welcome feature.

**Cross-selling opportunities**: Consider whether your app could benefit from cross-selling or upselling opportunities. A shopping cart allows you to suggest related items or offer promotions based on the contents of the user's cart, which can help increase sales and revenue. If cross-selling is an important aspect of your business model, implementing a shopping cart feature may be advantageous.

**Checkout flow**: Evaluate the checkout flow and user journey in your app. A shopping cart can provide a clear and structured path for users to review their selections, enter payment and shipping information, and confirm their order. If your app's checkout process involves multiple steps or requires users to provide additional information beyond just selecting items, a shopping cart can help simplify and streamline the process.

**Communication:** if a registered user adds one or more items to a cart, they have signalled an interest in those items, that you can use to increase sales. For example, you can send a user a follow-up email, reminding them that they have items in a cart, suggesting that they finalize the purchase.

## Planning a shopping cart

Our metaphorical cart is a container for items, and the digital version is no different. From a database perspective, this means we need to think about how to store the items in the cart, and what their relationship should be. We highlight some of the questions that are worth asking as you start planning.

### The cart, products and items

In the physical world, the products in the shelves don't change as you put them into the cart. But if you think about it – their *state* changes. They're no longer on the shelf, they are part of a joint transaction that will be documented on a receipt, and they are of course coming with the customer to their home.

In the same way, the items in a customer's digital shopping cart may *look* like the same product as is displayed on the shelf, but in many cases it isn't. Their state has changed as well, and from a database perspective, this is worth pausing and thinking about.

Let's define three things before exploring further:

* The **product** is the thing that you show your users in a list or on a page: the product on the shelf
  * The **item** is the thing that the user actually collects and pays for: the item in the cart
* The **cart** is the container that holds the items.

How are *products* and *items* different? In some cases, they aren't, but as we talked about *states* in the real world, these sometimes have to be taken into consideration when working digitally. The distinction is not necessarily visible to your customers, and is another example that the structure of your database does not always have to match what the user sees. The idea of using *products* and *items* as separate entities is to be able to manage each one in isolation – if that sounds cryptic, keep reading: we'll use some examples to illustrate:

#### Changing products

A product or service may change during its life cycle. For example, a beanie may cost $15 during the winter season, and then the price is dropped to $10 during the summer season. Likewise, a property like color, size, name or description could be updated.

From a database standpoint, let's consider a scenario where we have two main data types:

* Carts
* Products.

Products are viewed, added to carts, and then they are sold.

However, this setup can pose a challenge regarding historical records. If a product in a cart is the same database thing as the one you display in the store, any changes made to the product will retroactively update all previously purchased instances of that product. Consequently, when viewing their order history, users may be surprised to find items and prices they no longer recognize, and the total sum on the cart may no longer reflect what they actually paid.

#### Deleting products

Expanding on this line of thinking, there are scenarios where a product, after being added to the cart and subsequently purchased, might be deleted from your app's database. If the original, viewable product (the product in the store's shelves) were directly linked to the cart data type, this could result in the product disappearing from previously checked-out carts, since the database thing no longer exists. This not only has the potential to confuse customers who access their order history but could also distort purchase statistics.

### Maintaining historical records

To mitigate these issues, we can set up a database structure that allows us to store a historical record of a product *at its time of purchase.* There are different ways to do this, and our list of suggestions is surely not exhaustive, but can introduce you to some ideas on how to find the right balance between engineering effort and maintaining records for historical and statistical purposes.

**Storing items as a separate data type**

You can create a separate data type, which represents the product *as it was* when it was added to the cart. This means you copy all relevant information from the product (such as the name, price and description) over to the new thing when it is created. You have the option to create this record either upon addition to the cart or upon checkout.

* Opting for the latter can be advantageous as it prevents the addition of redundant records that may remain in the database if the user abandons the cart, as well as making sure the information is up to date at the time of purchase.
* This method also offers flexibility in managing future updates to the cart. For instance, if an item needs to be returned and refunded later on, having each item in the cart stored as separate database entities enables you to utilize these records to uphold the complete history of each item.
* Connecting this data type to the actual product can be advantageous (such as *item's product*). Doing so allows you to track statistical metrics isolated to a single product, such as the number of items sold, total revenue generated, and the number of products returned.

**Storing a receipt**

You can also store additional information directly on the cart, such as a textual representation of the items sold. This is akin to the printed receipt in the physical world, providing a static record of the purchase.

* This way, you are storing less data in the database, and spending less WU, at the cost of some flexibility.
* You can set up the receipt to store as much information as needed, and it will remain static regardless of subsequent changes to products, shipping costs or other variables that might change over time.

**Storing products in Stripe**

{% hint style="info" %}
The use of the product object in Stripe's API is not natively supported within the standard Stripe plugin. To use this feature, you must either set up a connection through the [API Connector](#user-content-fn-1)[^1], or find a community plugin that supports this.
{% endhint %}

Stripe supports adding products. Products can be created in the Stripe dashboard, or be created and updated through the Stripe API. There are pros and cons of using this approach:

* Stripe automatically maintains historical records. For example, if a product is purchased, and subsequently modified or deleted, the historical record in Stripe's database remains unchanged. That way, the records kept at the time of purchase are immutable, and changes will only affect future transactions.
* You can not only sell, but also fetch arrays of products with their details through the API to show them in your app.
* Stripe provides default fields such as name, description, package dimensions, and a shipping boolean to indicate if the product is a physical good. However, its capability for further customization beyond these built-in fields is limited.
  * The price is stored as a separate object
* By using Stripe, you become dependent on a third-party service, which can lead to a degree of lock-in with their system. Should you wish to incorporate additional payment providers or switch to a different payment gateway, significant modifications to your existing setup may be required.
* If you have a very large number of products, it might become challenging to efficiently manage and navigate through them within Stripe's dashboard or via API calls. Stripe may also encounter [rate limits](#user-content-fn-2)[^2] if you continually perform API requsts.

As the list shows, Stripe can be a good choice for securely storing accurate product sales data, but comes with some limitations. If you want to avoid lock-in, or if you offer a large portfolio of products, it can make good sense to maintain the flexibility that setting up a customized solution in Bubble offers.

### Shopping cart features

Deciding on what kind of features your shopping cart should offer is also worth planning before building. As we discussed in the *Planning features* section, features are not something that you need to implement all at once – you can do so in batches, and collect feedback from users as you go.

Let's look at some common features you can consider:

#### Add to cart

This feature seems blatantly obvious, but there are some considerations worth having a look at. The first is of course that you will need some way to add items to your cart in the first place. As we covered in the previous section, the method for doing this varies across different apps, so the actual structure and workflows needed will also differ.

Secondly, there may be sub-features to the *Add to cart* feature that may need some closer inspection:

* **Quantity:** some eCommerce stores choose to let the user select a quantity when they select a product. That is, the quantity of *one* product (as opposed to the total number of items). For example, a user may buy two identical beanies.
* **Attributes:** Products may have different attributes, such as size, color, and material.
* **Inventory**: if you plan to build an app that keeps track of an inventory, you may need to set this up in a way, so that a sold item is subtracted from that inventory. This can also be extended into reserving items for a period while they are in the cart. Additionally, implementing a feature to check the inventory status of a product before it is added to the cart can ensure real-time accuracy.
* **Booking**: if you are selling services such as consulting, coaching, or other types of live sessions, you need to make the Add to cart feature different than a typical eCommerce store. For example, instead of picking a product and color, the user may pick a service and then proceed to pick a date in a calendar. Perhaps you'd also want this system to automatically check your availability based on other booked sessions, or by connecting it to an external calendar through the API Connector or a plugin.
* **Customization**: depending on the product or service you are selling, you may also need more complex customization. For example, if you are a travel operator, the user may have the opportunity to pick a duration, lodging or other trip details.

The purpose of listing these points is of course not to imply that every app needs this extensive feature set, but to highlight that adding something to a cart may be as simple as clicking a button, or more complex. Whatever you need, Bubble can handle it.

#### Delete from cart

A common feature that's easy to forget, is the ability to delete one or more items from the cart, without having to delete the whole cart and start over.

#### Edit cart

The same goes for editing a cart. Again, depending on what you sell, editing can simply mean adjusting the quantity of one single line item, or allowing more extensive edits, such as changing a booking date or product/service attribute.

#### Shipping

If you are selling physical products, you may need to implement a feature to collect a user's address.

* **Text field:** the simplest way to get and store a user's address is by use of a simple text field. You can also use the *Address* field type, combined with the *Searchbox* element, to let the user search for an address from Google Maps. This way, the user can quickly add all relevant information without having to type it in.
* **Tracking**: if you want to implement a feature for tracking a shipment, you may need to connect to a courier that offers an API for this. Tracking usually works by registering an expected shipment with the courier using an API call, and getting a tracking number in return that you can show or send to the user.
* **Dynamic shipping cost**: there are different ways in which shipping cost can be dynamic. For example:
  * Shipping items together can be more efficient than shipping them individually
  * The weight, volume and distance of the shipment can affect the total cost
  * Shipping sensitive items, like art, may also be priced differently.\
    \
    Again, it can be useful to connect to a courier service and provide them with data that allows them to calculate and return the cost to you. If that is the case, you may need to store some data on the products, such as its weight and volume.

#### Accessing the shopping cart

As a user adds items to the cart, they will at some point want to see their order to complete the purchase. Keep in mind that this is one *step* that a user has to go through in the checkout process, so it makes sense to make it easy for the customer to understand how to navigate to it.

It has become a common practice to place an easy-to-see icon in the top-right corner of the app, as illustrated below:

<figure><img src="/files/9TKcyZrxJ37K1Rtb6RqZ" alt=""><figcaption></figcaption></figure>

This design practice has become so prevalent, that many users are expecting it there. Still, you can play around with additional features to encourage users to go to their cart to finalize the purchase, and increase your conversion rate:

* **Item counter:** some apps choose to have a counter next to the cart symbol to indicate that there are items in the cart. Not only is this an easy way to confirm that an item has been added, but it as the added upside of being a gentle way to remind users of their cart, should they abandon it and then return to the store at a later time.
* **Nudges:** you can also be creative with subtle nudges to encourage people to go to the cart, such as a slight animation of the cart symbol, a change in color when the cart is not emtpy, or even a popup message or floating group that asks if the user wants to proceed to the cart after adding an item.

### The shopping cart page

In many cases, the page that holds the shopping cart, is the same page as the *checkout* page, which we will explore in the next article in this series. We are using the word *page* to indicate an isolated section where the user can see the contents of their cart. You can also choose to set it up as a [single-page application](#user-content-fn-3)[^3], meaning that the shopping cart/checkout page is a group that you show and hide as needed.

In the next article in this series, we'll have a look at the checkout process.

Article: [Checkout page](/help-guides/getting-started/building-your-first-app/ecommerce-and-payments/checkout-page)

[^1]: The *API Connector* is a Bubble-made plugin that lets you set up outbound API calls to external apps and services.

    Article series: [The API Connector](/help-guides/integrations/api/the-api-connector)

[^2]: Rate limits refer to the restrictions placed on the number of API calls or the amount of data a single client can request within a specified timeframe. For instance, if an API imposes a rate limit of 500 calls per client per day, any requests beyond this threshold within the same day may be blocked or denied until the limit resets.\
    \
    Article series: [APIs](/help-guides/integrations/api)

[^3]: A single-page application is an app that relies on hiding and showing content on the same page, instead of loading a new page to show specific content. You can read more about this in the dedicated article below:\
    \
    Article series: [Single-page and multi-page apps | Navigation](/help-guides/logic/navigation)


# Checkout page

In this article, we'll look into what is often the final step before the user actually pays for one or more products – the checkout page.

## Defining the checkout page

The checkout page often gives your end-users a final look at their purchase before they commit to buying and go ahead with the payment transaction. Its purpose is to provide all the details needed for a user to make an informed purchase decision, and with the added benefit of building trust and confidence in your app, if set up right.

Below is an example of what a checkout page may look like. As an internet user, you will likely have seen many of these. While they can differ in design, many of them contain the basic elements needed for the purposes described above, and for the business to stay compliant with relevant requirements, such as the GDPR[^1] and CCPA[^2], as well as commerce and tax laws.

<figure><img src="/files/V8D7mM6wTEk78GfFtTNT" alt=""><figcaption><p>A checkout page should give customers a well-organized final look at what exactly they are purchasing, what they will be charged and why, and the method by which they will be paying. In some cases, other details are also relevant, such as the delivery address for physical products.</p></figcaption></figure>

Your user interface and user experience both play a very large part in generating revenue, ensuring customer satisfaction, and compliance with regulations[^3]. In this section, we’ll go over some of the most important things to keep in mind when you design your checkout experience.

## Planning a checkout page

### Transparency

Whenever any transaction is involved, transparency is important. What this means is that you should set up your app to clearly show what exactly the customers are purchasing and why they are paying a certain amount. From a regulatory perspective, including specific details about any transaction may also be required.

This is usually done by listing all the details involved in a transaction, from the user’s perspective. The different records in a purchase transaction are often divided into three categories:

<table><thead><tr><th width="165">Record</th><th width="323">Description</th><th>Visible to customers</th></tr></thead><tbody><tr><td>Line items</td><td>The actual items being purchased, such as a beanie and a book</td><td>Yes</td></tr><tr><td>Additional fees</td><td>Extra costs, such as shipping</td><td>Yes</td></tr><tr><td>Cost of sales</td><td>Payment gateway transaction fee, and operational costs</td><td>No</td></tr></tbody></table>

A good practice is to list the line items and any additional fees separately, while incorporating the cost of sales into the product cost.

For instance, it's relevant to show the price of each item in a cart along with the combined shipping cost, as this provides transparency to the customer. However, fees associated with the payment gateway transaction, are typically not relevant to the customer and are often absorbed into the product cost. Therefore, these costs are usually factored into the overall pricing of the product rather than being itemized separately.

Displaying your business details, including its legal name, address, phone number, email address, and government organizational ID, is not only a recommended practice but also a requirement in many regions.

### Ease of use

Making a purchase process easy to understand and quick can also help you generate more sales and satisfied customers. When customers find it easy to navigate through the checkout process, they are more likely to complete their purchases and return for future transactions.

* **Reducing steps:** reducing the number of steps the customer has to take in order to complete the purchase can increase the conversion rate. Some eCommerce sites, like Amazon.com, have even implemented a one-time checkout process that lets returning customers buy products with a single click, since Amazon already has the information needed to complete it.<br>
* **Responsive design:** making your app work well on mobile devices, as well as larger screens, increases the number of potential customers. Using Bubble’s responsive engine, you can make the same page work well on all screen sizes and devices. Keep in mind the differences between different devices:<br>
  * Font sizes that appear suitable on a laptop screen may appear too small on mobile devices.<br>
  * Buttons and links that are to small, or placed too closely together, can be challenging for mobile users to click.<br>
  * Ensure that elements do not overlap in certain scenarios. For instance, an element placed within a floating group that appears well-positioned on a laptop screen may overlap crucial elements on mobile screens.<br>
* **Editable cart:** if you are implementing a shopping cart feature that allows the users to add more than one item, they may find it useful to be able to make changes to the cart, such as deleting an item.

### Cross-selling and upselling

Cross-selling and upselling are both sales techniques used to increase the value of a purchase, and can be of value to the user as well.

* **Cross-selling:** Cross-selling means recommending related or complementary products to the one the customer is already considering or has already purchased. The aim is to encourage customers to add additional items to their cart that enhance or complement their original purchase. For example, suggesting batteries when a customer buys a flashlight or offering a phone case when purchasing a new smartphone. As these examples show, cross-selling can also be useful to customers if you display the right products or services.<br>
* **Upselling:** Upselling involves persuading the customer to purchase a higher-end or more expensive version of the product/service they are interested in or have already chosen. For example, encouraging customers to upgrade to a larger size, premium model, or extended warranty when buying a product.

### Post-purchase information

* **Purchase confirmation:** it’s always a good idea to let the user know the status of the transaction. If it is successful, it’s common to redirect the user to a thank you page or section. Thank you pages are also sometimes used to track sales analytically in platforms like Google Analytics.<br>
* **Email confirmation:** many users will also be looking for an email that confirms the purchase and its details. You can use our dedicated Sendgrid plugin to send customized emails. Emails that are used for this purpose are generally known as transactional emails.<br>
* **Tracking links:** if you are selling physical products, users may find it useful to be able to track the shipment. Many courier companies have an API that allows you to set up a shipment and get a tracking number in return. You can use the API Connector to set this up.

***

Keep in mind that all of the above suggestions are just that – suggestions. Your final checkout page is yours to design, and this is meant just to inspire. However, keep in mind that keeping your users informed, the process transparent and fast, and the design working on all relevant devices are all good basic tips for setting up. Also, make sure that your checkout page is compliant with any rules and regions that pertain to your sector, type of products/services and geographical region.

## Transactional details

{% hint style="danger" %}
It's important to emphasize that when we discuss transactional details, we are not referring to cardholder information, such as the card number, expiration date, and security code. These particulars are typically managed by the payment gateway and should not be stored in your app's database.
{% endhint %}

Whenever a customer pays for something, like the items in a shopping cart, a transaction happens. If you're using an online payment gateway such as Stripe, this is an automated process that arranges for money to be moved from the customers bank account to your company's account.

Whenever such a transaction takes place, the payment gateway stores details about it, such as the date, time, customer info and amount. This record in their database is given a unique ID that helps you identify it later if needed. For example, a customer might ask for a refund, at which point you'll need to refer to the original payment.

It's highly recommended to store some details from the transaction in your Bubble database when a transaction is complete.

Structurally, there are some questions worth asking:

* Will purchases always be for **one item**, or for a **collection of items** (shopping cart)
  * This could affect where it makes the most sense to store the transactional details
  * If your app has a shopping cart feature, it makes sense to save the transaction ID on a data type that represents the full purchase (as opposed to each individual item), such as the data type that represents the shopping cart
* Which **details** do I want to store?
  * Storing the transaction ID normally allows you to get other details such as date, time, and amount from the payment gateway
  * You can still opt to save other details. For example, you might need them to create and maintain statistical data.

The transaction ID is normally returned right after the transaction is finalized. The official Stripe plugin automates this process by giving you access to the ID in the same workflow that initiated the payment. We cover this in the next article in this series:

Article: [One-time payments](/help-guides/getting-started/building-your-first-app/ecommerce-and-payments/one-time-payments)

[^1]: GDPR, or the General Data Protection Regulation, is a comprehensive data protection law enacted by the European Union (EU) to safeguard the privacy and personal data of individuals within the EU and the European Economic Area (EEA).\
    \
    Article series: [Compliance](/help-guides/optimizing-an-application/compliance)

[^2]: The California Consumer Privacy Act (CCPA) is a state law in California designed to enhance privacy rights and consumer protection for residents of California, United States.

    It grants consumers more control over the personal information that businesses collect about them and imposes obligations on businesses regarding the collection, use, and disclosure of personal information.

[^3]: In many regions worldwide, online commerce is subject to regulation aimed at safeguarding consumer rights, ensuring transparency, facilitating returns, addressing complaints, and ensuring compliance with taxes and other fees.

    If you're considering venturing into online sales, it's highly advisable to seek legal guidance to ensure compliance with regional and sector-specific regulations.


# One-time payments

{% hint style="warning" %}
**Disclaimer:** Note that while Stripe is mentioned in this document as an example of a payment platform, this should not be interpreted as an endorsement or recommendation of Stripe's services. The information provided here does not constitute legal advice. As an app builder, it is your responsibility to select a payment platform that meets your specific needs. You are also responsible for ensuring compliance with the terms and conditions of the chosen payment platform.

We encourage you to conduct thorough research and, if necessary, consult with a legal professional to make an informed decision.
{% endhint %}

{% hint style="info" %}
This article outlines how you can use Stripe to accept one-time payments in your Bubble app. If you are looking for information regarding using Stripe to receive payment for the Bubble template and plugin marketplace, see [this article](/account-and-marketplace/account-and-billing/selling-on-the-marketplace).
{% endhint %}

## Defining one-time payments

One-time payments are payments that are made in isolation between two parties, as opposed to [recurring payments (subscription)](#user-content-fn-1)[^1] or [marketplace payment](#user-content-fn-2)[^2] (where a third party is part of the transaction).

Imagine a user (customer) adding a product to a cart, and then completing a payment transaction to pay for it (such as buying and paying for a t-shirt) – this is what we define as a one-time payment. In this article, we'll look into how you can plan for the development of this time of payment solution.

## Planning one-time payments

One early point to talk about is the understanding that most purchases, even if it contains multiple cost records, is still completed in one transaction. Let's unwrap what we mean by that: imagine that you have an app where you sell home-made beanies:

<figure><img src="/files/zP5pGpSFDZJ8f7CvuuOk" alt="" width="188"><figcaption></figcaption></figure>

Now, of course you will want your customers to pay for the product when they buy it. But wait – there may be other costs associated with this:

* Shipping
* Multiple products
* VAT/Sales tax
* Import tax

Before we explore these common cost details, we'll look into the point we just made: even if there are multiple cost items, such as shipping, VAT/taxes and import tax, your users will not pay three times; they'll usually still pay only once.

This may sound obvious, but the underlying point is that you'll need to structure and design your app to take these costs into account, and then prepare that total cost for the payment gateway. We'll cover the costs one-by-one.

We are using Stripe in this example, as it offers features that can handle parts of the collection of different cost items.

<table><thead><tr><th width="211">Cost</th><th>Possible handling</th></tr></thead><tbody><tr><td><a href="#product-cost">Product</a></td><td>The app sets the cost and sends it to the payment platform.</td></tr><tr><td><a href="#multiple-products">Multiple products</a></td><td>The app calculates the total cost and sends it to the payment platform</td></tr><tr><td><a href="#shipping-costs">Shipping cost</a></td><td>The app calculates the total cost and sends it to the payment platform</td></tr><tr><td><a href="#vat-sales-tax-gst-compliance">VAT/Sales tax</a></td><td>Stripe offers tools to help businesses handle <a data-footnote-ref href="#user-content-fn-3">VAT (Value Added Tax)</a> and sales tax.</td></tr><tr><td><a href="#import-tax">Import tax</a></td><td>The app calculates imports tax and adds it to the total amount.</td></tr></tbody></table>

### Product cost

The product cost is set/calculated by your app. In other words, Stripe will not have any say in the cost of the product and it's your responsibility to send the correct value through the plugin/API.

### Multiple products

Many apps give their users the opportunity to buy more than one product at once. When you plan your app, there are two ways to set this up:

* **Selecting an item quantity**: you can include a number in the final checkout that allows users to specify the number of beanies they want to buy
* **Offering a shopping cart**: Alternatively, you can offer a shopping cart that users can add items to. This allows greater freedom, such as buying different products.

However you choose to design your app, this kind of setup does not affect Stripe's handling of the total amount: you will still be sending one total sum, regardless of the number of items.

### Shipping costs

If you are selling physical products, it's common to ask the users to cover the shipping costs. This can be a simple, set cost, or a dynamic cost based on the number of products, weight, destination address and other details that are relevant.

Again, this is a data point that your app will handle. Stripe does not calculate shipping cost, or consider it separate from the total cost. In other words, whatever your users pay for shipping (if anything), you will only send the total amount to Stripe.

### VAT/Sales tax (GST compliance)

In many countries, products and services are subject to VAT (Value Added Tax) or sales tax, and this tax is typically calculated based on the location of the user or customer rather than the seller's location. VAT rates can differ based on various factors, including the type of product or service, whether the purchaser is a business or an individual, and the specific geographical region.

Collecting VAT and sales tax, especially if you sell to a global audience, can be somewhat complex to understand and calculate correctly, and some payment gateways (like Stripe) offer tools to simplify this process. These tools are designed to automatically calculate and collect the appropriate amount of tax on transactions, based on the location of the business and its customers.

You can read more about the tools that Stripe offers on the page below:

External page: [Sales tax, VAT, and GST compliance | Stripe Docs](https://stripe.com/docs/tax)

### Import tax

Many countries impose an import tax on products brought into their borders. The specific rates and regulations governing this tax differ significantly from one country to another. While apps and online retailers are normally not legally required to collect import tax, some opt to do so as a value-added service. By including import taxes in the total cost at the point of sale, these businesses provide customers with transparent pricing, helping them understand the full cost upfront. This approach can enhance customer experience by preventing unexpected charges upon product delivery.

As highlighted above, the total cost of any online purchase often involves a combination of various charges. These can include the base price of the product or service, applicable VAT or sales tax based on the customer's location, potentially import taxes for international purchases, and sometimes additional fees like shipping or handling.

### Common pitfalls with one-time payments

Planning your payment and total cost structure is an important part of creating a viable business. Unrealistic calculations or overlooking associated costs can distort your perception of revenue potential and create a false sense of confidence. By thoroughly considering all expenses and factors involved in sales, you can gain a more accurate understanding of your business's financial health and make informed decisions to optimize profitability.

In this section, we’ll go over some common pitfalls in sales planning.

#### Lacking cost transparency

Failing to provide clear and comprehensive cost breakdowns to users during the checkout process can lead to confusion and dissatisfaction. Ensure that users have full visibility into all applicable costs, including product prices, shipping fees, taxes, and any additional charges, to avoid surprises and build trust.

#### Getting the price wrong

Setting a low price can help attract customers – and there’s nothing wrong with that strategy. Still, the amount you are giving away may need to be made somewhere else, or you may end up with less revenue or even a loss on a sale.

The same principle applies to products that are overpriced – while fewer sales may be needed to reach a certain revenue target, striking the right balance between markup and sales potential can often lead to better optimization.

#### Neglecting hidden costs

Overlooking hidden costs, such as transaction fees charged by payment gateways, storage costs, and man-hours, can lead to unexpected expenses. Factor in all potential costs associated with making an order generate a positive net revenue.

#### Miscalculating shipping costs

Failing to accurately estimate shipping expenses can potentially give you an unwelcome surprise on the cost side. Shipping cost can vary depending on the carrier, the total weight and volume of the shipment and the address it is sent to. If you are selling products where you expect a dynamic shipping cost, you can look into integrating with shipping carriers using the API connector, or set up internal calculations that give a sufficiently accurate estimate.

#### Missing regulatory compliance

Neglecting to adhere to legal requirements, such as consumer protection laws or tax regulations, can result in costly penalties or legal disputes. Ensuring compliance with relevant regulations is essential to safeguard your business reputation and financial stability.

## Managing one-time payments in Stripe in your Bubble app

{% hint style="info" %}
This article doesn't cover all the technical details of the plugin, but is meant as an introduction to making one-time payments with Stripe. See the core reference entry below for details on the different actions and properties of the Stripe plugin:

Reference: [The Stripe plugin](/core-resources/bubble-made-plugins/stripe)
{% endhint %}

Bubble's official Stripe plugin is well-suited for handling one-time payments. The plugin has built-in features for charging a user immediately, holding a charge. refunding, and more. You will find all the plugin's actions under the *Payments* section in the action dropdown.

<figure><img src="/files/xqaohStNZLsBDCyxfb4O" alt="" width="375"><figcaption><p>All actions related to Stripe can be found under the <em>Payments</em> section in the action dropdown.</p></figcaption></figure>

### Charging a user

{% hint style="info" %}
To see an up-to-date demo of what Stripe's checkout page looks like, and how it can be customized, you can use their interactive Checkout developer page below:

External page: [Explore Stripe Checkout](https://checkout.stripe.dev/)
{% endhint %}

Charging a user means to send the amount and other relevant details to Stripe, so that the user can complete a payment. The user will be redirected to Stripe's checkout page, where they can enter the card details. You start the charge process by triggering the [*Charge the current*](#user-content-fn-4)[^4] *user* action, and Bubble will proceed as below:

<figure><img src="/files/dHAztMWEtOa7tCerVoBc" alt="" width="375"><figcaption></figcaption></figure>

### Confirming the payment in your app

As the illustration shows, the final step of the process is redirecting the user back to the page where the initial page where the process was started. Bubble behaves as if the user never left the page, meaning that the workflow that contained the *Charge the current user* action will continue to run as soon as the user returns to the page.

The plugin connects to Stripe's API, which means that Stripe gets a request from your app, and then sends a response in return. You can use this to verify that the payment was successful, or to display an error message if it wasn't, as well as save any relevant data in your database as needed.

<figure><img src="/files/JS5vklHW84GNn3YEWxq7" alt=""><figcaption><p>Using the <em>Results of step 1</em> data source, you can get relevant information from a charge attempt in Stripe. In this example, we are saving the Charge ID.</p></figcaption></figure>

<details>

<summary>Stripe's response</summary>

The following parameters are made available after a charge has been attempted (whether it was successful or not):

* **Amount**: The total amount of the transaction.
* **Amount Received (including coupons)**: The amount received from the customer after applying any discounts or coupons.
* **Application Fee**: The fee that you keep (only relevant for marketplaces[^5] using Stripe Connect)
* **Currency**: The currency used for the transaction.
* **Charge Id**: The unique identifier for the payment charge.
* **Captured**: Indicates whether the payment has been successfully captured.
* **Payment intent id**: The unique identifier for the payment intent.
* **Order description**: Description of the order or item purchased.
* **Email of charged user**: The email address of the customer who made the payment.
* **Product name**: The name of the product or service purchased.
* **Product image**: An image representing the product or service purchased.
* **Statement descriptor**: A description that appears on the customer's credit card statement to identify the transaction.

</details>

The expandable box above shows the different parameters that are returned to your app after a charge attempt has been made. If the charge was unsuccessful, Stripe will still return parameters. The following key parameters are often a good idea to save, to make it easy to manage the transaction later:

* **Amount (number):** this parameters displays the total amount that was captured in the payment. If the payment was successful, this amount should match what you send in the *Charge the current user* action.
* **Charge ID (text)**: this is the auto-generated unique ID of the charge in Stripe's records. It's often useful to save this ID in your app's database, so that you are able to refer to it later as needed. For example, if you want to refund a charge, you will need this ID. You cannot set the initial value or change the value of this ID. There's normally no reason to display this value to the user.
* **Captured (yes/no)**: this parameter, returned as a yes or no, confirms whether the charge was successful or not. You can use this to take any necessary steps depending on the status of the charge, such as alerting the user of its success or failure.

While you may find the other parameters useful too, these three are generally the ones you want to use to continue to build your checkout workflow. By combining them, you can verify the payment and continue communicating to the user the current status of their purchase, as well as save data needed for your own records.

### Refunding a payment

{% hint style="info" %}
The Stripe plugin enables full refunds for a charge. To process partial refunds, you'll need to configure a custom action using the API Connector plugin.

Article series: [The API Connector](/help-guides/integrations/api/the-api-connector)
{% endhint %}

Sometimes, you'll find it necessary to refund a payment. The Stripe plugin comes with an action to easily refund the full amount in one step:

<figure><img src="/files/eI8Rg8595JGYfpqa5Ivs" alt="" width="375"><figcaption></figcaption></figure>

To refund a payment, the only needed parameter is the *Charge ID* that was returned when the initial payment was made. A refund may fail under the following circumstances:

<table><thead><tr><th width="254">Circumstance</th><th>Refunding</th></tr></thead><tbody><tr><td>No refund made before</td><td>The full amount will be refunded</td></tr><tr><td>Partial refund made before</td><td>The remaining amount will be refunded</td></tr><tr><td>Full refund made before</td><td>Nothing will be refunded</td></tr><tr><td>Transaction is <a data-footnote-ref href="#user-content-fn-6">disputed</a></td><td>Nothing will be refunded</td></tr></tbody></table>

## FAQ: Using Stripe checkout in your Bubble app

#### Do I need to set up a form to collect card details?

No, it's unnecessary and in fact, it's not recommended. Stripe handles the collection of this information for you in a secure, encrypted manner that complies with regulatory standards. This simplifies the process of accepting payments from customers, as you don't need to ensure compliance yourself. Instead, you can rely on Stripe to manage this aspect securely and efficiently. You should never store information about the user's card in your database or anywhere else.

#### What happens if the user clicks "Back" without finishing the Stripe checkout process?

If the user has already been redirected to Stripe Checkout, and then clicks back in their browser, the payment will register as failed. See the [*Confirming the payment*](#confirming-the-payment) section on how to handle this.

#### When is the money transferred to my account?

The charge from the user's card is made immediately, and the money is transferred to your app's Stripe account. The money will then be sent to the bank account you have specified in your Stripe dashboard. You can read more about setting up your account and payout frequency in the Stripe Docs article below:

External page: [Receive Payouts | Stripe Documentation](https://docs.stripe.com/payouts)

## Other ways to learn

<details>

<summary>Core reference</summary>

Core reference: [The Stripe plugin](/core-resources/bubble-made-plugins/stripe)

</details>

<details>

<summary>Video lessons</summary>

YouTube: [Bubble and Stripe | Recorded webinar](https://www.youtube.com/watch?v=HGFVnTf7kMg)

</details>

<details>

<summary>External documentation</summary>

[Stripe docs](https://stripe.com/docs)

* [How Stripe Checkout works](https://docs.stripe.com/payments/checkout/how-checkout-works)
* [Webhooks](https://stripe.com/docs/webhooks)

</details>

[^1]: Stripe has a dedicated feature for recurring billing, to support subscription services such as a SaaS app. You can read more about this in the article below:

    Article: [Subscriptions](/help-guides/getting-started/building-your-first-app/ecommerce-and-payments/subscriptions)

[^2]: *Marketplace apps* are platforms that enable third-party vendors to sell goods and/or services within a digital "marketplace." Stripe offers built-in functionalities to facilitate this.\
    \
    Article: [Marketplace](/help-guides/getting-started/building-your-first-app/ecommerce-and-payments/marketplace)

[^3]: VAT (Value Added Tax) is a type of consumption tax placed on a product whenever value is added at each stage of the supply chain, from production to the point of sale. The amount of VAT the user pays is based on the cost of the product, minus any costs of materials used in the product that have already been taxed.

[^4]: The *Charge the current user* action sends the current user to Stripe's checkout page to make a payment.\
    \
    For this action to be available, the official Stripe plugin needs to be installed in your app.\
    \
    Reference: [The Charge the current user action](/core-resources/bubble-made-plugins/stripe#charge-the-current-user)

[^5]: *Marketplaces* are apps that sell products and services on behalf of a third-party. You can read more about that payment type in the article below:\
    \
    Article: [Marketplaces](/help-guides/getting-started/building-your-first-app/ecommerce-and-payments/marketplace)

[^6]: When a Stripe transaction is disputed, it means that the customer has raised a concern or disagreement regarding the charge, leading to a dispute process initiated by the payment card network or issuing bank.\
    \
    External page: [Disputes | Stripe Docs](https://docs.stripe.com/disputes)


# Subscriptions

{% hint style="warning" %}
**Disclaimer:** Note that while Stripe is mentioned in this document as an example of a payment platform, this should not be interpreted as an endorsement or recommendation of Stripe's services. The information provided here does not constitute legal advice. As an app builder, it is your responsibility to select a payment platform that meets your specific needs. You are also responsible for ensuring compliance with the terms and conditions of the chosen payment platform.

We encourage you to conduct thorough research and, if necessary, consult with a legal professional to make an informed decision.
{% endhint %}

## Defining subscriptions

Subscriptions are a form of payment setup that triggers billing at set, recurring times. This can be particularly useful in scenarios like collecting a monthly or annual fee in exchange for a product or service offered by your app.

<figure><img src="/files/OXKsKgoO8UlfinZSCBtL" alt="" width="375"><figcaption><p>With a single action, you can set up regular payments from a customer.</p></figcaption></figure>

This model is frequently employed by SaaS (Software as a Service) applications, where the product is the app itself. Implementing a subscription-based payment system simplifies the financial relationship for both the app owner and the user, ensuring a consistent revenue stream for the former and uninterrupted service access for the latter.

## Planning subscription services

Just as with other payment types, it's useful to carefully consider your revenue model and the kind of subscription you plan to charge your users before you build it.

In this section, we'll explore various options you can consider for your subscription model. This will help you make informed decisions about structuring your app's revenue stream in a way that aligns with your business goals and meets your users' needs.

### Single subscription

Simply offering one single subscription at a set cost is the easiest way to get started. In this scheme, your end-users are not looking at multiple plans, discounts or anything else that might complicate the pricing structure, but are simply offered one choice with a set price.

#### Example:

<table><thead><tr><th width="209">Plan</th><th>Price</th></tr></thead><tbody><tr><td>Monthly subscription</td><td>$12</td></tr></tbody></table>

### Multiple plans

For complex apps and services, it can be beneficial to offer multiple subscription plans, providing end-users with options to choose how much they're willing to pay. Typically, the price point of each plan correlates with the level of access or features available to the user.

This tiered approach allows users to select a plan that best fits their needs and budget, while also giving them the flexibility to upgrade for more advanced features as required.

#### Example:

<table><thead><tr><th width="175">Plan</th><th width="142">Price</th><th>Possible differences</th></tr></thead><tbody><tr><td>Basic plan</td><td>$12 per month</td><td>Basic features</td></tr><tr><td>Professional plan</td><td>$24 per month</td><td>Additional features, additional team members</td></tr><tr><td>Enterprise plan</td><td>$36 per month</td><td>All features, advanced reports, even more team members</td></tr></tbody></table>

The previous description serves merely as an example to demonstrate a potential approach to structuring tiered subscription plans. The specific plans you choose to offer and their distinct features depend entirely on your preferences and business strategy. However, it's advisable to have a clear plan in place before beginning development. This ensures that both your app and Stripe setup are aligned and configured to support your chosen subscription model effectively.

<figure><img src="/files/rkaInrqgas0W326BT9Y4" alt=""><figcaption><p>Using multiple different plans you can offer your customers tiered payments. <strong>Hint:</strong> the design above is part of Bubble's <a href="/pages/diMQVVnPUnq4Zdzh8nXO">Component Library</a>, and you can set up one just like it in seconds.</p></figcaption></figure>

### Long-term commitment discounts

Some services offer discounts on their plans as an incentive for end-users who commit to a longer subscription period. For instance, choosing an annual plan might come with a discount, serving as a reward for the user's willingness to pay a larger sum upfront.

There doesn't need to be any kind of complex calculations behind this – simply set up a new plan with a different price in Stripe's dashboard.

## Managing plans and subscribers in your Bubble app using Stripe

<details>

<summary>The different between subscriptions and plans</summary>

When working with subscriptions and plans, it's useful to get to know the difference between the two, and how they are stored in Stripe.

In short, a user is **subscribed** to one or more **plans**. In other words, the subscription object contains information such as the customer, start/end dates, trial period, and plan(s) the user is subscribed to.

The *plan* object contains the information about a particular subscription offering. It specifies details such as the name, cost, and billing interval.

</details>

Stripe, in conjunction with Bubble's official plugin, is engineered to streamline the management of subscriptions in your Bubble app. This entails the straightforward creation of pricing plans, specification of pricing and billing frequency, and instructing Stripe to manage the subscription process on your behalf. You will find all the plugin's actions under the Payments section in the action dropdown.

<figure><img src="/files/xqaohStNZLsBDCyxfb4O" alt="" width="375"><figcaption><p>All actions related to Stripe can be found under the <em>Payments</em> section in the action dropdown.</p></figcaption></figure>

When you build an app using subscriptions, most of the work is usually done in the Stripe dashboard:

* **Create plans:** In your Stripe Dashboard, you can define the products or services you offer, along with the pricing plans associated with them. You'll specify details such as the plan name, pricing amount, billing frequency (e.g., monthly or annually), and any trial periods if applicable.
* **Customer billing:** When a user subscribes to one of your plans, Stripe handles the billing process. It can generate invoices, automatically charge customers according to the billing frequency, and send email notifications about upcoming charges.
* **Subscriber management:** You can easily manage your subscribers within the Stripe Dashboard. This includes handling cancellations, upgrades, downgrades, and other subscription-related actions.
* **Subscription events:** Stripe provides webhook[^1] events related to subscriptions, allowing you to track changes, cancellations, or other subscription-related actions in real-time. You can set up webhook endpoints in your Bubble app to handle these events.
* **Reporting and analytics:** Stripe offers reporting tools that help you gain insights into your subscription business. You can access data on revenue, customer retention, churn rates, and more.

### Subscribing a user to a plan in your Bubble app

{% hint style="info" %}
Before proceeding to set up the action needed to start an end-user on a plan, you need to set up one or more plan(s) in the Stripe Dashboard and finish setting up the Stripe plugin with Client ID and Secret Key. Bubble will then fetch the available subscription plans for you.
{% endhint %}

To start a subscription for a user, you use the [*Subscribe the user to a plan*](#user-content-fn-2)[^2] action. A user can be subscribed to one or more plans at the same time, and you can start multiple plans in one single action by using the [*Subscription type*](#user-content-fn-3)[^3] property.

<figure><img src="/files/dHAztMWEtOa7tCerVoBc" alt="" width="375"><figcaption></figcaption></figure>

When you use the *Subscribe a user to a plan* action, Bubble will redirect the user to the Stripe checkout page, where they can enter their card details.

### Confirming the transaction and subscription

As the illustration shows, the final step of the process is redirecting the end-user back to the page where the initial page where the process was started. Bubble behaves as if the user never left the page, meaning that the workflow that contained the *Charge the current user* action will continue to run as soon as the end-user returns to the page.

The plugin connects to Stripe's API, which means that Stripe gets a request from your app, and then sends a response in return. You can use this to verify that the payment was successful, or to display an error message if it wasn't, as well as save any relevant data in your database as needed.

<details>

<summary>Stripe's response</summary>

The following parameters are made available after a charge has been attempted (whether it was successful or not):

* **Subscription ID:** The unique identifier for the subscription.
* **Plan ID:** Unique identifier for the subscription plan associated.
* **Plan name:** The name of the subscription plan.
* **Items:** Lists subscription items.
* **Status:** Describes the current status of the subscription.
* **Starting date:** When the subscription started.
* **Creation date:** Denotes when the subscription was created.
* **Current period starting date:** The start date of the current billing period.
* **Current period ending date:** The end date of the current billing period.
* **Quantity:** Specifies the associated quantity.
* **Trial end date:** Indicates when any trial period ends.
* **Trial start date:** Indicates when any trial period ends.

</details>

The expandable box above shows the different parameters that are returned to your app after a charge attempt has been made. If the charge was unsuccessful, Stripe will still return parameters. The following key parameters are often a good idea to save, to make it easy to manage the subscription later:

* **Subscription ID (text)**: this is the auto-generated unique ID of the subscription object in Stripe's records. It's often useful to save this ID in your app's database, so that you are able to refer to it later as needed. For example, if you want to cancel a subscription, you will need this ID. You cannot set the initial value or change the value of this ID. There's normally no reason to display this value to the end-user.
* **Plan ID**: this is the auto-generated unique ID of the plan in Stripe's records. The ID is generated when the Plan is created, and not when a subscription is created. You cannot set the initial value or change the value of this ID.

Other parts of the response can of course be useful to store as well, but these two make it easy to quickly identify a subscription and the plan to which the subscription is connected.

### Canceling a plan subscription in your Bubble app

To terminate a user's subscription to a plan in your Bubble app, you can use the *Cancel the current user's plan* action. You can cancel multiple plans in a single action

### Incomplete payments

{% hint style="info" %}
**Common misconception:** Note that *Incomplete payments* refer to the *status* of the transaction, not the amount to be paid. In other words, an incomplete payment in this context is a transaction process that is not yet completed.
{% endhint %}

The *Subscribe the user to a plan* action lets you allow incomplete payments. You can specify the behavior using the *Payment behavior* dropdown. This property allows you to set the status of subscriptions as "incomplete" if it can't be fully completed.

#### Why use incomplete payments?

Stripe Checkout handles [Strong Customer Authentication (SCA)](#user-content-fn-4)[^4] by default, ensuring that payments are authenticated according to regulatory requirements. However, there may be scenarios where additional customer actions are necessary to complete a payment.

In such cases, the incomplete payment parameter provides flexibility in managing subscriptions. For example, if a payment requires SCA authentication but the customer fails to complete the process within a specified time frame, the subscription may transition to an incomplete status. This allows you to handle scenarios where payments cannot be immediately confirmed, providing a seamless experience for both you and your customers while ensuring compliance with regulatory requirements.

{% hint style="warning" %}
**Time limit:** If the *PaymentIntent* remains unconfirmed for 23 hours, subscriptions automatically transition to an *incomplete\_expired* status, at which point the payment is considered failed.
{% endhint %}

The following options are available when you set the *Payment behavior* property. The *Stripe parameter* column indicates the associated [Stripe parameter](#user-content-fn-5)[^5].

<table><thead><tr><th width="211">Option</th><th width="201">Stripe Parameter</th><th>Explanation</th></tr></thead><tbody><tr><td>Allow incomplete</td><td>allow_incomplete</td><td>Creates Subscriptions with status=incomplete if the first invoice requires additional payment actions from the customer.</td></tr><tr><td>Error if incomplete</td><td>error_if_incomplete</td><td>Generates an error if the first invoice can't be paid, preventing the creation of Subscriptions with incomplete status.</td></tr><tr><td>Pending if incomplete</td><td>pending_if_incomplete</td><td>Sets Subscriptions with status=pending if the first invoice requires additional payment actions from the customer. Useful for managing scenarios where additional actions are needed before activating the Subscription.</td></tr><tr><td>Default incomplete</td><td>default_incomplete</td><td>Sets the default behavior for Subscriptions, creating them with incomplete status if the first invoice requires additional payment actions from the customer.</td></tr></tbody></table>

## Trial periods

{% hint style="warning" %}
Setting up a trial period is not support by the Stripe plugin, but you can set this up in the API Connector plugin.

Article: [The API Connector](/help-guides/integrations/api/the-api-connector)\
External page: [Using trial periods | Stripe docs](https://stripe.com/docs/billing/subscriptions/trials)
{% endhint %}

Stripe allows you to provide trial periods in your app, allowing customers to try it out for a specified duration before making a payment. This is useful when you want your customers to be able to try out your app for a free period, such as a week or month.

To set up a trial period, all you need to do is to add the `trial_end` parameter to the call. This is a timestamp that determines *when* the trial should end. As such, you don't set up a given period, but a start and end date, where the start date is automatically set to the time of signup and the end date is specified with the `trial_end` parameter.

After the trial period concludes, and if the subscription status hasn't been paused, Stripe automatically generates an invoice for the subscription. Starting a trial period requires the user to provide their card details.

## Invoices

Stripe automatically creates an invoice for each billing cycle of a subscription (including the first). When you subscribe a customer, the invoice is first generated, and then immediately finalized.

On later billing cycles, the process looks slightly different:

First, Stripe generates the new invoice. It maintains this invoice in a [draft state](#user-content-fn-6)[^6] for 60 minutes, while it tries to finalize the transaction using the user's saved payment method. This is to avoid the invoice becoming finalized[^7] without payment. If the payment is successful, the invoice is updated to paid.

### What's included on a Stripe invoice?

Stripe automatically includes the following information on invoices. Your app needs to provide information as needed:

* Line items with product details
* Custom invoice numbering
* Customer information
* Discounts or coupons
* Tax rates
* [Line items](#user-content-fn-8)[^8]
* Memo[^9]
* Footer

### Customizing invoices

Stripe lets you customize invoices so that they match your company's brand. You can customize the following points:

* Icon
* Logo
* Brand color
* Accent color

Changes to the brand can be done through the Stripe's API, but is not supported in the official Stripe plugin. However, you can change the design of the invoice in the Stripe Dashboard, by opening the *Branding settings* panel.

External page: [Branding Settings | Stripe Dashboard](https://dashboard.stripe.com/account/branding)

## FAQ: Stripe subscriptions

#### How are invoices and receipts different?

Invoices and receipts serve different purposes in a financial transaction:

1. **Invoice:**
   * An invoice is a document issued by a seller to **request payment** from a buyer for goods or services.
   * It typically includes details such as the seller's information, buyer's information, a breakdown of products or services provided, quantities, prices, and the total amount due.<br>
2. **Receipt:**
   * A receipt is a document provided by the seller to the buyer **after the payment** has been made.
   * It acknowledges that payment has been received and provides proof of the transaction.
   * A receipt usually includes information such as the date of payment, payment method, transaction reference or ID, and details of what was purchased.

#### What is a line item?

A line item is a specific entry or itemized charge within an invoice or bill that represents a particular product, service, or fee. It includes the item's description, price, currency and tax rate. You can optionally link the line item to an existing subscription by providing the subscription ID.

Line items are used to clearly specify what the recipient of the invoice is being charged for, making it easier to understand and verify the expenses or services being billed.

Let's illustrate with an example, and imagine your company is offering a SaaS app plan. In addition, you offer extra credits that can be used for fast-track customer support:

Invoice: ABC-123\
Date: January 15, 2024\
Due: February 1, 2024

<table><thead><tr><th width="154">Product</th><th width="128">Currency</th><th>Cost</th></tr></thead><tbody><tr><td>Monthly fee</td><td>USD</td><td>$39.00</td></tr><tr><td>Extra credits</td><td>USD</td><td>$12.00</td></tr></tbody></table>

Each of the rows is one line item. Stripe automatically includes a line item for the relevant plan the user is subscribed to, and you can add additional line items as needed by using the [*Create an invoice item*](#user-content-fn-10)[^10] action.

## Other ways to learn

<details>

<summary>Articles</summary>

* [The Bubble API](/help-guides/integrations/api/the-bubble-api) - how to set up API workflows that can receive webhooks from Stripe
* [The API Connector](/help-guides/integrations/api/the-api-connector) - setting up additional Stripe API calls not supported by the official Stripe plugin, using the API Connector
* [The Component Library](/help-guides/design/the-component-library) – use ready-made component templates to set up designs similar to the pricing plans example above

</details>

<details>

<summary>Core reference</summary>

[The Stripe plugin](/core-resources/bubble-made-plugins/stripe)

</details>

<details>

<summary>External documentation</summary>

[Stripe docs](https://stripe.com/docs)

* [Subscriptions](https://stripe.com/docs/billing/subscriptions/overview)
  * [Prorate for usage-based billing](https://docs.stripe.com/billing/subscriptions/cancel#prorate-for-usage-based-billing)
  * [Invoices](https://docs.stripe.com/invoicing)
* [Webhooks](https://stripe.com/docs/webhooks)

</details>

[^1]: Webhooks are real-time notifications to inform your app about important events, such as successful payments or canceled subscriptions.\
    \
    In essence, it means that when an event occurs, Stripe can make a request to one of your API workflows.\
    \
    Article series: [The Bubble API](/help-guides/integrations/api/the-bubble-api)\
    External page: [Stripe webhooks](https://stripe.com/docs/webhooks)

[^2]: The *Subscribe the user to a plan* action charges a user and starts a billing cycle in Stripe.

    Reference: [Subscribe the user to a plan](/core-resources/bubble-made-plugins/stripe#subscribe-the-user-to-a-plan)

[^3]: *Subscription type* lets you choose between subscribing a user to *one* plan or *multiple plans*.

    Reference: [Subscription type](/core-resources/bubble-made-plugins/stripe#subscription-type)

[^4]: *SCA (Strong Customer Authentication)* regulation is a requirement within the European Union aimed at enhancing the security of online payments and reducing fraud.

    It mandates that customers authenticate themselves using two or more factors when making electronic transactions.\
    \
    External page: [SCA | Stripe Docs](https://stripe.com/en-no/guides/strong-customer-authentication)

[^5]: The plugin makes an API call to Stripe, and includes these parameter behind the scenes. The official Stripe documentation linked below explains each parameter in more detail:

    External page: [Subscription payment behavior | Stripe Docs](https://docs.stripe.com/api/subscriptions/create#create_subscription-payment_behavior)

[^6]: Invoices can have different statuses, such as *draft* and *paid*. All invoices start in a draft state.

    You can read more about invoice states in the article below:

    External page: [Invoice states | Stripe Docs](https://docs.stripe.com/invoicing/overview#invoice-statuses)

[^7]: When an invoice is finalized, its status is changed to *paid*. Most details on an invoice cannot be changed after it is paid.

    You can read more about invoice states in the article below:

    External page: [Invoice states | Stripe Docs](https://docs.stripe.com/invoicing/overview#invoice-statuses)

[^8]: A line item is a specific entry or itemized charge within an invoice or bill that represents a particular product, service, or fee.

    Article FAQ: [What is a line item?](#what-is-a-line-item)

[^9]: The *Memo* field in Stripe is known as *Description* in Bubble.

[^10]: The *Create an invoice item* action creates a new item and adds it to the user's next invoice.

    Reference: [Create an invoice item](/core-resources/bubble-made-plugins/stripe#create-an-invoice-item)


# Marketplace

{% hint style="warning" %}
**Disclaimer:** Note that while Stripe is mentioned in this document as an example of a payment platform, this should not be interpreted as an endorsement or recommendation of Stripe's services. The information provided here does not constitute legal advice. As an app builder, it is your responsibility to select a payment platform that meets your specific needs. You are also responsible for ensuring compliance with the terms and conditions of the chosen payment platform.

We encourage you to conduct thorough research and, if necessary, consult with a legal professional to make an informed decision.
{% endhint %}

{% hint style="info" %}
This article describes how you can use the Stripe plugin as a payment provider in your Bubble app. If you are looking for information about earning regarding the Bubble template and plugin marketplace, see [this article.](/account-and-marketplace/account-and-billing/selling-on-the-marketplace)
{% endhint %}

This article looks into how to plan for building a marketplace app.

## Defining marketplace apps

Marketplace apps are somewhat different from one-time paments and subscriptions, in that they involve a third party. In this article, we'll refer to the different roles in this transaction as the following:

* The **marketplace** is your app (the Stripe account)
* The **customer** is the person buying something
* The **seller** is the third party selling something (the Connected Stripe account)

Marketplace apps have transformed the way we engage in commerce, creating digital spaces where buyers and sellers converge to exchange products, services, or information. Instead of an app selling a set catalog of products and services, marketplace apps allow sellers to sell *their* products and services in your app.

<figure><img src="/files/0QcXU2PXaeJ4cYIEgQy2" alt="" width="188"><figcaption></figcaption></figure>

<div align="center"><figure><img src="/files/4ErJ0RuakfAgOBPLXnzk" alt="" width="375"><figcaption><p>Marketplace apps connect sellers with customers – whether they are selling physical products, online courses, or something completely different.</p></figcaption></figure></div>

We define *marketplace app* as an app where a transaction is split between these three parties. The Bubble platform, for example, allows users to set up an app and generate revenue with it, but Bubble is not a marketplace app under this definition, as we're not a part of the customer transaction.

As illustrated in our [list of different marketplace examples](/help-guides/getting-started/building-your-first-app/ecommerce-and-payments#marketplaces), marketplace apps encompass a broad spectrum of products and services. Interestingly, some apps operate as marketplaces without users always realizing their marketplace nature: for example, Uber and Lyft don't actually own any vehicles, but simply offer a marketplace that connects passengers with drivers and organizes the transaction.

There are a variety of marketplaces live today, selling stuff like:

* Tangible goods
* Video content, such as courses
* Live sessions, such as coaching, counseling, and music lessons
* Services like transportation and food delivery
* Specialized product categories such as art and collectibles
* Rental of real estate properties
* Accommodation in hotels

Many marketplaces started out serving a niche, and then grew over time to handle multiple categories. As you plan your app, it's worth keeping in mind that everything starts with the first step.

{% hint style="info" %}
In this article, we will use the term "product" to refer to any item or service being offered in a marketplace, regardless of its specific nature.
{% endhint %}

## Planning marketplace apps

{% hint style="info" %}
Stripe Connect offers various customization options to suit different business models, but this article primarily focuses on the model described below. If you are new to Bubble and/or Stripe, it's likely you will find this setup the easiest to set up.
{% endhint %}

While marketplace apps can have very different structures, many employ a revenue model that bears a resemblance to the following structure. We'll define the app user as *end-user* in this example, to separate them from sellers.

<figure><img src="/files/C1oPxlZUSEjCWdKwrAuY" alt="" width="563"><figcaption></figcaption></figure>

As illustrated the process is as follows:

1. End-user (customer) **browses** products in the marketplace.
2. End-user **purchases** one or more products.
3. End-user makes a **payment** through Stripe.
4. The amount is split between the three parties:
   1. **Stripe** retains its transaction fee
   2. A portion of the payment is retained in the **seller's** Stripe account.
   3. Another portion is transferred to the **marketplace** owner's Stripe account.

Stripe's product, Stripe Connect, automates the allocation of payments and fees.

### Stripe Connect for building marketplace apps in Bubble

Stripe Connect is Stripe's solution for helping platforms and marketplaces efficiently manage payments and payouts for multiple sellers or service providers within their ecosystem. Whether you're building a marketplace for various types of products, services, or a combination of both, Stripe Connect simplifies the process of handling financial transactions. This section explores how Stripe Connect functions and how you can use it to establish a secure and streamlined payment system for your platform.

First, let's see how Stripe Connect relates to each of the three parties involved in a marketplace transaction:

1. **Platform**: The platform is your app, that facilitates transactions between buyers and sellers. The platform uses Stripe Connect to handle payments and payouts for its users.
2. **Seller**: Sellers are individuals or businesses that offer products or services on the platform. They receive payments from buyers through the platform. Each seller has their own Stripe account connected to the platform.
3. **Customer**: Customers are the users who purchase products or services from sellers within the platform. They make payments to the platform, which then distributes the appropriate portion to the seller after deducting any fees or commissions.

Stripe Connect acts as the intermediary that facilitates the flow of funds between these three parties, ensuring secure and transparent transactions within the platform.

### Fees

The part of the transaction that the platform (your app) keeps, is often called the *platform fee*. To understand how this is structured, let's look at the total fee structure for each of these two parties (platform and seller).

In the example below, we show the calculation for each party, and how much is party is left with. Please note that these values are purely for demonstration and are not reflective of actual fees:

<table><thead><tr><th width="209">Description</th><th width="83">Fee</th><th>Total</th></tr></thead><tbody><tr><td>Transaction</td><td></td><td>$100</td></tr><tr><td><a data-footnote-ref href="#user-content-fn-1">Stripe transaction fee</a></td><td>0,5%</td><td>$0.5</td></tr><tr><td><a data-footnote-ref href="#user-content-fn-2">Platform fee</a></td><td>5%</td><td>$5</td></tr><tr><td><strong>Seller's payout</strong></td><td></td><td><strong>$94,5</strong></td></tr><tr><td><strong>Stripe keeps</strong></td><td></td><td><strong>$0.05</strong></td></tr><tr><td><strong>Platform keeps</strong></td><td></td><td><strong>$5</strong></td></tr></tbody></table>

As the table illustrates, both the platform fee and Stripe transaction fee is paid by the *seller,* and whichever platform fee you've set is calculated on the total transaction value. The *buyer* is never made aware of the calculation going on behind the scenes, and is simply asked to pay the full $100 amount in one transaction.

The fee is set when you initiate the transaction, using the *Charge the current user* action (described [below](#charging-a-sale-on-behalf-of-a-seller)).

## Managing marketplace Bubble apps in Stripe

### Registering sellers

Every seller who sells something through your app needs to have their own individual Stripe account. This setup ensures that each seller can receive payments directly and manage their financial transactions independently within your platform.

The official Stripe plugin supports setting up seller accounts automatically. This will work whether the seller has an existing Stripe account, or wants to create a new one. To set up sellers on Stripe, use the [*Register the user as a seller*](#user-content-fn-3)[^3] action. Keep in mind that the [Stripe plugin](#user-content-fn-4)[^4] must be installed and correctly set up to work.

<figure><img src="/files/N9qOMPW2ty5fal304q2N" alt="" width="375"><figcaption><p>The <em>Register the user as a seller</em> action lets you easily create seller accounts in your app. The user will be forwarded to Stripe, but you can pre-populate many of the fields as illustrated above</p></figcaption></figure>

### Identifying the correct seller

When building a marketplace app, accurately identifying the appropriate seller for each sale is important to ensure proper payment to the respective seller. What you should keep in mind, is to structure your database so as to keep track of which seller is connected to a particular product.

You likely have a *product* data type (or similar) in your app, that contains information about the item being sold, such as price and description. You can store which user is the registered seller of that particular product on that data type, Alternatively, you can use a more over-arching data type that contains the user.

#### Using the *Created by* field on the product

Whenever a thing is created in your Bubble database, the *Created by* (user) field on that thing is populated automatically with the user that created it. This is an easy way to identify the correct seller. However, there are some potential pitfalls that are worth considering:

* The user needs to be logged in, to be associated with the record they create
* The *Created by* field can't be edited
  * If you want to be able to move a product to a different user, you need to create a separate field of type *user* for this
* If a user is deleted, the *Created by* field will be left empty

In a Bubble expression, you would refer to the seller as `Product's Created by`, provided that the data type is called *Product.*

#### Using a separate *User* field on the product

If you prefer to use a field that you can edit, you can instead set up a field on the product data type that identifies the seller. To do this, simply add a field of type *User*, and give it a descriptive name such as *Seller* or *Owner.* Keep in mind the following:

* The field will not be populated automatically, meaning that you need to specify the seller in the workflow where the thing is created
* If the user is deleted, the field will be left empty

In a Bubble expression, you would refer to the seller as `Product's Seller`.

#### Using a separate data type

You can also consider using a broader data type to contain seller information. For example, if you have a marketplace where each user sets up a virtual "shop", you may use this data type to store the seller information.

For example, let's imagine that a seller called John Doe sets up a shop called *John's Shop.* In this case, each product would likely be connected to the *shop,* instead of directly to the user, looking something like this: `Product's Shop`. To identify the seller to register with the sale, you would set up the expression like this: *`Product's Shop's Owner`.*

This approach can be valuable in certain situations. For instance, if your application facilitates collaborative product management among teams, the shop entity can serve as the data type that connects them.

Let's look at some of the potential pitfalls:

* The seller sent to Stripe needs to be a user. As such, you will still need to refer to one registered user when sending the charge to Stripe.
* The initial setup and maintenance is slightly more complex, as you will be dealing with more data types.
* If privacy rules apply to the products, you may need to [plan for this](#user-content-fn-5)[^5] as you structure your database

### Charging a sale on behalf of a seller

When you are ready to bill a buyer for a purchase linked to a seller, you use the [*Charge the current user action*](#user-content-fn-6)[^6], just like you would with any other one-time payment. The difference is that for this charge, you need to define which seller is responsible for the sale, so that the seller's fee can be registered with Stripe. The seller needs to be registered (see the [above step](#registering-sellers)) for this to work.

<figure><img src="/files/UXx42dWUdP9WqutBrzuG" alt="" width="375"><figcaption></figcaption></figure>

To instruct Bubble to sell on behalf of another user, and to identify that user, follow these steps:

1. Check the *The payee of this transaction is another user*
2. Assign the seller in the [*Trans. payee* field](#user-content-fn-7)[^7]

If your app is to impose a fee on the sale, you can also fill out the [*App fee*](#user-content-fn-8)[^8] field.

### Seller payouts in your Bubble app

As the default setting, any funds transferred to a connected account are held in the connected account's (seller's) Stripe balance and are scheduled for daily rolling payouts. If you want to modify the payout frequency, you can do so by changing settings in the [Stripe dashboard](https://dashboard.stripe.com/)'s payout settings.

For up-to-date information on that process, please check the documentation below:

External page: [Stripe Connect Payouts](https://stripe.com/docs/connect/add-and-pay-out-guide)

## Other ways to learn

<details>

<summary>Articles</summary>

* [The Bubble API](/help-guides/integrations/api/the-bubble-api) - how to send up API workflows that can receive webhooks from Stripe
* [The API Connector](/help-guides/integrations/api/the-api-connector) - to set up additional Stripe API calls not supported by the official Stripe plugin, you can use the API Connector

</details>

<details>

<summary>Core reference</summary>

[The Stripe plugin](/core-resources/bubble-made-plugins/stripe)

</details>

<details>

<summary>External documentation</summary>

[Stripe docs](https://stripe.com/docs)

* [Stripe Connect](https://stripe.com/docs/connect) (Stripe's marketplace solution)
* [Webhooks](https://stripe.com/docs/webhooks) (returning data from Stripe to your app upon an event)

</details>

[^1]: Stripe's transaction fee is the charge imposed by Stripe for processing a payment transaction. Stripe retains this fee after the funds have been successfully collected.

    The fee mentioned here is for illustrative purposes only.

[^2]: The platform fee represents the charge your app imposes on each transaction. The value, such as 5%, provided here is merely an example; you determine the appropriate fee for your platform.

[^3]: *Register the user as a seller* is an action that registers the currently logged-in user as a seller on Stripe.

    Reference: [Register the user as a seller](/core-resources/bubble-made-plugins/stripe#register-the-user-as-a-seller)

[^4]: You can find more information about each setting in the Stripe plugin in our Core Reference:\
    \
    Reference: [The Stripe plugin](/core-resources/bubble-made-plugins/stripe)

[^5]: Privacy rules only accommodate one tier of ownership. For example, if a privacy rule is connected to a user, you can refer to <mark style="color:green;">`product's user`</mark>, but you can't set up <mark style="color:red;">`product's shop's user`</mark>. You can read more about privacy rules in the article below:\
    \
    Article: [Privacy rules](/help-guides/data/the-database/protecting-data-with-privacy-rules)

[^6]: The *Charge the current user* action sends the current user to Stripe's checkout page to make a payment.\
    \
    For this action to be available, the official Stripe plugin needs to be installed in your app.\
    \
    Reference: [The Charge the current user action](/core-resources/bubble-made-plugins/stripe#charge-the-current-user)

[^7]: This field accepts the *User* data type.

[^8]: This field supports a currency value, and not a percentage. You can calculate a fee using an expression like the above example.


# Creating and managing projects

This section covers how to create, manage and delete projects connected to your Bubble account

## Creating projects

To create a new projects, go to the [*Projects* section](https://bubble.io/home/projects) of your Bubble account and click the *Create new project* button in the upper right corner.

<figure><img src="/files/5rBphoWRI8GVwt9Q8Dny" alt=""><figcaption></figcaption></figure>

You will be asked to give your project a name and whether you want to create a project from a template.

{% hint style="info" %}
To build an project from a template, you first need to add one or more templates from the [Template marketplace](https://bubble.io/templates) to your account. You can then select a template from the Create project dropdown.
{% endhint %}

### Projects names

When you give your project a name (such as my-bubble-application), that name is reserved and a unique URL is generated. The name is held and used by Bubble for indexing purposes, which means:

* The **name cannot be changed** after it's set
* The **name cannot be reused** in another project by you or anyone else, even after the project is deleted

The project name is visible in your project's default URL, such as:

`https://my-bubble-application.bubbleapps.io`

Once you are on a [custom domain](#user-content-fn-1)[^1], the project name is no longer visible anywhere in your live project.

### What is created?

Everything needed to start building your project is automatically set up when you create a new project:

* Your project gets a **unique URL** based on the name you gave it
* The **default pages** are set up, such as index and 404 pages.
* **Two databases** (one for Development and one for Live)
* Any **template** you selected (if any) is automatically applied

### Onboarding

If you are new to Bubble or want a repeat of the basics, you may find our onboarding tools useful. The onboarding wizard is available whenever you create a new project.

## Managing projects

### Project plans

Each project is connected to a pricing plan of your choice, to ensure that you can work on multiple projects that are in different stages of its life cycle, growth and requirements. You can read more about the plans in our [Account and Billing](/account-and-marketplace/account-and-billing) section.

### Deleting a projects

If you no longer need a project, you can delete it completely from your account.

#### Implications of deleting a project

{% hint style="danger" %}
It's very important that you understand the implications of deleting a project before going through with it. Please read the section below carefully.
{% endhint %}

* Once a project is deleted, it cannot be recovered, even by the Bubble team.
* The project's name[^2] is locked even after it's deleted – don't delete an project in an attempt to free up the name (the project name is only visible to your users in your project's default URL).
* Ensure the app ID is entered exactly as it appears, including all lowercase letters and hyphens
* It is not possible to delete only the mobile portion of an app while retaining the web app.
* All details related to your project are deleted without exceptions (including the databases).

#### How do delete a project

To delete a project, first locate it in the [*Projects* section](https://bubble.io/home/projects) of your Bubble account. Make sure you are logged in first.

Click the relevant project to reveal the sidebar, and then click the menu icon to reveal the project options. Click *Delete project.*

<figure><img src="/files/0ua0dXUjK1RZcPhDc5xD" alt=""><figcaption></figcaption></figure>

After clicking the *Delete project* option you will be asked a security question to confirm the process. Deleting the projects can take up to a few minutes, depending on its complexity and the size of its database.

<figure><img src="/files/1IUJApcS0T5PMESA0ZM0" alt=""><figcaption></figcaption></figure>

#### Who can delete a project?

Only administrators can delete a project. If you cannot see the *Delete project* button, it means you don't have the sufficient access level to delete it.

### Duplicating a project

You can also make an exact copy of a a project. To duplicate a project, first locate it in the [Projects section](https://bubble.io/home/projects) of your Bubble account. Make sure you are logged in first.

Click the project to display the right-hand side menu, and then click the menu option to reveal the projects options:

<figure><img src="/files/Gt99FV5gjQveiJKJlvDo" alt=""><figcaption></figcaption></figure>

You will be asked to give the duplicate project a name, and whether to copy the content of the database as well.

{% hint style="danger" %}
Copying the database can have implications for the privacy of your project's users: keep in mind that both the Development and Live databases are copied, potentially including sensitive data.
{% endhint %}

{% hint style="warning" %}
If you decide not to copy the database, you will not be able to do so later without manually exporting and importing it. We recommend giving careful thought as to include it or not.
{% endhint %}

#### Who can duplicate a project?

Only administrators can duplicate a project. If you cannot see the *Duplicate project* option, it means you don't have the sufficient access level to delete it.

## Collaboration

If you are added to a project as a collaborator[^3], you will also see it in your list of projects. The same is true for anyone that you add as a collaborator to one of your projects.

You can read more about Collaboration in our [dedicated article](/help-guides/maintaining-an-application/collaboration).

## The agency plan

The agency plan is made for developers and teams that build projects for others, and grants broader access to collaboration across multiple projects.

See our article on [Building projects for others](/account-and-marketplace/account-and-billing/building-apps-for-others) for more information on the agency plan.

## Relevant articles

<details>

<summary>Canceling your Bubble plan</summary>

If you want to cancel your Bubble plan/subscription, follow the steps in the article below:

Article section: [Account and Billing](/account-and-marketplace/account-and-billing) | [Canceling your Bubble plan](/account-and-marketplace/account-and-billing/pricing-plans/plans-and-billing#canceling-a-plan)

</details>

[^1]: The *domain* is the address or URL through which internet users can access a website. It's a human-readable form of an IP address. It makes up the root URL for your Bubble app.\
    \
    For example, the Bubble website domain is <http://www.bubble.io.\\>
    \
    Article: [Custom domain and DNS](broken://pages/Sfb2EVgX6WfgYIibQCNa)

[^2]: You can read more about your app's name here:\
    \
    Article section: [App names](#app-names)

[^3]: A *collaborator* is any Bubble developer that you invite to your app using the Collaboration feature.

    Article: [Collaboration](/help-guides/maintaining-an-application/collaboration)


# The Bubble editor

This section covers how to navigate the Bubble editor

The Bubble editor refers to the editor connected to any one app (as opposed to the dashboard where you navigate your different apps and manage your account).

{% embed url="<https://www.youtube.com/playlist?list=PLoNVJrdvQQYlGHGcKDCpbgAv1lruNA8db>" %}
Our Academy course to introduce you to Bubble and its interface.
{% endembed %}

{% hint style="info" %}
**Multiple browser tabs editing the same app**

Opening the Bubble editor for the same app in multiple browser tabs might result in unexpected behavior. We recommend keeping the editor of an app limited to just one tab at a time.
{% endhint %}

The Bubble editor is like a digital workshop where you build and design your app. Think of it as a crafting table where you arrange, tweak, and connect different parts to make your app look and work just the way you want.

We will go into the technical details of design, data and logic in later chapters, but for now we'll cover how the editor is structured, and the different tools available.

<details>

<summary><mark style="color:blue;">Tabs and sections:</mark> how the editor is structured</summary>

First, we'll explore the basic structure of the editor and how to navigate it. The editor is divided into tabs, and some of those tabs are further divided into sections.

Article: [Tabs and sections](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections)

</details>

<details>

<summary><mark style="color:blue;">Key features:</mark> Saving, undo, navigation and contextual menus</summary>

Next, we'll look into basic functionality like saving, undo/redo and using the Bubble toolbar and right-click contextual menus.

Article: [Key features](/help-guides/getting-started/navigating-the-bubble-editor/tools/key-features)

</details>

<details>

<summary><mark style="color:blue;">Tools:</mark> the property editor, search tool and issue tracker</summary>

Then, we'll have a look at the most widely used tools in the editor. The property editor is used to change the properties of elements on the page. The search tool is an advanced tool to search for a wide range of different objects in your app and the issue tracker helps you identify and fix issues.

Article: [The property editor](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/design-tab/the-property-editor)\
Article: [The search tool](/help-guides/getting-started/navigating-the-bubble-editor/tools/the-search-tool)\
Article: [The Issue Checker](/help-guides/getting-started/navigating-the-bubble-editor/tools/the-issue-tracker)

</details>

<details>

<summary><mark style="color:blue;">Previewing and deploying:</mark> running your app with test and live users</summary>

Bubble comes with a built-in tool to preview your app. This Development environment comes with a completely independent database and lets you try out your app and all its features before deploying it to live users.

Deploying your app means transferring or updating the current version of your application from the Development environment to the Live environment, making it accessible to end-users.

Article: [Previewing your app](/help-guides/getting-started/navigating-the-bubble-editor/previewing-your-app)\
Article: [Deploying your app](/help-guides/publishing-your-app/deploying-your-app)

</details>

## Other ways to learn

<details>

<summary>Core reference</summary>

The core reference section below covers each individual setting in each of Bubble's tabs and sections.

Core reference: [Bubble's interface](/core-resources/bubbles-interface)

</details>


# Tabs and sections

The Bubble editor is organized into *tabs* that align with the different aspects of app development, such as design, data management, style customization, and technical settings/logs. Some tabs are further segmented into *sections*.

**Tabs** are navigated using the left-hand menu that remains visible at all times.

<figure><img src="/files/DotC41GI7Z7H2gJRIwt3" alt=""><figcaption><p>The left-hand bar gives you quick access to all <strong>tabs</strong> in the Bubble editor at all times.</p></figcaption></figure>

**Sections** (when available) are navigated using the menu that is visible at the top of the currently active tab:

<figure><img src="/files/TtiiYa7XvOyn2phEjvtp" alt=""><figcaption><p>Some tabs are further divided into <strong>sections</strong> relevant for that tab.</p></figcaption></figure>

These two bars make up the Bubble editor navigation, and contain the following tabs and sections:

### Design

The design tab is where you design pages in your app by adding elements such as text, images, buttons and icons.

Article: [The Design tab](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/design-tab)\
Article series: [Design](/help-guides/design)

### Workflow

The workflow[^1] tab is where you set up events and actions to instruct Bubble what to do as the user interacts with your app, such as what happens when a button is clicked.

Article: [The Workflow tab](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/workflow-tab)\
Article series: [Workflows](/help-guides/logic/workflows)

#### The backend workflow editor

If you have enabled the [Workflow API](#user-content-fn-2)[^2] in your app, you can access the backend workflow editor by using the *Pages* dropdown and clicking the *Backend workflows* link at the bottom. You can learn more in the links below. If you are just starting to learn Bubble, this is a more advanced topic.

Article: [The Workflow API](/help-guides/integrations/api/the-bubble-api/the-workflow-api)\
Article section: [Enabling the Workflow API](/help-guides/integrations/api/the-bubble-api/the-workflow-api#activating-the-workflow-api)\
Article section: [Accessing the backend workflow editor](/help-guides/integrations/api/the-bubble-api/the-workflow-api#accessing-the-backend-workflow-editor)

### Styles

#### Element styles

Element styles are collections of style settings that you can apply to elements, including color, shadow, font, borders and responsive behavior.

Article: [The Styles tab](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/styles-tab)\
Article series: [Styling](/help-guides/design/variables-and-styles)

#### Style variables

Style variables are used to store font and color variables that can be applied to elements or element styles.

\
Article: [Font variables](/help-guides/design/variables-and-styles/font-variables)\
Article: [Color variables](/help-guides/design/variables-and-styles/color-variables)

### Plugins

The Plugins tab is where you install, uninstall and update plugins in your app.

Article: [The plugin tab](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/plugins-tab)\
Article: [Plugins](/help-guides/integrations/using-plugins)

## Other ways to learn

<details>

<summary>Core reference</summary>

The core reference section below covers each individual setting in each of Bubble's tabs and sections.

Core reference: [Bubble's interface](/core-resources/bubbles-interface)

</details>

[^1]: A *workflow* is the combination of an *event* that triggers one or more *actions*.\
    \
    They are how you instruct Bubble to respond to what the user does.\
    \
    Article: [Workflows](/help-guides/logic/workflows)

[^2]: The *Workflow API* is the part of Bubble's API that allows you to enable other apps and systems to trigger API workflows in your app.\
    \
    Article: [The Workflow API](/help-guides/integrations/api/the-bubble-api/the-workflow-api)\
    Article series: [The Bubble API](/help-guides/integrations/api/the-bubble-api)


# Property Editor Beta

This page is dedicated to the the beta for the redesigned property editor (as of Dec 2025).

{% content-ref url="/pages/GIIG3EbTAoI4WltzySGr" %}
[Quick start guide (For new users)](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/property-editor-beta/quick-start-guide-for-new-users)
{% endcontent-ref %}

{% content-ref url="/pages/z8xffuRDRufBaS6ICwg6" %}
[Overview of the property editor beta](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/property-editor-beta/property-editor)
{% endcontent-ref %}

{% content-ref url="/pages/BQnH2YEYKKUO7W8X7ZHA" %}
[Navigating the Redesigned Property Editor](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/property-editor-beta/property-editor-migration-guide-for-existing-users)
{% endcontent-ref %}

Looking for information about the original property editor? Please see the following section:

[The property editor](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/design-tab/the-property-editor)


# Quick start guide (For new users)

Last Updated: April 2026

### Introduction

#### What is the property editor (PE)?

The property editor is your control center for customizing elements on your page. It's where you adjust how elements look, behave, and respond to user actions. The property editor appears as a pinned panel on the right side of your editor whenever you select an element on the canvas.

<figure><img src="/files/CyZ72nAlQw6Rp2y1GkN5" alt=""><figcaption></figcaption></figure>

***

### Working with the property editor

#### Basic navigation

* Resize the panel by dragging its left edge
* Collapse sections you're not actively using to reduce visual clutter and make frequently-used controls easier to access
* Search within dropdowns (like the color picker) to quickly find variables or options
* Unpin the elements tree when you want more canvas space—press Esc to hide the property editor temporarily
* Access custom states via the button in the top-right of the property editor header

<figure><img src="/files/0UXjGC5Ea0B2mNnNhloi" alt=""><figcaption></figcaption></figure>

***

### Understanding the property editor’s tabs

The property editor is organized into three main tabs: Visual, Interaction, and Conditional. Each tab controls a different aspect of your element's behavior.

![](/files/l5NywHjGFOCzZOB2mKKW)

#### Visual tab

**What it controls:** How your element looks and where it's positioned on the page.

**Key sections:**

* **Content:** Set the text, image, or data source for your element (varies by element type)
* **Size:** Set width and height using Fixed, Fill, or Fit options

<figure><img src="/files/SNt2WtREvjRR6UtjqadE" alt=""><figcaption></figcaption></figure>

* **Layout:** Configure alignment, padding, margins, and spacing.
  * Use the visual nine-grid alignment tool for quick positioning
  * Set gap between child elements in container elements
  * Easily set matching top/bottom or left/right margins and padding in a single input

<figure><img src="/files/CK6iOKpTH4GQDm0rUvvY" alt=""><figcaption></figcaption></figure>

* **Style:** Set colors, fonts, borders, shadows, and other visual properties
  * Properties with a blue background indicate they're overriding your element's Style settings
* **Configure:** Element-specific settings (e.g., for repeating groups, inputs, buttons)

<figure><img src="/files/YWvEoQfUI2cHiJFem8qU" alt=""><figcaption></figcaption></figure>

* **Appearance:** Additional styling options specific to the element type.
* **Background, Shadows, Border**: Add/adjust a background, shadow, or border of the element

{% hint style="info" %}
**Quick tip:** The new nine-grid alignment tool provides a visual way to position elements within their parent container — just click the grid position you want.
{% endhint %}

***

#### Interaction tab

**What it controls:** How users interact with your element and how the element behaves dynamically.

<figure><img src="/files/VyRIUrghM2s1hxgQOHFd" alt=""><figcaption></figcaption></figure>

Key sections:

* **Visibility:** Control when the element appears on the page
* **Workflows:** View and create frontend workflows (events and actions) connected to this element
  * See the count of workflows directly in the Interaction tab header
  * Add additional workflows using the + button when workflows already exist

<figure><img src="/files/0UXirnCBHDAwc9ypmk3z" alt=""><figcaption></figcaption></figure>

* **Options and Transitions:** Configure clickability, hover effects, and other interactive properties.

***

#### Conditional tab

**What it controls:** When elements appear or change based on criteria you define.

<figure><img src="/files/QClZ3FTXhafMt1bED2rT" alt=""><figcaption></figcaption></figure>

**Key features:**

* **Name your conditionals** for easy identification (instead of showing the full expression)
* **Drag and drop** to reorder conditions
* **Collapse/expand** individual conditionals to manage multiple conditions cleanly
* **Duplicate conditionals** to quickly create similar rules without starting from scratch
* **See inherited style conditionals** alongside element-specific conditionals for full visibility into what's affecting your element
* **Auto-scroll to new conditionals** newly created conditionals automatically scroll into view
* *Conditions are evaluated top-down, with later conditions overriding earlier ones when properties overlap*

***

### Examples: Bringing it all together

The best way to learn the property editor is to see it in action. Here are three practical examples which demonstrate how to use the property editor’s key features:

#### Example 1: Styling a button

1. Select your button element on the canvas
2. Go to the **Visual** tab in the property editor
3. In the **Style** section: Choose your button color using the color picker
4. In the **Layout** section: Set padding for a comfortable click area
5. Use the **nine-grid alignment tool** to position the button where you want it within its parent

<figure><img src="/files/vQbGdZ6DYbtcm1lbZ0zX" alt=""><figcaption></figcaption></figure>

***

#### Example 2: Making an element conditional

1. Select the element you want to make conditional
2. Go to the Conditional tab
3. Click + New to create a new conditional
4. Name it something clear (e.g., Show when user is logged in)
5. Define your condition (e.g., Current User is logged in)
6. Set which properties change when the condition is true (e.g., set This element is visible to yes)

<figure><img src="/files/dvc0yeqDYy5rtgyoQNqE" alt=""><figcaption></figcaption></figure>

***

#### Example 3: Adding workflows

With the **Interaction** tab, you can add element-level workflows directly without leaving the Design tab.

1. Select your element
2. Go to the Interaction tab
3. Open the Workflows section
4. Click the + button to add a new event. You'll see two common triggers:
5. An element is clicked (most common)
6. An element has an error running a workflow (enables you to customize what happens if the workflow results in an error — e.g. displaying a helpful error message)
7. After adding the event, you can edit the full workflow in the Workflow tab
8. Return to the Interaction tab in the property editor to view and modify actions in context

<figure><img src="/files/pRpibLVw1zmLC9fIwzbw" alt=""><figcaption></figcaption></figure>

***

#### Custom states

Custom states live in a dedicated Custom States panel accessible from the property editor header.

<figure><img src="/files/eiMoNFPnaqUidYkASRKI" alt=""><figcaption></figcaption></figure>

**What are custom states?**\
Custom states are temporary, element-specific data storage that help you track information and pass it between workflows without using the database. They're useful for managing UI state, form data, and dynamic interactions.

***

#### Keyboard Shortcuts to Know

| Shortcut                  | Action                                            |
| ------------------------- | ------------------------------------------------- |
| Esc                       | Close the property editor                         |
| Ctrl + D (Cmd + D on Mac) | Duplicate the selected element                    |
| Ctrl + C                  | Copy element                                      |
| Ctrl + V                  | Paste element                                     |
| Ctrl + G                  | Group selected elements                           |
| Cmd + Click               | Select the element underneath the current element |
| Cmd + /                   | Insert dynamic data into an expression            |

<br>

***

### Tips for success

* Sections are organized by frequency of use—the most common settings appear at the top, so you spend less time scrolling
* Collapse sections you rarely use to keep your workspace clean and focused
* Use drag-and-drop to quickly reorder conditionals, constraints, workflow fields, custom states, and other properties
* Changes preview immediately on the canvas, so you can see results as you build
* Use collapsible conditionals to manage complex logic without losing track of what each rule does
* Name your conditionals clearly to make them easier to identify and maintain over time
* Check the Interaction or Conditionals tab headers for a quick count of how many workflows or conditionals (respectively) are attached to an element


# Overview of the property editor beta

## Welcome to the property editor beta

We’re rolling out an early beta of the new element property editor — a redesigned, more intuitive way to work with your element settings in Bubble. This updated editor is built to make your workflow faster and clearer, whether you’re structuring a layout, adjusting styles, or fine-tuning your app’s conditional behavior.

Because this is an early beta, you may still encounter rough edges or missing functionality. We’re releasing it now so we can learn from real usage, gather your feedback, and continue improving the experience.

Use this document as a guide to understand how the new editor is organized, how to navigate its sections, and how to get the most out of the tools available today.

### The main differences

The new property editor introduces some major changes from the previous editor.

* Property editor position: The property editor is now locked in position on the right side of the screen, as opposed to being a draggable window.
* New property categories: All properties are now assorted into three main categories that are different from the old editor. More details on this can be found below.
* New look: the editor has undergone a major overhaul to make element properties easier to understand at a glance. Along with a lighter, cleaner look, you’ll notice descriptive icons added to key properties and a more streamlined layout that hides secondary options behind icons. These updates are designed to reduce visual clutter and help you quickly find what you need, while still giving you access to all the flexible options you’re used to.
* Separate window for dynamic expressions: dynamic expressions now open in their own dedicated popup, giving you more space to review and edit them comfortably.

## The element property editor

The element property editor is the main tool for configuring the elements on your page. When you double-click an element on the page or single-click it in the left-hand element tree, this draggable popup appears, displaying various fields for customization.

## The top bar

The top bar in the property editor consists of the element name, as well as a group of icons.

### Custom states

Clicking the custom states icon opens a list of all custom states stored on the selected element. From there, you can view, edit, or manage any of the associated states. If you have one or more custom states stored on the selected element, a count will be displayed next to the icon.

### Contextual menu

Clicking the three dots in the upper right corner opens up the contextual menu for the selected element.

* Notes: opens up the note panel for the selected element
* Make reusable: Creates a reusable element from the selected elements (and any elements within. This option does not delete the original element.
* Inspect: Opens the element inspector and displays a list of references to the selected element, including any associated workflows or dynamic expressions that use it.
* Rename: Allows you to rename the element by setting focus to its name in the top bar
* Replace: Allows you to replace this element with an element of a different type, such as replacing a text input with a multiline input.
* Delete: Deletes the selected element(s).
* Tutorials: Opens a mini video player with a short tutorial about the selected element type.

## The navigation bar

The navigation bar allows you to navigate through the three property categories:

* Visual: the content and look of the selected elements.
* Interaction: interactive properties of the selected elements, such as visibility, animations, transitions and workflows.
* Conditional: control how elements behave or appear in defined circumstances.

## Visual

In the visual category, you set the content and look of the selected elements.

### Content

The Content section shows the type of data an element holds, and each element supports different kinds of dynamic content.

#### Containers

Containers (such as groups) can hold data that their child elements can work with. For example, if you have a group that contains a form for editing a user profile, you can set the group’s type to User. The elements inside the group can then access that user’s data to display and update fields like their email address, name, or phone number.

Repeating groups and tables expect a list of things, meaning more than a single record of data. Each cell in the repeating group or table holds one record, which the elements inside that cell can then interact with.

#### Visual elements

Visual elements can hold different types of dynamic data, such as text, images, or dates. Each element type expects a specific kind of content. For example:

* Text: A text element expects a text string as its data source and displays that text on the page.\
  Button: A button expects a text string and/or an icon to use as its label.
* Image: An image element expects an image URL to display the associated image.

#### Input forms

Input form elements accept different kinds of user-provided data, and each input type expects a specific format. For example:

* Input (text): Accepts a text string, such as a name or email address.
* Input (number): Accepts numeric values, such as quantities or prices.
* Input (date/time): Accepts a date, time, or both, depending on the configuration.
* Dropdown: Accepts a single choice from a list of options.
* Multiselect dropdown: Accepts a list of selected options.

### Size

The Size section lets you set a static or dynamic size for an element, giving you control over how it behaves in a responsive layout. For example, you can keep an element at a fixed size like 50×50 pixels, allow it to take up a dynamic width or height within its container, or let it adapt to its content — such as a button stretching to fit its caption.

#### Containers

Containers act as both parent and child elements, meaning their size properties affect not only their own layout and behavior but also that of the elements they contain — including other nested containers.

#### Elements

All other elements’ size properties affect only the element itself, but the result can indirectly influence sibling elements. For example, when an element grows or shrinks, it can change how nearby elements wrap, align, or distribute space within the same container. In other words, these elements don’t control their siblings — but their own size changes can still shape the overall layout around them.

#### Sizing modes

When you set the size of an element, you can choose from three sizing modes. Each mode defines how the element determines its width and height in a responsive layout.

**Fixed**\
The element keeps a fixed size, defined either in pixels or as a percentage of its parent container. It won’t resize automatically based on its content or the available space.

**Fit**\
The element adjusts to the size of its content.

* Containers: The container’s size is determined by the combined size of the elements inside it.
* Elements: The element’s size is determined by its own content, such as the text in a button or the dimensions of an image.

**Fill**\
The element expands to fill the available space inside its parent container. If multiple elements use Fill, they share the available space based on their size and layout settings.

#### Maximum/minimum size

Minimum and maximum size settings let you control how far an element can shrink or grow, depending on its sizing mode. Fit and Fill elements can use both minimum and maximum values to prevent them from becoming too small or growing beyond what you intend. Fixed-size elements do not use minimum or maximum constraints, since their dimensions are defined directly.

**Adding maximum/minimum size**

The minimum and maximum size properties are added to an element’s width and height settings. When they’re available, a + icon appears next to the Width and Height fields. You can apply constraints to the width, the height, or both, depending on what the layout requires.

### Layout

The Layout section defines how an element adapts to the page, view, or container around it, and how it interacts with its sibling elements. These settings apply to both containers and the elements inside them, though the specific options vary depending on what type of element you’ve selected.

The layout properties you apply to a parent element determine which layout options are available for its child elements.

#### Containers

**Alignment**

When you have a container selected, you can select an alignment property:<br>

* Column: arranges child elements vertically (forming a column).
* Top: arranges child elements to the top (building a column from the top down).
* Center: arranges child elements to the center (building a column from the center outward).
* Bottom: arranges child elements from the bottom (building a column from the bottom up).
* Space around: distributes child elements so there is equal space on all sides, creating even spacing before, between, and after each element.
* Space between: distributes child elements so the first and last touch the container edges, with equal spacing only between the elements.
* Row: arranges child elements horizontally (forming a row).
* Left: arranges child elements to the left (building a row from left to right).
* Center: arranges child elements to the center (building a row from the center outward).
* Right: arranges child elements to the right (building a row from right to left).
* Space around: distributes child elements so there is equal space on all sides, creating even spacing before, between, and after each element.
* Space between: distributes child elements so the first and last touch the container edges, with equal spacing only between the elements.
* Align: aligns child elements to a specific area of the parent, divided into nine possible positions. This position is set individually on each child element.
* Fixed: allows you to position and size child elements with absolute pixel values.

**Spacing**

Spacing properties let you control the distance between elements and the spacing inside and outside an element’s border — known as padding and margins, respectively.<br>

* Gap: The gap property sets a fixed pixel distance between each element in a container. The direction of the gap depends on the container’s layout settings. For example, if the container uses a column layout, the gap is applied vertically between elements; in a row layout, it can apply both horizontally and vertically when elements wrap onto new lines.
* Padding: The space inside a container's border, creating distance between the container and its child elements. Think of it as an “inner cushion”.
* Margins: The space outside an element’s border, creating distance between the element and other elements around it. Think of it as an “outer cushion”

#### Elements

**Column layout**

In a column layout, you can set an element’s horizontal position to left, center, or right, or let it stretch to fill the available horizontal space. Stretching updates the element’s width to Fill.

**Row layout**

In a row layout, you can set an element’s vertical position to top, center, or bottom, or let it stretch to fill the available vertical space. Stretching updates the element’s height to Fill.

**Align layout**

In the align layout, you can position each element using a 9-point alignment grid. You can choose the horizontal and vertical positions from the dropdowns, or click directly on the grid to set the alignment.

**Fixed layout**\
In a fixed layout, element positions are set using absolute pixel values along the X and Y coordinates. You can enter these values in the property editor, or drag the element directly on the canvas to place it.

### Configure

In the Configure section, you’ll find properties that apply only to the specific type of element you’re working with.

### Style

In the Style section, you manage the style applied to the selected element. Styles apply to individual element types, so if you select multiple elements of different types, you won’t be able to manage their style together.

#### Select a style

You can select a predefined style in the dropdown to associate it with the currently selected element(s). Clicking the + symbol in the Style picker allows you to create a new style based on the properties of the selected element

#### Overriding styles

Styles can be overridden, meaning you can assign a style to an element and then change one or more of its styling properties. The element will still update when you edit the associated style, but any overridden properties will stay as you set them.

Any element with one or more overridden properties will display an Overridden label in the style selector.

**Reset style overriding**

Whenever an element has overridden style properties, a reset icon appears. Clicking it restores the element’s styling to match the original style.

#### Editing a style

To edit a style associated with an element, click the edit style icon. This opens the Style tab, and any changes you make there will apply to all elements that use that style.

#### Detaching a style

To detach an element from its style, click the detach icon. The element will keep its current styling properties but will no longer be connected to the style.

### Appearance

The appearance section lets you manage the opacity, rotation and border radius of the selected element(s).

#### Opacity

Opacity controls how transparent the selected element is. 100% opacity means the element is fully visible, while 0% opacity makes it invisible. A few details are useful to keep in mind:

* 0% opacity doesn’t hide the element — it remains present on the page and keeps its width and height, but is fully transparent.
* Opacity affects child elements as well. If you change the opacity of a container, every element inside that container will inherit that level of transparency.

#### Rotation

The rotation property rotates an element by a specified number of degrees. Note that container elements cannot be rotated.

#### Radius

This radius property allows you to set the corner roundness of your selected element. You can do it individually or in combination which is helpful if you have the same radius applied to all corners.

### Border

The border section lets you add a border to the selected element(s) and adjust its appearance. To add a border, click the + icon.

#### Border style

The border style dropdown lets you choose the appearance of the border, such as solid, dotted, or dashed. If two or more individual borders have different border styles, the main border style property will display as mixed.

#### Border width

The border width property allows you to set the width of the border in a pixel value. If two or more individual borders have different width values, the main border width property will display as mixed.

#### Individual borders

The Individual borders property lets you configure each of an element’s four borders separately, including their style, width, color, and radius.

To edit individual borders, click the individual borders icon. This opens a popup where you can adjust the settings for each border.

#### Border color and opacity

The border color/opacity property lets you assign a color or color variable to an element’s border and adjust the opacity of that color. If two or more individual borders have different colors/opacity, the main color property will display as mixed.

### Background

The background section allows you to adjust the background style of the selected element(s). There are three different background styles:

* Color: assigns a static color or color variable to the element(s). You can also adjust the opacity of the color.
* Gradient: assigns a linear or radial gradient with a start and end color and opacity. You also have the option of adding an intermediate color by clicking the + symbol.
* Image: assigns an image file as a background and lets you adjust how that image appears, including its width, alignment, repetition, and cropping. You can also set a background color for any part of the element not covered by the image or video, if it’s a page element.

### Text

For elements that display text, such as text elements or buttons, the Text section lets you adjust the text-related properties of that element.

* Font: sets the font (or font variable) for the element.
* Font weight: sets the font weight property, which controls how thick or bold the text appears.
* Font size: sets the font size in pixels.
* Alignment: sets the text alignment (left/center/right).
* Text properties:
* Bold
* Italic
* Underline
* More properties
* Word spacing
* Line spacing
* Letter spacing
* Text shadow

### Shadow

The shadow section lets you assign and adjust a shadow on the selected element(s).

* Position (X and Y): Sets the shadow’s horizontal and vertical offset in pixels. Positive values move the shadow right or down; negative values move it left or up.
* Blur: Controls how soft or sharp the shadow appears. Higher values create a more diffused, softer shadow; lower values create a sharper edge.

Spread: Expands or contracts the size of the shadow. Positive values make the shadow larger and more pronounced; negative values pull it inward, making it tighter around the element.

## Interaction

Interaction settings define how an element behaves when someone uses your app. These controls let you manage an element’s visibility, determine whether it can be clicked or scrolled, and attach transitions or workflows that respond to user actions. Together, they shape how the element feels and functions from the end user’s point of view.

### Visibility

Visibility controls whether an element appears when the page first loads, whether it collapses when hidden, and how it behaves when its content exceeds its available space.

#### Visible on page load

If this property is enabled, the element is visible when the page loads. If it’s disabled, the element is hidden by default, and it will collapse on load if that option is enabled for the element. This setting only affects run-mode; in the editor, visibility is controlled through the element tree.

#### Allow vertical scrolling

When enabled, the element becomes scrollable whenever its content is taller than the space available. This lets users scroll through the content inside the element without affecting the rest of the page.

### Options

#### Make clickable

This property determines whether the element can be clicked by the user. It’s especially useful for elements that have an Element is clicked workflow attached. When disabled, the element won’t respond to click interactions.

Keep in mind that this setting only affects the user experience. It should not be relied on as a security measure, since it can be modified on the client side. To ensure workflows run only for the right users, add server-side conditions that prevent unauthorized access.

Additional options are available for specific element types.

### Transitions

The Transitions settings let you add animated changes to an element’s visual properties, applying them gradually as opposed to instantly. When a property such as color, shadow, border, or size updates—whether through a workflow or a conditional statement—the transition defines how quickly and smoothly that change takes place.

#### Transition types

Transition types define how an animated change progresses over time. They control the “feel” of a transition—whether it starts gently, ends abruptly, or moves at a constant pace.

* Ease: A smooth, natural-feeling animation that starts slowly, speeds up, and then slows down again near the end.
* ease-in: Begins slowly and then accelerates toward the end, creating a soft start and a quicker finish.
* Ease-out: Starts quickly and slows down as it completes, giving the transition a gentle landing.
* Ease-in-out: Combines both behaviors: a slow start, faster middle, and slow finish for an even, balanced motion.
* Linear: Moves at a constant speed from start to finish with no acceleration or deceleration. Good for mechanical or precise animations.
* Step-start: Jumps immediately to the end state at the start of the transition, with no gradual change.
* Step-end: Holds the initial state for the duration of the transition, then jumps to the final state at the very end.

### Workflows

The Workflows section lists all workflows connected to the selected element and lets you add new ones. This section appears only when a single element is selected and acts as an accelerator to the Workflow tab.

## Conditionals

The Conditional section lets you define rules that change an element’s appearance or behavior when certain expressions evaluate to true.

Conditionals follow a When → Then structure. The When portion is a dynamic expression that must evaluate to yes. The then portion defines which properties should change while that condition is true. The dynamic expression is evaluated continuously, meaning it reacts instantly to changes in your app’s data or states.

### Adding a condition

To add a condition, click the + symbol in the upper right corner of the section. To expand all conditions, click the Expand all icon.

### Moving conditions

You can reorder conditions by dragging them—hover a condition to reveal the drag handle—or by using Move to top and Move to bottom in the condition’s contextual menu.

Conditionals are evaluated from top to bottom. If two conditions modify the same property, the condition lower in the list overrides any conflicting settings from the ones above it.

### Previewing conditions

You can preview how a condition will look when its expression evaluates to yes by clicking the Preview icon on the relevant properties. This affects only the editor—it doesn’t enable or disable the condition. It simply gives you a visual preview so you can confirm that the properties produce the result you expect.

### Expand/Collapse conditions

This quick action available in the title header via an icon button allows you to show all of the conditions in this tab expanded or collapsed. The expanded condition shows the expression and related changed properties in complete view.

\ <br>


# Navigating the Redesigned Property Editor

Last updated: July 2026

The property editor has undergone a major redesign. This guide will help you understand what’s changed, why we made these updates, and how to get up to speed quickly.

### What changed

* **Reorganized for clarity:** Properties are grouped into logical tabs (Visual, Interaction, Conditional) with collapsible sections. You can drag-and-drop to reorder conditionals, constraints, workflow fields, custom states, and more. And you can now see the count of workflows, conditionals, and custom states on each element, so there's less scrolling, less tab-switching, and more visibility into how your elements are set up.
* **New tools shaped by community feedback:** Global expressions, workflow editing, agent entry points, searchable color picker, expandable dynamic expressions, and more.
* **Built on a modern foundation:** We've migrated to SolidJS and a new design system, which means faster bug fixes, quicker feature releases, and the groundwork for efficient AI capabilities in the editor.

### Recent updates we’re excited about

*Only available in the new property editor.*

#### Dark mode editor

* A long-requested update. Toggle it on from the moon icon at the bottom left of your editor sidebar.

<figure><img src="/files/CwI5NyWjAWuxCXa7rMcc" alt=""><figcaption></figcaption></figure>

#### Global saved expressions

* Another highly requested update that lets you define an expression once and reuse it across your app.

<figure><img src="/files/4fkAIoTyqCMYLVNSc2uA" alt=""><figcaption></figcaption></figure>

#### Add and edit workflow events and actions

* Reorder and edit workflow actions inline without switching tabs.[\[Screenshot\]](https://drive.google.com/file/d/1wzuOSnVQgA4-zlsR7PZT76aZAN0nLERr/view?usp=sharing)

<figure><img src="/files/cfIG43mTmnNoS8w1wRHZ" alt=""><figcaption></figcaption></figure>

#### Use the Agent to build dynamic expressions

* Use the Agent to build dynamic expressions with natural language

<figure><img src="/files/rMRON260O85uMWuAwZJz" alt=""><figcaption></figcaption></figure>

### What’s changed

#### Light mode / Pinned PE

<img src="/files/jtBK0z7hL4sm2I0hI0N2" alt="" height="352" width="624">

| Before                                      | After                                            |
| ------------------------------------------- | ------------------------------------------------ |
| Displayed in dark mode only                 | Updated UI for dark mode or light mode           |
| Positioned as a floating interface element  | Integrated as a pinned element within the layout |
| Visually separated from the primary layout  | More clearly anchored within the page structure  |
| Less predictable placement during scrolling | Provides a consistent and predictable location   |
| Difficult for our engineers to improve      | Creates room for future updates to the canvas    |

#### Same tabs, new names

<img src="/files/63BGK5Zcy0mXE3Zzj69o" alt="" height="352" width="624">

| Before                                | After                                |
| ------------------------------------- | ------------------------------------ |
| Appearance, Layout, and Conditional   | Visual, Interaction, and Conditional |
| Emphasized styling and page structure | Emphasizes user intent and behavior  |

#### Reorganized and collapsible sections

<img src="/files/ITojkyEz8btcxlK0RRxD" alt="" height="352" width="624">

| Before                                             | After                                                                    |
| -------------------------------------------------- | ------------------------------------------------------------------------ |
| Properties were presented in long, static lists    | Properties are grouped by purpose                                        |
| Related settings were spread across multiple areas | Logical sectioning reflects how builders use each element                |
| Sections could not be collapsed                    | Sections can be collapsed to reduce visual noise                         |
| Visual scanning required frequent scrolling        | Frequently edited settings are surfaced earlier with new intuitive icons |

#### More control of your editor

Resize as you need it

<img src="/files/Br2T0qPuiU018qGaRqxM" alt="" height="352" width="624">

Show/hide your editor as you build

<img src="/files/kPmdUZQSnhsn9B4ehuF7" alt="" height="352" width="624">

Unpin your elements tree when you want

<img src="/files/Y0guFmgvaMFOJWhb5Ih5" alt="" height="352" width="624">

**Size and layout properties have a new home**

<img src="/files/smoNMNApG6atkpueicS1" alt="" height="352" width="624">

\ <br>

| Before                                                     | After                                                        |
| ---------------------------------------------------------- | ------------------------------------------------------------ |
| Sizing and layout settings were fragmented                 | All size and layout attributes are consolidated into one tab |
| Width and height controls lived alongside unrelated fields | Layout controls live together in one section                 |
| Responsive configuration required context-switching        | Responsive behaviors can be adjusted in one pass             |

<br>

**Responsive building made easier**

<img src="/files/vqDzxrxnptMpLg1hX3Ls" alt="" height="352" width="624">

&#x20;

<img src="/files/tuv3O1vYumtSvgnnuf1M" alt="" height="352" width="624">

&#x20;

<img src="/files/q9IU3aiPRYSZRS4KFsiL" alt="" height="352" width="624">

<br>

| Responsive behavior had to be configured manually | <p>Padding and margin behavior is standardized</p><p><br></p> |
| ------------------------------------------------- | ------------------------------------------------------------- |
| Layout tuning required additional setup           | Fixed, fill, and fit dropdown replaces legacy checkboxes      |
| Standard properties for configuring alignment     | Modern, visual nine-grid view of element’s alignment          |
| Manually add in spacing for all sides             | Easily add or edit spacing options for vertical or horizontal |

#### Drag-and-drop conditionals, custom states, constraints, and workflow fields

<img src="/files/ZDL2VlC64wSlveWlT7Xq" alt="" height="352" width="624">

| Most properties were visible at once with little organizational structure            | Expand or collapse conditionals to get more space or more cards in view             |
| ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
| Reordering required multiple clicks                                                  | Drag and drop re-ordering is properly supported                                     |
| Conditional properties can get lengthy and take up space                             | Conditionals can be collapsed by clicking in the header row for more breathing room |
| Creating a new conditional adds it to the bottom of the tab, potentially out of view | Conditionals scroll into view upon creation                                         |

#### Conditional names

<img src="/files/J4Ay6d7kQuXhZ64MXsvj" alt="" height="352" width="624">

| Before                                                  | After                                               |
| ------------------------------------------------------- | --------------------------------------------------- |
| Conditionals displayed the full expression as the label | Conditional names are customizable                  |
| Identifying logic required reading the entire rule      | Logic can be understood without expanding each rule |

#### Add/edit workflows from the new property editor

<img src="/files/cwM3jiHoZPMbCzgsiMtb" alt="" height="352" width="624">

| Before                                                                              | After                                                                                    |
| ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| Adding workflows to elements while designing required switching to a new editor tab | Frontend workflows are visible in-context                                                |
| No ability to see workflows from the Property Editor                                | New ability to create and view multiple workflows from the property editor at once.      |
| No ability to edit workflow actions from the Property Editor                        | New ability to reorder, add, and edit workflow actions directly from the Property Editor |

**Edit fields and operators in expressions without deleting them**

<img src="/files/y3qnSw0452OcDAzyN8Cg" alt="" height="352" width="624">

| Before                                                                                                                                           | After                                                                                                                                                                                         |
| ------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Editing fields or operators meant deleting the entire constraint and rebuilding it from scratch which was slow, disruptive, and easy to mess up. | Edit fields and operators directly in place. No more deleting and rebuilding constraints. Make quick tweaks without breaking your flow, so iterating on logic is faster and less error-prone. |

#### New custom states panel

<img src="/files/1jmxe6OMoPtnAvyop4Ze" alt="" height="352" width="624">

&#x20;

<img src="/files/DXnFUkiE9XezouohFYFz" alt="" height="352" width="624">

| Before                                                                         | After                                                         |
| ------------------------------------------------------------------------------ | ------------------------------------------------------------- |
| Custom states were hidden behind an info icon                                  | Custom states have a dedicated panel                          |
| Discoverability depended on prior knowledge                                    | Access requires no navigation                                 |
| Required to click into action in order to see if custom states have been added | See if there are custom states added on an element right away |

#### New color picker

<img src="/files/NU1LciWp8pNnjs1cttIf" alt="" height="352" width="624">

| Before                                                    | After                                   |
| --------------------------------------------------------- | --------------------------------------- |
| Colors were entered manually or selected from small lists | Full variable list appears in one view  |
| Variable discovery was limited                            | Searchable variable lookup              |
| Searching required manually scrolling through             | Live previews update immediately        |
| Navigating to the styles tab to edit a color variable     | Directly edit while you’re mid-building |

#### Complete list of property/label updates<br>

Simplified properties

| Element(s) or Section                                                                                                                                                            | Old name                                                                                | New name                                                        |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| Background Section > Video Picker                                                                                                                                                | <p>Play video silent</p><p><br></p>                                                     | <p>Play with sound</p><p><br></p>                               |
| AppBarButton, Button, FloatingGroup, Group, HorizontalListItem, Icon,Image,SelectableList, SelectableListItem,Shape, Sheet, ShortList, ShortListItem, TableCrossAxis, Text, Link | This element isn't clickable                                                            | <p>Make clickable/interactive</p><p><br></p>                    |
| Input, SearchBox                                                                                                                                                                 | Prevent "enter" key from submitting                                                     | <p>Submit on 'Enter'</p><p><br></p>                             |
| Popup                                                                                                                                                                            | This popup can't be closed by pressing 'Esc'                                            | <p>Close by pressing 'Esc'</p><p><br></p>                       |
| Text                                                                                                                                                                             | Do not apply bb-code                                                                    | BBCode applies                                                  |
| WebView                                                                                                                                                                          | Disable Zooming                                                                         | Enable zooming                                                  |
| Text > Options                                                                                                                                                                   | Do not apply bb-code                                                                    | BBCode applies                                                  |
| Page                                                                                                                                                                             | Apply gap spacing between elements                                                      | Row gap or Column Gap                                           |
| Input                                                                                                                                                                            | Limit the number of characters                                                          | Max number                                                      |
| <p>Mobile > App Bar</p><p>Containers</p>                                                                                                                                         | <p>Override back button label</p><p>Allow vertical scrolling when content overflows</p> | <p>Override back…</p><p>Allow vertical scrolling</p><p><br></p> |

Label changes<br>

| Element(s) or Section          | Old label                                    | New label                                       |
| ------------------------------ | -------------------------------------------- | ----------------------------------------------- |
| All elements > Visibility      | This element is visible on page load         | <p>Visible on page load (toggle)</p><p><br></p> |
| Icon> Options                  | <p>Make the icon spin</p><p><br></p>         | <p>Make icon rotate continuously</p><p><br></p> |
| Alert > Configure              | Position the alert at the top                | Position at top of page                         |
| Video >Options                 | Play the video automatically on page load    | <p>Autoplay on load</p><p><br></p>              |
| Video > Options                | Replay video when over                       | Loop on repeat                                  |
| Map                            | Auto-close when another is clicked on        | Auto-close when another is clicked              |
| HTML                           | Wait to render this element until is visible | Wait to render until visible                    |
| HTML                           | Display as an iframe                         | Display as iframe                               |
| Repeating Group                | Show partial list on last page if needed     | Show partial list on last page                  |
| Input                          | Max number                                   | Max characters                                  |
| Form elements                  | The input should not be empty                | Make required                                   |
| Form elements                  | This input is disabled                       | Make disabled                                   |
| Picture uploader               | Make this file private                       | Make private                                    |
| Picture uploader               | Limit image size before upload               | Resize images over 800px x 600px                |
| Mobile sheet                   | Drag handle                                  | Include drag handle                             |
| Form elements                  | Enable auto-binding                          | Auto-binding                                    |
| Elements                       | Make this element fixed-width                | Dropdown with Fixed, Fill, and Fit options      |
| Make this element fixed-height | <p><br></p>                                  | <p><br></p>                                     |
| Fit width to content           | <p><br></p>                                  | <p><br></p>                                     |
| Fit height to content          | <p><br></p>                                  | <p><br></p>                                     |
| Make First / Make Last         | Text labels on selected elements             | Arrows instead of text                          |
| Previous / Next                | Text labels on selected elements             | Left/up right/down arrows                       |

#### Keyboard shortcuts

| Shortcut          | Action                                                      |
| ----------------- | ----------------------------------------------------------- |
| Ctrl + P          | Preview your app. Same as clicking **PREVIEW**.             |
| Ctrl + T          | Switch between **Design**, **Workflow**, and **Data** tabs. |
| Cmd + Click       | Select the element under the current element.               |
| Cmd + Drag        | Resize the current element symmetrically.                   |
| Shift + Drag      | Resize and keep proportions constant.                       |
| Ctrl + C          | Copy the current element, action, or event.                 |
| Ctrl + V          | Paste the current element, action, or event.                |
| Ctrl + X          | Cut the current element, action, or event.                  |
| Ctrl + Shift + C  | Copy the current element’s formatting.                      |
| Ctrl + Shift + V  | Paste formatting to the current element.                    |
| Ctrl + D          | Duplicate the current element.                              |
| Cmd + K           | Show or edit the workflow for the current element.          |
| Ctrl + A          | Select all elements on the page.                            |
| Ctrl + G          | Group selected elements into a new group.                   |
| Ctrl + E          | Center the current element relative to its parent.          |
| Ctrl + B          | Make text bold.                                             |
| Ctrl + I          | Make text italic.                                           |
| Ctrl + U          | Underline text.                                             |
| Cmd + /           | Insert dynamic data into an expression.                     |
| Esc               | Close the property editor.                                  |
| Option + Cmd + \[ | Make first.                                                 |
| Option + Cmd + ]  | Make last.                                                  |
| Cmd + \[          | Move left or up.                                            |
| Cmd + ]           | Move right or down.                                         |


# Design tab

{% hint style="info" %}
Bubble introduced a new responsive engine in 2021. If you have an older app, you may still see the legacy engine and its properties. We recommend that you update your app to the new responsive engine. The documentation for the legacy responsive engine is available in the link below.

Article series: [Responsive design (legacy)](/help-guides/design/responsive-design/legacy-articles)
{% endhint %}

## The UI builder

The UI Builder is your workspace for crafting and tweaking visual components on your app's pages.

Using Bubble's intuitive WYSIWYG[^1] editor, you can visualize how elements will appear to your app's end-users. As you design, keep in mind that elements[^2], including containers[^3] and visual elements, come with layout settings to ensure responsive designs.

By default, every Bubble app includes certain pages[^4] such as the 404 page, displayed when users access a non-existent URL on your app, and the reset\_pw page for password resets. To manage multiple pages or dive into reusable components, navigate using the dropdown on the top left of the top bar.

**Article series:** [Design](/help-guides/design)

## The element tree

The element tree displays all the components present on the page. Indentation indicates a hierarchy, where an element resides within a container. Note that elements can be nested multiple times, like groups inside groups, and so on.

<figure><img src="/files/ntBrRs6NXT9pVO7O4GtY" alt=""><figcaption><p>The element tree shows all elements on the page in a hierarchy.</p></figcaption></figure>

**Article:** [The element tree](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/design-tab/the-element-tree)

## The element pallet

The element pallet lists all the elements that you can place on the page. Bubble comes with a long list of different elements, and more can be added with plugins.

<figure><img src="/files/wWIByBB9TiUSRUi7OtWp" alt=""><figcaption><p>The element pallet contains all the different types of elements you can add to the page.</p></figcaption></figure>

Elements are divided into categories for easy navigation:

* Visual elements
* Containers
* Input forms
* Reusable elements

You can also use the search bar to quickly find the element you are looking for. To place an element on the page:

* Drag the element from the element pallet
* Click the element in the element pallet and then "draw" it on the page

**Article series:** [Elements](/help-guides/design/elements)

## Responsive

You can switch the Design tab from UI Builder mode to the Responsive mode. Responsive settings refer to the configuration of how the page is displayed on varying screen widths - how the layout 'responds' to changing widths. As such, the Responsive tab allows you to quickly get a sense of how your page layout will respond depending on the screen size without previewing it live.

<figure><img src="/files/8UGhzn35q4yx2XXrKk6Z" alt=""><figcaption><p>Switching between <em>Design</em> mode and <em>Responsive</em> mode lets you tweak the page's responsive behavior.</p></figcaption></figure>

At the top of the left flyout of this sub-tab, there are shortcuts to snap the page width to different common screen widths, e.g. mobile portrait, mobile landscape, tablet and laptop. You can also change the page width to any arbitrary width using the ruler at the top.

The responsive type will also show you the current size of the selected element as well as its parent.

**Article series:** [Responsive design](/help-guides/design/responsive-design)

## Other ways to learn

<details>

<summary>Related articles</summary>

**Design**\
This section covers how to place elements on the page, tweak their settings and layout properties and how the different element types work.\
Article series: [Design](/help-guides/design)\
\
**Responsive design**\
This section covers how you set your app up to behave on different devices and screen sizes. The core reference entry covers the different responsive properties available.\
Article series: [Responsive design](/help-guides/design/responsive-design)\
Reference: [Responsive properties](/core-resources/elements/responsive-properties)<br>

**Previewing your app**\
For every change you make, you can preview your app in the Development Environment.\
Article: [Previewing your app](/help-guides/getting-started/navigating-the-bubble-editor/previewing-your-app)

</details>

<details>

<summary>Core reference</summary>

* [The design tab](/core-resources/bubbles-interface/design-tab)

</details>

[^1]: "WYSIWYG" stands for "What You See Is What You Get."\ <br>

    In the context of the Bubble editor, it means that the layout and elements you arrange and design in the editor will look and behave similarly when previewed or live.

    Essentially, it allows you to visually build and see the app as users will experience it.

[^2]: *Elements* are the visual components or building blocks used to design and structure the user interface of a Bubble app.

    They can range from basic items like text and buttons to more complex widgets like sliders and maps.

    Article series: [Elements](/help-guides/design/elements)

    Reference: [Element settings](/core-resources/elements)

    <br>

[^3]: Containers are used to contain elements and control how they behave on the page.\ <br>

    You can place elements inside of a container, making it the parent and the element(s) its children. The element tree will display the hierarchy of the parent-child relationship.

    Article series: [Containers](/help-guides/design/elements/web-app/containers)

    Reference: [Container settings](/core-resources/elements/containers)

    Reference: [Container layout settings](/core-resources/elements/container-layout-types)

[^4]: The *page* is the canvas on which you place input elements, text, images, icons, videos and everything else that your users need to make use of your app. Every page has a unique URL.

    Article: [The page](/help-guides/design/elements/web-app/the-page)


# The element tree

The element tree is a panel in the Bubble editor that shows every [element](/help-guides/design/elements) on the current [page](/help-guides/design/elements/web-app/the-page) or [view](/help-guides/design/elements/ios-and-android-app/the-view) in a hierarchical view. It's one of the most useful tools for navigating, understanding, and organizing a Bubble app, especially as designs grow more complex.

The tree gives you a bird's-eye view of the entire hierarchy, from the top-level page or view down to the smallest nested elements. Selecting an element in the tree also selects it on the canvas, making it easy to jump between structural navigation and hands-on design work.

## What the element tree shows

Every element you've added to the current page or view appears in the tree, nested according to its position in the hierarchy. Container elements can be expanded and collapsed to show or hide their children, letting you focus on specific sections of the design.

<figure><img src="/files/VCqxOK0Yf6VZ2xF8PAje" alt="Bubble&#x27;s element tree."><figcaption><p>The element tree gives you a complete overview of all the elements on the page or view.</p></figcaption></figure>

The tree updates in real time. As you add, move, delete, or rename elements, the tree reflects those changes immediately.

## Layers

Elements in the tree are organized into distinct layers, based on how they interact with the page or view. Each layer serves a different purpose and appears in its own section.

<figure><img src="/files/sagbDa3XzyxN0OiqKa14" alt=""><figcaption></figcaption></figure>

### Overlays

The *Overlays* layer holds elements that appear on top of the main content, such as popups, floating groups, and sheets. These elements sit above the main layout and are typically shown and hidden through workflows.

Grouping overlays into their own section keeps them separate from the main design, so they're easy to find and manage without cluttering the rest of the tree.

### Navigation

On native mobile, the *Navigation* layer holds the app bar and tab bar. These are pseudo child elements that appear when enabled in the view's properties. They handle the standard navigation patterns of the app and are edited from within the view.

### Layers

The *Layers* section holds the main content of the page or view: the elements users interact with as part of the normal flow. Groups, buttons, inputs, lists, and other elements all live here.

Organizing elements into these layers makes it easier to understand a page or view at a glance. The main content is separate from the overlays and navigation, and each part can be edited without visually competing with the others.

## Element tree modes

The element tree has two modes. You can either show all elements on the page or view in a tree view, with parents and children, or filter it to show only elements that are hidden on page load.

<figure><img src="/files/ENziVCDWBm9yQNk7JxiL" alt=""><figcaption></figcaption></figure>

## Working with the element tree

The element tree is more than just a display. It's an interactive tool for organizing and navigating your design.

### Creating elements

You can create an element by dragging it from the element palette directly into the element tree.

### Selecting elements

Click any element in the tree to select it. The corresponding element on the canvas is highlighted, and its properties open in the property editor. This is often the fastest way to select an element that's hidden behind other elements, deep inside a container, or otherwise hard to reach on the canvas.

### Rearranging elements

You can drag and drop elements in the tree to move them between containers or reorder them within a container. This is especially useful for restructuring a design precisely. which can be more difficult on the design canvas.

<figure><img src="/files/L5o2cRM0XQmFgUOCWM4I" alt="" width="269"><figcaption></figcaption></figure>

### Expanding and collapsing containers

Container elements have arrows next to their names in the tree. Click them to expand or collapse the container's children. Collapsing containers you're not currently working with helps keep the tree focused and easier to scan.

### Renaming elements

Rename elements directly in the tree by double-clicking their name. Clear, descriptive names make the tree easier to navigate as your design grows.

### Searching

The search field at the top of the tree lets you filter elements by name. This is useful for large pages with many elements, or for quickly finding a specific element in a deeply nested design. Click the X symbol to clear the filter.

{% hint style="info" %}
**Advanced search:** For a more advanced search, you can also use the [search tool](/help-guides/getting-started/navigating-the-bubble-editor/tools/the-search-tool).
{% endhint %}

<figure><img src="/files/RH0Qu1VCxUMHAtI1CsJa" alt=""><figcaption><p>You can quickly find the elements you're looking for by using the search bar at the top of the element tree.</p></figcaption></figure>

### Locking an element

You can lock an element by hovering it in the element tree and clicking on the lock symbol. When locked, the element can't be dragged on the canvas. The element can still be dragged in the element tree.

<figure><img src="/files/BGcYVHzinYwmtM8mAH10" alt=""><figcaption></figcaption></figure>

### Visibility

The tree includes visual cues for each element's visibility state. Hover the element and inspect or click the eye symbol to toggle visibility.

{% hint style="info" %}
**Visibility:** Toggling visibility in the element tree affects the editor only. It doesn't change how elements appear in your live app.
{% endhint %}

<figure><img src="/files/dn1wrYGXqV6rQCMOITog" alt=""><figcaption></figcaption></figure>

## Tips for keeping the tree readable

* **Name your containers.** A tree full of *Group A*, *Group B*, and *Group C* is much harder to navigate than one with names like *Group header*, *Group hero section*, and *Group footer*.
* **Keep nesting deliberate.** Deep hierarchies can be hard to reason about. Use nesting when it serves the layout, not by default.
* **Collapse what you're not working on.** Long trees benefit from collapsing containers you don't need to see. It keeps the visible tree focused on the current task.
* **Take advantage of the layer separation.** Overlays and navigation elements stay separate from the main content, which keeps the tree tidy. Design your app to lean into that separation.
* **Use search on large pages.** As your app grows, the search field becomes one of the most valuable ways to jump directly to the element you need.

## Why the element tree matters

The element tree is often described as a navigation tool, but its real value goes deeper. It gives you a clear model of how your design is structured, which helps you:

* **Understand data flow.** Seeing the parent-child relationships makes it easier to plan how data moves through containers and their children.
* **Diagnose layout issues.** When something doesn't look right on the canvas, checking the tree often reveals a container that's positioned unexpectedly or a nesting issue that's hard to spot visually.
* **Refactor confidently.** Moving elements around, restructuring containers, and converting groups into reusable elements all become easier when you can see the full structure at a glance.
* **Communicate with collaborators.** A well-organized tree makes your app easier for teammates to understand, especially when they're navigating parts of the design they didn't build themselves.

The element tree is one of those tools you learn to rely on more the more you build. Investing time in keeping it clean and organized pays off across every part of your app.


# The property editor

The property editor is the main tool for configuring the elements on your page. When you double-click an element on the page or single-click it in the left-hand element tree, this draggable popup appears, displaying various fields for customization.

## The top bar

The top bar in the property editor consists of the element name, as well as a group of icons. The X on the right-hand side closes the element inspector.

### Element name

The left-hand side shows the name of the currently selected element. Bubble will automatically generate a name that consists of the element type (i.e. *Input* or *Button*) and an alphabetical sequence (i.e. *A, B, C*).

This is an editable field where you can assign any name you want to an element:

<figure><img src="/files/hbRlUxu3usxgQLFiU07f" alt=""><figcaption><p>You can assign any name you want to an element by clicking its name in the top bar of the property editor</p></figcaption></figure>

### Contextual video

The first icon shows a play symbol. Clicking this will expand a video tutorial relevant for the currently selected element. Note that not all elements have an associated tutorial – if the icon is not showing it means that there's no video available.

<figure><img src="/files/K8XnqH2q3pHsztgm4dPc" alt=""><figcaption></figcaption></figure>

### Element inspector

The second icon shows an information symbol. Clicking this opens the *element inspector*, which lets you inspect different ways in which this element interacts with the rest of your app.

<figure><img src="/files/QzFTuF8w5Qoet6KmJxL2" alt=""><figcaption></figcaption></figure>

In this case we have selected a button.

#### Custom states

This shows all the [custom states](#user-content-fn-1)[^1] that are stored on the current element, as well as their type and default value. From here, add new custom states, modify their names and default values, or remove them.

#### Events

This section will list all the [*events*](#user-content-fn-2)[^2] that are connected to this element. In the example in the above screenshot, we have one connected event (*Button A is clicked)*. Clicking it will take you to the workflow editor and the associated workflow.

#### Actions

This section shows the [*actions*](#user-content-fn-3)[^3] that are connected to this element. In the example in the above screenshot we have one connected action (*Show button A*). Clicking it will take you to the workflow editor and the associated action.

#### Elements

This section shows the elements that are referencing the currently selected element in a dynamic expression. It will also list the parent container elements of those elements.

### Comments

The icon showing a speech bubble icon will expand the [comment editor](#user-content-fn-4)[^4] for that element. This lets you take notes relevant for the currently selected element.

<figure><img src="/files/z2uawecSMzXsh4B4tCPK" alt="" width="375"><figcaption></figcaption></figure>

## Tabs

Right below the top bar of the you'll find the property editor tabs. Each one represents a category of properties that can be applied to the selected element.

<figure><img src="/files/1CThT315s73hXMLrCqY0" alt="" width="375"><figcaption></figcaption></figure>

### Appearance

The *Appearance* tab is where you set up the visual appearance of the element, such as its background style, borders, font, shadow, transparency and transitions. This is also where you can apply or detach [*Styles*](#user-content-fn-5)[^5].

For a detailed list and explanations of all appearance properties, refer to the core reference entry below:

Reference: [Styling properties](/core-resources/elements/styling-properties)

### Layout

The *Layout* tab is where you set up the responsive properties of an element, determining how the element behaves in relation to other elements on a responsive page. This includes properties like width/height, padding/margins and alignment.

For a detailed list and explanations of all responsive properties, refer to the core reference entry below. To learn more about responsive design, you can also check out the article.

Reference: [Responsive properties](/core-resources/elements/responsive-properties)\
Article series: [Responsive design](/help-guides/design/responsive-design)

### Conditional

The *Conditional* tab is where you set up [*conditional expressions*](#user-content-fn-6)[^6] that control the visibility and styling of the selected element. For example, you can make a button change color based on whether the user is logged in or not.

To learn more about how to set up dynamic expressions and use them in conditions, see the articles below:

Article: [Dynamic expressions](/help-guides/logic/dynamic-expressions)\
Article: [Conditions](/help-guides/logic/conditions)

[^1]: *Custom states* are variables that you can save on any element on the page. They let you store data temporarily that is reset when the page is reloaded.\
    \
    Article: [Custom states](/help-guides/data/temporary-data/custom-states)

[^2]: An *event* is anything that tells a workflow to run, such as a button being clicked or an input's value being changed.\
    \
    Article: Events\
    Article series: Workflows

[^3]: *Actions* are the operations that take place when a workflow is triggered by an event.\
    \
    Article: [Actions](/help-guides/logic/workflows/actions)

[^4]: You can add *comments* to different parts of your app, such as elements and workflows, to help you keep track of what the different pages, key actions, styles and data types are about.\
    \
    Article: [Commenting](/help-guides/maintaining-an-application/commenting)

[^5]: Styles are predefined sets of visual properties (like colors, fonts, and borders) that can be applied to elements. By modifying a single style, you can effortlessly restyle all related elements.\
    \
    Article: [Styles](/help-guides/design/variables-and-styles/styles)

[^6]: *Dynamic expressions* are like "live" formulas that update in real-time based on user input, database updates and other changes in your app.\
    \
    Conditional expressions can update the styling of an element based on the outcome of the dynamic expression.\
    \
    Article: Dynamic expressions\
    Article: Conditions


# Workflow tab

{% hint style="info" %}
This article provides a general overview of the Workflow tab. For a deeper dive into how the workflow editor functions and how to build with it effectively, we recommend exploring the article series linked below.

Article series: [Workflows](/help-guides/logic/workflows)
{% endhint %}

The workflow[^1] tab is where you set up events and actions to instruct Bubble what to do as the user interacts with your app, such as what happens when a button is clicked.

<figure><img src="/files/aeFo41sXEibLZKEYuQOA" alt=""><figcaption></figcaption></figure>

A workflow is a combination of an *event* (which triggers the workflow) and one or more *actions* (which tells Bubble what to do when that event occurs). For example, the click of a button (event) can lead to creating a new thing in the database (action).

<figure><img src="/files/hSA6ohISkg2sxudIaUFe" alt=""><figcaption></figcaption></figure>

Workflows are displayed as squares, and clicking one will display the actions within that workflow.

Article series: [Workflows](/help-guides/logic/workflows)

## Workflow inspector

Whenever you select an event or an action, the workflow inspector will be displayed. This shows the relevant settings for the object you have selected.

<figure><img src="/files/ezrf6keXfV1sxchVWFR7" alt=""><figcaption></figcaption></figure>

In the above example we have selected a [*Go to page*](#user-content-fn-2)[^2] action, and the workflow inspector shows the relevant settings available for this action. In this case, this step in the workflow (marked as *Step 2*) will send the user to the index page.

## Other ways to learn

<details>

<summary>Related articles</summary>

* Article series: [Workflows](/help-guides/logic/workflows)
  * Article: [Events](/help-guides/logic/workflows/events)
  * Article: [Actions](/help-guides/logic/workflows/actions)

</details>

<details>

<summary>Core reference</summary>

* [The workflow tab](/core-resources/bubbles-interface/workflow-tab)
* [List of events](/core-resources/events)
* [List of actions](/core-resources/actions)

</details>

[^1]: A *workflow* is the combination of an *event* that triggers one or more *actions*.\
    \
    They are how you instruct Bubble to respond to what the user does.

    \
    Article: [Workflows](/help-guides/logic/workflows)

[^2]: The *Go to page* action redirects the user, along with relevant data, to a specific page in your app.

    Reference: [Go to page action](/core-resources/actions/navigation#go-to-page)


# Data tab

The Data tab is where you manage different types of data in your app, such as the database, [uploaded files](#user-content-fn-1)[^1] and [option sets](#user-content-fn-2)[^2].

Article series: [Data](/help-guides/data)

## Data types

In the Data types section you manage the different [data types](#user-content-fn-3)[^3] in your app. You can create, change and delete data types and add fields[^4] as needed.

Article series: [The database](/help-guides/data/the-database)

## Privacy

The Privacy section is where you set up privacy rules to secure private data in your app. Privacy rules are conditions that you set up on each data type in order to protect the data from being viewed and edited by unauthorized users.

Article: [Protecting data with privacy rules](/help-guides/data/the-database/protecting-data-with-privacy-rules)

## App data

The App data section contains Bubble's built-in database editor, where you can browse all data stored in the database and create, view, edit and delete data.

Article: [The database editor](https://manual.bubble.io/help-guides/data/the-database/managing-data)

## Option sets

The Option sets section is for managing [option sets](#user-content-fn-5)[^5] – static pieces of information that you can reference around your app, such as a list of colors, countries or any other non-changing data that you want to show in lists, dropdowns and other selection controls.

Article: [Option sets](/help-guides/data/static-data/option-sets)

## File manager

The File manager section is Bubble's built-in editor for files that have been uploaded to your app through the editor or by users. You can search for, view, delete and upload files from here.

Article: [Files](/help-guides/data/files)

## Other ways to learn

<details>

<summary>Core reference</summary>

* [The data tab](/core-resources/bubbles-interface/data-tab)

</details>

[^1]: Bubble comes with built-in tools both on the editor side and in your app to upload and store files and images.<br>

    \
    Article: [Files](/help-guides/data/files)

[^2]: *Option sets* let you set up different types of static options in a database-like structure, but without using the database.

    This is useful to store information like days of the week, marital status, colors, states, countries and other data that you want to load quickly.

    \
    Article: [Option sets](/help-guides/data/static-data/option-sets)

[^3]: *Data types* are like containers that hold specific types of information and each data type is made up of *fields* where that information is stored.\
    \
    For example, one data type might be used to hold information about sports teams (name, coach, and roster of players). Another data type could be used to hold information about games, (date, location, and final score).

    \
    Article: [Data types and fields](/help-guides/data/the-database/data-types-and-fields)

[^4]: All data types consist of *fields* that each store a specific kind of information.\
    \
    For example, on the *User* data type, you could add a field called *Name* and another called *Address* to store the data.

    \
    Article: [Data types and fields](/help-guides/data/the-database/data-types-and-fields)

[^5]: Option sets let you set up different types of static options in a database-like structure, but without using the database.

    This is useful to store information like days of the week, marital status, colors, states, countries and other data that you want to load quickly and that's rarely updated.<br>

    \
    Article: [Option sets](/help-guides/data/static-data/option-sets)


# Global tab

## Element styles

The Styles section acts as the central hub for the visual and layout themes shared among elements in your app, ensuring a uniform look and feel. By using styles, you can efficiently define and maintain your app's aesthetic without manually adjusting individual elements.

Moreover, Bubble's Style Variables sub-tab offers predefined fonts and color variables. This feature accelerates the design process, ensuring any change in a style variable gets reflected across all its instances seamlessly.

Article series: [Styling](/help-guides/design/variables-and-styles)\
Reference: [Styling properties](/core-resources/elements/styling-properties)

## Style variables

### Fonts

In the Fonts section you can set up font variables. This lets you control what fonts are used in your app from one place. Font variables can be applied to individual elements, and to styles.

Article: [Font variables](/help-guides/design/variables-and-styles/font-variables)

### Colors

In the Colors section you can set up font variables. This lets you control what colors are used in your app from one place. Color variables can be applied to individual elements, and to styles.

Article: [Color variables](/help-guides/design/variables-and-styles/color-variables)

## Global expressions

In the global expressions section, you can set up global dynamic expressions that can be re-used across your app.

Article: [Global expressions](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/styles-tab/global-expressions)

## Other ways to learn

<details>

<summary>Related articles</summary>

* Article series: [Styles](/help-guides/design/variables-and-styles/styles)
  * Article: [Font variables](/help-guides/design/variables-and-styles/font-variables)
  * Article: [Color variables](/help-guides/design/variables-and-styles/color-variables)
* Article series: [Design](/help-guides/design)

</details>

<details>

<summary>Core reference</summary>

* [The styles tab](/core-resources/bubbles-interface/styles-tab)
* [Styling properties](/core-resources/elements/styling-properties)

</details>


# Global expressions

Global expressions let you build a dynamic expression once and reuse it anywhere in your app. They  give you a single source of truth for any expression that would otherwise be duplicated across multiple pages, workflows, or elements.

{% hint style="info" %}
Global expressions are available in the **new property editor only**. If you set up a global expression in the new property editor, and then switch to the old editor, they will still work, but cannot be edited until you switch back.
{% endhint %}

You can find global expressions in *Global – Global expressions.*

### How they work

{% hint style="info" %}
Global expressions have access to **global data sources**, meaning data sources that are available anywhere in your app, such as `Do a search for`, `Get an option` and `Current user`.

Context-specific data sources such as `This element`, `This page` and `Get data from page URL` are currently not available.
{% endhint %}

When you create a global expression, you build a dynamic expression the same way you would anywhere else in Bubble. Once saved, the expression becomes available throughout your app and can be referenced anywhere a dynamic expression is used.

If you need to change the expression later, you only change it in one place. The update applies automatically everywhere the global expression is referenced.

<figure><img src="/files/7huzISke3UsECIE6oUMK" alt=""><figcaption></figcaption></figure>

### Parameters

Global expressions can accept parameters, which let you customize the expression each time it's used.

The structure works like this: you set up the core logic of the expression once, with any constraints or values that are always the same. Then, when you reference the expression elsewhere in your app, you can supply additional values through parameters. This makes the same global expression usable across different contexts without having to rebuild the underlying logic.

For example, a global expression set up as a search with a few base constraints can accept additional constraints as parameters wherever it's used. The core query stays consistent, while each use can refine it for its specific context.

### Using global expressions in a dynamic expression

Any global expression you set, creates a new data source carrying the name you give it, and this data source can be inserted into any dynamic expression in your app. To insert a global expression, select *Use global expression*, and select the expression you want to use. Then, populate the parameters.

### When to use a global expression

Global expressions are most useful when the same logic appears in multiple places. Common examples include permission checks, date calculations, status checks, formatted strings, and pricing calculations.

If an expression only appears once in your app, there's no need to make it global. The benefit comes from centralizing logic that would otherwise be duplicated.

### Benefits

Using global expressions gives you a few practical advantages:

* **One place to make changes.** Updating an expression that's used in many parts of your app becomes a single edit instead of a hunt through the editor.
* **A single source of truth.** Everywhere the expression is used reflects the same logic, which reduces the risk of subtle inconsistencies between similar expressions.
* **Cleaner expressions throughout your app.** Long or complex expressions can be replaced with a named reference, making workflows and elements easier to read.


# Plugins tab

Plugins are extensions or modules that add specific features (such as accepting payments with Stripe) or new element types. There is a collection of [Bubble-developed plugins](/core-resources/bubble-made-plugins), and thousands of community-developed plugins.

In the Plugin Tab, you can:

1. **Browse and install Plugins:** Browse Bubble's large catalogue of plugins
2. **Manage Existing Plugins:** View and uninstall plugins
3. **Configure Plugin Settings:** Adjust the settings or provide necessary API keys for specific plugins to function correctly.

## Related articles and links

<details>

<summary>Related articles and links</summary>

* Article: [Plugins](/help-guides/integrations/using-plugins)
* Page: [List of plugins](https://bubble.io/plugins)
* Article series: [Building plugins](/account-and-marketplace/building-plugins)

</details>

<details>

<summary>Core reference</summary>

* [The plugins tab](/core-resources/bubbles-interface/plugins-tab)

</details>


# Settings tab

The Settings tab lets you control key configuration options for your app. You’ll find options for general app info, privacy, API services, SEO, language, Bubble version, workload and features specific to mobile apps—all in one place. Use this section to manage how your app behaves, appears, and connects with users and external services.

Settings in this tab apply to the current app only—they do not affect your overall Bubble account.

## Sections

### App plan

Manage the plan your app is on, paid plugin subscriptions and file storage.

Article: [Pricing and Workload](/account-and-marketplace/account-and-billing/pricing-plans)\
Page: [Bubble pricing](https://bubble.io/pricing) (comparison of the different pricing plans)

#### General

General app settings such as:

* Privacy and security
* General appearance
* [General services API keys](#user-content-fn-1)[^1] (such as Sendgrid and Google Maps)
* [Custom fonts](#user-content-fn-2)[^2]
* [Importing Figma designs](#user-content-fn-3)[^3]
* [Optimize app](#user-content-fn-4)[^4]

### Domain/email

This is where you can connect your app to a domain and configure its [DNS settings](#user-content-fn-5)[^5]. This is also where you set your app's email settings and [SSL/TLS configuration](#user-content-fn-6)[^6].

Article: [Custom domain and DNS](broken://pages/Sfb2EVgX6WfgYIibQCNa)

### Languages

The Languages tab lets you translate static strings in your app into different languages and manage the settings for how users change their language.

Article: [App texts (translating your application)](/help-guides/data/static-data/app-texts-translations)

### SEO / metatags

This is where you'll find settings for SEO and social media sharing. In here you can manage:

* Title, description and image for social media
* Custom header and body
* 301 redirects
* Files in the root folder
* robots.txt and other SEO-related settings

Article: [Social media sharing](broken://pages/-MUUJGGgXT-x5NrF7ttC)\
Article: [Email settings](broken://pages/-MUUNYlJhe5tqMARhsWS)

### API

In the API section you can modify [your app's API settings](#user-content-fn-7)[^7].

Article: [The Bubble API](/help-guides/integrations/api/the-bubble-api)

### Collaboration

The Collaboration section lets you add other Bubble users to collaborate on your app, as well as controlling the access level of each collaborator. This is also where you transfer app ownership to another Bubble user.

Article: [Collaboration](/help-guides/maintaining-an-application/collaboration)

## Other ways to learn

<details>

<summary>Core reference</summary>

* [The settings tab](/core-resources/bubbles-interface/settings-tab)

</details>

<details>

<summary>Video lessons</summary>

* [How to set up a custom domain](https://www.youtube.com/watch?v=vGk6nfq41L4)

</details>

[^1]: Some of Bubble's features, such as sending emails and displaying maps, rely on external API services.\
    \
    You may need to enter your own API key for these services to work. This is handled in the general settings section.

[^2]: Bubble comes with a lot of fonts built-in, but you can also implement your own using the custom fonts feature.

    Article: [Using custom fonts](/help-guides/design/variables-and-styles/using-custom-fonts)

[^3]: Figma is a third-party design tool often used to create design drafts for applications. Using this feature you can import the design directly into Bubble.\
    \
    Article: [Importing Figma designs](/help-guides/design/importing-from-figma)\
    External page: [Figma website](https://www.figma.com/)

[^4]: As you develop your app, you can end up with data that's no longer needed, such as unused styles, deleted option sets and data types and element properties.

    The Optimize app feature clears unused data from your app to make the codebase lighter.

[^5]: DNS (Domain Name System) is a system that translates human-readable domain names (like bubble.io) into IP addresses (like 192.168.0.1) that computers use to identify each other on the internet.

    This is how a user's device connects to the correct server (identified by an IP address) when your app's URL is entered into the browser.

[^6]: SSL (Secure Sockets Layer) and TLS (Transport Layer Security) are cryptographic protocols designed to secure network traffic by encrypting the data.\
    \
    In practice, this means that the data sent to and from the Bubble server to the user's device is encrypted and secure.

    Article section: [The HTTP protocol](/help-guides/integrations/api/introduction-to-apis/what-is-a-restful-api#what-is-the-http-protocol)

[^7]: In this context, your app's API settings refer to *inbound* connections. You can read more about the Bubble API in the article below.\
    \
    Article: [The Bubble API](/help-guides/integrations/api/the-bubble-api)\
    \
    For outgoing requests to other systems, you need to use the API Connector plugin:

    Article: [The API Connector](/help-guides/integrations/api/the-api-connector)


# Overview

## My plan

### My plan

Shows the current subscription plan for the app, including plan name, price, and a short description. Lists key inclusions such as workload units, file storage, app editor count, server log hours, and development version. Includes a button to change plans and links to learn more about plans and pricing.

### Workload

Displays how many [workload units](#user-content-fn-1)[^1] have been used during the current billing cycle, along with the total monthly allocation. Shows whether overages are enabled or disabled. Includes options to change the plan or upgrade for more workload, and a link to view usage metrics.

### Storage

Indicates the percentage and amount of file storage currently in use out of the total allocation. Includes options to change the plan or upgrade for more storage, and a button to view file storage details.

### Plugins

Lists the total number of plugins installed in the app, along with the name, publisher, and license type for each. Includes a button to manage plugins.

## General

### Privacy and security

This section controls who can access and edit your app. It includes settings for application rights, password protection, two-factor authentication, and additional security options like iframe rendering and file upload restrictions. Some features in this section require a Growth plan to deploy.

#### Password protection

Allows you to enter a username and password that must be entered before your app can be viewed in run-time. Can be applied only to your Development environment, or to both Development and Live. We highly recommend keeping your Development environment password protected with a unique username and password.

#### Two-factor authentication

Allows you to enable two-factor authentication for users logging in to your app.

### General appearance

Customize basic visual elements of your app, including the favicon, progress bar color, and the loading spinner color used in repeating groups.

<details>

<summary>Favicon and progress bar/spinner colors</summary>

{% hint style="info" %}
A **favicon**, short for "favorite icon", is a small image that represents your app.

It's the little icon you see in the browser tab next to the title of the page, and it also often appears in bookmarks, browser history, and on shortcuts if you save a website to your home screen on a mobile device.
{% endhint %}

## Setting a favicon

To assign a favicon to your app, first navigate to *Settings - General* and scroll to the *General appearance* header.

Directly underneath you will find the *Favicon* settings, which allows you to upload an image to be used as the favicon. The favicon should be a square image, and keep in mind that:

* Favicons should be in PNG or JPG format. SVG is not supported by all browsers.
* It may be viewed as small as 16x16 pixels: small details such as text may be lost
* Some browsers have a dark mode: darker favicons may become hard to see against a dark background

## Changing the color of the progress bar and spinner

The progress bar is the loading bar at the top of the page when Bubble performs certain tasks such as writing to the database. The *spinner* is the spinning icon visible when data is loading, such as when Bubble is fetching data for a repeating group.

The color of both of these can be changed by navigating to *Settings - General* and scroll to the *General appearance* header. There you will find the two settings:

* Progress bar color
* Repeating group spinner

{% hint style="info" %}
To efficiently maintain colors and consistency across your app, we recommend using Color variables. This can even be applied to app-wide settings such as the progress bar and spinner. You can read more about color variables in the article below.

Article: [Color variables](/help-guides/design/variables-and-styles/color-variables)
{% endhint %}

</details>

### General services API keys

Add API keys for services Bubble integrates with, such as Google Geocode and Google Maps. These keys are required when using a custom domain.

### Algolia

Enable Algolia search in your app. This feature requires a Growth plan to activate.

### iOS appearance

Manage how your app appears on iOS devices. Upload startup images for different screen sizes and configure behavior such as hiding the Safari UI or disabling zoom gestures.

### Custom fonts

Add custom fonts to your app by specifying a font name and linking to a CSS file path. Each font weight must be added individually. External resources like FontLibrary are supported.

### Design import

Redirects you to the Figma-to-Bubble design converter, which replaces the previous in-editor design import tool.

### App file management

Remove unused items to keep your app lightweight and improve performance. Tools are available to optimize the application and clean up app changes history.

### Importing and exporting

Export or import the full app configuration, including pages, workflows, and settings. These options require a Growth plan to use.

### Advanced options

Enable additional settings like timezone overrides for date and time inputs or page-level behavior controls. You can also expose the option to add an ID attribute to HTML elements, which is helpful for applying custom CSS or targeting elements with JavaScript.

<details>

<summary>Exposing the HTML element ID attribute</summary>

Exposing the ID attribute for elements in Bubble is useful for a number of different reasons. For example, you can use it to apply custom style or binding Javascript code to specific elements. Some plugins also require that you identify an element using the ID attribute.

### Enabling and setting the ID attribute

Exposing an ID attribute on elements is disabled by default, but can be enabled by:

1. Navigating to *Settings - General*, scrolling to the *Advanced options* header and checking the *Expose the option to add an ID attribute to* [*HTML elements*](#user-content-fn-2)[^2] checkbox
2. Then, navigate to the element to which you want to apply an ID, open up its property editor and scroll to the bottom of the *Appearance* tab. See the illustration below:

<figure><img src="/files/y7zQUEhGmHDcPvPSKY5A" alt=""><figcaption></figcaption></figure>

### Formatting an ID

When formatting an ID, it's useful to stick to a few rules to ensure compatability with different technologies and browsers:

1. **Uniqueness**: The ID must be unique within a page. You can use dynamic content in the ID Attribute, such as including the index of a the cell of a repeating group.
2. **Start with a Letter**: It should start with a letter (uppercase or lowercase) followed by any combination of letters, digits, hyphens, underscores, colons, and periods.
3. **No Spaces**: It should not contain any spaces.
4. **Case Sensitivity**: IDs are case-sensitive. For instance, `elementID` and `elementid` would be considered different IDs.
5. **Avoid Special Characters**: Though some special characters like hyphens and underscores are allowed, it's generally a good practice to keep IDs alphanumeric to avoid any unexpected behavior in different browsers or technologies.

Lastly, whenever you reference an ID, keep in mind that the ID string matches exactly with the one assigned to the element. Since IDs are case-sensitive, even a slight variation in case or an extra space can cause the reference to fail. We recommend copy-pasting the ID to make sure it matches.

</details>

#### Show parentheses in dynamic expressions in editor

When enabled, this feature visually clarifies the order of operations in dynamic expressions by automatically inserting parentheses. For example, an expression like:\
\
`Current Page’s Width + 2 * 3`\
\
will display as:\
\
`(Current Page’s Width + 2) * 3`\
\
This helps you understand how the expression will be evaluated at runtime.

Expression parentheses are especially helpful for:

* Advanced users working with complex or nested expressions
* New users learning how Bubble handles expression logic

In addition to improving readability, this feature makes it easier to insert, move, or modify parts of your expression. Enabling parentheses in dynamic expressions can also unlock expression structures that aren’t otherwise possible. In some cases, parentheses are required to construct more advanced logic, especially when chaining multiple operators or formatting steps together.

<figure><img src="/files/vcT70GzYghWk2ZL9Wvnn" alt=""><figcaption><p>In this example, with parentheses <strong>disabled</strong>, you can’t add the <code>trimmed</code> modifier because Bubble treats the expression as a single, linear chain and doesn’t allow that additional operation at this point.</p></figcaption></figure>

<figure><img src="/files/oDC9PZbiaCvegflGoXJB" alt=""><figcaption><p>With parentheses <strong>enabled</strong>, Bubble evaluates the grouped part of an expression first and treats the result as a standalone value. This makes it possible to apply additional operators and modifiers that aren’t available in a single, linear expression.</p></figcaption></figure>

For example, certain combinations of modifiers, such as applying both `trimmed` and `lowercase` to the same value, may only be achievable when parentheses are enabled. By allowing you to explicitly group parts of an expression, parentheses give you finer control over evaluation order and make it possible to build more complex expressions that would otherwise be out of reach.

## API

### Public API endpoints

Manage access to your app’s APIs and backend workflows. You can enable the Workflow API, Data API, and choose whether to hide the Swagger documentation. Deployment of these features requires the Growth plan.

Article series: [The Bubble API](/help-guides/integrations/api/the-bubble-api)

### Infinite recursion protection

Set limits on how many times a workflow can schedule itself (recursively) to prevent accidental infinite loops. Limits can be set separately for development and live environments. If a workflow exceeds the limit, it is automatically stopped.

Article:[ Infinite recursion protection](/help-guides/workload/tracking-workload/monitoring-workload/infinite-recursion-protection)\
Article: [Recursive workflows](/help-guides/integrations/api/the-bubble-api/the-workflow-api/api-workflows/recursive-api-workflows)

### 3rd party OAuth / SAML access

Configure login redirects for OAuth or SAML-based authentication. Use this section to add and manage third-party apps for SSO.

### Discourse SSO

Set up single sign-on with Discourse forums. You can enter the base URL, SSO secret, and optional flags for account setup behavior. You’ll also need to deploy your app and enable SSO in Discourse’s settings using the provided Bubble API URL.

### Advanced

Download your app’s public [JSON web key](#user-content-fn-3)[^3] for use in external authentication or verification workflows.

## Collaboration

### Users editing this app

Manage who can collaborate on your app and control the level of access each user has. You can set permissions for app editing, data access, log viewing, and limit access to only the development version. Admin rights can also be toggled per user.

### Invite a user

Invite collaborators by entering their email address. You can choose to invite them or transfer app ownership. This section also includes an option to show other users’ cursors while they edit the app in real time. A Growth plan is required to deploy with collaborators.

Article: [Collaborators](/help-guides/maintaining-an-application/collaboration)

## Languages

### General settings

Set the primary language for your app and optionally connect a language field on the user type to support language-specific experiences.

### Application texts and messages

Manage the static text content used throughout your app, such as system messages or labels. You can create new texts, edit existing ones, and export or import translations using CSV files. This section also supports multi-language configurations based on your selected language.

Article: [Translating your app](/help-guides/data/static-data/app-texts-translations)

## Sub apps

### Sub applications

Create sub apps that inherit functionality from your main app. You can optionally copy the database from the main app when creating a sub app. This feature is useful for managing white-labeled apps or app instances for different clients. Requires the Team plan.

### Push to sub applications

Push updates from your main app to existing sub apps. This helps keep all sub applications in sync with your latest changes. This feature also requires the Team plan.

Article: [Sub applications](/help-guides/optimizing-an-application/sub-apps)

## Versions

### Bubble release tier

Controls how your app receives updates to the Bubble platform. You can choose a release tier (e.g., immediate or delayed) depending on how quickly you want to adopt platform changes. This feature requires the Growth plan.

### Available Bubble Engine versions and current version

Shows which Bubble Engine version your app is currently using, along with available updates. You can review version details and choose to upgrade when you're ready. This helps you manage stability and take advantage of engine improvements.

### Beta features

{% hint style="warning" %}
**Note:** Beta features are experimental and may be changed or removed at any time.
{% endhint %}

Allows you to enable features that are still in development and not yet part of a stable release. These features can be toggled on or off at any time, but must be deployed to use in the Live version. You can also provide feedback or report issues directly.

## Notifications

#### Manage your notifications

Set up custom alerts to monitor your app’s workload usage. Notifications help you stay informed and take action before hitting usage limits. Use the *Create notification* button to get started.

[^1]: Workload units measure the computing resources your app uses, such as running workflows, processing actions, and database requests.

    Article series: [Workload](/help-guides/workload)

[^2]: HTML elements in this context is not to be confused by the Bubble HTML element type.\
    \
    In the context of ID attributes, HTML element refers to *any* element placed on the page, and the ID is a part of the HTML code that helps your browser render the page.

[^3]: The app’s public JSON key is used to verify signed data from your app, such as authentication tokens, in external systems.


# Web app

## SEO and metatags

### Social media and open graph settings

Set the metadata that controls how your app appears when shared on social platforms. You can customize the title, site name, description, and thumbnail used for link previews.

<details>

<summary>Social media sharing</summary>

Many social media platforms (and other types of software that allow link sharing) fetch some information about your app that it uses to generate a preview. This gives you as the app's developer the chance to set up the text content and image that the platform displays, to ensure it represents your app in a good way.

## Open Graph tags for Social Networks <a href="#open-graph-tags-for-social-networks" id="open-graph-tags-for-social-networks"></a>

### What is Open Graph?

Open Graph is a protocol that enables web pages to become rich objects (meaning that they are displayed with details like a customizable title and preview image) in social media platforms and other online services.

It was first introduced by Facebook, but has since been adopted by many other platforms such Twitter and LinkedIn. By tweaking the Open Graph settings in Bubble, you can control what content is displayed on platforms that support the Open Graph protocol.Open Graph is a protocol that enables web pages to become rich objects (meaning that they are displayed with details like a customizable title and preview image) in social media platforms and other online services.

It was first introduced by Facebook, but has since been adopted by many other platforms such Twitter and LinkedIn. By tweaking the Open Graph settings in Bubble, you can control what content is displayed on platforms that support the Open Graph protocol.

### Social Metatags

{% hint style="info" %}
This setting specifies the tags that are used when your app's *domain* is shared. You can also set specific tags for a given *page*. See the [page level settings section](#page-level-settings).
{% endhint %}

Popular social networks let you customize how links to your website look, when posted on their platforms. You can control this setting (open graph meta tags) in the Meta tags section and customize the image and the content of the text that will be displayed.

Here's an example of what the tags for the [www.bubble.io](http://www.bubble.io) website look like on LinkedIn:

<figure><img src="/files/xnBvhtlsTxhCnouz9AaS" alt=""><figcaption><p>This link preview is automatically generated by LinkedIn, based on the settings in the Social Media / Open Graph section</p></figcaption></figure>

## Page level settings

Settings in the *Settings* tab will be applied to all pages of the application. Bubble also lets you modify these values at the page level. This is in particular useful if you want to use some dynamic information (for instance, the current page's product's image) instead of a generic image.

If you have some settings at the page level, they will prevail over the application's settings. To edit the page's setting:

1. Open up the page that you want to edit
2. Double-click the page itself or locate it in the element tree to open up the property editor
3. In the *Appearance tab,* scroll down to the *Title/Description* and *Image* setting as illustrated below:

<figure><img src="/files/8x4ZaDXnbHcegosxle12" alt=""><figcaption></figcaption></figure>

</details>

### SEO settings

Manage settings that affect how search engines interpret and index your app. Options include exposing heading tags, pointing URLs to your primary domain, customizing your `robots.txt`, and exposing a sitemap file.

<details>

<summary>Custom headers and body</summary>

## What is a custom header or body for?

{% hint style="info" %}
Using this setting adds a custom header/body to ***all*** of your pages. If you want to add them to one or more **specific pages**, you can do so in the pages property editor.

Article: [The page](/help-guides/design/elements/web-app/the-page)
{% endhint %}

### Custom headers

{% hint style="info" %}
The content in the **header** loads *before* the content on the page and is useful for content that you need to ensure is loaded before the page is loaded and rendered.
{% endhint %}

A custom header refers to the HTML content that can be added to the `<head>` section of every page in your app. The content inside the `<head>` tag does not appear on the page itself, but it sends specific instructions to the browser, such as:

1. **Meta Tags**: If you need to add meta tags to all pages (in addition to the tags already built into Bubble), you can place them here
2. **Link to External CSS**: External stylesheets are typically linked in the header to ensure that they are loaded before the page content
3. **Link to External JavaScript Libraries**: Although JavaScript can be loaded in the header or the body, certain libraries and scripts that must be loaded before the page content are placed in the header
4. **Third-party Integrations**: Some third-party integrations require code snippets to be placed in the head. The third-party documentation will usually instruct where to place the code.

### Custom body

{% hint style="info" %}
The content in the **body** loads *as* the page is being rendered. It's useful for code that interacts with elements and content that don't necessarily need to be loaded before the page starts displaying
{% endhint %}

A custom body refers to content placed within the `<body>` tag of the HTML document. This is where the content displayed on the webpage itself is housed (including every element you place on the page in the Design tab). You might use a custom body for:

1. **Embedding Custom HTML Elements**: For special content that should be present on every page.
2. **Embedding JavaScript**: When scripts are dependent on the page content, or when they should execute after the content has been loaded, they are typically placed near the end of the body section.
3. **Third-party Integrations**: Certain third-party integrations are placed in the body.

As you can see from the examples, there is some overlap as to what kind of content is placed where. If you are relying on a third-party code snippet, we recommend you check the documentation for that third party to make sure it's implemented in the right place.

</details>

### Advanced settings

Add custom script or meta tags to the `<head>` or `<body>` of your app pages. Useful for adding analytics, third-party integrations, or custom styles.

### 301 redirections

Set up permanent server-side redirects from one URL to another. This helps preserve SEO value when changing URLs and ensures users are routed to the correct destination. Supports wildcard redirects for dynamic patterns.

### Hosting files in the root directory

Upload and host static files at the root level of your domain (e.g., `yourdomain.com/filename`). This is often used to verify domain ownership or support third-party services. Files are version-specific and should not conflict with page names.

## Domain and email

When an app is created, Bubble uses the name you provide to generate a unique URL where you can run [both the development and live branches](#user-content-fn-1)[^1] of your app. For example, if your app has the name `my-bubble-application`, Bubble generates the URLs below:

{% tabs %}
{% tab title="Live" %}

```url
https://my-bubble-application.bubbleapps.io
```

{% endtab %}

{% tab title="Development" %}

```python
https://my-bubble-application.bubbleapps.io/version-test/
```

{% endtab %}
{% endtabs %}

<details>

<summary>What does it mean to connect to a custom domain?</summary>

Connecting to a custom domain means linking your Bubble app to a domain you own (like `yourapp.com`) instead of using the default Bubble URL (e.g. `yourapp.bubbleapps.io`). This gives your app a professional web address and allows you to manage branding, SEO, and email settings tied to your domain. See the article below for a more in-depth look:

Article: [Custom domain and DNS](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/settings-tab/web-app/custom-domain-and-dns)

</details>

<details>

<summary>Domain registrars</summary>

Registrars are accredited entities that manage the reservation of domain names. There are many different providers and in the list below you'll find some of the biggest ones:

1. GoDaddy - [https://www.godaddy.com](https://www.godaddy.com/)
2. Namecheap - [https://www.namecheap.com](https://www.namecheap.com/)
3. Bluehost - [https://www.bluehost.com](https://www.bluehost.com/)
4. HostGator - [https://www.hostgator.com](https://www.hostgator.com/)
5. Google Domains - [https://domains.google](https://domains.google/)
6. DreamHost - [https://www.dreamhost.com](https://www.dreamhost.com/)
7. 1&1 IONOS - [https://www.ionos.com](https://www.ionos.com/)
8. Network Solutions - [https://www.networksolutions.com](https://www.networksolutions.com/)
9. Dynadot - [https://www.dynadot.com](https://www.dynadot.com/)
10. Register.com - [https://www.register.com](https://www.register.com/)

*<mark style="color:red;">Bubble is not responsible for the content, accuracy, or practices of third-party websites or services that are linked from our platform and website. We provide these links for your convenience. Always review the terms and conditions and privacy policies of any third-party websites or services that you visit.</mark>*

</details>

### Domain settings

Connect a custom domain to your app. Enter your domain name and click **Set up this domain** to register it with Bubble and receive the instructions needed to configure your DNS.

### Email settings

Customize your app's email behavior after a domain has been set up. Domain configuration is required before enabling email features.

<details>

<summary>Email settings</summary>

{% hint style="info" %}
This section explores using Bubble's built-in email features, which utilize Sendgrid. If you want to set up a more customized solution or prefer a different email API provider, consider configuring an API using the API Connector plugin or browsing the plugin store for plugins related to your desired provider.

Article: [The API Connector plugin](/help-guides/security/api-security/api-connector-security)

Article series: [Plugins](/help-guides/integrations/using-plugins)
{% endhint %}

There are many scenarios in which you'll need to email the users of your app. Whether it's to notify them about updates, send password reset links, share promotional offers, or simply welcome them onboard, effective email communication enhances user engagement and trust in your platform.

Sending emails can be done sing the *Send Email* action. Some specific email types, such as resetting a user's password, has a built-in feature that generates a secure password reset token for you and can also automatically send the email.

Bubble offers two ways to send emails:

* **Shared email address:** if you haven't set up a custom domain and/or SendGrid account, Bubble offers a shared email address from which you can email your users. For apps in development, this solution is suitable. However, for live apps, we recommend using a dedicated SendGrid account to ensure email deliverability. The shared email address also has a limitation of 50 recipients per email.
* **Custom domain:** when you own a custom domain, you can customize SendGrid and Bubble's built-in email features use that domain instead. This is a more reliable solution for apps with live users.

When your app does not use a custom domain, all emails will come from `admin-no-reply@bubble.io`. This is a shared email address from Sendgrid that comes out-of-the-box with your new Bubble app to help you get started.

In many scenarios you'll need to send an email to your users. This article covers the basic email settings needed to use Bubble's built-in email system.

<figure><img src="/files/ujum5bjGTnDpz7XqzJbQ" alt=""><figcaption></figcaption></figure>

## Built-in email features

Bubble lets you send emails with customizable content, and additionally offers built-in features for sending more technically-oriented emails, such as those required for resetting a user's password.

Click on each point below for more information and links to their core reference entries:

* [Send email](#user-content-fn-2)[^2]
* [Send confirmation email](#user-content-fn-3)[^3]
* [Send password reset mail](#user-content-fn-4)[^4]
* [Send meeting request by email](#user-content-fn-5)[^5]

## Using the shared email address

If you have not yet connected your app to a custom domain, you can use Bubble's shared email address. What this means is that all your emails will come from the same address as other apps using the same feature:

```
admin-no-reply@bubble.io
```

This is useful for when you are developing and testing your app, but we recommend setting up your own SendGrid account when your app goes live. You can read more about why and how to do that in the section below.

The built-in functionality for sending from the shared address comes out-of-the-box when your app is set up, and you don't need to make any changes to your app's settings. To send an email, simply use the [*Send email*](#user-content-fn-2)[^2] feature in the workflow editor.

<figure><img src="/files/PaKX8fwYXITUqPW4xBDh" alt=""><figcaption></figcaption></figure>

### Limitations of using the shared email address

Using Bubble's shared email address comes with a few limitations:

* Due to rate limiting from SendGrid, a maximum of 50 recipients can be specified per email
* You can only send emails from the `admin-no-reply@bubble.io` email address
* Measures such as [domain authentication](#user-content-fn-6)[^6] is only available if you are managing your own SendGrid account
* SendGrid provides a range of features including custom email templates, tracking, analytics, and more. These are only available if you register your own account
* SendGrid keeps logs of all sent emails. The logs are only available if you register your own account.

## Using a custom domain

Regardless of the email domain you want to use, Bubble's built-in email feature uses SendGrid to send emails. If you want to use your own username[^7] and domain[^8], you need to set up your own SendGrid account.

### Setting up SendGrid

{% hint style="info" %}
Before you set up your SendGrid account, you must own the domain that you want to use and connect it to your Bubble app. You can read more about registering our domain in the article below.

Article: [Custom domain and DNS](broken://pages/Sfb2EVgX6WfgYIibQCNa)
{% endhint %}

#### Step 1: Creating a SendGrid account

The first thing you need to do is to register a SendGrid account at <https://signup.sendgrid.com/>.

#### Step 2: Generating an API key

After logging in, you need to get your API key from SendGrid (see SendGrid documentation [here](https://sendgrid.com/docs/Classroom/Send/How_Emails_Are_Sent/api_keys.html)). Make sure to treat the API key as sensitive data and store it in a secure way.

#### Step 3: Set up and validate the key in Bubble

Navigate to *Settings - Domain / Email* and scroll down to the header that says *Email settings.* Note that if you have not yet connected your Bubble app to a domain, the setting will not be displayed.

Enter it in the input box in the *SendGrid API Key* field, and click on *Verify Sendgrid Key*. If your key is valid, a validation email will be sent to the account you used to sign up for Bubble.

![](/files/-M5smvYhkodZ76iixpsx)

Once you have validated your SendGrid connection, you will be able to, optionally, enter a template ID (so that all emails coming from your app follow a template you design in SendGrid).

You will also be able to modify the alias of the admin email.

![](/files/-M5sc4NdXTK3kIDhOFwd)

Note that there are some things you can do on SendGrid's end to optimize email delivery, such as domain white-labeling, IP white-labeling, etc.

We recommend you read [SendGrid's documentation](https://docs.sendgrid.com/) for more details about their service.

</details>

### TLS encryption (HTTPS)

Enable HTTPS for secure communication between your app and users. TLS encryption can only be configured after a custom domain has been set.

[^1]: In Bubble, you have the flexibility to run your app in two distinct environments: Development and Live.

    The Development environment serves as a sandbox, allowing you to make changes and preview your app without affecting the live version.

    The Live environment is what your end-users interact with.

    Notably, each environment has its own separate database, ensuring that data in development does not mix with live data.

    For enhanced collaboration among teams, Bubble offers the ability to create additional custom branches.

    Learn more about this feature in the following article:

    Article series: [Version control](/help-guides/maintaining-an-application/version-control)

[^2]: The *Send email* feature lets you send emails with fully customizable content to one or more email addresses.\
    \
    Reference: [Send email](/core-resources/actions/email#send-email)

[^3]: The Send confirmation email feature is used to send an email to a registered user's email address, that contains a unique link that they can access to confirm that they have access to the email address they specified.\
    \
    Reference: [Send confirmation email](/core-resources/actions/account#send-confirmation-email)

[^4]: The *Send password reset email* feature is used to send an email to a registered user's email address, that contains a unique link that they can access to reset their password. The link leas the user to the built-in *reset\_pw* page.

    Reference: [Send password reset email](/core-resources/actions/account#send-password-reset-email)

[^5]: The Send meeting request by email feature sends an email that contains a calendar event recognized by most major calendar providers such as Google, Outlook and iCal.\
    \
    Reference: [Send meeting request by email](/core-resources/actions/email#send-meeting-request-by-email)

[^6]: Domain authentication proves to email providers that SendGrid has permission to send emails on your behalf.

    This signals that the email is legitimate, which helps in reducing the chances of your email being marked as spam. This is essential for maintaining a good sender reputation.

    External link: [Sendgrid: How to set up domain authentication](https://docs.sendgrid.com/ui/account-and-settings/how-to-set-up-domain-authentication)

[^7]: The *username* in this context means the part of the email address that comes before the @.\
    \
    For example, if you set up the email address <admin@my-bubble-application.com>, then *admin* is the username.

[^8]: Domains are the root of your app's or website's URL. For example, the Bubble website is hosted on the domain `www.bubble.io`.\
    \
    In the context of email sending, the domain is the part that comes after the @, such as bubble.io


# Custom domain and DNS

When an app is created, Bubble uses the name you provide to generate a unique URL where you can run [both the development and live branches](#user-content-fn-1)[^1] of your app. For example, if your app has the name `my-bubble-application`, Bubble generates the URLs below:

{% tabs %}
{% tab title="Live" %}

```url
https://my-bubble-application.bubbleapps.io
```

{% endtab %}

{% tab title="Development" %}

```python
https://my-bubble-application.bubbleapps.io/version-test/
```

{% endtab %}
{% endtabs %}

## What does it mean to connect to a custom domain?

{% hint style="info" %}
Each individual app can only be connected to **one** domain or subdomain. If you need to use multiple domains or subdomains, consider utilizing the sub-app feature.

Article: [Sub-apps](/help-guides/optimizing-an-application/sub-apps)
{% endhint %}

Setting up a custom web domain means configuring your app to be accessed through a specific domain name that you've chosen, rather than the default address provided by Bubble. This involves registering a unique domain name (like `www.my-custom-domain.com`) and then linking it to your website. In that sense, the domain makes up the "root" of all your app's URL's.

{% tabs %}
{% tab title="Live" %}

```url
https://www.my-custom-domain.com
```

{% endtab %}

{% tab title="Development" %}
{% code overflow="wrap" %}

```
https://www.my-custom-domain.com/version-test/
```

{% endcode %}
{% endtab %}
{% endtabs %}

Connecting to a custom domain means to customize your site’s URL, making it more recognizable and often more professional-looking, reflecting your brand or the app's purpose more accurately.

## Quick guide: setting up a custom domain

Setting up a custom domain is done in three steps:

{% stepper %}
{% step %}

### Register the domain

The [first step](#step-1-register-the-domain) is to buy the domain you want, and register it with a registrar[^2].
{% endstep %}

{% step %}

### Add the domain in Bubble

Then, you [add that domain](#step-2-add-the-domain-in-bubble) (such as `www.example.com`) in the Bubble editor.
{% endstep %}

{% step %}

### Change DNS settings

After registering the domain in Bubble, we will provide you with DNS settings that you need to [register with your registrar](#step-3.-change-the-dns-settings).
{% endstep %}
{% endstepper %}

### `Example.com` versus `www.example.com`

Most apps and websites are expected to load regardless of whether the user types in `example.com` or `www.example.com` as the URL. For SEO purposes, Bubble automatically redirects one to the other, depending on which one you entered as your app's custom domain.

For example:

| Your custom URL                           | User types                | Redirects to              |
| ----------------------------------------- | ------------------------- | ------------------------- |
| example.com                               | <https://www.example.com> | <https://example.com>     |
| [www.example.com](http://www.example.com) | <https://example.com>     | <https://www.example.com> |

## In-depth guide: setting up a custom domain

{% hint style="info" %}
Many registrars also offer hosting, meaning that they'll show you a price for the domain, and for a hosting period (typically a year or more). Since Bubble apps are hosted on Bubble's server, you don't need to purchase hosting as well. Make sure you only pay for the domain.
{% endhint %}

{% hint style="warning" %}
Using a domain that incorporates the term `bubble`, such as [www.bubbleagency.com](http://www.bubbleagency.com/), is not permitted for a custom domain in Bubble. This is because it violates our terms of service. Custom domains must avoid using 'bubble' to maintain compliance with our guidelines.

Page: [Terms of service](https://bubble.io/terms)
{% endhint %}

### Step 1: Register the domain

Domains are purchased from a company known as a domain registrar. Once it has been purchased, it becomes your unique identifier on the web, and no one can register the same domain as you. Many domains are registered for the first time, but there's also a market for buying existing domains that ranges from a few US dollars to several million.

<details>

<summary>Domain registrars</summary>

Registrars are accredited entities that manage the reservation of domain names. There are many different providers and in the list below you'll find some of the biggest ones:

1. GoDaddy - [https://www.godaddy.com](https://www.godaddy.com/)
2. Namecheap - [https://www.namecheap.com](https://www.namecheap.com/)
3. Bluehost - [https://www.bluehost.com](https://www.bluehost.com/)
4. HostGator - [https://www.hostgator.com](https://www.hostgator.com/)
5. Google Domains - [https://domains.google](https://domains.google/)
6. DreamHost - [https://www.dreamhost.com](https://www.dreamhost.com/)
7. 1&1 IONOS - [https://www.ionos.com](https://www.ionos.com/)
8. Network Solutions - [https://www.networksolutions.com](https://www.networksolutions.com/)
9. Dynadot - [https://www.dynadot.com](https://www.dynadot.com/)
10. Register.com - [https://www.register.com](https://www.register.com/)

*<mark style="color:red;">Bubble is not responsible for the content, accuracy, or practices of third-party websites or services that are linked from our platform and website. We provide these links for your convenience. Always review the terms and conditions and privacy policies of any third-party websites or services that you visit.</mark>*

</details>

Users access your app by typing its domain into their browser's address bar. Think of it as the digital equivalent of your physical address — it's how people find you on the internet.

### Step 2: Add the domain in Bubble

It doesn't matter which registrar you have used when you want to configure it in Bubble, as long as you have access to make changes to the [domain's DNS settings](#user-content-fn-3)[^3].

To set up the domain with your app, do the following:

{% stepper %}
{% step %}

### Open domain settings

Navigate to the *Settings* tab and *Domain and email* section.
{% endstep %}

{% step %}

### Enter the name

Enter the domain you purchased in [step 1](#step-1-register-the-domain), in the *Domain name* input field.
{% endstep %}

{% step %}

### Confirm

Click *Set up this domain.*
{% endstep %}
{% endstepper %}

When you've completed these steps, Bubble will show you the DNS records that you need for step 3. Typically, they look something like this:

{% hint style="warning" %}
**Do not use the IP addresses below** – they are only meant as an illustration. Use the IP addresses that Bubble provides in the Domain subtab.
{% endhint %}

<table><thead><tr><th width="99">Type</th><th width="126">Name</th><th>Value</th></tr></thead><tbody><tr><td>A</td><td></td><td>104.16.36.105</td></tr><tr><td>A</td><td></td><td>104.16.42.105</td></tr><tr><td>A</td><td></td><td>104.19.240.93</td></tr><tr><td>A</td><td></td><td>104.19.241.93</td></tr><tr><td>A</td><td>www</td><td>104.16.36.105</td></tr><tr><td>A</td><td>www</td><td>104.16.42.105</td></tr><tr><td>A</td><td>www</td><td>104.19.240.93</td></tr><tr><td>A</td><td>www</td><td>104.19.241.93</td></tr></tbody></table>

This table outlines the DNS A record configurations with their respective types, names, and values.

<details>

<summary>What are "Type A" records?</summary>

**A records**, or **Address Records**, are a fundamental part of DNS (Domain Name System).

They map a domain name to its corresponding IP address, which is a numerical label assigned to each device on a network. This mapping allows users to access websites using familiar domain names (like [www.example.com](http://www.example.com/)) rather than numeric IP addresses. A records are essential for directing internet traffic and making the web user-friendly.

</details>

<details>

<summary>Why are there so many similar records?</summary>

#### Multiple IP addresses

Having multiple IP address values for a domain in DNS settings provides redundancy and load balancing:

* **Redundancy** ensures that if one server (or IP address) becomes unavailable, others can take over, maintaining the accessibility of your app.
* **Load balancing** distributes traffic across several servers, improving app performance and reducing the chance of overloading a single server.

This setup enhances the reliability and efficiency of hosting your app. You only need to set the DNS records once.

#### Domain prefix

In the example above, you'll see that we link to four different records two times (one blank and one with www). This means that both `https://my-app.com` and `https://www.my-app.com` will properly redirect to your app.

</details>

### Step 3. Change the DNS settings

{% hint style="info" %}
Navigating this process can get somewhat technical, as the interface varies between registrars. If you need some guidance, it’s a good idea to contact your registrar’s customer support. Also, feel free to seek advice on the [forum](https://forum.bubble.io/), as there’s a good chance another user has experience with the same registrar and can offer helpful insights.
{% endhint %}

After obtaining the DNS details, there's no need to make any more adjustments within Bubble. Your next steps should be carried out in the domain registrar service where your domain is managed.

{% stepper %}
{% step %}

### Log in to the registrar's dashboard

These steps are performend in the registrar's dashboard, not in Bubble's settings.
{% endstep %}

{% step %}

### Navigate to to DNS settings

Look for the *DNS settings* or *DNS Zone File* within your registrar’s dashboard.
{% endstep %}

{% step %}

### Remove empty A records

In this section, remove any existing [A records](#what-are-type-a-records) that have an empty name ('') or `www.` Note that [some registrars use '@' as a placeholder for an empty domain](#user-content-fn-4)[^4].
{% endstep %}

{% step %}

### Enter the records Bubble provided

Replace them with the records provided earlier. Proceed by adding the records accordingly
{% endstep %}
{% endstepper %}

### Propagating the changes

After setting up the DNS records, the new settings need to propagate across the internet. **This can take up to 24 hours**, but usually you should see some results after 2-4 hours, and sometimes faster.

<details>

<summary>Why do DNS settings need time to propagate?</summary>

For those new to the concept, let's talk about why DNS settings need time to propagate.

DNS stands for Domain Name System, which is essentially the phonebook of the internet. It's a system that translates human-friendly domain names like "bubble.io" into IP addresses like "192.0.2.1" that computers use to communicate with each other.

To make this process fast, DNS information isn't fetched fresh every time someone visits a site. Instead, networks, internet providers, and even your own device store cached copies of recent lookups. When you type a domain into your browser, your computer usually checks one of these caches first before reaching out across the internet.

When you update your DNS settings, this change needs to be reflected in all those local copies (DNS servers). This process is known as *propagation*.

However, since these copies are not all updated instantly (they refresh at different intervals), it takes time for them all to catch up with the change. This is why you might not see your website immediately after you've made changes to its DNS settings, and why sometimes your friend in another country can see the changes sooner or later than you can.

Typically, DNS propagation can take anywhere from a few minutes to 48 hours, depending on various factors including the settings of the DNS servers and how often they are set to update their records.

The propagation process is up to the registrar, and cannot be affected by Bubble.

</details>

## Checking the DNS settings

Bubble lets you check the success of the operation below the records, and you can check the process by clicking on the *Check my settings* button. If you encounter an error, it could indicate that the records have not been configured correctly, or that the changes have not yet propagated through the system. See our [FAQ below](#faq-domain-and-dns) for possible solutions to common errors.

<figure><img src="/files/4Mut1n9OjGOWN3l05eIl" alt=""><figcaption></figcaption></figure>

As soon as a domain is properly configured, all requests to yourapp.bubbleapps.io will automatically be redirected to yourdomain.com. This applies to both the Live and Development anvironment.

## Setting up a subdomain

<details>

<summary>What is a subdomain?</summary>

Imagine a website being like an office building, and the domain name is the building's address. A subdomain is like a specific department or section within that building, with its own unique identifier.

In more technical terms, a domain is the main web address you use to reach a website (like bubble.io). A subdomain is an additional part added to the front of the domain, separated by a dot. For instance, support.yourapp.com could be the subdomain for the customer support section of your main app located on yourapp.com.

This way, you can organize different parts of the app (like having separate areas for blog posts, customer support, legal documentation and product pages), all while still being connected to the main domain.

</details>

If you already are using a domain and would like to use a subdomain to point to your Bubble app (for instance, app.yourapp.com). The steps are mostly identical to the above, with just a few changes:

{% stepper %}
{% step %}

### Register the domain

When you purchase a domain name, you gain the rights to create subdomains within it, and you don't need to buy these subdomains separately.

Subdomains, like `blog.example.com` or `shop.example.com`, are part of the main domain (`example.com`) and can be set up directly through your domain registrar's website.
{% endstep %}

{% step %}

### Add the domain in Bubble

Instead of adding the main domain ( such as `www.example.com`), you enter the URL that includes the subdomain (such as `app.example.com` or `dashboard.example.com).`
{% endstep %}

{% step %}

### Change the DNS settings

After registering the subdomain in Bubble, we will provide you with DNS settings that you need to register with your registrar. Registering them is done in the same way as [described earlier](#step-3.-change-the-dns-settings), but note that the DNS records will be different.
{% endstep %}
{% endstepper %}

## Changing/removing a domain

If you need to change your domain, you first need to remove the current domain. Clicking on 'delete this domain name' will terminate the connection between the application and the domain.

Note that your domain will still point to Bubble's server until you remove the DNS records in the registrar service. A Bubble page will be displayed with a message indicating there is no app linked to this domain. Once you have removed the domain, you can add a new domain as described above.

{% hint style="info" %}
**Browser cache:** Note that if you remove a domain and want to use an appname.bubbleapps.io domain as initially happens, you may have to clear the browser cache, as the redirection is usually cached at the browser level.
{% endhint %}

#### Will a domain change affect uploaded file URLs and dynamic URL expressions?

In short, no.

* **Uploaded files** are stored on S3, rather than the domain directly. The URL remains the same evein if you set, change or disable a custom domain.
* **URL expressions** automatically update to reflect your app's current domain. However, if you have hard-coded your app's domain somewhere, you will need to update it. We recommend using the `This URL` or `Website home URL` data sources instead of manually typing in a domain, to ensure it remains consistent.

## SSL encryption (HTTPS)

{% hint style="info" %}
For all apps created after **October 2019**, TLS is mandatory, and this setting cannot be changed.
{% endhint %}

TLS and SSL are encryption protocols that keep data secure while it's being sent between systems. All apps hosted on Bubble use TLS automatically, and this can't be turned off.

This means any data traveling between the user's device and Bubble's servers, along with related services, is encrypted in transit. Even if a third party were to intercept it, the contents can't be read.

<details>

<summary>What is TLS and SSL?</summary>

*TLS (Transport Layer Security)* and *SSL (Secure Sockets Layer)* are cryptographic protocols designed to provide secure communication over a computer network, such as between the Bubble server and the devices of you and your users.

TLS is the newer version of SSL and offers enhanced security features, but SSL as a term is still often used interchangeably with 'TLS' in everyday language. In practice, SSL is considered deprecated today, and Bubble uses TLS for all connections.

This encryption is applied both to your app and to the Bubble editor, making data secure while it's in transit between the server and device.

</details>

## FAQ: Domain and DNS

<details>

<summary>What is a custom domain?</summary>

A custom domain is your own web address, such as `yourapp.com`, used in place of Bubble's default `yourappname.bubbleapps.io` URL. A custom domain gives your app a professional, branded appearance.

</details>

<details>

<summary>Do I need a paid plan to use a custom domain?</summary>

Yes. Custom domains are available on Bubble's paid plans. The free plan uses the default `bubbleapps.io` subdomain only. See the [pricing page](https://bubble.io/pricing) for current plan details.

</details>

<details>

<summary>Do I have to buy a domain through Bubble?</summary>

No. You purchase the domain separately from a registrar such as GoDaddy, Namecheap, or Google Domains, then point it to Bubble through DNS settings.

</details>

<details>

<summary>Where do I configure my custom domain in Bubble?</summary>

In the *Settings* tab of the editor, under *Domain & Email*. Enter your domain name there and follow the prompts to connect it.

See more in our [guide](#quick-guide-setting-up-a-custom-domain).

</details>

<details>

<summary>How long does it take for a custom domain to start working?</summary>

DNS changes can take anywhere from a few minutes to 48 hours to propagate. In most cases, the domain becomes accessible within a few hours. Note that the timing can vary based on on your device, IP and geographic location.

</details>

<details>

<summary>Why does DNS propagation take time?</summary>

Networks, internet providers, and devices store cached copies of recent DNS lookups to make domain resolution fast. When you make a DNS change, the new information has to wait for those cached copies to expire before it's picked up. This is what causes the delay.

DNS propagation is handled by third parties, and is outside of Bubble's control.

</details>

<details>

<summary>Does Bubble provide an SSL certificate?</summary>

Yes. Bubble automatically provisions and renews an SSL certificate for your custom domain, so your app is served over HTTPS without manual configuration.

</details>

<details>

<summary>Why are my users are getting "This Connection is Not Secure" errors?</summary>

Cloudflare has not validated your domain yet.

If you click on "Advanced" and then look at the certificate details, and the certificate is of the form `ssl123456.cloudflare.net`, your DNS entry is pointing to the correct place, but Cloudflare has not validated your domain yet. Please wait an hour and check again.

</details>

<details>

<summary>How long does it take for my domain to move to Cloudflare?</summary>

Two minutes to several hours, depending on how long your DNS records take to update.

The moment you register your custom domain with Bubble and Cloudflare, your domain is registered with both services. In order for it to work correctly, Cloudflare needs to validate that your DNS entries point to their servers.

When you update your DNS records (adding, removing, or renaming a record), the length of time they stay available is determined by the record's **TTL** (time-to-live) value.

Let's say you change a record at your registrar, and that record has a TTL of 1 hour. After 1 hour, you can be certain that **that** server is reporting your new record to the internet. However, there are many name servers on the internet, and all of them will need to have your most up-to-date record. **On average**, you can expect your records to be broadcast across the internet within twice the length of your TTL. So if your TTL is 1 hour, expect your record to be everywhere after 2 hours.

Once your record has been updated everywhere, Cloudflare will be able to verify that you own the domain, and your app will become active.

</details>

<details>

<summary>How do I know my site is working on Cloudflare?</summary>

There are several tools.

The first, to check to see if your domain records have propagated, is the [Google DNS lookup tool](https://toolbox.googleapps.com/apps/dig/).

When you've changed your domain records, you can go to the A record tab to check to see if your A record has propagated.

```
mywebpage.us. 299 IN A 104.19.241.93
mywebpage.us. 299 IN A 104.19.240.93
```

You can also take advantage of a service like [What's my DNS](https://www.whatsmydns.net), which tests your custom domain from many locations all over the world. It's not uncommon to see a few blank entries on this page; what you're looking for is any records that return an error (red X) or different results than you are expecting.

Cloudflare serves a page on a certain route on every site hosted on their domain at `/cdn-cgi/trace`. You can see bubble's page at [bubble.io/cdn-cgi/trace](http://bubble.io/cdn-cgi/trace); replace 'bubble.io' with your own custom domain (once it loads) to see if it's working.

The output should look somewhat like this:

```
fl=xxxxx
h=bubble.io
ip=xxx.xxx.xxx.xxx
ts=1572657558.41
visit_scheme=http
uag=Mozilla/5.0 [...]
colo=EWR
http=http/1.1
loc=US
tls=off
sni=off
warp=off
```

</details>

<details>

<summary>I am a legacy customer; what should I do to temporarily disable Cloudflare?</summary>

If you're having problems that you think might be caused by Cloudflare, you can temporarily disable Cloudflare by changing your DNS records from `A 104.xxx.xxx.xxx` to the A record you were previously assigned.

When you've identified what the problem was and are ready to turn Cloudflare back on, delete the A record and replace it with the A records Bubble provides.

</details>

<details>

<summary>Can I use a subdomain instead of a root domain?</summary>

Yes. You can connect a subdomain such as `app.yourapp.com` or `dashboard.yourapp.com`. The DNS records to configure are shown in the Bubble editor.

</details>

<details>

<summary>I'm getting 1001 errors.</summary>

Verify your A records, wait two hours, and check again.

Cloudflare has to verify that your A records are pointing to Bubble's IPs. If you're sure your A records are correct, Cloudflare will schedule a re-check of your records for several days (starting at 1 minute intervals, increasing to a maximum of 2 hours between checks). If, after 2 hours, your site is still showing 1001 errors, go to Bubble, delete your custom domain, wait one minute, and re-create it.

</details>

<details>

<summary>I am getting 1014 errors</summary>

If you're using Cloudflare for DNS, turn off proxying.

The most common cause of 1014 errors is when you're using Cloudflare for DNS, and you're using an "orange cloud" (proxy) setting. Click the orange cloud to turn off proxying (resulting in a grey cloud).

</details>

<details>

<summary>I'm getting 525 errors on my site.</summary>

Wait an hour and check again.

If you are a legacy customer who wasn't using SSL prior to switching over to Cloudflare, some of Bubble's servers won't have your updated certificate available to them. A 525 error happens when the Origin Server (where your app is hosted on Bubble) serves non-secure content (http) to a server that is expecting secure content (https).

We refresh your SSL and certificate settings on all servers once per hour, so if you've recently changed your settings wait and try again.

</details>

<details>

<summary>I've followed all the instructions here, and I still get "This Connection is Not Secure."</summary>

It's possible that we cannot issue SSL certificates on your domain. You will have to remove your CAA record and try again.

Go to the Google DNS lookup tool's [CAA tab](https://toolbox.googleapps.com/apps/dig/#CAA/) and check to see if your bare domain (e.g. `example.com`) has a CAA record. A CAA record restricts which certificate providers can issue certificates for your domain. (Only about 1% of Bubble apps have a CAA record.)

For instance, when you look up `google.com` with this tool, you can see the following record:

```
id 61662
opcode QUERY
rcode NOERROR
flags QR RD RA
;QUESTION
google.com. IN CAA
;ANSWER
google.com. 21599 IN CAA 0 issue "pki.goog"
;AUTHORITY
;ADDITIONAL
```

This means that *only* `pki.goog` is allowed to issue certificates for sites across the `google.com` domain.

SSL Certificate issuance is a critical part of making your site work on Bubble and Cloudflare, so if you find a CAA record, delete it. Your site should begin working in a matter of minutes.

</details>

<details>

<summary>Can I use multiple custom domains with the same app?</summary>

Each Bubble app supports one primary custom domain. To use additional domains, you can set up redirects through your domain registrar or DNS provider.

</details>

<details>

<summary>What happens to the default bubbleapps.io URL after I set up a custom domain?</summary>

The default URL continues to work alongside your custom domain.

</details>

<details>

<summary>Can I send emails from my custom domain?</summary>

Yes. Once a custom domain is set up, you can configure email sender addresses in the *Domain & Email* section of the *Settings* tab. Emails can then be sent from addresses such as `support@yourdomain.com` instead of the default Bubble address.

</details>

<details>

<summary>What if my domain isn't working after I set it up?</summary>

Common causes include DNS records that haven't propagated yet, incorrect values, or conflicting records left over from a previous setup. A tool like [dnschecker.org](https://dnschecker.org) can help you confirm that the records are pointing to the correct values. If the issue continues, contact your registrar or Bubble's Success team.

</details>

<details>

<summary>Can I change or remove my custom domain later?</summary>

Yes. You can update or remove your custom domain at any time in the *Domain & Email* section of the *Settings* tab. Changes take effect once DNS updates have propagated.

</details>

## Other ways to learn

<details>

<summary>Video lessons</summary>

* [How to set up a custom domain](https://www.youtube.com/watch?v=vGk6nfq41L4)

</details>

<details>

<summary>Relevant articles</summary>

[What is a RESTful API?](/help-guides/integrations/api/introduction-to-apis/what-is-a-restful-api)

Since the loading of a web page is technically one or more API calls between the server and device, this section on APIs covers the technical basics of what's going on in during a requests and response.

</details>

[^1]: In Bubble, you have the flexibility to run your app in two distinct environments: Development and Live.

    The Development environment serves as a sandbox, allowing you to make changes and preview your app without affecting the live version.

    The Live environment is what your end-users interact with.

    Notably, each environment has its own separate database, ensuring that data in development does not mix with live data.

    For enhanced collaboration among teams, Bubble offers the ability to create additional custom branches.

    Learn more about this feature in the following article:

    Article series: [Version control](/help-guides/maintaining-an-application/version-control)

[^2]: A registrar in this context is a company or organization accredited by the Internet Corporation for Assigned Names and Numbers (ICANN) or a national country code top-level domain (ccTLD) authority to register and manage domain names.

    Registrars provide services to the public that allow them to purchase and register domain names.

[^3]: A DNS server is like a phone book for the internet. It translates website names that we humans understand (like [www.bubble.io](http://www.bubble.io)) into numbers (called IP addresses) that computers understand, so your computer can find and connect to the right website.\
    \
    This is how your user's browsers finds its way to the Bubble server when they type in the URL of your app.\
    \
    In the context of your registrar, the DNS settings are needed to instruct the name server that the selected domain should lead to your Bubble app.

[^4]: This means that the '@' symbol is used instead of a blank space to refer to the root domain.

    For example:\
    Name: ""

    Value: 104.16.36.10\
    \
    Would instead be set up as:\
    Name: "@"

    Value: 104.16.36.10


# Native mobile

{% tabs %}
{% tab title="Global native mobile settings" %}
For a more in-depth look at native mobile settings, we recommend reading our article series on publishing your mobile app. Use the tabs above to access guides for each mobile platform.

Article series: [Publishing your native mobile app](/help-guides/publishing-your-app/native-mobile-app)
{% endtab %}

{% tab title="iOS app store settings" %}
The article below explains how to prepare your app for submission to the iOS App Store.

Article: [Publishing your app to the iOS App Store](/help-guides/publishing-your-app/native-mobile-app/ios-app-store)
{% endtab %}

{% tab title="Google Play Store settings" %}
The article below explains how to prepare your app for submission to the Google Play Store.

Article: [Publishing your app to the Google Play Store](/help-guides/publishing-your-app/native-mobile-app/google-play-store)
{% endtab %}
{% endtabs %}

The *Native mobile settings* tab contains configuration options specific to building and managing your native mobile app. Here, you can manage mobile-only features like app appearance, device permissions, platform-specific behavior, language and publishing settings for iOS and Android.

## Basic details

Enter the core information needed to prepare your app for submission to the app stores. This includes the app display name, icon, and splash screen background color. You can also optionally define a custom app scheme for handling deep links or app-to-app communication. For shareable https\:// links that open your app directly, see [universal links](https://manual.bubble.io/help-guides/publishing-your-app/native-mobile-app/global-native-mobile-settings#universal-links) — these don't use the app scheme.

## Apple App Store

{% hint style="info" %}
For a complete guide on publishing your mobile app to the Apple App Store, we recommend reading our dedicated article on the topic:

Article: [Publishing your app in the iOS App Store](/help-guides/publishing-your-app/native-mobile-app/ios-app-store)
{% endhint %}

Provide the credentials and identifiers required to submit your app to Apple’s App Store. This includes your Bundle ID and Team ID, which link your app to your Apple Developer account.

### App Store Connect API

Enter your Key ID, Issuer ID, and upload your private key to enable automated submission and management through the App Store Connect API.

### Apple push notification service

To enable push notifications on iOS, provide your APNs Key ID and upload your APNs key. These credentials allow your app to communicate with Apple’s notification service.

### Universal links

**iOS App Store ID:** your app's numeric Apple ID from App Store Connect, used to send iOS users to your App Store listing when they open a universal link with the App store fallback without the app installed. This field appears after your App Store Connect settings have been validated.

## Android Play Store

{% hint style="info" %}
For a complete guide on publishing your mobile app to the Apple App Store, we recommend reading our dedicated article on the topic:

Article: [Publishing your app in the Android Play Store](/help-guides/publishing-your-app/native-mobile-app/google-play-store)
{% endhint %}

### Android Play Store settings

Enter the information required to submit your mobile app to the Google Play Console. This includes your Package name, JSON key, and code signing credentials such as the code signing key, alias, and password.

### Firebase Cloud Messaging

To enable push notifications on Android, upload your Firebase service account key and google-services.json file. These files connect your app to Firebase for notification delivery.

### Universal Links

**Android SHA-256 fingerprint:** a comma-separated list of your app's signing certificate fingerprints from the Google Play Console, used for universal linking. This field appears after your Play Store settings have been validated.

## Builds and live versions

This is where you manage builds and live versions.

Before submitting your app to the app stores, it’s helpful to understand how builds, OTA (over-the-air) updates, and live versions work. These concepts form the foundation of mobile deployment and give you more control over how and when updates reach your users.

This includes understanding how version numbers work, when new builds are required, how OTA updates differ from full submissions, and how to manage and deprecate older live versions of your app over time.

For a full breakdown, we recommend reading our dedicated article:

Article section: Native mobile apps: [Understanding Builds, OTA Updates, and Live Versions for deployment](#builds-and-live-versions)

## Device permissions

This is where you manage which device features your app will request. This is needed to stay compliant with store guidelines. You can read more about how device permissions work in the article below.

Article section: [Global native mobile settings – Device permissions](/help-guides/publishing-your-app/native-mobile-app/global-native-mobile-settings#device-permissions)

## Languages

This is where you manage the language or languages your app will support. Defining this helps meet app store requirements and ensures users see accurate localization in the store listings. You can read more about localization on mobile in the article below:

Article section: [App texts (translated strings) and languages in native mobile apps](/help-guides/data/static-data/app-texts-translations#app-texts-in-native-mobile-apps)


# Logs tab

The Logs tab gives you access to three different tools:

## App metrics

The app metrics section is where you can analyze the workload[^1] that your app uses. Workload represents how hard the server is working to support your app. Through the app metrics charts, you can discern where most of the server's effort is directed.

Article series: [Pricing and workload](/account-and-marketplace/account-and-billing/pricing-plans)

## Server logs

Every action performed by Bubble is logged so that you can look at when and how it was executed. This aids in debugging processes.

Article: [Server logs](/help-guides/maintaining-an-application/testing-an-application/using-server-logs)

## Scheduler

API workflows can be scheduled to run at a specific time. When an API workflow is scheduled, it can be viewed in the API workflow scheduler. From here you can pause or cancel the workflow.

Article series: [The Workflow API](/help-guides/integrations/api/the-bubble-api/the-workflow-api)\
Article: [The workflow API scheduler](/help-guides/maintaining-an-application/scheduler)

[^1]: Workload, in essence, measures the work that Bubble performs to power your application. As your app processes actions such as loading a page or querying your database, it contributes to your monthly workload consumption.


# Tools

This article series covers the different tools available in the Bubble editor.

## Key features

* [Saving](/help-guides/getting-started/navigating-the-bubble-editor/tools/key-features#saving-your-app)
* [Undo/Redo](/help-guides/getting-started/navigating-the-bubble-editor/tools/key-features#undo-and-redo)
* [Shortcut to the docs](/help-guides/getting-started/navigating-the-bubble-editor/tools/key-features#access-to-the-reference)
* [Page selector](/help-guides/getting-started/navigating-the-bubble-editor/tools/key-features#page-selector)
* [View, edit, arrange and contextual menus](/help-guides/getting-started/navigating-the-bubble-editor/tools/key-features#the-edit-view-and-arrange-menus)

## The search tool

The search tool lets you search for almost anything in your app using different constraints.

Article: [The search tool](/help-guides/getting-started/navigating-the-bubble-editor/tools/the-search-tool)

## The issue tracker

The issue tracker gives you an overview over issues in your app (such as invalid expressions and missing properties).

Article: [The issue tracker](/help-guides/getting-started/navigating-the-bubble-editor/tools/the-issue-tracker)

## The debugger

The debugger lets you check your workflows and element properties in real-time when you preview your app.

Article: [The debugger](/help-guides/getting-started/navigating-the-bubble-editor/tools/using-the-debugger)\
Article: [Previewing your app](/help-guides/getting-started/navigating-the-bubble-editor/previewing-your-app)

## Notes

You can add notes to almost any object in the Bubble editor.

Article: [Notes](/help-guides/getting-started/navigating-the-bubble-editor/tools/notes)


# Key features

## Saving your app

Bubble saves your app automatically, in real time. As soon as you modify an element, action, setting, etc., the change will be saved. You will see a 'saving' notification in the top bar for a brief instant, which will change to 'saved' as soon as the saving process is finished.

### Saving errors

Occasionally, you may experience an error when Bubble is trying to save the latest changes. This is usually caused by a weak connection and will be resolved as soon as your device is back online.

The *Saving...* notification in the Bubble toolbar will turn red if it cannot communicate successfully with the server. If this occurs, we advise pausing your work to reduce the risk of data loss.

<figure><img src="/files/VLkSP3BO8gv6PeMfXLOf" alt=""><figcaption></figcaption></figure>

## Undo and redo

You can undo or redo any changes made to your app. Use the respective icons in the top bar to revert or reapply modifications. If you are making significant changes or experimenting with your app and might want to revert to a particular version, we recommend using the [version control](#user-content-fn-1)[^1] feature.

## Shortcuts to the core reference <a href="#access-to-the-reference" id="access-to-the-reference"></a>

You can access the core reference docs from the editor through the 'See reference' tool-tip. When you hover over an icon or a field for a few seconds, the tool-tip will appear. Clicking on the tool-tip will open a new tab and take you to the right place within the docs. If you already have the docs open, then you'll instantly redirect to the right place.

<figure><img src="/files/0vmmWUaCFyzopmNyOk7w" alt=""><figcaption><p>Hovering an object in the Bubble editor for a second will display the <em>See reference</em> tooltip, which takes you to the relevant entry in the core reference.</p></figcaption></figure>

You can read more about the difference between the user manual and the core reference in our [Introduction section](/#using-the-bubble-docs).

## App Interface Manager

The app interface manager is where you navigate your Bubble app's web and mobile content, like pages, views and reusable elements.

### App switcher

The app switcher lets you toggle between the web and native mobile versions of your app. It provides a simple way to navigate and manage each version's structure within the editor.

To navigate between web and native mobile, select the relevant version in the upper right corner:

<figure><img src="/files/4F5y03gsksgZSmmpV2hg" alt=""><figcaption><p>By selecting <em>Web app</em> or <em>Mobile app</em> in the app switcher, you can easily switch between the two versions of your app.</p></figcaption></figure>

### Navigating pages, views and reusable elements

After selecting the web app or mobile app, your app interface manager will display the relevant information:

<figure><img src="/files/BEMVJdq0We5hNitdcA6C" alt=""><figcaption><p>In this example, we've selected <em>Mobile</em> and you'll see all content related to your native mobile app.</p></figcaption></figure>

* **Web**:
  * **Pages:** the different pages in your web app.
    * **Reusable web element:** collections of elements that can be reused across your web app. Marked with a purple color.
* **Mobile:**
  * **Views:** lists all the views you have created in your app.
    * **Reusable mobile elements:** collections of elements that can be reused across your native mobile app. Marked with a purple color.
* **Folders:** allow you to organize your pages, views and reusable elements into folders for quick access. See more [below](#working-with-folders).

### Working with folders

Pages, mobile views, and reusable elements can be organized into folders for easier navigation. Folders are only visible to you and do not impact your app's functionality or appearance for users. Folders do not change or impact the URL of any pages contained within them.

#### Creating a new folder

To create a new folder, click the *Add new* button in the upper right corner of the app interface manager. Select folder, and give the folder a name.

#### Adding a page, view or reusable element to a folder

To add a page, mobile view or reusable element to a folder:

* Hover the relevant page, view or reusable element.
* Click the contextual menu `...`
* Click *Manage folder* and select a folder.

#### Removing a page, view or reusable element from a folder

To remove a page, mobile view or reusable element from a folder:

* Hover the relevant page, view or reusable element.
* Click the contextual menu `...`
* Click *Manage folder* and select *Remove folder.*

## The Bubble menu

The Bubble menu gives you access to the main menus:

* Edit
* Arrange
* View
* Grids & Guides

For more details on this menu, see the links below:

Reference: [Toolbar](/core-resources/bubbles-interface/toolbar) | [Bubble menu](/core-resources/bubbles-interface/toolbar#the-bubble-logo-main-menu)

[^1]: Bubble's version control system lets you divide the development of your project into independent parts, so that you and other editors with access to the project can iterate on one part of the app without impacting other parts.\
    \
    Article series: [Version control](/help-guides/maintaining-an-application/version-control)


# The search tool

{% hint style="info" %}
This page explores using the *Search tool*, which lets you search for objects in the Bubble editor. Not to be confused with *Do a search for,* which is used to perform database searches in your app. You'll find more information about that feature in the article below:

Article: [Finding data](/help-guides/data/the-database/finding-data)
{% endhint %}

The search tool lets you search for a range of different objects in the Bubble editor, such as elements, data types, and actions.

{% embed url="<https://www.youtube.com/watch?v=ks6WkQ--ESY>" %}

## Accessing the search tool

The search tool is a part of Bubble's toolbar and is accessed by clicking the magnifier symbol.

<figure><img src="/files/iBlpAjFOYkE2lFgYMfkw" alt=""><figcaption></figcaption></figure>

## Search by type

By specifying the type of object you want to search for, Bubble offers filters specifically for that type.

For example, if you want to search for a specific type of *action*, you can select *Action type* in the *Search by* field. Bubble will then show you a list of the different action types available.

<figure><img src="/files/t23GkPWoqPcUuLK8hgAH" alt=""><figcaption><p>By setting the <em>Search by</em> to a specific type, Bubble offers filters that match that type.</p></figcaption></figure>

Clicking on any result in the search tool will take you directly to that part of the editor. In the example above, we're searching for an *action*, and clicking a result will take you directly to that action in the workflow tab.

## Contains text

The *Contains text* tool lets you search for text inside of the different types you are looking for. This option is fairly flexible, as it broadens the search to include text from sub-objects as well as the main object.

For instance, let's say you have a workflow that consists of an event and one action. The event is a button-click, and the action makes changes to a thing. We've saved the label *Save average score* on the action.

<figure><img src="/files/7FnZJRjdvnrYYhwui5tg" alt=""><figcaption></figcaption></figure>

In this example, we have:

1. Set the *Search by* field to *Event type*
2. Selected *An element is clicked* as the event type
3. Entered the string *average* in the *Contains text* field

As you can see, the search tool returns the event we were looking for, even though the event itself doesn't contain the string *average*. That string is saved on an action *inside* of the workflow. In this way, the search tool will not only return results from event names or descriptions but also delve into the actions within those workflows.

## Searches and performance

When you're working in a big app, the search tool can potentially have a lot of data to search through and can start to slow down. You will usually get a result a lot faster if you narrow down the search to the current page


# The Issue Checker

The Issue Checker helps identify and resolve problems in your application. When you're building your app, Bubble automatically checks for inconsistencies and errors, and lists the results in the Issue Checker tool.

## Accessing the issue checker

The search tool is a part of Bubble's toolbar and is accessed by clicking the issue counter. Note that if there are no issues, the counter will not be visible.

<figure><img src="/files/mEYSYmZ5bT8bnazNW89A" alt=""><figcaption><p>Click the image to enlarge.</p></figcaption></figure>

## How the issue checker works

The Issue Checker can flag a variety of issues, from missing data sources to invalid actions in workflows. For example, if you set up text element with a dynamic expression, and that expression is not returning a text value, the Issue Checker would flag this as an issue.

<figure><img src="/files/2dqwWrRcWD3rTeWudtac" alt=""><figcaption></figcaption></figure>

The Issue Checker dynamically monitors your app for potential errors or problems. As you make modifications, it automatically updates to highlight any new issues or clear out those that have been resolved. This real-time feedback ensures that you can address any potential problems immediately, helping to maintain the integrity and functionality of your application.

{% hint style="warning" %}
**Note:** some issues, especially those that involve intricate calculations or dependencies, might take a bit longer to be detected and displayed. In some cases, the issue(s) might not appear until you navigate to the specific page in the editor where they exist. Always ensure you thoroughly review each page of your app before you deploy to make sure all issues are caught.
{% endhint %}

You cannot deploy your app to Live if there are issues. We recommend keeping the number of issues to a minimum at all times, preferably at zero.

## Disabling the Issue Checker

The Issue Checker can be completely disabled by adding a URL parameter in the URL of the Bubble editor.

```url
&issues_off=true
```

The full URL of the editor would then look something like the below:

{% code overflow="wrap" %}

```url
https://bubble.io/page?type=page&name=index&id=my-bubble-application&tab=tabs-2&issues_off=true
```

{% endcode %}

{% hint style="warning" %}
We generally discourage disabling the Issue Checker, as it's important to keep the number of issues in your app at zero. Additionally, disabling the Issue Checker lets you deploy the app even with potential issues, which can lead to a live app not functioning properly.
{% endhint %}

{% hint style="danger" %}
**The issue checker and API workflow names:** There’s a known limitation in the issue checker for backend workflows. If an issue is flagged for a **missing API workflow name**, it may not clear immediately after you update the name. To refresh the issue checker in these cases, switch away from the backend workflows tab and then return to it, or **refresh the page** entirely. This ensures the issue checker is using the most up-to-date backend workflow information.
{% endhint %}


# The debugger

When encountering unexpected behavior, the debugger comes in handy for closely examining each step of a workflow or the details of an element as the app is being used.

The debugger primarily serves two key purposes:

* **Run workflows action-by-action** and check data (such as the result of a search) related to each step
* **Inspect the elements on the page** to check their attributes, conditions and associated data

The debugger does not have any visible parts in the Bubble editor, but is visible when you run your app in Development.

## Enabling and disabling the debugger <a href="#activating-the-debugger" id="activating-the-debugger"></a>

When you click *Preview* to see your app in run-mode, the debugger is automatically activated. If you want to enable or disable it, you simply have to change the [URL parameter](#user-content-fn-1)[^1] *debug\_mode*. When the debugger is enabled, you will see the parameter in the URL:

```
debug_mode=true
```

A full URL will look like the following:

```
https://my-bubble-application.bubbleapps.io/version-test?debug_mode=true
```

...or if you have multiple URL parameters:

{% code overflow="wrap" %}

```
https://my-bubble-application.bubbleapps.io/version-test?parameter=key&debug_mode=true
```

{% endcode %}

The debugger is meant for desktop use and is not designed to work on mobile.

To disable the debugger, simply remove the parameter, or set its value to *false*.

## Using the debugger

When the debugger is active, you will see a bar at the bottom of the screen when you preview your app:

<figure><img src="/files/sjQKv13NqUtESw1hqric" alt=""><figcaption><p>When the debugger is active you will see it as a bar at the bottom of the screen.</p></figcaption></figure>

{% hint style="info" %}
When the debugger is enabled, Bubble automatically adds space at the bottom of the page. This is only visible in debugging mode and not to your Live users.
{% endhint %}

<figure><img src="/files/nqRCaYz9P5SoZ3MKrThc" alt=""><figcaption></figcaption></figure>

* The **left-hand side** shows the different controllers for inspecting **workflows**
* The **right-hand side** shows the controls for inspecting **elements**

### Workflows

The left side of the Debugger is the Workflow Debugger. You can see three buttons that control how the debugger behaves when a workflow is being triggered. Three modes are possible:

1. **Normal** mode runs workflows without interruption.
2. **Slow** mode runs workflows with a a one-second pause between each action
3. **Step-by-step**' mode lets you control the execution of the workflow by pausing between each action until you click *Run next* (only visible when step-by-step mode is enabled)

Step-by-step is the most widely used debugger mode, as it gives you complete control over each action step, allowing you to progress at your own pace.

When the mode is active, Bubble will work for a workflow to be triggered by an event. As soon as that happens, it will pause on the event itself so that you can check what triggered it and any associated data.

<figure><img src="/files/SEUfw2M45ThXP3uJLF4j" alt=""><figcaption><p>The first step the debugger will show is the event that triggered the workflow. In this case it was a button-click. The step we are currently inspecting is marked in grey, and the next step is in white.</p></figcaption></figure>

Clicking *Run next* will move on to the the next step: the *Create a new Product* action:

<figure><img src="/files/OMqa6GlB8lCZy4EwpC4O" alt=""><figcaption></figcaption></figure>

Note the numbers in the screenshot above:

1. In this example, we inspect the *Name* field of the Product we are creating, where the value "T-shirt" is displayed. By clicking the value, we can trace the data source from which it is derived.
2. After clicking the value, we can see its data source on the right-hand side: in this example the value came from an input form called *Input Product name*.

By pausing at each step and examining the details of each action, you can verify if the data yields the anticipated values and is saved as intended.

{% hint style="info" %}
The debugger status is saved when the page is refreshed. If a workflow navigates to a different page or triggers a page refresh, the resuming workflow will execute subsequent actions in the same mode.
{% endhint %}

### Adding breakpoints <a href="#adding-breakpoints" id="adding-breakpoints"></a>

When working on complex pages with numerous workflows, the step-by-step mode might not be ideal since it stops too frequently. If you want to investigate a specific workflow, event, or action, you can add a breakpoint that activates the debugger in step-by-step mode when that event or action is executed.

<figure><img src="/files/wIVK30WEYWmgfqNACILY" alt=""><figcaption></figcaption></figure>

Breakpoints are added in the workflow editor, either on an event or an action. As soon as Bubble encounters that event or action, it will pause and enable step-by-step mode from that point forward.

{% hint style="info" %}
Note that this setting only has an effect when the debugger is active (meaning debug\_mode=true appears in the URL) and will not influence how your application runs in Live.
{% endhint %}

## Inspecting elements

Sometimes, you may need to determine why an element is displayed in a specific manner, particularly when using conditions or displaying data. The debugger lets you select an element on the page and view the list of conditions and fields, along with their values.

<figure><img src="/files/qJKoWIWgiYi6tsTKunlK" alt=""><figcaption></figcaption></figure>

First, to enable Inspect mode, click the *Inspect* button in the bottom right corner. There are two ways to select an element to inspect:

* You can click the element on the page (workflows will not trigger when you are in inspect mode)
* You can use the dropdown list next to the *Inspect* button and search for/select the element from there (this is useful for elements that are invisible).

### Understanding expression evaluations <a href="#understanding-expressions-evaluations" id="understanding-expressions-evaluations"></a>

When an element is selected, you can start to evaluate its properties, conditions and expressions. In the example below, we have selected the *Create Product* button.

<figure><img src="/files/uhfwPfakSpbS0jG1ufEZ" alt=""><figcaption></figcaption></figure>

Conditions are displayed in a separate list under the header *Conditions*. If they are in red color, it means the condition does *not* return true. If the condition is true, it will be displayed in a green color.

### Digging into expressions

By clicking the relevant expression[^2], you can take a closer look at each step and how it evaluates.

<figure><img src="/files/vw6X2RsUuf0GjNdXHdQ1" alt=""><figcaption></figcaption></figure>

In the example above, we can check the last part of the expression: *is logged in*. We can see that this part of the expression returns a *no*.

If we click the first part of the expression, we can see the parameters associated with the data source *Current user:*

<figure><img src="/files/VD5rfAZDvkFLLWQsbR30" alt=""><figcaption></figcaption></figure>

Any expression can be inspected in this way. Most fields on this user are empty since they are logged out, but you can see the unique ID and Created/Modified date that Bubble generates for all users who visit your app.

Each expression allows you to inspect every data source, operator and comparison. Any sub-expressions are also accessible by clicking them.

## Run-mode execution errors

The debugger also lets you identify run-mode execution errors, such as when an API call to a service returns an error due to a missing parameter. When a workflow or element encounters an error, the debugger icon turns red and becomes clickable.

Clicking on it reveals the list of errors.

When you encounter an issue, particularly when using external services through plugins, checking for execution errors in the debugger should be one of your initial troubleshooting steps.

<figure><img src="/files/5zcZRHnVcg1WHLlZdEfd" alt=""><figcaption></figcaption></figure>

## Other ways to learn

<details>

<summary>Video lessons</summary>

* [How to use the debugger](https://youtu.be/UNtXp_VDssk)

</details>

[^1]: A *URL parameter* is a piece of information added to the page's URL after a question mark (?).\
    \
    It can be used to store data and for navigation.

    Article: [Navigation](/help-guides/logic/navigation)

[^2]: *Expressions* are dynamic pieces of data or calculations that are used within elements, workflows, or conditions to define values, set up booleans, or manipulate data.

    Article: [Dynamic expressions](/help-guides/logic/dynamic-expressions)


# Notes

This section covers how to add notes to different parts of your Bubble app to document your work

As you develop your Bubble application you may find the need to document specific parts of it. For example, you may need to write down what a specific workflow is for, or add some notes about where to use a specific style.

<figure><img src="/files/RkG3tBLBDEWDvzshHClR" alt=""><figcaption><p>In this example we have added a comment to the OK button.</p></figcaption></figure>

Bubble has a built-in note system that lets you save text comments for yourself and your collaborators. These notes are only visible in the Bubble editor, and cannot be seen by your app’s users. They are visible to all collaborators, regardless of their access level.

You can add notes in the following places:

* [Data types](#user-content-fn-1)[^1]
  * Data type fields
* [Option sets](#user-content-fn-2)[^2]
  * Option set options
  * Option set attributes
* Elements[^3]
* Events[^4] (workflows)
  * Actions
* [API Connector](#user-content-fn-5)[^5] connections
  * API Connector calls
* [Privacy rules](#user-content-fn-6)[^6]
* Styles[^7]

Notes can be added to each unique instance of the above types, meaning for example that every element and action you add can have its own note.

## Managing notes

### Adding and editing notes

The ability to add comments to something in the Bubble editor is displayed with the note icon:

<figure><img src="/files/1Tg2VPDtYKR1RmDUVEET" alt=""><figcaption></figcaption></figure>

Clicking that icon will open up the *Comments* panel and let you add or edit a comment on the relevant item.

<figure><img src="/files/2hYChZUeOlzqb5xn2FT9" alt=""><figcaption><p>Clicking the <em>Note</em> icon will open up the Notes sidepanel. In this example we're looking at the field of a data type called <em>City</em>.</p></figcaption></figure>

Whenever an item has a note saved on it, the icon gets a fill color:

<figure><img src="/files/FeLlJy9as0FiA2bIP5qI" alt=""><figcaption></figcaption></figure>

### Deleting notes

To delete a note, simply open it up for editing and remove its text content.

## Browsing all notes

To see all the notes in your app in a list, click the *See all* button in the notes sidepanel:

<figure><img src="/files/Y2cDqhY4kPqesQPNH2Hs" alt=""><figcaption></figcaption></figure>

[^1]: The database consists of different *data* *types* that you as the developer plan and set up.

    Data types are like containers that hold specific types of information and each data type is made up of *fields* where that information is stored.\
    \
    Article: [Data types and fields](/help-guides/data/the-database/data-types-and-fields)

[^2]: Option sets let you set up different types of static options in a database-like structure, but without using the database.

    This is useful to store information like days of the week, marital status, colors, states, countries and other data that you want to load quickly and that's rarely updated.\
    \
    Article: Option sets

[^3]: Everything you place on a Bubble page, such as input fields, buttons, images and icons, are known as *elements*.\
    \
    They make up the visual part of how users interact with your app.\
    \
    Article series: [Bubble elements](/help-guides/design/elements)

[^4]: A workflow consists of an *event* which triggers it, and *actions* that run when that triggering happens.\
    \
    This is how user interaction leads to something happening in your app. For example, a button click could be the *event* and *Log the user in* could be the action.\
    \
    Article series: [Workflows](/help-guides/logic/workflows)

[^5]: The *API Connector* is a Bubble-built plugin that lets you set up connections to third-party apps and systems via outbound API requests.\
    \
    Article series: [API](/help-guides/integrations/api)\
    Article: [The API Connector](/help-guides/integrations/api/the-api-connector)

[^6]: Privacy rules are conditions that you set up on each data type in order to protect the data from being viewed and edited by unauthorized users.

[^7]: Bubble lets you set up overarching Styles for elements within your app to streamline the design process

    By modifying a single style, you can restyle all related elements, making it easy to set up and manage a cohesive look and feel in your app.\
    \
    Article: [Styles](/help-guides/design/variables-and-styles/styles)


# Previewing your app

Bubble is a WYSIWYG[^1] editor, which means that what you see in the editor is generally what you will see in the finished app. Every alteration you make within Bubble is instantly saved, allowing you to preview your evolving app on-demand.

<figure><img src="/files/GqFsk1rEcjCNBj3kcKSr" alt=""><figcaption></figcaption></figure>

To preview your app, click the *Preview* button in the upper right corner of the Bubble toolbar. This will open up a new tab where your app will load.

## Test and Live environments

By default, your application is split into two distinct environments: Development and Live. This structure allows you to continue refining your application without affecting the live version. Whenever you click the *Preview* button, Bubble automatically runs the Development environment, often called *version-test*.

You can recognize this by the URL that Bubble opens:

<figure><img src="/files/yBl9qXwBdISJdabkEJa4" alt=""><figcaption><p>The URL shows that this is the preview of your app, and not the Live environment</p></figcaption></figure>

To run the app in Live, you first need to deploy[^2] it.

## *Run as* a specific user

When you preview your app, you can run the app as a specific user to test how the app behaves as that user. This is particularly useful when you work with [privacy rules](#user-content-fn-3)[^3] and conditions[^4]: some users may not have access to specific data and actions, and by running the app as different types of users, you can make sure that it behaves in the expected way.

The *run as* feature is found in the *Data – App Data* section when you have the user data type selected. You'll see the list of users in the data table, and on the left-most column of the table you'll see a text link that says *Run as*.

<figure><img src="/files/nstsil703VaY9XlkUQqM" alt=""><figcaption><p>The <em>Run as</em> feature lets you preview the app as a specific user.</p></figcaption></figure>

### *Run as* in Live

You can also run as a specific user in your live app. To do that, switch the database view to the live database (see the red link that says *Switch to live database*) in the upper right corner of the screenshot above).

## Safe modes

If you are experiencing issues in your app, you can run it in different *Safe modes* to try to identify where the problem lies. To preview the app in safe mode, press and hold the Preview button. You will see a menu that lets you choose between a few different modes.

Running in safe mode does not affect Live in any way.

### Safe - no custom HTML

This will preview the app without any added custom HTML.

### Safe - no community plugin

This will disable any community-built plugins before previewing. Note that Bubble-built plugins will still be enabled.

### Safe - no community plugin and HTML

This will both disable community-built plugins and HTML. Note that Bubble-built plugins will still be enabled.

### Disabling safe mode

Running any of these preview modes will add a URL parameter to your preview's URL. To disable the safe mode, you can either:

* Remove the URL parameter from the URL
* Click the *Preview* button in the upper right corner again without holding

## The debugger

Previewing your app also give you access to the debugger. The debugger lets you troubleshoot and inspect the real-time behavior of your app. It provides insights into workflow execution and element properties.

Article: [The debugger](/help-guides/getting-started/navigating-the-bubble-editor/tools/using-the-debugger)

[^1]: "WYSIWYG" stands for "What You See Is What You Get." In the context of the Bubble editor, it means that the layout and elements you arrange and design in the editor will look and behave similarly when previewed or live.

[^2]: Deploying your app means transferring or updating the current version of your application from the Development environment to a Live environment, making it accessible to end-users.\
    \ <br>

    Article: [Deploying your app](/help-guides/publishing-your-app/deploying-your-app)

[^3]: *Privacy rules* are conditions that you set up on each data type in order to protect the data from being viewed and edited by unauthorized users.\ <br>

    Article: [Protecting data with privacy rules](/help-guides/data/the-database/protecting-data-with-privacy-rules)

[^4]: *Conditions*, or conditional expressions\[^5], let you set up mechanisms that check whether a specific question returns a *yes* or a *no* answer and then take an action, stop an action or make a change in your app in response.

    Article: [Conditions](/help-guides/logic/conditions)<br>

    Article: [Dynamic expressions](/help-guides/logic/dynamic-expressions)


# Transitioning to Bubble from...

Many come to Bubble from traditional development backgrounds such as [JavaScript](/help-guides/getting-started/transitioning-to-bubble-from/javascript), [HTML/CSS](/help-guides/getting-started/transitioning-to-bubble-from/html-and-css), and [SQL](/help-guides/getting-started/transitioning-to-bubble-from/sql). These, and other languages, are standards that have evolved over decades and are supported by all major browsers. Making the transition to Bubble can feel a bit different, given that Bubble has a consistently visual approach to design, interactivity, and data management.

So, how come all major browsers also support Bubble so well?

The answer lies in the fact that Bubble uses the same tried and tested methods to render pages, enable user interaction, and read, create, and manipulate data. Bubble is built on top of these foundational web technologies, ensuring compatibility and leveraging the strengths of traditional development practices. The app that Bubble delivers to the web browser is a combination of HTML/CSS, JavaScript, and a database built on PostgreSQL.

This ensures that Bubble can host secure, highly compatible, and efficient applications without straying from its original vision: to empower anyone to build and launch fully functional web applications without writing any code. To make this vision a reality, Bubble doesn’t simply offer the “absence of code” but instead sets up your app as a central hub that addresses a range of technological challenges from one place:

* Designing pages
* Setting up and running workflows
* Managing databases
* Communicating with external apps and systems through APIs
* Managing user authentication securely
* Scaling from one, to a hundred, to millions of users

While it’s technically true that Bubble “generates” code for you, this doesn’t mean you can export this code and run it somewhere else. The ease and speed with which apps are built rely on the engine of the Bubble server, which is specifically designed to host millions of apps and help users build and scale those apps efficiently and securely. The two sides (client-side[^1] and server-side[^2]) are in constant communication, and this integrated approach ensures that Bubble provides a seamless experience, combining powerful development tools with robust hosting infrastructure. While you are certainly welcome to understand how the underlying infrastructure works, its hidden complexity is one of Bubble’s main benefits for non-technical users.

However, this holistic approach can lead traditional coders to approach Bubble expecting the intricacy and methodologies they are accustomed to. With many processes automated and simplified, traditional coding practices and terminology may not always apply.

This article series will explore the transition from traditional coding to Bubble, and how common methods and terms/phrases change as you make this shift.

<details>

<summary>JavaScript (and other similar languages)</summary>

This article explores common methods and terms used in JavaScript and how you can use Bubble’s tools to achieve similar outcomes. Those coming from languages similar to JavaScript, such as Python, Ruby, and PHP, will likely find this article useful as well.

Article: [Transitioning from JavaScript to Bubble](/help-guides/getting-started/transitioning-to-bubble-from/javascript)

</details>

<details>

<summary>HTML/CSS</summary>

Bubble's design engine is entirely visual, relying on a drag-and-drop interface to control the look of the page and its elements, its responsive properties, and its styling. While the underlying logic has much in common with HTML and CSS, this article will explore how Bubble achieves this without requiring any code.

Article: [Transitioning from HTML/CSS to Bubble](/help-guides/getting-started/transitioning-to-bubble-from/html-and-css)

</details>

<details>

<summary>SQL</summary>

Bubble hosts and manages your app’s databases automatically, simplifying both the methodology and terminology of building relational databases. In this article, we’ll explore how creating a database in Bubble differs from traditional SQL methods.

Article: [Transitioning from SQL to Bubble](/help-guides/getting-started/transitioning-to-bubble-from/sql)

</details>

[^1]: *Client-side* in this context means the user's device.

[^2]: *Server-side* in this context means the Bubble server.


# JavaScript

For those accustomed to working with JavaScript, transitioning to Bubble will in many ways feel similar. This article will explore the transition from JavaScript to Bubble specifically, but since several programming languages like Python, Ruby, and PHP share many commonalities with JavaScript, you may find it useful if you come from a different language as well.

Before diving into the specifics, it's helpful to understand Bubble's philosophy: it is designed to empower anyone to build and launch fully functional web applications without writing any code. This is important when transitioning from a traditional coding background like JavaScript, as it highlights a crucial aspect of the platform: while Bubble is often called a no-code platform, its core feature set extends far beyond the absence of written code. Bubble offers a way to create designs and workflows using a graphical interface, but it also hosts your app’s database, manages CSS-like styles, provides its own client-side and server-side workflow engine, supports a robust inbound and outbound API system, and includes comprehensive tools for user authentication, data privacy, and app deployment.

Bubble strives to provide an all-in-one visual platform; essentially, the less users have to worry about the intricacies of the underlying hosting infrastructure, the more they can focus on building their applications.

For seasoned JavaScript developers, Bubble’s holistic approach can initially feel different. JavaScript development traditionally involves hands-on control over every aspect of the application stack—from writing and optimizing code to managing servers and configuring build tools. Bubble abstracts much of this complexity away, packaging these capabilities into an integrated visual interface. While this may require an adjustment, it ultimately frees developers from the tedium of manual coding and server management. This shift allows them to focus more on creativity and user experience, and it opens up app creation to a whole new group of non-technical but creative individuals.

For developers transitioning from JavaScript to Bubble, it’s interesting to know that Bubble itself is built on top of JavaScript. This foundation means that while you don't need to write JavaScript code directly, the principles and logic that drive your applications in Bubble are rooted in the same technology. In other words, transitioning to Bubble may require some mental adjustments, but having a traditional coding background can give you an advantage in no-code development as well. Bubble also supports extending your app with custom JavaScript code.

First, let's examine the differences in terminology between the two platforms, before delving into their distinct approaches to app development.

## Terminology

Direct comparisons between terms used in JavaScript and those in Bubble can be of limited use, as each term doesn't always have a direct counterpart. In this section, we’ll instead look at some well-known terms used in JavaScript, and then different concepts in Bubble that can serve a similar meaning or purpose when you develop.

### Event listener

An event listener in JavaScript is a function that waits for a specific event to occur on a specified element. When the event occurs, the event listener executes a block of code. This mechanism allows developers to create interactive web applications by responding to user actions such as clicks, key presses, or form submissions.

In Bubble, this is handled by workflow[^1] [*events*](#user-content-fn-2)[^2]. When an event occurs, such as a button click, page load, or data change, it initiates a series of actions defined in the workflow.

<figure><img src="/files/Y84VHKPbxoBjYWoZjhLg" alt="" width="375"><figcaption><p>In Bubble, you build workflows by combining an event (1) and one or more actions (2).</p></figcaption></figure>

It’s worth noting that the two terms are not exactly the same: events in Bubble encompass a broader range of triggers, not just those related to elements. For example, a button or icon being clicked is considered an event, but you can also set up events that react to changes in the database or other non-element interactions.

### Function

A function is a block of code designed to perform a specific task. Functions take inputs (arguments or parameters), process them through a series of statements, and then return an output. They are fundamental building blocks for creating reusable, modular code. Functions help manage complexity by breaking down tasks into smaller, manageable parts.

<figure><img src="/files/9rezIdyep42wH5JxUYCH" alt="" width="563"><figcaption><p>In Bubble, a <em>workflow</em> consists of an <em>event</em> and one or more <em>actions.</em></p></figcaption></figure>

In Bubble, workflows are the equivalent of functions but are designed to be more accessible through the visual interface. A workflow[^1] in Bubble is a sequence of steps triggered by an event[^2], such as a button click, page load, or data change. Each step in the workflow performs an action[^3], such as modifying data, [navigating to a page](#user-content-fn-4)[^4], or displaying an alert - some actions could themselves be viewed as the equivalent of smaller, simpler functions. You can read more about workflows and its components in the article series below.

Article series: [Workflows](/help-guides/logic/workflows)

### Variable

In this context, we mean data that is stored temporarily for some purpose. Bubble doesn’t use the term “variable” directly but offers different ways to store data during a user’s session, as opposed to storing it permanently in the database.

#### Custom states

Custom states in Bubble function as temporary storage fields that you can add to any element on the page through the Bubble editor. They can hold built-in field types such as text, number, and date, as well as custom data types created in the database or data from external sources like APIs or plugins. These states can store a single item or a list (array).

Custom states are created visually in the Bubble editor and can be used in workflows and as data sources. They can be pre-populated with static data like text or numbers, but otherwise need to be populated using the [*Set state of an element*](#user-content-fn-5)[^5] action. While they are not inherently dynamic, they rely on workflows for their population and modification.

Custom states are useful to hold information throughout a single page session. If the user closes the tab or navigates to another page, the information is lost.

You can read more about custom states in the article below.

Article: [Custom states](/help-guides/data/temporary-data/custom-states)

#### Container element data sources

Container elements such as groups and repeating groups can hold dynamic data in their respective data source field. This can be referenced from other dynamic expression using the element as a data source, such as `Group Task's task`, Group Text's text or `Repeating Group Tasks's List of Tasks`.

Note that if you are using these elements exclusively to store variables (as opposed to displaying something on the page), you may want to look into using [reusable element custom properties](#reusable-element-custom-properties), as they offer more flexibility.

#### Reusable element custom properties

[Reusable elements](#user-content-fn-6)[^6] in Bubble, similar to components in React or Vue, allow you to set up custom properties that can hold any type of data you specify, including single records or lists. Unlike custom states, these custom properties can hold dynamic data specified with a dynamic expression and do not require workflows to update. Custom properties are particularly useful in scenarios where data storage is not necessarily based on user action. For example, by pre-populating a reusable element's custom property with a dynamic expression, the data is instantly available on page load, rather than needing to be set using an action.

It also means the data is not static. Populating a custom state with a database search would retain the list as it was at the time of the search. In contrast, a custom property updates dynamically, reflecting any changes made in the database. This ensures that the data displayed in the reusable element is always current.

You can read more about reusable elements and custom properties in the article below:\
\
Article: [Reusable elements](/help-guides/design/elements/web-app/reusable-elements)

#### Result of step X

A workflow[^1] in Bubble is a collection of an event[^2] and one or more actions[^3], akin to how a [function](#function) in programming is a block of code designed to perform a specific task, consisting of a sequence of statements that execute in order when the function is called.

Workflows, like functions, sometimes need to temporarily store data for later use. In JavaScript, you might store data in variables to use in subsequent steps of the function. In contrast, Bubble automatically manages the creation and storage of data produced by one action, allowing it to be used in subsequent actions within the same workflow.

For example, if you use the [*Create a new thing*](#user-content-fn-7)[^7] action to create a new thing (database record) in step 1 of a workflow, you can use the [*Result of step 1*](#user-content-fn-8)[^8] [data source](#user-content-fn-9)[^9] in subsequent steps to refer to the thing that was created.

### Object

Objects are structures used to store collections of data and more complex entities. An object is a collection of properties, where each property is defined as a key-value pair. Objects can contain various types of data, including strings, numbers, arrays, functions, and even other objects.

Data within the Bubble platform is stored in its internal structure. Technically, being built on JavaScript, Bubble uses objects, but they are not available to developers in the same way as in JavaScript. Instead, Bubble structures data in ways that are more accessible to non-technical users.

This is done in a few different ways:

#### Things

A thing represents an individual record in the Bubble database and is used to store structured data. Each thing belongs to a *data type*, which is akin to a class in object-oriented programming. Data types define the structure of the thing by specifying the fields (properties) it can contain. Data types are created using Bubble's visual editor, and the database can then be populated with things using workflows[^1].

* **Data types:** Data types in Bubble define the structure and fields for things, comparable to classes or schemas in programming. For example, a job board app could have the data types Position and Company.
* **Fields:** Similar to object properties, a thing can have fields that store a preset type of data. The field is set up on the data type. For example, a Company data type could have the fields Name and Address.
* **Relationships:** Relationships between data types can be set up visually, directly in the data type’s fields (see our dedicated article on SQL for more info on how Bubble joins tables). For example, in a job board app, the Position data type could be linked to the Company data type.

The article series below covers in detail how the Bubble database is set up:

Article series: [The Database](/help-guides/data/the-database)

#### Option sets

Option sets in Bubble are used to manage static, predefined lists of values. They are particularly useful for storing data that does not change frequently, such as categories, statuses, or colors. Option sets provide a more structured and efficient way to handle enumerations compared to creating separate data types and records for such fixed lists.

Option sets are similar to enums in traditional programming languages. Each option set contains a list of options (values) that can be referenced throughout your application. For example, you might have an option set called “Task Status” with options like *To Do*, *In Progress*, and *Completed*.

Article: [Option sets](/help-guides/data/static-data/option-sets)

#### JSON

Bubble supports the JSON[^10] format for sharing data with external applications and integrating with other web services. This capability means that Bubble can both convert its existing data into JSON format and accept incoming data from other systems in the same format. By using JSON, Bubble facilitates seamless data exchange, making it easier to connect your application with third-party APIs, perform data imports and exports, and build integrated solutions that interact smoothly with a variety of external services.

Bubble also provides a few different ways to store temporary data (variables), that you can read more about in the [Variables](#variable) section.

### Modules/packages

A module is a single file that contains functions, classes, or variables, while a package is a collection of modules organized in a directory hierarchy. They facilitate code reuse, maintainability, and namespace management, allowing developers to break down large applications into manageable pieces and share reusable components across projects.

Modules/packages don't have a direct counterpart in Bubble, but can be implemented in a few different ways.

#### Plugins

Plugins[^11] are add-ons that extend the functionality of your app. They can provide new elements, actions, and data sources that integrate seamlessly into the Bubble editor. They can be created to add new features, connect to external APIs, or integrate with third-party services. The Bubble plugin marketplace offers a variety of plugins developed by the Bubble community and Bubble itself. Some plugins are free, while others are offered with a one-time price or subscription.

#### Custom JavaScript code

Bubble also offers several ways to implement custom JavaScript to extend its functionality. This is particularly useful for adding custom features that extend beyond Bubble's core functionality or integrating with external JavaScript libraries. You can write custom JavaScript in Bubble using the [HTML element](#user-content-fn-12)[^12] or by creating custom plugins. This capability enables the import and use of JavaScript modules or packages directly within a Bubble application.

## Other concepts in JavaScript and Bubble

### Building user interfaces

In a JavaScript-based app or website, building a user interface typically involves writing HTML, CSS, and JavaScript code. Frameworks like React or Vue.js can streamline this process, but you still need to understand how to structure components, manage state, and handle events manually. For example, displaying a user’s name might involve creating a React component, managing state with useState, and using an API call to fetch the user data.

Bubble simplifies UI creation through its visual editor. You can drag and drop elements[^13] onto the page, adjust their properties[^14], set conditions[^15] and define workflows[^1] that dictate how elements behave. To display a user’s name, you simply place a text element and bind it to the user’s name field from the database, using Bubble’s dynamic expression editor.

<figure><img src="/files/tZXQnPl5VhNyFlt3xNwl" alt=""><figcaption><p>Using dynamic expression, you can easily load and display data from the database. The expression above will simply show the text saved in the <em>Name</em> field of the currently logged in users.</p></figcaption></figure>

Bubble handles the data fetching and updating automatically, by establishing a [WebSocket connection](#user-content-fn-16)[^16] to keep dynamic database data on the page updated in real-time. When a change occurs in the database, Bubble pushes updates through this WebSocket connection to all relevant pages. This ensures that any data displayed on the page is automatically refreshed without requiring a second manual query or a page refresh.

{% hint style="info" %}
If you have experience working with HTML and CSS, we also have an article that explains how you approach designing, styling and assigning responsive behavior to pages and elements in Bubble:

Article: [Transitioning from HTML/CSS to Bubble](/help-guides/getting-started/transitioning-to-bubble-from/html-and-css)
{% endhint %}

### Data management

Managing data in JavaScript often involves setting up and interacting with [RESTful APIs](#user-content-fn-17)[^17] or GraphQL. You write functions to make HTTP requests, handle responses, and update the UI based on the retrieved data. This process requires knowledge of asynchronous programming, promises, and possibly state management libraries like Redux.

Bubble provides a built-in database and data management system. Data operations like creating, reading, updating, and deleting records (CRUD) are handled through Bubble's workflows[^1] or auto-binding[^18]. For instance, saving a form input involves creating a workflow that saves the input data to a specified data type.

{% hint style="info" %}
If you have experience working with SQL, we also have an article that explains the differences between traditional SQL development and Bubble’s approach to data management.

Article: [Transitioning from SQL to Bubble](/help-guides/getting-started/transitioning-to-bubble-from/sql)
{% endhint %}

### Event handling and interactivity

Event handling in JavaScript involves writing functions that respond to user actions such as clicks, form submissions, or page loads. You attach these functions to DOM elements using event listeners, and keep track of a sometimes growing number of events and dependencies that can become fairly complex.

In Bubble, event handling is managed through workflows[^1]. You visually design workflows that trigger in response to user actions. For example, when a button is clicked, you can set a workflow to display a message, navigate to a different page, or update a database record.

### Data security

Ensuring data security in a JavaScript application involves setting up authentication and authorization mechanisms, often using libraries like Passport.js or Firebase. You write code to protect routes, manage user sessions, and validate user inputs. Implementing robust security measures requires a solid understanding of web security principles.

Bubble simplifies data security with [privacy rules](#user-content-fn-19)[^19] and [built-in authentication systems](#user-content-fn-20)[^20]. Privacy rules allow you to define who can view, modify, or interact with specific data types based on conditions built with Bubble’s dynamic expression editor. Bubble also provides built-in user management features, including login, sign-up, and password reset functionalities.

#### Performance optimization

Performance optimization in JavaScript involves techniques such as lazy loading, code splitting, and minimizing DOM manipulations. Developers must identify performance bottlenecks and apply appropriate solutions, which can be complex and time-consuming.

Bubble, on the other hand, handles many performance optimizations behind the scenes. While you can influence performance through efficient design and limiting the amount of data loaded at once, Bubble’s platform manages server-side optimizations, caching, and database indexing automatically.

Additionally, Bubble abstracts the numerous server metrics typically managed in traditional app hosting into a single metric called workload[^21]. Using Bubble’s built-in tools, you can gain both a holistic and detailed view of the processes that consume the most server resources. This allows you to effectively optimize performance and scalability, without needing to delve into the complexities of server management.

[^1]: A *workflow* is the combination of an *event* that triggers one or more *actions*.

    Article series: [Workflows](/help-guides/logic/workflows)

[^2]: An *event* is the part of a workflow that triggers it. This leads to one or more actions running.

    Article series: [Workflows](/help-guides/logic/workflows)

    Article series: [Events](/help-guides/logic/workflows/events)

[^3]: An *action* is the part of a workflow that performs a task. A workflow consists of an event (trigger) and one or more actions.

    Article series: [Workflows](/help-guides/logic/workflows)

[^4]: You can read more about different types of page navigation in the article series below.

    Article series: [Navigation](/help-guides/logic/navigation)

[^5]: *Set state of an element* is an action that save a piece of data in a custom state on a selected element.

    Reference: [Set state of an element](/core-resources/actions/element#set-state-of-an-element)

[^6]: *Reusable elements* are a collection of elements, actions and customizable properties that can be re-used across your app. They are commonly used for headers and footers, for example.

    Article: [Reusable elements](/help-guides/design/elements/web-app/reusable-elements)

[^7]: *Create a new thing* is an action that creates a new thing (database record) and optionally populates it with data.

    Reference: [Create a new thing](/core-resources/actions/data-things#create-a-new-thing)

    Article series: [The database](/help-guides/data/the-database)

[^8]: *Result of step X* is a data source available only in workflows, that fetches the data returned by a previous action in the same workflow.

[^9]: A *data source* is the part of a dynamic expression that fetches data from a given source, such as the database, the current user, the current date/time or an external API.

    Reference: [Data sources](/core-resources/data/data-sources)

[^10]: JSON, which stands for JavaScript Object Notation, is a lightweight data-interchange format. It is easy for humans to read and write, and easy for machines to parse and generate.

    JSON is used primarily to transmit data between a server and web application.

[^11]: Article: [Plugins](/help-guides/integrations/using-plugins)\
    Page: [Bubble plugins](https://bubble.io/plugins)\
    \
    Article series: [Building plugins](/account-and-marketplace/building-plugins)

[^12]: The HTML element allows you to directly embed an element on the page, where you can write or paste HTML, CSS, or JavaScript code.

    Reference: [HTML element](/help-guides/design/elements/web-app/visual-elements#html)

[^13]: Elements are the "building blocks" that make up your page, such as text, images, icons and buttons.

    Article series: [Elements](/help-guides/design/elements)

[^14]: The properties of an element define its attributes, such as size, colors, responsive behavior, data source, and more.

[^15]: Element *conditions* are rules that dynamically change an element’s properties based on the yes/no result returned by a dynamic expression.

    Article: [Conditions](/help-guides/logic/conditions)

[^16]: A *WebSocket* is a communication protocol that provides a real-time, bidirectional connection between the user’s browser and the server, enabling instant data updates without needing to refresh the page.

[^17]: A RESTful API is a set of web services that operate over HTTP to access and use data. It follows REST principles, using standard HTTP verbs like GET, POST, PUT, and DELETE for operations.

[^18]: Auto-binding is a feature in Bubble that allows you to configure input forms, like text inputs, to automatically update and save their values to a specific data type and field in the database as soon as they are modified.

[^19]: *Privacy rules* are settings that restrict access to data based on specific conditions, as specified in dynamic expressions.

    This ensures that sensitive information is only visible to authorized users.

[^20]: Bubble features a built-in system to handle user authentication such as creating accounts, logging in, staying logged in and resetting passwords.

    Article series: [User accounts](/help-guides/data/user-accounts)

[^21]: *Workload* is a metric that aggregates all the work the server has to do to power your app over a given time period.

    Article series: [Workload](/help-guides/workload)


# HTML and CSS

This article explores the similarities and differences between HTML/CSS and Bubble.

First, let’s establish a simple fact: Bubble allows you to build web applications using the same languages that web browsers recognize – including HTML and CSS. While Bubble is often described as a no-code platform, a more accurate description in this context is that it takes the app that you have built visually, and then generates the underlying code—HTML, CSS, and JavaScript—that browsers can execute. When you view a page created in Bubble, what you see is the browser’s interpretation of this code. The JavaScript files acts as an “engine” that not only powers the page and its functions but crucially, it also manages communication between the user’s device and the Bubble server.

This is useful for those with an HTML and CSS background, as you'll quickly recognize that the logic and structure are fundamentally similar. However, Bubble adapts these principles to help non-technical users easily design and style their apps. Instead of typing code, it’s all done visually, using the page editor and styles. This holistic approach means that Bubble’s way of designing and the accompanying terminology may feel somewhat different from what you are used to, but you'll quickly find that the underlying principles share many commonalities.

## Page design and styling

### Elements

Building a user interface with HTML and CSS involves writing markup[^1] and styling code. You manually define the structure using HTML tags and style elements with CSS properties. For example, to create a button, you might write:

```html
<button class="my-button">Click Me</button>
```

And then add the CSS properties:

```css
.my-button {
    background-color: blue;
    color: white;
    padding: 10px;
}
```

You may also need to handle responsive design using [media queries](#user-content-fn-2)[^2] and other techniques to ensure your layout adapts to different screen sizes. Bubble moves the process of writing this code in HTML and CSS into its visual editor, essentially hiding the code from the user.

You drag and drop elements[^3] onto the page, adjust their properties, set conditions[^4], and define workflows that dictate how elements behave. There is a large collection of core elements available, such as:

* Buttons
* Links
* Text elements
* Input forms and rich text input forms
* File/image uploaders
* Icons
* HTML elements
* Map elements

You can read more about the built-in element types in this article series. Additionally, you can extend with more elements using plugins and/or custom code and Javascript libraries.

<figure><img src="/files/ohuPYlRzWrER3WiIMftr" alt=""><figcaption><p>Bubble allows you to control an element's styling properties and responsive behavior in a visual manner.</p></figcaption></figure>

To create a button, you simply place a button element on the page and customize its appearance through the editor. Bubble automatically handles responsive design, allowing you to set breakpoints and adjust styles visually.

<figure><img src="/files/SmRNpO7NsUPHybl3wt6j" alt=""><figcaption><p>Bubble has a built-in responsive editor that allows you to flexibly test your design using customizable breakpoints – and every pixel value in between.</p></figcaption></figure>

Article series: [Elements](/help-guides/design/elements)

### Containers

Containers is the umbrella term for a set of elements in Bubble that includes:

1. **Group:** A versatile container element that can hold other elements such as text, images, and buttons.<br>
2. **Repeating group:** A dynamic container used to display a list of data from a query, or in Bubble terms, a data source. Each cell in a repeating group can contain a set of elements that are repeated for each item in the data source, making it ideal for displaying lists, tables, or galleries.<br>
3. **Floating group:** A container that remains fixed on the screen while the rest of the content scrolls. Floating groups are useful for creating sticky headers, footers, or sidebars that stay visible as users navigate through the app.<br>
4. **Popup:** A modal container that appears over the current content when triggered. Popups are used for dialogs, notifications, forms, and other content that needs to be presented in a separate layer without navigating away from the current page. Popups can also apply a whiteout effect, essentially partially/fully hiding or blurring the content below.<br>
5. **Group focus:** A container designed to display content relative to a target element. It is typically used for dropdowns, tooltips, or contextual menus that need to appear next to a specific element when triggered.

Each of these containers doesn’t necessarily have a direct HTML/CSS counterpart but would instead require a combination of HTML elements and CSS properties to achieve the same functionality. In Bubble, these are set up as distinct element types to make it easier for users to make particular design decisions by picking elements with certain attributes.

Article series: [Containers](/help-guides/design/elements/web-app/containers)

## Styles

Bubble's Styles feature offers a user-friendly alternative to traditional CSS, allowing users to define and manage the appearance of elements through a visual interface rather than writing code. In CSS, you create class selectors and apply styles by writing and maintaining CSS rules in a stylesheet. This approach requires familiarity with CSS syntax and concepts such as specificity and inheritance.

In contrast, Bubble's Styles feature lets you apply consistent formatting across your application by creating style presets for elements like buttons, text, and containers. These presets can be easily updated through the visual editor, automatically propagating changes throughout the app.

When you create an app, it comes with a collection of pre-set styles that you can apply to elements right away. By using these styles (or creating your own), you can easily maintain consistency across your application. You can access the Styles tab to modify the appearance of all styled elements simultaneously by making changes in one place, similar to editing a CSS file in traditional web development.

<figure><img src="/files/xZoRxDk24vcN4vIPIxZ5" alt=""><figcaption><p>The Styles tab in the Bubble editor lets you control the styling properties of elements in one central place.</p></figcaption></figure>

Circling back to the introduction to this article, Bubble’s aim isn’t to fundamentally replace HTML and CSS. Instead, Bubble seeks to make the design flexibility of these technologies accessible to a broader audience by providing a more visual and user-friendly approach.

You can read more about styles in the article series below:

Article series: [Styling](/help-guides/design/variables-and-styles)

### Font and color variables

Bubble’s Styles tab includes both font and color variables that allow you to link fonts and colors with styles or directly with elements.

By updating a font or color variable, you can change the appearance across your entire app. This approach is akin to defining styles in a CSS file and applying them with class selectors. For those with experience in UX design, this parallels the code concept of design tokens. Bubble’s visual interface makes this process easier, especially for non-technical users, by centralizing design management. This not only ensures consistency throughout your application but also encourages users without UX experience to consider design best practices.

Article series: [Styling](/help-guides/design/variables-and-styles) | [Font variables](/help-guides/design/variables-and-styles/font-variables) | [Color variables](/help-guides/design/variables-and-styles/color-variables)

## Responsiveness

If you are coming from an HTML and CSS background, you’ll know that responsiveness is a key aspect of modern web design, ensuring that applications look and function well on a variety of devices and screen sizes. Bubble addresses this need with its responsive engine, which is essentially a layer on top of Flexbox. While it follows the same principles, Bubble simplifies the process with a visual interface for working with the responsive properties of any style (similar to CSS classes) or individual container/element.

This essentially means you can see the result as the properties as the properties are applied, and use Bubble’s built-in responsive viewer to test design solutions while you are building.

Flexbox and Bubble's responsive engine share a lot of similarities:

1. **Container-based layouts:** Both Bubble’s responsive engine and Flexbox rely on containers to manage the layout of child elements. In Flexbox, a container is defined with the display: flex property, which allows you to control the alignment, direction, and distribution of the container's children. Similarly, in Bubble, you use container elements like groups and repeating groups to organize your content and control its layout.
2. **Alignment and justification:** Flexbox offers properties like justify-content, align-items, and align-content to control the alignment and spacing of child elements within a container. Bubble mimics this functionality with its visual settings for horizontal and vertical alignment and justification.
3. **Direction and order:** Flexbox uses the flex-direction property to define the direction of content flow within a container, either as a row (horizontal) or column (vertical). Bubble provides similar options, enabling you to set the direction of elements within a container. Additionally, both Flexbox and Bubble allow you to reorder elements within a container, either through the order property in CSS or by rearranging elements in Bubble by dragging and dropping or using the reorder buttons.
4. **Flexibility and growth:** Flexbox’s flex-grow, flex-shrink, and flex-basis properties allow child elements to grow, shrink, or maintain a fixed size based on the available space. Bubble incorporates these principles by letting you set an element’s minimum, maximum or fixed size within their containers. These settings tell Bubble that a given element should always stay within a specific pixel value or percentage of its container's width.
5. [**Breakpoints**](#user-content-fn-5)[^5] **and** [**media queries**](#user-content-fn-2)[^2]**:** While Flexbox itself does not handle media queries, responsive design often involves using them to adjust layouts for different screen sizes. In Bubble, you can define breakpoints directly within the responsive editor. At each breakpoint, you can customize the layout, visibility, and behavior of elements, similar to how you would use media queries in CSS to create responsive designs. Because you can save breakpoints in one centralized place, similar to styles and style variables, you can change the behavior of your entire app by making a single change in breakpoints. These breakpoints can be accessed through Bubble’s dynamic expression editor, making it easy to conditionally set the behavior of containers and elements.

Article series: [Responsive design](/help-guides/design/responsive-design)

[^1]: *Markup* is a way of writing text that defines the structure and layout of a webpage. It uses tags to specify elements like headings, paragraphs, images, and links, helping to organize and format content for the web.

[^2]: Media queries are a way in CSS to make your website look good on different devices, like phones, tablets, and computers. They change the style of your website based on the size of the screen.

[^3]: *Elements* are the building blocks that you place on the page to design your app, such as text, images, icons and buttons.

    Article series: [Elements](/help-guides/design/elements)

[^4]: *Conditions* allow you to set rules for each element that changes how the element looks depending on whether the rule returns a yes or a no.

    Article section: [Elements](/help-guides/design/elements) | [Conditions](/help-guides/design/elements#conditions)

[^5]: Breakpoints are specific points where the layout of your website changes to fit different screen sizes better. They help ensure your website looks great whether viewed on a small phone or a large computer screen.

    For example, a breakpoint of 320 px is often used as the smallest screen width (usually a phone) to which your app should be compatible.


# SQL

If you are accustomed to working with [SQL databases](#user-content-fn-1)[^1], Bubble may feel both familiar and different at the same time. In this article, we’ll explore the similarities and differences between these two approaches, and how a small shift in mindset can help you understand the nuances of Bubble’s approach to data management.

One key point to remember is that Bubble acts as a layer over SQL. It employs its own terminology and automates many SQL processes, including table joins, to make database construction intuitive, even for those without a technical background.

Despite the different visual interface, Bubble’s database technology is built on PostgreSQL[^2], so the underlying logic remains the same. However, your SQL background might require a bit of adaptation, as Bubble’s automation and visualization of data management offer a unique approach compared to traditional SQL practices.

## Terminology

Before diving into the specifics of database management, let’s examine how Bubble’s terminology differs from traditional SQL. These terms ***are not direct synonyms*** but reflect how each concept is applied within Bubble compared to SQL. Click any underlined term for more contextual information.

<table><thead><tr><th width="226">SQL</th><th>Bubble counterpart</th></tr></thead><tbody><tr><td><a data-footnote-ref href="#user-content-fn-3">Database record</a></td><td>Thing</td></tr><tr><td><a data-footnote-ref href="#user-content-fn-4">Table</a></td><td><a data-footnote-ref href="#user-content-fn-5">Data type</a></td></tr><tr><td>Column</td><td><a data-footnote-ref href="#user-content-fn-6">Field</a></td></tr><tr><td>Primary key</td><td><a data-footnote-ref href="#user-content-fn-7">Unique ID</a></td></tr><tr><td>Query</td><td><a data-footnote-ref href="#user-content-fn-8">Search/Do a search for</a></td></tr><tr><td><a data-footnote-ref href="#user-content-fn-9">Join</a></td><td><a data-footnote-ref href="#user-content-fn-10">Custom field</a></td></tr><tr><td>Insert</td><td><a data-footnote-ref href="#user-content-fn-11">Create a New Thing</a></td></tr><tr><td>Update</td><td><a data-footnote-ref href="#user-content-fn-12">Make Changes to a Thing</a></td></tr><tr><td>Delete</td><td>Delete a Thing</td></tr></tbody></table>

## Database setup

The first difference you’ll notice is that databases are created automatically. When you create a new application, Bubble creates one database for the [development environment and one for the live environment](#user-content-fn-13)[^13], and the two operate independently of each other. Upon [deploying your application](#user-content-fn-14)[^14], the structure of both databases is synchronized, but their data content remains separate.

In other words, unlike traditional SQL where setting up a database requires manual configuration, Bubble automates the process. With traditional SQL, you must install and configure a database server, create the database, and define the schema manually. This setup is typically done through command-line interfaces or SQL editors, using SQL syntax and commands.

Additionally, in traditional SQL, you need to manually define tables, columns, data types, indexes, and relationships by writing SQL scripts or using database management tools. Creating separate environments for development and production involves setting up different instances or databases, configuring connections, and managing migrations and synchronizations between them.

In contrast, Bubble handles all of this for you in the editor. You can define your data types (equivalent to tables) and fields (equivalent to columns) without writing any SQL code. Bubble’s built-in features for data management allow you to create, modify, and delete data records without needing to write custom queries, making the process more intuitive and accessible for beginners.

### Users

Bubble automatically generates a User data type when you create a new application. This data type includes an email field and secure handling of passwords. The User data type integrates with Bubble's built-in authentication system, making it easy to implement user login, sign-up, password reset, and other authentication features without manual setup or compromising security.

<figure><img src="/files/SpgdGOWiEvvVaNr9wiBY" alt=""><figcaption><p>The User type is automatically set up in all apps. It comes with a set of built-in fields, but can be extended with custom fields as needed. Illustrated above with the <em>Name</em> field of type <em>text</em>.</p></figcaption></figure>

While you can add as many additional fields (columns) to the User data type (table) as you want, there's no need to manually define the schema or relationships for user data—Bubble handles these aspects internally.

### Built-in fields

Every data type in Bubble automatically includes a list of built-in fields that are pre-configured and managed by Bubble, providing useful metadata without requiring additional setup.

* **Creator:** Identifies the user who created the thing.
* **Modified date:** Automatically updates to reflect the last modification date of the thing.
* **Created date:** Automatically sets to the date and time the thing was created.
* **Slug:** A URL-friendly identifier that can be used for SEO and navigation.
* **Unique ID:** A unique identifier of each single thing, comparable to a primary key in SQL

The data in these fields are populated and updated automatically, with the exception of the slug, which is set according to specific rules to ensure it remains URL-friendly. In contrast with traditional SQL, there’s no need for explicit schema definitions and additional logic to achieve the functionality offered by the built-in fields.

The User type comes with the additional *email* field.<br>

## Backups

Bubble automatically handles database backups. They are handled at the platform level, meaning that Bubble will create incremental backups of both your app’s databases for every change that happens, without requiring user action. This also means you can restore backups to any specific point in time, within the timeframe allowed by your plan.

The automated backup system is designed to be user-friendly, requiring no technical knowledge or configuration from the user. However, it differs from traditional methods in that you don’t have direct access to the backup files, but you can rewind to any point in time in your plan’s retention window, as well as export one or more tables to CSV. You can read more about how Bubble backs up both the app and its databases in this article.

Article series: [Restoring database backups](/help-guides/maintaining-an-application/database-maintenance/database-copy-and-backups)

## Joining tables

Joining tables in SQL and Bubble involves fundamentally different approaches due to the nature of each platform. In SQL, joining tables is an explicit, manual process. You write SQL queries using commands like JOIN, INNER JOIN, LEFT JOIN, and RIGHT JOIN to combine rows from two or more tables based on related columns. This process requires specifying the exact columns and conditions for the join, making it flexible but necessitating a solid understanding of SQL syntax and database relationships.

In contrast, Bubble abstracts much of this complexity. Instead of writing queries, you create relationships between data types using custom fields (which are akin to foreign keys in SQL). When you need to combine data from different types, Bubble handles the underlying logic for you. This means you interact with a visual interface to define relationships between data types.

<figure><img src="/files/oFo4LGqaqaAZTNTpvBmz" alt=""><figcaption><p>In the example above, we have set up a custom field on the User data type that links it to a company. In plain English, this means we can easily see which company any user is associated with.</p></figcaption></figure>

In Bubble, every "thing" (record) in the database has its own unique ID, similar to a primary key in SQL. However, unlike traditional SQL databases, you don’t need to reference this unique ID directly in the same way. Bubble simplifies data management by allowing you to create relationships between data types using custom fields, which acts more like foreign keys in SQL but with less manual effort.

Let’s explore how different types of relationships can be handled in SQL and Bubble. While this is not an exhaustive list of all possible scenarios or methods, it illustrates how Bubble manages relationships in a more visual manner, helping you adapt to its approach.

### One-to-many

In SQL, a one-to-many relationship is established by adding a foreign key in the table representing the “many” side that references the primary key of the table on the “one” side. For example, if you have a *Projects* table and a *Tasks* table, each task would include a project\_id column to indicate which project it belongs to. You would then write SQL queries to join these tables when you need to fetch related data.

<figure><img src="/files/ozLlyTYMLMv9in1PYmo9" alt=""><figcaption><p>In this example (similar to the <em>User</em> example above) we have linked a <em>Task</em> with a <em>Project</em>.</p></figcaption></figure>

In Bubble, creating a one-to-many relationship is handled through the visual interface. For example, if you have two data types called *Project* and *Task*, you would add a field of type Project to the Task data type to link each task to a specific project. Conversely, you can add a field of type Task (as a list) to the Project data type to maintain a list of tasks associated with each project.

<figure><img src="/files/Bb4FgM21Gu4YHDe9V6eN" alt=""><figcaption><p>In this example, we have linked the <em>Task</em> and <em>Project</em> data type from the Project data type instead. Note the second rectangle, showing the one-to-many relationship between the two: in plain English, a project can contain a list of tasks.</p></figcaption></figure>

### Many-to-many

In SQL, a many-to-many relationship is typically implemented using a join table (sometimes called a junction table). This join table contains foreign keys referencing the primary keys of the two related tables. For example, if you have *Tasks* and *Projects* tables, a TaskProjects join table would contain student\_id and course\_id columns to represent the many-to-many relationship.

<figure><img src="/files/Bb4FgM21Gu4YHDe9V6eN" alt=""><figcaption></figcaption></figure>

In Bubble, creating a many-to-many relationship involves adding a list field to both data types. For instance, if you have *Task* and *Project* data types, you could add a field of type Projects (as a list) to the Task data type, and a field of type Task (as a list) to the Project data type. This allows each task to be associated with multiple projects and each project to be associated with multiple tasks.

<figure><img src="/files/6YTnTuggNtmUJ91Y2qAP" alt=""><figcaption></figcaption></figure>

### One-to-One

In SQL, a one-to-one relationship is created by ensuring that both tables have unique constraints on their foreign keys, typically by using primary keys. For example, if you have a Users table and a Profiles table, each user would have a unique profile, and the Profiles table would include a user\_id column with a unique constraint to ensure one-to-one mapping.

In Bubble, a one-to-one relationship is achieved by adding a custom field of one data type to another data type. For example, if you have User and Profile data types, you can add a field of type Profile to the User data type and a field of type User to the Profile data type. This setup ensures that each user is linked to a single profile and vice versa.

<figure><img src="/files/agtbmDznJBwlYLFJ3VqT" alt=""><figcaption><p>Again, we are using a custom field to link two data types. If needed, you can add a <em>User</em> field on the <em>Profile</em> data type to link the other way too.</p></figcaption></figure>

## Searching for data

Instead of performing queries using SQL syntax, using commands like SELECT, WHERE, and JOIN, Bubble has a visual interface that lets you build dynamic expressions. Dynamic expressions let you set up data sources, of which a database search is a potential source.

Searches in Bubble are configured using constraints, which act as filters to refine the data retrieved from the database. You specify these constraints visually, selecting fields and setting conditions without needing to write SQL queries.

One key advantage of Bubble’s approach is how it handles relationships between data types. In SQL, you need to explicitly write JOIN commands to combine data from related tables, specifying the exact columns and conditions for the join.

In Bubble, as we explored in the earlier section, relationships between data types are created through custom fields, which act like foreign keys. When you perform a search or build a dynamic expression, Bubble automatically manages these relationships, effectively handling the joins for you.

For example, if you have two data types, Project and Task, you can link tasks to projects by adding a Tasks list field to the Project data type. When you search for projects, Bubble will include related project data without requiring you to write any join queries.

<figure><img src="/files/sy20lVIXeS3LQSZCHXrE" alt=""><figcaption><p>In this <em>Do a search for</em> expression we are querying the database for projects related to a specific task.</p></figcaption></figure>

Dynamic expressions in Bubble are designed to automatically reflect the latest data changes, ensuring your app always shows the most up-to-date information. Bubble accomplishes this by using a WebSocket connection to manage real-time updates. Whenever the database is modified, updates are instantly pushed through the WebSocket to all relevant users and pages. This process keeps your displayed data current without the need for manual refreshes or additional queries.

## Data security

In traditional SQL databases, managing data privacy involves defining roles and permissions through SQL commands. Administrators write queries to specify which actions (like SELECT, INSERT, UPDATE, DELETE) users can perform on tables or views. Advanced security features, such as row-level security and view restrictions, require detailed planning and technical expertise, often handled by database administrators.

Bubble simplifies data privacy with privacy rules. Instead of writing SQL commands, you define who can view, modify, and interact with data based on user roles and conditions. This intuitive approach makes it easy for users of all technical levels to implement robust access controls, ensuring data security without the need for deep technical knowledge. Bubble’s privacy rules automatically enforce these settings, providing a straightforward and secure way to manage data privacy.

Article series: [Protecting data with privacy rules](/help-guides/data/the-database/protecting-data-with-privacy-rules)

### Data API

Additionally, Bubble offers a built-in inbound API feature that enables you to grant external systems and applications access to your app’s database through the Data API, allowing them to read and manage some or all of your database’s data. This access is also governed by privacy rules, allowing you to configure specific permissions based on the identity of the client, ensuring secure and controlled data interactions.

You can read more about the Data API in the article series below.

Article: [The Data API](/help-guides/integrations/api/the-bubble-api/the-data-api)

[^1]: An SQL database is a system that uses Structured Query Language (SQL) to store, manage, and retrieve data.

    It organizes data into tables with rows and columns.

[^2]: PostgreSQL is an open-source relational database management system that uses SQL to interact with data.

    It is widely used for both small and large applications in traditional code environments.

[^3]: In this context, a “database record” refers to a single entry within a table.

[^4]: In this context, “table” refers to the structure used to store and organize data.

[^5]: You can set up as many custom data types as you need in your Bubble database. A data type is any isolated type of data that can include one or more fields, such as User, Task, Project, etc.

    Article series: [The database](/help-guides/data/the-database)

[^6]: A *field* is a pie

[^7]: Every thing (database record) in the Bubble database is assigned a Unique ID. This is akin to a primary key in traditional SQL development.

[^8]: A *Search* is a query to the database to fetch data based on specific constraints. *Do a search for* is the name of the actual data source in Bubble.

    Reference: [Do a search for](/core-resources/data/data-sources#do-a-search-for)\
    Article: [Finding data](/help-guides/data/the-database/finding-data)

[^9]: In this context, “join” refers to linking data from different tables through related fields.

[^10]: In this context, “custom field” refers to a field set to a custom data type, meaning another data type that you have created in the database.

    Generally, custom fields refer to any field that is not built-in.

[^11]: *Create a new Thing* is an action that creates a new thing (database record) and optionally populates its fields with data.

    Reference: [Create a new Thing](/core-resources/actions/data-things#create-a-new-thing)

[^12]: *Make changes to a Thing* is an action that updates an existing thing (database record) with new information.

    Reference: [Make changes to a Thing](/core-resources/actions/data-things#make-changes-to-thing)

[^13]: All Bubble apps are create with one development version and one live version. The article series below covers this in-depth.\
    \
    Article series: [Version control](/help-guides/maintaining-an-application/version-control)

[^14]: *Deploying* your app in Bubble means publishing your application to a live environment, making it accessible to users.

    Article: [Deploying your app](/help-guides/publishing-your-app/deploying-your-app)


# Design

At the heart of Bubble's capabilities is the ability to build precise, professional designs, from simple forms to full-featured websites and SaaS apps.

## Introduction

<figure><img src="/files/yKkgGJApcsHUEJvfpxTF" alt=""><figcaption></figcaption></figure>

Design is about the look and feel of your app, including its visual appeal, the experience it delivers, and the emotions it evokes in users. Working on design means focusing on the aesthetics: the layout of elements, typography, colors, and effects like borders, shadows, and transitions.

Good design serves several important purposes. A thoughtful design creates an intuitive user interface, making the app easy and enjoyable to navigate. It also helps you establish a consistent brand identity, making your app recognizable and helping it stand out.

{% embed url="<https://www.youtube.com/watch?v=21n8iSE6Xwk&list=PLoNVJrdvQQYlGHGcKDCpbgAv1lruNA8db&index=3>" %}

### Great design solves problems

If you think about it, most apps are really a polished layer on top of machinery that simplifies a task the user would otherwise find complicated.

Take a marketplace for finding freelancers, a task management app, or an e-commerce store for buying art. None of these tasks are impossible to solve without an app, but a good app makes them simpler and more enjoyable. Great app ideas make it easy for users to solve a problem and store their data securely.

This is why design isn't just about making your app look good. The goal of any well-designed app is to combine appealing visuals with user-friendliness. There are several aspects to a successful design, and the list below is a good starting point for planning your app's user interface

{% hint style="info" icon="user-robot" %}
Bubble's AI Agent can generate polished, on-brand designs from a natural-language description. Try prompting it with the layout, tone, or specific components you want, and let it handle the initial setup so you can focus on refining the details.

Article: [Bubble's AI Agent](/help-guides/ai/bubble-ai-agent)
{% endhint %}

{% hint style="info" icon="newspaper" %}
You can read out more extensive guide on the theory behind designing user interfaces (UI) and thinking about the user experience (UX) in the article below:

Article: [Design and UX](/help-guides/getting-started/building-your-first-app/design-and-ux)
{% endhint %}

## Bubble's design tools

Bubble offers a unique way to build apps: 100% WYSIWYG[^1] tools to draw your app on an empty canvas and preview[^2] the design immediately. There's no code to write or unexpected results.

By combining Bubble's design tools, you can create layouts that are consistent, pixel-perfect, fully responsive, and compatible across devices and browsers. If you're building with [AI](/help-guides/ai/bubble-ai-agent), these tools let you fine-tune the results after the initial generation, or keep prompting the Agent to adjust things for you.

### Elements

Your app's user interface is made up of elements: input fields, buttons, images, checkboxes, calendars, icons, and more, each serving a specific purpose. Bubble's WYSIWYG visual editor lets you pick and place elements directly on the page instead of writing code, and preview the result immediately.

<figure><img src="/files/fBbxL9kiI3cOD9Ri9tSd" alt="A screenshot of the Bubble editor showing a dashboard interface being built."><figcaption><p>By combining elements, you can build interfaces ranging from simple forms to full-featured dashboards.</p></figcaption></figure>

Our Element section covers the different element types and how they can be combined to build a virtually limitless number of different user interfaces.

Article series: [Elements](/help-guides/design/elements)

### Styling

Every visible element on your page can be styled in different ways. You can apply styling on a per-element basis or you can use Bubble's *Styles* feature to save stylesheets that can be applied to multiple elements and be controlled from one central place.

Bubble also allows you to set up [Font and Color variables](#user-content-fn-3)[^3] that apply those properties to Styles and elements across your app.

**Article series**: [Styling](/help-guides/design/variables-and-styles)\
**Core reference:** [List of styling properties](/core-resources/elements/styling-properties)

### Responsive design

Bubble's responsive engine ensures that your app looks and behaves well on different devices, such as smartphones, tablets, and desktop computers. Settings up a responsive design eliminates the need to create separate versions of a website for different devices.

**Article series**: [Responsive design](/help-guides/design/responsive-design)\
**Core reference:** [List of responsive properties](/core-resources/elements/responsive-properties)

## Additional design resources

In addition to Bubble's tools for building your own designs, we have different resources that can help you set up the design even faster.

Article section: [Design and UX resources](/help-guides/getting-started/building-your-first-app/design-and-ux#design-resources)

### Importing from Figma

Figma is a popular and useful tool for the first stage of prototyping and design and Bubble has a tool for importing design from Figma into Bubble.

**Article:** [Importing from Figma](/help-guides/design/importing-from-figma)

[^1]: WYSIWYG stands for *What You See Is What You Get*.

    In the Bubble editor, this means the layout and elements you arrange in the editor look and behave the same way when previewed or deployed live. You can build your app visually and see it exactly as your users will experience it.

[^2]: Every change that you make in the Bubble editor is immediately visible in the preview of your app.

    Article: [Previewing your app](/help-guides/getting-started/navigating-the-bubble-editor/previewing-your-app)

[^3]: The *Font* and *Color* variables in the [*Styles*](/help-guides/design/variables-and-styles/styles) tab let you save colors and fonts to reuse throughout your app. When you change a variable, every style and element linked to it updates automatically.

    Article: [Color variables](/help-guides/design/variables-and-styles/color-variables)

    Article: [Font variables](/help-guides/design/variables-and-styles/font-variables)


# Elements

This section covers elements – the building blocks that make up your application's user interface

{% hint style="info" %}
This is the long-form article on Bubble's different elements. If you want the short-form technical reference that details all the different element properties, you'll find that in the link below.

**Core reference:** [Elements](/core-resources/bubble-elements)
{% endhint %}

## What is an element?

An element is any individual piece of your app's user interface. Buttons, images, input fields, containers, icons, and maps are all examples of elements. Together, they make up everything a user sees and interacts with on the screen.

<figure><img src="/files/fBbxL9kiI3cOD9Ri9tSd" alt=""><figcaption><p>Combining elements like input fields and buttons is how you design your app and give users different ways to interact with it.</p></figcaption></figure>

Every part of your app's user interface is made up of elements organized in a hierarchy. Each element has a set of style and responsive properties that control how it looks and behaves. Together, the hierarchy and these properties give the browser everything it needs to render the page correctly.

<figure><img src="/files/jFeXSu7VG9ZhHXzXW2RD" alt="The element tree and element palette"><figcaption><p>The <strong>element tree</strong> shows all the elements on the current page or view in a hierarchy, and the <strong>element palette</strong> lets you add new elements to the page or view.</p></figcaption></figure>

Bubble lets you control every layer of this: which elements exist, how they look, how they behave across different screen sizes, and how they respond as elements are shown, hidden, or animated on the page.

<details>

<summary>What is the element hierarchy?</summary>

Every element you add becomes part of a hierarchy that shapes both its structure and design. In web apps, the page is the parent of every other element on it. In mobile apps, the view serves the same role.

Within that parent, you can group elements into containers to create additional parent-child relationships. This hierarchy is what the browser or mobile app uses to render the layout correctly.

Container elements can hold data, style, and layout properties that their child elements inherit or reference. This makes hierarchy an important tool for keeping your design consistent and your data flowing predictably.

**Article:** [The element hierarchy](/help-guides/design/elements/the-element-hierarchy)

</details>

### Web and mobile elements

Bubble supports both web apps and native mobile apps, and elements work slightly differently on each.

* **Web apps** run in a browser. They're built around pages, with elements arranged in a hierarchy on each page. Web elements are designed for the browser environment and include familiar building blocks like text, buttons, images, repeating groups, popups, and floating groups.
* **Native mobile apps** run as installed apps on iOS and Android devices. Instead of pages, they're built around views, which serve the same role but exist within a single app shell rather than being loaded as separate URLs. Mobile elements are designed for how mobile operating systems handle layout and interaction, and include native components.

Some elements are only available on one platform.

### Using AI to build with elements

Bubble's AI Agent can design pages from scratch with a simple prompt. Describe what you need in your preferred language, and the Agent gathers context from your app and presents a plan for what it will build, ready for you to approve.

<figure><img src="/files/5RXJNMrpmZdF3ys0BGO5" alt="Bubble&#x27;s AI agent presenting a plan for building a signup and login page."><figcaption><p>Bubble's AI Agent will present you with a plan that you can approve before you start building.</p></figcaption></figure>

Shortly after you approve the plan, your new page is ready, with elements and workflows connected and set up to use. From there, you can refine the design directly in the editor or by prompting the Agent for adjustments.

<figure><img src="/files/jrPROxySMp4GZqH2bcKR" alt="Login and signup page built by the Bubble AI Agent."><figcaption><p>Shortly after you approve the plan, the Agent has built the login and signup page.</p></figcaption></figure>

The Agent can also add, modify, or reconfigure individual elements on an existing page, making it a useful partner throughout the design process, not just at the start.

***

## Types of elements

Bubble offers many different elements, split into a few categories based on their purpose. Plugins can also add new elements, ranging from simple utilities to complex integrations.

### **Visual elements**

<figure><img src="/files/6cIvv6znSo3L5KBlevIl" alt="Visual elements combined to show a hero section on a website."><figcaption><p>Visual elements let you design your page to display information and accept clicks. Every element in this example is a visual element: a text element, an image, and a button.</p></figcaption></figure>

Visual elements are placed on the page for aesthetic purposes or to display information. They don't accept data input, though they can accept clicks and are often connected to workflows.

**Article:** [Visual elements](/help-guides/design/elements/web-app/visual-elements) | [Mobile-specific visual elements](/help-guides/design/elements/ios-and-android-app/visual-native-app-elements)

<table><thead><tr><th width="160.66796875">Platform</th><th width="176.30859375">Element</th><th>Purpose</th></tr></thead><tbody><tr><td>Web/Mobile</td><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#text">Text</a></td><td>Displays static or dynamic text.</td></tr><tr><td>Web/Mobile</td><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#button">Button</a></td><td>A clickable element that triggers a workflow when pressed.</td></tr><tr><td>Web/Mobile</td><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#icon">Icon</a></td><td>Displays a small graphic symbol, often used for actions or visual cues.</td></tr><tr><td>Web</td><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#link">Link</a></td><td>A clickable element that navigates to another page or external URL.</td></tr><tr><td>Web/Mobile</td><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#image">Image</a></td><td>Displays a static or dynamic image.</td></tr><tr><td>Web/Mobile</td><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#shape">Shape</a></td><td>Displays a rectangle.</td></tr><tr><td>Web</td><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#alert">Alert</a></td><td>Displays a temporary message to the user, typically for feedback or notifications.</td></tr><tr><td>Web</td><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#video">Video</a></td><td>Embeds a video player, supporting YouTube and Vimeo.</td></tr><tr><td>Web</td><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#html">HTML</a></td><td>Renders custom HTML directly on the page.</td></tr><tr><td>Web/Mobile</td><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#map">Map</a></td><td>Displays an interactive map with optional markers.</td></tr><tr><td>Web</td><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#built-on-bubble">Built on Bubble</a></td><td>A small badge that links to Bubble's homepage.</td></tr></tbody></table>

### **Containers**

<figure><img src="/files/giLHZKHIX1Q5PwaPNapE" alt="Input form inside a container."><figcaption><p>Groups can be used both to organize elements like the form above, and to hold the data that you want to work with, such as a user. The group is highlighted with a dashed border in the example.</p></figcaption></figure>

Containers hold other elements. They serve two main purposes:

* They **group other elements** (including other containers) into a single unit. All elements inside belong to the same level of the page hierarchy and move or delete together with the container.
* They can also **hold data** relevant to the elements inside. For example, a group with a *User* type of content can pass that user's data down to inputs and text elements inside it.
* Containers can hold **a single thing** (like a specific user) or a **list of things** (like a list of users), specified in the *Holds data* column in the table below.

Bubble offers several container types, each suited to different layouts and behaviors.

**Article:** [Containers](/help-guides/design/elements/web-app/containers) | [Mobile-specific containers](/help-guides/design/elements/ios-and-android-app/containers)

<table><thead><tr><th width="119.41796875">Platform</th><th width="174">Container type</th><th width="150.15625">Holds data</th><th>Purpose</th></tr></thead><tbody><tr><td>Web/mobile</td><td><a href="/pages/0InSA6TX3K40sT0u4F83">Group</a></td><td>Single thing</td><td>Hold elements and data anywhere on the page</td></tr><tr><td>Web</td><td><a href="/pages/F2Z6HIq5lrrnkRUAKQbg">Repeating group</a></td><td>List</td><td>Show a list of things in a flexible design such as cards, lists, and a masonry grid</td></tr><tr><td>Web</td><td><a href="/pages/xNR63E81QtKAbMSiK90O">Table element</a></td><td>List</td><td>Show a list of things in rows/columns with headers</td></tr><tr><td>Web</td><td><a href="/pages/vSeYGneOPOJmfX4xfS1u">Popup</a></td><td>Single thing</td><td>Display elements on top of everything else, such as a warning message</td></tr><tr><td>Web/mobile</td><td><a href="/pages/1fNX5lhkwMiZUxo4mHsI">Floating group</a></td><td>Single thing</td><td>Hover elements above other elements and stick them to one of the screen edges, like a top navigation bar that stays put</td></tr><tr><td>Web</td><td><a href="/pages/uDrZfAW9NewNRUhmyEfB">Group focus</a></td><td>Single thing</td><td>Display a group that disappears as soon as you click anywhere else, such as a dropdown menu</td></tr><tr><td>Mobile</td><td><a href="/pages/X2g2WXxpMVRkgRZjrAJR#short-list">Short list</a></td><td>List</td><td>A container for displaying a fixed set of data items. Loads all items immediately, making it best for shorter lists.</td></tr><tr><td>Mobile</td><td><a href="/pages/X2g2WXxpMVRkgRZjrAJR#horizontal-list">Horizontal list</a></td><td>List</td><td>Displays a horizontally scrolling list of items.</td></tr><tr><td>Mobile</td><td><a href="/pages/X2g2WXxpMVRkgRZjrAJR#sheets">Sheet</a></td><td>Single thing</td><td>A native overlay that slides up from the bottom of the screen</td></tr></tbody></table>

### Input forms

<figure><img src="/files/giLHZKHIX1Q5PwaPNapE" alt="Input form inside a container."><figcaption><p>Input elements are used to collect information from users, such as text, dates, emails, and file uploads.</p></figcaption></figure>

Input forms are elements that allow your users to provide data to your application.

**Article:** [Input forms](/help-guides/design/elements/web-app/input-forms) | [Mobile-specific input forms](/help-guides/design/elements/ios-and-android-app/input-forms)

<table><thead><tr><th width="125.16796875">Platform</th><th width="209">Element</th><th>Purpose</th></tr></thead><tbody><tr><td>Web/mobile</td><td><a href="/pages/9usn2lynDsMjIzGqjm6r#text">Text input</a></td><td>Names, comments, other short info.</td></tr><tr><td>Web/mobile</td><td><a href="/pages/9usn2lynDsMjIzGqjm6r">Multiline text</a></td><td>Descriptions and other longer text. Allows line breaks. No text formatting.</td></tr><tr><td>Web/mobile</td><td><a href="/pages/uNmsNaHIMteorQ6PaDIa">Checkbox</a></td><td>Check to hold a yes/no value, such as accepting terms and conditions.</td></tr><tr><td>Web</td><td><a href="/pages/uNmsNaHIMteorQ6PaDIa">Dropdowns</a></td><td>Pick an option from a dropdown list, either static or from the database.</td></tr><tr><td>Web</td><td><a href="/pages/uNmsNaHIMteorQ6PaDIa">Search box</a></td><td>Search-as-you-type in the database.</td></tr><tr><td>Web</td><td><a href="/pages/uNmsNaHIMteorQ6PaDIa#radio-button">Radio buttons</a></td><td>Select one option out of multiple.</td></tr><tr><td>Web</td><td><a href="/pages/uNmsNaHIMteorQ6PaDIa#slider-input">Slider input</a></td><td>Pick a numerical value with a visual, draggable slider.</td></tr><tr><td>Web/mobile</td><td><a href="/pages/yU4rI57C4wpL8CQuejLA">Date/time picker</a></td><td>Select a date and optionally a time.</td></tr><tr><td>Web</td><td><a href="/pages/yF6fIrsbNIvkMzWEmd8K#image-uploader">Picture uploader</a></td><td>Upload and preview images like profile pictures.</td></tr><tr><td>Web</td><td><a href="/pages/yF6fIrsbNIvkMzWEmd8K#file-uploader">File uploader</a></td><td>Upload other types of files.</td></tr><tr><td>Mobile</td><td><a href="/pages/kc2HeG4yI6rEmy95HTvO#selectable-lists">Selectable list</a></td><td>Select one or more items from a set list.</td></tr></tbody></table>

### **Reusable elements**

Reusable elements let you build a component once and use it across your app. When you update the reusable element's definition, every instance updates too, which makes them useful for headers, footers, or any component that appears in multiple places.

**Article:** [Resuable elements](/help-guides/design/elements/web-app/reusable-elements)

***

## How elements behave

Elements can be set to behave in different ways depending on the situation or the user's actions. There are two main ways to make them interactive: conditions and workflows. Custom states can be used to temporarily store information.

### Conditions

Conditions let you set rules that change how an element looks or behaves based on whether a rule evaluates as yes or no. Nearly every visual property can be adjusted this way, which opens up a lot of possibilities, from subtle style changes to hiding and showing entire sections of the page.

Some examples:

* A condition on a button that greys it out when a required form field is empty.
* A condition on a group that hides it when the current user isn't logged in.
* A condition on a group that shows it only when a specific URL parameter is present.

**Article:** [Conditions](/help-guides/logic/conditions)

### Workflows

Workflows connect events (like a button click) to actions (like saving data, sending an email, or navigating to another page). Any element can be the trigger for a workflow, and workflows can chain multiple actions together to build complex behavior.

Some common uses include:

* Creating, editing, or deleting records in the database
* Logging users in and out
* Showing, hiding, or animating elements
* Navigating between pages or to external links

**Article:** [Workflows](/help-guides/logic/workflows)

### Custom states

Custom states let you store temporary information on an element, such as the current tab in a set or whether a section is expanded. States can be read and updated by workflows and referenced in conditions, giving you flexible control over how the element behaves without touching the database.

**Article:** [Custom states](/help-guides/data/temporary-data/custom-states)

***

## Working with elements

Once you know how elements are structured and how they behave, you can work on placing, styling, and configuring them in the editor.

### Adding elements

The element palette on the left side of the screen lets you add new elements to the current page, view, or reusable element. To add a new element, click its type in the palette and draw it directly on the page or view. You can also drag an element straight from the palette onto the canvas.

You can use the *Search assets*&#x20;

<figure><img src="/files/AIaM6nex0ZGeKld9NEre" alt="Element palette"><figcaption><p>To add new elements to the canvas, use the element palette on the left side of the editor.</p></figcaption></figure>

### The element property editor

Selecting any element opens the element property editor on the right side of the workspace. This is where you configure the element's name, data source, layout, style, conditions, and behavior. Every element has its own set of properties, based on its type.

If you select multiple elements, some properties can be edited in bulk.

<figure><img src="/files/yXcw6kx4PEyiQ20oit3M" alt=""><figcaption><p>The element property inspector lets you configure each element's properties individually.</p></figcaption></figure>

### Naming elements

You can edit an element's name at the top of the property editor. Naming conventions are up to you: some developers use prefixes like `btn_submit`, others use plain descriptions like `Submit button`. Whatever style you choose, keeping it consistent across your app makes elements easier to find and debug as your app grows.

<figure><img src="/files/L1RXzYVg5Arr6jVmDtuf" alt=""><figcaption><p>You can give each element a name by clicking its label in the top of the property editor.</p></figcaption></figure>

By default, Bubble generates a name based on the element's type and content. Renaming as you build helps avoid confusion later, especially when you have several similar elements on the same page.

### Finding and rearranging elements

Larger pages can have many elements. A few tools make them easier to navigate:

#### The element picker

The element picker in the toolbar lists every element on the page. Start typing to filter, and hover to see a thumbnail before selecting.

<figure><img src="/files/dM693UPgLSMWzURwuy1j" alt=""><figcaption><p>The element picker lets you quickly select an element by name.</p></figcaption></figure>

#### The element tree

The element tree gives you a hierarchical view of every element and is useful for both selecting and rearranging them by dragging and dropping them to a new position in the hierarchy. Use the search bar at the top of the element tree to filter elements by name.

**Article:** [The element tree](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/design-tab/the-element-tree)

<figure><img src="/files/5QAXAv4s6rBYfBtUEZik" alt="Searching for elements in the element tree."><figcaption><p>You can quickly find elements by typing part of their name in the element tree's filter bar.</p></figcaption></figure>

#### X-ray view

X-ray view makes elements semi-transparent in the editor, allowing you to see elements that overlap.

<figure><img src="/files/orALcTz4F6eqG6Amnwvy" alt=""><figcaption></figcaption></figure>

#### App search tool

The app search tool lets you search across pages and workflows for element names, expressions, and more.

<figure><img src="/files/uKKw9v5YcMWXws2gR4Jl" alt=""><figcaption><p>The app search tool in the upper right half of the toolbar lets you search for elements and many other things across your app.</p></figcaption></figure>

**Article:** [The search tool](/help-guides/getting-started/navigating-the-bubble-editor/tools/the-search-tool)

### Inspecting elements

In the element property editor, you can click the contextual dropdown and select *Inspect* to see other parts of your app that are connected to the element.

<figure><img src="/files/C0L8zfdsKTyMTeR9hPUI" alt="Inspect element tool"><figcaption><p>In this example, we're using the <em>Inspect</em> tool to see workflows connected to a specific button.</p></figcaption></figure>

***

## Styles and variables

Rather than styling each element individually, you can define reusable styles and variables that keep your design consistent across the app.

* **Styles** bundle multiple property values together and can be applied to elements of the same type.
* **Font and color variables** let you save fonts and colors that you can reference throughout your app. Updating a variable updates everywhere it's used.

This system makes it much easier to keep a consistent look and to make design changes at scale.

**Article series:** [Styles and variables](/help-guides/design/variables-and-styles)

***

## Element security

Even though Bubble is a no-code platform, your app is ultimately delivered as code that runs in the user's browser. The elements you place on the page become part of that code, which means their names, static text, and default values are visible to anyone who inspects the page.

{% hint style="info" icon="lock" %}
A good rule of thumb is to assume that anything you add to an element can be seen by a curious user. Avoid placing sensitive information anywhere on the page, including in element properties, workflows, custom states, and option sets. For data that needs to be protected, use privacy rules in the database instead.
{% endhint %}

**Article:** [Client-side and server-side](/help-guides/security/client-side-and-server-side)

## Other ways to learn

<details>

<summary>Video lessons</summary>

* [Searching for elements](https://youtu.be/7N5MNPdAOmA)
* [How to swap element positions](https://www.youtube.com/watch?v=04dDc9VaW2Y)

</details>


# The element hierarchy

This section covers the logic of the hierarchy that is structured on your page as you add elements to it.

## The element hierarchy

Every page or view in a Bubble app is made up of elements arranged in a hierarchical structure. This hierarchy shapes how elements are organized, how they inherit data, and how they respond to layout changes.

## Layers in the element tree

The [element tree](/help-guides/getting-started/navigating-the-bubble-editor/tabs-and-sections/design-tab/the-element-tree) in a native mobile app organizes elements into distinct layers, based on how they interact with the view. Each layer serves a different purpose and appears in its own section of the tree.

<figure><img src="/files/sagbDa3XzyxN0OiqKa14" alt="Bubble&#x27;s element tree with element tree layers highlighted."><figcaption><p>The hierarchy on a page or view is divided into layers. The page or view is always at the top.</p></figcaption></figure>

### Overlays

The *Overlays* layer holds elements that appear on top of the view's content, such as sheets (mobile) and popups (web). Elements in this layer float above the main layout and are typically shown and hidden through workflow actions.

### Navigation (mobile)

The *Navigation* layer holds the [app bar and tab bar](/help-guides/design/elements/ios-and-android-app/the-view#view-appearance-settings). These are pseudo child elements that appear when enabled in the [view's](/help-guides/design/elements/ios-and-android-app/the-view) properties. They handle the standard navigation patterns of the app and are edited from within the view. This layer is only visible when you're editing a mobile app.

### Layers

The *Layers* section holds the main content of the view: the elements users interact with as part of the normal flow. Groups, buttons, inputs, lists, and other elements all belong here.

Organizing elements into these layers makes it easier to understand a view at a glance. The main content is separate from the overlays and navigation, and each part can be edited without visually competing with the others.

## The three types of components

A Bubble page or view is built from three types of components. Each plays a distinct role in how the app looks and behaves.

### The page or view

The page (on web) or view (on native mobile) is the top-level component. Every element you add exist inside it. The page/view sits at the very top of the hierarchy, and everything else on the screen is a descendant of it.

The page/view also holds properties that affect the whole screen, such as background color, layout mode, and its data type. In many ways, the page/view is a special kind of container: the one container every other element ultimately belongs to.

### Containers

Containers group elements together and control how they behave as a unit. They act as boxes that hold other elements, forming the branches of the hierarchy.

Containers can:

* Hold visual and interactive elements as children.
* Contain other containers, creating deeper levels of nesting.
* Apply layout rules that affect how their children are arranged.
* Hold a data source that their children can reference.
* Be styled or left transparent, depending on the design.

Common container types include groups, repeating groups, popups, floating groups, sheets, and reusable elements. Each behaves differently, but all follow the same core principle: they contain other elements.

**Article:** [Web containers](/help-guides/design/elements/web-app/containers)\
**Article:** [Mobile containers](/help-guides/design/elements/ios-and-android-app/containers)

### Elements

Elements are the actual content users see and interact with. These are the leaves of the hierarchy: the specific pieces that make up your app's interface.

Elements include:

* **Visual elements** like text, images, icons, videos, and shapes.
* **Input elements** like text fields, checkboxes, dropdowns, and file uploaders.
* **Interactive elements** like buttons, links, and clickable cards.

Elements can trigger workflows, display data, and respond to user input. They can also be styled, hidden, animated, or made conditional based on the state of the app.

**Article:** [Web app elements](/help-guides/design/elements/web-app)\
**Article:** [Mobile app elements](/help-guides/design/elements/ios-and-android-app)

## How the hierarchy works

The way these three components are organized and nested defines the structure of your page or view. Every element has a parent, and every container can have children. This creates a tree-like structure that flows from the page or view down to the individual elements users interact with.

Here's a simplified example of a page hierarchy:

<figure><img src="/files/Emo25jup9urlf7TtebOQ" alt=""><figcaption></figcaption></figure>

Each level of indentation represents a parent-child relationship. Group 1 and Group 2 are children of the page, and the elements to the right are child elements of them.

The hiearchy works in the same way for both web and mobile apps.

## Why the hierarchy matters

The hierarchy isn't just an organizational tool. It has real, practical implications for how your app looks and behaves.

### Layout and responsive design

Container layout rules cascade to their children. A container with a column layout stacks its children vertically. A container with a row layout arranges them side by side. Changing a container's layout affects every child inside it.

This is what makes the hierarchy essential for responsive design. By nesting containers with different layout modes, you can build complex layouts that adapt gracefully to different screen sizes.

### Data inheritance

Containers can hold data, and their children can reference that data directly. For example, if a group holds a *User*, any element inside it can reference `Parent group's User's name` without having to look the user up again.

This "data flows down" pattern is one of the most powerful features of the hierarchy. It lets you set data once at a high level and use it throughout everything nested inside.

### Styling and visibility

Styling and visibility often follow the hierarchy. Hiding a container hides everything inside it. Applying an opacity change or a transform can affect a whole branch of the tree at once. This makes it easy to control large sections of the interface as single units.

### Workflows

Workflows can reference parent groups, sibling elements, and children, all through the hierarchy. Knowing how elements are nested helps you understand which references are available where.

## Pseudo child elements

Some elements can create *pseudo child elements* automatically as part of their configuration. These are elements that appear in the hierarchy without being added manually. They're generated by the parent element based on how it's set up.

Examples include:

* **The app bar and tab bar**, added as pseudo child elements of a view when enabled through the view's properties.
* **Section list and vertical list content**, generated inside a view or a sheet when the *View type* is set to a list-based option.
* **Horizontal list content**, added when a horizontal list is included in a view or sheet.

Pseudo child elements behave like any other element in the hierarchy: they can be selected, styled, given workflows, and referenced in dynamic expressions. What sets them apart is that they can't be created or deleted directly. They exist because of the parent's configuration, and they disappear if that configuration changes.

This pattern lets Bubble expose complex native components in a way that fits naturally into the element hierarchy, without forcing you to build each part from scratch.

## How the browser or app uses the hierarchy

In many ways, the hierarchy is how your browser or mobile app "sees" the page or view before rendering it. The browser reads the tree from top to bottom, uses the container layout rules to determine positioning, and then paints each element on the screen.

Bubble handles most of this automatically, but understanding the hierarchy helps you anticipate how your design will render, how changes will cascade, and how to structure things for the best results.

## Tips for working with the hierarchy

* **Use the element tree.** The element tree in the editor gives you a full view of the hierarchy on any page or view. It's the fastest way to understand how things are nested and to move elements between containers.
* **Group early.** As pages grow, having a clear grouping structure makes them easier to reason about. Even small components benefit from being placed inside a group that gives them a clear role in the layout.
* **Name your containers.** A hierarchy full of *Group A*, *Group B*, and *Group C* is much harder to maintain than one where each container has a meaningful name. Naming makes the structure self-documenting.
* **Don't over-nest.** Deep nesting can slow down rendering and make the layout harder to reason about. Use nesting deliberately, not by default.
* **Think in trees.** Every layout is a tree. When something doesn't look right, tracing the hierarchy is often the fastest way to find out why.


# Web app

Web apps include a wide range of elements designed for browser-based interfaces. These range from basic visual elements like text and images to interactive components like buttons, inputs, and containers, giving you everything you need to build responsive web pages.

Many web elements are shared conventions found across most modern web apps, while others are specific to Bubble. Below is a list of the elements available when building for the web.

## Containers

<table><thead><tr><th width="188.0546875">Element</th><th>Description</th></tr></thead><tbody><tr><td><a href="/pages/0InSA6TX3K40sT0u4F83">Group</a></td><td>The most basic and versatile container type. Used to organize elements, control layout, and hold data.</td></tr><tr><td><a href="/pages/F2Z6HIq5lrrnkRUAKQbg">Repeating group</a></td><td>Displays a list of items by repeating a set of elements for each entry in the data source.</td></tr><tr><td><a href="/pages/vSeYGneOPOJmfX4xfS1u">Popup</a></td><td>An overlay centered on the screen that appears above other elements, typically used for focused interactions like forms, messages, or confirmation dialogs.</td></tr><tr><td><a href="/pages/1fNX5lhkwMiZUxo4mHsI">Floating group</a></td><td>A container that stays fixed to a side of the screen as the user scrolls, ideal for headers, sidebars, or floating action buttons.</td></tr><tr><td><a href="/pages/uDrZfAW9NewNRUhmyEfB">Group focus</a></td><td>An overlay anchored to a specific element that hides automatically when the user clicks outside of it. Useful for menus, tooltips, and inline pickers.</td></tr><tr><td><a href="/pages/xNR63E81QtKAbMSiK90O">Table</a></td><td>Displays a list of items in a structured, column-based layout with support for headers, sticky rows, and grid-like designs.</td></tr></tbody></table>

## Visual elements

<table><thead><tr><th width="152.8984375">Element</th><th>Description</th></tr></thead><tbody><tr><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#text">Text</a></td><td>Displays static or dynamic text on the page.</td></tr><tr><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#button">Button</a></td><td>A clickable element that can trigger a workflow when pressed.</td></tr><tr><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#icon">Icon</a></td><td>Displays a small graphic symbol, often used for actions, navigation, or visual cues.</td></tr><tr><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#link">Link</a></td><td>A clickable element that navigates to another page or external URL.</td></tr><tr><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#image">Image</a></td><td>Displays a static or dynamic image on the page.</td></tr><tr><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#shape">Shape</a></td><td>Displays a rectangle.</td></tr><tr><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#alert">Alert</a></td><td>A temporary message that appears on the page to give the user feedback, such as a confirmation or error notice.</td></tr><tr><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#video">Video</a></td><td>Embeds a video player, supporting YouTube and Vimeo.</td></tr><tr><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#html">HTML</a></td><td>Renders custom HTML directly on the page, useful for embedding third-party components or content.</td></tr><tr><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#map">Map</a></td><td>Displays an interactive map with optional markers.</td></tr><tr><td><a href="/pages/c57rZiHFpEl3aHUPdhqr#map">Built on Bubble</a></td><td>A small badge that links to Bubble's homepage, showing that your app was built with Bubble.</td></tr></tbody></table>

## Input forms

<table><thead><tr><th width="185.81640625">Element</th><th>Description</th></tr></thead><tbody><tr><td><a href="/pages/9usn2lynDsMjIzGqjm6r#input">Input</a></td><td>A single-line field for entering short text, numbers, emails, or other short values.</td></tr><tr><td><a href="/pages/9usn2lynDsMjIzGqjm6r#multiline-input">Multiline input</a></td><td>A multi-line field for entering longer text, such as descriptions, comments, or messages.</td></tr><tr><td><a href="/pages/uNmsNaHIMteorQ6PaDIa#checkbox">Checkbox</a></td><td>A clickable element that lets users toggle a yes/no value.</td></tr><tr><td><a href="/pages/uNmsNaHIMteorQ6PaDIa#dropdown">Dropdown</a></td><td>A collapsible list of options that lets users select a single value.</td></tr><tr><td><a href="/pages/uNmsNaHIMteorQ6PaDIa#searchbox-element">Searchbox</a></td><td>A text field that lets users search for items in a list as they type. Also supports Google Maps addresses.</td></tr><tr><td><a href="/pages/uNmsNaHIMteorQ6PaDIa#radio-button">Radio buttons</a></td><td>A group of options where users can select one value from a set of choices.</td></tr><tr><td><a href="/pages/uNmsNaHIMteorQ6PaDIa#slider-input">Slider input</a></td><td>A slider control for selecting a numeric value within a defined range.</td></tr><tr><td><a href="/pages/yU4rI57C4wpL8CQuejLA#date-time-picker">Date/time picker</a></td><td>A picker for selecting dates, times, or both.</td></tr><tr><td><a href="/pages/yF6fIrsbNIvkMzWEmd8K#image-uploader">Picture uploader</a></td><td>Lets users upload an image file, with an image preview built in.</td></tr><tr><td><a href="/pages/yF6fIrsbNIvkMzWEmd8K#file-uploader">File uploader</a></td><td>Lets users upload files of any type.</td></tr><tr><td><a href="/pages/uNmsNaHIMteorQ6PaDIa#multiselect-dropdown">Multi dropdown</a></td><td>A collapsible list of options that lets users select multiple values at once.</td></tr></tbody></table>

## Reusable element

Reusable elements are containers that can contain other elements and workflows. This container can be reused across your app.

**Article:** [Reusable element](/help-guides/design/elements/web-app/reusable-elements)


# The page

The page is the blank canvas on which you design your web app's user interface.

{% hint style="info" icon="desktop" %}
The page is the element at the top of the hierarchy in a **web app**. For native mobile apps, see [the view](/help-guides/design/elements/ios-and-android-app/the-view).
{% endhint %}

At the core of everything related to design and user interface in Bubble is **the page**. It's the canvas where you place [input elements](#user-content-fn-1)[^1], text, images, icons, videos, and everything else your users need to interact with your app.

<figure><img src="/files/g7vknD4UEVoQUWqiNbZL" alt=""><figcaption><p>The page gives you a blank canvas on which to build your vision.</p></figcaption></figure>

## What is a page?

No matter what kind of app you're building, from a simple website to a complex eCommerce platform, the page is the starting point for all your design and development work. Think of the page as the base level in a page's hierarchy: every element you add sits inside it.

<details>

<summary>What is a page's hierarchy structure?</summary>

A Bubble page is composed of three types of components:

* **The page**\
  The page is the top-level component of a web page and organizes all the groups and elements placed on it.\
  **The page is the top container for all elements**<br>
  * **Groups**\
    Groups are containers used to hold other elements and control their layout. Think of them as boxes that can hold elements, and even other groups, to create a hierarchy. Groups can be styled or made invisible to the user. They play a major role in your page's design, since the way they're set up determines how the elements inside them behave.\
    **Groups are containers for elements and for sub-groups**<br>
  * **Elements**\
    Elements are the content your users see and interact with, such as buttons, links, images, icons, and input fields. They can be placed inside a group, or directly on the page.\
    **Elements are the things that users interact with on the page**

In a way, the hierarchy above is how your browser sees the page before rendering it on the screen. You can read more about the element hierarchy in the article below.

Article: [The element hierarchy](/help-guides/design/elements/the-element-hierarchy)

</details>

## Default pages

When you create a new Bubble web app, a few pages are included by default. These pages share a few traits:

* They're always present when your app is created.
* Their names can't be changed.
* They can't be deleted.

### index

The index page is the main page of your app (more on that [below](#the-index-page)).

### 404

The 404 page is where your users are taken if they type in an URL in the URL bar that includes your domain, but the page name they enter doesn't exist:

{% code overflow="wrap" %}

```
https://appname.bubbleapps.io/non-existing-page → https://appname.bubbleapps.io/404
```

{% endcode %}

The 404 is an [http error code](#user-content-fn-2)[^2] that is used to tell your browser that the page doesn't exists.

<figure><img src="/files/zlsllbd9jgBmk6UTw1i1" alt=""><figcaption><p>The 404 page can be edited freely just like any other page – but it can't be deleted.</p></figcaption></figure>

You can use this page to tell your users that the page couldn't be found and point them in another direction.

### reset\_pw

This is the page to which your users are directed if they need to reset their password. They will most likely follow a link in a reset password email to this page.

<figure><img src="/files/JNL36W1zV3p3SOOmthaj" alt=""><figcaption><p>The reset_pw page is a special page that lets your users reset their password safely. It's part of Bubble's core functionality and can't be removed or renamed.</p></figcaption></figure>

## The index page

The index page is the main page of your app. It's the first page that loads when someone enters your app's domain in the browser. It's also the only page that can be displayed without its name appearing in the URL.

For example, all of the following URLs take you to the index page, even without the page name included:

#### No custom domain

<table><thead><tr><th width="185">Version</th><th>URL</th></tr></thead><tbody><tr><td><strong>Development</strong></td><td>https://appname.bubbleapps.io/version-test</td></tr><tr><td><strong>Live</strong></td><td>https://appname.bubbleapps.io</td></tr></tbody></table>

#### Custom domain

<table><thead><tr><th width="185">Version</th><th>URL</th></tr></thead><tbody><tr><td><strong>Development</strong></td><td>https://www.mydomain.com/version-test</td></tr><tr><td><strong>Live</strong></td><td>https://www.mydomain.com</td></tr></tbody></table>

If your app has any custom branches[^3] set up in version control, the *version-test* part of the URL is replaced by the ID[^4] of the currently active branch.

### Setting an index page

When you create an app in Bubble, the index page is created automatically. You cannot name another page index, but you can replace it by using the *Make this page the new index* function. To change the index page:

{% stepper %}
{% step %}

### Open the page dropdown menu

Open the page navigator, click the three dots on the page you want to make the new index.
{% endstep %}

{% step %}

### Click *Make index*

Select *Make index* in the dropdown menu.
{% endstep %}
{% endstepper %}

You can also change the index page by right-clicking the page element in the design canvas and clicking *Make this page the new index.*

<figure><img src="/files/KOv9lX8GeEHdrsu9k0Lw" alt="Setting a page as the index page by selecting Make index in the page dropdown menu."><figcaption><p>Set a new index page by clicking the <em>Make index</em> option in the page dropdown.</p></figcaption></figure>

{% hint style="info" %}
The old index page will be named `old_index`*.*
{% endhint %}

<details>

<summary>Video lesson</summary>

Video: [How to set a new index page](https://youtu.be/UrWYCO1bdhM)

</details>

## The page URL

Every page you add is automatically assigned a unique URL that users can access, just like any other website. All page URLs follow the same pattern: the root URL of your app followed by the page name.

#### No custom domain

<table><thead><tr><th width="166">Version</th><th>URL</th></tr></thead><tbody><tr><td><strong>Development</strong></td><td>https://myapp.bubbleapps.io/version-test/<strong>page-name</strong></td></tr><tr><td><strong>Live</strong></td><td>https://myapp.bubbleapps.io/<strong>page-name</strong></td></tr></tbody></table>

#### Custom domain

If you have connected your app to a custom domain, the URL will look like this:

<table><thead><tr><th width="154">Version</th><th>URL</th></tr></thead><tbody><tr><td><strong>Development</strong></td><td>https://mydomain.com/version-test/<strong>page-name</strong></td></tr><tr><td><strong>Live</strong></td><td>https://mydomain.com/<strong>page-name</strong></td></tr></tbody></table>

{% hint style="info" %}
If your app has any custom branches[^5] set up in version control, the *version-test* part of the URL is replaced by the ID[^4] of the currently active branch.
{% endhint %}

## Creating a new page

To create a new page:

{% stepper %}
{% step %}

### Click the *New* button

Open the page navigator and click the *New* button.
{% endstep %}

{% step %}

### Click *Web page*

Select *Web page* in the dropdown menu.
{% endstep %}
{% endstepper %}

<figure><img src="/files/e92Ec4RZ35LakZKpCpHI" alt="Create new page dropdown menu"><figcaption><p>Create a new page using the <em>New</em> and <em>Web page</em> option.</p></figcaption></figure>

You will be asked to provide a name for the new page and whether you want to [clone an existing page](#cloning-a-page).

<figure><img src="/files/bnQSLHl1OBk5GK0mMUPl" alt="Create a new page and assign a name dialog."><figcaption><p>You can give your page any name you want, as long as it's URL-safe and unique within the app.</p></figcaption></figure>

### Naming your page

You can create as many pages as you need in your app, as long as they have unique names. Keep in mind when naming pages that they need to be URL-safe[^6]. Any non-URL-safe strings you provide will automatically be [URL encoded](#user-content-fn-7)[^7].

A good rule of thumb for page names is to use only the English letters a-z and numbers 0-9, and to replace spaces with dashes. When URL-encoding a page name, Bubble will:

* Change the text to lowercase.
* Replace spaces with `-`.
* Replace special characters with `_`.

## Cloning a page

Cloning a page creates an exact copy of it under a new name, including all elements and workflows. There are three ways to duplicate a page:

By selecting *New* → *Web page,* and populating the *Clone from* field in the new web page popup...

<figure><img src="/files/e92Ec4RZ35LakZKpCpHI" alt="Create new page dropdown menu"><figcaption><p>You can clone a page by using the <em>New web page</em> option and then selecting a page to clone.</p></figcaption></figure>

... by having the page you want to duplicate open in the editor and selecting *Edit - Duplicate this page...*

<figure><img src="/files/SqBR9MAr5fx8PgkOUPpT" alt="Cloning a page from the Edit menu using the Duplicate page option."><figcaption><p>You can also clone the page by selecting <em>Duplicate page</em> while having the page you want to clone open in the editor.</p></figcaption></figure>

... or by using the contextual dropdown menu and clicking *Duplicate*.

<figure><img src="/files/scwCYsG6GHgCkTbg6Ggk" alt="Duplicate page from the contextual dropdown menu."><figcaption><p>You can duplicate a page by clicking the contextual page menu and selecting <em>Duplicate.</em></p></figcaption></figure>

Regardless of the method you choose, you will be asked to provide a name for the clone and confirm which page to clone in the page dropdown marked below:

<figure><img src="/files/Pp5B4yWZRFC3Ujcuh6oA" alt="Giving the duplicated page a name."><figcaption><p>Give the duplicated page a name, and select which page to clone.</p></figcaption></figure>

## Page properties

Each page can have their own individual settings configured that affects how that page looks and behaves. Settings are available in the **Property Editor.**

There are two ways to access the page settings:

1. Left-click anywhere on the page itself (avoid clicking on another group or element).
2. Clicking on the page in the page's [element tree](#user-content-fn-8)[^8].

<figure><img src="/files/ZaYvG8w7xKIkq3HfEzTQ" alt="Selecting the page in the element tree."><figcaption><p>To select the page from the element tree, click it at the top of the tree.</p></figcaption></figure>

{% hint style="info" %}
For the full list of all page properties, see our [page properties](/core-resources/elements/page-element) article in the core reference.
{% endhint %}

When you select the page, Bubble automatically displays the element property editor on the right side of the screen:

<figure><img src="/files/2ZbhZ8ssFMu9PCVRNkW7" alt="Element property editor for the page element."><figcaption><p>Clicking anywhere on the page opens up the element property editor for the page.</p></figcaption></figure>

### Visual

The **Visual** tab lets you control the visual properties of the page, such as its content type, layout, size, style, background, and appearance.

### Interaction

The **Interaction** *tab* lets you control the page's interactive properties, such as workflows, transitions and meta tags.

### Conditional

The **Conditional** tab lets you change the page's properties when one or more conditions[^9] are true.

## Organizing your pages

Pages can be organized into folders in the Bubble editor to make them easier to find.

{% hint style="warning" %}
Organizing pages into folders makes it easier to find them in the editor, but it doesn't affect the page's URL or how users navigate to it in your app.
{% endhint %}

### Creating a page folder

To create a new page folder:

{% stepper %}
{% step %}

### Click the *New* button in the page navigator

Open the page navigator and click on the *New* button in the upper right corner.
{% endstep %}

{% step %}

### Name the folder

Click *Folder* in the dropdown menu and enter a name in the dialog that appears.
{% endstep %}
{% endstepper %}

<figure><img src="/files/ut9cPZsuRQiZY3UkNmAU" alt="Creating a new page folder."><figcaption><p>Create a new page folder by selecting <em>New</em> and then <em>Folder</em>.</p></figcaption></figure>

### Adding a page to a folder

To move a page into a folder:

{% stepper %}
{% step %}

### Open the page contextual menu

Open the page navigator, and click the contextual menu icon next to the page name.
{% endstep %}

{% step %}

### Click *Manage folder*

Click *Manage folder* and select the folder you want to page the page into.
{% endstep %}
{% endstepper %}

{% hint style="info" %}
You need to [create a folder](#creating-a-page-folder) before you try to move a page.
{% endhint %}

<figure><img src="/files/oR01C5uiya4fJsV315cA" alt="Move a page into a folder."><figcaption><p>Click the page's contextual menu and select <em>Manage folder</em> to move it into a folder.</p></figcaption></figure>

### Managing folders

You will find all the folders you've created at the bottom of the page navigator. Use the contextual menu to delete a folder.

<figure><img src="/files/purLlHSUc9rT2j0nEYUi" alt="Managing page folders in the page navigator."><figcaption><p>Open the page navigator, and you can manage folders at the bottom.</p></figcaption></figure>

<details>

<summary>Video lessons</summary>

Video: [How to organize your pages in folders](https://youtu.be/VriA0xZg7co)

</details>

## FAQ: Pages

<details>

<summary>Can I add page folders to my URL structure?</summary>

Bubble doesn't support page folders in your app's URLs. Every page follows the same URL structure described above, regardless of how it's organized in the editor.

</details>

<details>

<summary>Can I publish and unpublish pages?</summary>

All pages are public. Once a page is created, it's accessible in your app as soon as changes are deployed. If you'd like to keep the elements on a page without making the page available to users, you can convert its contents into a reusable element and store them there instead.

</details>

<details>

<summary>How do I navigate between pages?</summary>

To send a user to another page, use the [*Go to page*](/core-resources/bubble-workflows/bubble-actions/navigation-actions-in-web-apps#go-to-page) workflow action or the [link element](/core-resources/bubble-elements/element-properties/web-element-properties/visual-element-properties/link-element).

</details>

<details>

<summary>Can I restrict a page to logged-in users?</summary>

Yes. You can restrict access using a workflow that triggers on page load, checks whether the user is logged in, and redirects them to a login page if they aren't. Keep in mind that page-level redirects aren't fully secure on their own, since page assets are downloaded to the client before the redirect runs. Sensitive data should always be protected with privacy rules in the database.

</details>

<details>

<summary>What's the difference between a page and a reusable element?</summary>

A page is a standalone canvas that users can navigate to via a URL. A reusable element is a component you build once and use in multiple places, such as a header, footer, or shared form. Pages appear in the navigation and have their own URLs, while reusable elements are embedded inside pages or other reusable elements.

</details>

<details>

<summary>Can two pages share the same URL?</summary>

No. Every page in your app must have a unique name, and each name maps directly to a URL. If you want two pages to appear at the same URL, you'll need to combine them into one page and use conditions or dynamic groups to show different content based on the situation.

</details>

<details>

<summary>Can I use dynamic URLs, like `/product/[product-name]`?</summary>

Yes. Bubble supports dynamic URLs by giving the page a type of content and passing data to it. Use the *slug* field on a data type to show a URL-friendly name.

</details>

<details>

<summary>What happens if a user visits a page that doesn't exist?</summary>

Bubble automatically shows a 404 error page. You can customize the design of the 404 page in the same way as any other page in your app.

</details>

<details>

<summary>Can I make a page load faster?</summary>

Yes. A few things help: reduce the number of elements on the page, limit the amount of data loaded at page load, avoid heavy media files where possible, and use conditions to defer rendering of non-essential content. Bubble also caches assets automatically for repeat visits.

</details>

## Other ways to learn

<details>

<summary>Related articles</summary>

* Core reference: [*Go to page*](https://manual.bubble.io/help-guides/design/elements/web-app/pages/-MTujrgFLiHe7MfwSBOF#go-to-page-...)
* Core reference: [The link element](/core-resources/elements/visual-elements#link)
* Article section: [The link element](/help-guides/design/elements/web-app/visual-elements#link)
* Article series: [Navigation](/help-guides/logic/navigation)

</details>

<details>

<summary><mark style="color:blue;">Core reference:</mark> page element settings</summary>

Reference: [The page element](/core-resources/elements/page-element)

</details>

<details>

<summary><mark style="color:blue;">Video lessons</mark></summary>

* [How to use the page element](https://youtu.be/9MBzCjbJnNI)
* [How to set a new index page](https://youtu.be/UrWYCO1bdhM)
* [How to organize your pages in folders](https://youtu.be/VriA0xZg7co)

</details>

[^1]: **Input elements** are components that let users provide different types of data, such as text, numbers, and dates. This data can be saved to the database or used elsewhere in your app.

    With input elements, you can build anything from simple sign-in pages to intricate forms.

    **Article series:** [Input forms](/help-guides/design/elements/web-app/input-forms)

[^2]: **HTTP error codes** are messages the server sends to the user's browser when something goes wrong.

    A 404 error means the page couldn't be found on the server.<br>

    **Article section:** [What is the HTTP protocol?](/help-guides/integrations/api/introduction-to-apis/what-is-a-restful-api#what-is-the-http-protocol)

[^3]: A branch is an independent iteration of your application that can be developed in isolation. You can see the creation of a branch as splitting your app into two copies, kind of like two cells dividing. The cells are genetically identical clones at first, but can keep evolving independently of each other.\
    \
    Article section: [Environments and branches](#environments-and-branches)

[^4]: For each custom *branch you create in your app,* Bubble generates a unique ID.\
    \
    Article: [Version control](/help-guides/maintaining-an-application/version-control)

[^5]: A branch is an independent iteration of your application that can be developed in isolation.

    You can see the creation of a branch as splitting your app into two copies, kind of like two cells dividing. The cells are genetically identical clones at first, but can keep evolving independently of each other.\
    \
    Article section: [Environments and branches](#environments-and-branches)

[^6]: **URL-safe** means the text is in a format a browser can read as part of a URL.

    Special characters like spaces, slashes, and ampersands can cause issues and should be avoided.

[^7]: **URL encoding** converts a string of text into a format the browser can read as part of a URL without errors.

    For page names, Bubble applies URL encoding automatically whenever it's needed.

[^8]: The element tree shows the hierarchy of all elements on the current page. You can use it to open the settings for any element, including the page itself.

    **Article:** [The element hierarchy](/help-guides/design/elements/the-element-hierarchy)

[^9]: Conditions in Bubble are expressions that return either true or false, and use that result to change how an element looks or behaves.\
    \
    Article: [Conditions](/help-guides/logic/conditions)


# Containers

This section covers the container elements, used to group and control the behavior of other elements

{% hint style="info" %}
This section takes a long-form look at what containers are and how you can use them in different situations.\
\
To see the full list of properties available on container elements, you can check out our more concise [core reference entry on containers](/core-resources/elements/containers).
{% endhint %}

{% hint style="info" icon="desktop" %}
This article covers containers for wep apps. For containers used in mobile apps see the article below:

**Article:** [Mobile containers](/help-guides/design/elements/ios-and-android-app/containers)
{% endhint %}

<figure><img src="/files/giLHZKHIX1Q5PwaPNapE" alt="Example of containers used to build a form."><figcaption><p>Containers can hold elements like the form in the example above. Here we're using a <a href="/pages/0InSA6TX3K40sT0u4F83"><em>group</em></a> element.</p></figcaption></figure>

Containers hold other elements[^1] and control how they behave on the page. Placing an element inside a container makes the container its parent and the element its child, forming the hierarchical structure the browser uses to render the layout correctly. You can see this parent-child relationship in the [element tree](#user-content-fn-2)[^2].

Containers are one of the most important tools in Bubble. They control layout, hold data, group related elements, and are the foundation of any responsive design. Mastering containers is essential to mastering design in Bubble.

## What containers do

Containers serve three main purposes:

1. **They organize elements into a hierarchy.** Grouping related elements together makes them easier to manage, move, style, and reference.
2. **They control layout.** A container's layout settings determine how its child elements are arranged and how they respond to different screen sizes.
3. **They hold data.** A container can be given a data source, making that data available to every element inside it.

Most containers do more than one of these at the same time. A single group might hold a form's fields, control their layout, and load the user record being edited, all in one.

### Container types

Bubble offers several container types on web, each suited to different situations.

<table><thead><tr><th width="176.2109375">Container</th><th>Purpose</th></tr></thead><tbody><tr><td><a href="/pages/0InSA6TX3K40sT0u4F83">Group</a></td><td>The most common container. Used to organize elements, control layout, and hold data.</td></tr><tr><td><a href="/pages/F2Z6HIq5lrrnkRUAKQbg">Repeating group</a></td><td>Displays a list of items by repeating a template cell for each record.</td></tr><tr><td><a href="/pages/xNR63E81QtKAbMSiK90O">Table</a></td><td>Displays a list of items in a structured table.</td></tr><tr><td><a href="/pages/vSeYGneOPOJmfX4xfS1u">Popup</a></td><td>An overlay that appears on top of the page, typically for dialogs or focused interactions.</td></tr><tr><td><a href="/pages/1fNX5lhkwMiZUxo4mHsI">Floating group</a></td><td>A container that stays fixed in place on the screen as the user scrolls, often used for headers or persistent menus.</td></tr><tr><td><a href="/pages/uDrZfAW9NewNRUhmyEfB">Group focus</a></td><td>A small overlay anchored to another element, typically used for dropdown menus or contextual actions.</td></tr></tbody></table>

## Controlling layout with containers

Containers use one of four layout modes to arrange their children:

* **Column** stacks children vertically. Each element sits below the previous one.
* **Row** arranges children horizontally, side by side.
* **Align** lets you position children in one of nine fixed positions within the container.
* **Fixed** gives each element a fixed position, without automatic layout logic.

Each layout mode comes with its own set of alignment and spacing options. Choosing the right layout mode is one of the most important decisions when building a responsive design. You can also nest containers inside other containers.

Using containers to control layout is the key to an efficient responsive design.

**Article series:** [Responsive design](/help-guides/design/responsive-design)

## Loading data into containers

Containers can hold data, which makes it available to every element inside them.

For example, a form for editing a user might have a container with its data source set to *`Current User`*. Every input field inside that container can then reference *`Parent group's User's name`*, *`Parent group's User's email`*, and so on, without needing to fetch the user separately in each element.

### **Two ways to load data**

There are two main ways to load data into a container:

#### Set the container's data source

Setting a container's data source makes the data load instantly. First, you need to set the *Type of content*, such as `User`. This tells Bubble what kind of data to expect, and the container will only accept that type. Then set the *data source* itself, such as `Current User`.

*`Current User`* is just one option. Any expression that returns a `user` is valid, such as `Do a search for:first item`, as long as the search returns a `user`.

The data loads automatically when the page loads. This is useful for data that's known upfront, like the current user or the current page thing.

<figure><img src="/files/s1jHdZPsPfX4cC4ErHMW" alt="Loading a data source directly on a container on page load."><figcaption><p>In this example, we have a group called <em>User form.</em> The type of content is set to <code>User</code>, and the expression that loads the user into the form is <code>Current User</code>.</p></figcaption></figure>

In containers that handle multiple things, like a repeating group, you follow the same logic. Any expression that returns a list of users will be accepted:

<figure><img src="/files/Ejf2vweZ2s7snGk6gT97" alt="Loading a list into a repeating group on page load."><figcaption><p>A repeating group requires a list of things returned by the dynamic expression.</p></figcaption></figure>

#### Push data with a workflow

Use the *Display data in a group/popup* and *Display data in a repeating group* actions to load data into a container in response to a user action. This is useful when the data depends on something the user does, like clicking a specific item in a list.

<figure><img src="/files/tUGwrWAOfRsJU05Cjvnu" alt=""><figcaption><p>In this example, clicking a button triggers an action that loads data into a container.</p></figcaption></figure>

Both methods make the data available to child elements. The choice comes down to whether the data should load immediately or in response to something the user does.

### **Referencing data in elements**

Once a container holds data, its child elements can reference it directly. In the field settings for a child element, use expressions like `Parent group's User's email` to pull data from the container.

<figure><img src="/files/yz3MMkx82fsL1thzlnbQ" alt=""><figcaption><p>In this example, we're loading the email address of the user held in the container, as the input's initial content.</p></figcaption></figure>

This makes containers a powerful way to structure a page: load the data once at the container level, then reference it from wherever it's needed inside.

### **Referencing data in workflows**

The same works in workflows. A *Save* button inside a container holding a user can reference `Parent group's User` directly, making it easy to save changes back to that user.

<figure><img src="/files/oqCXPBS9c9G1gglyAFM1" alt=""><figcaption><p>In this example, a button inside a group references the <code>Parent group's user</code> to make changes to it.</p></figcaption></figure>

For this to work, the workflow must be triggered by an element inside the container. If a *Save* button sits outside the container, it won't have access to the container's data through the `Parent group's thing` expression, but you can still reference the group by name.unless it references it another way.

This pattern works with any container type, including cells inside a repeating group.

### Resetting a container

Resetting a container reverts it to its original state. The *Reset a group/popup* action clears the current data and restores the data source defined at the container level.

{% hint style="info" %}
**Reset doesn't mean empty**. It means "return to the initial state." If a container had a data source set at the element level, resetting it reloads that data. If no data source was set, it becomes empty.
{% endhint %}

Whenever the content of a container changes, either through a data source update or a workflow, all child elements update to reflect the new data.

## Styling containers

Containers can be styled just like any other element. They support background colors, borders, border radius, shadows, opacity, and more. They can also be set to fully transparent or invisible when their only purpose is to control layout or hold data.

To open a container's style settings, click on it in the element tree or on the canvas. Its properties appear in the property editor on the right.

<figure><img src="/files/vxjr9tr8a6DOOyrstymg" alt="A container&#x27;s styling properties in the element property editor."><figcaption><p>Containers can be styled individually, or by using predetermined styles.</p></figcaption></figure>

Using shared styles or style variables keeps container styling consistent across your app.

Article series: [Styles](/help-guides/design/variables-and-styles/styles)

## Working with containers in the editor

A few tips make working with containers easier:

* **Use the element tree to navigate parent-child relationships**. It's the fastest way to select a specific container in a complex design.
* **Give containers meaningful names**. Group Header and Group Cart Items are much easier to work with than Group A and Group B.
* **Nest carefully**. Nested containers make responsive design more flexible, but excessive nesting can slow down rendering and make the layout more difficult to manage.
* **Consider reusable elements**. If the same container structure appears in multiple places, converting it into a reusable element saves time and keeps the design consistent.

## Container security

Like all elements, containers become part of your app's client-side code. Their names, structure, and any static content are visible to anyone inspecting the page. Sensitive information should never be placed inside a container's properties, data source expressions, or default values. Protect sensitive data with privacy rules in the database instead.<br>

## FAQ: Web containers

<details>

<summary>What's the difference between a group and a repeating group/table?</summary>

A [group](/help-guides/design/elements/web-app/containers/groups) holds a single set of elements and, optionally, a single piece of data. A [repeating group](/help-guides/design/elements/web-app/containers/repeating-groups) or [table](/help-guides/design/elements/web-app/containers/table-elements) displays a list of items by repeating a template cell for each record in the list.&#x20;

</details>

<details>

<summary>Can I nest containers inside each other?</summary>

Yes. Nesting containers is a common pattern for building responsive layouts. Just keep an eye on how deep the nesting goes, since deeply nested layouts can be harder to maintain.

Also keep in mind that nesting [repeating groups](/help-guides/design/elements/web-app/containers/repeating-groups) or [tables](/help-guides/design/elements/web-app/containers/table-elements) can slow your app down and consume more workload[^3] than you intended, since the data source loads separately in each cell.

</details>

<details>

<summary>Does child data update automatically when I change the container's data source?</summary>

Yes. When a container's data source changes, all child elements referencing that data update to reflect the new value.

</details>

<details>

<summary>What happens to child elements when I delete a container?</summary>

Deleting a container also deletes every element inside it. To keep the child elements, move them out of the container first.

</details>

<details>

<summary>Can containers hold different data types?</summary>

Each container's data source has a single type, such as User or Post. Nested containers can each hold their own data type, allowing more complex layouts.

</details>

<details>

<summary>How do I know which container an element belongs to?</summary>

The [element tree](#user-content-fn-2)[^2] shows the full hierarchy. Selecting an element also highlights its position in the tree, making it easy to see which container it belongs to.

</details>

<details>

<summary>Can I make a container invisible?</summary>

Yes. Uncheck *Visible on page load* in the property editor, or set the container's opacity or background to be fully transparent. Invisible containers still function normally for layout and data.

You can collapse a container's height and width when it's invisible, by checking the *Collapse when hidden.* This is the most common way of setting up SPAs[^4], by hiding and showing groups as needed.

</details>

<details>

<summary>Which container should I use for a form?</summary>

A standard group is usually the right choice. Set its data source to the record being edited, add the input fields as children, and connect them to the container's data through `Parent group's` expressions.

</details>

<details>

<summary>Can a container trigger a workflow?</summary>

Yes. Setting up a workflow on the container's *An element is clicked* event lets it behave like a clickable element. This is useful for cards, list items, and other interactive layouts.

</details>

[^1]: Elements are the individual pieces of your app's interface, such as buttons, images, input fields, and containers. Combined, they make up everything a user sees and interacts with on the page.\
    \
    Article: [Elements](/help-guides/design/elements)

[^2]: The *element tree* is the list of elements organized in a hierarchy that you can see on the left side of the Bubble design editor.\
    \
    Article: [The element tree](broken://pages/I16X4KOwxZmqx2I6J6XO)

[^3]: Workload is a measure of how much processing your app uses. Every server-side operation, like database queries, workflow actions, and external API calls, consumes workload

    **Article series:** [Workload](/help-guides/workload)

[^4]: An **SPA**, or **single-page app**, is a type of web app that keeps the user on one page and dynamically shows and hides content instead of loading separate pages for each section.

    **Article:** [Single-page applications](/help-guides/logic/navigation/single-page-applications-spa)


# Group element (web)

This section covers Groups, that can be used to contain elements and data and control the responsive behavior of child elements

Groups are the most basic and versatile container type in Bubble. A group can be placed anywhere on a page or inside another container, and used to organize elements, control layout, hold data, and add visual styling.

Because they're so flexible, groups are the container you'll reach for most often when building an app.

{% embed url="<https://www.youtube.com/watch?v=HWmgmzIQRfg>" %}

<figure><img src="/files/giLHZKHIX1Q5PwaPNapE" alt="Group element example with a form."><figcaption><p>Groups can hold elements like the form in the example above.</p></figcaption></figure>

## What groups do

Groups serve three main purposes:

1. **Organize elements.** A group holds other elements as its children, keeping related items together and forming a clear hierarchy in the element tree.
2. **Control layout.** Layout settings on a group determine how its children are arranged and how they respond to different screen sizes.
3. **Hold data.** A group can be given a data source, making that data available to every element inside it.

Groups can serve one or all of these purposes at the same time. A single group might structure a form, control its layout, and load the user record being edited.

For a broader look at container types and how they compare, see the article on containers.

## Loading data into a group

Groups can hold a single piece of data, made available to every element inside. Setting this up takes two steps:

1. Set the group's [*Type of content*](#user-content-fn-1)[^1] to the type of data it will hold, such as `User` or `Post`. This tells Bubble what to expect and prevents mismatched [data sources](#user-content-fn-2)[^2].
2. Set the *Data source* to an expression that returns a value of that type, such as `Current User` or `Do a search for:first item`.

Once the data source is set, child elements can reference it directly using expressions like `Parent group's User's name`.

There are two ways to load data into a group:

### Set the data source directly

The data loads as soon as the page loads. This works well for data known upfront. The section [above](#loading-data-into-a-group) explains this method.

<figure><img src="/files/s1jHdZPsPfX4cC4ErHMW" alt=""><figcaption><p>Setting the data source directly loads the data immediately.</p></figcaption></figure>

### Push data with a workflow

Use the [*Display data in a group/popup*](/core-resources/bubble-workflows/bubble-actions/element-actions#display-data-in-a-group-popup) action to load data in response to a user action, such as clicking an item in a list.

<figure><img src="/files/tUGwrWAOfRsJU05Cjvnu" alt="Using the Display data in group/popup action to push data to a group."><figcaption><p>You can push data to a group as a result of user action, by using the <em>Display data in group/popup</em> action.</p></figcaption></figure>

Both methods make the data available the same way. The choice comes down to whether the data should load immediately or in response to user action.

## Controlling layout with groups

Groups use one of four layout modes to arrange their children:

* **Column** stacks children vertically.
* **Row** arranges children horizontally.
* **Align** positions children in one of nine fixed positions inside the group.
* **Fixed** lets you place each child at a specific position without automatic layout logic.

Each mode has its own alignment and spacing options. Choosing the right layout mode is one of the most important decisions when building a responsive design.

Nested groups are a common pattern: an outer group with a column layout might hold several inner groups with row layouts, each representing a section of the page. Together, they form the responsive structure of the design.

**Article series:** [Responsive design](/help-guides/design/responsive-design)

## Styling groups

Groups can be styled like any other element. They support background colors, borders, border radius, shadows, opacity, and more. They can also be made transparent or fully invisible when their only purpose is to organize elements or control layout.

<figure><img src="/files/vxjr9tr8a6DOOyrstymg" alt="Styling properties for a group element."><figcaption><p>Group's can be styled individually, or by using predetermined styles.</p></figcaption></figure>

Using shared styles or style variables keeps groups consistent across your app.

**Article series:** [Styles](/help-guides/design/variables-and-styles/styles)

## Collapsing hidden groups

When a group is set to be invisible, it can also be set to **collapse**. A collapsed group takes up no space on the page, so surrounding elements move up to fill the gap. When the group becomes visible again, the layout reflows to make room for it.

This behavior is controlled by the *Collapse when hidden* property. Without it, an invisible group still occupies its original space, leaving an empty area on the page.

<figure><img src="/files/8PiQa2Iq1eN1IGrJMRgo" alt="The Collapse when hidden property on a group element."><figcaption><p>The <em>Collapse when hidden</em> property collapses the element when it's hidden, making room for other elements to take its place. Both height and width are collapsed.</p></figcaption></figure>

Collapsing hidden groups is the foundation of many common Bubble patterns, including [single-page apps](/help-guides/logic/navigation/single-page-applications-spa).

## Using groups for navigation

Because groups can be shown, hidden, and collapsed, they're a natural way to build in-page navigation without moving the user to a different page.

By placing different sections of your app inside separate groups and toggling their visibility, you can let users move between sections instantly without triggering a page reload. This pattern is often used to build single-page apps (SPAs).

<figure><img src="/files/8XwCjAUuciKrG3sIbr7E" alt="Animation of a group collapsing, making room for another group." width="417"><figcaption><p>In the example above, we are toggling the visibility of Group A and Group B.</p></figcaption></figure>

### **Building an** [**SPA**](#user-content-fn-3)[^3] **with groups**

The pattern typically looks like this:

1. Add several groups to the page, each representing a section of the app (dashboard, settings, profile, and so on).
2. Disable *Visible on page load*, with *Collapse when hidden* enabled.
3. Use conditions or workflows to show the right group based on the user's current context.

Because only one group is visible at a time, the layout adjusts automatically, and the user sees a clean view of the section they're in.

#### **Showing and hiding groups**

There are two ways to control which group is visible:

**Conditions**

Set conditions on each group that show it when certain criteria are true. A common pattern uses URL parameters, such as showing a group when the URL contains `?navigation=edit_user`. This makes each section bookmarkable and shareable.

<figure><img src="/files/VKeFRqMuXvPrI1HTFzPI" alt="Showing a group using a conditional"><figcaption><p>In this example we're using <a href="/pages/45paecU4jIMLUMYn1R51">URL parameters</a> to show a group.</p></figcaption></figure>

**Workflows**

Use actions like [*Show an element*](/core-resources/bubble-workflows/bubble-actions/element-actions#show-an-element), [*Hide an element*](/core-resources/bubble-workflows/bubble-actions/element-actions#hide-an-element), [*Toggle an element*](/core-resources/bubble-workflows/bubble-actions/element-actions#toggle-an-element), or [*Animate an element*](/core-resources/bubble-workflows/bubble-actions/element-actions#animate-an-element) to control visibility in response to user actions. Toggle is useful for switching between two states, while Animate adds transitions for a smoother visual effect.

{% embed url="<https://www.youtube.com/watch?v=gDqAc9hj6Mc>" %}

#### **Benefits of using groups for navigation**

* **Faster transitions.** Switching between sections happens instantly, since the page doesn't reload.
* **Preserved state.** Data and input values stay intact as the user moves between sections.
* **Simpler navigation logic.** All the flow lives on one page, making it easier to reason about.

#### **Things to keep in mind**

* SPAs can grow complex quickly. Naming groups clearly and organizing the element tree carefully makes the design easier to manage.
* All assets on the page load at once, so a large SPA can affect initial load time.
* [URL parameters](#user-content-fn-4)[^4] or custom states are commonly used to track which section is currently visible, letting users bookmark or share links to specific views.

## FAQ: Groups in web apps

<details>

<summary>What's the difference between a group and a repeating group?</summary>

A group holds a single set of elements and, optionally, a single piece of data. A repeating group displays a list of items by repeating a template cell for each record in a list.

Groups are used for individual pieces of content, while repeating groups are used for collections.

</details>

<details>

<summary>Can groups hold other groups?</summary>

Yes. Nested groups are a core building block for responsive layouts.

</details>

<details>

<summary>Do I need to give every group a data source?</summary>

No. Groups can be used purely for layout, styling, or organization without holding any data. A data source only becomes relevant when the elements inside need to reference specific information.

</details>

<details>

<summary>What happens to child elements when I delete a group?</summary>

Deleting a group also deletes every element inside it. To keep the child elements, move them out of the group first.

</details>

<details>

<summary>Can I make a group clickable?</summary>

Yes. Set up a workflow triggered by the group's When clicked event, and the group will behave like a clickable element. This is useful for cards, menu items, and interactive layouts.

</details>

<details>

<summary>Can a group be transparent?</summary>

Yes. Set the background to fully transparent or reduce the opacity. The group still functions normally for layout and data, but has no visible appearance.

Note that reducing the opacity and setting the group to invisible is not the same. For groups to collapse, they need to be invisible.

</details>

<details>

<summary>What's the difference between hidden and collapsed?</summary>

A hidden group is invisible but still takes up space on the page. A collapsed group is invisible and takes up no space, so surrounding elements move to fill the gap. Collapsing is controlled by the Collapse when hidden property.

</details>

<details>

<summary>Are groups available in native mobile apps?</summary>

Yes, see [this article](/help-guides/design/elements/ios-and-android-app/containers).

</details>

## Other ways to learn

<details>

<summary><mark style="color:blue;">Core reference:</mark> Group properties</summary>

In the core reference section, you'll find all the settings associated with groups.

Reference: [Groups](/core-resources/bubble-elements/element-properties/web-element-properties/container-properties/group-element)

</details>

[^1]: The **Type of content** property tells Bubble what kind of data the container will hold, such as `User` or `Post`. This determines what data source it accepts and what child elements can reference through `Parent group`.

[^2]: A **data source** is any source your app pulls data from, such as a database search, a specific record, an option set, or an external API.

    **Core reference:** [Data sources](/core-resources/data/data-sources)

[^3]: A single-page app (SPA) lets users move between different sections without reloading the page. Instead of splitting content across multiple pages, everything lives on one page, and elements are shown or hidden based on the user's actions.\
    \
    **Article:** [Single-page applications](/help-guides/logic/navigation/single-page-applications-spa)

[^4]: A URL parameter is a piece of data attached to the end of a URL, used to pass information to the page. Parameters follow a `?key=value` format, such as `?section=dashboard`, and can be read by your app to control what's displayed or how it behaves.

    **Article:** [URL-parameters](/help-guides/data/temporary-data/url-parameters)


# Repeating group element (web)

This section covers the container type repeating group, used to display lists of things such as records from the database

A repeating group is a container used to display a list of items, such as records from your database. It works by repeating the same set of elements once for each item in the list, making it easy to build lists, grids, and card layouts without writing code.

Repeating groups are one of the most powerful tools in Bubble. Almost any interface that shows more than one thing at a time, from feeds to product catalogs to search results, is built using them.

{% hint style="info" %}
To show a list of things, you can also use the **table element**. You can read more about the table element and how it's different from a repeating group in the article below:

Article: [The table element](/help-guides/design/elements/web-app/containers/table-elements)\
Article section: [The difference between repeating groups and tables](/help-guides/design/elements/web-app/containers/table-elements#the-difference-between-repeating-groups-and-tables)
{% endhint %}

## What repeating groups do

At the most basic level, a repeating group takes a list of things and repeats its child elements once for each item. For example, if you show a list of five blog posts, everything you add to the repeating group is duplicated five times, once per post.

<figure><img src="/files/RTbHwEcsFALqHQ1DaQh8" alt="Repeating group showing a list of blog posts."><figcaption><p>A repeating group is used to show a list of something, such as these blog posts.</p></figcaption></figure>

Each of these repetitions is called a **cell**. A cell holds all the child elements and represents a single item from the list. Text elements can display data from that item, buttons can trigger actions on it, and containers can be styled to give each cell its own look.

Repeating groups work similarly to regular groups: you set a *Type of content*, provide a *Data source*, and the elements inside can reference the data. The difference is that a regular group holds one item, while a repeating group holds many.

## Referencing data in a cell

Because each cell represents a different item in the list, referencing data inside a cell uses the *Current cell* expression. If the *Type of content* is *Post*, you'd reference `Current cell's Post's Title` to show the post's title.

This pattern lets Bubble automatically insert the right data into each cell without you having to configure them individually. One text element inside the repeating group displays all the names in the list, one per cell.

## Common designs

Repeating groups are flexible enough to build almost any list-based interface. Here are three of the most common patterns.

### Simple lists

The most basic use of a repeating group is a straightforward list, such as a contact list, a task list, or a search result list. Each cell displays a few pieces of information, usually as text.

To build one:

1. Set the [*Type of content*](#user-content-fn-1)[^1] to the data type you want to display, such as `Post`.
2. Set the [*Data source*](#user-content-fn-2)[^2] using an expression like `Do a search for Posts`.
3. Add text elements inside the cell and reference the data with expressions[^3] like `Current cell's Post's Title`.

The result is a clean list that updates automatically when the underlying data changes.

### Card layouts

The same repeating group can be used to display richer content, like product cards or dashboard tiles.

To build a card layout, place a regular group inside the cell of the repeating group. Style that inner group with a background color, border radius, and shadow to give it the look of a card. Inside the card, add elements like images, text, and buttons.

<figure><img src="/files/cxgatA0zdKBpCXLQVqeS" alt=""><figcaption><p>By combining the repeating group with child containers and elements, you can set up card designs.</p></figcaption></figure>

Each element inside the card references its parent group's data (the current item), and the parent group references the current cell of the repeating group. This lets you design complex, styled cards that populate automatically for each item in the list.

For example, a product catalog might use cards that show a product image, name, price, and an *Add to cart* button, all inside a single card group that repeats once per product.

### Masonry grids

Repeating groups can also display items in a masonry layout, where cells have varying heights and pack together automatically. This is useful for image galleries, blog previews, or any content where items naturally have different sizes.

{% embed url="<https://demo.arcade.software/hVybqmgzGgm6oMMX93c2?embed>" %}

To set up a masonry layout:

1. Make sure the page uses the responsive engine.
2. Open the repeating group's property editor and uncheck both *Set fixed number of rows* and *Stretch rows to fill vertical space*.
3. Set *Scroll direction* to *Vertical*.
4. Check *Display items as masonry grid*.

<figure><img src="/files/GI425dMsOf0VCXlBdjsE" alt=""><figcaption><p>Enabling <em>Display items as a masonry grid</em> lets you show items in the repeating group at dynamic sizes that adjust automatically to any screen size.</p></figcaption></figure>

You can control the spacing between cells using the *Row cell gap* and *Column cell gap* settings.

## Loading data into a repeating group

There are two main ways to load data into a repeating group:

### Set the data source directly

{% hint style="info" %}
Repeating groups require a **list** as their data source, unlike most other containers, which work with a single item.
{% endhint %}

The list loads as soon as the page loads, using an expression like `Do a search for Users`. This is useful when the data is known in advance.

<figure><img src="/files/CAjdc716ZjqhZj9QN84k" alt="Loading content into a repeating group by setting a data source."><figcaption><p>You can load a list into a repeating group by using a data source such as <a href="/pages/-MShSvpeDGq5TEeTupJf#do-a-search-for"><em>Do a search for.</em></a></p></figcaption></figure>

### Push data with a workflow

Use the [*Display list in a repeating group or table*](/core-resources/bubble-workflows/bubble-actions/element-actions#display-list-in-a-repeating-group-or-table) action to load a list in response to a user action, such as clicking a filter or performing a search. This is useful when the data depends on something the user does.

<figure><img src="/files/RhinWV5Gwl38V4oaL9JB" alt=""><figcaption><p>You can also load a list into a repeating group using the <a href="/pages/jBFRRz2u1brrkN2NclGa#display-list-in-a-repeating-group-or-table"><em>Display list in a repeating group</em></a> action.</p></figcaption></figure>

## Layout and scrolling

Repeating groups give you a lot of flexibility in how they arrange and display their cells. By combining a few key properties, you can build everything from short, contained lists to long, infinite feeds and horizontally scrolling carousels.

The main levers to play with are:

* **Rows**, which controls how many rows the repeating group shows at a time.
* **Columns**, which controls how cells are arranged horizontally.
* **Scroll direction**, which controls whether the repeating group scrolls vertically or horizontally, or wraps to the next row.
* **Display items as masonry grid**, which enables a dynamic, packed layout with variable cell heights.

The way you combine these determines the character of the list.

#### **Fitting the list to its content**

Setting *Rows* to *Fit to content* makes the repeating group grow to show every row it contains, with no internal scrolling. The page itself scrolls if the list is long enough to run off the screen. This works well for lists where you want the entire list visible at once, or where the surrounding page layout should adjust to the size of the list.

#### **Filling the container**

Setting *Rows* to *Fill container* makes the repeating group take up all the vertical space available inside its parent, and adds internal scrolling when the list is longer than the visible area. This is useful when the repeating group sits inside a fixed-height section, like a sidebar or a card, and you want the list to scroll within that space without affecting the rest of the page.

#### **Setting a fixed number of rows**

Setting *Rows* to *Fixed number* shows a specific number of rows at a time, regardless of how many items are in the list. This is the foundation for paginated interfaces, where users click through pages of results. The *Show next*, *Show previous*, and *Go to page* actions let you build controls that navigate between pages.

#### **Building grids**

The *Columns* setting controls how many cells sit side by side in each row. Combining a multi-column layout with *Fit to content* is a common pattern for grid-based designs, like a product catalog or an image gallery. Combining columns with *Fill container* creates a grid that fills the available space and scrolls internally.

#### **Horizontal scrolling**

Setting *Scroll direction* to *Horizontal* turns the repeating group into a row that scrolls sideways. This works well for carousels, thumbnail rows, and featured content sections. Horizontal repeating groups typically use a single row with multiple columns, and their width is limited by the parent container.

#### **Masonry layouts**

Enabling *Display items as masonry grid* creates a packed grid where cells have varying heights and fit together automatically. This is ideal for image galleries, blog previews, or any layout where content naturally comes in different sizes. Masonry layouts require the responsive engine and work only with vertical scrolling.

#### **Mixing and matching**

Most real-world designs come from combining these settings in specific ways:

* A social feed: *Rows* set to *Fit to content*, one column, vertical scrolling.
* A product grid: *Rows* set to *Fit to content*, multiple columns, vertical scrolling.
* A dashboard sidebar list: *Rows* set to *Fill container*, one column, vertical scrolling.
* A featured content carousel: *Rows* set to a fixed number, multiple columns, horizontal scrolling.
* A photo gallery: masonry grid enabled, multiple columns, vertical scrolling.

Experimenting with these combinations is the fastest way to get a feel for what each setting does.

### FAQ: Repeating groups

<details>

<summary>How many items can a repeating group display?</summary>

There's no strict limit, but very large lists can affect performance. For long lists, use extended vertical scrolling to load items progressively, or apply constraints to the search to limit how much data is returned.

</details>

<details>

<summary>Can I sort or filter the items in a repeating group?</summary>

Yes. When using *Do a search for* as the data source, you can add constraints and sorting rules directly in the search. You can also chain operators like `:sorted by` or `:filtered` to control the order and contents of the list.

</details>

<details>

<summary>What's the difference between a repeating group and a table?</summary>

Both display lists, but tables are designed for structured, column-based data, while repeating groups offer more layout flexibility. Repeating groups are better suited to cards, feeds, and mixed-content lists.

</details>

<details>

<summary>Can I nest a repeating group inside another repeating group?</summary>

Yes, but it comes with a performance cost. Each parent cell runs its own data query for the nested repeating group, which can consume significant workload even with small lists. Use nesting carefully.

</details>

<details>

<summary>How do I reference the item in a cell?</summary>

Use the *Current cell* expression. For example, if the *Type of content* is *User*, `Current cell's User's name` references the name of the user in that specific cell.

</details>

<details>

<summary>Can I make the cells clickable?</summary>

Yes. Add a workflow triggered by a click on any element inside the cell.

</details>

<details>

<summary>What happens if the list is empty?</summary>

The repeating group renders no cells and takes up no space. You can use conditions to show a placeholder message or graphic when the list is empty.

</details>

<details>

<summary>Can I display a list without loading it from the database?</summary>

Yes. The data source can be any expression that returns a list, such as a list stored in a [custom state](/help-guides/data/temporary-data/custom-states), a list returned by an [API call](/help-guides/integrations/api/the-api-connector), or options from an [option set](/help-guides/data/static-data/option-sets).

</details>

<details>

<summary>Can I paginate a repeating group?</summary>

Yes. Set *Rows* to *Fixed number* to show a set number of items at a time, and use the *Show next*, *Show previous*, and *Go to page* actions to control navigation between pages.

</details>

<details>

<summary>How do I find the position of an item in the list?</summary>

Use `Current cell's index` to reference the position of the item within the repeating group. Bubble uses 1-indexing, so the first cell is index 1.

To find the index of a specific record (as opposed to a specific cell), you can use the :`index of` operator.

</details>

<details>

<summary>Can I update the list without reloading the page?</summary>

Yes. Use the *Display list in a repeating group* action to load a new list based on user actions, or update the data source dynamically through workflows. The list refreshes automatically without a page reload.

</details>

<details>

<summary>Are repeating groups available in native mobile apps?</summary>

No. Repeating groups are web-only. For native mobile apps, use other alternative list elements.

</details>

## Other ways to learn

<details>

<summary><mark style="color:blue;">Core reference:</mark> Repeating group properties</summary>

In the core reference section, you'll find all the settings associated with repeating groups.

Reference: [Repeating groups](/core-resources/elements/containers#repeating-group)

</details>

<details>

<summary><mark style="color:blue;">Video lessons and interactive demos</mark></summary>

We have multiple video lessons that cover different uses of Repeating Groups:

* [Creating your first Repeating Group](https://www.youtube.com/watch?v=e6oQU__8pmE)
* [How to create a full list Repeating Group](https://www.youtube.com/watch?v=X6aaew_twCA\&t=1s)
* [Using Repeating Group layout styles](https://www.youtube.com/watch?v=abMbztw-lmc)
* Horizontal and vertical scrolling Repeating Groups
  * [Vertical scrolling](https://www.youtube.com/watch?v=vcWUgBEQPCk) ↑↓
  * [Horizontal scrolling](https://www.youtube.com/watch?v=J6mvea8B1Wk) ←→
* [How to create a fixed Repeating Group](https://www.youtube.com/watch?v=h2EVLsL_inU)
* How to create a masonry grid layout using Repeating Groups
  * [Youtube video](https://www.youtube.com/watch?v=-asG45y04aI)
  * [Interactive demo](https://www.youtube.com/watch?v=-asG45y04aI)
* Searching for data
  * [How to set the type of data to search for](https://www.youtube.com/watch?v=MJPNaa1FvDE)
  * [How to use search constraints](https://www.youtube.com/watch?v=gOjGDCJrXYI)

</details>

[^1]: The **Type of content** property tells Bubble what kind of data the repeating group will hold, such as `User` or `Post`. This determines what data source it accepts and what child elements can reference through `Current cell`.

[^2]: A **data source** is any source your app pulls data from, such as a database search, a specific record, an option set, or an external API.

    **Core reference:** [Data sources](/core-resources/data/data-sources)

[^3]: **Dynamic expressions** are how you reference and manipulate data in Bubble. They let you pull values from data sources, apply operators, and combine data in different ways to produce a final result, such as `Current User's name` or `Do a search for Posts:count`.

    **Article:** [Dynamic expressions](/help-guides/logic/dynamic-expressions)


# Table element (web)

This section covers the table element, used to display lists of things such as records from the database in a table-like structure of rows and columns

{% hint style="warning" %}
The Table element requires:

* that you are using [Bubble version 21+](#user-content-fn-1)[^1]
* that the page is set to use the [responsive engine](#user-content-fn-2)[^2]
  {% endhint %}

{% hint style="info" %}
To show a list of things, you can also use the repeating group element. You can read more about the repeating group element and how it's different from a table element in the article below:

Article: [Repeating groups](/help-guides/design/elements/web-app/containers/repeating-groups)\
Article section: [The difference between repeating groups and tables](#the-difference-between-repeating-groups-and-tables)
{% endhint %}

The table element is a container[^3] used to display a list of items in a structured, column-based layout. Like a [repeating group](/help-guides/design/elements/web-app/containers/repeating-groups), it repeats its content once for each entry in a list. Unlike a repeating group, it's designed specifically for tabular data, with rows, columns, and features like [sticky headers](#repeating-rows) built in.

<figure><img src="/files/oZgcemgt41CAw2fzOTci" alt="Table element displaying a list of users."><figcaption><p>Using the table element, you can set up lists in a structured, column-like layout.</p></figcaption></figure>

Tables are especially useful for interfaces where data is naturally organized in columns, such as user lists, product catalogs, financial reports, or admin dashboards.

## When to use a table vs. a repeating group

Tables and [repeating groups](/help-guides/design/elements/web-app/containers/repeating-groups) share a lot of behavior. Both are containers that display a list of items by repeating their content for each entry. The difference comes down to how the content is arranged.

**Use a table when:**

* The data has a clear column structure.
* You want column headers that stay in place as users scroll.
* You need to keep row content aligned across columns automatically.
* The layout is closer to a spreadsheet or data grid than a card feed.

**Use a repeating group when:**

* The design is flexible and doesn't fit neatly into rows and columns.
* Cards, masonry grids, or media-rich lists suit your content better.
* You want more freedom over how each cell is laid out.

## Static and repeating rows

Tables are made up of two kinds of rows:

### Static rows

Static rows aren't tied to the data source. Elements placed inside them appear exactly once. Static rows are typically used for column headers, but they can also hold summary rows, filters, or any content that shouldn't repeat with the list.&#x20;

<figure><img src="/files/JZqie3S25Th6zZhagzG4" alt="The static row of a table element."><figcaption><p>Static rows are useful for non-dynamic content, such as column headers.</p></figcaption></figure>

You can [add multiple static rows](#add-columns-and-rows) if needed.

### Repeating rows

Repeating rows are where the list data lives. They work the same way as a repeating group cell: the content of the first repeating row is duplicated once for each entry in the data source. You only ever edit the top repeating row, and the rest follow along automatically.

<figure><img src="/files/fc3qEZH1geaPiD7pttNn" alt="The repeating row of a table element."><figcaption><p>The repeating row is displayed in the editor with a ∞ symbol. Its content is duplicated for each entry in the table's data source.</p></figcaption></figure>

This split is what makes tables so well suited to structured data. Column headers stay put, and the data below them updates automatically as the list changes.

<figure><img src="/files/wKpU4wMnOC8GDkx9XWst" alt="The element property editor highlighting the &#x22;make sticky&#x22; feature."><figcaption><p>With the <em>Make sticky</em> property, the table's column headers stay in view while the user scrolls.</p></figcaption></figure>

Static rows can also be made sticky, so they stay in view as users scroll through long lists. This is what enables the classic "sticky header" pattern common in data grids and dashboards.

## Building a table

When you draw a table on the page, Bubble creates one with three columns and four rows by default, with the top row set as static. From there, you build the table in a few steps.

### **Load the data**

{% hint style="info" %}
Tables require a **list** as their data source, unlike most other containers, which work with a single item.
{% endhint %}

#### **Set the data source directly**

Click the table itself, either by clicking the icon in its top-left corner or selecting it in the element tree. In the property editor, set:

* [**Type of content**](#user-content-fn-4)[^4]**:** the data type the table will hold, such as `User` or `Post`.
* [***Data source***](#user-content-fn-5)[^5]***:*** the expression[^6] that returns the list, such as `Do a search for Users`.

<figure><img src="/files/vAtoPRR8CROkSQaasDn0" alt="The data source of a table element in the property editor."><figcaption><p>The <a href="/pages/-MShSvpeDGq5TEeTupJf#do-a-search-for"><em>Do a search for</em></a> data source populates the table with a list of users.</p></figcaption></figure>

Once the data source is set, the repeating rows below the static row will populate with the list.

#### Push data with a workflow

Use the [*Display list in a repeating group or table*](/core-resources/bubble-workflows/bubble-actions/element-actions#display-list-in-a-repeating-group-or-table) action to load a list in response to a user action, such as clicking a filter or performing a search. This is useful when the data depends on something the user does.

<figure><img src="/files/Wn5JL2krVXSaC4CDncee" alt=""><figcaption></figcaption></figure>

### **Add content to the rows**

Click into the cells of the static row to add column headers, usually as text elements. Then click into the first repeating row to add the content that will appear in each row of data. Reference the list item with `Current cell's [Type]'s [field]`, such as `Current cell's User's name`.

### **Add columns and rows**

You can add or remove columns and rows as your design evolves:

* **Add a column** by right-clicking a column and selecting *Add column to left* or *Add column to right*, or by hovering between two columns and clicking the plus icon that appears.
* **Add a static row** by right-clicking an existing row and selecting *Add row above* or *Add row below*.
* **Remove a column** by right-clicking it and selecting *Delete*, or by clicking the X above its column handle.
* **Remove a row** by right-clicking it and selecting *Delete*.

You can't delete the last remaining column, since a table must have at least one.

Repeating rows work differently: they aren't added individually. Instead, the number of rows is controlled by the data. If *Set fixed number of repeating rows* is checked, you specify the row count directly. If it isn't, Bubble displays as many rows as there are items in the data source.

## Working with rows, columns, and cells

The table element behaves a little differently from other containers, since rows, columns, and cells are all selectable pseudo-elements with their own properties. You can select elements in the element tree, or by using the methods below.

### The whole table

Click the icon in the top-left corner of the table to select the entire element. From here, you can set the *Type of content*, *Data source*, styling, layout, and conditions for the table as a whole. The element tree also lists the table with all its rows and columns as children.

<figure><img src="/files/vrux3OcttnRYjRqlvPOq" alt="Selecting a table element by clicking its handle."><figcaption><p>Clicking the handle in the top-left corner selects the entire table and opens its properties in the property editor.</p></figcaption></figure>

### Rows

Click a row's numbered handle to select it and manage its [properties](/core-resources/bubble-elements/element-properties/web-element-properties/container-properties/table-element/table-row-column). For repeating rows, changes made to the top row apply to all rows below.

<figure><img src="/files/Hb0BK2u4HzTIfLV1MkYD" alt=""><figcaption><p>Clicking a row's numbered handle selects it and opens its properties in the property editor.</p></figcaption></figure>

### Columns

Click a column's lettered handle to select it and manage its [properties](/core-resources/bubble-elements/element-properties/web-element-properties/container-properties/table-element/table-row-column). Column changes affect the entire column across both static and repeating rows.

<figure><img src="/files/pzhX3AZfuKe4oisnHEN4" alt="Selecting a column in a table."><figcaption><p>Clicking a column's numbered handle selects it and opens its properties in the property editor.</p></figcaption></figure>

### Cells

Click directly on a cell to select it. Each cell has its own [properties](/core-resources/bubble-elements/element-properties/web-element-properties/container-properties/table-element/table-row-column/table-row-column-cell). Cells in static rows are edited individually, while cells in repeating rows are edited on the first row and repeat for all rows below.

<figure><img src="/files/2awO5KeVxmhVRdLbi2W9" alt="Selecting a cell in a table."><figcaption><p>Clicking an individual cell selects it and opens its properties in the property editor.</p></figcaption></figure>

### **Selecting multiple rows or columns**

Hold Shift while clicking to select multiple rows or columns at once, then apply properties to all of them at the same time. Note that not all properties can be changed in bulk.

## Orientation

By default, tables are oriented vertically, with rows stacked top-to-bottom. Setting the orientation to horizontal transposes rows and columns, so rows run left-to-right and columns stack vertically. This can be useful for comparison tables or layouts where categories run horizontally instead of vertically.

<figure><img src="/files/7zkhHFDiMCbfFbsfkyHq" alt="Table element with horizontal orientation."><figcaption><p>Setting the orientation to horizontal transposes rows and columns.</p></figcaption></figure>

## Responsive behavior

Tables handle responsive design a little differently from other containers. It helps to think of the table itself, and each of its rows and columns, as separate containers with their own responsive settings.

**Table size** is set on the table element as a whole. This defines the overall width and height, and constrains the size of the rows and columns unless the table is set to fit its content.

**Column width** is set at the column level. Changing a column's width affects every row in that column, both static and repeating.

**Row height** is set at the row level. For repeating rows, the height is controlled by the properties on the top repeating row.

**Cell layout** works like any other container. Each cell can use column, row, align, or fixed layout to control its child elements. Static row cells are edited individually, while repeating row cells are controlled by the top row.

## FAQ: Table element

<details>

<summary>Can I sort or filter the items in a table?</summary>

Yes. When using Do a search for as the data source, you can add constraints and sorting rules directly in the search, or chain operators like :sorted by and :filtered to control the order and contents of the list.

</details>

<details>

<summary>Can I have multiple static rows?</summary>

Yes. You can add multiple static rows.&#x20;

</details>

<details>

<summary>Can static rows be sticky when scrolling?</summary>

Yes. Static rows can be [set to remain in view](#static-rows) as users scroll through the list below, which is useful for keeping column headers visible in long tables.

</details>

<details>

<summary>How do I reference the item in a row?</summary>

Use the Current cell expression, just like in a repeating group. If the Type of content is Post, `Current cell's Post's title` references the title of the post in that row.

</details>

<details>

<summary>Can I make rows or cells clickable?</summary>

A static or repeated row can be set to clickable by using the [*An element is clicked*](/core-resources/bubble-workflows/bubble-events/frontend-event-properties/element-event-properties#an-element-is-clicked) action.

Columns and cells aren't clickable directly, but you can add a workflow triggered by a click on any child element. Reference `Current cell's [Type]` inside the workflow to act on that specific item.

</details>

<details>

<summary>How do I paginate a table?</summary>

Set the number of rows to *Fixed* and combine it with actions like [Show next page](/core-resources/bubble-workflows/bubble-actions/element-actions#show-next-page-of-a-repeating-group-or-table), [Show previous page](/core-resources/bubble-workflows/bubble-actions/element-actions#show-previous-page-of-a-repeating-group-or-table), and [Go to page](/core-resources/bubble-workflows/bubble-actions/element-actions#go-to-page-of-a-repeating-group-or-table) on the table.

</details>

<details>

<summary>What happens if the data source is empty?</summary>

The repeating rows disappear, leaving only the static rows visible. You can add a conditional to display a placeholder message in the table when the list is empty.

</details>

<details>

<summary>Can I nest tables inside other tables?</summary>

It's possible but not recommended. Nested lists come with a performance cost, since each parent row runs its own query for the nested content.

</details>

<details>

<summary>Are tables available in native mobile apps?</summary>

No. The table element is web-only.&#x20;

</details>

<details>

<summary>Can I export the data in a table?</summary>

The table itself doesn't include built-in export functionality, but the Download data as CSV action can export the data behind it. Use this to give users an export button that pulls from the same data source as the table.

</details>

## Other ways to learn

<details>

<summary><mark style="color:blue;">Core reference:</mark> Table element properties</summary>

In the core reference section, you'll find all the settings associated with table elements.

Reference: [Table elements](/core-resources/elements/containers#table-element)

</details>

[^1]: To ensure compatibility with existing apps, some updates from Bubble require manual enabling. Updates are applied to individual apps.

    \
    You can apply updates in *Settings - Versions*.

[^2]: Bubble updated its editor and page rendering engine to better support responsive design.\
    \
    You can read more about this in the article series below:\
    \
    Article series: [Responsive design](/help-guides/design/responsive-design)

[^3]: Containers are used to contain elements and control how they behave on the page.\
    \
    Article series: [Containers](/help-guides/design/elements/web-app/containers)

[^4]: The **Type of content** property tells Bubble what kind of data the repeating group will hold, such as `User` or `Post`. This determines what data source it accepts and what child elements can reference through `Current cell`.

[^5]: A **data source** is any source your app pulls data from, such as a database search, a specific record, an option set, or an external API.

    **Core reference:** [Data sources](/core-resources/data/data-sources)

[^6]: **Dynamic expressions** are how you reference and manipulate data in Bubble. They let you pull values from data sources, apply operators, and combine data in different ways to produce a final result, such as `Current User's name` or `Do a search for Posts:count`.

    **Article:** [Dynamic expressions](/help-guides/logic/dynamic-expressions)


# Popup element (web)

This section covers the container type Popup, which is a group that hovers above all other elements on the screen

{% embed url="<https://www.youtube.com/watch?v=YXxYeHlowWk>" %}

A popup is a container that appears above the rest of the page and is centered on the screen, regardless of how far the user has scrolled. Popups can dim or blur the content behind them, which makes them well suited to grabbing the user's attention with a message, form, or focused action.

Common uses include:

* Error messages and alerts
* Registration and login forms
* Contact and feedback forms
* Newsletter signup prompts
* Promotional offers and discounts
* Confirmation dialogs before destructive actions

<figure><img src="/files/YMuEnoQ1xIWxJ6IxTNlW" alt=""><figcaption><p>Popups can be used to bring attention to your users immediately.</p></figcaption></figure>

## How popups behave

Popups sit in a special layer above the rest of the page. They're always centered horizontally and stay in place as the user scrolls, giving them a consistent, focused appearance regardless of the page's state.

By default, popups dim the page behind them, drawing attention to the popup itself. You can adjust this behavior through the popup's properties, including changing the color of the overlay, adjusting its opacity, or applying a blur effect. Users can typically dismiss a popup by clicking outside of it, though this behavior can be turned off if the popup requires an explicit action to close.

Because popups are their own container, they can hold any elements you'd place in a regular group, including forms, images, buttons, and even other containers.

## Loading data into a popup

Popups can hold a [data source](#user-content-fn-1)[^1], just like a regular group. This makes it easy to open a popup with a specific piece of data loaded, such as an "edit user" popup that receives the user being edited.

There are two common patterns:

### Set the data source directly

&#x20;The popup loads its data as soon as it's shown, based on an expression[^2] like `Current User`.

<figure><img src="/files/WM1YCAGHm7qoX5cexgup" alt="Loading data into a popup by settings its data source."><figcaption><p>Setting the data source on a popup makes it load its data as soon as the popup is shown.</p></figcaption></figure>

### Push data with a workflow

Push data with a workflo&#x77;**.** Use the [*Display data in a group or popup*](/core-resources/bubble-workflows/bubble-actions/element-actions#display-data-in-a-group-popup) action to load a specific item into the popup at the moment it opens. This is the more flexible pattern, since it lets the popup receive different data based on which button was clicked.

Once the data is loaded, child elements inside the popup can reference it with expressions like `Parent group's User's name`.

<figure><img src="/files/WqJrZWwwFZHg4yKPH3dx" alt=""><figcaption><p>Using an action you can load a specific item into the popup when it opens.</p></figcaption></figure>

## Resetting data in a popup

To clear the data in a popup, you can use the [*Reset a group/popup*](/core-resources/bubble-workflows/bubble-actions/element-actions#reset-a-group-popup) action. Note that this resets the data back to its original [data source](#set-the-data-source-directly). If the data source is empty, it will reset to an empty value. If the data source is not empty, it will reset to the original item.

{% hint style="info" %}
The popup automatically fades when hidden, and the animation takes 500 ms. If you reset the data and then hide the popup, you may see the content change during the fade-out. To avoid this, [schedule a custom event](/core-resources/bubble-workflows/bubble-actions/custom-actions#schedule-a-custom-event) with a 500 ms delay that [*resets the popup's content*](/core-resources/bubble-workflows/bubble-actions/element-actions#reset-a-group-popup), so the reset happens after the animation completes.
{% endhint %}

## Showing and hiding popups

Popups are never visible on page load and can't be shown using conditions. Instead, they're controlled by workflow actions:

* [**Show an element**](/core-resources/bubble-workflows/bubble-actions/element-actions#show-an-element) displays the popup with a subtle fade-in animation.
* [**Hide an element**](/core-resources/bubble-workflows/bubble-actions/element-actions#hide-an-element) closes the popup with a matching fade-out.
* [**Animate an element**](/core-resources/bubble-workflows/bubble-actions/element-actions#animate-an-element) displays or hides the popup with a specific animation, such as sliding in from the side or scaling up from the center.

For popups that need to open with specific data, use *Display data in a group* just before *Show an element* in the same workflow, so the data is ready before the popup appears.

## Popup events

Adding a popup to a page gives you two extra events you can use to trigger workflows:

* [**A popup is opened**](/core-resources/events/element-events#a-popup-is-opened) runs each time the popup is shown, whether through *Show an element* or *Animate an element*.
* [**A popup is closed**](/core-resources/events/element-events#a-popup-is-closed) runs each time the popup is hidden[^3].

Both events trigger every time the popup opens or closes, not just the first time. This makes them useful for setting up or cleaning up state around the popup.

Common uses include:

* Resetting input fields after the popup closes, so they're empty the next time it opens.
* Preloading data when the popup opens.
* Logging analytics events tied to popup interactions.

## Using multiple popups

You can have as many popups on a page as you need. Only one is typically visible at a time, though. Showing a second popup while the first is open can lead to confusing overlaps, so it's usually better to close the first popup before opening the next one.

## Styling popups

Popups can be styled like any other container. The overlay behind the popup can also be customized, giving you control over how much of the page is visible when the popup is open.

Using shared styles or style variables keeps popups consistent with the rest of your app's design.

**Article series**: [Styles](/help-guides/design/variables-and-styles/styles)

## Popups and security

Popups are a visual layer, not a security boundary. The elements behind a popup are still fully present in the page's client-side code, and a curious user can inspect the page to see them. Dimming or blurring the background is a UI effect only.

Never rely on a popup to hide sensitive information. Use [privacy rules](/help-guides/data/the-database/protecting-data-with-privacy-rules) in the database and server-side conditions to control what data actually reaches the client.

### Using popups as login/signup screens

A common pattern is to use a popup as a login/signup screen that appears if the current user is logged out. This approach should be used with care:

* Make sure all database data is protected by [privacy rules](/help-guides/data/the-database/protecting-data-with-privacy-rules)
* Assume that the user can hide the popup and see what's underneath, even if you use a background color and/or blur.
* Assume that if the user logs out, data they had access to before logging out is still loaded and visible
* All sensitive events and actions should be protected with [server-side conditions](/help-guides/security/page-security#conditions)

Generally, we recommend using dedicated pages for login and signup, and redirecting logged-out users to those pages. That said, always assume a user can reach any page, regardless of your setup. This is why privacy rules and server-side conditions matter no matter which method you choose.

## FAQ: Popups

<details>

<summary>Can popups be shown on page load?</summary>

Yes. Trigger the Show an element action from a [*Page is loaded*](/core-resources/bubble-workflows/bubble-events/frontend-event-properties/general-event-properties#page-is-loaded) event, or use conditions inside a workflow to decide whether the popup should open based on the state of the page or the user.

</details>

<details>

<summary>Can popups be closed by clicking outside of them?</summary>

Yes. This is the default behavior, but it can be disabled in the popup's properties for popups that require an explicit action to close. Note that this is a UI setting only, not a security measure.

</details>

<details>

<summary>Can I have multiple popups open at the same time?</summary>

Technically yes, but it can lead to confusing layouts as the popups overlap. It's typically better to close one popup before opening the next.

</details>

<details>

<summary>How do I pass data to a popup when I open it?</summary>

Use the [Display data in a group/popup](/core-resources/bubble-workflows/bubble-actions/element-actions#display-data-in-a-group-popup) action just before [Show an element](/core-resources/bubble-workflows/bubble-actions/element-actions#show-an-element) in the same workflow. This loads the data into the popup before it appears, so the elements inside can reference it right away.

</details>

<details>

<summary>How do I reset a popup's inputs when it closes?</summary>

Use the [A popup is closed](/core-resources/events/element-events#a-popup-is-closed) event to run [Reset relevant inputs](/core-resources/bubble-workflows/bubble-actions/element-actions#reset-relevant-inputs) or [Reset a group/popup](/core-resources/bubble-workflows/bubble-actions/element-actions#reset-a-group-popup), which clears the input values used inside the popup. This way, the popup opens fresh the next time it's shown.

</details>

<details>

<summary>Can I nest containers inside a popup?</summary>

Yes. Popups can hold any elements, including regular groups and other containers.

</details>

<details>

<summary>Can I make the background transparent or remove the overlay?</summary>

Yes. The overlay behind the popup can be styled or made fully transparent, letting you show a floating popup without dimming the page behind it.

</details>

<details>

<summary>Can I open different popups from the same button?</summary>

Yes. Use conditions on the workflow to decide which popup to show based on the current context, or run multiple show actions with their own conditions.

</details>

<details>

<summary>Are popups available in native mobile apps?</summary>

No. Popups are web-only. For native mobile apps, use [sheets](/core-resources/bubble-elements/element-properties/native-mobile-element-properties/container-properties-mobile/sheet-element-mobile) instead.

</details>

<details>

<summary>Can I animate the popup as it opens and closes?</summary>

Yes. Use the Animate an element action to apply a specific animation, such as sliding, scaling, or fading, when the popup appears or disappears.

Note that popups fade in and out over 500 ms by default.

</details>

<details>

<summary>Can I show an element using conditions, or on page load?</summary>

Popups don't have a visibility property like many other groups have. They need to be displayed with an action. To show a popup as soon as the page loads, you can use the [*Page is loaded*](/core-resources/bubble-workflows/bubble-events/frontend-event-properties/general-event-properties#page-is-loaded) event.

</details>

## Other ways to learn

<details>

<summary><mark style="color:blue;">Core reference:</mark> Popup properties</summary>

In the core reference section, you'll find all the properties associated with popups.

**Core reference**: [Popups](/core-resources/elements/containers#popup)

</details>

<details>

<summary><mark style="color:blue;">Video lessons</mark></summary>

* [How to use the popup element](https://www.youtube.com/watch?v=YXxYeHlowWk)
* [How to trigger workflows from popup events](https://www.youtube.com/watch?v=AhiJv3jYZEg)

</details>

[^1]: A **data source** is any source your app pulls data from, such as a database search, a specific record, an option set, or an external API.

    **Core reference:** [Data sources](/core-resources/data/data-sources)

[^2]: **Dynamic expressions** are how you reference and manipulate data in Bubble. They let you pull values from data sources, apply operators, and combine data in different ways to produce a final result, such as `Current User's name` or `Do a search for Posts:count`.

    **Article:** [Dynamic expressions](/help-guides/logic/dynamic-expressions)

[^3]: **Note:** this event triggers as soon as the popup's hide animation starts. The animation has a duration of 500 ms.


# Floating group element (web)

This section covers the floating group, a container type used to attach a group to one of the sides of the screen so it stays in place regardless of scrolling position.

A floating group is a container that hovers above the rest of the page. It can be attached to any side of the screen and stays in place regardless of how far the user scrolls. Because they're independent of the page's normal flow, floating groups are the go-to element for headers, sidebars, and other UI that needs to stay visible at all times.

#### Common uses

Floating groups are used for anything that needs to remain accessible as the user moves through a page:

* **Navigation menus** that stay at the top of the screen while users scroll.
* **Sidebars** that provide persistent navigation or context.
* **Chat widgets** anchored to a corner of the screen.
* **Shopping cart summaries** that follow the user across the site.
* **Back-to-top buttons** that appear once the user has scrolled down.
* **Cookie consent banners** at the bottom of the page.
* **Notifications and toasts** that appear briefly and dismiss themselves.

<figure><img src="/files/lie8GPQtiftoUKiMKBqL" alt=""><figcaption><p>A typical use case for floating groups is to set up a navigation header that sticks to the top of the screen.</p></figcaption></figure>

## How floating groups behave

Floating groups sit in their own layer above the rest of the page. Because of this, they don't affect the layout of the surrounding content. Regular elements don't push around floating groups or make room for them, and floating groups don't take up space in the normal flow.

This is what allows a floating header to stay pinned to the top of the viewport while the user scrolls, or a sidebar to stay fixed to the left of the screen no matter how tall the page becomes.

Floating groups can hold any elements a regular group can, including text, images, buttons, dropdowns, and other containers.

### Attaching to the screen

Every floating group is attached to the screen along a horizontal and vertical axis. Together, these two settings determine where the group appears and how it stays positioned as the user scrolls.

The horizontal axis controls whether the group sticks to the left, right, or spans both sides. The vertical axis controls whether it sticks to the top, bottom, or spans both. A group that spans both edges stretches to fill the screen along that axis.

<figure><img src="/files/mVcpo9QqOKBEwwKSf6YE" alt="Float relative to properties on a floating group."><figcaption><p>The <em>[...] float relative too</em> properties controls whether the group sticks to the left, right, or spans both sides.</p></figcaption></figure>

Margins let you offset the group from the edge it's attached to. For example, a sidebar might be attached to the left edge with a 20-pixel top margin so it sits slightly below the top of the screen.

### Layering with z-index

The z-index[^1] controls whether a floating group sits above or below other elements. By default, floating groups sit above the page content, which is what makes them float visibly.

You can also set a floating group to sit beneath the page. This is occasionally useful for background layers or design effects, but it comes with a catch: if the page has its own background style, that background can hide the floating group entirely.

<figure><img src="/files/cJGmTAcaY151RcEhYUcE" alt="Z-index property of a floating group."><figcaption><p>The z-index controls whether the gloating group appears above or beneath the page.</p></figcaption></figure>

Unless you're intentionally using a floating group as a background layer, leave the z-index setting on its default so the group appears above the page.

## Loading data into a floating group

Floating groups can hold a [*data source*](#user-content-fn-2)[^2], just like regular groups.

### Set the data source directly

Set the [*Type of content*](#user-content-fn-3)[^3] and *Data source* on the group, and its child elements can reference the data using `Parent group's` expressions[^4].

<figure><img src="/files/Tu1j5t5f8oQdJ8aCuovk" alt="Data source on a floating group"><figcaption><p>Setting the data source on a floating group makes it load its data as soon as the group is shown.</p></figcaption></figure>

This is useful when a floating group needs to display data about the current context, such as a header showing the current user's name, or a cart summary showing the current order.

### Push data with a workflow

Data can also be loaded dynamically by using the [*Display data in a group/popup*](/core-resources/bubble-workflows/bubble-actions/element-actions#display-data-in-a-group-popup) action. This is useful when you need to load data into the floating group as the result of a user action.

<figure><img src="/files/aS0YPbM8Tj6cVTne48Ab" alt="Loading data into a floating group using an action."><figcaption><p>Using an action you can load a specific item into the floating group as the result of a user action.</p></figcaption></figure>

## Showing and hiding floating groups

Floating groups can be shown, hidden, and animated in the same way as regular groups. Use the [*Show an element*](/core-resources/bubble-workflows/bubble-actions/element-actions#show-an-element), [*Hide an element*](/core-resources/bubble-workflows/bubble-actions/element-actions#hide-an-element), [*Toggle an element*](/core-resources/bubble-workflows/bubble-actions/element-actions#toggle-an-element), or [*Animate an element*](/core-resources/bubble-workflows/bubble-actions/element-actions#animate-an-element) actions to control visibility.

Some common patterns:

* A back-to-top button that appears only once the user has scrolled past a certain point.
* A notification banner that slides in from the top and disappears after a few seconds.
* A sidebar that opens and closes based on a button click.

## Styling floating groups

Floating groups can be styled like any other container. They support background colors, borders, border radius, shadows, and opacity. Because they sit above the page, they often benefit from a subtle shadow or border to visually separate them from the content behind them.

Using shared styles or style variables keeps floating groups consistent with the rest of your app's design.

**Article series:** [Styles](/help-guides/design/variables-and-styles/styles)

## Responsive behavior

Floating groups follow the same layout system as other containers, so they support column, row, align, and fixed layout modes. Combined with their ability to span or attach to edges, this gives you a lot of flexibility for responsive design.

A common pattern is a floating header that stays attached to the top of the screen and uses a row layout inside, with elements like a logo, navigation links, and a login button arranged horizontally. On smaller screens, the same header can shift to a column layout or collapse into a menu.

## Frequently asked questions

<details>

<summary>Can a floating group be attached to more than one side of the screen?</summary>

Yes. Setting the group to span both edges on a given axis stretches it to fill the screen along that direction. This is useful for full-width headers, full-height sidebars, or footer bars.

</details>

<details>

<summary>Do floating groups affect the layout of the rest of the page?</summary>

No. Floating groups sit in their own layer above the page and don't take up space in the normal flow. Other elements aren't pushed around by them.

</details>

<details>

<summary>Can I have more than one floating group on the same page?</summary>

Yes. You can add as many floating groups as you need, and each can be attached to a different part of the screen. A common example is a page with both a floating header and a floating sidebar.

</details>

<details>

<summary>Can I load data into a floating group?</summary>

Yes. Set the [*Type of content and Data source on the group*](#set-the-data-source-directly), and its child elements can reference the data using Parent group's expressions. You can also load data into the floating group using an [action](#push-data-with-a-workflow).

</details>

<details>

<summary>Can floating groups be shown and hidden?</summary>

Yes. Use the [*Show an element*](/core-resources/bubble-workflows/bubble-actions/element-actions#show-an-element), [*Hide an element*](/core-resources/bubble-workflows/bubble-actions/element-actions#hide-an-element), [*Toggle an element*](/core-resources/bubble-workflows/bubble-actions/element-actions#toggle-an-element), or [*Animate an element*](/core-resources/bubble-workflows/bubble-actions/element-actions#animate-an-element) actions, just like with a regular group.

</details>

<details>

<summary>Can I animate a floating group as it appears?</summary>

Yes. Use the [*Animate an element*](/core-resources/bubble-workflows/bubble-actions/element-actions#animate-an-element) action to slide, fade, or scale the group in and out.

</details>

<details>

<summary>Why is my floating group hidden behind the page?</summary>

The *Floating z-index* property is likely set to *Beneath the page*. Change it to sit above the page in the group's properties.

</details>

<details>

<summary>How do I make a floating group appear only after the user scrolls?</summary>

Set the group to invisible on page load, then use a workflow triggered by the Current page scrolling position to show it once the user has scrolled past a certain point.

You can also use a conditional on the floating group to show and hide it based on the data source [*Current page scrolling position*](/core-resources/data/data-sources#current-page-scrolling-position).

</details>

<details>

<summary>Are floating groups available in native mobile apps?</summary>

Yes, floating groups are available in native mobile apps too. Note that the two platforms are completely separate, and the properties and behavior of the floating group can vary.

</details>

<details>

<summary>Can I nest containers inside a floating group?</summary>

Yes. Floating groups can hold any elements, including other containers, which makes them useful for building headers, sidebars, and menus with layered layouts.

</details>

<details>

<summary>Can I collapse the width and/or height of a floating group</summary>

You can't collapse the width or hight directly, but you can place a group inside the floating group and collapse it instead. If they share the same dimensions, the floating group will collapse along with the group.

</details>

## Other ways to learn

<details>

<summary><mark style="color:blue;">Core reference:</mark> Floating group properties</summary>

In the core reference section, you'll find all the properties associated with floating groups.

**Core reference:** [Floating groups](/core-resources/elements/containers#floating-group)

</details>

<details>

<summary><mark style="color:blue;">Video lessons</mark></summary>

We have multiple video lessons on how to set up and use Floating groups:

* [How to use Floating Groups](https://www.youtube.com/watch?v=lvuovDbPsHg)
* [How to build a sidebar layout](https://www.youtube.com/watch?v=xPr2C-CcW_k)
* [How to build a responsive navigation bar](https://www.youtube.com/watch?v=3lUlmTZ_IQo)

</details>

[^1]: The z-index controls the stacking order of overlapping elements. Higher values sit in front of lower ones.

    Z-index come from the depth dimension of 3D space: x, y, **z.**

[^2]: A **data source** is any source your app pulls data from, such as a database search, a specific record, an option set, or an external API.

    **Core reference:** [Data sources](/core-resources/data/data-sources)

[^3]: The **Type of content** property tells Bubble what kind of data the container will hold, such as `User` or `Post`. This determines what data source it accepts and what child elements can reference through `Current cell`.

[^4]: **Dynamic expressions** are how you reference and manipulate data in Bubble. They let you pull values from data sources, apply operators, and combine data in different ways to produce a final result, such as `Current User's name` or `Do a search for Posts:count`.

    **Article:** [Dynamic expressions](/help-guides/logic/dynamic-expressions)


# Group focus element (web)

This section covers the group type Group Focus. This group will remain visible for as long as it is in focus, typically used for dropdown menus

{% embed url="<https://www.youtube.com/watch?v=l_SbovrRt2s>" %}

A group focus is a container that appears next to another element and stays visible only while it's in focus. As soon as the user clicks anywhere outside of it, the group focus automatically hides itself, no workflow needed.

Because of this behavior, group focus elements are ideal for lightweight, temporary UI that appears in response to a specific interaction and disappears again once the user's attention moves elsewhere.

<figure><img src="/files/7UozR6jK7QBoGhtQinPV" alt="Example using group focus as a dropdown menu."><figcaption><p>Group focus is useful for setting up dropdown menus</p></figcaption></figure>

#### Common uses

Group focus elements are best suited to:

* **Dropdown menus** that appear next to a button or icon.
* **Contextual menus** triggered from list items or table rows.
* **Tooltips** that show extra information near a specific element.
* **Inline pickers**, such as a date or color picker anchored to an input field.

## How a group focus behaves

Two things set a group focus apart from other containers:

**It's positioned relative to a reference element.** A group focus is always anchored to another element on the page, called its reference element. It appears next to that element based on an offset you define, and its position follows the reference element as the layout changes.

<figure><img src="/files/e8lfUOKsov2CWOiSxdhD" alt="The reference element property of a group focus."><figcaption><p>A group focus is anchored to another element on the page.</p></figcaption></figure>

**It hides itself automatically.** Once a group focus is visible, it stays open until the user clicks somewhere outside of it. At that point, it hides without any action needed. You don't have to build a workflow to close it, and you don't need to track its state.

Together, these behaviors make the group focus a lightweight way to add temporary, contextual UI without wiring up separate open and close logic.

## Showing a group focus

Group focus elements aren't visible by default. To show one, use the Show an element action, or use Animate an element for a smoother transition.

For example, clicking a menu icon can trigger Animate an element with a slide-in animation to reveal the group focus. As soon as the user clicks anywhere outside the group focus, it disappears on its own.

You don't need a workflow to hide it. That said, if you want to hide it explicitly, Hide an element still works.

## Positioning and offsets

The group focus's position is controlled by two settings:

* **Reference element:** the element the group focus is anchored to.\
  Offset: the distance in pixels between the group focus and the reference element, allowing you to position it above, below, or to the side.
* **Negative offset values** shift the group focus in the opposite direction, which is useful for aligning menus with the right edge of an icon or the top edge of an input.

Because the group focus follows the reference element, it stays properly positioned as the page scrolls or the layout adjusts.

## Loading data into a group focus

Like other containers, a group focus can hold data.

### Set the data source directly

Set the [*Type of content*](#user-content-fn-1)[^1] and [*Data source*](#user-content-fn-2)[^2] on the group focus, and its child elements can reference the data using `Parent group's` expressions[^3].

<figure><img src="/files/ijcBcqcmGtYEu9eTwf3p" alt=""><figcaption><p>Setting the data source on a group focus makes it load its data as soon as the group is shown.</p></figcaption></figure>

### Push data with a workflow

This is useful when a group focus needs to show information tied to a specific record, such as a menu showing options for a particular user or a tooltip displaying details about a specific product.<br>

<figure><img src="/files/2CtVusgeFg0ogWuvYHhC" alt="Action pushing data to a group focus."><figcaption><p>Using an action you can load a specific item into the floating group as the result of a user action.</p></figcaption></figure>

### Using group focus inside repeating groups

Repeating groups pose a challenge for group focus elements. Because a repeating group contains multiple cells, Bubble can't tell which cell's element to use as the reference. This means you can't directly reference an element inside a cell as the group focus's anchor.

The workaround is to place both the reference element and the group focus inside a reusable element, then place that reusable element in the repeating group.

**How it works:**

* Create a reusable element and set its Type of content to the data type you'll be working with (for example, User).
* Inside the reusable element, add the reference element (like a menu icon) and the group focus that anchors to it.
* In the repeating group, place an instance of the reusable element and set its data source to `Current cell's [Type]`, such as `Current cell's User`.

This makes each instance of the reusable element self-contained, with its own reference element and group focus tied to the specific record in that cell.

## Styling a group focus

Group focus elements can be styled like any other container. They support background colors, borders, border radius, shadows, and opacity. A subtle shadow or border usually works well since group focus elements sit above the surrounding page content.

Using shared styles or style variables keeps group focus elements consistent with the rest of your app's design.

**Article series**: [Styles](/help-guides/design/variables-and-styles/styles)

## FAQ: Group focus

<details>

<summary>How is a group focus different from a popup?</summary>

A popup is centered on the screen and typically dims the page behind it, drawing attention to a message or focused interaction. A group focus is anchored to a specific element on the page and closes automatically when the user clicks outside of it, making it better suited to menus, tooltips, and other lightweight UI.

</details>

<details>

<summary>How is a group focus different from a floating group?</summary>

A floating group sticks to a side of the screen and stays visible regardless of scrolling. A group focus is tied to a specific element on the page and appears next to it, staying open only while it's in focus.

</details>

<details>

<summary>Does the group focus follow the reference element as the page scrolls?</summary>

Yes. The group focus stays anchored to the reference element and moves with it if the page layout changes.

</details>

<details>

<summary>How do I hide the group focus manually?</summary>

Use the [*Hide an element*](/core-resources/bubble-workflows/bubble-actions/element-actions#hide-an-element) or [*Animate an element*](/core-resources/bubble-workflows/bubble-actions/element-actions#animate-an-element) action. In most cases you won't need to, since the group focus hides itself when the user clicks outside of it.

</details>

<details>

<summary>Can I animate the group focus when it appears?</summary>

Yes. Use the [*Animate an element*](/core-resources/bubble-workflows/bubble-actions/element-actions#animate-an-element) action to slide, fade, or scale it in for a smoother transition.

</details>

<details>

<summary>Can I have multiple group focus elements open at once?</summary>

Only one group focus can be open at a time. Opening a new one automatically closes the previous one.

</details>

<details>

<summary>Can I load data into a group focus?</summary>

Yes. [Set the Type of content and Data source](#loading-data-into-a-group-focus), and reference the data inside using Parent group's expressions. You can also push data [using a workflow](#push-data-with-a-workflow).

</details>

<details>

<summary>How do I use a group focus inside a repeating group?</summary>

See [this method](#using-group-focus-inside-repeating-groups).

</details>

<details>

<summary>Can the group focus's offset be dynamic?</summary>

The offset is set to a fixed pixel value in the property editor and doesn't accept dynamic expressions. Design the offset for the position that makes sense with your reference element.

</details>

<details>

<summary>Are group focus elements available in native mobile apps?</summary>

No. Group focus is web-only. On native mobile, similar behavior can be built with [sheets](/core-resources/bubble-elements/element-properties/native-mobile-element-properties/container-properties-mobile/sheet-element-mobile) or [other native components](/core-resources/elements/native-mobile-elements).

</details>

## Other ways to learn

<details>

<summary><mark style="color:blue;">Core reference:</mark> Group focus settings</summary>

In the core reference section, you'll find all the settings associated with floating groups.

Reference: [Group focus](/core-resources/elements/containers#group-focus)

</details>

<details>

<summary><mark style="color:blue;">Video lessons</mark></summary>

* [How to use the Group focus element](https://www.youtube.com/watch?v=l_SbovrRt2s)

</details>

[^1]: The **Type of content** property tells Bubble what kind of data the container will hold, such as `User` or `Post`. This determines what data source it accepts and what child elements can reference through `Parent group`.

[^2]: A **data source** is any source your app pulls data from, such as a database search, a specific record, an option set, or an external API.

    **Core reference:** [Data sources](/core-resources/data/data-sources)

[^3]: **Dynamic expressions** are how you reference and manipulate data in Bubble. They let you pull values from data sources, apply operators, and combine data in different ways to produce a final result, such as `Current User's name` or `Do a search for Posts:count`.

    **Article:** [Dynamic expressions](/help-guides/logic/dynamic-expressions)


# Visual elements (web)

This section describes the visual elements that are available in the Bubble editor

**Visual elements** are the elements you can place on that page that cannot contain other elements (groups) and cannot accept input (input elements). They usually serve two purposes:

* To display information or contribute to the aesthetics of the page
* To accept button clicks

<figure><img src="/files/6cIvv6znSo3L5KBlevIl" alt=""><figcaption></figcaption></figure>

In the illustration above, all elements are visual elements: text, an image and a button.

The visual elements category contains the following elements:

## Text

A non-editable text element. Both the header and the body copy in the image above are text elements.

<details>

<summary>Video lessons</summary>

* [How to use the text element](https://www.youtube.com/watch?v=mtS7bLsV-tg)
* [Formatting text with BBCode](https://www.youtube.com/watch?v=mtS7bLsV-tg)
* [How to Use Text Transform Operators](https://www.youtube.com/watch?v=kmvmYAlrjUk)

</details>

## Button

A button is a shape with a text/icon that's optimized for click workflows. It can contain a text, an icon or both.

<details>

<summary>Video lessons</summary>

* [How to use the button element](https://www.youtube.com/watch?v=tk1Rjo8SziI)
* [How to use the *An element is clicked* event](https://www.youtube.com/watch?v=CObRfZiAitY)

</details>

## Icon

Icon elements let you pick an icon to display from several different [icon collections](#user-content-fn-1)[^1].

{% hint style="warning" %}
On Android devices, **phosphor “filled” icons may display as black boxes** instead of the intended icon. This issue is due to a bug in a third-party library required for rendering these icons.

At this time, there is no workaround. The issue will remain in place until it is resolved by the library author. If your app relies on filled phosphor icons, consider using an alternative icon style for Android builds.
{% endhint %}

<details>

<summary>Video lessons</summary>

* [How to use the icon element](https://www.youtube.com/watch?v=DR-UZ27J8GM)
* [How to use the *An element is clicked* event](https://www.youtube.com/watch?v=CObRfZiAitY)

</details>

## Link

The link element lets you set up a link with a custom text and destination. It can dynamically point to one of your internal pages (including sending data to that page) or point towards an static and/or external link.

<details>

<summary>Video lessons</summary>

* [How to use the link element](https://www.youtube.com/watch?v=-wJZPaXQZpw)

</details>

## Image

Image elements allow you to place images in your app. The element supports all widely used image types such as png, jpg, svg, webp and gif.

<details>

<summary>Video lessons and articles</summary>

* Video: [How to use the image element](https://www.youtube.com/watch?v=eazYG0lRzrg)

Bubble also has a separate element for uploading images:

* Video: [How to use the image uploader element](https://www.youtube.com/watch?v=ZXjhQkM5qV0)
  * Article: [File uploads](/help-guides/design/elements/web-app/input-forms/file-uploads)

</details>

#### Generating an image with AI

You can generate images with AI. Type in a prompt and select a size, quality, and style. The *Auto* setting lets the AI choose style setting based on your prompt. The number of tokens used depends on your selections.

Check *Generate Alt tags* to automatically generate an [Alt text](#user-content-fn-2)[^2].

## Shape

The shape element adds a rectangle to the page which can be resized, rounded and styled in different ways.

<details>

<summary>Video lessons</summary>

* [How to use the shape element](https://www.youtube.com/watch?v=z_H7jQUFsTA)

</details>

## Alert

The alert element adds a bar that can show a text message for a set amount of seconds before it disappears. It's used to display temporary messages such as success and error messages.

<details>

<summary>Video lessons</summary>

* [How to use the alert element](https://www.youtube.com/watch?v=Die7FRWEsbY\&t=18s)
* [How to use the e*lement has error* event](https://www.youtube.com/watch?v=_HNvvPxcWAU\&t=3s)

</details>

## Video

The video element lets you implement streaming video in your app. The element supports YoutTube and Vimeo.

<details>

<summary>Video lessons</summary>

* [How to use the video element](https://www.youtube.com/watch?v=wOc2FIja-uE)

</details>

## HTML

The HTML element lets you place a snippet of HTML code on the page. It can be useful when you need to include custom HTML code in your app that cannot be achieved through Bubble's visual editor. This can be HTML code, CSS styles, and JavaScript code.

Typical use cases for the HTML element include:

* Embedding third-party widgets
* Using custom CSS to style or animate elements
* Adding custom functionality with Javascript

<details>

<summary>Video lessons</summary>

* [How to Use The HTML Element](https://www.youtube.com/watch?v=T8Y6JFg8Ph8)

</details>

## Map

The Map element lets you implement a Google Map on the page, fully navigatable by your users.

<details>

<summary>Video lessons</summary>

* [How to use the map element](https://www.youtube.com/watch?v=bZL1nCBhoWk)
* [How to use the map marker is clicked event](https://www.youtube.com/watch?v=474NiBb14iw)

</details>

## Built on Bubble

This element lets you add a small "Built without code: Bubble" logo to your app.

[^1]: Icons are contained within libraries, similar to all the letters in a specific font.

    For a list of the natively supported icon libraries in Bubble, see the icon element.

    Reference: [Icon element](https://manual.bubble.io/core-resources/elements/visual-elements#icon)

[^2]: "Alt text" is short for **alternative text**. It's a written description of an image used by screen readers for visually impaired users, displayed when an image fails to load, and read by search engines to understand what an image depicts.


# Input forms (web)

This section covers Input forms. These are element that accept data input from a user such as text, numbers, dates, uploads and dynamic content.

Input forms are how you collect information from your users. They range from simple text fields and checkboxes to more advanced controls like date pickers, dropdowns, and file uploaders.

Bubble includes a range of built-in input types for common needs, letting you build everything from a quick signup form to a detailed multi-step questionnaire.&#x20;

{% hint style="info" %}
Bubble includes a wide range of built-in input types for common needs. If you need something more specialized, the [plugin store](https://bubble.io/plugins) offers additional inputs from Bubble and the community.
{% endhint %}

## Categories of input

User input can mean many things, and most apps combine several input types to make forms easy to fill out and to collect data in the right format.

<table><thead><tr><th width="169.29296875">Category</th><th width="332.015625">Purpose</th><th>Examples</th></tr></thead><tbody><tr><td><a href="/pages/9usn2lynDsMjIzGqjm6r">Text and numbers</a></td><td>Collect written or numerical values, from short entries like names and email addresses to long-form content like descriptions. Supports plain and rich text.</td><td>Text input, multiline input, number input</td></tr><tr><td><a href="/pages/yU4rI57C4wpL8CQuejLA">Dates and time</a></td><td>Collect date and time values.</td><td>Date/time picker, date range picker</td></tr><tr><td><a href="/pages/yF6fIrsbNIvkMzWEmd8K">File uploads</a></td><td>Let users upload files.</td><td>File uploader, image uploader</td></tr><tr><td><a href="/pages/uNmsNaHIMteorQ6PaDIa">Selection controls</a></td><td>Present pre-defined options for the user to choose from, either static or dynamic.</td><td>Checkbox, radio button, dropdown, sliding switch</td></tr></tbody></table>

## Combining inputs into forms

Most real interfaces combine several inputs into a single form. A signup form, for example, typically includes text inputs for a name and email, a password input, and a submit button. A checkout form might combine text inputs, dropdowns, and a date picker.

<figure><img src="/files/X0JLOEx4Tmcu2k0QsY5w" alt="Laptop showing a signup form."><figcaption><p>A simple signup form like the one above combines several different categories of input forms to make the UI easy for your users to understand and to prepare the data for processing – such as saving it in the database.</p></figcaption></figure>

There's no dedicated "form" element in Bubble. Forms are built by placing multiple inputs inside a container (usually a group) and setting up a workflow that reads their values when the user submits.

A common pattern:

1. Place the inputs inside a group.
2. Give the group a [*Type of content*](#user-content-fn-1)[^1] that matches the record being created or edited, such as `User`.
3. Add a *Submit* button below the inputs.
4. Set up a workflow that runs when the button is clicked, referencing the inputs' values to create or update the record.

This makes forms easy to organize, style, and reset as a unit.

## Input values

### Referencing input values

Every input element has a value that can be referenced in expressions. For example, `Input Email's value` returns whatever the user has typed into the email input.

Input values are used in:

* **Workflows.** Actions like [*Create a new thing*](/core-resources/bubble-workflows/bubble-actions/database-actions#create-a-new-thing) or [*Make changes to a thing*](/core-resources/bubble-workflows/bubble-actions/database-actions#make-changes-to-thing) can pull values directly from inputs to save them to the database.
* **Expressions.** Text elements, conditions, and other inputs can reference the value of another input to display or react to it.
* **Conditions.** Element visibility, styles, and behavior can respond to what the user has typed.

### Setting initial content

Most inputs support an *Initial content* setting, which populates the input with a value when the page loads. This is useful for edit forms, where the current data should be pre-filled so the user can update it.

<figure><img src="/files/NsyywxmK9QwoDsYrsOLu" alt="Setting the initial content property of an input element."><figcaption></figcaption></figure>

For example, an "edit post" form might load with `Parent group's Post's excerpt` as the initial content in the excerpt input.

Initial content is dynamic, so it updates automatically if the underlying data changes.

### Triggering a workflow when an input is changed

{% hint style="info" %}
If you want to save data immediately when an input's value is changed, you can also use [auto-binding](#auto-binding).
{% endhint %}

You can trigger a workflow whenever the value of an input changes, using the [*An input's value is changed*](/core-resources/bubble-workflows/bubble-events/frontend-event-properties/element-event-properties#an-inputs-value-is-changed) event. This is useful for real-time actions, such as saving changes to the database whenever the value changes. &#x20;

<figure><img src="/files/bxj40kOA3CNv7ynNTF2h" alt="The &#x22;In input&#x27;s value is changed&#x22; event."><figcaption><p>Using the <a href="/pages/PsKwiWQlXizeRXZbz1so#an-inputs-value-is-changed"><em>An input's value is changed</em></a> event, you can trigger a workflow immediately when the value of an input form changes.</p></figcaption></figure>

Workflows can also be triggered by clicking submit buttons, pressing Enter inside an input, or interacting with any other element in the form.

### Resetting inputs

{% hint style="info" %}
The [*Reset relevant inputs*](/core-resources/bubble-workflows/bubble-actions/element-actions#reset-relevant-inputs) action returns each input to its original state, which may include a default value rather than clearing it entirely.
{% endhint %}

The [*Reset relevant inputs*](/core-resources/bubble-workflows/bubble-actions/element-actions#reset-relevant-inputs) action resets all inputs used in the current workflow, returning them to empty or to their default values. This is useful after submitting a form, so the fields are ready for the next entry.

<figure><img src="/files/DDtVw9xg82ey9yTQaBNS" alt="The &#x22;Reset relevant inputs&#x22; action."><figcaption><p>The reset relevant inputs action returns each input used in the workflow to its original state.</p></figcaption></figure>

Alternatively, resetting a group with [*Reset a group/popup*](/core-resources/bubble-workflows/bubble-actions/element-actions#reset-a-group-popup) clears every input inside the group at once, regardless of which workflow they were used in.

## Validation and required fields

Inputs support validation to help ensure users provide the right kind of data. Options include:

* **Content format.** Restricting an input to a specific format, such as email, integer, decimal or password.
* **Required fields.** Marking inputs as required, which prevents submit buttons from running workflows until the required inputs are filled.
* **Min and max values.** Setting numerical or date limits for what the input accepts.

<figure><img src="/files/jhQSp9vIRoCdbdz5RDS7" alt="Choosing the format of a text input element."><figcaption><p>Use the <em>Format</em> property to set an input format. The example above shows a text input element.</p></figcaption></figure>

Validation is a UI convenience and helps guide users to fill out forms correctly, but it isn't a security measure. Data sent to the server should still be validated [server-side](/help-guides/security/client-side-and-server-side).

## Auto-binding

Auto-binding is a feature that saves an input's value directly to the database as the user changes the value, without needing a workflow to trigger the save. It's a fast way to build "always saved" experiences, where changes are persisted automatically instead of waiting for a submit button.

### **How auto-binding works**

When auto-bind is enabled on an input, three things need to be in place:

1. The input sits inside a container with a *Type of content* set to the record being edited, such as `Current User`.
2. The input's *Field to modify* property is configured to save to a specific field on that record.
3. The relevant [privacy rules](#user-content-fn-2)[^2] allow auto-binding on that field.

<figure><img src="/files/dWKJRNlC7X5GGQeImrpZ" alt="Auto-binding properties of a text input element."><figcaption><p>Auto-binding lets you automatically save changes to the database as the input's value changes.</p></figcaption></figure>

Once these are set, any change to the input value is written straight to the database. There's no need for a *Save* button or a workflow to handle the update.

### **When to use auto-binding**

Auto-binding works best in scenarios where you want fields to be saved instantly as the user interacts with input elements. You cannot create new things with auto-binding, only make changes to existing ones.

For forms that need to be validated before saving, or where the user should have a chance to review changes, a standard workflow with a submit button is usually a better fit.

### **Auto-binding and privacy rules**

Auto-binding is controlled by privacy rules. For a field to be editable through auto-bind, the current user must match a privacy rule that grants auto-bind access to that specific field.

<figure><img src="/files/UPreNlsLO19ybJkVS6pY" alt="Privacy rules for auto-binding."><figcaption><p>Auto-bind privacy rules controls what fields a user who matches the rules can edit.</p></figcaption></figure>

This gives you fine-grained control over which fields can be edited through auto-binding, even when the data itself is visible. For example, a user might be able to view a shared document but only auto-bind edits to fields they own.

### **Things to keep in mind**

* **Changes are saved instantly.** There's no confirmation step, so undoing an accidental change means restoring the previous value manually.
* **Every change is a database write.** Rapidly changing inputs can generate a lot of workload. For inputs that update frequently, consider using a workflow that saves on blur or after a short delay instead.
* **Auto-binding is web-only.** On native mobile, values need to be saved through workflows.

## Input forms and security

Inputs can be disabled through the *This input is disabled* property, which makes them read-only in the UI. This is a display setting, **not** a security control.

For real security, use privacy rules to control who can see and modify data in the database, and add server-side conditions to your workflows so sensitive actions only run when the user is authorized.

**Article series:** [Security](/help-guides/security)\
**Article:** [Client-side and server-side](/help-guides/security/client-side-and-server-side)

## FAQ: Input forms

<details>

<summary>How do I read the value of an input?</summary>

Use an expression like `Input Email's value`. Every input has a value that can be referenced in workflows, expressions, and conditions.

</details>

<details>

<summary>How do I pre-fill an input with data?</summary>

Use the [*Initial content*](#setting-initial-content) property to set a default value. This can be static text or a dynamic expression, such as `Current User's name`. When auto-bind is enabled, the initial content is populated automatically from the auto-bound field.

</details>

<details>

<summary>How do I clear an input after the user submits a form?</summary>

Add the [*Reset relevant inputs*](/core-resources/bubble-workflows/bubble-actions/element-actions#reset-relevant-inputs) action to the workflow that runs when the form is submitted. It clears all inputs used in that workflow. To clear every input in a group at once, use [*Reset a group/popup*](/core-resources/bubble-workflows/bubble-actions/element-actions#reset-a-group-popup) on the containing group.

</details>

<details>

<summary>Can I trigger a workflow every time the user types?</summary>

Yes. Use the [*An input's value is changed*](/core-resources/bubble-workflows/bubble-events/frontend-event-properties/element-event-properties#an-inputs-value-is-changed) event to run a workflow every time the input's value updates. Bubble triggers this event automatically as the user types.

{% hint style="info" %}
Be careful when running workflows on every input change. The event can trigger very frequently, which can quickly generate a high number of workflow runs, consume workload, and slow down the user experience.
{% endhint %}

</details>

<details>

<summary>How do I make an input required?</summary>

Enable the This input should not be empty option in the input's properties. When the input is empty, workflows that depend on it won't run until it's filled.

</details>

<details>

<summary>Can I validate the format of a text input?</summary>

Yes. Set the Content format property to restrict the input to a specific format. Validation runs client-side and gives immediate feedback in the UI.

For more advanced validations, use conditions.

</details>

<details>

<summary>How do I save the value of an input to the database?</summary>

Use a workflow triggered by the submit button. Add a [*Create a new thing*](/core-resources/bubble-workflows/bubble-actions/database-actions#create-a-new-thing) or [*Make changes to a thing*](/core-resources/bubble-workflows/bubble-actions/database-actions#make-changes-to-thing) action, and reference the input's value in the field you want to save, such as `Input Email's value`.

</details>

<details>

<summary>Can I autosave inputs as the user types?</summary>

Yes. Use the [*An input's value is changed*](/core-resources/bubble-workflows/bubble-events/frontend-event-properties/element-event-properties#an-inputs-value-is-changed) event to run a [*Make changes to a thing*](/core-resources/bubble-workflows/bubble-actions/database-actions#make-changes-to-thing) action every time the input value changes. Alternatively, enable auto-bind on the input to save the value automatically to the parent group's thing.

</details>

<details>

<summary>Can I disable an input based on a condition?</summary>

Yes. Add a condition to the input that sets This input is disabled to yes when the condition is true. Remember that disabling is a UI control, not a security measure.

</details>

<details>

<summary>How do I combine multiple inputs into a form?</summary>

Place the inputs inside a group with a [Type of content](#user-content-fn-1)[^1] matching the record you want to create or edit. Add a submit button that runs a workflow to save the inputs' values to the database.

</details>

<details>

<summary>Are input elements the same in web and native mobile apps?</summary>

Most input types are available on both platforms, but some inputs and options are specific to one. Refer to the article on mobile inputs for details on how they differ.

</details>

## Other ways to learn

<details>

<summary>Video lessons</summary>

* [How to trigger workflows from input changes](https://www.youtube.com/watch?v=mDEVJLujlkQ)

</details>

[^1]: The **Type of content** property tells Bubble what kind of data the container will hold, such as `User` or `Post`. This determines what data source it accepts and what child elements can reference through `Parent group`.

[^2]: **Privacy rules** are settings that control who can see and modify data in your database. They're evaluated on the server, making them the primary way to keep sensitive data secure and to control access at the field level.

    **Article:** [Privacy rules](/help-guides/data/the-database/protecting-data-with-privacy-rules)


# Text and number input elements (web)

This section covers elements that accept text and numbers as user input

Text and number inputs allow your users to provide their own text strings and numbers to your application that can then be saved in the database or used in other ways.

There are a few different elements that are useful in various scenarios, and those elements can be set up to handle different kinds of formatting and user experiences.

## Input

{% hint style="info" %}
This is the user manual article on the Input Element which gives an overview of how the element works. If you want the technical reference that describes all the settings for this element, check out the Reference entry:

Reference: [Input Element](/core-resources/elements/input-forms#input)
{% endhint %}

Input elements are used for single-line plaintext, but they allow you to specify a content format from a wide range of different options. The *Field type returned* in the list below specifies what kind of database field format the input will return.

<details>

<summary>How Bubble stores text formatting</summary>

Bubble uses a markup language called BBCode to store text formatting such as bold, italitcs, bullet points etc. The BBCode is generated and applied automatically as you add formatting to your text.

BBCode is simpler and safer to use than HTML and because it has more limited capabilities, and only allows for a select number of tags. This stops your users from being able to upload any malicious code such as CSS or Javascript that might break the app.

External link: [Guide to BBCode](https://www.bbcode.org/how-to-use-bbcode-a-complete-guide.php)\
External link: [BBCode tags reference](https://www.bbcode.org/reference.php)

### I want to use HTML instead

Sometimes it's useful to be able to store formatted text as HTML, such as when you want to pass formatted text to an email sending service that only supports this format.

None of the built-in Bubble text editors supports HTML formatting, but there are multiple [plugins](https://bubble.io/plugins) that allow you to store text with formatting as HTML instead of BBCode. Searching the plugin store for *Rich Text Editor* is likely to give you some results that support this.

Regular text elements don't support HTML either: if you want to display text formatted with HTML you can use the built-in HTML element.

</details>

{% hint style="info" %}
Because of the predictive text functionality on **Android devices**, some content formats involving masks have a slightly different UX. This applies to US Phone, Percentage, Currency, Date, Euro date, and Text (numbers only).

On Android devices, the input allows users to type anything and validates the content against the mask only after the user stops typing.

This does not affect the final data generated by the input.
{% endhint %}

### **Text**

The input will accept any text value.

**Field type returned:** text

<figure><img src="/files/a1Q0jz2bcbAdu5fpdfYl" alt="Input element with no formatting."><figcaption></figcaption></figure>

### Email

The input will only accept text values that are formatted like an email address.

**Field type returned:** text

<figure><img src="/files/GriLSgjCJpgr8sTV8MZX" alt="Input element with email formatting."><figcaption></figcaption></figure>

### Password

The input will accept any kind of text value, but the characters written will be replaced by asterisks.

**Field type returned**: text

<figure><img src="/files/vBj6C4qHCMng4Nf23vos" alt="Input element with password formatting, showing only asterisks."><figcaption></figcaption></figure>

### Integer

The input will only accept any whole number. Use this when you need to collect numerical values that can later be used for calculations (as opposed to numbers saved as text). You can specify a dynamic minimum and maximum value when this option is selected.

**Field type returned:** number

<figure><img src="/files/ZYMD2yXbuEoubawLm67g" alt="Input element with number (integer) formatting."><figcaption></figcaption></figure>

### Decimal

The input will accept any whole number or decimal number. You can specify a minimum and maximum value, as well as the number of decimals to include. You can also set the input to automatically include thousands separators as the user types. This setting is only aesthetic and does not affect the final value.

**Field type returned:** number

<figure><img src="/files/BnP7kHBYfhYrI7PaYuY2" alt="Input element with decimal formatting."><figcaption></figcaption></figure>

### Address

A geographic address. With this option, the input automatically validates the address with Google Maps. Note that the input does not automatically search for valid addresses as the user types, so the user has to provide an already valid address (by copy/pasting directly from Google Maps for example). If you want the user to be able to see Google Maps search results as they type you may want to look into using the Search box element instead.

**Field type returned:** address

<figure><img src="/files/9cnwKVNAwGQOK59WRIfF" alt="Input element with address formatting."><figcaption></figcaption></figure>

### US phone

This input will accept correctly formatted US phone numbers. The value is saved as a text.

<figure><img src="/files/uS8qFmnoEJqpXOtFXC4M" alt="Input element with US phone number formatting."><figcaption></figcaption></figure>

**Field type returned:** text

### Percentage

This input will format the user's input as a percentage and accept input such as 20%. Bubble will automatically save it in the correct numerical equivalent of the provided percentage point. For example, 20% will be saved as the number 0.2 and 150% will be saved as 1.5.

<figure><img src="/files/YOiLpe8N9roHvSowr7iZ" alt="Input element with percentage formatting."><figcaption></figcaption></figure>

**Field type returned:** number

### Currency

The input will accept any whole number or decimal number and it will automatically format the input in the chosen currency, such as *$20*. You can specify a minimum and maximum value, as well as the number of decimals to include. You can also set the input to automatically include thousands separators as the user types. The currency and thousands separators settings are only aesthetic and do not affect the final value.

<figure><img src="/files/uk1MRzt7V4kYMZUT9FrV" alt="Input element with currency formatting."><figcaption></figcaption></figure>

**Field type returned:** number

### Date

The input will accept any date, but does not include time. It automatically formats the input's value as mm/dd/yyyy while the user types, guiding the user to provide the date in the right way. You can specify a given time zone for the input and Bubble will automatically calculate any offset from the user's current time zone. If no time zone is provided, the user's current time zone will be used. Dates are saved at 12 am (00:00) on the given date.

<figure><img src="/files/EWZcrPENMU6p6PQif6PP" alt="Input element with date formatting."><figcaption></figcaption></figure>

Field type returned: date

### Euro date

The input will accept any date, but does not include time. It automatically formats the input's value as dd/mm/yyyy while the user types, guiding the user to provide the date in the right way. You can specify a given time zone for the input and Bubble will automatically calculate any offset from the user's current time zone. If no time zone is provided, the user's current time zone will be used. Dates are saved at 12 am (00:00) on the given date. The difference between this content format and the *Date* format is purely visual.

<figure><img src="/files/xkrI80oOgVOP2jBF6YsR" alt="Input element with European date formatting."><figcaption></figcaption></figure>

**Field type returned:** date

### Text (numbers only)

This input will only accept whole numbers and the result will be saved as text. This is useful when you want to save numerical values that need to remain exactly as the user typed (without losing leading zero's for example) and will not be used for any type of calculation, such as international phone numbers, zip codes and order numbers.

<figure><img src="/files/ZYMD2yXbuEoubawLm67g" alt="Input element with numbers as text formatting."><figcaption></figcaption></figure>

**Field type returned:** text

<details>

<summary>Storing numbers as text versus storing them as numbers</summary>

### What is the difference between the two?

The Bubble database offers two different field types that can both be used to save numerical values: text and number.

Why would you ever want to save a number as a text string? There are a few scenarios where it makes sense to do so:

#### **When you need to include leading zero's**

In an integer (or number) a leading zero represents no value and is disregarded when saved to the database. After all 045 and 45 are the same thing from a mathematical point of view and the zero only adds confusion and increases the total size of the database.

Sometimes leading zero's are necessary: for example a phone number or zip code might start with a zero and they must be stored as text to preserve the complete value.

#### When you need to pass the data as text

Sometimes when you communicate with an external system through an API, that system may expect a numerical value to be passed as text. In that case it can be useful to store it as text in the database so as to avoid having to convert it later.

###

### When to use what

**Text** should be used when you are working with numerical values that will remain static and will not be included in any kinds of calculations. For example:

* Phone numbers
* Zip codes
* Postal codes
* Product/Part numbers

**Numbers** should be used when you are working with values that may need to be calculated or aggregated, such as:

* Year of birth
* Age
* Counts and totals (such as the item cart and total value of a cart)
* Financial numbers
* Enumerated values, such as the values used to represent the days of the week (e.g. Sunday = 0, Monday = 1, etc.)

</details>

## Multiline input

As the name suggests, the Multiline input element allows you to set up a text input element that allows for multiple lines of text. Like the regular text input, this element supports plaintext only, and does not provide any custom formatting.

<figure><img src="/files/PrgSppsIEgZXv80Ezl8O" alt="Multiline input element"><figcaption></figcaption></figure>

Use the Multiline input element when you need the user to be able to supply multiple lines of text but you don't want them to be able to add formatting such as bold, italic and headers.

**Field type returned:** text

## Rich Text Editor

{% hint style="info" %}
The Rich Text Editor is a plugin. It's made by Bubble but it needs to be installed in your app before you can use it.\
\
Plugin link: [Rich text Editor](https://bubble.io/plugin/rich-text-editor-1580238841425x582072028873097200)
{% endhint %}

The Rich Text Editor is another multiline input element that allows users to type a rich formatted text with formatting such as colors, fonts, bullet points and links.

<figure><img src="/files/rTJ5LGrk9XLOJYG413MT" alt="Rich text input with toolbar at the top."><figcaption><p>The Rich Text Editor plugin lets your users add advanced formatting to their text.</p></figcaption></figure>

### Hidden toolbar

The Rich Text Editor element comes with a setting to give it a more minimalistic design where the toolbar is only visible when you select some text:

<figure><img src="/files/qzrdogIHH3hWjItcriYM" alt="Rich text input with hidden, floating toolbar."><figcaption></figcaption></figure>

You can also choose to show fewer format settings than the ones above. The screenshot shows the full suite of available options.

## More ways to learn

<details>

<summary><mark style="color:blue;">Core reference:</mark> text input</summary>

Reference: [Input Element](/core-resources/elements/input-forms#input)

</details>

<details>

<summary><mark style="color:blue;">Video lessons</mark></summary>

* [How to use the text input element](https://youtu.be/C7Yg9JbVQsI)
* [How to use text transform operators](https://www.youtube.com/watch?v=kmvmYAlrjUk\&t=5s)

</details>


# Date and time input elements (web)

This section covers elements that accepts dates and time as user input

Date and time elements allow your users to specify a date, a time or both in different ways. Whether you want to save a user's birthday, filter search results or accept bookings in a specific time zone, Bubble offers multiple tools to set it up in a user-friendly way.

{% hint style="info" %}
Designing a UX that handles dates and time opens up for a wide range of different scenarios. While Bubble's built-in tools provide a variety of features, there are also a lot of plugins that offer even more customization.\
\
Check out our [plugin store](https://bubble.io/plugins) to learn more.
{% endhint %}

<details>

<summary>How Bubble handles timestamps and time zones</summary>

WorWorking with dates and times, especially across time zones, can be complex. Bubble automatically handles time zone calculations, including adjustments for Daylight Saving Time (DST).

To learn more about how this works and how to override time zones when needed, check out the article below:

**Article:** [Time, dates and time zones](/help-guides/data/time-dates-and-time-zones)

</details>

## Date/time picker

{% embed url="<https://www.youtube.com/watch?v=Vhk1qcqL4Z8>" %}

The date/time picker lets your users pick a date and a time from a visual calendar. It can also be set up to only pick a date, in which case the field to the right in the animation below will be hidden.

<figure><img src="/files/EefTSgKvF5wXpBeCrE6M" alt="Animation showing how a datetime picker works."><figcaption><p>The date/time picker gives your users a visual interface to pick a date or both.</p></figcaption></figure>

If you set up the element to only ask for a date, the date will be saved at 12 am (00:00) on the chosen date in the [current user's time zone](#user-content-fn-1)[^1]. If you have selected a different time zone in the [Property Editor](#user-content-fn-2)[^2], the date will be saved at 12 am (00:00) in that time zone.

You can set a minimum and maximum date as well as a minimum and maximum time of day, and you can set the interval for the time picker (which is 30 minutes by default).

**Field type returned:** date

<details>

<summary><mark style="color:blue;">Video lessons</mark></summary>

* [How to use the date/time picker input element](https://www.youtube.com/watch?v=Vhk1qcqL4Z8)

</details>

## Input

The input element is mainly used for text input, but can be set up to expect a date instead. In this case the user will not see a calendar or time picker, but will instead be able to type the date directly. Note that the input element supports dates but not time. It can be set up to format the date in:

* US format (mm/dd/yyyy)
* European format (dd/mm/yyyy)

<figure><img src="/files/EWZcrPENMU6p6PQif6PP" alt="Input element with date formatting."><figcaption></figcaption></figure>

**Field type returned:** date

[^1]: All computers have a time zone setting, and this is passed on to the internet browser. Bubble in turn uses this information to determine what time zone the user is in, so as to correctly show the current date/time.\
    \
    Bubble uses this time zone by default, but it can be overriden in many elements and operators.

[^2]: The Property Editor is the floating window that lets you edit the settings of a given element.\
    \
    You display the Property Editor either by double-clicking the element in the design tab or clicking it once in the Element Tree.


# File upload input elements (web)

This section covers elements that lets your users upload files and images

File upload elements let your users upload any kind of file to the Bubble server. Bubble has two different elements for uploading files: one for general file uploads and one specifically for images.

<details>

<summary>File storage and the database</summary>

In the Bubble Data Type editor you can add two types of file upload fields:

* file
* image

When you upload a file to Bubble, the file itself isn't stored in the database. The process happens in two steps: Bubble uploads the file to an AWS server, then saves the URL of that file in the database.

This means that file and image fields in your database hold only a short URL pointing to the file, not the file itself. From a performance standpoint, the database stays lightweight, and the file is only downloaded when it's actually needed.

</details>

<details>

<summary>Deleting uploaded files</summary>

The database doesn't hold files, only their URLs. Clearing the URL from a record doesn't delete the file itself.

To delete the file from the server, use the *Delete an uploaded file* action in a workflow. Keep in mind you need the file's URL to run this action, so delete the file before clearing the URL from the database.\
\
Video tutorial: [Deleting uploaded files](https://www.youtube.com/watch?v=-z6b2UH_JAw)

</details>

{% hint style="warning" %}
Uploaded files are public by default, meaning that anyone with the URL can access it. If you want your files to be securely private, you need to check the *Make this file private* on the uploader element and set up your privacy rules correctly. This applies both to images and other files.\
\
You can read more about these settings in the core reference articles below:

Reference: [Uploading files to be private](/core-resources/elements/input-forms#make-this-file-private)\
Reference: [Protecting uploaded files with Privacy Rules](/core-resources/data/privacy#view-attached-files)\
Video tutorial: [Setting up Privacy Rules](https://www.youtube.com/watch?v=1-meIeBUXPY)
{% endhint %}

## File uploader

{% embed url="<https://www.youtube.com/watch?v=2wQo0Nve4Zs>" %}

The File uploader element is used for uploading non-image files. It also supports uploading image file types, but the Image uploader element offers some additional features for that.

<figure><img src="/files/A2v5OCqPQvQkxHJpkDAh" alt="File uploader element."><figcaption><p>The File uploader element will show the filename after the user has selected a file to upload.</p></figcaption></figure>

You can set a maximum size for the uploaded file. The File uploader element introduces a new action that lets you cancel an ongoing upload.

## Picture uploader

{% embed url="<https://www.youtube.com/watch?v=ZXjhQkM5qV0>" %}

The Image uploader element is used for uploading images and will give you a preview of the image after the upload. If you have set an image as its initial content it will also show that image, making it useful for images that are sometimes updated, like profile pics.

<figure><img src="/files/ONIc7U6iYUQHJwwUiK0x" alt="Image uploader element in an app. Formatted to be circular."><figcaption><p>The Image uploader element will show the image after it has been uploaded. In the example above we have styled the element to be circular.</p></figcaption></figure>

## Multi-FileUploader (with drop area)

The Multi-FileUploader is similar to the regular file Uploader, but with two key differences:

* It allows uploading multiple files
* It allows you to set up a drop area where your users can drag-and-drop files

<figure><img src="/files/0KiXfVsw2rMN2qZF3Hnb" alt="Dragging files into a multi-file uploader element."><figcaption></figcaption></figure>

It allows you to set a maximum number of files to upload and an initial list of files to display upon page load.

{% hint style="info" %}
The Multi-FileUploader element is a plugin. It's made by Bubble but it needs to be installed in your app before you can use it.\
\
Search for Multi-FileUploader in the plugin store to install it.
{% endhint %}


# Selection control input elements (web)

This section covers selection control elements, that lets you set up input elements with predefined options

Selection control elements are input elements that let your users make a choice from a preselected set of options. This ranges from a simple *yes/no* answer to picking one or more records from the database or other data sources.

## Checkbox

{% embed url="<https://www.youtube.com/watch?v=HnJPoIAXgRc>" %}

The Checkbox element is the most basic selection control element which simply returns a yes or a no depending on whether the box is checked or not.

<figure><img src="/files/gaDPRK7UsSYXwu0Xubx6" alt="Checkbox example with terms and conditions."><figcaption></figcaption></figure>

<details>

<summary><mark style="color:blue;">Video lessons</mark></summary>

* [How to use the checkbox input element](https://www.youtube.com/watch?v=HnJPoIAXgRc)

</details>

## Radio button

{% embed url="<https://www.youtube.com/watch?v=jqj5LnkjWNs>" %}

The Radio button element is similar to the checkbox, but allows you to add more than one option. You can set up a static list of text options (in the example below we have used the texts Option 1, Option 2 and Option 3) or you can fetch a list of Things from the database and display one of their fields as the label.

<figure><img src="/files/7Iq3FHQ0zZlOeHhRk342" alt="A list of three radio buttons."><figcaption></figcaption></figure>

<details>

<summary><mark style="color:blue;">Video lessons</mark></summary>

* [How to use the radio button input element](https://www.youtube.com/watch?v=jqj5LnkjWNs)

</details>

## Dropdown

{% embed url="<https://www.youtube.com/watch?v=5jdnZtSBbjA>" %}

The Dropdown element, similarly to the Radio button element lets you give your users one choice out of a selection of texts or from the database.

<figure><img src="/files/KhGnhBlB1hE9zPivCITA" alt="A dropdown element."><figcaption></figcaption></figure>

<details>

<summary><mark style="color:blue;">Video lessons</mark></summary>

* [How to use the dropdown input element](https://www.youtube.com/watch?v=jqj5LnkjWNs)

</details>

## Searchbox element

{% embed url="<https://www.youtube.com/watch?v=TYlb77ObEyg>" %}

The search box element lets you set up a flexible text search for database records. The element looks like a regular [input field](/help-guides/design/elements/web-app/input-forms/text-and-numbers#input-elements) but quickly and dynamically returns search results as the user types.

<figure><img src="/files/9QOlKfqS9MxJQYpF4rBJ" alt="The searchbox element showing products."><figcaption></figcaption></figure>

{% hint style="warning" %}
**Note:** Each time you type or change a character in the search box element, it triggers a fresh [database query](#user-content-fn-1)[^1]. While this ensures a rapid search response, it's worth remembering that each query consumes some workload[^2]. To learn more about workload management, refer to our article series linked below.\
\
Article series: [Pricing and workload](/account-and-marketplace/account-and-billing/pricing-plans)
{% endhint %}

<details>

<summary><mark style="color:blue;">Video lessons</mark></summary>

* [How to use the searchbox element](https://www.youtube.com/watch?v=TYlb77ObEyg)
* [How to use the *Do a search for* data source](https://www.youtube.com/watch?v=-2_3kuyOxkw)
* [How to use search constraints](https://www.youtube.com/watch?v=gOjGDCJrXYI)

</details>

## Slider input

{% embed url="<https://www.youtube.com/watch?v=4GxuhmKij_4>" %}

The Slider input element lets your users select a numerical value out of a range. You can set a minimum and a maximum value and the value of each step.

<figure><img src="/files/IaFPpJa4QRXVcHDEs77b" alt="SliderInput element."><figcaption></figcaption></figure>

<details>

<summary><mark style="color:blue;">Video lessons</mark></summary>

* [How to use the slider input element](https://www.youtube.com/watch?v=4GxuhmKij_4)

</details>

## Multiselect dropdown

The Multiselect dropdown element lets your users select multiple choices from a list of texts or records from the database. They choose the item from a dropdown list or search for them by typing.

<figure><img src="/files/VvfayREIs7ayPtui7bR1" alt="Multiselect dropdown element."><figcaption></figcaption></figure>

The element returns a list of whichever type of data you specify.

{% hint style="info" %}
The Multiselect dropdown element is a plugin. It's made by Bubble but it needs to be installed in your app before you can use it.\
\
Search for Multiselect dropdown in the plugin store to install it.
{% endhint %}

<details>

<summary><mark style="color:blue;">Video lessons</mark></summary>

* [How to use the *Do a search for* data source](https://www.youtube.com/watch?v=-2_3kuyOxkw)
* [How to use search constraints](https://www.youtube.com/watch?v=gOjGDCJrXYI)

</details>

[^1]: A *database query* means that Bubble communicates with the server to either transmit or retrieve data, such as saving something or finding something using *Do a search for*.\
    \
    Article series: [The database](/help-guides/data/the-database)\
    Article: [Finding data](/help-guides/data/the-database/finding-data)\
    Reference: [Do a search for](/core-resources/data/data-sources#do-a-search-for)

[^2]: *Workload* is a measure of the work that Bubble does in order to power your application. Each time your app is tasked with processing an action – such as loading a page and [querying your database](#user-content-fn-3)\[^3] – it contributes to your monthly workload consumption.\
    \
    Article series: [Pricing and workload](/account-and-marketplace/account-and-billing/pricing-plans)


# Reusable element (web)

This section covers reusable elements: elements that can be using in multiple places in your app

Reusable elements are elements you build once and use across your app. They function like custom components, letting you package a design and its behavior into a single unit that you can drop anywhere it's needed.

<figure><img src="/files/w6sg3YiWn6SiCYdYbHD5" alt="Laptop with Bubble&#x27;s homepage showing a top navigation bar with a reusable element."><figcaption><p>The navigation toolbar on <a href="https://www.bubble.io/">bubble.io</a> (marked in red) is a typical use case for reusable elements. The toolbar, along with all its buttons and workflows, can be reused in as many places as needed across the app.</p></figcaption></figure>

Because updates to a reusable element apply automatically to every instance of it in your app, they're a powerful tool for keeping your design consistent, avoiding duplication, and making your app faster to build and maintain.

## What reusable elements do

Any group in Bubble can be turned into a reusable element. Once converted, that group becomes a standalone component you can place on any page in your app.

Reusable elements have three main advantages:

1. **Consistency.** Every instance shares the same structure and design, so updates apply everywhere at once.
2. **Efficiency.** You build the component once instead of rebuilding it on every page.
3. **Flexibility.** Instances can be customized through parameters, letting each one adapt to the context it's used in.

Common examples include headers, footers, navigation menus, cards, form components, popups, and login forms.

### Reusable elements vs. pages

Reusable elements and pages both hold structured layouts, but they serve different purposes:

<table><thead><tr><th width="198.2109375">Feature</th><th>Reusable element</th><th>Page</th></tr></thead><tbody><tr><td>Purpose</td><td>A shared component used inside pages</td><td>A standalone screen users navigate to</td></tr><tr><td>URL</td><td>No</td><td>Yes</td></tr><tr><td>Contains workflows</td><td>Yes</td><td>Yes</td></tr><tr><td>Can be nested</td><td>Yes, inside other reusable elements or pages</td><td>No</td></tr><tr><td>Parameters</td><td>Yes, through custom properties</td><td>Yes, through the <em>Type of content,</em> often called <em>the page thing.</em></td></tr></tbody></table>

Use reusable elements for components that appear across multiple pages. Use pages for full screens users can navigate to directly.

### Reusable elements vs. groups

Reusable elements and groups can look similar in the editor, but they serve different purposes.

<table><thead><tr><th width="135.16015625">Feature</th><th>Reusable element</th><th>Group</th></tr></thead><tbody><tr><td>Scope</td><td>Shared across pages</td><td>Belongs to a single page</td></tr><tr><td>Editing</td><td>Edited in isolation</td><td>Edited directly on the page</td></tr><tr><td>Updates</td><td>Changes apply to every instance across the app</td><td>Changes affect only the specific group</td></tr><tr><td>Workflows</td><td>Has its own workflows, defined inside the reusable element</td><td>Workflows live with the page it's on</td></tr><tr><td>Parameters</td><td>Accepts data through <em>Type of content</em> or custom properties</td><td>Accepts data through a single <em>Type of content</em></td></tr></tbody></table>

Use a group when the component is only needed on one page. Use a reusable element when the component will appear in multiple places, or when you want to encapsulate its design and behavior for reuse.

### Reusable elements for web and mobile

Reusable elements are created and managed separately for each platform, since web elements and native mobile elements are not compatible with each other. To create a [reusable element for mobile](/help-guides/design/elements/ios-and-android-app/mobile-reusable-elements), switch to the mobile editor and add it there in the same way.

## How reusable elements work

A reusable element has two parts:

* **The definition.** The reusable element itself, edited from the reusable elements section of the editor. Any changes made here affect every instance across your app.
* **The instance.** A placed copy of the reusable element on a page or inside another reusable element. Instances share the design and behavior of the definition, but can accept parameters to display different content.

The definition and each instance have their own separate property editors. This split gives you a single source of truth for the component's core properties, while still allowing individual instances to adapt to the page or context they're used in.

Once a reusable element is defined, adding it to a page works the same as with any other element. Drag it from the palette or the sidebar and configure any parameters it accepts.

## Types of reusable elements

When you create a reusable element, you choose what type of container it will be. The type controls how the reusable element behaves when placed on a page. Three options are available:

{% hint style="info" %}
Even if you plan to use a reusable element inside a popup or floating group, it can be worth setting it up as a *Group* type and placing that group inside a regular popup or floating group on the page. This gives you more flexibility in certain scenarios, such as when you want to collapse the reusable element's width or height, or use it in different contexts without being locked into a specific overlay behavior.
{% endhint %}

### Group

The default option. A group-type reusable element behaves like a regular group on the page it's placed on. It sits inline with the surrounding content and can be arranged using the same layout modes as any other container.

Use this type for most reusable components, including headers, footers, cards, and form sections. You can read more about the group element in the article below:

**Article:** [Group](/help-guides/design/elements/web-app/containers/groups)

### Popup

Turns the reusable element into a popup. Instances open on top of the page, centered on the screen, and dim the background to draw focus to the popup itself.

Use this type when the reusable element is a shared dialog, form, or focused interaction that should appear as an overlay. You can read more about the popup element in the article below:

**Article:** [Popup](/help-guides/design/elements/web-app/containers/popups)

### Floating group

Turns the reusable element into a floating group. Instances stay fixed to a side of the screen and remain visible as the user scrolls. Use this type for shared components that need to stay in place, such as a persistent header, a sidebar, or a floating action button.

The type is set when the reusable element is created and defines its behavior for every instance of it. Choose the type based on how the component will be used most often across your app. You can read more about the floating group element in the article below:

**Article:** [Floating group](/help-guides/design/elements/web-app/containers/floating-groups)

## Creating a reusable element

There are two ways to create a reusable element:

### Convert an existing group

Right-click a group on the page and select *Convert to a reusable element*. Bubble creates a new reusable element that contains the group's original design and elements. It does not delete or replace the original group, but you can manually delete it and place the new reusable in its stead.

This is useful when you've already built a component on a page and realize it should be reusable.

### Create one from scratch

In the page navigator, click the *New* button and *Reusable web element*, give it a name, and build the design from scratch.

This is useful when you're starting fresh, such as building a shared header before adding it to any pages.

## Detaching a reusable element

Occasionally, you may want to break the link between an instance and its definition. Right-clicking a reusable element instance and selecting *Detach reusable element* converts it into regular page elements. The design, layout, and workflows are preserved on the page, but the connection to the original reusable element is removed.

Once detached, the elements behave like any other elements on the page:

* Changes made to the original reusable element no longer affect them.
* Changes made to the detached elements don't affect the reusable element or any other instances.
* The elements can be edited independently, like any other design on the page.

Detaching is useful when a page needs a variation of the reusable element that's different enough to warrant a separate design, or when the reusable element is being phased out and you want to preserve the existing content on the page.

This action can't be undone directly. If you need to revert, you can convert the elements back into a reusable element manually, but there's no automatic way to reattach them to the original.

## Passing data to and from reusable elements

### Using the *Type of content*

Reusable elements accept a *Type of content* just like a regular group, popup or floating group. You define the type of content, such as *User, Post*, or text on the [definition](#how-reusable-elements-work), and set the data source on the [instance](#how-reusable-elements-work).

### Using custom properties

Reusable elements can accept data through custom properties, similar to how a view accepts data on native mobile. This lets each instance display different content while sharing the same structure.

To set this up:

1. Open the reusable element's definition.
2. Add one or more custom properties, each with a name and a type (such as *User*, *Post*, or *Text*).
3. Reference those properties from elements inside the reusable element using expressions like `Reusable element's User's name`.
4. Add the reusable element to a page, and configure its properties with the data you want to pass in.

For example, a *User card* reusable element might accept a *User* parameter. Instances on different pages can display different users by passing different values into that parameter.

Custom properties can also be read from outside of the reusable element, and is a useful way to pass data both ways.

### Using custom states

You can also pass data between a page and a reusable element using the *Set state of an element* action:

1. Create a custom state on the reusable element itself, not on any of its child elements, and set it to the correct data type.
2. Use the *Set state of an element* action from the page's workflow to write data into that custom state.

The reusable element's child elements can then reference the custom state directly in expressions or conditions.

This method works both directions. A page can push data into a reusable element, and a reusable element can update a state that other elements react to.

### Using URL parameters

URL parameters don't pass data directly into a reusable element, but because both the page and any reusable elements it contains can read the current URL, parameters make a useful shared channel.

This is especially convenient when the data being shared corresponds naturally to something users might bookmark or share, such as a filter, a category, or the current view of a single-page app.

Like custom states, URL parameters work in both directions. A page can set a parameter that a reusable element reads, and a reusable element can update the URL for the page to react to.

## Reusable elements and workflows

Reusable elements have their own workflows. Anything triggered inside a reusable element is defined and edited there, not on the page it's placed on. This keeps behavior encapsulated with the component.

Common patterns:

* A header with a login button that triggers a login popup, both defined in the same reusable element.
* A card component that opens a detail view when clicked.
* A form component that validates and submits its own data.

Workflows inside a reusable element can also communicate with the page it's placed on through custom events, letting the page respond when something happens inside the reusable element.

### Custom events

Custom events are workflows that don't run on their own. Instead, they run when explicitly triggered by another workflow. Inside a reusable element, custom events are especially useful, because they let you expose specific behaviors that can be triggered from the page the reusable element sits on.

This makes reusable elements more flexible. A reusable component isn't just a self-contained unit: it can respond to instructions from the outside world, letting pages and other components coordinate with it.

Custom events also let you use reusable elements as workflow libraries. By defining a set of commonly used workflows as custom events inside a reusable element, you can manage them in a single central place and trigger them from any page that includes the reusable element.

The reusable element doesn't need to contain any visual elements for this to work. A reusable element created purely to hold shared workflows is a common pattern for organizing app-wide logic, such as user account actions, analytics tracking, or shared utility flows.

#### Defining a custom event

Custom events are defined inside the reusable element's workflow editor, alongside any other workflows the component uses. Each custom event has:

* **A name**, used to reference it when triggering.
* **Optional parameters**, which pass data into the event from the workflow that triggers it.
* **A set of actions**, which run when the event is triggered.
* **Optional return values,** which pass one or more values back to the original workflow.

Once defined, a custom event is available to any workflow that has access to the reusable element.

#### Triggering a custom event from a page

To trigger a custom event inside a reusable element from the page it's on, use the [*Trigger a custom event from a reusable element*](/core-resources/bubble-workflows/bubble-actions/custom-actions#trigger-a-custom-event-from-a-reusable-element) action. This action lets you:

1. Select the reusable element instance on the page.
2. Choose the specific custom event to trigger.
3. Pass values into any parameters the event accepts.

If you are using return values, you can reference them in the next step of the workflow as *Result of step X*.

## Styling reusable elements

Reusable elements can be styled like any other element. The styling defined on the reusable element carries over to every instance, keeping the design consistent.

Individual instances can override some properties, such as size or visibility, but the underlying design is controlled by the definition. Using shared [styles](/help-guides/design/variables-and-styles/styles) or [style variables](/help-guides/design/variables-and-styles) inside a reusable element keeps the design consistent both within the component and across the rest of your app.

## FAQ: Reusable elements for web

<details>

<summary>What's the difference between a reusable element and a group?</summary>

A group is a container that lives on a specific page. A reusable element is a component that can be used across multiple pages, with a shared definition that updates every instance at once.

</details>

<details>

<summary>Can I convert a group into a reusable element?</summary>

Yes. Right-click a group and select *Convert to a reusable element*. Bubble creates a new reusable element from the group and its child-elements, but does not delete or replace the original group.

</details>

<details>

<summary>Can reusable elements accept data?</summary>

Yes. Add custom properties to the reusable element to accept data from the page or component that uses it. Reference the properties inside the reusable element using expressions like Reusable element's \[property].

</details>

<details>

<summary>Can a reusable element trigger a workflow on the page it's placed on?</summary>

No. You can trigger a custom event defined in a reusable element from the outside, by using the [*Trigger a custom event from a reusable element*](/help-guides/logic/workflows/events/frontend-events/custom-events#trigger-a-workflow-in-a-reusable-element), but it does not work the other way.

You can use workarounds, such as using a custom parameter or custom state on the reusable element, and then using the *Do when condition is true* event that watches the custom property/state's value.

</details>

<details>

<summary>Can I trigger a workflow inside a reusable element from the page?</summary>

Yes. Use the [*Trigger a custom event from a reusable element*](/help-guides/logic/workflows/events/frontend-events/custom-events#trigger-a-workflow-in-a-reusable-element) action, and reference a custom event defined inside the reusable element.

</details>

<details>

<summary>Can reusable elements contain other reusable elements?</summary>

Yes. Nesting reusable elements is a common pattern, especially for complex layouts where smaller components are combined into larger ones.

</details>

<details>

<summary>Can I use a popup as a reusable element?</summary>

Yes. Popups can be built as reusable elements and reused across pages, keeping the design and behavior consistent everywhere they appear.

</details>

<details>

<summary>Do changes to a reusable element affect existing instances?</summary>

Yes. Any change to the reusable element applies immediately to every instance across your app.

</details>

<details>

<summary>Can individual instances have different styling?</summary>

Some properties, like size or visibility, can be adjusted at the instance level. The underlying design and structure are controlled by the reusable element definition. You can use a combination of custom properties and conditionals to style a reusable element in different circumstances.

</details>

<details>

<summary>Are reusable elements available on native mobile?</summary>

Yes, but the two platforms have separate reusable element libraries, meaning that a reusable element made for web cannot be used on mobile, and vice versa. This is because web elements are incompatible with mobile elements.

</details>

<details>

<summary>Are reusable elements good for performance?</summary>

Technically yes. Reusing components keeps the overall codebase leaner, since the same code is loaded once and used in multiple places. In most cases the impact on performance is small, but small decisions add up, so using reusable elements is generally a good habit when performance matters.

Working with reusable elements can also make the Bubble editor itself more responsive. Because you edit them in isolation, Bubble has fewer elements and workflows to load at once, which can reduce memory usage while you build.

That said, the biggest benefit of reusable elements is developer efficiency. They save time, reduce duplication, and make your app easier to maintain.

</details>

## Other ways to learn

<details>

<summary>Video lessons</summary>

* [Converting elements to reusable elements](https://www.youtube.com/watch?v=3GEH_hCaAWk)
* [How to build a responsive navigation bar](https://www.youtube.com/watch?v=3lUlmTZ_IQo)

</details>


# iOS and Android app

iOS and Android apps include elements that are native to their operating systems.&#x20;

However, many elements used in web apps are also supported in native mobile apps. Below is a list of web app elements that are also available when building mobile apps.

## Containers

Containers are elements that contain other elements.

<table><thead><tr><th width="121.4921875">Element</th><th>Description</th></tr></thead><tbody><tr><td><a href="/pages/NvacCqH3lNaRhxAJVECA">Group</a></td><td>The most basic and versatile container type. Used to organize elements, control layout, and hold data.</td></tr><tr><td><a href="/pages/lhtfublW1vNS4m5LiZcN">Floating group</a></td><td>A container that stays fixed to a side of the screen as the user scrolls, ideal for persistent headers, floating action buttons, or status banners.</td></tr><tr><td><a href="/pages/tMWVlyqYJBFmjso00QHy">Short list</a></td><td>A container for displaying a small, fixed list of items.</td></tr><tr><td><a href="/pages/QxGmyPCTI498McajLuRP">Horizontal list</a></td><td>A container that displays a scrollable list of items side by side, useful for carousels, category rows, and media galleries.</td></tr><tr><td><a href="/pages/j7YZDQYm5o8UbHD5UeKS">Sheet</a></td><td>A container that slides up from the bottom of the view, sitting above the current content while keeping the underlying view partially visible.</td></tr></tbody></table>

## Visual

Visual elements are elements that display something, such as text, images, icons and maps.

<table><thead><tr><th width="105.890625">Element</th><th>Description</th></tr></thead><tbody><tr><td><a href="/pages/TygnjEUgwbgASJ4mD35F">Text</a></td><td>Displays static or dynamic text on the view.</td></tr><tr><td><a href="/pages/iJgvHoFu1Dg3IvxKO9n6">Button</a></td><td>A tappable element that triggers a workflow when pressed.</td></tr><tr><td><a href="/pages/sxe485tIPRmezA67Cs6E">Icon</a></td><td>Displays a small graphic symbol, often used for actions, navigation, or visual cues.</td></tr><tr><td><a href="/pages/UsQ4TqOUGrFQowPvPUtT">Image</a></td><td>Displays a static or dynamic image on the view.</td></tr><tr><td><a href="/pages/tRJbRLOEyQNOzRoQ53tn">Shape</a></td><td>Displays a rectangle or other geometric shape, often used for backgrounds, dividers, or decorative elements.</td></tr><tr><td><a href="/pages/xktB1axBf9aH6RoI9AGq">Map</a></td><td>Displays an interactive map with optional markers.</td></tr><tr><td><a href="/pages/Sa1zfuZlznpKjmE8NJfX">Web view</a></td><td>Embeds a page from your web app in your mobile app.</td></tr></tbody></table>

## Input forms

Input forms are elements that collect data from the user, such as text, numbers, dates/time and option selection.

<table><thead><tr><th width="166.546875">Element</th><th>Description</th></tr></thead><tbody><tr><td><a href="/pages/kc2HeG4yI6rEmy95HTvO#input">Input</a></td><td>A single-line field for entering short text, numbers, emails, or other short values.</td></tr><tr><td><a href="/pages/kc2HeG4yI6rEmy95HTvO#multiline-input">Multiline input</a></td><td>A multi-line field for entering longer text, such as descriptions, comments, or messages.</td></tr><tr><td><a href="/pages/kc2HeG4yI6rEmy95HTvO#checkbox">Checkbox</a></td><td>A tappable element that lets users toggle a yes/no value.</td></tr><tr><td><a href="/pages/kc2HeG4yI6rEmy95HTvO#selectable-lists">Selectable list</a></td><td>A list where users can select one or more items from a predefined set of options.</td></tr><tr><td><a href="/pages/kc2HeG4yI6rEmy95HTvO#date-timepicker">Date/time picker</a></td><td>A native picker for selecting dates, times, or both.</td></tr></tbody></table>

## Reusable elements

Reusable elements are containers that can contain other elements and workflows. This container can be reused across your app.

**Article:** [Reusable element](/help-guides/design/elements/ios-and-android-app/mobile-reusable-elements)


# The view element (mobile)

{% hint style="info" icon="mobile-button" %}
The page is the element at the top of the hierarchy in a **mobile app**. For web apps, see [the page](/help-guides/design/elements/web-app/the-page).
{% endhint %}

A view is the top-level element in a native mobile app, serving the same role that a page does in a web app. Every mobile screen your users interact with is a view, and everything else on the screen sits inside it.

<figure><img src="/files/bfZAK55NuuCQCdBY08SL" alt="The view canvas of the mobile app editor."><figcaption><p>The view is the top-level element in a native mobile app.</p></figcaption></figure>

If pages are canvases in a web app, views are the canvases of a mobile app.

## What a view is

A view is a full-screen container that holds all the elements that make up a screen in your app. Instead of loading separate URLs like a web app does, a mobile app moves between views within a single app shell.

This has a few important implications:

* **No URLs.** Views don't have their own web addresses. Users navigate between views through your app's UI, not by typing a URL or clicking a link.
* **Instant transitions.** Because there's no page reload, moving between views is fast.
* **Preserved state.** Data loaded into one view can be preserved as users move between views, depending on the navigation pattern.

Typically, each view represents a distinct screen. A social app might have views for a home feed, a profile, a settings page, and a compose screen, each designed and configured independently.

### Lists

Mobile views differ from web pages in that they can hold regular content (elements placed on the view) or fullscreen lists. Instead of building lists with elements, you can set the view's type to vertical list or section list.

You can read more about this [below](#view-types).

## Views vs. pages

Views serve the same purpose as pages, but with a few key differences. The main distinction is that you don't choose between them: views are for mobile apps, and pages are for web apps. Beyond that, there are some functional differences worth being aware of:

<table><thead><tr><th width="173.24609375">Aspect</th><th>Page (web)</th><th>View (mobile)</th></tr></thead><tbody><tr><td><strong>Access</strong></td><td>URL-based, accessible directly by typing the address</td><td>Reached through navigation inside the app</td></tr><tr><td><strong>Transitions</strong></td><td>Full-page reloads</td><td>Instant, native transitions</td></tr><tr><td><strong>Availability</strong></td><td>Anyone with the link can attempt to visit</td><td>Only accessible through the app itself</td></tr><tr><td><strong>SEO and indexing</strong></td><td>Can be indexed by search engines</td><td>Not indexable, since views aren't URLs</td></tr></tbody></table>

{% hint style="warning" %}
**Version control and branches:** We recommend avoiding adding a mobile app directly to the Main branch unless you plan to work in a sub-branch created after the mobile app has been added to Main.

Adding a mobile app to both Main and an existing branch can lead to conflicts. As long as you create a branch after adding the mobile app to Main, or add the mobile app in a branch and then merge it into Main, the setup should work as expected.

**Article series:** [Version control](/help-guides/maintaining-an-application/version-control)
{% endhint %}

## Managing views

Mobile views are managed through the pages dropdown at the top of the editor. From here, you can create new views, open existing ones, or delete views you no longer need.&#x20;

<figure><img src="/files/Plj44RrQ3yoT0iQhphE1" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
Note on branches: Avoid adding a mobile app directly to the Main branch unless you plan to work in a sub-branch created after the mobile app has been added to Main. Adding a mobile app to both Main and an existing branch may lead to conflicts. As long as you create a branch after adding the mobile app to Main, or add the mobile app in a branch and then merge it with Main, the setup should work as expected.
{% endhint %}

## View types

Views come in four types, each suited to a different layout style. The view type determines how the view handles scrolling, content layout, and how users interact with the content inside.

### Scrollable

The most common view type. In a scrollable view, users can scroll vertically to see all of its content, similar to a standard web page. This is the right choice for most views, especially those with forms, mixed content, or anything that might extend beyond the height of the screen.

Scrollable views are flexible and work well for the majority of screens in an app, from settings pages to detail views.

### Non-scrollable

A non-scrollable view keeps its content fixed to the size of the device screen. Nothing scrolls, and everything the user sees is contained within the visible area.

This type is useful for:

* **Splash screens** shown while the app loads.
* **Full-screen images or videos** where the media should fill the entire display.
* **Maps** where the map itself provides its own navigation and doesn't need the surrounding view to scroll.
* **Focused single-purpose screens**, such as an onboarding step with just a headline and a call-to-action.

Non-scrollable views work best when the content is intentionally limited to what fits on the screen at once.

### Vertical list

A vertical list turns the view into a scrollable list, similar to a repeating group on web. The list repeats a set of elements for each item in the data source, creating a familiar mobile list interface.

Vertical list views are ideal for:

* **Social feeds**, where each post is a repeating item.
* **Contact lists** or **message inboxes**.
* **Search results**.
* **Any long, scrolling collection of similar items**.

Because vertical lists lazy-load their items, they perform well even with large collections.

### Section list

A section list works like a vertical list but groups items into sections based on a shared property. Each section has a header, and items are organized under the header they belong to.

Section list views work well for:

* **Contacts grouped alphabetically**, with headers like *A*, *B*, *C*, and so on.
* **Tasks grouped by category or status**.

The section list handles the grouping and header rendering automatically, so you only need to configure the data source and choose the grouping property.

## View appearance settings

Several appearance settings control how the view integrates with the device and the app shell.

### **Show top app bar**

Toggles the app bar at the top of the view. The app bar holds a title and navigation controls.

<figure><img src="/files/BBUTwNOUwvtEn8ig3Q0i" alt="The top app bar at the top of the view."><figcaption><p>The top app bar.</p></figcaption></figure>

### **Safe area**

Toggles the safe area on the view. When enabled, device-specific padding keeps content clear of hardware features like notches, home indicators, and rounded corners.&#x20;

<figure><img src="/files/hVS0p2a4dyLV3wHQdNwe" alt="The top and bottom safe area on the view canvas."><figcaption><p>The safe area helps you avoid elements being cut off or overlapped by device features.</p></figcaption></figure>

Disabling the safe area allows content to extend across the entire screen, which is useful for immersive views like maps or full-screen media.

{% hint style="info" %}
The top safe area will remain visible on the canvas if a **top app bar** is added to the view, and the bottom safe area will be visible if a **tab bar** is present. However, even with the safe area setting toggled off, it will still impact content near the side safe areas, even though those areas don’t appear on the canvas.
{% endhint %}

### **Show status bar**

Toggles the device's status bar, which displays time, battery level, and network status.&#x20;

<figure><img src="/files/nLq7Q4PX0MZMCuBC8hmK" alt=""><figcaption></figcaption></figure>

Disabling it creates a more immersive view, though it also hides information users may expect to see.

### **Show tab bar**

Toggles the tab bar at the bottom of the view. This option is checked and locked when *Include as tab item* is enabled, since a view that acts as a tab item must display the tab bar.

<figure><img src="/files/Aa17NAVOz1GBpsGcqP1U" alt="The tab bar."><figcaption></figcaption></figure>

### **Include as tab item**

Adds the current view to the tab bar as a tab item. The tab item defaults to the view's name and a star icon, both of which can be customized.

## Custom properties

Views can hold data through custom properties, similar to how a reusable element passes data between its definition and its instances. Custom properties let a view accept data when it's opened, so its content can adapt to whatever the user is doing at the time.

<figure><img src="/files/pTVXu624xMvMgZt2lGZK" alt="Editing a custom property on a view element."><figcaption><p>Custom properties can hold any kind of data. In this example, we have a custom property called <em>Blog post</em>, which holds the data type <em>Post</em>.</p></figcaption></figure>

For example, a *Post detail* view might have a *Blog post* property that receives the specific post the user tapped in a list. Once the property is set, elements inside the view can reference it in dynamic expressions.

#### What custom properties can hold

A custom property can hold any type of value your app works with, including:

* **Dynamic values**, such as a specific record from a data type (like a `Post`, `User`, or `Order`), or a basic value like text, number, or date.
* **Colors**, either as a hex code or a saved color variable.
* **Yes/no values**, useful for toggling states or driving conditions.

Each property can be marked as a list to hold multiple values of the same type, or set as optional if a value isn't always required.

#### Setting up a property

Properties are configured through the property editor for the view itself. Each property has:

* **A name**, used to reference it in expressions.
* **A type**, defining what kind of value it accepts.
* **An optional description**, useful for keeping the purpose of the property clear when you or a collaborator returns to it later. See more [here](#describing-a-property).

Properties can also have a default value or be left empty until data is passed in.

#### Setting a property's value

There are two ways to give a property a value:

* **When navigating to the view.** The *Go to view* workflow action lets you pass values into each of the view's properties. This is the most common pattern: the user taps something, and the destination view opens with the relevant data already loaded.
* **From within the view.** The *Set current view's property* action assigns or updates a property while the view is active. This is useful for tracking state that changes as the user interacts with the view, such as which tab is currently selected or whether a section is expanded.

#### Describing a property

You can add a description to each property using the *Description* field. Descriptions don't affect how the property behaves; they're a convenience for you and your team, giving the property a bit of context so its purpose stays clear when someone opens the view later.

Descriptions are especially useful for properties whose names aren't fully self-explanatory, or for views with several properties that serve different roles.

#### Referencing a property

Once a property is defined, elements and workflows inside the view can reference it using `Current view's [property name]`. This works the same way as referencing a reusable element's properties from within the element.

## Navigation between views

Navigating between views is different from navigating between pages on the web. Views are more tightly integrated with your app, and both the method and settings used to move between them affect how the navigation flow behaves.

There are two ways to navigate between views in Bubble:

### Tab navigation

Tab navigation is powered by the tab bar, a persistent navigation panel at the bottom of the screen. Each tab item references a single view, so tapping a tab takes the user directly to that view without needing a workflow.

<figure><img src="/files/NXkqG32vsfvntEZtcDhN" alt="The tab bar in a mobile app."><figcaption><p>The tab bar allows you to set up a persistent navigation panel without needing workflows.</p></figcaption></figure>

The tab bar is a global component, similar to a reusable element on web. There's only one tab bar in your app, but it can appear across multiple views, and changes made to it anywhere apply everywhere.

Tab items can be added to the tab bar in two ways:

* **Designating a view as a tab item.** Enable *Include as tab item* on a view. This adds a tab item that references the current view.
* **Adding a tab item from the tab bar.** From the tab bar's property editor, add a new tab item and either link it to an existing view or create a new one.

A tab item can only reference one view, and a view can only be linked to one tab item.

You can customize each tab item's label, icon, icon size, colors, and active/inactive states through its property editor. Conditions can also be used to hide a tab item based on criteria like the user's login status.

### Using the go to view action

Similar to *Go to page* in a web app, but no URL loads and no page refreshes. The new view opens either as a [stack or as a modal](#stack-and-modal-navigation).

<figure><img src="/files/rXqRDWfg8cjUR53jr7Vh" alt="The go to view action with stack navigation type."><figcaption><p>Using the <em>Go to view</em> action, you can select the view to navigate to, and whether to use stack or modal navigation.</p></figcaption></figure>

#### **Stack and modal navigation**

The *Go to view* action supports two navigation styles:

* **Stack navigation.** The new view is placed on top of the current one, adding a layer to the stack. The user can return to the previous view using the back button or a swipe gesture. The previous view remains active in memory, with its content preserved. Stack navigation is what powers common flows like tapping a list item to see its details, then returning to the list.&#x20;
* **Modal.** The new view is presented as a full-screen sheet over the current view. Modals are typically dismissed by swiping down or tapping a close button, and are well suited to focused, temporary tasks such as composing a message.

#### Stack navigation example

In Spotify, tapping a playlist from the *Home* tab pushes a new view onto the *Home* tab's navigation stack. Tapping the back arrow returns you to the *Home* view. If you switch to the *Your Library* tab and open the same playlist there, it opens within that tab's stack instead, keeping each tab's navigation history independent.

<figure><img src="/files/xMBm2J84J2Ky14tOhTlS" alt="Example of Spotify using stack navigation."><figcaption><p>Tapping a playlist in Spotify pushes a view to the <em>Home</em> tab's navigation stack. Tapping back returns you to the <em>Home</em> view.</p></figcaption></figure>

#### **Resetting the navigation stack**

The *Go to view* action includes an option to reset the navigation stack. This clears the navigation history, so the user can't return to the previous view. It's useful for flows like completing a login, where returning to the login screen after signing in doesn't make sense.

Resetting the stack removes previous views from memory, so any data stored on those views (like form content) is no longer accessible.

### FAQ: Views

<details>

<summary>What's the difference between a view and a page?</summary>

A page belongs to a web app and is accessed via a URL. A view belongs to a native mobile app and is reached through in-app navigation. Both serve as top-level containers for their respective platforms, but they behave differently.

</details>

<details>

<summary>Do views have URLs?</summary>

No. Views live inside the mobile app and aren't accessible through a URL. Navigation between views happens through [workflows](#using-the-go-to-view-action) or [tab taps](#tab-navigation) within the app itself.

</details>

<details>

<summary>How do I navigate between views?</summary>

There are two ways: tap an item in the [tab bar](#tab-navigation), or use the [*Go to view* workflow action](#using-the-go-to-view-action). *Go to view* supports [stack navigation, modal presentation](#stack-and-modal-navigation), and [resetting the navigation stack](#resetting-the-navigation-stack).

</details>

<details>

<summary>Can I pass data to a view when navigating to it?</summary>

Yes. Define [custom properties](#custom-properties) on the view, then pass values into those properties when using [*Go to view*](#using-the-go-to-view-action). The view's child elements can then reference the properties in dynamic expressions.

</details>

<details>

<summary>What's the difference between stack navigation and a modal?</summary>

[Stack](#stack-and-modal-navigation) navigation adds the new view on top of a stack, keeping the previous view active behind it. A [modal](#stack-and-modal-navigation) presents the new view as a temporary sheet, usually for focused tasks. Both are triggered through the [*Go to view*](#using-the-go-to-view-action) action.

</details>

<details>

<summary>Can I have more than one tab item link to the same view?</summary>

No. A tab item references exactly one view, and a view can only be linked to one tab item.

</details>

<details>

<summary>Can I hide a tab item based on a condition?</summary>

Yes. Add a condition[^1] to the tab item and use it to set *This element is visible* to no when the condition is true. This is useful for tab items that should only appear to certain users, such as admins or logged-in users.

</details>

<details>

<summary>Can I hide the status bar on a specific view?</summary>

Yes. Turn off [*Show status bar*](#show-status-bar) in the view's properties. The status bar stays hidden while that view is active.

</details>

<details>

<summary>Can I hide the tab bar on a specific view?</summary>

Yes. Turn off [*Show tab bar*](#show-tab-bar) in the view's properties. Note that if *Include as tab item* is enabled, the tab bar is required and can't be hidden on that view.

</details>

<details>

<summary>What happens if I reset the navigation stack?</summary>

The navigation history is cleared, and the user can't return to previous views.&#x20;

</details>

<details>

<summary>Can views be indexed by search engines?</summary>

No. Views live inside the mobile app and don't have URLs, so they can't be indexed. Content that needs to be discoverable through search should also be published as web pages.

</details>

<details>

<summary>Can I change a view's type after building it?</summary>

Yes, but switching between view types can temporarily remove existing content from the view. Switching back restores it. Test carefully if the view already contains significant work.

</details>

[^1]: A conditional lets you change how an element looks or behaves when specific criteria are met.\
    \
    **Article:** [Conditions](/help-guides/logic/conditions)


# Container elements (mobile)

Containers hold other elements and control how they behave on the view. Placing an element inside a container makes the container its parent and the element its child, forming the hierarchical structure the browser uses to render the layout correctly. You can see this parent-child relationship in the element tree.

Containers are one of the most important tools in Bubble. They control layout, hold data, group related elements, and are the foundation of any responsive design. Mastering containers is essential to mastering design in Bubble.

## What containers do <a href="#what-containers-do" id="what-containers-do"></a>

Containers serve three main purposes:

1. **They organize elements into a hierarchy.** Grouping related elements together makes them easier to manage, move, style, and reference.
2. **They control layout.** A container's layout settings determine how its child elements are arranged and how they respond to different screen sizes.
3. **They hold data.** A container can be given a data source, making that data available to every element inside it.

Most containers do more than one of these at the same time. A single group might hold a form's fields, control their layout, and load the user record being edited, all in one.

<details>

<summary>The element hierarchy</summary>

All elements in a mobile app are organized in a hierarchy that shapes both the layout and how data flows through your design. Understanding this hierarchy is key to building views that behave predictably and look right across devices.

Article: [The element hierarchy](/help-guides/design/elements/the-element-hierarchy)

</details>

## Container types

<table><thead><tr><th width="168.4296875">Element</th><th>Description</th></tr></thead><tbody><tr><td><a href="/pages/NvacCqH3lNaRhxAJVECA">Group</a></td><td>The most basic and versatile container type. Used to organize elements, control layout, and hold data.</td></tr><tr><td><a href="/pages/lhtfublW1vNS4m5LiZcN">Floating group</a></td><td>A container that stays fixed to a side of the screen as the user scrolls, ideal for persistent headers, floating action buttons, or status banners.</td></tr><tr><td><a href="/pages/tMWVlyqYJBFmjso00QHy">Short list</a></td><td>A container for displaying a small, fixed list of items.</td></tr><tr><td><a href="/pages/QxGmyPCTI498McajLuRP">Horizontal list</a></td><td>A container that displays a scrollable list of items side by side, useful for carousels, category rows, and media galleries.</td></tr><tr><td><a href="/pages/j7YZDQYm5o8UbHD5UeKS">Sheet</a></td><td>A container that slides up from the bottom of the view, sitting above the current content while keeping the underlying view partially visible.</td></tr><tr><td><a href="/pages/kc2HeG4yI6rEmy95HTvO#selectable-lists">Selectable list</a></td><td>A list with built-in functionality for selecting one or more records.<br><strong>Note:</strong> a selectable list behaves like a container, but is listed as an input form in the element palette, because it accepts user input. </td></tr></tbody></table>

## Lists in mobile apps

In a web app, you will mostly use repeating groups or tables to build lists. Native mobile apps use the mobile operating system's native components, and thus behave a bit differently. Lists *can* be built using elements, but are often instead displayed as a property of an already existing element. For example, the view has two built-in list types:

* [**Vertical lists**](/help-guides/design/elements/ios-and-android-app/the-view#view-types)**:** a list that take up the full screen and lazy-loads[^1] as the user scrolls.
* [**Section lists**](/help-guides/design/elements/ios-and-android-app/the-view#view-types)**:** lists that are divided into a section, such a by category or by a numerical value such as age. Also lazy-loads.

You won't find these in the element palette, as they are pseudo child elements of the view itself. You set this behavior in the *View type* property.

**Article:** [The view](/help-guides/design/elements/ios-and-android-app/the-view)

### Pagination

Pagination is a web pattern that doesn't translate well to mobile because of the small screen. It's still possible to set up pagination, but we recommend instead using the more mobile-friendly pattern of fullscreen lists that lazy-load[^1]. For lists longer than 20-30 or so items, we recommend using [vertical lists](/help-guides/design/elements/ios-and-android-app/the-view#view-types) or [section lists](/help-guides/design/elements/ios-and-android-app/the-view#view-types).

For very long lists, implementing search or filtering can be a user-friendly option to long scrolling.

### List elements

While longer lists should use [vertical lists](/help-guides/design/elements/ios-and-android-app/the-view#view-types) or [section lists](/help-guides/design/elements/ios-and-android-app/the-view#view-types), native mobile apps also has list elements that can be placed directly on the view or inside a container.&#x20;

* [**Short list:**](/help-guides/design/elements/ios-and-android-app/containers/short-list-element-mobile) Short lists are similar to repeating groups, but load all content immediately. As the name suggests, they should be used only to hold lists of a few items.
* [**Horizontal list:**](/help-guides/design/elements/ios-and-android-app/containers/horizontal-list-element-mobile) A horizontal list is used to display a list of items in a scrollable, side-to-side layout.
* [**Selectable list:**](/help-guides/design/elements/ios-and-android-app/input-forms#selectable-lists) A selectable list is also a container for small lists, that has built-in functionality for selecting one or more records, similar to radio buttons or a list of checkboxes in web apps.\
  **Note:** the selectable list element is considered an input form element because i accepts user input. You'll find it under Input forms in the element palette.

## Popups in mobile apps

Using popups is a common scenario in web apps, used to display messages, ask for confirmation, show details about data and for login/sign-up flows.

Native mobile apps don't have popups, but you can instead use one of the native alternatives:

* [**A view:**](/core-resources/elements/native-mobile-elements/view-element) A view can be quickly navigated to, fills the whole screen and can be navigated back from
* [**A sheet:**](/help-guides/design/elements/ios-and-android-app/containers/sheet-mobile) A sheet is a container that slides up from the bottom of a native mobile view, sitting above the current content while keeping it partially visible.

## Controlling layout with containers

Containers use one of four layout modes to arrange their children:

* **Column** stacks children vertically. Each element sits below the previous one.
* **Row** arranges children horizontally, side by side.
* **Align** lets you position children in one of nine fixed positions within the container.
* **Fixed** gives each element a fixed position, without automatic layout logic.

Each layout mode comes with its own set of alignment and spacing options. Choosing the right layout mode is one of the most important decisions when building a responsive design. You can also nest containers inside other containers.

Using containers to control layout is the key to an efficient responsive design.

**Article series:** [Responsive design](/help-guides/design/responsive-design)

## Styling containers

Containers can be styled just like any other element. They support background colors, borders, border radius, shadows, opacity, and more. They can also be set to fully transparent or invisible when their only purpose is to control layout or hold data.

To open a container's style settings, click on it in the element tree or on the canvas. Its properties appear in the property editor on the right.

<figure><img src="/files/vxjr9tr8a6DOOyrstymg" alt="A container&#x27;s styling properties in the element property editor."><figcaption><p>Containers can be styled individually, or by using predetermined styles.</p></figcaption></figure>

Using shared styles or style variables keeps container styling consistent across your app.

Article series: [Styles](/help-guides/design/variables-and-styles/styles)

## Working with containers in the editor

A few tips make working with containers easier:

* **Use the element tree to navigate parent-child relationships**. It's the fastest way to select a specific container in a complex design.
* **Give containers meaningful names**. Group Header and Group Cart Items are much easier to work with than Group A and Group B.
* **Nest carefully**. Nested containers make responsive design more flexible, but excessive nesting can slow down rendering and make the layout more difficult to manage.
* **Consider reusable elements**. If the same container structure appears in multiple places, converting it into a reusable element saves time and keeps the design consistent.

[^1]: Lazy loading is a technique where data or content is loaded only when it's needed, such as when the user scrolls into view. This helps improve performance by avoiding loading everything upfront.


# Group element (mobile)

Groups are the most basic and versatile container type. A group can be placed anywhere on a view or inside another container, and used to organize elements, control layout, hold data, and add visual styling.

Because they're so flexible, groups are the container you'll reach for most often when building a mobile app.

## What groups do

Groups serve three main purposes:

1. **Organize elements.** A group holds other elements as its children, keeping related items together and forming a clear hierarchy in the element tree.
2. **Control layout.** Layout settings on a group determine how its children are arranged and how they respond to different screen sizes.
3. **Hold data.** A group can be given a data source, making that data available to every element inside it.

Groups can serve one or all of these purposes at the same time. A single group might structure a form, control its layout, and load the user record being edited.

For a broader look at container types on mobile, see the article on mobile containers.

## Loading data into a group

Groups can hold a single piece of data, made available to every element inside. Setting this up takes two steps:

1. Set the group's *Type of content* to the type of data it will hold, such as `User` or `Post`. This tells Bubble what to expect and prevents mismatched data sources.
2. Set the *Data source* to an expression that returns a value of that type, such as `Current User` or `Do a search for:first item`.

Once the data source is set, child elements can reference it directly using expressions like `Parent group's User's name`.

There are two ways to load data into a group:

#### Set the data source directly

The data loads as soon as the view opens. This works well for data known upfront.

<figure><img src="/files/ZgaI1X0Z5jMUlYTeOdtj" alt="Loading data into a mobile group by setting a data source."><figcaption><p>Setting the data source directly loads the data immediately.</p></figcaption></figure>

#### Push data with a workflow

Use the *Display data in a group* action to load data in response to a user action, such as tapping an item in a list.

<figure><img src="/files/tUGwrWAOfRsJU05Cjvnu" alt="Using the Display data in group/popup action to push data to a group."><figcaption><p>You can push data to a group as a result of user action, by using the <em>Display data in group/popup</em> action.</p></figcaption></figure>

Both methods behave the same way once the data is loaded.

## Controlling layout with groups

Groups use one of four layout modes to arrange their children:

* **Column** stacks children vertically.
* **Row** arranges children horizontally.
* **Align** positions children in one of nine fixed positions inside the group.
* **Fixed** lets you place each child at a specific position without automatic layout logic.

Each mode has its own alignment and spacing options. Choosing the right layout mode is one of the most important decisions when building a responsive mobile design that adapts across different device sizes.

Nested groups are a common pattern: an outer group with a column layout might hold several inner groups with row layouts, each representing a section of the view. Together, they form the responsive structure of the design.

## Styling groups

Groups can be styled like any other element. You can style individual groups by adjusting their different properties, or by connecting them to predefined styles[^1] and/or [style variables](#user-content-fn-2)[^2].

<figure><img src="/files/bTX9OLDhLPwvzb3fbaUs" alt="The styling properties of a mobile group."><figcaption><p>You can style individual groups by adjusting their different properties, or by connecting them to predefined styles and/or style variables.</p></figcaption></figure>

Using shared styles or style variables keeps groups consistent across your app.

## Collapsing hidden groups

When a group is set to be invisible, it can also be set to collapse. A collapsed group takes up no space in the view, so surrounding elements move up to fill the gap. When the group becomes visible again, the layout reflows to make room for it.

This behavior is controlled by the *Collapse when hidden* property. Without it, an invisible group still occupies its original space, leaving an empty area in the view.

Collapsing hidden groups is useful for building dynamic mobile layouts, where sections of a view appear or disappear based on the user's actions or context.

### Using groups for in-view state changes

While mobile navigation typically happens through views, groups can also be used to change what's visible inside a single view. This is useful for things like:

* Switching between sections of a settings screen without navigating to a new view.
* Toggling filters, sort options, or additional details on a list.
* Showing and hiding form steps within a multi-step form.

By placing different sections inside separate groups and toggling their visibility, you can build focused, in-view interactions that stay within the same screen.

### Showing and hiding groups

Two ways to control which group is visible:

#### Conditions

Set conditions on each group that show it when certain criteria are true. For example, showing one group when a toggle is on and another when it's off.

#### Workflows

Use actions like [*Show an element*](/core-resources/actions/element#show-an-element), [*Hide an element*](/core-resources/actions/element#hide-an-element), [*Toggle an element*](/core-resources/actions/element#toggle-an-element), or [*Animate an element*](/core-resources/actions/element#animate-an-element) to control visibility in response to user actions. Toggle is useful for switching between two states, while Animate adds transitions for a smoother visual effect.

#### Things to keep in mind

* For navigation between full screens, use views instead of groups. Mobile apps are built around views, and users expect standard mobile navigation patterns for major transitions.
* Groups work best for smaller in-view state changes, not entire screen changes.
* Naming groups clearly and organizing the element tree carefully makes the design easier to manage.

### Frequently asked questions

<details>

<summary>Can groups hold other groups?</summary>

Yes. Nested groups are a core building block for responsive mobile layouts. Just avoid unnecessary nesting, since deeply nested layouts can be harder to maintain.

</details>

<details>

<summary>Do I need to give every group a data source?</summary>

No. Groups can be used purely for layout, styling, or organization without holding any data. A data source only becomes relevant when the elements inside need to reference specific information.

</details>

<details>

<summary>What happens to child elements when I delete a group?</summary>

Deleting a group also deletes every element inside it. To keep the child elements, move them out of the group first.

</details>

<details>

<summary>Can I make a group tappable?</summary>

Yes. Set up a workflow triggered by the group's *When tapped* event, and the group will behave like a tappable element. This is useful for cards, list items, and other interactive layouts.

</details>

<details>

<summary>Can a group be transparent?</summary>

Yes. Set the background to fully transparent or reduce the opacity. The group still functions normally for layout and data, but has no visible appearance.

</details>

<details>

<summary>What's the difference between hidden and collapsed?</summary>

A hidden group is invisible but still takes up space in the view. A collapsed group is invisible and takes up no space, so surrounding elements move to fill the gap. Collapsing is controlled by the *Collapse when hidden* property.

</details>

<details>

<summary>Should I use a group or a view for navigation?</summary>

Use a view for full-screen navigation, such as moving between the main sections of your app. Use a group for in-view changes – showing and hiding content within the same screen.

</details>

[^1]: Styles are reusable sets of visual properties, like colors, fonts, and borders, that can be applied to elements across your app. Updating a style automatically updates every element that uses it, keeping your design consistent.

    **Article:** [Styles](/help-guides/design/variables-and-styles/styles)

[^2]: Style variables let you save individual colors and fonts, and reference them anywhere in your app. Updating a variable updates every style and element that uses it, making it easy to change your app's look in one place.

    **Article:** [Style variables](/help-guides/design/variables-and-styles)


# Floating group element (mobile)

A floating group is a container that hovers above the rest of the view. It can be attached to any side of the screen and stays in place regardless of how far the user scrolls. Because they're independent of the view's normal flow, floating groups are the go-to element for UI that needs to stay visible at all times.

{% hint style="warning" %}
Mobile apps already include the **native app bar and tab bar**, designed for familiar navigation patterns on mobile devices. We recommend using floating groups only when those built-in components don't meet the needs of your design. See more [below](#floating-groups-vs.-app-bars-and-tab-bars).
{% endhint %}

## Common uses of floating groups

Floating groups are used for anything that needs to remain accessible as the user moves through a view:

* Persistent action buttons that follow the user as they scroll, like a compose or add button.
* Sidebars that provide contextual navigation or information.
* Custom headers when the standard app bar doesn't fit the design.
* Cart summaries or status bars that stay visible during a task.
* Back-to-top buttons that appear once the user has scrolled down.
* Custom notifications and banners that appear briefly and dismiss themselves.<br>

## How floating groups behave

{% hint style="info" %}
Floating groups will position themselves just below the top app bar when aligned to the top or just above the tab bar when aligned to the bottom, if either of these elements are present on the view.
{% endhint %}

Floating groups sit in their own layer above the rest of the view. Because of this, they don't affect the layout of the surrounding content. Regular elements don't push around floating groups or make room for them, and floating groups don't take up space in the normal flow.

This is what allows a floating header to stay pinned to the top of the screen while the user scrolls, or an action button to stay fixed to the bottom-right regardless of the view's content.

Floating groups can hold any elements a regular group can, including forms, images, buttons, icons, and other containers.

### Floating groups vs. app bars and tab bars

Native mobile apps include built-in components for the most common persistent UI:

* **App bar** for a header at the top of the view. This may also include a back button.
* **Tab bar** for navigation at the bottom of the view.

For these use cases, prefer the built-in components.

<figure><img src="/files/8Jft2BNrulq2RERL1EDq" alt=""><figcaption><p>Native mobile apps include built-in components for the most common persistant UI. They are enable by selecting the view and adjusting the <em>Navigation</em> properties.</p></figcaption></figure>

They handle safe areas, animations, and platform conventions automatically. Floating groups are best used for custom UI that doesn't fit those standard patterns, or when the built-in components don't give you the flexibility your design needs.

## Attaching to the screen

Every floating group is attached to the screen along a horizontal and vertical axis. Together, these two settings determine where the group appears and how it stays positioned as the user scrolls.

<figure><img src="/files/VERMCKsVeKBwcyOhbpd8" alt="Attaching a floating group on mobile to the top and left edge of the screen."><figcaption><p>In this example, the group floats relative to the top and left edge.</p></figcaption></figure>

The horizontal axis controls whether the group sticks to the left, right, or spans both sides. The vertical axis controls whether it sticks to the top, bottom, or spans both. A group that spans both edges stretches to fill the screen along that axis.

Margins let you offset the group from the edge it's attached to. For example, a floating action button might be attached to the bottom-right of the screen with a 20-pixel margin so it sits comfortably away from the edge.

## Working with safe areas

Native mobile apps include safe areas: the parts of the screen not covered by hardware features like notches, home indicators, or rounded corners. Floating groups can extend into these areas or respect their boundaries.

<figure><img src="/files/MjaPSh085Sk1nC4qNKXd" alt=""><figcaption></figcaption></figure>

Use the *Safe area behavior* property to set whether the floating group ignores the safe area, applies it to the container itself, or applies it only to the group's child elements.

## Working with the on-device keyboard

Native mobile keyboards can take up a significant portion of the screen, and floating groups pinned to the bottom of the view often end up hidden behind them. The *Move with keyboard* property controls how the floating group responds when the keyboard opens.

* **Checked:** the floating group shifts upward to stay visible above the keyboard. This is useful for elements the user needs to see or interact with while typing, such as an input field, a chat composer, or a submit button.
* **Unchecked:** the floating group stays in its fixed position, even if that means being covered by the keyboard. This is fine for elements that aren't relevant during typing.

<figure><img src="/files/yH1vXVyt1RTfZXukI0Tz" alt="The Move with keyboard property on a mobile floating group."><figcaption><p>The <em>Move with keyboard</em> property controls how the floating group responds when the keyboard opens.</p></figcaption></figure>

For most floating groups anchored to the bottom of the view, leaving *Move with keyboard* checked provides the smoother user experience.

## Layering with z-index

The z-index controls whether a floating group sits above or below other elements. By default, floating groups sit above the view content, which is what makes them float visibly.

<figure><img src="/files/VitQew0VYZILDoOGDxXI" alt=""><figcaption></figcaption></figure>

You can also set a floating group to sit beneath the view. This is occasionally useful for background layers or design effects, but it comes with a catch: if the view has its own background style, that background can hide the floating group entirely.

Unless you're intentionally using a floating group as a background layer, leave the z-index setting on its default so the group appears above the view.

## Loading data into a floating group

Floating groups can hold a data source, just like regular groups. It can be loaded in two different ways:

#### Set the data source directly

The data loads as soon as the view opens. This works well for data known upfront.

<figure><img src="/files/VKmqZZcUGJcmLgcgEG91" alt=""><figcaption><p>Setting the data source directly loads the data immediately.</p></figcaption></figure>

#### Push data with a workflow

Use the *Display data in a group* action to load data in response to a user action, such as tapping an item in a list.

<figure><img src="/files/YdHfJJhMxIKfnqOIyzAC" alt="The Display data in a group action pushing data to a mobile floating group."><figcaption><p>You can push data to a floating group as a result of user action, by using the <em>Display data in group/popup</em> action.</p></figcaption></figure>

Both methods behave the same way once the data is loaded.

## Showing and hiding floating groups

Floating groups can be shown, hidden, and animated in the same way as regular groups.

### Using actions

Use the  [*Show an element*](/core-resources/actions/element#show-an-element), [*Hide an element*](/core-resources/actions/element#hide-an-element), [*Toggle an element*](/core-resources/actions/element#toggle-an-element), and [*Animate an element*](/core-resources/actions/element#animate-an-element) actions to control visibility.

### Using a conditional

You can also use a conditional to show or hide the floating group based on specific criteria.

<figure><img src="/files/cGAmyyKsZHpqnOcjWYaN" alt="Conditional controlling the visibility of a floating group on mobile."><figcaption><p>This floating group will only be visible when the current user is logged in.</p></figcaption></figure>

## Styling floating groups

Floating groups can be styled like any other element. You can style individual floating groups by adjusting their different properties, or by connecting them to predefined styles[^1] and/or [style variables](#user-content-fn-2)[^2].

<figure><img src="/files/bTX9OLDhLPwvzb3fbaUs" alt="The styling properties of a mobile floating group."><figcaption><p>You can style individual floating groups by adjusting their different properties, or by connecting them to predefined styles and/or style variables.</p></figcaption></figure>

Using shared styles or style variables keeps groups consistent across your app.

Frequently asked questions

<details>

<summary>Can a floating group be attached to more than one side of the screen?</summary>

Yes. You can set the behavior horizontally and vertically, letting the floating group lock to a specific combination like top-left, bottom-right, or bottom-center.

</details>

<details>

<summary>Do floating groups affect the layout of the rest of the view?</summary>

No. Floating groups sit in their own layer above the view and don't take up space in the normal flow. Other elements aren't pushed around by them.

</details>

<details>

<summary>Can I have more than one floating group on the same view?</summary>

Yes. You can add multiple floating groups, each attached to a different part of the screen. A common example is a view with both a floating action button and a floating notification banner.

Keep in mind that mobile screens are smaller than desktop displays and vary between device models. What looks good in preview may feel cramped on certain devices, so test your design across a range of screen sizes.

</details>

<details>

<summary>Can I load data into a floating group?</summary>

Yes. [Set](#set-the-data-source-directly) the Type of content and Data source on the group, and its child elements can reference the data using `Parent group's` expressions. You can also push data using a [workflow](#push-data-with-a-workflow).

</details>

<details>

<summary>Can floating groups be shown and hidden?</summary>

Yes. You can control the behavior using [workflow actions](#using-actions) or [conditionals](#using-a-conditional).

</details>

<details>

<summary>Can I animate a floating group as it appears?</summary>

Yes. Use the [Animate an element](/core-resources/actions/element#animate-an-element) action to slide, fade, or scale the group in and out.

</details>

<details>

<summary>Why is my floating group hidden behind the view?</summary>

The [z-index](#layering-with-z-index) is likely set to Beneath the view. Change it to sit above the view in the group's properties.

</details>

<details>

<summary>How do I make a floating group appear only after the user scrolls?</summary>

Yes. Use a conditional, and set the *Current view scrolling position* data source to a specific value.

</details>

<details>

<summary>Should I use a floating group or the app bar for a header?</summary>

Use the app bar for standard headers with a title and navigation controls. Use a floating group when you need a custom header design that goes beyond what the app bar supports. In general we recommend using the native tabs.

</details>

<details>

<summary>Can I nest containers inside a floating group?</summary>

Yes. Floating groups can hold any elements, including other containers, which makes them useful for building custom headers, sidebars, and menus with layered layouts.

</details>

[^1]: Styles are reusable sets of visual properties, like colors, fonts, and borders, that can be applied to elements across your app. Updating a style automatically updates every element that uses it, keeping your design consistent.

    **Article:** [Styles](/help-guides/design/variables-and-styles/styles)

[^2]: Style variables let you save individual colors and fonts, and reference them anywhere in your app. Updating a variable updates every style and element that uses it, making it easy to change your app's look in one place.

    **Article:** [Style variables](/help-guides/design/variables-and-styles)


# Short list element (mobile)

{% hint style="warning" %}
**Note on performance:** Short lists load the entire data source at once, rather than using lazy loading. This makes them ideal for shorter lists, while longer lists may impact performance.
{% endhint %}

A short list is a container used to display a fixed list of items in a native mobile app. Like a vertical list, it repeats its child elements once for each item in the data source, but it loads all its data at once rather than lazy-loading as the user scrolls. This makes it well suited to short, predictable collections that fit naturally within a view.

Short lists are the right choice for compact, focused lists that live alongside other content in a view.

## What short lists do

At the most basic level, a short list takes a list of things and repeats its child elements once for each item. If you show a list of five records, everything you add to the short list is duplicated five times, once per record.

Each of these repetitions is called an **item**. An item holds all the child elements and represents a single item from the list. Text elements can display data from that item, buttons can trigger actions on it, and containers can be styled to give each item its own look.

Short lists work similarly to regular groups: you set a *Type of content*, provide a *Data source*, and the elements inside can reference the data. The difference is that a regular group holds one item, while a short list holds many.

{% hint style="info" %}
Short lists require a **list as their data source**, unlike most other containers, which work with a single item.
{% endhint %}

## When to use a short list

Short lists are ideal for small, predictable collections that fit naturally within a view.

For larger lists or lists that grow unpredictably, use a [vertical list](/help-guides/design/elements/ios-and-android-app/the-view#view-types) instead. Vertical lists load data progressively as the user scrolls, which keeps performance smooth even with a lot  of items.

<table><thead><tr><th width="182.3046875">Feature</th><th>Short list</th><th>Vertical list</th></tr></thead><tbody><tr><td>Data loading</td><td>Loads all data at once</td><td>Lazy-loads as the user scrolls</td></tr><tr><td>Best for</td><td>Small to medium lists</td><td>Long or unpredictable lists</td></tr><tr><td>Multiple per view</td><td>Yes</td><td>Only one per view</td></tr><tr><td>Scroll behavior</td><td>Scrolls with the surrounding view</td><td>Scrolls independently within the view</td></tr></tbody></table>

The rule of thumb: if the list is short and predictable, a short list is often the better fit. If the list can grow long or is the main focus of the view, use a vertical list.

## Loading data into a short list

There are two main ways to load data into a short list:

### Set the data source directly

The list loads as soon as the view opens, using an expression like `Do a search for Posts`. This is useful when the data is known in advance.

<figure><img src="/files/GUy3oQHfrHfqpweo5hVR" alt="Setting the data source of a short list."><figcaption><p>Filling in the short list's <em>Type of content</em> and <em>Data source</em> loads the data immediately.</p></figcaption></figure>

### Push data with a workflow

Use the *Display list in a short list or selectable list* action to load a list in response to a user action. This is useful when the data depends on something the user does.

<figure><img src="/files/1w1HBlchJHHw6LpWq4Vz" alt="Pushing data to a short list using an action."><figcaption></figcaption></figure>

Both methods behave the same way once the list is loaded.

## Referencing data in an item

Because each item represents a different record in the list, referencing data inside an item uses the *Current item* expression. If the *Type of content* is *Post*, you'd reference `Current item's Post's title` to show the post's title.

<figure><img src="/files/6qlK61mZgBR3BuUjFuQ2" alt="Referencing the item in a short list."><figcaption><p>Using <code>Current item's Post's title</code>, you can display the title of each post in the list.</p></figcaption></figure>

One text element inside the short list displays all the names in the list, one per item.

## Styling short lists

Short lists can be styled like any other element. You can style individual short lists by adjusting their different properties, or by connecting them to predefined styles[^1] and/or [style variables](#user-content-fn-2)[^2].

<figure><img src="/files/bTX9OLDhLPwvzb3fbaUs" alt="The styling properties of a mobile floating group."><figcaption><p>You can style individual short lists by adjusting their different properties, or by connecting them to predefined styles and/or style variables.</p></figcaption></figure>

## Performance considerations

Because short lists load all data at once, they're best kept short. A few things to keep in mind:

* **Fewer than a dozen items is ideal.** Short lists shine when they display a small, predictable set of records.
* **Item complexity matters.** Items with many elements take longer to render, so keeping the item design simple helps performance.
* **Watch out for nested lists.** Placing a short list inside another list runs the data query for every parent item, which can compound quickly.

If the list starts to grow beyond what a short list can comfortably handle, [switch to a vertical](#when-to-use-a-short-list) list to take advantage of lazy loading.

## FAQ: Short lists

<details>

<summary>How is a short list different from a vertical list?</summary>

A short list loads all its data at once and is designed for small, fixed lists. A vertical list lazy-loads data as the user scrolls, making it better for larger collections. You can also have multiple short lists in a single view, but only one vertical list.

</details>

<details>

<summary>How many items can a short list display?</summary>

There's no strict limit, but short lists work best with small collections. If the list grows beyond a dozen or so items, or if the data comes from a search that can grow over time, use a vertical list instead.

</details>

<details>

<summary>Can I have multiple short lists in the same view?</summary>

Yes. This is one of the main advantages of a short list. You can stack multiple short lists in a single view.

</details>

<details>

<summary>Can I sort or filter items in a short list?</summary>

Yes. When using `Do a search for` as the data source, you can add constraints and sorting rules directly in the search. You can also chain operators like `:sorted` by or `:filtered` to control the order and contents of the list.

</details>

<details>

<summary>How do I reference the record in an item?</summary>

Use the `Current cell` expression. For example, if the Type of content is User, `Current cell's User's name` references the name of the user in that specific cell.

</details>

<details>

<summary>Can I make the cells tappable?</summary>

Yes. Add a workflow triggered by a tap on any element inside the cell, or on the cell's parent group. Inside the workflow, reference Current cell's User (or whatever type of content the list uses) to act on that specific item.

</details>

<details>

<summary>What happens if the list is empty?</summary>

The short list renders no cells and takes up no space. You can use conditions to show a placeholder message or graphic when the list is empty.

</details>

<details>

<summary>Can I display a list without loading it from the database?</summary>

Yes. The data source can be any expression that returns a list, such as a list stored in an option set, a list returned by an API call, or a list constructed with `:merged with`. You aren't limited to database searches.

</details>

<details>

<summary>How do I find the position of an item in the list?</summary>

Use `Current item's index` to reference the position of the item within the short list. Bubble uses 1-indexing, so the first cell is index 1.

</details>

<details>

<summary>Are short lists available in web apps?</summary>

No. Short lists are mobile-only. On web, use [repeating groups](/help-guides/design/elements/web-app/containers/repeating-groups) instead.

</details>

[^1]: Styles are reusable sets of visual properties, like colors, fonts, and borders, that can be applied to elements across your app. Updating a style automatically updates every element that uses it, keeping your design consistent.

    **Article:** [Styles](/help-guides/design/variables-and-styles/styles)

[^2]: Style variables let you save individual colors and fonts, and reference them anywhere in your app. Updating a variable updates every style and element that uses it, making it easy to change your app's look in one place.

    **Article:** [Style variables](/help-guides/design/variables-and-styles)


# Horizontal list element (mobile)

A horizontal list is a container used to display a list of items in a scrollable, side-to-side layout. Like other list types on mobile, it works by repeating the same set of elements once for each item in the data source. Unlike vertical or short lists, horizontal lists scroll left and right, making them well suited for content that benefits from being browsed sideways.

## What horizontal lists do

Horizontal lists take a list of things and repeat their child elements once for each item, laying them out in a row that the user can scroll through. If you show a list of ten products, everything you add to the horizontal list is duplicated ten times, once per product, and arranged side by side.

Each of these repetitions is called an **item**. An item holds all the child elements and represents a single record from the list. Text elements can display data from that item, buttons can trigger actions on it, and containers can be styled to give each item its own look.

Horizontal lists work similarly to other lists on mobile: you set a *Type of content*, provide a *Data source*, and the elements inside can reference the data.

{% hint style="info" %}
Horizontal lists require a **list as their data source**, unlike most other containers, which work with a single item.
{% endhint %}

## When to use a horizontal list

Horizontal lists shine when the content works better as a sideways scroll than a vertical stack. Common uses include:

* **Category carousels** at the top of a screen, letting users tap between sections.
* **Featured content rows**, like recommended articles, videos, or products.
* **Media galleries**, such as scrollable image or video thumbnails.
* **Story previews**, similar to the horizontal story rows in social apps.
* **Chip or tag lists**, where users can scroll through options that don't fit on a single line.

Horizontal lists are also often used inside vertical lists, letting each row of a vertical layout contain its own sideways-scrolling collection.

## Placement rules

Horizontal lists can't be placed directly on a *list view*. They can only be used in:

* A non-list view.
* Inside another list on a list view, such as nested within a vertical list.

This means horizontal lists are typically added as one component within a larger layout, rather than being the primary structure of a view.

## Loading data into a horizontal list

To load data into the horizontal list, use a dynamic expression in its data source. The list loads as soon as the view opens, using an expression like `Do a search for Posts`.&#x20;

<figure><img src="/files/gUtcR039WvEpKY1OXSM7" alt="Setting the data source on a horizontal list."><figcaption><p>To load data into a horizontal list, use the <em>Type of content</em> and <em>Data source</em> properties.</p></figcaption></figure>

Horizontal lists can't be loaded through a workflow action, but you can use a conditional to change the data source based on specific circumstances.

## Referencing data in an item

Each item represents a different record in the list. Reference data inside an item using the `Current item` expression. If the *Type of content* is *Post*, you'd reference `Current item's Post's name` to show the product's name.

One text element inside the horizontal list then displays all the names in the list, one per item.

## Spacing between items

Horizontal lists include built-in properties for controlling the spacing between items:

* **List item gap spacing** turns spacing on or off.
* **Item gap (px)** sets the exact spacing in pixels.

These controls make it easy to keep items visually separated without adding extra margins or padding to individual items.

## Snapping

Snapping controls how the list settles when the user stops scrolling. Instead of free-scrolling and stopping wherever momentum runs out, a snapping list comes to rest at a defined point, either on an individual item or on a full page of content. Bubble reads your layout settings, including item size, padding, and gaps, and applies the correct native snapping behavior automatically.

For more info, see the core reference entry below.

**Core reference:** [The snapping property](/core-resources/bubble-elements/element-properties/native-mobile-element-properties/container-properties-mobile/horizontal-list-element-mobile)

## Styling horizontal lists

Horizontal lists can be styled like any other container. This controls the styling of the list itself, while individual elements inside each item can be styled independently.

You can style individual horizontal lists by adjusting their different properties, or by connecting them to predefined styles[^1] and/or [style variables](#user-content-fn-2)[^2].

### Performance considerations

Because horizontal lists load their items upfront, they're best suited to small or medium collections. A few things to keep in mind:

* **Long lists can slow down rendering.** For very long collections, limit the data source with search constraints or pagination.
* **Media-heavy items add up.** Loading many high-resolution images at once can affect performance and data usage. Consider using optimized image sizes.
* **Nested lists compound cost.** Each parent list runs its own query for the nested list, so nesting should be used deliberately.

### Frequently asked questions

<details>

<summary>How is a horizontal list different from a vertical list?</summary>

The main difference is the scroll direction. Horizontal lists scroll sideways, making them well suited to carousels, category rows, and media galleries. Vertical lists scroll top to bottom and are typically used for feeds and long collections.

</details>

<details>

<summary>Can I place a horizontal list directly on a list view?</summary>

No. Horizontal lists can only be placed on non-list views, or nested inside another list on a list view. For example, a horizontal list can sit inside an item of a vertical list.

</details>

<details>

<summary>How many items can a horizontal list display?</summary>

There's no strict limit, but horizontal lists load their items upfront, so they perform best with smaller collections. For long lists, apply constraints to the search or paginate the results.

</details>

<details>

<summary>How do I reference the item in an item?</summary>

Use the `Current item` expression. For example, if the *Type of content* is Post, `Current item's Post's title` references the title of the post in that specific item.

</details>

<details>

<summary>Can I make items tappable?</summary>

Yes. Add a workflow triggered by a tap on any element inside the item, or on the item itself. Inside the workflow, reference `Current item's Post` (or whatever type of content the list uses) to act on that specific item.

</details>

<details>

<summary>Can I nest a horizontal list inside another list?</summary>

Yes. Nesting a horizontal list inside a vertical list is a common pattern for building content-rich screens with themed rows. Just keep an eye on performance, since each parent item runs its own data query for the nested list.

Also be aware that the size of the smallest mobile screens can make interaction difficult if you have lists within lists.

</details>

<details>

<summary>Can I sort or filter items in a horizontal list?</summary>

Yes. When using `Do a search for` as the data source, you can add constraints and sorting rules directly in the search. You can also chain operators like `:sorted` by or `:filtered` to control the order and contents of the list.

</details>

<details>

<summary>Can I control the spacing between items?</summary>

Yes. Enable [List item gap spacing](#spacing-between-items) on the list and set the Item gap (px) to define the exact spacing between items.

</details>

<details>

<summary>Can I use swipe actions on horizontal list items?</summary>

No. Swipe actions are available on vertical and section lists only, since sideways scrolling in a horizontal list conflicts with the swipe gesture.

</details>

<details>

<summary>Are horizontal lists available in web apps?</summary>

No. Horizontal lists are mobile-only. On web, use [repeating groups](/help-guides/design/elements/web-app/containers/repeating-groups) with horizontal scrolling instead.

</details>

[^1]: Styles are reusable sets of visual properties, like colors, fonts, and borders, that can be applied to elements across your app. Updating a style automatically updates every element that uses it, keeping your design consistent.

    **Article:** [Styles](/help-guides/design/variables-and-styles/styles)

[^2]: Style variables let you save individual colors and fonts, and reference them anywhere in your app. Updating a variable updates every style and element that uses it, making it easy to change your app's look in one place.

    **Article:** [Style variables](/help-guides/design/variables-and-styles)


# Sheet (mobile)

A sheet is a container that slides up from the bottom of a native mobile view, sitting above the current content while keeping it partially visible. Sheets are used for focused interactions that don't require leaving the current screen, such as filters, forms, or contextual details.

<figure><img src="/files/k5J8wNinJxojQOoFsvab" alt="Example of a sheet element in a mobile app."><figcaption><p>Sheets slide up from the bottom, keeping the view partially visible.</p></figcaption></figure>

Sheets are similar to modals, but with an important difference: sheets are best when the context of the view behind them is still relevant. A map app displaying search results in a sheet is a classic example: users can scroll through results while continuing to see the map underneath.

Sheets are added directly to a view, functioning much like popups on the web.

## Common uses

Sheets work well for:

* **Search results** overlaid on top of a map or other visual content.
* **Filter and sort options** that adjust what's shown in the underlying view.
* **Detail views** that expand contextually without switching screens.
* **Quick forms** like adding a note, sending a message, or confirming a small action.
* **Bottom navigation menus** with expanded options.

## Sheets vs. modals

Sheets and modals both provide focused, temporary experiences on top of a view, but they serve different purposes:

<table><thead><tr><th width="169.640625">Feature</th><th>Sheet</th><th>Modal</th></tr></thead><tbody><tr><td>Position</td><td>Slides up from the bottom</td><td>Presented as a full-screen overlay</td></tr><tr><td>Underlying view</td><td>Often partially visible</td><td>Typically fully hidden</td></tr><tr><td>Best for</td><td>Contextual actions where the background stays relevant</td><td>Fully focused tasks with no distraction</td></tr><tr><td>Dismissal</td><td>Swipe down or button</td><td>Swipe down or close button</td></tr><tr><td>Snap points</td><td>Multiple sizes possible</td><td>Full-screen only</td></tr></tbody></table>

Use a sheet when the user benefits from seeing the underlying view. Use a modal when the task requires the user's full focus.

## How sheets behave

Sheets sit above the view content in their own layer. They can partially or fully cover the screen, depending on the size the user drags them to. Because the underlying view often stays visible, sheets are a natural way to show additional information without breaking the user's focus.

<figure><img src="/files/yUPqdXFG2eqocnw7Jrl3" alt="" width="285"><figcaption></figcaption></figure>

Sheets can hold any elements a regular group can, including forms, images, buttons, and other containers.

### View types

The *View type* property determines how the sheet handles scrolling and content layout. By default, the sheet behaves like a group, acting as a container where you can add and manage elements freely.

When you set the view type to *Vertical list* or *Section list*, a pseudo child element is added to the element tree containing that list. Each list has its own data source, independent of the sheet's data source, letting you combine a single-record sheet with a list of items in the same layout.&#x20;

<table><thead><tr><th width="140.859375">View type</th><th>Description</th></tr></thead><tbody><tr><td>Scrollable</td><td>The default option. The sheet's content scrolls vertically when it extends beyond the visible area. Best for sheets with mixed content, such as forms, detail views, or long-form information.</td></tr><tr><td>Not scrollable</td><td>The sheet's content is fixed to the size of the sheet and doesn't scroll.</td></tr><tr><td>Vertical list</td><td>Adds a vertical list to the sheet, letting it display a scrollable list of items. This is useful when the sheet's primary purpose is to show a collection, like a list of options, contacts, or search results.</td></tr><tr><td>Section list</td><td>Adds a section list to the sheet, grouping items into named sections. Ideal for sheets that display categorized content, such as organized by category or   by date.</td></tr></tbody></table>

## Loading data into a sheet

Sheets can hold a data source, just like a regular group. This makes it easy to open a sheet with a specific piece of data loaded, such as showing details for the item a user just tapped.

Sheets, like views, support horizontal lists and section lists. Enabling either through the *View type* property adds it as a pseudo child element with its own separate list data source.

The sheet itself keeps its original single-record data source, which can still be referenced by other child elements. This lets you customize the horizontal or section list's data source to reflect the sheet's parent data, connecting the two without giving up either one.

### **Setting the data source directly**

Set the sheet's *Type of content* and *Data source* directly. The sheet loads its data as soon as it's shown, based on an expression like `Current User`.

<figure><img src="/files/oMXmc4b0eJaSVtkSDl2F" alt=""><figcaption></figcaption></figure>

### Pushing data with a workflow

Use the *Display data in a group* action to load data in response to a user action, such as tapping an item in a list.

<figure><img src="/files/7D98gyKuTmrggN5LAw3P" alt="Pushing data to a sheet using a workflow action."><figcaption><p>You can push data to a sheet as a result of user action, by using the <em>Display data in group</em> action.</p></figcaption></figure>

## Showing and hiding sheets

Sheets are opened with the [*Show element*](/core-resources/actions/element#show-an-element) action. Bubble applies a native slide-up animation automatically, giving the sheet the familiar mobile feel.

To close a sheet, either use the [*Hide an element*](/core-resources/actions/element#hide-an-element) action or let the user dismiss it by swiping down (when *Swipe to close* is enabled).

## Snap points

Snap points define the sizes a sheet can settle at. When you set snap points, users can drag the sheet up or down to switch between them, and the sheet snaps to the nearest one when released.

Each sheet has a default snap point that determines the size it opens to. Snap points are especially useful for sheets with a lot of content, or for scenarios where users may want to move the sheet out of the way to interact with what's behind it.

## Controlling the backdrop

Sheets support several properties for controlling how the background of the view appears when the sheet is open. These are UI settings only and shouldn't be relied on for security.

#### **Block interaction behind sheet**

When enabled, users can't interact with the content behind the sheet. This is similar to the behavior of a popup on the web, and is useful when the sheet requires the user's full attention.

#### **Backdrop color**

Applies a solid color over the underlying view when the sheet is open, drawing focus to the sheet by dimming or tinting the background.

#### **Backdrop blur**

Applies a blurred overlay over the underlying view when the sheet is open, softening the background without hiding it entirely.

## Interaction options

Sheets include a few properties for controlling how users can interact with them:

#### **Swipe to close**

When enabled, users can swipe the sheet downward to close it. Disabling this is useful when the sheet should only be dismissed by a specific button, such as a *Cancel* or *Save* action.

#### **Drag handle**

Shows a small horizontal bar at the top of the sheet, giving users a visual cue that the sheet can be dragged.

## Styling sheets

Sheets can be styled like any other container. The top edge of a sheet often uses rounded corners to create the familiar "card sliding up" appearance common in mobile apps.

<figure><img src="/files/oApoPGeCOIjd9i2NwR4P" alt="Sheet element styling properties."><figcaption><p>You can style individual sheets by adjusting their different properties, or by connecting them to predefined styles and/or style variables.</p></figcaption></figure>

You can style individual horizontal lists by adjusting their different properties, or by connecting them to predefined styles[^1] and/or [style variables](#user-content-fn-2)[^2].

## Sheets and security

Sheets are a visual layer, not a security boundary. The elements behind a sheet are still present in the app's client-side code, and blocking interaction or blurring the background is a UI effect only.

Never rely on a sheet to hide sensitive information. Use [privacy rules](#user-content-fn-3)[^3] in the database and server-side conditions to control security.

## FAQ: Sheets

<details>

<summary>How is a sheet different from a modal?</summary>

Sheets slide up from the bottom and often leave the underlying view partially visible, keeping context in view. Modals take over the full screen and are better for fully focused tasks where the underlying view isn't relevant.

</details>

<details>

<summary>How is a sheet different from a popup on the web?</summary>

Sheets are the native mobile equivalent of popups. They behave similarly, but with native mobile behaviors like swipe-to-close, snap points, and the familiar slide-up animation.

</details>

<details>

<summary>Can users close a sheet by swiping?</summary>

Yes, if *Swipe to close* is enabled. Disable it when you want to control its visibility using workflows.

</details>

<details>

<summary>Can I have multiple snap points on a sheet?</summary>

Yes. Define multiple snap points to let users drag the sheet between different sizes. The sheet snaps to the nearest one when released.

</details>

<details>

<summary>Can I load data into a sheet?</summary>

Yes. Set the *Type of content* and *Data source* on the sheet. You cannot load data into sheets using a workflow action.

You can change the data source based on specific criteria by using conditionals[^4].

</details>

<details>

<summary>Can I have more than one sheet on a view?</summary>

Yes. You can add multiple sheets to a view and open the one that fits the current context. Only one sheet should be visible at a time to avoid overlapping.

</details>

<details>

<summary>Can I animate a sheet as it opens?</summary>

Sheets use a native slide-up animation automatically.

</details>

<details>

<summary>Can I block interaction with the view behind the sheet?</summary>

Yes. Enable *Block interaction behind sheet* to prevent users from tapping or scrolling the content underneath.

</details>

<details>

<summary>Can I dim or blur the view behind the sheet?</summary>

Yes. Use Backdrop color to apply a tint or Backdrop blur to apply a blurred overlay. These can be used separately or combined.

</details>

<details>

<summary>Are sheets available in web apps?</summary>

No. Sheets are mobile-only. On web, use [popups](/help-guides/design/elements/web-app/containers/popups) or [floating groups](/help-guides/design/elements/web-app/containers/floating-groups) instead.

</details>

[^1]: Styles are reusable sets of visual properties, like colors, fonts, and borders, that can be applied to elements across your app. Updating a style automatically updates every element that uses it, keeping your design consistent.

    **Article:** [Styles](/help-guides/design/variables-and-styles/styles)

[^2]: Style variables let you save individual colors and fonts, and reference them anywhere in your app. Updating a variable updates every style and element that uses it, making it easy to change your app's look in one place.

    **Article:** [Style variables](/help-guides/design/variables-and-styles)

[^3]: Privacy rules control who can see and modify data in your database. They're evaluated on the server, making them the primary way to keep sensitive data secure and to manage access at the field level.

    **Article:** [Privacy rules](/help-guides/data/the-database/protecting-data-with-privacy-rules)

[^4]: Conditionals let you change how an element looks or behaves when specific criteria are met. Each conditional is built as a rule that returns yes or no, and lets you adjust properties like the data source.

    **Article:** [Conditionals](/help-guides/logic/conditions)


# Visual elements (mobile)

## Text

A non-editable text element.

## Button

A button is a shape with a text/icon that's optimized for click workflows. It can contain a text, an icon or both.

## Icon

Icon elements let you pick an icon to display from several different [icon collections](#user-content-fn-1)[^1].

{% hint style="warning" %}
On Android devices, **phosphor “filled” icons may display as black boxes** instead of the intended icon. This issue is due to a bug in a third-party library required for rendering these icons.

At this time, there is no workaround. The issue will remain in place until it is resolved by the library author. If your app relies on filled phosphor icons, consider using an alternative icon style for Android builds.
{% endhint %}

## Image

Image elements allow you to place images in your app. The element supports all widely used image types such as png, jpg, svg, webp and gif.

#### Generating an image with AI

You can generate images with AI. Type in a prompt and select a size, quality, and style. The *Auto* setting lets the AI choose style setting based on your prompt. The number of tokens used depends on your selections.

Check *Generate Alt tags* to automatically generate an [Alt text](#user-content-fn-2)[^2].

## Shape

The shape element adds a rectangle to the view which can be resized, rounded and styled in different ways.

## Map

The Map element lets you implement a Google Map on the view, fully navigatable by your users. You can also use Apple Maps on iOS devices by enabling the *Use Apple Maps on iOS* property.

## WebView

The Web View element allows you to display one of your app’s pages inside an element within your mobile app. You can set its responsive behavior just like any other element, ensuring it adapts well to different screen sizes.

<figure><img src="/files/6dlqEBhN8CobHZsM2okh" alt=""><figcaption></figcaption></figure>

Keep in mind that users won’t be able to navigate to other pages while using the Web View, so it’s important to design the page with all necessary content on a single page. The Web View element is limited to displaying pages from your own app, meaning external URLs cannot be used.

{% hint style="warning" %}
The Web View element currently does not work in the web preview— it only functions properly in the BubbleGo app.
{% endhint %}

#### Disable zoom

The *Enable Zooming* property allows users to use the pinch-to-zoom gesture to zoom in or out. This setting is disabled by default.

<figure><img src="/files/RRJ55niWbUV6PsgwRArF" alt=""><figcaption></figcaption></figure>

It helps improve the user experience, particularly for apps that rely heavily on webviews, where unintended zooming can cause a disruptive interface. If you want to allow zooming, you can disable this setting in the element's properties.

## Sign up with Apple button

{% hint style="info" %}
**Note**: The Apple Sign-In Button has fewer styling options than other Bubble elements. This is by design: Apple's Human Interface Guidelines specify how the button may appear, and App Review checks it. Bubble exposes the options Apple allows and locks the rest, so a button you can place in the editor is one that passes review. You can read more about this in the article linked below.
{% endhint %}

The *Sign up with Apple* button is a pre-built element that triggers Apple's sign-in flow when tapped. It handles the native experience on iOS and the browser-based flow on Android and web, and can be styled to match your app's design.

Before using this element, follow the setup steps in the main article to configure your app on the Apple side.

**Article**: [Setting up Sign in with Apple](/help-guides/data/user-accounts/sign-in-with-apple)

[^1]: Icons are contained within libraries, similar to all the letters in a specific font.

    For a list of the natively supported icon libraries in Bubble, see the icon element.

    Reference: [Icon element](https://manual.bubble.io/core-resources/elements/visual-elements#icon)

[^2]: "Alt text" is short for **alternative text**. It's a written description of an image used by screen readers for visually impaired users, displayed when an image fails to load, and read by search engines to understand what an image depicts.


# Input forms (mobile)

**Input forms** are how you collect information from your users. They range from simple text fields and toggles to more complex controls like date pickers, dropdowns, and selectable lists.

Bubble includes a range of built-in input types for common needs, letting you build everything from a quick signup form to a detailed multi-step questionnaire. Each input type is designed to feel native to the platform, matching the interactions users expect.

## Input

Input elements are used for single-line plaintext, but they allow you to specify a content format from a wide range of different options. The *Field type returned* in the list below specifies what kind of database field format the input will return.

<details>

<summary>How Bubble stores text formatting</summary>

Bubble uses a markup language called BBCode to store text formatting such as bold, italitcs, bullet points etc. The BBCode is generated and applied automatically as you add formatting to your text.

BBCode is simpler and safer to use than HTML and because it has more limited capabilities, and only allows for a select number of tags. This stops your users from being able to upload any malicious code such as CSS or Javascript that might break the app.

External link: [Guide to BBCode](https://www.bbcode.org/how-to-use-bbcode-a-complete-guide.php)\
External link: [BBCode tags reference](https://www.bbcode.org/reference.php)

### I want to use HTML instead

Sometimes it's useful to be able to store formatted text as HTML, such as when you want to pass formatted text to an email sending service that only supports this format.

None of the built-in Bubble text editors supports HTML formatting, but there are multiple [plugins](https://bubble.io/plugins) that allow you to store text with formatting as HTML instead of BBCode. Searching the plugin store for *Rich Text Editor* is likely to give you some results that support this.

Regular text elements don't support HTML either: if you want to display text formatted with HTML you can use the built-in HTML element.

</details>

{% hint style="info" %}
Because of the predictive text functionality on **Android devices**, some content formats involving masks have a slightly different UX. This applies to US Phone, Percentage, Currency, Date, Euro date, and Text (numbers only).

On Android devices, the input allows users to type anything and validates the content against the mask only after the user stops typing.

This does not affect the final data generated by the input.
{% endhint %}

### **Text**

The input will accept any text value.

**Field type returned:** text

<figure><img src="/files/a1Q0jz2bcbAdu5fpdfYl" alt="Input element with no formatting."><figcaption></figcaption></figure>

### Email

The input will only accept text values that are formatted like an email address.

**Field type returned:** text

<figure><img src="/files/GriLSgjCJpgr8sTV8MZX" alt="Input element with email formatting."><figcaption></figcaption></figure>

### Password

The input will accept any kind of text value, but the characters written will be replaced by asterisks.

**Field type returned**: text

<figure><img src="/files/vBj6C4qHCMng4Nf23vos" alt="Input element with password formatting, showing only asterisks."><figcaption></figcaption></figure>

### Integer

The input will only accept any whole number. Use this when you need to collect numerical values that can later be used for calculations (as opposed to numbers saved as text). You can specify a dynamic minimum and maximum value when this option is selected.

**Field type returned:** number

<figure><img src="/files/ZYMD2yXbuEoubawLm67g" alt="Input element with number (integer) formatting."><figcaption></figcaption></figure>

### Decimal

The input will accept any whole number or decimal number. You can specify a minimum and maximum value, as well as the number of decimals to include. You can also set the input to automatically include thousands separators as the user types. This setting is only aesthetic and does not affect the final value.

**Field type returned:** number

<figure><img src="/files/BnP7kHBYfhYrI7PaYuY2" alt="Input element with decimal formatting."><figcaption></figcaption></figure>

### Address

A geographic address. With this option, the input automatically validates the address with Google Maps. Note that the input does not automatically search for valid addresses as the user types, so the user has to provide an already valid address (by copy/pasting directly from Google Maps for example). If you want the user to be able to see Google Maps search results as they type you may want to look into using the Search box element instead.

**Field type returned:** address

<figure><img src="/files/9cnwKVNAwGQOK59WRIfF" alt="Input element with address formatting."><figcaption></figcaption></figure>

### US phone

This input will accept correctly formatted US phone numbers. The value is saved as a text.

<figure><img src="/files/uS8qFmnoEJqpXOtFXC4M" alt="Input element with US phone number formatting."><figcaption></figcaption></figure>

**Field type returned:** text

### Percentage

This input will format the user's input as a percentage and accept input such as 20%. Bubble will automatically save it in the correct numerical equivalent of the provided percentage point. For example, 20% will be saved as the number 0.2 and 150% will be saved as 1.5.

<figure><img src="/files/YOiLpe8N9roHvSowr7iZ" alt="Input element with percentage formatting."><figcaption></figcaption></figure>

**Field type returned:** number

### Currency

The input will accept any whole number or decimal number and it will automatically format the input in the chosen currency, such as *$20*. You can specify a minimum and maximum value, as well as the number of decimals to include. You can also set the input to automatically include thousands separators as the user types. The currency and thousands separators settings are only aesthetic and do not affect the final value.

<figure><img src="/files/uk1MRzt7V4kYMZUT9FrV" alt="Input element with currency formatting."><figcaption></figcaption></figure>

**Field type returned:** number

### Date

The input will accept any date, but does not include time. It automatically formats the input's value as mm/dd/yyyy while the user types, guiding the user to provide the date in the right way. You can specify a given time zone for the input and Bubble will automatically calculate any offset from the user's current time zone. If no time zone is provided, the user's current time zone will be used. Dates are saved at 12 am (00:00) on the given date.

<figure><img src="/files/EWZcrPENMU6p6PQif6PP" alt="Input element with date formatting."><figcaption></figcaption></figure>

Field type returned: date

### Euro date

The input will accept any date, but does not include time. It automatically formats the input's value as dd/mm/yyyy while the user types, guiding the user to provide the date in the right way. You can specify a given time zone for the input and Bubble will automatically calculate any offset from the user's current time zone. If no time zone is provided, the user's current time zone will be used. Dates are saved at 12 am (00:00) on the given date. The difference between this content format and the *Date* format is purely visual.

<figure><img src="/files/xkrI80oOgVOP2jBF6YsR" alt="Input element with European date formatting."><figcaption></figcaption></figure>

**Field type returned:** date

### Text (numbers only)

This input will only accept whole numbers and the result will be saved as text. This is useful when you want to save numerical values that need to remain exactly as the user typed (without losing leading zero's for example) and will not be used for any type of calculation, such as international phone numbers, zip codes and order numbers.

<figure><img src="/files/ZYMD2yXbuEoubawLm67g" alt="Input element with numbers as text formatting."><figcaption></figcaption></figure>

**Field type returned:** text

<details>

<summary>Storing numbers as text versus storing them as numbers</summary>

### What is the difference between the two?

The Bubble database offers two different field types that can both be used to save numerical values: text and number.

Why would you ever want to save a number as a text string? There are a few scenarios where it makes sense to do so:

#### **When you need to include leading zero's**

In an integer (or number) a leading zero represents no value and is disregarded when saved to the database. After all 045 and 45 are the same thing from a mathematical point of view and the zero only adds confusion and increases the total size of the database.

Sometimes leading zero's are necessary: for example a phone number or zip code might start with a zero and they must be stored as text to preserve the complete value.

#### When you need to pass the data as text

Sometimes when you communicate with an external system through an API, that system may expect a numerical value to be passed as text. In that case it can be useful to store it as text in the database so as to avoid having to convert it later.

###

### When to use what

**Text** should be used when you are working with numerical values that will remain static and will not be included in any kinds of calculations. For example:

* Phone numbers
* Zip codes
* Postal codes
* Product/Part numbers

**Numbers** should be used when you are working with values that may need to be calculated or aggregated, such as:

* Year of birth
* Age
* Counts and totals (such as the item cart and total value of a cart)
* Financial numbers
* Enumerated values, such as the values used to represent the days of the week (e.g. Sunday = 0, Monday = 1, etc.)

</details>

## Multiline input

As the name suggests, the Multiline input element allows you to set up a text input element that allows for multiple lines of text. Like the regular text input, this element supports plaintext only, and does not provide any custom formatting.

<figure><img src="/files/PrgSppsIEgZXv80Ezl8O" alt="Multiline input element"><figcaption></figcaption></figure>

Use the Multiline input element when you need the user to be able to supply multiple lines of text but you don't want them to be able to add formatting such as bold, italic and headers.

**Field type returned:** text

## Date/TimePicker

The Date/TimePicker is a native mobile element specifically created for the native mobile app editor, offering an alternative to the traditional date/time picker used in the web app editor.

It automatically adapts to the device's system, displaying the iOS picker on Apple devices and the Android picker on Android devices. This behavior is enabled by default, requiring no additional configuration from your side.

{% hint style="warning" %}
The Date/TimePicker is using the native component on the device, and **will not render properly on web preview**.

Please use BubbleGo to test any flows involving the Date/TimePicker.
{% endhint %}

### Appearance

The date/time picker’s properties control two key parts:

* The [**element**](#element-properties) placed in the view.
* The [**native date/time picker** ](#native-date-time-picker-properties)which appears when the user clicks the element.

### Element properties

* **Picker style**: Choose between:
  * Label/Icon: Show both a label and a customizable icon.
  * Label: Show just a label.
* **Icon placement**: Choose to display the icon to the left (leading) or right (trailing) of the label.
* **Icon size:** Set the size of the icon in pixels.
* **Icon color:** Set the color of the icon.

### Native date/time picker properties

* **Input type:**
  * Dat&#x65;**:** Displays the date picker only. The date value will be stored in the database as the selected date + current time.
  * Tim&#x65;**:** Displays the time picker only. The time value will be stored in the database as the current date + selected time.\
    \
    To combine the date from one picker with the time from another picker, you can use the `change time to` operator.<br>
* **Accent color**
  * Color picker: Sets the color of a few properties in the calendar picker. See the screenshot below.
* **Minimum/Maximum date:** sets the minimum and maximum datetime that can be picked in the date/time picker.

## Selectable lists

A *selectable list* functions as an input element that allows users to *select one or multiple options* from a list. It is particularly useful for building filters, segmented controls, and selection forms without requiring additional custom states or workflows.

<figure><img src="/files/gLW7PESgdvMOxQpElrED" alt="Selectable list element showing a list of blog posts."><figcaption><p>Selectable lists allow users to select one or multiple options from a list.</p></figcaption></figure>

It can function in *single select* or *multi-select* mode, with additional customization options for selection limits and workflow integration. Below are the key behaviors of the selectable list.

### Single-select and multi-select behavior

Selectable lists can be set to single-select or multi-select mode, depending on how you want users to interact with them.

#### **Single select behavior**

In single-select mode, only one option can be active at a time. Selecting a new item automatically replaces the previous selection, similar to how radio buttons work.

Users can also clear the current selection by tapping the selected item again, returning the list to an unselected state. This makes it easy for users to change their mind or start over without needing a separate reset action.

#### **Multi-select behavior**

In multi-select mode, users can choose more than one option from the list.

By default, there's no cap on how many items can be selected. If you want to limit the selection, you can set a maximum number of choices. Once the user reaches that limit, the remaining options are automatically disabled, and the user has to deselect an item before selecting a new one.

### **Validation and required lists**

Selectable lists can be marked as required, similar to how you'd mark an [input field](#input) as required. This helps ensure the user makes a valid selection before their data is saved.

The way validation appears follows the same pattern as required inputs:

* **Conditional styling for missing selections activates when the values are being saved.** For example, if the user tries to submit a form without making a selection, the list can visibly indicate that a selection is missing, such as by highlighting a border or showing an error message.
* **The required styling clears automatically once the selection meets the requirement.** In single-select mode, this happens as soon as an item is selected. In multi-select mode with a range restriction, the styling clears once the required number of items has been selected.

### **Performance considerations**

* The selectable list *loads all items immediately*, meaning it does not use lazy loading or virtualization like the [*vertical list*](#vertical-list).
* Large data sets may impact performance, so it is important to limit the number of options when possible.
* This element is *not a replacement for vertical lists* and should not be used when handling extensive lists of items.

### Formatting selected items

The selectable list element has an `is selected` and `isn't selected` data source, that can be used in conditionals to apply formatting to selected items.

<figure><img src="/files/p6NDltoi6xGkomTV2K9X" alt="Conditional formatting on a selected list item in a selectable list element on mobile."><figcaption></figcaption></figure>

The conditional must be set on the *SelectableListItem* pseudo child element, and not on the Selectable list element itself.

### **Workflows and actions**

Workflows can be triggered when a user interacts with a list item. When adding a workflow from the list item property editor, the "When element is clicked" workflow event is automatically pre-filled.

The following actions are available for selectable lists:

* **Set selectable list value:** Set a selected value for the selectable list (single item or list, depending on the [selection type](#single-select-behavior)).
* **Select all:** Selects all the available entries in the list.
* **Unselect all:** Deselects all selected entries.
* **Display list:** Set a list of entries for the selectable list, formatted as a list.
* **Clear list:** Removes all entries from the list.
* **Scroll to entry:** Scrolls the list to specific record.

### Properties

The following properties are available on selectable lists:

* 's list of items (if multi)
* 's selected item (if single)
* 's list of selected items (if multi)
* 's list of unselected items (if multi)
* 's minimum number of selections (if range is selected)
* 's maximum number of selections (if range is selected)
* 's number of allowed selections (if exact is selected)
* Width
* Height
* isn’t valid
* is disabled

#### Conditional properties

The following conditional properties are available on selectable lists:

* **Minimum number of selections** (`number`)**:** the minimum number of selectable entries, as specified in the minimum number of selection property
* **Maximum number of selections** (`number`)**:** the maximum number of selectable entries, as specified in the maximum number of selection property
* **Number of selections** (`number`)**:** The currently number of selected entries

## Checkbox

The Checkbox element provides an intuitive way to manage selection states without needing complex workflows or custom states.

Here are two common use cases for the Checkbox element:

#### **Filtering to-dos**

The Checkbox element can be used to filter lists, such as a list of to-do items. Instead of manually creating icons, workflows, and custom states to manage selections, you can add the Checkbox element directly to each list item. The checkbox works similarly to a button, allowing for customization of icons, labels, sizes, and colors.

It also includes built-in conditionals for the checked and unchecked states, making it simple to toggle between states by tapping. The current value of the checkbox is easily readable, so you can trigger actions based on whether it is selected or not.

#### **Managing Task Status**

Another common use case for the Checkbox element is to visually indicate task completion. For instance, when a task is marked as done, the checkbox will appear checked, and when a task is not done, it will appear unchecked. You can modify the default state of the checkbox through conditionals. For example, if a task is marked as complete in the database, the checkbox will automatically reflect that state by appearing checked.

#### Key Features:

* **Customizable appearance**: Adjust icons, labels, colors, and sizes.
* **Default checked/unchecked states**: Built-in toggling functionality.
* **Conditional behavior**: Reflect the current state based on conditions (e.g., task completion).


# Reusable element (mobile)

Reusable elements are components you build once and use across your app. They function like custom components, letting you package a design and its behavior into a single unit that you can drop anywhere it's needed.

Because updates to a reusable element apply automatically to every instance of it in your app, they're a powerful tool for keeping your design consistent, avoiding duplication, and making your app faster to build and maintain.

## What reusable elements do

A reusable component is a standalone component you can place on any view in your app.

Reusable elements have three main advantages:

1. **Consistency.** Every instance shares the same structure and design, so updates apply everywhere at once.
2. **Efficiency.** You build the component once instead of rebuilding it on every view.
3. **Flexibility.** Instances can be customized through parameters, letting each one adapt to the context it's used in.

Common examples include headers, app bar sections, cards, form components, and shared media modules.

### Reusable elements for web and mobile

Reusable elements are created and managed separately for each platform, since mobile elements and web elements are not compatible with each other. To create a [reusable element for web](/help-guides/design/elements/web-app/reusable-elements), switch to the web editor and add it there in the same way.

## How reusable elements work

A reusable element has two parts:

* **The definition.** The reusable element itself, edited from the reusable elements section of the editor. Any changes made here affect every instance across your app.
* **The instance.** A placed copy of the reusable element on a view or inside another reusable element. Instances share the design and behavior of the definition, but can accept parameters to display different content.

The definition and each instance have their own separate property editors. This split gives you a single source of truth for the component's core properties, while still allowing individual instances to adapt to the context they're used in.

Once a reusable element is defined, adding it to a view works the same as with any other element. Drag it from the palette or the sidebar and configure any parameters it accepts.

## Reusable elements vs. groups

Reusable elements and groups can look similar in the editor, but they serve different purposes.

<table><thead><tr><th width="152.87890625">Feature</th><th>Reusable element</th><th>Group</th></tr></thead><tbody><tr><td>Scope</td><td>Shared across views</td><td>Belongs to a single view</td></tr><tr><td>Editing</td><td>Edited in isolation</td><td>Edited directly on the view</td></tr><tr><td>Updates</td><td>Changes apply to every instance across the app</td><td>Changes affect only the specific group</td></tr><tr><td>Workflows</td><td>Has its own workflows, defined inside the reusable element</td><td>Workflows live in the view it's on</td></tr><tr><td>Parameters</td><td><a href="#passing-data-to-reusable-elements">Accepts data</a> through custom properties, custom states or <em>Type of content</em></td><td>Accepts data through <em>Type of content</em> or custom states</td></tr></tbody></table>

Use a group when the component is only needed on one view. Use a reusable element when the component will appear in multiple places, or when you want to encapsulate its design and behavior for reuse.

## Reusable elements vs. views

Reusable elements and views both hold structured layouts and their own workflows, but they serve different purposes.

<table><thead><tr><th width="184.95703125">Feature</th><th>Reusable element</th><th>View</th></tr></thead><tbody><tr><td>Purpose</td><td>A shared component used inside views</td><td>A standalone screen users navigate to</td></tr><tr><td>Navigation</td><td>Placed inside a view, not navigated to</td><td>Reached through tab bar or <em>Go to view</em></td></tr><tr><td>Contains workflows</td><td>Yes</td><td>Yes</td></tr><tr><td>Can be nested</td><td>Yes, inside other reusable elements or containers</td><td>No, views can't contain other views</td></tr><tr><td>Parameters</td><td>Yes, through custom properties</td><td>Yes, through custom properties</td></tr><tr><td>Instances</td><td>Can be used many times across the app</td><td>One per screen in your app</td></tr><tr><td>Updates</td><td>Changes apply to every instance at once</td><td>Changes affect only that view</td></tr></tbody></table>

Use a reusable element for components that appear across multiple views. Use a view for full screens users can navigate to.

## Creating a reusable element

There are two ways to create a reusable element:

### Convert an existing group

Right-click a group on the view and select *Convert to a reusable element*. Bubble creates a new reusable element. The original group is not deleted or replaced.

This is useful when you've already built a component on a view and realize it should be reusable.

### Create one from scratch

In view navigator, click *New* button and *mobile reusable element*, give it a name, and build the design from scratch.

This is useful when you're starting fresh, such as building a shared header before adding it to any views.

## Types of reusable elements

When you create a reusable element for a mobile app, you choose what type of container it will be. The type controls how the reusable element behaves when placed on a view.

### Group

The default option. A group-type reusable element behaves like a regular group on the view it's placed on. It sits inline with the surrounding content and can be arranged using the same layout modes as any other container. Use this type for most reusable components, including headers, cards, form sections, and content modules.

**Article:** [Group](/help-guides/design/elements/ios-and-android-app/containers/group-element-mobile)

### Sheet

Turns the reusable element into a sheet. Instances slide up from the bottom of the view, sitting above the current content while keeping it partially visible. Use this type when the reusable element is a shared sheet-based interaction, such as a filter panel, a comment thread, or a quick action menu.

**Article:** [Sheet](/help-guides/design/elements/ios-and-android-app/containers/sheet-mobile)

### Floating group

Turns the reusable element into a floating group. Instances stay fixed to a side of the screen and remain visible as the user scrolls. Use this type for shared components that need to stay in place, such as a persistent header, a floating action button, or a status banner.

Even if you plan to use a reusable element inside a sheet or floating group, it can be worth setting it up as a *Group* type and placing that group inside a regular sheet or floating group on the view. This gives you more flexibility in certain scenarios, such as when you want to collapse the reusable element's width or height, or use it in different contexts without being locked into a specific overlay behavior.

**Article:** [Floating group](/help-guides/design/elements/ios-and-android-app/containers/floating-group-element-mobile)

## Passing data to reusable elements

### Using the *Type of content*

Reusable elements accept a *Type of content* just like a regular group, sheet or floating group. You define the type of content, such as *User, Post*, or text on the [definition](#how-reusable-elements-work), and set the data source on the [instance](#how-reusable-elements-work).

### Using custom properties

Reusable elements can accept data through custom properties. This lets each instance display different content while sharing the same structure.

To set this up:

1. Open the reusable element's definition.
2. Add one or more custom properties, each with a name and a type (such as *User*, *Post*, or *Text*).
3. Reference those properties from elements inside the reusable element using expressions like `Reusable element's User's name`.
4. Add the reusable element to a view, and configure its properties with the data you want to pass in.

For example, a *User card* reusable element might accept a *User* parameter. Instances on different views can display different users by passing different values into that parameter.

Custom properties can also be read from outside of the reusable element, and is a useful way to pass data both ways.

### Using custom states

You can also pass data between a view and a reusable element using the *Set state of an element* action:

1. Create a custom state on the reusable element itself, not on any of its child elements, and set it to the correct data type.
2. Use the *Set state of an element* action from the view's workflow to write data into that custom state.

The reusable element's child elements can then reference the custom state directly in expressions or conditions.

This method works both directions. A view can push data into a reusable element, and a reusable element can update a state that other elements react to.

## Reusable elements and workflows

Reusable elements have their own workflows. Anything triggered inside a reusable element is defined and edited there, not on the page it's placed on. This keeps behavior encapsulated with the component.

Common patterns:

* A header with a login button that triggers a login popup, both defined in the same reusable element.
* A card component that opens a detail view when clicked.
* A form component that validates and submits its own data.

Workflows inside a reusable element can also communicate with the page it's placed on through custom events, letting the page respond when something happens inside the reusable element.

### Custom events

Custom events are workflows that don't run on their own. Instead, they run when explicitly triggered by another workflow.

This makes reusable elements more flexible. A reusable component isn't just a self-contained unit: it can respond to instructions from the outside world.

Custom events also let you use reusable elements as workflow libraries. By defining a set of commonly used workflows as custom events inside a reusable element, you can manage them in a single central place and trigger them from any page that includes the reusable element.

The reusable element doesn't need to contain any visual elements for this to work. A reusable element created purely to hold shared workflows is a common pattern for organizing app-wide workflows.

#### Defining a custom event

Custom events are defined inside the reusable element's workflow editor, alongside any other workflows the component uses. Each custom event has:

* **A name**, used to reference it when triggering.
* **Optional parameters**, which pass data into the event from the workflow that triggers it.
* **A set of actions**, which run when the event is triggered.
* **Optional return values,** which pass one or more values back to the original workflow.

Once defined, a custom event is available to any workflow that has access to the reusable element.&#x20;

#### Triggering a custom event from a page

To trigger a custom event inside a reusable element from the page it's on, use the [*Trigger a custom event from a reusable element*](/core-resources/bubble-workflows/bubble-actions/custom-actions#trigger-a-custom-event-from-a-reusable-element) action. This action lets you:

1. Select the reusable element instance on the page.
2. Choose the specific custom event to trigger.
3. Pass values into any parameters the event accepts.

If you are using return values, you can reference them in the next step of the workflow as *Result of step X*. The original workflow waits for the custom event's workflow to finish before it continues.

### Styling reusable elements

Reusable elements can be styled like any other element. The styling defined on the reusable element carries over to every instance, keeping the design consistent. Individual instances can override some properties, such as size or visibility, but the underlying design is controlled by the definition.

You can use a combination of custom properties and conditionals to style a reusable element differently based on how it's being used.

Using shared styles or style variables inside a reusable element keeps the design consistent both within the component and across the rest of your app.

### Detaching a reusable element

Occasionally, you may want to break the link between an instance and its definition. Right-clicking a reusable element instance and selecting *Detach reusable element* converts it into regular elements. The design, layout, and workflows are preserved on the view, but the connection to the original reusable element is removed.

Once detached, the elements behave like any other elements on the view:

* Changes made to the original reusable element no longer affect them.
* Changes made to the detached elements don't affect the reusable element or any other instances.
* The elements can be edited independently, like any other design on the view.

Detaching is useful when a view needs a variation of the reusable element that's different enough to warrant a separate design, or when the reusable element is being phased out and you want to preserve the existing content on the view.

### Frequently asked questions

<details>

<summary>What's the difference between a reusable element and a group?</summary>

A group is a container that lives on a specific view. A reusable element is a component that can be used across multiple views (or multiple times on the same view), with a shared definition that updates every instance at once.

</details>

<details>

<summary>Can I convert a group into a reusable element?</summary>

Yes. Right-click a group and select *Convert to a reusable element*. Bubble creates a new reusable from the group. It does not delete or replace the original group.

</details>

<details>

<summary>Can reusable elements accept data?</summary>

Yes. See [here](#passing-data-to-reusable-elements).

</details>

<details>

<summary>Can a reusable element trigger a workflow on the view it's placed on?</summary>

No. Although you can set up a workaround, such as setting up a *Do when condition is true* event that reacts to a change in value of a reusable element's custom property or custom state.

</details>

<details>

<summary>Can I trigger a workflow inside a reusable element from the view?</summary>

Yes. Use the Trigger a custom event from a reusable element action, and reference a custom event defined inside the reusable element.

</details>

<details>

<summary>Can reusable elements contain other reusable elements?</summary>

Yes. Nesting reusable elements is a common pattern, especially for complex layouts where smaller components are combined into larger ones.

</details>

<details>

<summary>Can I use a sheet as a reusable element?</summary>

Yes. Sheets can be built as reusable elements and reused across views, keeping the design and behavior consistent everywhere they appear.

</details>

<details>

<summary>Do changes to a reusable element affect existing instances?</summary>

Yes. Any change to the reusable element applies immediately to every instance across your app.

</details>

<details>

<summary>Can individual instances have different styling?</summary>

Some properties, like size or visibility, can be adjusted at the instance level. The underlying design and structure are controlled by the reusable element definition. You can use a combination of custom properties and conditionals to change th styling under specific circumstances.

</details>

<details>

<summary>Can reusable elements be shared between web and mobile?</summary>

No. Web and mobile have separate reusable element libraries, since the two platforms use different element types. A reusable element built for web can't be used on mobile, and vice versa.

</details>


# Variables and styles

This section covers the different styling properties that can be applied to elements, such as colors, borders, shadows and fonts.

## Variables and styles

Variables and styles are the tools you use to keep your app's design consistent, flexible, and easy to update. Instead of styling every element individually, you define colors, fonts, and shared visual properties once, then reference them wherever they're needed.

<figure><img src="/files/y6NmPYVcuMD57OUIUiJ6" alt="Data dashboard using styles for light mode and dark mode"><figcaption><p>Using variables and styles you can quickly change the entire look and feel of your app.</p></figcaption></figure>

This approach saves time as you build, and makes it much easier to update your app's look and feel later. Changing a color or font in one place updates everywhere it's used, so you can iterate on your design without hunting down every element that needs to change.

### Why variables and styles matter

Design consistency is one of the hardest things to maintain in a growing app. Without a shared system, small differences pile up: two shades of blue that should have been the same, three slightly different heading fonts, buttons that don't quite match each other.

<figure><img src="/files/Pbb29D3JVuTcRu09j0n6" alt="Three differently styled button elements."><figcaption><p>Using styles, you can create very different designs and apply them to elements across your app quickly.</p></figcaption></figure>

Variables and styles solve this by giving you a single place to experiment with and save design decisions. Every element that uses a variable or style reflects the current definition, so your app stays consistent as it grows.

They also make design changes much less painful. Want to switch to a new brand color? Update the color variable. Want to change your body font? Update the font variable. The change flows through your entire app automatically.

## What's in this section

The *Variables and styles* section covers four related tools:

### Color variables

Color variables let you save named colors and reuse them across your app. Common examples include brand colors, background colors, text colors, and status colors like success and error. When you update a color variable, every element that uses it updates too.

**Article:** [Color variables](/help-guides/design/variables-and-styles/color-variables)

### Font variables

Font variables let you save named fonts and reuse them across your app. Each variable holds a font family, weight, and style, so you can reference *Body*, *Heading*, or *Caption* directly instead of configuring the same font settings on every element.

**Article:** [Font variables](/help-guides/design/variables-and-styles/font-variables)

### Styles

Styles bundle multiple visual properties together into a reusable definition. Instead of setting each property separately, you can apply a style like *Primary button* or *Body text* to instantly configure the element with the right combination of properties. Changing the style updates every element that uses it.

**Article:** [Styles](/help-guides/design/variables-and-styles/styles)

### Custom fonts

Custom fonts let you upload your own font files to use in your app. This is useful when your app needs a specific font that isn't included in Bubble's built-in library, such as a custom brand font or a font licensed from a foundry.

**Article:** [Custom fonts](/help-guides/design/variables-and-styles/using-custom-fonts)

## How they fit together

Variables and styles work best as a system. Color and font variables are the foundation. Styles combine those variables with other visual properties into named, reusable definitions. Custom fonts extend what's available to use in the first place.

A typical setup might look like this:

* **Custom fonts** load your brand's typography into the app.
* **Font variables** define named text styles like *Heading 1*, *Body*, and *Caption*, each based on your custom fonts.
* **Color variables** define your brand palette, background colors, and text colors.
* **Styles** combine these variables with other properties, such as padding, borders, and hover states, to define reusable component styles like *Primary button* or *Card*.

When you build with this system, adjusting your app's design becomes a matter of updating a handful of definitions instead of touching every element on every page.


# Color variables

Color variables are named colors you save once and reuse across your app. Instead of picking the same [hex code](#user-content-fn-1)[^1] and [alpha value](#user-content-fn-2)[^2] every time you style an element, you reference the variable, keeping your color choices consistent everywhere they're used.

<figure><img src="/files/dSXe85h8haDBqlEuGGqz" alt="The Colors sub-section in the Variables section in the Global tab."><figcaption><p>In the <em>Global</em> tab and <em>Variables</em> section you'll find the list of color variables in your project.</p></figcaption></figure>

When you update a color variable, every element that uses it updates too. This makes it much easier to iterate on your app's look and feel, especially as your app grows.

## What color variables are for

Most apps rely on a small palette of colors that appear over and over: brand colors, background colors, text colors, and status colors like success and error. Without variables, each of those colors ends up scattered across your app, hardcoded on individual elements or in styles. Updating one color means finding and updating every style and element that uses it.

Color variables replace that pattern with a single, named reference. Once a color is saved as a variable, it becomes part of your design system, available to any element or style that needs it.

## Creating a color variable

To create a color variable:

{% stepper %}
{% step %}

### Navigate to the variables section

Open the *Global* tab and click the *Variables* section.
{% endstep %}

{% step %}

### Find Color variables

Scroll down until you see the *Colors* header
{% endstep %}

{% step %}

### Create a new variable

Click the *New color variable* button in the upper right corner of the Color variables section.
{% endstep %}

{% step %}

### Name the new variable

The name is how you'll reference the color in the editor, so it should make the variable easy to identify at a glance.

* **Name by role, not appearance.** *Primary* is more useful than *Blue*, because the name still makes sense if you change the color later. Naming by appearance breaks down as soon as your palette evolves.
* **Group related variables with a prefix.** Prefixes like *Text*, *Background*, and *Border* help related colors sort together and make the picker easier to scan. For example: *Text default*, *Text muted*, *Text inverse*.
* **Be specific but concise.** *Background subtle* is clearer than *Background* if you have more than one background variable. Aim for names that describe purpose in as few words as possible.
* **Match your team's naming conventions.** If you're working with others, agree on a naming pattern early. Consistency across the palette is more valuable than any specific naming choice.
  {% endstep %}

{% step %}

### Describe the color variable

Descriptions aren't required, but they're useful for anyone (including future you) trying to understand what the variable is meant for.

* **Explain the intent, not the color.** A description like *Used for primary calls to action* is more helpful than *Bright blue used on buttons*. The intent is what stays consistent even if the color changes.
* **Note where it should be used.** If a variable has a specific purpose, spell it out. For example: *Backgrounds behind cards and modals* or *Error states, including borders and text*.
* **Note where it shouldn't be used.** If two variables are similar, describing when to pick one over the other prevents mix-ups. For example: *Use for headings only; body text should use Text default*.
* **Keep it short.** A sentence or two is usually enough. Descriptions are meant to give quick clarity, not full documentation.
  {% endstep %}

{% step %}

### Pick a color

The color picker gives you a few different ways to choose the color for your variable. Any of the methods below can be used together to fine-tune the exact shade you want.

<figure><img src="/files/6yoxXApyLXXMTRuVwtVf" alt="The color field, hue slider, opacity slider and manual color input"><figcaption></figcaption></figure>

#### The color field

The large square is where you pick the color's saturation and brightness. Click anywhere inside it to select a color, or drag the marker to adjust. Moving up brightens the color, moving down darkens it. Moving right adds saturation, moving left removes it.

#### The hue slider

The narrow band below the color field is the hue slider. It runs across the color spectrum, from red on the left through green, blue, and back to red. Slide the marker along the band to change the base hue, then use the color field above to adjust the saturation and brightness.

#### The opacity slider

The checkered slider below the hue slider controls opacity. Slide left to make the color more transparent, or right to make it fully opaque. Opacity is useful for overlays, subtle backgrounds, and softer highlights.

#### The hex code and opacity input

At the bottom, you can type a hex code directly. This is the fastest way to enter an exact color, especially when working from a brand palette or design system. The percentage next to the hex code shows the current opacity.

You can also copy hex codes out of the field to reuse the same color elsewhere, or paste in codes you've picked from another tool.
{% endstep %}

{% step %}

### Save it

Click *Create* to save the new color variable. It will be displayed in the list of color variables and can be used right away.
{% endstep %}
{% endstepper %}

## Using color variables

Anywhere you'd normally set a color in the property editor, you can pick a color variable instead. The color picker shows your saved variables alongside the standard color options, making it easy to apply the right one.

<figure><img src="/files/ZJFj1Tcq94mhSelO8poY" alt="Color variables shown in the color property of an element"><figcaption><p>Color variables are available anywhere you work with colors. In this example, we're setting the background color of an element. The top section lets you pick a custom color, while the highlighted bottom section lets you pick from your saved color variables.</p></figcaption></figure>

Color variables can be used in:

* **Element properties**, such as backgrounds, borders, text color, and shadows.
* **Styles**, so an entire component's color palette is built from your variables.
* **Conditional properties**, letting you change an element's color based on a rule while still using variables.

## Updating a color variable

{% stepper %}
{% step %}

### Navigate to the variables section

Open the *Global* tab and click the *Variables* section.
{% endstep %}

{% step %}

### Find the color variable

Scroll down until you see the *Colors* header, and then find the color variable you want to update.
{% endstep %}

{% step %}

### Set the new color and alpha settings

On the right side of the color variable row you can see the color and alpha value. Click any of them to make changes.
{% endstep %}

{% step %}

### Change the name and/or description

To open up the full color variable edit window, click the pencil icon on the left side of the color variable row. Make the needed changes and click *Save.*
{% endstep %}

{% step %}

### The color is updated everywhere

The color is now updated everywhere it's used in your project.
{% endstep %}
{% endstepper %}

This is what makes color variables so powerful. Instead of tracking down every place a color is used, you can change your app's palette in one place and see the change propagate everywhere.

## Searching for components using a specific color variable

*Find usages* is useful when you want to review the impact of a change before making it, clean up unused variables, or trace inconsistencies in your design. It's especially helpful in larger apps, where the same variable can be referenced in many places across pages, reusable elements, and workflows.

<figure><img src="/files/LG40rQPqHvpssMMxS07T" alt="Finding uses of a specific color variable"><figcaption></figcaption></figure>

{% stepper %}
{% step %}

### Find the color variable

Navigate to *Global*, *Variables* and scroll to the color variable you want to search for.
{% endstep %}

{% step %}

### Open the contextual menu

Click the ... menu on the right side of the color variable row and click *Find usages*.
{% endstep %}

{% step %}

### Click the component in the search results

The *App search tool* will open and show all components that use the color variable. Click one to focus on it in the editor.
{% endstep %}
{% endstepper %}

## Common uses of color variables

Color variables work best when they reflect the roles colors play in your design, rather than the specific colors themselves. A good starting palette often includes:

* **Brand colors**, such as *Primary* and *Secondary*.
* **Background colors**, such as *Background default* and *Background subtle*.
* **Text colors**, such as *Text default*, *Text muted*, and *Text inverse*.
* **Border colors**, such as *Border default* and *Border strong*.
* **Status colors**, such as *Success*, *Warning*, *Error*, and *Info*.

Building your palette this way makes it easier to maintain, easier to explain to collaborators, and easier to adjust as your design evolves.

## Tips for working with color variables

* **Name variables by role, not appearance.** *Primary* is easier to work with than *Blue*, because it stays accurate even if the color changes.
* **Keep the palette small.** A tight palette is easier to reason about and enforces consistency. Aim for a handful of well-chosen variables rather than a large list.
* **Use variables in styles.** Applying color variables inside your styles is what ties the whole system together. Elements pick up colors indirectly through the styles they use.
* **Plan for dark mode from the start.** If you know your app will support dark mode, name your variables in a way that makes both light and dark values easy to define, such as *Background default* rather than *White*.

## FAQ: Color variables

<details>

<summary>What's the difference between a color variable and picking a color directly?</summary>

A color variable is a saved, named color that can be reused across your app. Picking a color directly hardcodes the specific hex code into that element, so it can't be updated centrally.&#x20;

Color variables make it easy to keep your palette consistent and to change colors in one place.

</details>

<details>

<summary>Can I use color variables in styles?</summary>

Yes. Styles can reference color variables just like individual elements can. Using variables inside styles is what ties the whole design system together, since your elements pick up colors indirectly through the styles they use.

</details>

<details>

<summary>What happens when I change a color variable?</summary>

Every element and style that uses the variable updates automatically to reflect the new color. This is one of the main reasons to use color variables in the first place.

</details>

<details>

<summary>Can I delete a color variable that's still in use?</summary>

You can, but any element referencing it will no longer be linked to it, and revert to a hardcoded color. Use [Find usages](#find-the-color-variable) to review where the variable is used before deleting it, so you can update or replace it where needed.

{% hint style="warning" %}
Deleting a color variable can't be undone, so proceed with care.
{% endhint %}

</details>

<details>

<summary>How many color variables should I have?</summary>

There's no strict rule, but a small, well-chosen palette is easier to maintain than a large one. Most apps do well with a set that covers brand colors, backgrounds, text, borders, and a few status colors.

</details>

<details>

<summary>Can I use color variables in conditional properties?</summary>

Yes. Conditional properties support color variables, so you can change an element's color based on a rule while still referencing your saved palette.

</details>

<details>

<summary>Can I organize color variables into groups?</summary>

There's currently no way to organize color variables into groups or folders.

</details>

<details>

<summary>Do color variables work in both web and native mobile apps?</summary>

Yes. Color variables are part of your app's design system and are available on both platforms.

</details>

<details>

<summary>Can I use color variables for dark mode?</summary>

Yes. One common approach is to set up a conditional on a style that checks a value on the user, such as a *dark mode* toggle, and changes the color accordingly.

<figure><img src="/files/e0XRxvyPLs7kjRSJ3hak" alt=""><figcaption><p>In this example, we have a field on the user called <em>Dark mode</em>, and if set to <em>Yes</em>, the style will use a different color. <strong>Note:</strong> this condition is placed on the style directly, not on an element.</p></figcaption></figure>

</details>

[^1]: A hex code is a six-digit number used in to represent a specific color. It condenses RGB values into a single code.\
    \
    The first two digits of the hex code represent the red component, the middle two represent the green component, and the last two represent the blue component.

[^2]: Alpha value in hex codes represents the opacity level of a color. While traditional hex color codes have 6 digits (e.g., #RRGGBB), adding an alpha value extends it to 8 digits (e.g., #RRGGBBAA).

    The two extra characters denote transparency, with '00' being fully transparent and 'FF' being fully opaque.

    These codes are generated automatically in Bubble.


# Font variables

Font variables are named font settings you save once and reuse across your app. Instead of setting the same font family on every element and/or style, you reference the variable, keeping your typography consistent everywhere it's used.

<figure><img src="/files/2YvxdYUgvj4pUfR5VsIl" alt="Font variable properties in the Variable section of the Global tab"><figcaption><p>Navigate to <em>Global</em> and <em>Variables</em> to find your project's font variables.</p></figcaption></figure>

When you update a font variable, every element that uses it updates too. This makes it much easier to iterate on your app's typography, especially as your app grows.

## What font variables are for

Most apps rely on a small set of text styles that appear over and over: headings, body text, captions, buttons, and links. Without variables, each of those styles ends up scattered across your app, hardcoded on individual elements. Updating one style means finding and updating every element that uses it.

Font variables replace that pattern with a single, named reference. Once a font is saved as a variable, it becomes part of your design system, available to any element or style that needs it.

## Creating a font variable

{% stepper %}
{% step %}

### Navigate to Font variables

Go to the *Global* tab and *Variables* section. You'll find the list of font variables.
{% endstep %}

{% step %}

### Add a new font variable

Click + *New font variable* to create a new variable. The font variable editor will open.
{% endstep %}

{% step %}

### Name and describe the font

The name and description you give a font variable have a big impact on how easy it is to work with, especially as your app grows and other people collaborate with you.

#### Naming variables

The name is how you'll reference the font in the editor, so it should make the variable easy to identify at a glance.

* **Name by role, not appearance.** *Brand heading* is more useful than *Inter*, because the name still makes sense if you change the font later. Naming by appearance breaks down as soon as your typography evolves.
* **Group related variables with a prefix.** Prefixes like *Heading*, *Body*, and *Caption* help related fonts sort together and make the picker easier to scan. For example: *Heading primary*, *Heading secondary*, *Body primary*.
* **Match your team's naming conventions.** If you're working with others, agree on a naming pattern early. Consistency is more valuable than any specific naming choice.

#### Writing descriptions

Descriptions aren't required, but they're useful for anyone (including future you) trying to understand what the variable is meant for.

* **Explain the intent, not the font.** A description like *Used for primary page headings* is more helpful than *Inter*. The intent is what stays consistent even if the font changes.
* **Note where it should be used.** If a variable has a specific purpose, spell it out. For example: *Section titles inside cards and modals* or *Metadata like timestamps and author names*.
* **Note where it shouldn't be used.** If two variables are similar, describing when to pick one over the other prevents mix-ups. For example: *Use for standalone captions; body text should use Body default*.
* **Keep it short.** A sentence or two is usually enough. Descriptions are meant to give quick clarity, not full documentation.
  {% endstep %}

{% step %}

### Pick the font

In the dropdown at the bottom, pick the font you want to use.
{% endstep %}

{% step %}

### Save it

Click *Create* to create the new font variable. You'll see it in the list immediately.
{% endstep %}
{% endstepper %}

## Using font variables

Font variables are available anywhere you work with typography in the editor. In the property editor, wherever you'd normally set a font family, you can pick a variable instead. The picker shows your saved variables alongside the standard font options, making it easy to apply the right one.

<figure><img src="/files/R94O4RStJBLEnUR0a0AJ" alt="Showing font variables in the list of fonts"><figcaption><p>Wherever you work with typography, your font variables appear at the top of the list of available fonts.</p></figcaption></figure>

Font variables can be used in:

* **Element properties**, such as text elements, buttons, inputs, and any other element that displays text.
* **Styles**, so an entire component's typography is built from your variables. Using variables inside styles is what ties the whole system together.
* **Conditional properties**, letting you change an element's font based on a rule while still referencing your saved set.

Since font variables only set the font family, other typography settings like size, weight, and style are configured separately on the element or style. This gives you the flexibility to use the same font family in different sizes and weights across your app while still keeping the underlying family consistent.

## Updating a font variable

{% stepper %}
{% step %}

### Navigate to the variables section

Open the *Global* tab and click the *Variables* section.
{% endstep %}

{% step %}

### Find the font variable

Find the font variable you want to update.
{% endstep %}

{% step %}

### Set the new font

On the right side of the font variable row you can see the font picker. Click it and select the font you want to use.
{% endstep %}

{% step %}

### Change the name and/or description

To open up the full font variable edit window, click the pencil icon on the left side of the font variable row. Make the needed changes and click *Save.*
{% endstep %}

{% step %}

### The font is updated everywhere

The font is now updated everywhere it's used in your project.
{% endstep %}
{% endstepper %}

This is what makes font variables so powerful. Instead of tracking down every place a font is used, you can change it in one place and see the change propagate everywhere.

## Searching for components using a specific font variable

*Find usages* is useful when you want to review the impact of a change before making it, clean up unused variables, or trace inconsistencies in your design. It's especially helpful in larger apps, where the same variable can be referenced in many places across pages, reusable elements, and workflows.

<figure><img src="/files/ehYtqDjGypJ4qLgE0nOM" alt=""><figcaption></figcaption></figure>

{% stepper %}
{% step %}

### Find the font variable

Navigate to *Global*, *Variables* and scroll to the font variable you want to search for.
{% endstep %}

{% step %}

### Open the contextual menu

Click the ... menu on the right side of the font variable row and click *Find usages*.
{% endstep %}

{% step %}

### Click the component you want to select

The *App search tool* will open and show all components that use the font variable. Click one to focus on it in the editor.
{% endstep %}
{% endstepper %}

## FAQ: Font variables

<details>

<summary>What's the difference between a font variable and setting a font directly?</summary>

A font variable is a saved, named font that can be reused across your app. Setting a font directly hardcodes the choice onto that element, so it can't be updated centrally. Font variables make it easy to keep your typography consistent and to change fonts in one place.

</details>

<details>

<summary>Can I use font variables in styles?</summary>

Yes. Styles can reference font variables just like individual elements can. Using variables inside styles is what ties the whole design system together, since your elements pick up the font indirectly through the styles they use.

</details>

<details>

<summary>What happens when I change a font variable?</summary>

Every element and style that uses the variable updates automatically to reflect the new font. This is one of the main reasons to use font variables in the first place.

</details>

<details>

<summary>Can I delete a font variable that's still in use?</summary>

You can, but any element referencing it will lose the connection and revert to a hardcoded font. Use [Find usages](#find-the-font-variable-1) to review where the variable is used before deleting it, so you can update or replace it where needed.

</details>

<details>

<summary>How many font variables should I have?</summary>

There's no strict rule, but a small, well-chosen set is easier to maintain than a large one. Most apps do well with just a few, covering things like headings, body text, and supporting text.

{% hint style="info" %}
**Note on performance**: Each font family is a separate file that the user's browser needs to download for the page to render correctly. Using multiple font families adds to the initial page load. Fonts are cached after the first download, so subsequent visits and page loads are faster.
{% endhint %}

</details>

<details>

<summary>Can I use font variables in conditional properties?</summary>

Yes. Conditional properties support font variables, so you can change an element's font based on a rule while still referencing your saved set.

</details>

<details>

<summary>Do font variables work in both web and native mobile apps?</summary>

Yes. Font variables are part of your app's design system and are available on both platforms.

</details>

<details>

<summary>Can I use a custom font in a font variable?</summary>

Yes. Upload the font through Custom fonts first, then reference it in your font variable.

</details>

<details>

<summary>Do font variables control font size or weight?</summary>

No. Font variables only set the font family. Size, weight, and other typography settings are configured on the element or style directly.

</details>


# Styles

This section covers styles, Bubble's tool for managing centralized stylesheets that can be applied to elements across your app

Styles are reusable design definitions that bundle multiple visual properties into one place. Instead of styling every element individually, you define a style once and apply it wherever you need it. When you update the style, every element that uses it updates too.

This makes styles one of the most powerful tools for keeping your app's design consistent and easy to change. A well-organized set of styles serves as your app's design language, giving every button, text element, and container a shared foundation to build on.

Each style is connected to a specific element type and includes different properties that can be edited (depending on the element type), such as:

* Background color or image
* Border
* Shadow
* Font
* Transitions

Styles work hand in hand with [color](/help-guides/design/variables-and-styles/color-variables) and [font](/help-guides/design/variables-and-styles/font-variables) variables.

## Web and mobile styles

The Styles tab supports **styles for web and mobile apps in the same project**, allowing you to define and manage visual settings for both mobile and web components. With platform-specific filtering, compatibility indicators, and canvas previews, it's easy to build consistent designs across platforms while accounting for the differences between them.

### Filtering styles by platform

A platform filter is available in the left sidebar of the Styles tab. This lets you view:

* **All** styles (web and mobile)
* **Mobile**-only styles
* **Web**-only styles

<figure><img src="/files/PATrhGjlEhxCChuQ3Mkj" alt="Filtering styles by platform"><figcaption><p>You can filter styles by platform by using the highlighted buttons above.</p></figcaption></figure>

Use this filter to focus on the styles relevant to the platform you’re designing for.

### Compatibility badges

Each style includes a compatibility badge that shows where it can be used:

* A **phone icon** indicates the style applies to mobile-only elements
* A **computer icon** indicates web-only elements
* **Both icons** appear for styles that apply to elements shared between mobile and web

These badges help clarify which platform each style supports.

### Style editor for shared elements

Some elements, like **buttons**, are available on both mobile and web. For these shared elements, the style editor groups settings into:

* **Shared properties**, which apply across both platforms
* **Platform-specific properties**, which are unique to either web or mobile

For example, shadows are handled differently between platforms. These differences are reflected in the style editor, with mobile- and web-specific controls shown where relevant.

The expandable box below lists elements by platform:

<details>

<summary>Element platform table</summary>

<table><thead><tr><th width="234.31640625">Name</th><th>Type</th></tr></thead><tbody><tr><td><strong>Web and mobile</strong></td><td></td></tr><tr><td>Text</td><td>Both</td></tr><tr><td>Alert</td><td>Web</td></tr><tr><td>Input</td><td>Both</td></tr><tr><td>Multiline Input</td><td>Both</td></tr><tr><td>Dropdown</td><td>Web</td></tr><tr><td>Checkbox</td><td>Both w/ Different properties</td></tr><tr><td>Radio Buttons</td><td>Web</td></tr><tr><td>Date/Time Picker</td><td>Both w/ Different properties</td></tr><tr><td>File Uploader</td><td>Web</td></tr><tr><td>Popup</td><td>Web</td></tr><tr><td>Button</td><td>Both</td></tr><tr><td>Link</td><td>Web</td></tr><tr><td>Icon</td><td>Both</td></tr><tr><td>Image</td><td>Both</td></tr><tr><td>Video</td><td>Web</td></tr><tr><td>Shape</td><td>Both</td></tr><tr><td>Slider</td><td>Web</td></tr><tr><td>Group</td><td>Both</td></tr><tr><td>Floating Group</td><td>Both</td></tr><tr><td>Table</td><td>Web</td></tr><tr><td>Group Focus</td><td>Web</td></tr><tr><td>Repeating Group</td><td>Web</td></tr><tr><td>Search Box</td><td>Web</td></tr><tr><td>Map</td><td>Both w/ Different properties</td></tr><tr><td>HTML</td><td>Web</td></tr><tr><td></td><td></td></tr><tr><td><strong>Mobile only</strong></td><td></td></tr><tr><td>Sheet</td><td>Mobile</td></tr><tr><td>Tab Bar</td><td>Mobile</td></tr><tr><td>Tab item</td><td>Mobile</td></tr><tr><td>Top App Bar</td><td>Mobile</td></tr><tr><td>Web View</td><td>Mobile</td></tr><tr><td>Horizontal List</td><td>Mobile</td></tr><tr><td>Horizontal list item</td><td>Mobile</td></tr><tr><td>Short List</td><td>Mobile</td></tr><tr><td>Short list item</td><td>Mobile</td></tr><tr><td>Selectable list</td><td>Mobile</td></tr><tr><td>Selectable list item</td><td>Mobile</td></tr><tr><td>Vertical list item</td><td>Mobile</td></tr><tr><td>Section header item</td><td>Mobile</td></tr><tr><td>Swipe action</td><td>Mobile</td></tr></tbody></table>

</details>

### Web/mobile preview toggle

<figure><img src="/files/9BYZtdDcwoKvFyaH4gSK" alt="Web/mobile preview toggle in the style canvas"><figcaption></figcaption></figure>

When editing styles for shared elements, the canvas preview includes a toggle that lets you switch between **web** and **mobile** views. This helps you see how the style will appear in each context.

If a style applies only to one platform, the toggle is hidden and the preview reflects that platform by default.

### Creating mobile-only styles

Styles can be created for mobile-only elements such as:

* **App bars**
* **Tab items**
* **Sheets**

When editing a mobile-only style, the style editor shows mobile-specific settings and the preview reflects the mobile context. These styles won’t appear in web-only projects.

### Notes

* Mobile-only styles do not include the platform toggle or web-specific settings.
* Preview behavior for mobile elements may differ slightly depending on the element’s structure or layout properties.

## Creating a new style

You can define as many styles as you want. Styles are applied to one type of element, such as Buttons, but you can have more than one style for a Button. Try to name them in a way that is easy to read, as you'll have to pick the style that you need for each button. You can also specify which style should be used by default when you insert a new element in this tab.

{% stepper %}
{% step %}

#### Navigate to Styles

Go to the *Global* tab and open *Styles*.
{% endstep %}

{% step %}

#### Add a new style

Click *+ New* to open the *Create a new style* dialog.
{% endstep %}

{% step %}

#### Name the style

Enter a name that describes the style's purpose, such as *Accent*, *Destructive*, or *Ghost*.
{% endstep %}

{% step %}

#### Choose the element type

Select the element type this style applies to from the *Element style* dropdown, such as *Button*, *Text*, or *Group*. Each style is tied to a specific element type and can only be applied to elements of that type.
{% endstep %}

{% step %}

#### Create the style

Click *Create*. The new style opens in the editor, where you can configure its visual and conditional properties.
{% endstep %}
{% endstepper %}

## Configuring styles

Once a style is created, it opens in the editor, where you can configure all the properties that make up its visual definition. This is where the style comes to life, defining the exact look and behavior every element using it will inherit.

### The style canvas

The center of the editor shows a live preview of the style applied to an example element.

<figure><img src="/files/TgAfAGmh8wh8V4Oa5DNA" alt="The style canvas showing a preview of a style"><figcaption><p>The style canvas gives you a preview of an element with the style applied. The right-hand bar is where you configure the properties and conditionals for the selected style.</p></figcaption></figure>

As you adjust properties, the preview updates immediately, letting you see how the style will look before applying it to real elements in your app.

### Visual properties

The *Visual* tab in the property editor holds the style's default properties: everything that applies whenever the style is used without a condition being met.

<figure><img src="/files/cr1DJAsx7fbMxqBamtms" alt="The property editor in the styles tab"><figcaption><p>The right-hand bar is where you configure the visual properties of the selected style.</p></figcaption></figure>

Depending on the element type, these can include:

* Background color and opacity
* Text color, font, and size
* Borders and corner radius
* Padding and spacing
* Shadows and other effects

Setting these values here means every element using the style picks them up automatically. Change a value in the style, and every element updates.

### Conditional properties

The *Conditional* tab lets you define how the style responds to specific states, such as when the element is hovered, focused, disabled, or matches a custom condition.

Each conditional has two parts:

* **When**, which defines the condition, such as *This Button is hovered*.
* **Then**, which defines the property changes that apply when the condition is true.

Conditionals only override the properties you explicitly change. Everything else falls back to the default values in the *Visual* tab. This makes it easy to build styles that respond to interaction without duplicating the base configuration.

<figure><img src="/files/XYqsaX79BiL8K74gBqzJ" alt="A conditional configured on a style"><figcaption><p>Using a dynamic expression, we can detect when the button is hovered and change to a slightly darker color.</p></figcaption></figure>

For example, a button style might use its default background color from the *Visual* tab and switch to a darker shade in the *Conditional* tab when hovered. Only the background color needs to be defined in the conditional. Padding, text style, and everything else stay the same.

{% hint style="info" %}
**Data sources:** When setting up conditionals directly on a style, the available data sources are more limited than when building conditionals on an individual element. This is because styles are global and can't reference page-specific components, such as an element on a specific page.
{% endhint %}

### Setting a default style

The *Set as default* option in the top right marks the current style as the default for its element type. New elements of that type are then created with this style applied automatically.

{% hint style="info" %}
Setting a new default doesn't change any existing elements. It only affects which style is applied to new elements you create afterward.
{% endhint %}

<figure><img src="/files/uaYDELMew3kXMLxiHYnb" alt="Showing the markers for the default style in the list of styles"><figcaption></figcaption></figure>

1. The current default style
2. Clicking this button sets the selected style as the new default

Setting a default style is useful when you want most elements to share the same look without having to apply the style manually each time. Elements can still be assigned a different style at any point.

### Finding elements in use

The *Find elements in use* option opens the app search tool, showing every element that currently uses this style. This is useful for reviewing the impact of a change before making it, cleaning up unused styles, or tracing inconsistencies in your design.

## Using styles with elements

Applying a style is the fastest way to make sure an element matches your app's design language. From the property editor, you can apply, override, and detach styles as needed, giving you flexibility without breaking the consistency of your design.

### Applying a style to an element

{% stepper %}
{% step %}

#### Select the element

Select the element on the page or view.
{% endstep %}

{% step %}

#### Navigate to the *Style* section

In the element property editor, make sure the *Visual* tab is active, and scroll down to *Style.*

<figure><img src="/files/6fN7k0nkZ5zEM8afzFYz" alt="The Style section of the element property editor."><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Add a style

Click + to open the style dropdown.
{% endstep %}

{% step %}

#### Choose a style

Select a style from the dropdown. The element immediately picks up all the properties defined in the style, including any conditional properties.
{% endstep %}
{% endstepper %}

### Detaching a style from an element

Detaching a style breaks the connection between the element and the style. The element keeps its current appearance, but any future updates to the style no longer apply. From this point on, the element behaves like any other element without a style, with all its properties configured directly.

{% stepper %}
{% step %}

#### Select the element

Select the element on the page or view.
{% endstep %}

{% step %}

#### Open the Style section

In the *Visual* tab and *Style* section of the property editor, find the currently applied style.
{% endstep %}

{% step %}

#### Detach the style

<figure><img src="/files/axmIuMAOOX1f8tmEcTQw" alt="The detach style icon"><figcaption></figcaption></figure>

Click the detach icon in the upper right corner of the *Style* section, and the Style is immediately detached.
{% endstep %}
{% endstepper %}

### Overriding style properties

Sometimes an element needs a small variation from its style. Instead of creating a new style just for one element, you can override specific properties directly on the element.

{% stepper %}
{% step %}

#### Select the element

Select the element on the page or view.
{% endstep %}

{% step %}

#### Find the property

In the element property editor, find the property you want to change, such as *Background* or *Border.*
{% endstep %}

{% step %}

#### Adjust the value

Change the value to override the style's default.
{% endstep %}
{% endstepper %}

When an element has at least one overridden property, its selected Style in the property editor will show a small *Overridden* tag:

<figure><img src="/files/KR9KKGoVnGQN4yoasbG9" alt="The overridden tag in the property editor."><figcaption><p>The style is marked with <em>Overridden if the element has at least one overridden property.</em></p></figcaption></figure>

Overridden properties are visually marked with a slightly darker color in the property editor, so you can see at a glance which properties are following the style and which have been changed.

<figure><img src="/files/hKCmXhVgGZot5H7O5YtD" alt="A overridden property displayed in a different color"><figcaption></figcaption></figure>

The element still uses the style for every other property, so future style updates continue to apply to the properties you haven't overridden.

### Resetting a style override

If you decide an override was a mistake or is no longer needed, you can reset it and return the property to the style's value.

{% stepper %}
{% step %}

#### Select the element

Select the element on the page or view.
{% endstep %}

{% step %}

#### Navigate to the Style section in the property editor

Make sure the *Visual* tab is active in the property editor and locate the *Style* section.
{% endstep %}

{% step %}

#### Reset the override

Click the reset icon above the style selector. The element's properties snap back to the style's definition.
{% endstep %}
{% endstepper %}

## Using styles in conditionals

Conditionals can be placed on elements to make them look or behave differently based on specific criteria. You can read more about conditionals [here](/help-guides/logic/conditions).

When setting up a conditional, you tell Bubble to check for specific criteria and apply one or more properties when the condition is true. You can set each property individually, or apply a style to change multiple properties at once.

<figure><img src="/files/l3NaBIFtXdCTNNElJZZk" alt="Changing an element&#x27;s style with a conditional"><figcaption><p>You can change the style of an element based on specific criteria, by using conditionals. In this example, the style of the text changes when the user is logged in.</p></figcaption></figure>

Even when a style is applied through a conditional, you can still adjust individual properties on top of it.

## Conflicting conditionals

Sometimes a conditional is defined both on a style and on an element that uses that style. When the two conflict, the conditional on the element takes priority.

## FAQ: Styles

details>

What's the difference between a style and a variable?

A variable is a single named value, such as a color or a font family. A style is a full definition that combines multiple properties into a reusable configuration, often referencing variables in the process. Styles let you build complete component looks, while variables give you the individual pieces to work with.

<details>

<summary>Can I use variables inside styles?</summary>

Yes. Styles work best when they reference color and font variables instead of hardcoding values. This ties your design system together: variables define the individual values, and styles combine them into reusable component definitions.

</details>

<details>

<summary>What happens when I update a style?</summary>

Every element that uses the style updates automatically to reflect the new values. Any properties overridden on individual elements stay the same, and continue to follow the updated style for everything else.

</details>

<details>

<summary>Can I apply the same style to different element types?</summary>

No. Styles are tied to a specific element type, since different elements have different properties. A button style can only be applied to buttons, a text style only to text elements, and so on.

</details>

<details>

<summary>Can I override properties in a style on a specific element?</summary>

Yes. Selecting an element and changing a property directly overrides the style's value for that element only. The rest of the style still applies, and future updates to the style continue to apply to the properties you haven't overridden.

</details>

<details>

<summary>What happens if I detach a style from an element?</summary>

The element keeps its current appearance, but the connection to the style is broken. Future updates to the style no longer apply, and the element behaves like any other element without a style.

</details>

<details>

<summary>Can I use styles on both web and native mobile apps?</summary>

Yes, although the element types and properties can vary. You can read more about this and cross-platform styling [here](#web-and-mobile-styles).

</details>

<details>

<summary>Can I set a style as the default for an element type?</summary>

Yes. In the style's editor, select a style and click *Set as default*. New elements of that type will use this style automatically. Existing elements aren't affected.

</details>

<details>

<summary>Can I delete a style that's still in use?</summary>

You can, but any element referencing it will revert back to hardcoded styling. Use [Find elements](#finding-elements-in-use) in use to review where the style is applied before deleting it.

</details>

<details>

<summary>Can styles include conditional behavior?</summary>

Yes. The Conditional tab in the style's editor lets you define how the style responds to specific states, such as hover, focus, or a custom condition. Only the properties you explicitly set inside the conditional are changed. Everything else falls back to the default values.

</details>

<details>

<summary>What happens when a conditional on an element and a conditional on a style conflict?</summary>

The conditional on the element takes priority. If both change the same property, the element's conditional wins.

</details>

<details>

<summary>Are styles the same as reusable elements?</summary>

No. Styles apply to individual elements and control their visual properties. Reusable elements are shared components that include their own structure, workflows, and design. You'll often use both together, with reusable elements built from styled components.

</details>


# Custom fonts

Bubble uses [Google Web Fonts](https://fonts.google.com/) for text types. This is a big library of open-source fonts that can be freely used, and the font files are stored on a Google server. Your easiest option when designing in Bubble is to use one of the fonts available in this library.

{% hint style="info" %}
The Google Web Fonts library is so widely used that there's a fair chance the fonts you use are already cached in the browser of many of your users. In essence, this means another website has already loaded the font for you, and the user doesn't need to download it again.

This leads to a faster page load and a smaller total page size.&#x20;
{% endhint %}

Sometimes you'll want to install your own font. For example, you might need the design to follow a style guide for a particular brand and use a commercial font that they've licensed.

This guide shows you how to install a custom font in your app. Once a font is installed, it appears at the top of the list in the font dropdown, and it works the same way on both web and native mobile apps.

## Installing a custom font

You'll find the custom font section in the *Settings* tab, under *General*. This is where you add new fonts and remove ones you no longer need. Keeping fonts that aren't used in your app will slow down page load, so we recommend keeping only the fonts your pages actually use.

To add a font, upload the font file itself. Bubble accepts .ttf and .otf files. You don't need to write any CSS or host the font anywhere: upload the file and Bubble takes care of the rest.

<figure><img src="/files/d9VNSqzDPtaNIB5F7Fu9" alt="Upload button for installing custom fonts to your Bubble app" width="494"><figcaption><p>You can upload a single font file, multiple files or a whole folder for a font family.</p></figcaption></figure>

A font family usually comes as several files, one for each weight and style (for example, Inter-Regular.ttf, Inter-Bold.ttf, and Inter-Italic.ttf). You can upload them in a single step.

You have a few options:

* A single file on its own
* Several files at once
* A whole folder for a font family

Bubble reads each file, recognizes the font family it belongs to along with its weight and style, and groups the files under one family. Once uploaded, the family is available everywhere you choose a font: on elements, in styles, and in style variables. The weight dropdown for that family only offers the weights you've uploaded.

{% hint style="info" icon="mobile" %}
A font uploaded this way renders the same on your web app and your native mobile app, from a single upload. There's no separate step for mobile.&#x20;
{% endhint %}

## Moving from CSS-linked fonts (legacy)

Previously, you added a custom font by entering the path to a CSS file that referenced the font. Fonts you've already added that way will keep working on the web with no change.

Two things to know:

* You can no longer add a new font through a CSS file link. New fonts are added by uploading the font file, as described above.
* CSS-linked fonts don't render in native mobile apps. Only uploaded font files do.

To bring an existing CSS-linked font to mobile, or to move fully onto the upload method, upload the .ttf or .otf files for that family using the steps above, then remove the old CSS link.

{% hint style="info" %}
**Note on special characters:** This applies to the legacy CSS-link method only. Special characters in fonts aren't added automatically and will default to Times. You can add them manually when adding your custom font as a `<link>`. For example, if you're adding the following Google Webfont to your Bubble app:

{% code overflow="wrap" %}

```html
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700" rel="stylesheet">
```

{% endcode %}

We recommend adding a second link with any special characters you'd like to include, using the `&text=` parameter:

{% code overflow="wrap" %}

```html
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&text=%E2%86%90%E2%86%92%E2%86%96%E2%86%97%E2%86%98%E2%9C%93" rel="stylesheet">
```

{% endcode %}
{% endhint %}


# Responsive design

Responsive design is a method for giving your users a great experience no matter what kind of device they're using to access your app. Instead of building separate pages or views for different screen sizes, the goal is to create a single design that automatically adjusts its layout and content to fit the screen it's rendered on.

<figure><img src="/files/Ak1aUHyK9WeqL2SXi3No" alt=""><figcaption><p>Responsive design ensures your app looks as intended on all resolutions and screen sizes by adjusting the layout and content to fit properly.</p></figcaption></figure>

Responsive design matters for a few reasons:

* **It reaches the broadest range of users**, since they can access your app from all types of devices.
* **It reduces development and maintenance time**, since you're working with one design instead of many.
* **On the web, it helps search engines rank your pages higher**, since they favor pages that work well on all devices.

## Bubble's responsive engine

Bubble's responsive engine is built into the same visual editor you use to build your pages and views. It uses a grid-based layout system that adjusts how elements are arranged based on the screen size and orientation of the device.

The engine works the same way on both web and native mobile apps. The core concepts (containers, layout modes, spacing, alignment) apply identically across platforms. What changes is the range of devices you're designing for and the specific elements available on each platform.

An important point to keep in mind with responsive design is that it isn't just about resizing elements to fit the current screen. It's about making deliberate decisions: resizing some elements, hiding others, and rethinking parts of the UI entirely.

Think about apps you use every day. The mobile version isn't just a smaller copy of the desktop version. It adapts in many ways to feel natural on both platforms, from how content is organized to how users navigate through it. Great responsive design accounts for those differences instead of trying to force one layout to work everywhere.

For example, on a large desktop screen, a page might have a three-column layout with a navigation menu, main content area, and a sidebar. On a smaller mobile screen, the same page can rearrange itself into a single column, with a menu icon that opens a slide-in navigation. On native mobile, a view might have a horizontally scrolling row of cards that adapts to different phone widths, using safe areas to stay clear of hardware features like notches.

Understanding the engine at a conceptual level is the fastest way to master responsive design in Bubble. The specifics change by platform, but the tools you use to shape a responsive layout are consistent.

### Mobile web apps versus native mobile apps

An important distinction in this article is the difference between a native mobile app and a responsive web app that works on mobile. Keep these points in mind:

* A responsive web app adjusts its layout to fit the current screen size, regardless of the device.
* A responsive web app can be used on a tablet or smartphone, but that's not the same as a native mobile app. It's still a web app, accessed through a browser.
* A native mobile app is built specifically for iOS and Android devices and can't be accessed through a browser. In principle, native mobile apps follow the same responsive practices as web apps: views should adapt to different screens. On mobile devices, though, the gap between the largest and smallest screen is usually smaller than it is on the web.

## Responsive design on the web

Web apps span the widest range of screen sizes. A single page might be viewed on a 4K monitor, a laptop, a tablet in landscape mode, and a phone in portrait mode, all in the same day. Responsive design on the web is about making that single page look great across all of them.

The most common breakpoints to plan for are:

* **Desktop screens**, roughly 1200 pixels wide and up.
* **Laptop screens**, roughly 900 to 1200 pixels.
* **Tablets**, roughly 600 to 900 pixels.
* **Phones**, roughly 320 to 600 pixels.

Web-specific elements like repeating groups, popups, and floating groups all support the responsive engine, so you can build layouts that reflow naturally between these breakpoints. Common patterns include:

* Multi-column layouts on desktop that collapse to a single column on mobile.
* Navigation menus that turn into hamburger menus on smaller screens.
* Sidebars that hide entirely or collapse into overlays on phone-sized screens.

**Article series:** [Elements for web apps](/help-guides/design/elements/web-app)

## Responsive design on native mobile

Native mobile apps run on a narrower range of screen sizes than web apps, but that range still includes plenty of variation. Different phone models have different screen widths, aspect ratios, and safe areas, and users can rotate their devices between portrait and landscape orientations.

Responsive design on mobile is about handling these differences smoothly. The responsive engine works the same way as on web, but the elements you're working with are mobile-specific: vertical lists instead of repeating groups, sheets instead of popups, and views instead of pages.

Key considerations for mobile responsive design:

* **Safe areas.** Modern phones have notches, home indicators, and rounded corners. The safe area keeps important content clear of these features, and views can be configured to respect it.
* **Device variation.** Even within the same class of device, screens vary in width and height. Layouts should adapt to fit both compact and full-sized phones.
* **Orientation.** Users can rotate their devices between portrait and landscape, changing the aspect ratio significantly.
* **Content density.** Mobile screens are smaller than desktop screens, so layouts often need to prioritize the most important content and defer secondary content to overlays like sheets.

**Article series:** [Elements for native mobile apps](/help-guides/design/elements/ios-and-android-app)

## Containers

Every responsive layout in Bubble comes down to containers. A container holds your elements and decides how they behave when the screen size changes, whether that's stacking, resizing, or repositioning. If you want control over how your app looks on different devices, containers are where that control lives.

Each container has a layout mode that determines how its children are arranged, and a sizing method that determines how each child grows or shrinks. Set these two things correctly, and your design adapts on its own. You won't need to build separate versions of a page for different screens.

### Container layout modes

Regardless of platform, containers are the foundation of responsive design. Each container uses one of four layout modes to arrange its children:

<table><thead><tr><th width="147.7855224609375">Layout mode</th><th>What it does</th></tr></thead><tbody><tr><td>Column</td><td>Stacks children vertically (on top of each other). Widths adjust to fill the container, while heights fit each element's content.</td></tr><tr><td>Row</td><td>Arranges children horizontally, side by side. Heights adjust to fill the container, while widths fit each element's content.</td></tr><tr><td>Align</td><td>Positions children in one of nine fixed positions within the container.</td></tr><tr><td>Fixed</td><td>Places each child at a specific position, without automatic layout logic.</td></tr></tbody></table>

As you design your app, you will usually use a combination of these layout modes to get a design that looks the way you want, and adapts to different screen sizes.&#x20;

### Sizing and spacing

Elements in a responsive design can be sized using a mix of fixed values, percentages, and content-based sizing. The right choice depends on how the element should behave as the screen size changes:

<table><thead><tr><th width="166.16973876953125">Sizing method</th><th>What it does</th></tr></thead><tbody><tr><td>Fixed</td><td>Keeps the element the same size across all devices. Useful for elements that should never scale, like icons or small buttons.</td></tr><tr><td>Percentage</td><td>Scales the element with its container. Useful for elements that should grow or shrink with the available space.</td></tr><tr><td>Fit to content</td><td>Sizes the element based on what's inside it. Useful for elements like text or buttons that adjust to their content.</td></tr><tr><td>Fill container</td><td>Stretches the element to take up all available space in its container.</td></tr></tbody></table>

Spacing works the same way across platforms. Padding and gaps can be set on containers to control how much room elements have around them, and how much space sits between them.

### Nesting containers

A common approach is to nest containers inside each other. A page might have one container arranged in a column, with a row container nested inside it to hold a set of buttons side by side. Each container only has to manage its own children, which keeps the overall structure easier to follow, even as a page grows more complex.

Nesting also gives you more precise control. You can apply a different layout mode or sizing method to each level, so a section of your page can behave independently from the rest.

## Testing your design

Bubble's editor includes a responsive preview that lets you test how your design looks at different screen sizes without leaving the editor. On web, you can slide between screen widths to see how the layout adjusts. On native mobile, you can preview on different device models to check spacing, safe areas, and orientation.

Testing on real devices is still worth doing, especially for mobile apps. Emulators can catch most issues, but nothing beats seeing the design in the user's hand.

### Getting started

The next article covers the fundamentals of building responsive pages and views, including how containers, layout modes, and sizing work in practice.

## What is responsive design?

Responsive design is a method that gives your users a great experience no matter what kind of device they are using to access your app.<br>

\
Instead of having to set up separate pages for different devices, the goal of responsive design is to create a single page that automatically adjusts its layout and content to fit the screen size and resolution of the device being used to access it.

Responsive design is important for several reasons:

* It gives the broadest number of users access to your app as they can access from all types of devices
* It reduces development time since you don't need to design and maintain separate pages
* Search engines tend to favor pages that are accessible on all devices which can help these pages [rank higher](#user-content-fn-1)[^1]

## Bubble's responsive engine

Bubble features a design environment that lets you control every aspect of your app's responsiveness in the same visual editor that you use to build your pages. It uses a grid-based layout to change the appearance of the page depending on the screen size and orientation of the device.

For example, on a larger screen like a desktop computer, your page might have a three-column layout with a navigation menu, main content area, and a sidebar. But on a smaller screen, such as a smartphone, the same page might rearrange its content into a single column layout with a "hamburger" menu icon to access the navigation.

In the next article we'll explore the basics of how this design method works.

Article: [Building responsive pages](/help-guides/design/responsive-design/building-responsive-pages)

## Other ways to learn

<details>

<summary>Video lessons</summary>

We have several video lessons that can help you learn about designing responsive pages.

* [Understanding the column container layout](https://www.youtube.com/watch?v=VeJKByCma1k\&list=PLoNVJrdvQQYm8x9PXaRPiuXWa8AdJRNz6\&index=1)
* [Understanding the row container layout](https://www.youtube.com/watch?v=a-paDi5Nj5o\&list=PLoNVJrdvQQYm8x9PXaRPiuXWa8AdJRNz6\&index=2)
* [How to build a sidebar layout](https://www.youtube.com/watch?v=xPr2C-CcW_k\&list=PLoNVJrdvQQYm8x9PXaRPiuXWa8AdJRNz6\&index=3)
* [How to build a responsive navigation bar](https://www.youtube.com/watch?v=3lUlmTZ_IQo\&list=PLoNVJrdvQQYm8x9PXaRPiuXWa8AdJRNz6\&index=4)
* [Dragging and dropping elements in the element tree](https://www.youtube.com/watch?v=DJOcPN0O0Co\&list=PLoNVJrdvQQYm8x9PXaRPiuXWa8AdJRNz6\&index=12)
* [Layout and sizing properties in Conditionals](https://www.youtube.com/watch?v=ezxvJ2cj7R4\&list=PLoNVJrdvQQYm8x9PXaRPiuXWa8AdJRNz6\&index=13)
* [Padding in a container element](https://www.youtube.com/watch?v=rR1eI6Lv4_I\&list=PLoNVJrdvQQYm8x9PXaRPiuXWa8AdJRNz6\&index=14)
* [Scrolling overflowing content](https://www.youtube.com/watch?v=v4b9BYmHtNw\&list=PLoNVJrdvQQYm8x9PXaRPiuXWa8AdJRNz6\&index=15)
* [Gap spacing for row and column layouts](https://www.youtube.com/watch?v=6iIOE93gF1s\&list=PLoNVJrdvQQYm8x9PXaRPiuXWa8AdJRNz6\&index=16)
* [How to use the align to parent container layout](https://www.youtube.com/watch?v=c7vAhhAJO5o\&list=PLoNVJrdvQQYm8x9PXaRPiuXWa8AdJRNz6\&index=17)

You'll find our full Youtube playlist about the responsive editor [here](https://www.youtube.com/watch?v=VeJKByCma1k\&list=PLoNVJrdvQQYm8x9PXaRPiuXWa8AdJRNz6).

</details>

{% hint style="info" %}
Bubble's responsive engine is a feature that was introduced in 2022. In older applications you may need to activate the feature on a page-by-page basis and convert those pages to the new engine.
{% endhint %}

[^1]: Search engines rank public pages based on numerous factors, such as content quality, relevance, backlinks, user experience, and more.

    Optimizing these elements can improve your page's visibility and position in search results. This is known as Search Engine Optimization (SEO).

    Article series: [SEO](/help-guides/maintaining-an-application/seo)




---

[Next Page](/llms-full.txt/1)

