Creating a Data Structure

When you build an application, you will need to think about the structure of the data, and how it should be represented in the Editor. That's where defining types and fields is extremely important.

Types

The different types of data ("data types") are the most high-level concept you define when you build a data-driven application. You can think of them like a kind of thing that you want to have data about in the database. If we were building Instagram, for example, then we would have a 'User' type and a 'Post' type. Defining these types is the first step to describe what the database will contain, before defining how to describe these items.

Fields

Fields are the way you define how an entry of a given data type should be described. Effectively, this is where you save the data of a given entry. For instance, a 'Post' will have fields like 'photo', 'title', 'description', etc. Defining these fields will let you save the right data at the right place, and display it in your application.

When you create a field on a data type, you have to pick a type for the that field. For instance, a Post will have a 'photo' field of type 'image' and a 'title' of type 'text'. Most fields will use one of the Bubble-built-in field types:

  • text: used to represent a text (a chain of characters)

  • number: used to represent a number, with our without decimals

  • yes/no: used to represent a value that is yes, or no (boolean in traditional programming languages)

  • date: used to represent a specific date, i.e. a date and a time

  • geographic address: used to represent an address (on a map)

  • image, file

  • number interval

  • date interval

The field type will have important consequences on how you can use the data when you're building your app, or operate on the data. Fields of type text can be concatenated or truncated, numbers can be multiplied, addresses, can be shown on a map, etc.

In addition to the built-in field types, a field can have a type that is actually another data type. These types, called composite types, are a way to associate two entries in the database. For instance, a 'Post' is going to have a 'Creator', of type 'User'. You can think of this as a situation where a 'Post' "points at" or is "connected to" a particular 'User' in your database.

Lastly, it is important to note that when you create a new field on a type, you must specify if the field is going to contain a single value or a list of values. Note that lists can hold up to 10,000 items, and that items in a list are automatically de-duped.

Things

A thing, in Bubble, is an entry in the database. For instance, a specific 'Post' created by a user in our example. The word "thing" is used for convenience to name database entries. In the Bubble editor, you'll see in many places mentions to "things", and as soon as you define a type of content that can be applied in the context, "thing" will be replaced by the type name, for instance 'Post'.

Built-in fields

By design, Bubble adds a few fields to each type. These fields are the 'Created Date' and the 'Modified Date' of type date. For things that aren't users, a third field is 'Creator', which is of field type 'User'. For each thing that isn't a 'User', this field will link it to the user that was logged in when the thing was created. Every thing also has a 'slug', which is a shorthand, unique way to refer to a thing. Slug values are commonly used in URLs.

Each thing in Bubble also gets a unique ID assigned when it gets created. The unique ID has the format of a long string of seemingly random numbers with an "x" in the middle. Using this field isn't common, but in some cases, if you need a way to uniquely identify a thing, you can use the 'unique id' field.

Instead of adding and managing these fields yourself, you can rely on Bubble's data engine to create these fields, assign the right data when a thing is created, and use that data where you need it.

Adding types & fields

There are two main ways to create data types and fields in Bubble. You can either do this in the Data Tab, or on the fly when designing your interface or building workflows.

Note that type and field deletions aren't real deletions in Bubble. The reason is the data is still stored in the database, and the application needs to have some information about the type. If you delete a type or a field, it will get hidden from the lists of types/fields you can pick from. In other words, deleting is reversible. Clicking on 'show deleted types' or 'show deleted fields' from the Data Tab will reveal the deleted items, and a 'restore' button will appear next to each entry.

Setting a default value

The Data Tab also lets you define default values for each field. This value will be used when a new thing of a given type is created. For instance, if you want to set up an initial score to 10, you can type 10 in the text box. Note that this only applies to things created after setting up a default value, and once you delete the default value, things will not use the default value.

The User type

Bubble makes the assumption that your application will rely on a user management system, letting users sign up, login, log out, and take actions in your app. Therefore, each new Bubble application has a built-in 'User' data type that acts like any type that you would create with a few additional, key properties. If your application doesn't use users, this will not impact your application.

The 'User' type behaves in a very similar fashion to other data types that you define in your application. For instance, you can modify a user, delete a user, list them in a repeating group, etc. However, there are a few key differences. Users can be used to handle sessions and authentication in your application. In other words, the 'User' type can be used to know who is currently using the application.

The 'User' data type has a few built-in fields that you can use in your application. The most important and common will be the 'email' field, that contains the email the user used when he/she signed up, or the first email that can get fetched from an external service if you're using an external service to authenticate users. A second field is 'email confirmed', which is a way to know whether the user clicked a link to validate the ownership of an email account, if that action was used in your application design. This field returns a yes/no value.

The case of API data

If you use some plugins that connect to external APIs in your app, it is very likely these APIs will add some data types to your application. For instance, if you're connecting to Twitter, a type of data that Twitter returns if you make some calls to them is a 'Tweet'. These data types are quite similar to the custom data types defined above, as they can describe things and have some fields. In many places of the Bubble Editor, you will be able to use them similarly to above. For instance, if you're using a repeating group to display a list, you will be able to pick 'Tweet' as type of content. However, the main difference is that you cannot create things of this type (you cannot create a thing of type 'Tweet'), and you cannot modify them.

Connecting types

"How should I structure my database" is a common - and important! - question. Luckily, your Bubble database is quite flexible and there are many right answers. It's also easy to "fix" later on if you realized you made a wrong turn. This section walks through a few common patterns for connecting one data type with another, especially in cases where one Thing might relate to many Things of another data type.

Technical users might recognize some of this content as explaining how "foreign key" or "many-to-many relationships" work in Bubble. Bubble does not use these terms and these concepts don't exist in quite the same way in a Bubble database, but you can still easily achieve the same end results.

Use cases

When you define a new field on a data type in your app's Data tab, you can specify whether it's a text, number, yes/no, or even another data type. You can also specify whether that field should just have one value of that type, or a list of values of that type.

Examples of when a field is a (singular) value of another data type:

  • A BlogPost data type has a Creator field which is a User

  • An Event data type has a Venue field which is a Building

  • A JobCandidate data type has a Degree From field which is a School

  • A Community data type has a State field which is a State

Examples of when a field is a list of values of another data type:

  • A BlogPost data type has a field for Tag which is a list of Tags

  • A Project data type has a field for Follower which is a list of Followers

  • A User of your marketplace app has a field for Favorite which is a list of Properties that they starred

  • A Contractor in your dispatching app has a field for Job which is a list of Jobs, past and present

For those coming from a technical database background, in Bubble you don't have to declare whether a relationship is "one-to-many" or "many-to-many". If you have data type A with thing a and data type B with thing b, and A has a field that's of type B, Bubble doesn't care how many a's are connected with a given b. You just have to tell Bubble that the field is type B, and whether it's a single or list of values.

Example scenarios

Let's run with the example of a Bubble app that's a blog, with the Post and Tag data types. An individual Post can have many Tags, and of course a Tag can be used on many Posts. How do you set this up?

The answer is "it depends" - like other areas of Bubble, you have the flexibility to build this in different ways, but there are tradeoffs that we'll cover here.

Here are the different major options you could use to create this relationship:

Option 1: Each Post has a Tag field that's a list of Tags

This means that "Blog Post #123" might have Tags "Cooking", "Travel" and "Fun", all stored on the Tag field of Post.

The pro with this approach is that when you have a Post and want to look up which Tags it has, that's easy! Let's say you have a page with assigned data type of Post (each Post on its own page, for example) - to get the Tags would be Current Page's Tags. This is a quick query.

The con with this approach is that if you have a Tag and want to look up which Posts have that Tag, the query is a bit more roundabout. Let's say each Tag also has its own page that lists all the Posts with that Tag. To find that list of Posts, you'd have to Do a search for Posts with a filter for Tag contains Current Page's Tag. This is a slower query (for most normal-sized blogs, this is probably not noticeably bad from a performance standpoint, but you can imagine parallel situations that have much bigger scale).

Option 2: Each Tag carries a list of Posts that have that Tag

This means that Tag "Cooking" has a field with "Blog Post #123", "Blog Post #153", "Blog Post #89", and so on.

The pros and cons here are the inverse of Option 1 above. The query to get all Posts of a particular Tag is easy, but to find all Tags on a single Post is a slightly longer query.

Option 1+2: You could do both of the above!

Each Post keeps track of which Tags it has, and each Tag keeps track of which Posts have that Tag.

There's nothing stopping you from doing both of the above options! This way it's quick to get all the Tags on a given Post and also quick to get all the Posts of a given Tag.

The downside here is that you need to do double the work. Each time a Post gets assigned a new Tag, you need to remember to add it to the Post's list of Tags and also the Tag's list of Posts. Not too much extra work, but it can be easy to forget to do this, leading to data consistency problems.

Option 3: A new PostTag data type that has one field for Post and one for Tag

This means that you create a new PostTag thing to handle each Post + Tag pairing. So one PostTag for "Blog Post #123" + "Cooking", another PostTag for "Blog Post #123" + "Travel", and another PostTag for "Blog Post #123" + "Fun".

(Technical folks may recognize this as a "joining table".)

This is kind of the "compromise" solution between Options 1 and 2. To find all the Tags of a Post, you'd have to Do a search for all PostTags where Post = Current Page's Post, or the opposite to find all the Posts of a Tag. This makes the search in both directions a bit slower than the fast search offered by Option 1 or 2. Also, note another small disadvantage is that it's possible to create duplicate PostTag entries, whereas if you were using a list in Option 1 or 2, Bubble automatically de-dupes entries in a list.

Why would somebody choose this option then? The answer is that it's much more scalable. If a data type has a list of things for a given field, that list maxes out at 10,000 entries. This may seem like a lot, but imagine, for example, how many Pinterest posts one User might heart over their lifetime! Option 1 or 2 would break at that scale, but Option 3 would handle it fine.

In practice, if you're creating this kind of relationship and expect one thing to have a list of more than 100 values of another thing, we recommend going with Option 3.

To summarize, the option you choose will depend first on the scale you need for your Bubble app (which might determine if Option 1 or 2 is even an option) and second on which direction(s) you might want to make queries super fast. Note that you could also start with Option 1 or 2 then later migrate to Option 3).

Special example: when friendships are complicated

Many apps might want to build a 'mutual' relationship, like a User being able to friend another User. The same options generally apply, but things just get a little confusing because everything is done with the same data type! In this case, you could go with Option 1 or 2, but if Users Alice and Bob are friends, it will get confusing if Bob is in Alice's list of Friends and not vice versa. Option 1+2 is a better way to go. For Option 3, you may want to modify the setup a little bit - instead of one field for one User and a second field for the other, instead you may want to have one field for "Friendship Members" that's a list of Users - this puts Alice and Bob "on the same footing" so to speak, so you don't have to worry about who's "Friend1" vs "Friend2". (Since social networks tend to have a lot of such relationships, we recommend trying Option 3 for this case.)

Type inconsistencies and issues

As previously mentioned, the Issue Checker helps you identify inconsistencies in your application. Many inconsistencies that you will have to fix when designing your application will be type inconsistencies. When an element or an action is expecting a given type, and you're using an expression to define the data that leads to another type, the Issue Checker will flag this as an issue to fix. Fixing these issues is very important to get the expected behavior of your application in run mode.

For instance, let's say you are using a group whose type of content is a 'Car'. This means the group (and the elements inside) will be referring to a particular 'Car' to display its picture, name, price, etc., in Run-mode. Now let's say you have an action that displays data in that group (in other words, that define which thing should be displayed in the group). This action should display a thing of type 'Car'. If you design your action in a way that in Run-mode, the thing you're trying to display in the group is evaluated as a 'User', there will be a type inconsistency. The Issue Checker will catch this in the editor.

Such type inconsistencies can also happen with field types. For instance, if you are using a 'Charge the current user' action to charge a user's credit card, one of the fields for this action is going to be the 'amount'. This field, naturally, should be a number. If you use a dynamic expression to define this amount, and if it evaluates to a text or an address, for example, then the Issue Checker will flag this as an issue to resolve. You can hover over a dynamic expression to see what it is evaluating to.