# Introduction to UI Bakery

What is UI Bakery and how does it work?

Imagine building your internal app as your idea flows?

Meet UI Bakery :doughnut: - a platform for creating internal software with *two ways* to build apps:

* **Low-code experience** (drag-and-drop + AI) - Connect your data, assemble your UI with drag-and-drop, use our [Custom App](/build-from-scratch/custom-app) and [Custom components](/concepts/custom-components-2.0) features to help you quickly build what you need with AI, and deploy the result app to your users.
* **AI-only experience** - Generate fully functional apps from plain-text or visual prompts, customize the React code directly or ask AI for changes, and deploy the app instantly and securely.

UI Bakery also ensures end-to-end security for our clients being **SOC 2 compliant**. Through our [Trust Portal](https://app.drata.com/trust/20bd3eb9-06a7-43c8-a64d-de309433e20a) you can request our latest SOC reports, penetration test (pentest) reports, and details about our internal security policies.

## How it works

### Low-code experience

1. **Build the UI** - Use our built-in components, such as Table, Form, Details, Chart, etc. and custom component and App features. Connect actions to UI elements.
2. **Connect a data source** - Use native connectors to databases (PostgreSQL, MySQL, MongoDB), business apps (Stripe, Hubspot, Airtable), or any HTTP API.
3. **Load data and build logic** - Load and send your data using Actions. Add navigations and conditions and map your data with custom code and third-party libraries.
4. **Publish the app and invite users** - Deploy your web updates and share the app with users.

### AI-only experience

1. **Start from a prompt** - Describe your app, attach visual examples, if you want, and AI will generate it in minutes.
2. **Connect your data** - Use our hosted databases or connect your own.
3. **Customize the app** - Edit React code directly or ask AI to make the necessary changes.
4. **Publish the app** - Release the app, choose the environments to deploy to, and share it with specific users or make it public.

#### Compare modes at a glance

| Feature              | Low-code experience                              | AI-only experience                 |
| -------------------- | ------------------------------------------------ | ---------------------------------- |
| **How you start**    | Assemble screens and logic visually              | Describe your app in plain text    |
| **UI creation**      | Drag & drop components + custom app & components | AI generates full AI automatically |
| **Data connections** | Databases, 3rd-party services and APIs           | Postgres, MySQL, MS SQL or API     |
| **Customization**    | Customize with JavaScript                        | Full React code access             |
| **Security**         | SOC 2, SSO, RBAC, audit logs                     | SOC 2, SSO, RBAC, audit logs       |

{% hint style="success" %}
You can always try one mode first and then switch to another seamlessly in your [Workspace settings](/concepts/workspace-management/account-and-organization#switching-between-ai-and-low-code-modes).
{% endhint %}

## Why choose UI Bakery?

* **Save development time** - No need to design layouts, tweak CSS, or maintain pipelines.
* **Flexible and customizable** - Extend your apps with JavaScript or React components.
* **Secure by design** - SOC 2 compliant, RBAC, SSO, and end-to-end data privacy.
* **Multiple ways to work** - Mixed experience for building from scratch and AI-only experience for more speed and flexibility.

UI Bakery supports both developers who prefer visual builders and teams ready to harness the power of AI. With us, you can build, iterate, and scale internal apps faster than before.

## Quick links

<table data-view="cards"><thead><tr><th data-type="content-ref"></th></tr></thead><tbody><tr><td><a href="/pages/VUlkTLHNkT3LjdxGFIgU">/pages/VUlkTLHNkT3LjdxGFIgU</a></td></tr><tr><td><a href="/pages/n5USYezr9m2jwqYeROGA">/pages/n5USYezr9m2jwqYeROGA</a></td></tr></tbody></table>


# Build from scratch

Build an app manually in the Low-code mode.

## Get an overview

<table data-view="cards"><thead><tr><th data-type="content-ref"></th></tr></thead><tbody><tr><td><a href="/pages/YsUqVWSq4zsnMo1HhfHM">/pages/YsUqVWSq4zsnMo1HhfHM</a></td></tr><tr><td><a href="/pages/sa4TsNCkdCrSHoJp0tZu">/pages/sa4TsNCkdCrSHoJp0tZu</a></td></tr><tr><td><a href="/pages/coZQU2EJraeczce2zpJe">/pages/coZQU2EJraeczce2zpJe</a></td></tr></tbody></table>

## Build your first app

<table data-view="cards"><thead><tr><th data-type="content-ref"></th></tr></thead><tbody><tr><td><a href="/pages/8gLA2NWJxl0I7AzUpJpo">/pages/8gLA2NWJxl0I7AzUpJpo</a></td></tr></tbody></table>

## Discover AI features

<table data-view="cards"><thead><tr><th data-type="content-ref"></th></tr></thead><tbody><tr><td><a href="/pages/OmipSDRv8A00o7rHBfWw">/pages/OmipSDRv8A00o7rHBfWw</a></td></tr><tr><td><a href="/pages/QGHmGMDQd75v4RgQZR2f">/pages/QGHmGMDQd75v4RgQZR2f</a></td></tr></tbody></table>


# Video intro

Want to know more about low-code building with UI Bakery? Go ahead and check out this overview video👇

{% embed url="<https://www.loom.com/share/5444b4c82de34b03b3f6c68bc5093f78?sid=9b893ab4-a3b3-4121-924d-cabba97e36af>" %}

{% hint style="info" %}
You can adjust the playback speed in the player settings to match your preferences.
{% endhint %}


# Main features

## Drag’n’drop responsive UI

Build interfaces of any complexity utilizing our components, without the need to learn CSS and JS frameworks.

{% @arcade/embed flowId="SNE2tlWTwriXUlm3pHkr" url="<https://app.arcade.software/share/SNE2tlWTwriXUlm3pHkr>" %}

***

## Code and no-code business logic

Effortlessly CRUD your data, add conditions, iterate through it, and debug with UI Bakery Actions.

{% @arcade/embed flowId="5Bp1SHYxc9MkkYdMrqFM" url="<https://app.arcade.software/share/5Bp1SHYxc9MkkYdMrqFM>" %}

***

## Deploy with a single click

Invite your users and instantly ship mission-critical updates to them.

{% @arcade/embed flowId="Gmt783iFHRGoc62FfKha" url="<https://app.arcade.software/share/Gmt783iFHRGoc62FfKha>" %}


# Glossary

<details>

<summary><mark style="color:blue;">Data source</mark></summary>

A connection set up from a server to a database on top of which you build your internal tool. It can be a database, an API, or a third-party service (for example, MySQL, Google Sheets, Airtable). You need to connect a data source and configure authentication to it to ensure a secure connection between the UI Bakery back end and your data.

✅ *UI Bakery doesn’t store your data. We only keep the encrypted credentials to access a data source.*

</details>

<details>

<summary><mark style="color:blue;">Action</mark></summary>

A piece of business logic implemented in your application. You can use it to load the data from a data source, send the data back, make API calls, navigate to app pages, generate PDF documents, and process any type of data with SQL or JavaScript.

Action results are available as variables `{{actions.actionName.data}}` that can be assigned to specific properties of components or referenced in other actions.

</details>

<details>

<summary><mark style="color:blue;">Action steps</mark></summary>

Small tasks of various types, such as executing an SQL query, running custom code, making an HTTP request, evaluating a condition or navigating to a different page. By combining multiple action steps developers can construct functional workflows, consolidate requests to various data sources, validate input data, or reload data based on specified conditions.

Action steps that have been given a name can also be referenced as `{{steps.name.data}}` within the parent action.

</details>

<details>

<summary><mark style="color:blue;">Component</mark></summary>

A UI element that can display your data and accept input from your users. You can work with various components: Table, Form, Detail, Chart, PDF Viewer, and others. Drag and drop any component you need to the working area to add it to an application page.

You can use most of the components as variables, such as `{{ui.componentName.value}}` or `{{ui.componentName.selectedRow.data}}`. They can be referenced within the application in actions or the properties of other components.

</details>

<details>

<summary><mark style="color:blue;">Triggers</mark></summary>

Events that enable handling user interactions within the application. Examples of triggers include **On Form Submit** and **On Table Row Select**. Actions can be linked to triggers and utilised to process data, send it to a database, and perform other operations.

</details>

<details>

<summary><mark style="color:blue;">Variables</mark></summary>

Act as a connecting element allowing you to use data from actions in components and vice versa. Actions, action steps, and components all provide various variables that can be accessed using curly braces `{{ }}`. Additionally, developers can create their own custom global or local variables, assign values using the **Save to State** action step, and use these variables in other actions and components.

:information\_source: *Bring out a list of variables in any code or text field in UI Bakery by typing two curly braces* `{{`.

</details>


# Getting started

Building an app with UI Bakery is easy and pretty straightforward. You can start building your internal app following the basic flow and make any other improvements to it later on.

{% hint style="success" %}
You can also try our [Custom App](/build-from-scratch/custom-app) and [Custom components 2.0](/concepts/custom-components-2.0) features and let the AI build the application or components you need quickly and easily.
{% endhint %}

Ready to dive in? Go ahead then:

<table data-view="cards"><thead><tr><th data-type="content-ref"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><a href="/pages/4kivuVJPi9ep53CFmFhr">/pages/4kivuVJPi9ep53CFmFhr</a></td><td><a href="/pages/4kivuVJPi9ep53CFmFhr">/pages/4kivuVJPi9ep53CFmFhr</a></td></tr><tr><td><a href="/pages/AhwM3EatEwxXI1p7Yrap">/pages/AhwM3EatEwxXI1p7Yrap</a></td><td></td></tr><tr><td><a href="/pages/tVAsJAv2QbYBCo5JxCxS">/pages/tVAsJAv2QbYBCo5JxCxS</a></td><td></td></tr><tr><td><a href="/pages/5R2jpaQx1JzbB63uUrtI">/pages/5R2jpaQx1JzbB63uUrtI</a></td><td></td></tr><tr><td><a href="/pages/xuGQMEOWVqzF72TCTp6F">/pages/xuGQMEOWVqzF72TCTp6F</a></td><td></td></tr><tr><td><a href="/pages/A1K6ffkaBf0Sq0hH1au3">/pages/A1K6ffkaBf0Sq0hH1au3</a></td><td></td></tr><tr><td><a href="/pages/AZvlphkdggeoKJVezeHj">/pages/AZvlphkdggeoKJVezeHj</a></td><td></td></tr><tr><td><a href="/pages/5RsqcNvBk7ycIZtvNNu0">/pages/5RsqcNvBk7ycIZtvNNu0</a></td><td></td></tr><tr><td><a href="/pages/siCSwG0zacSY8rTtItMh">/pages/siCSwG0zacSY8rTtItMh</a></td><td></td></tr><tr><td><a href="/pages/7jXcLLPOnx7EK7voy6Sd">/pages/7jXcLLPOnx7EK7voy6Sd</a></td><td></td></tr><tr><td><a href="/pages/bsZIJf3gka5cT0o5xnyH">/pages/bsZIJf3gka5cT0o5xnyH</a></td><td></td></tr><tr><td><a href="/pages/MUVIR5XGI0MlqIZw2Xhd">/pages/MUVIR5XGI0MlqIZw2Xhd</a></td><td></td></tr></tbody></table>

## Webinars

:tada: To explore our product further and find out more useful tips & tricks from our UI Bakery experts, be sure to subscribe to our [Youtube channel](https://www.youtube.com/channel/UCBzEgLANy2nbx52LTBsDfIw).

## Get help

If something is missing from our docs, feel free to ask our UI Bakery **AI assistant** or in our **Live Chat**, or contact us at <support@uibakery.io> 🤓 Our team is always willing to help.


# Create an application

After you sign up for UI Bakery, you can start building your application. As an admin of your workspace, you can invite team members and other users to it, create apps together, share access, and manage roles and permissions. All the apps created within your workspace are available to all users based on their permission level.

To start building a new app, click the **+ button** in the *Apps* section of the workspace menu and select **App**. You can choose an icon and color for your application. Another option is to create a new app from [template](https://uibakery.io/templates) via **+ > From template**. You can select a template from the list of available ones or select a blank template to start from scratch. \
You can also check our [repository](https://github.com/uibakery-templates) for even more templates!&#x20;

{% embed url="<https://demo.arcade.software/LNmTMOwfd8n3Ngq5Vzj2?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}

The app you created will immediately open in the **Edit** mode.

***

From the app creation menu here, you can also **import an app** from a ZIP archive or a linked GitHub repository. It gives you the flexibility to save and move your data without having to recreate apps across different workspaces.

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

We have a separate article on exporting/importing apps if you feel like exploring it further :point\_down:

{% content-ref url="/pages/WnV1meqiDIvy2sMhzsUM" %}
[Export & import an app](/concepts/export-import-an-app)
{% endcontent-ref %}


# Build UI

The next step is to start building your UI. For this purpose, you can use a variety of components available in the **Components** tab of the left side panel. Simply drag the component you need and drop it to the working area. One of the most common cases is using the **Table** component together with the **Form**.

{% embed url="<https://demo.arcade.software/P6UC9vxnRHknzTbpTShK?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}

When you select a component, you can access its **properties** displayed in the right side panel. Here, you can change any properties you want, for example, adjust table height, hide, delete or change the order of table columns, and so much more. You can also specify the settings for each specific column, as shown in the screen below.

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

Now, if you're ready to proceed, move on to the [next step](/build-from-scratch/getting-started/link-components) then. If you'd prefer not to for now, check out the section below :point\_down:

<details>

<summary><mark style="color:blue;">Mocking data</mark></summary>

In our flow here, as you've probably noticed, we haven't connected our data source yet. We first started building UI, and the components we added are not displaying the data from our data source.

And we want you to know that it's totally okay :slight\_smile: \
Sometimes, you may not want to immediately connect your data source to UI Bakery. There could be various reasons for this, such as the data source not being publicly accessible, having certain security restrictions, or simply feeling a bit lazy. We completely understand these situations as we've experienced them ourselves!&#x20;

If that is the case for you, have a look at a couple of [common methods](/build-from-scratch/getting-started/build-ui/mocking-data) demonstrating how you can mock your data in UI Bakery.

</details>


# Data mocking methods

## :heavy\_check\_mark:Custom Code action

The simplest way to mock data is to create an action of the JavaScript Code type, that will return the needed JSON object. For instance, if your API/DB table lists cars, you can do it in the following way:

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

The action can be then referenced in different UI Bakery fields using standard `{{actions.newAction.data}}` approach.

If you would also like to emulate the latency when requesting your data source, you can use **Promises** and **setTimeout** to return your data. For instance, your JS action can have the following code:

```javascript
const fakeData = { id: 1, car: 'Mitsubishi', car_model: 'Montero', car_color: 'Yellow', car_model_year: 2002, car_vin: 'SAJWJ0FF3F8321657', price: '$2814.46', availability: false };
const delay = 2000;

return new Promise((resolve, reject) => {
  setTimeout(() => resolve(fakeData), delay);
});

```

{% hint style="info" %}
One of the major benefits of using JavaScript Code action step is that you can easily replace your action with real data by simply changing the action type when you connect a real data source.
{% endhint %}

## :heavy\_check\_mark:State variables

State variables are a great way of mocking data when you not only want to mock reading data but also writing. Find out more about state variables:

{% content-ref url="/pages/jVs6hjp33tabG7HBAXkj" %}
[State variables](/concepts/app-state-variables)
{% endcontent-ref %}

## :heavy\_check\_mark:Google spreadsheets instead of SQL databases

SQL databases are often the data sources that people are most reluctant to expose publicly. Fortunately, Google spreadsheets function in a manner very similar to SQL databases within UI Bakery. This allows you to conveniently create a spreadsheet, where each sheet can be considered a table, and the cells in the first row can act as SQL columns:

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

After the spreadsheet is created, you can [connect it as a data source](/build-from-scratch/getting-started/connect-a-data-source) and [create actions](/concepts/actions/action-basics#creating-an-action) to retrieve and write data to it.

## :heavy\_check\_mark:Mocking HTTP API with JSON-server

JSON-Server is an **npm package** that you can run locally or on a remote server which provides a simple interface to create fake JSON API. You can create Mock API in three easy steps:

1. Install JSON-server package: \\

   ```
   npm install -g json-server
   ```
2. Create `db.json` file with similar format:\\

   ```
   {
     "posts": [
       { "id": 1, "title": "json-server", "author": "typicode" }
     ],
     "comments": [
       { "id": 1, "body": "some comment", "postId": 1 }
     ],
     "profile": { "name": "typicode" }
   }
   ```
3. Run JSON server:\\

   ```
   json-server --watch db.json
   ```

## :heavy\_check\_mark:UI Bakery's Test data sources

Don't forget that you can always use UI Bakery's Test data sources which are available right in the **Data Source** connect dialog.

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

### Extra 1: Using UI Bakery's self-hosted version

When your data sources are only accessible from a local network, you can also install UI Bakery's self-hosted version and access it from there. UI Bakery self-hosted is easy to install and run - check it out:point\_down:

{% content-ref url="/pages/ZnFkZzq8gpiNThg62q0I" %}
[UI Bakery on-premise](/on-premise/ui-bakery-on-premise)
{% endcontent-ref %}

### Extra 2: Using NgRok to proxy data sources

[NgRok](https://ngrok.com/) is a product that creates a secure tunnel from your data source to the internet. Learn more:point\_down:

{% content-ref url="/pages/mhjzji95IKuysN60tyfG" %}
[Connecting local database via ngrok](/concepts/data-sources/connecting-local-database-via-ngrok)
{% endcontent-ref %}


# Link components

So far in our flow we haven't been using the Form component, but in this article we will explore how you can link a *Form* with a *Table* and share data and state between them. One of the most common scenarios here may be when you want to display a selected row of a table inside a form. So how can you do that?

Start by dragging and dropping the **Form** component into the working area, if you haven't added it yet. Then, you need to reference your table component and use the data of the selected row property inside the *Data* field of the Form component. Simply start typing double curly braces to access the selection of variables.

{% hint style="info" %}
It's worth noting that all components you add to the working area receive a unique ID that you can use to reference them.
{% endhint %}

Now, when selecting a row in the table, the data from this row will be displayed separately in the form.

{% @arcade/embed flowId="ePsvDYLqWEJVKRmUX6sV" url="<https://app.arcade.software/share/ePsvDYLqWEJVKRmUX6sV>" %}


# Connect a data source

Now that you've built your UI, it's time you connected a data source. First-time users will have to do it following the instruction below. Next time though, since data sources are reusable across all your applications, you will simply have to select a previously connected data source in the list of Data sources and start using it right away.

## **To connect a new data source:**

1. Go to the **Data Sources** tab in the left side panel and click **Connect**.
2. In the window that opens, select the data source type.

{% hint style="info" %}
Use the **Search** bar to quickly find the data source you need or choose from *Popular* and our *Sample* ones.
{% endhint %}

3. Enter a name to identify it within your app and specify all the required **connection settings** fields, such as a list of credentials, an API URL, a Google sheet link, and others.

{% hint style="success" %}
UI Bakery doesn’t store your data. We only keep the encrypted credentials to access a data source.
{% endhint %}

4. Next, click **Test connection** to check if the configuration is correct.

{% hint style="info" %}
For specific data sources (for example, **HTTP**), you can proceed with connecting the data source without testing the connection.
{% endhint %}

5. (Optional) If you choose not to connect your data source immediately, you can use test MySQL or HTTP data sources instead.
6. To continue, click **Connect data source**.\
   Once connected, a list of tables available in the data source will be displayed. You can uncheck the unnecessary tables or change their properties and titles. They will be put in **Table/Form** titles by default when you use them.

{% hint style="info" %}
For HTTP data sources, no resources will appear in the list unless UI Bakery can extract them from the API schema.
{% endhint %}

That's it! The newly created data source will be added to the list of all available data sources. You can edit its settings or remove it from the list, if necessary.

{% hint style="danger" %}
Keep in mind that modifying or deleting a data source impacts all applications that use it.
{% endhint %}

{% @arcade/embed flowId="1KTRCS9pa3EEsC4IHf12" url="<https://app.arcade.software/share/1KTRCS9pa3EEsC4IHf12>" %}


# Load data

You’ve successfully connected your data source and now you can start loading data from it to display it or send it to your API or database. One of the most common operations you would do here is *loading a list of objects*. In order to do this, you have to create and execute a **Load Table** action.

## **To run a Load Table action:**

1. Click **Create Action** in the **Actions** tab.
2. Select the data source you need to load data from.
3. Depending on the data source selected, specify the necessary parameters.

{% hint style="info" %}
For example, select the necessary table or enter an API URL path. It will be added to the URL you’ve specified in the HTTP data source settings.
{% endhint %}

4. Next, click **Execute action**.\
   Your data will be displayed in the **Result** tab and errors (if any) will appear in the **Logs**.

{% embed url="<https://demo.arcade.software/HmWZNaDxf7zinPu99mWO?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}


# Bind data to UI

Now that you've fetched the required data from the data source, you need to bind the data from your executed action to the Data field of the respective component.&#x20;

In our case, we simply selected the **Load Users** action in the Data field of our Table component.

{% hint style="success" %}
For such components as **Table**, **Chart**, **List View**, and **Grid View**, the Data field is a dropdown. You just have to click it to see all available actions and variables - *Suggested*, *Page*, and *App -* and easily switch between them. The component structure will automatically regenerate based on your selection.
{% endhint %}

If you need to undo the changes, you can revert component structure to its previous state by clicking *Undo* in the toast that appears at the top or clicking the *Revert* button next to component structure.

{% @arcade/embed flowId="ex8KtZc2n8wxyqgqJTgU" url="<https://app.arcade.software/share/ex8KtZc2n8wxyqgqJTgU>" %}

***

The **JS mode** is still available for the Data field of *Table*, *Chart*, *List View*, and *Grid View* so you can switch to it if you prefer. In this mode, you can bind your actions to components manually, like before, and also manually regenerate component structure using the **Generate structure** button next to the *Columns* section.

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

#### :information\_source: The *Generate structure* button works in the following way:

When you click it, UI Bakery takes the first object from the data property and, based on the name and format of the data, suggests what type of column or input (for a Form) should be generated. This way, if the Table structure has already been generated and you want to change only the data itself, you have two options: either use the *Generate structure* button or manually add columns to the table.\
If you have a large number of columns, this button action will generate only about 20 columns and show only around 8. If you need more, you will have to add them manually.


# Transform data with JavaScript

UI Bakery allows transforming the data that comes from a database or an API by writing JavaScript code. You can do this by adding another action step inside your action and adding JavaScript code that will do what you need. Below, we'll show you how to do that based on the use case of adding a *Full name* property as a concatenation of the First and Last name properties.

## **To transform your data with JS:**

1. Go to your action (for example, *Load Users*) and add another action step of the **JavaScript Code** type.
2. Next, write your code in the JavaScript field.\
   In our case, we added the following code:

```javascript
return data.map(item => {
  return {...item, full_name: item.first_name + " " + item.last_name };
});
```

3. Click **Execute action**.

Your new property will be added to the result of the *Load Users* action as well as to the list of table columns. All components connected to this action will now also receive a new data set.

{% hint style="info" %}
You may need to regenerate the structure of your component for the new property to appear.
{% endhint %}

Now, you can add another column to the table that will display the Full name of the user. You can also change the order of the columns and hide the ones that display First and Last name separately.

{% embed url="<https://demo.arcade.software/FraqhELNIQADHoo3rcfr?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}

That's it! Now let's proceed to the [next step](/build-from-scratch/getting-started/change-component-data) and find out how you can change your component data.

***

If you want to learn more about data mapping and transforming, have a look at this article :point\_down:

{% content-ref url="/pages/FlXv8DDV9LepOxZWK1Z5" %}
[Data mapping & transforming](/build-from-scratch/getting-started/transform-data-with-javascript/mapping-and-transforming-data)
{% endcontent-ref %}


# Data mapping & transforming

Sometimes the data returned by a data source or an API is structured not in a proper way for UI Bakery to use it inside components. You may need to reformat your data or enrich it with other properties. You may also need to make some live data calculations before you display it. All this can be achieved with *UI Bakery Actions*.

## Accessing a nested object property

When retrieving a list of items, a lot of APIs may return an object that has a structure similar to this:

```javascript
{
  length: 10,
  records: []
}
```

The actual list here is placed under the nested `records` or another key. If we need to display this list in a Table component, we need to transform it before passing it to the Table.

### Transforming HTTP responses

To transform HTTP responses, we recommend creating a **separate code action step**. In our example here, we want to transform the following object response into an array response:

```javascript
{
allFilms: Object { films: Array[6] }
}
```

We simply need to add a JS code action step to our action and specify the following code:

```javascript
return {{data}}.allFilms.films;
```

{% hint style="info" %}
`{{data}}` in this case represents the result of the HTTP request.
{% endhint %}

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

Action steps are executed sequentially, which means that any step has access to the result of the previous step execution. The`{{data}}` variable keeps the result and the`{{error}}` variable keeps the caught error that may occur during the step execution.

As a result, the action will return an array of items that can be easily connected to a Table or other components.

## Transforming list object properties

In case you need to transform, rename, or access nested object keys, you can use a **JavaScript** `map` **function**.

For example, your API returns a list of items that have a nested object:

```javascript
[
  {
    id: 25,
    price: 1000,
    user: {
      name: 'John',
      email: 'john@mycompany.com'
    }
  }
]
```

&#x20;But you need a particular property, not the whole entity, so you need it to look like this:

```javascript
[
  {
    id: 25,
    price: 1000,
    name: 'John',
    email: 'john@mycompany.com'
  }
]
```

To transform a list of objects like this, use a separate **code action** step with the following JavaScript function:

```javascript
return {{data}}.map(item => {
  return {
    id: item.id,
    price: item.price,
    name: item.user.name,
    email: item.user.email
  };
});
```

{% @arcade/embed flowId="pnJ6dCjiLv99uCGWZXU1" url="<https://app.arcade.software/share/pnJ6dCjiLv99uCGWZXU1>" %}

You can also use a shorter version. It'll keep the original object but will also copy over the user properties to the first level of the object:

```javascript
return {{data}}.map(item => {
  return {
    ...item,
    ...item.user
  };
});
```

In the same way, you can do calculations, rename properties or add additional fields to the response.

## Loading a single object of the list

When an API returns a list of objects but you need only one object from this list, you can transform it using JavaScript syntax for accessing array objects by object index:

```javascript
return {{data[0]}};
```

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

In this case, only the first item of the list will be returned. This is helpful when you are working with an **SQL query** step and you need to receive only one item.

## Receiving the header of the HTTP response

If you need to get the header from the HTTP response, switch the *Transform result* toggle and specify the `{{res}}` variable in the *Modify the result* field. Then, run the action.

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

## Converting JSON into a JavaScript object

If the returned item holds a `string/JSON` representation of your data, you will need to parse it to a JavaScript object (using a `JSON.parse` function) before passing it to UI Bakery components:

```javascript
JSON.parse(data['your_item'])
```

:information\_source: Please note, that `JSON.parse` will fail against some empty values as well as non-valid JSON strings, so the safe statement will look like this:

{% code overflow="wrap" %}

```javascript
const yourItem = data['your_item'] ? JSON.parse(data['your_item']) : {};
```

{% endcode %}

If you are not completely sure whether the server returns a valid JSON string, you can extend it to this version:

```javascript
const yourItem = {};
try {
  yourItem = JSON.parse(data['your_item']);
} catch (e) {}

```


# Change component data

Once you've bound your data to a component, it doesn't mean you can't change it - say replace a list of Users with a list of Orders. You just have to follow the same flow as you did before when you were running your *Load Users* action.

Just to remind you, first you need to create a new action to fetch your data and select a new table you want to load, for example **Load Table > orders**. After you execute the action, simply bind the new data from the action to the Data field of the respective component.

As simple as that! The component will be regenerated based on the data structure of the new action data.

{% embed url="<https://demo.arcade.software/epx7glGBHdf6MZjGcuf6?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}


# Send a form

Next step is to learn how you can modify the data in the Form and push it back to the data source.&#x20;

## **To send a form:**

1. Select your **Form** and navigate to the **Triggers** section.
2. Select **On Submit** in the first dropdown menu, and next click **Create action** in the **Select action** menu.

<figure><img src="/files/1mtzunI1YISkkJMmjlir" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
The new action you create will be bound to the **On Submit** trigger of the form thus enabling sending data to the data source.
{% endhint %}

3. For the new action, select your data source and the appropriate step, for example, **Update row.**
4. Choose the table, if needed, and specify the field and values that you want to update.

{% hint style="info" %}
You can also use the HTTP with **POST** request step to send data to the API.
{% endhint %}

5. If you're not using an HTTP request, you need to configure the following:

* In the **Filters** section, configure the **identifier** that will be used for record matching. You can refer to it as:

<pre><code><strong>{{ui.formName.value.fieldName}}
</strong></code></pre>

`formName` is the name of your Form component, `fieldName` is the name of the field.

* In the **Configure Row** section, add `{{ui.formName.value.fieldName}}` to the value of the field you want to update.

<div data-full-width="false"><figure><img src="/files/q3VVvK5gnGqv7oDXJW0e" alt=""><figcaption></figcaption></figure></div>

To save time configuring each field separately, you can switch to JS mode in the **Configure Row** section and send the form value object as:

```
{{ui.formName.value}}
```

<figure><img src="/files/6qzVgrVSp6rtRcDxnqv5" alt=""><figcaption></figcaption></figure>

6. Now, click on the **plus sign** under the *update* action step and select **Execute Action** from the list. This will trigger another action after the first one (update action) is completed.
7. In the **Action to execute** dropdown, select the action that loads your data.&#x20;

{% hint style="info" %}
The **update** action updates data in the data source, so to make sure the table displays actual information you need to add another action step to reload its values.
{% endhint %}

8. Modify any values in the form and click **Submit**.\
   If everything is configured correctly, it will change the form value, and the table will be updated with new values as well.

{% embed url="<https://demo.arcade.software/1BtG9S9QPfglFsq84WLz?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}


# Create a filter

Another common scenario is creating a filter for your table for easier navigation through the records. Check out the instruction below to find out how you can do that:point\_down:

## **To add a filter:**

1. Locate the **Text input** component in the **Components** tab and drop it to the working area.
2. Give your component a meaningful name and specify any other parameters you need in the right hand panel.
3. Here, also navigate to the **Triggers** section and for the **On Change** trigger select your *loadUsers* action.
4. Next, go to this action and add a filter to return the values corresponding to the user email. \
   In our case, we configured the following filter:\
   &#x20;`email like {{ui.input.value}}`

Done! If you now start typing user email in the input form, the table will return any corresponding records that exist.

{% embed url="<https://demo.arcade.software/UBdLt1Qv5dlWxoB7mntN?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}


# Note on debugging

Debugging is a vital part of the software development process. In UI bakery, debugging information can be accessed using an additional panel inside of your action. Here you can see the payload that has been sent to the UI bakery data source. \
Besides that, you can access additional debugging information in the **Logs** tab. It displays the history of action executions and its different lifecycle states. This can be useful if your action has several steps and uses the result or error mapper.&#x20;

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

To give you an example of how it works, let's set an incorrect value for the filter we've configured in the previous step and see what happens when we try to execute the action. Now we get error messages both in the toasts and in the *Logs* tab. To fix it, we simply need to modify the value and set a correct one.\
\
When we try running the action now, it works as expected and the data is displayed correctly.

{% embed url="<https://demo.arcade.software/5poxgNAuStygXFDTgHUA?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}


# Deploy your application & invite users

Now that your app is ready, you can move to the final stage in the flow - that is deploying your app and providing end user access for your team. Additionally, you can also share your app, that is otherwise private by default. You'll get a sharable link to the specific environment you select and you can share it with your workspace members. Let's see how that works!

## Deploying an app

Deploying your app is really simple - you just need to click the **Release** button in the upper right corner of the screen. Here, you can choose the environment(s) you want to deploy to. You can deploy to both staging and production environments or start with staging for testing purposes and deploy to production after successful testing. \
You can also set a specific version of the release, give it a name and add a description of the changes.&#x20;

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

Also, if you deselect both the environments, you can create a **Draft release**. Read more about draft releases in [this section](/concepts/workspace-management/app-environments/release-management#draft-release).

## Sharing an app

As we've already mentioned before, all your applications are private by default. The access to your apps is managed based on [user roles](#inviting-users). For deployed apps, you can click on the link icon next to the environment to share the link to the app with other workspace members.

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

### Public app

If necessary, you can also make your application *public*. There are two ways you can do that:

* Simply open the app's settings and turn on the **Public** toggle.

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

* Click the **Share** button directly from the Builder or from the workspace dashboard. It opens a popup where you need to switch to the *Public* tab and toggle on the **Public** setting.

<figure><img src="/files/1aV2uk4EGrrUBesoRtOR" alt=""><figcaption></figcaption></figure>

#### Public app URL

Public applications hosted in **UI Bakery Cloud** use the `uibakery.app` domain:

```
https://uibakery.app/{viewId}
```

The `uibakery.app` domain is used only for public applications hosted in UI Bakery Cloud. Anyone with the link can open the public application without signing in to UI Bakery. Before sharing it, make sure that its data sources and actions are configured for anonymous access where required.

{% hint style="warning" %}
**Legacy public URLs**

Legacy URLs continue to work and should still be used for public applications that rely on UI Bakery authentication or the current user context. For applications intended for anonymous access, use the new uibakery.app URL.

Support for legacy URLs may change in the future. Use the new `uibakery.app` URL for newly shared applications and update legacy links in bookmarks, documentation, embedded content, and integrations.
{% endhint %}

## Inviting users

Before inviting users to your app, make sure you first understand the [**seats**](/concepts/workspace-management/seats-and-shared-permission-groups-in-ui-bakery)**,** [**roles**](/concepts/workspace-management/roles-in-ui-bakery) & [**permissions**](/concepts/workspace-management/role-permissions) available in UI Bakery. You can manage them all in the *Users & Permissions* section under your workspace name.

When you're ready to invite users, in the same section here, under the *Users* tab, click the **Invite users to workspace** button. Specify user email(s) and assign any role(s) you want.&#x20;

{% hint style="info" %}
You can also send up to 20 invites at once in bulk.
{% endhint %}

{% embed url="<https://demo.arcade.software/jr4r6wWRd1bZjfw8LS0J?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}

### Inviting users via Share

As an *Admin* of your workspace, you can also invite users to your app and manage their access via the **Share** button - directly from the Builder or from the workspace dashboard. It opens a popup where you need to enter user emails, assign them roles, and click **Invite users**.&#x20;

{% hint style="info" %}
Works only for *new* users - you won't be able to invite users who already have access to this project.
{% endhint %}

From there, you can also manage user access and share workspace and embed URLs.

{% @arcade/embed flowId="gPqVqRSUJyzSUiqwzeNZ" url="<https://app.arcade.software/share/gPqVqRSUJyzSUiqwzeNZ>" %}


# Custom App

{% hint style="success" %}
Available in the *Low-code* mode. On-premise users need to contact our [support team](mailto:support@uibakery.io) for more details.
{% endhint %}

The *Custom App* feature lets you build functional end-to-end applications, either with the help of our AI assistant or by writing the code yourself. Unlike [Custom Components 2.0](/concepts/custom-components-2.0), which don’t support databases and require you to create actions manually, the Custom App feature offers full functionality - including code editing, hosted database access, and automatic action generation.

## Overview

You can access the list of all your custom applications, as well as create new ones, from the **Apps** section of the workspace menu. Here, click the *+* button and select *Custom App*.

<figure><img src="/files/355QmUCKRczS0g4JbnQc" alt=""><figcaption></figcaption></figure>

## Building an app

### Generating & code editing

You can type in what you want to create in the chatbox or attach an image of a similar UI as a visual aid. The AI will start working and you'll be able to see all the steps taken while generating an app based on your prompt.

Once generated, you'll be able to inspect the code structure in the *Code* tab in the header. Here, you can also tweak the code to better suit your needs and manage the whole file and folder structure. Right click to create new files, folders, rename, and delete existing ones.

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

During app building, the AI can also read and analyze logs to address any issues that occur.

### Connecting data

You can choose between creating & connecting a *hosted* database or connecting your own data source to access the data you need.

{% hint style="success" %}
With our *hosted* databases, the AI has more capabilities including utilizing migrations. With *customer* databases, the AI can only read and delete data, but it cannot change its structure.
{% endhint %}

Click on the **Connect** tab in the top bar to see the list of all available data sources, both hosted and user. Here, you can also see which data source is connected now, switch to a different one, or [connect a new one](/build-from-scratch/getting-started/connect-a-data-source).

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

{% hint style="warning" %}
The AI will have access only to the data sources you authorize.
{% endhint %}

### Database management

From the **Database** tab in the top bar, you can manage your *primary* connected hosted database.

{% hint style="info" %}
Read more about primary hosted databases in AI apps [here](/build-with-ai#database-connection).
{% endhint %}

<figure><img src="/files/70nbpUaHh436YIMi6GnT" alt=""><figcaption></figcaption></figure>

You can manage all other hosted databases from the *Database* tab at the bottom of the workspace menu. Refer to the [Database Editor](/extras/ui-bakery-postgres/database-editor) page to learn more details.

### AI usage credits

Usage credits for the Custom App feature work in the same way as for Custom components 2.0. You can find more details here:point\_down:

{% embed url="<https://docs.uibakery.io/concepts/custom-components-2.0#using-tokens>" %}

## Publishing an app

Once you're ready to release your app, click the *Release* button in the upper right corner of the screen. Select your version, add a description if you want, and choose the environments you want to deploy to, just like with regular apps you build yourself.

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

You can also share the app with specific users or make it public from the *Share* button popup.

## Use case examples

Now, let's review two examples of custom apps you can generate in UI Bakery - utilizing both a hosted and user databases. Watch our interactive demos below and learn how you can build similar apps yourself.

### &#x20;App with hosted database

{% @arcade/embed flowId="1bCQMv2Mfa193FUiIoe0" url="<https://app.arcade.software/share/1bCQMv2Mfa193FUiIoe0>" %}

### App with user-managed database

{% @arcade/embed flowId="y414iZt3EJew6OTprBAQ" url="<https://app.arcade.software/share/y414iZt3EJew6OTprBAQ>" %}


# Build with AI

Generate an app with AI - no code required.

This guide walks you through building, editing, and publishing an example **Employee Portal Dashboard**. \
You can try it in your browser or just read along and watch interactive videos without spending usage credits.

{% hint style="info" %}
Note that your result app may differ from the one you see here.
{% endhint %}

## Step 1: Build your app with the prompt

1. On the homepage, click **Create new app.**
2. Paste this prompt in the chatbox:

{% code overflow="wrap" %}

```
Build a modern, responsive Employee Portal Dashboard: include employee profiles, company announcements, tasks & to-dos, leave & attendance, team directory, and quick action shortcuts. Arrange content with cards, tables, and charts for clarity. Add search/filter features, realistic sample data, and interactivity.
```

{% endcode %}

{% hint style="info" %}
You can also paste an image of a similar app you want to build or select from a number of available ready-made templates.
{% endhint %}

3. Submit your prompt and wait while the AI creates your app.
4. Try it out in the **Preview** tab.

{% @arcade/embed flowId="Nj72nJ3fZ6nP9BdkhSXd" url="<https://app.arcade.software/share/Nj72nJ3fZ6nP9BdkhSXd>" %}

{% hint style="success" %}
Congratulations! You've just built your first app.
{% endhint %}

### Understanding AI usage credits

UI Bakery runs on third-party large language models (LLMs) that process your prompts and any visual images to build applications. The AI uses credits to measure the effort required to complete each task. Credits are used during the following phases:

* Planning
* Generating

More complex requests naturally require more credits.

The amount of usage credits you have left is displayed in the bottom left corner of the chatbox.

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

As a new cloud user, you'll start with a free usage credit allowance on the *Free* plan.

{% hint style="info" %}
Initital credits have a one-month expiration date.
{% endhint %}

Once you use them up, you need to upgrade your plan to get additional credits and continue building. You can find details about available plans on [our website](https://uibakery.io/pricing).

## Step 2: Connect a data source

UI Bakery allows you to choose from creating & connecting a *hosted* database or connecting *your own* data source.&#x20;

### Option 1. Connect tab

You can connect a data source yourself via the **Connect** tab in the top bar.

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

To connect a new data source, click **Connect Data Source** in the top right corner, select the data source you need, and provide the necessary connection details.\
Check out what data sources are currently supported for connection on the [Integrations](/build-with-ai/integrations) page.

{% hint style="info" %}
You can find more info about data sources and how to connect them on these pages: [Connect a data source](/build-from-scratch/getting-started/connect-a-data-source), [List of data sources](/reference/data-sources).
{% endhint %}

#### Database connection

UI Bakery allows you to connect multiple data sources/databases to your app, if necessary. With databases, *two connection options* are available:

* **Set as primary** - the database will be used as the primary source of data in the app. It will be shown in the *Database* panel and all app migrations will be run against it.
* **Connect** - the database will be only used for loading and modifying data. Migrations will not be run against it.

<figure><img src="/files/7jr5WW0jCnBJHgwqLFYU" alt=""><figcaption></figcaption></figure>

You can create and manage your primary hosted database directly in the app builder via the **Database** tab in the top bar.

<figure><img src="/files/17Og24iUaVY6yIaZ1sTB" alt=""><figcaption></figcaption></figure>

{% hint style="success" %}
Check out [Database editor](/extras/ui-bakery-postgres/database-editor) to learn more about working with hosted databases.
{% endhint %}

### Option 2. Ask AI

The second option is to ask AI to connect the data source you need in the chat or it may also recognize it from the prompt and suggest this for you.

You can also paste your connection settings and the data source you want to connect into the chat (for example, username, host, port, etc.) and in the connection window you will see your credentials already pre-filled. This works for all supported data sources.

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

## Step 3: Customize the app

After generating you first app version, you can continue refining and customizing it to fit your needs. \
For example, let's now add some color accents to make the app more vibrant and visually engaging. Paste this prompt in the chatbox and press *Enter*:

{% code overflow="wrap" fullWidth="false" %}

```
Use light blue (#3B82F6) as the primary accent color. Apply it to the active navigation tab and all action buttons (e.g., Edit Profile, Add Task). Use a lighter tint #60A5FA for hover or focus states. Keep the rest of the theme unchanged.
```

{% endcode %}

{% @arcade/embed flowId="QPRzeQaN5zuX1OSenJli" url="<https://app.arcade.software/share/QPRzeQaN5zuX1OSenJli>" %}

{% hint style="success" %}
Great! Now you know how to change the app once it has been created. You can keep refining it till you get the result you need.
{% endhint %}

### Reverting to a checkpoint

Sometimes you may change your mind about the updates you made to the app and you may want to undo them. It's totally fine, and in such cases, the easiest way is to revert your app to a specific checkpoint.

For example, let's say you are not happy with the new accent color and you want to restore the app's original color scheme. Simply select the checkpoint from before the color change and confirm. The app will automatically revert to that state.

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

{% hint style="success" %}
Note that restoring your app to a previous version doesn't use any credits.
{% endhint %}

## Step 4: Make changes in the Code

You can make any changes you need to the app with prompting but it may also be useful to know how to make changes directly in the code as well.

{% hint style="warning" %}
We do recommend caution when changing something directly in the code since these changes are not tracked in release history. Make sure you know what you're doing or you may need the AI to help you fix it.
{% endhint %}

Let's change the name of the company in the sidebar.

1. Click the **Code** tab in the top bar.
2. Use the **Search** bar to look for the component you need to update (in our case, *topbar*).
3. Locate the '*Acme Corp*' text and change it to '*UI Bakery*'.
4. Go back to the **Preview** tab to see the changes applied.

{% @arcade/embed flowId="G5VvymAFOzDzi6YdcMZe" url="<https://app.arcade.software/share/G5VvymAFOzDzi6YdcMZe>" %}

{% hint style="success" %}
This action also doesn't use up any credits.
{% endhint %}

## Step 5: Publish your app

Now it's time to publish your app!&#x20;

Click the **Release** button in the top bar, select your version, add a description if you want, and choose the environments you want to deploy to.

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

You can also share the app with specific users or make it public from the **Share** button popup.

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

## Need help?

If you can't find something in our docs or you're just stuck and need help, feel free to contact us in the chat or at <support@uibakery.io>. Our team is always willing to help 🤓.


# Agent

Let's have a look at the AI Builder interface:

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

<details>

<summary><strong>1.Top bar</strong></summary>

* **UI Bakery icon** - access App settings, Release history, go back to the workspace menu, or access additional links (our docs, community, booking a demo, and more)
* [**Connect Git**](/concepts/source-control) - connect your Git repository
* **Preview** - see the real-time preview of the app you're building
* [**Code**](/build-with-ai/features#code-view) - see your app code and make changes to it
* [**Database**](/build-with-ai#database-connection) - create and manage your primary hosted database
* [**Connect**](/build-with-ai#step-2-connect-a-data-source) - connect a hosted database or your own data source right from the Builder
* **Settings** - manage your app settings
* **Reload** - refresh the iframe preview of the app without reloading the page
* [**Switch breakpoint**](/concepts/mobile-layout) - switch between a desktop & mobile layout
* **Full screen preview** - see the app in full screen
* [**Release**](/build-from-scratch/getting-started/deploy-your-application-and-invite-users#deploying-an-app) - deploy your app or create a draft release
* [**Share**](/build-from-scratch/getting-started/deploy-your-application-and-invite-users#sharing-an-app) - invite users to your app, manage their access, and make the app public

</details>

<details>

<summary><strong>2.Agent sidebar</strong></summary>

Displays the [**chat**](#chat) component to interact with the UI Bakery Agent.

{% hint style="success" %}
The left side panel is *resizable* - you can adjust its size to suit your workflow and build more comfortably.
{% endhint %}

</details>

<details>

<summary><strong>3.Workspace</strong></summary>

Shows the contents of the tab selected in the header, for example, *Preview*, *Code*, *Database*, etc.

</details>

<details>

<summary><strong>4.Bottom panel</strong></summary>

* **Logs** - shows the current app logs
* **Git** - used to activate Git when you've connected your Git repo
* **Command palette** - to navigate the org and search for necessary components, projects, etc.

{% hint style="info" %}
You can [disable the command palette](/concepts/workspace-management/account-and-organization#disabling-command-palette-for-end-users) for end users, if necessary.
{% endhint %}

</details>

## Chat

The Chat area is the main place where you interact with the UI Bakery Agent to build your app, either providing prompts or visual input. Here's what you can also find here:

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

**a.** Create a new chat with the Agent.

**b.** See the history of all chats and switch between them. You can also search them and delete the chats you don't need any more.

{% hint style="info" %}
By default, chats get the creation date & time stamp name but after the first prompt they're automatically renamed.
{% endhint %}

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

**c.** Expands/collapses the agent sidebar.

**d.** Here, you type in your prompt for the Agent.

{% hint style="info" %}
If at some point during building, you decide you want to go back to a specific app state, you can use the [*Revert to this checkpoint*](/build-with-ai#reverting-to-a-checkpoint) option under the prompt you need.
{% endhint %}

**e.** Displays the amount of [usage credits](/build-with-ai#understanding-ai-usage-credits) you have left.

**f.** **Picker tool** - you can select an element on the page that you want to change instead of describing it in text for more minute changes.

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

**g.** Attach an image to your prompt (for example, of a similar app you want to build) for inspiration.

**h.** You can choose between a **Fast/Smart** model depending on your request. Faster model serves better for some quick edits while the Smart one is best when you want to make some complex changes.

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

{% hint style="info" %}
The high-powered model requires more usage credits.
{% endhint %}

**i.** Click *this button* or press *Enter* to make your request.

While the agent is working, if the tab with the chat becomes inactive, you will hear a sound notification and a visual indicator will appear on the tab. This allows you to seamlessly work on other things and at the same time be aware that your prompt has been executed.

{% hint style="warning" %}
When building an app, you may get some *Runtime errors—*&#x79;ou may choose to fix or ignore them, if they're no longer actual, for example.

<img src="/files/lWbB9TrV65EmjFf0SrGh" alt="" data-size="original">
{% endhint %}


# Features

## Code view

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

In the *Code* tab, you can manage your app code and tweak it without prompting. Having said that, we do **recommend to be careful** when changing the code directly since such actions are not tracked in the release history. It will be impossible to revert to a specific checkpoint if something goes wrong.

You can right click on any folder or file to rename or delete it, and you can also add new files or folders to the existing structure. The *Search* bar at the top allows you to quickly search for any file you want to update.

### AGENTS.md

Below all folders, there's an **AGENTS.md** file—here you can add specific app-level instructions that the Agent will refer to when building an app.\
For example, you can give more details about your application context, specify your coding style or design guidelines, or just any other things you want the agent to apply consistently while building.&#x20;

It always helps to be specific and clear in your instructions and provide examples to help the Agent better understand your preferences.

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

## Tools

You can access app settings—either from the UI Bakery icon in the Builder or from the Workspace menu—and configure specific *tool settings*.

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

{% hint style="success" %}
All tools settings are NOT global and can be configured for each app separately.
{% endhint %}

### Migrations

The migrations tool allows the agent to adapt the database structure if the app requirements change over time or there's some structural mismatch.

It can apply migrations either automatically or manually. If you choose the manual option (*Always ask*), then each time there's a migration, a prompt will appear in the chat and you can select *Execute* or *Reject* there.

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

{% hint style="info" %}
By default, the *Always allow* option is enabled for all new projects.
{% endhint %}

### Screenshots

Think of this tool as the agent's eyes.\
The agent knows what exists in the app's schema, code, and configuration but sometimes apps behave in a different way when they're rendered. The screenshot tool allows the agent to inspect the actual UI state and make decisions based on what users really see.

For this tool, you can also choose either the automatic or manual option. If you choose *Always ask*, then each time the agent needs to take a screenshot, you'll see a prompt with the following options: *Always allow*, *Allow once*, and *Deny once*.

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

You can select the option that best fits your specific needs.

{% hint style="info" %}
By default, the *Always allow* option is enabled for all new projects.
{% endhint %}

### Executing actions

This tool is more like the agent's hands or sensors.\
It allows the agent to safely run controlled operations to inspect your app data instead of guessing, for example, query a database, check system state, call an API, etc. The results of this action call are used only to answer the user's request.

For this tool, you can also choose either the automatic or manual option. If you choose *Always ask*, then each time the agent needs to make an action call, you'll see a prompt with the following options: *Always allow*, *Allow once*, and *Deny once*.

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

{% hint style="info" %}
By default, the *Always ask* option is enabled for all new projects.
{% endhint %}

### Syncing data sources

This tool allows the agent to synchronize the data source structure to maintain an accurate schema model of your system. It is especially important in cases when:

* A column is renamed
* A new column is added
* A table is removed
* and others

Essentially, if anything changes in the database, the agent needs to synchronize those changes to avoid any possible issues.

Data source sync can happen either automatically or manually. If you choose the manual option (*Always ask*), then each time a data source sync is required, a prompt will appear in the chat and you can select the option that suits you.

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

{% hint style="info" %}
By default, the *Always allow* option is enabled for all new projects.
{% endhint %}

## Multi-page apps

With the UI Bakery AI agent, you can create multi-page applications that support persistent state and URLs with query parameters.

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

In the URL, you can see the page you're currently on—the parameter changes when switching between pages. The app also stays on the same page after reload and works with browser back/forward navigation as well.

## Conditional actions

In your app, it's possible to create **conditional** **actions** so that you can control action execution. It can be especially useful in such cases as:

* Not loading data until all filters have been selected
* Fetching data only after a modal or tab is opened
* Blocking mutations if a form is invalid or permissions are missing
* and others

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

## User context

The AI agent can access information about the currently logged-in user through the `{{user}}` context. This allows you to personalize actions, filter data, or apply user-based permissions.

Common fields include:

* `{{user.email}}`&#x20;
* `{{user.name}}`&#x20;
* `{{user.id}}`&#x20;

## Login

If you want to add login to your app, the AI agent will suggest the following options:

<figure><img src="/files/3IvJHpGfTVWPCrcthgLc" alt=""><figcaption></figcaption></figure>

* **Built-in UI Bakery auth** - Users can access the app through the UI Bakery workspace login. This option is good for internal apps and you can use SSO, RBAC, audit logs, and other out-of-the-box security features.
* **Custom auth** - Allows you to create your own custom login page with custom user database and auth logic within the application. This is a good choice for customer portals and similar apps where users are relevant only for this specific application.

You can choose one of these options in the prompt and the AI agent will implement the authentication.


# Integrations

To build your custom applications, you can connect your own data source or create a [hosted UI Bakery database](/extras/ui-bakery-postgres/database-editor).

{% hint style="success" %}
We've explored two ways how you can connect your data during app generation [here](/build-with-ai#step-2-connect-a-data-source).
{% endhint %}

The following *integrations* for AI-generated apps are currently supported:

* **Databases**
  * [AWS Athena](/reference/data-sources/aws-athena)
  * [AWS Redshift](/reference/data-sources/redshift)
  * [Big Query](/reference/data-sources/bigquery)
  * [Databricks](/reference/data-sources/databricks)
  * [Exasol](/reference/data-sources/exasol)
  * [JDBC](/reference/data-sources/jdbc)
  * [MariaDB](/reference/data-sources/mariadb)
  * [MongoDB](/reference/data-sources/mongodb)
  * [MySQL](/reference/data-sources/mysql)
  * [Oracle](/reference/data-sources/oracle)
  * [PostgreSQL](/reference/data-sources/postgresql)
  * [Presto](/reference/data-sources/presto)
  * [Redis](/reference/data-sources/redis)
  * [SAP Hana](/reference/data-sources/sap-hana)
  * [Snowflake](/reference/data-sources/snowflake)
  * [Spanner](/reference/data-sources/spanner)
  * [SQL Server](/reference/data-sources/sql-server)
  * [Supabase](/reference/data-sources/supabase)
* **Third-party services & APIs**
  * [Airtable](/reference/data-sources/airtable)
  * [GitHub](/reference/data-sources/github)
  * [Google Sheets](/reference/data-sources/google-sheets)
  * [GraphQL](/reference/data-sources/graphql)
  * [HTTP API](/reference/data-sources/http)
  * [OpenAI](/reference/data-sources/openai)
  * [OpenAPI](/reference/data-sources/open-api)
  * [SendGrid](/reference/data-sources/sendgrid)
  * [Stripe](/reference/data-sources/stripe)

{% hint style="info" %}
*HTTP* and *OpenAPI* data sources have improved logging—their requests include information about the credentials and configuration.

![](/files/2ZmdFPxjjHUlaP1pfMbz)
{% endhint %}


# Usage credits monitoring

You can monitor usage credits via the Instance API endpoint or by setting up the low-credit email alerts.

### Credits endpoint

Use the Instance API endpoint to read usage credits for a workspace by slug.

```
GET /api/instance/organization/{slug}/credits
Authorization: Bearer <UI_BAKERY_INSTANCE_API_TOKEN>
```

Example:

```bash
curl -i https://<your-instance-host>/api/instance/organization/acme/credits \
  -H "Authorization: Bearer $UI_BAKERY_INSTANCE_API_TOKEN"
```

The endpoint returns the standard Instance API response wrapper. The credits balance is in `result`.

Example response:

```json
{
  "status": "OK",
  "message": "Credits balance fetched",
  "result": {
    "organizationId": "b6b3e804-bf24-11f0-b596-9514a4f9f5de",
    "organizationSlug": "acme",
    "availableCredits": 249.68,
    "lastTopUpTotalCredits": 250.0,
    "percentageRemaining": 99.87,
    "billingDisabled": false
  }
}
```

The endpoint returns customer-facing UI Bakery usage credits, not raw AI/token credits.

Result fields:

<table data-header-hidden data-search="false"><thead><tr><th>Field</th><th>Description</th></tr></thead><tbody><tr><td><code>organizationId</code></td><td>Workspace view ID.</td></tr><tr><td><code>organizationSlug</code></td><td>Workspace slug from the request.</td></tr><tr><td><code>availableCredits</code></td><td>Remaining UI Bakery usage credits.</td></tr><tr><td><code>lastTopUpTotalCredits</code></td><td>Total usage credits from the latest top-up/current balance period.</td></tr><tr><td><code>percentageRemaining</code></td><td>Remaining percentage, calculated as <code>availableCredits / lastTopUpTotalCredits * 100</code>, rounded to 2 decimals. May be <code>null</code> if the total is missing or zero.</td></tr><tr><td><code>billingDisabled</code></td><td><code>true</code> when credits billing is disabled for the chat service/runtime.</td></tr></tbody></table>

Status codes:

| Status | Meaning                                                     | Body                          |
| ------ | ----------------------------------------------------------- | ----------------------------- |
| `200`  | Credits balance returned successfully.                      | `status`, `message`, `result` |
| `403`  | Missing or invalid Instance API token.                      | Instance API auth error body  |
| `404`  | Organization with the given slug was not found.             | `status`, `message`           |
| `502`  | Java backend could not fetch credits from the chat service. | `status`, `message`           |

### Low-credit alerts <a href="#id-28f0588d-53d0-4a56-8dd8-dd0ad0b564d9" id="id-28f0588d-53d0-4a56-8dd8-dd0ad0b564d9"></a>

The backend includes a scheduled job that checks Workspace credit balances and sends alerts when configured thresholds are reached.

Alerts are disabled by default. To deliver alerts, all of the following must be true:

* `UI_BAKERY_CREDITS_ALERT_ENABLED=true`
* at least one threshold is configured,
* at least one delivery channel is configured: email and/or webhook.

Alerts are skipped when `billingDisabled=true`.

#### Alert thresholds <a href="#b0f50ab2-bd5a-4f3d-baa1-5b723251282e" id="b0f50ab2-bd5a-4f3d-baa1-5b723251282e"></a>

Two threshold types are supported:

* absolute usage credits threshold;
* percentage remaining threshold.

An alert is sent when either configured threshold is reached.

Example:

```
UI_BAKERY_CREDITS_ALERT_THRESHOLD_CREDITS=10
UI_BAKERY_CREDITS_ALERT_THRESHOLD_PERCENT=20
```

This sends an alert when either:

* the workspace has 10 usage credits or fewer; or
* the workspace has 20% or less of its latest top-up remaining.

Use `-1` to disable a threshold:

```
UI_BAKERY_CREDITS_ALERT_THRESHOLD_CREDITS=-1
UI_BAKERY_CREDITS_ALERT_THRESHOLD_PERCENT=20
```

With this configuration, only the percentage threshold is used.

#### Alert configuration variables <a href="#b28ebf36-8b09-4186-96a9-e4ee62bd545a" id="b28ebf36-8b09-4186-96a9-e4ee62bd545a"></a>

<table data-header-hidden data-search="false"><thead><tr><th></th><th></th><th></th></tr></thead><tbody><tr><td>Variable</td><td>Default</td><td>Description</td></tr><tr><td><code>UI_BAKERY_CREDITS_ALERT_ENABLED</code></td><td><code>false</code></td><td>Enables or disables the scheduled low-credit alert job.</td></tr><tr><td><code>UI_BAKERY_CREDITS_ALERT_THRESHOLD_CREDITS</code></td><td><code>-1</code></td><td>Absolute usage credits threshold. <code>-1</code> disables this threshold.</td></tr><tr><td><code>UI_BAKERY_CREDITS_ALERT_THRESHOLD_PERCENT</code></td><td><code>-1</code></td><td>Percentage remaining threshold. <code>-1</code> disables this threshold.</td></tr><tr><td><code>UI_BAKERY_CREDITS_ALERT_EMAIL_ENABLED</code></td><td><code>true</code></td><td>Enables email delivery to organization admins.</td></tr><tr><td><code>UI_BAKERY_CREDITS_ALERT_WEBHOOK_URL</code></td><td>empty</td><td>Optional webhook URL for alert delivery.</td></tr><tr><td><code>UI_BAKERY_CREDITS_ALERT_COOLDOWN_HOURS</code></td><td><code>24</code></td><td>Per-organization cooldown after a successful alert. Prevents repeated alerts while credits remain low.</td></tr><tr><td><code>UI_BAKERY_CREDITS_ALERT_CRON</code></td><td><code>0 0 * * * *</code></td><td>Spring cron expression for scheduled checks. Spring cron includes seconds.</td></tr></tbody></table>

Important behavior:

* The alert job has a hard minimum check interval of 15 minutes. If the cron runs more often, checks are skipped until the next allowed time.
* Cooldown is tracked in memory per Java process and resets after backend restart.
* Cooldown is recorded only after at least one delivery channel succeeds.
* Email and webhook delivery are attempted independently. If one channel fails, the other can still succeed.

#### Webhook alerts <a href="#b0c47cee-acea-4ea2-8245-b5926bfb3c8d" id="b0c47cee-acea-4ea2-8245-b5926bfb3c8d"></a>

Set `UI_BAKERY_CREDITS_ALERT_WEBHOOK_URL` to send a `POST` request when credits are low.

```
UI_BAKERY_CREDITS_ALERT_ENABLED=true
UI_BAKERY_CREDITS_ALERT_WEBHOOK_URL=https://example.com/credits-alert-webhook
UI_BAKERY_CREDITS_ALERT_THRESHOLD_PERCENT=20
```

Payload example:

```json
{
  "event": "credits.low_balance",
  "organizationId": "b6b3e804-bf24-11f0-b596-9514a4f9f5de",
  "organizationSlug": "acme",
  "availableCredits": 9.5,
  "lastTopUpTotalCredits": 250.0,
  "percentageRemaining": 3.8,
  "billingDisabled": false,
  "text": "UI Bakery usage credits are low for acme: 9.5 usage credits remaining (3.8%)."
}
```

The webhook payload is not the same as the Instance API response. It intentionally includes `event` and `text` for automation/notification tools.

The `text` field is included so the payload can be used with Slack-compatible incoming webhooks or automation tools. The webhook request has a 5-second connection/read timeout.

### Email alerts <a href="#id-9960aeb7-7586-4601-98ef-66ec5392b930" id="id-9960aeb7-7586-4601-98ef-66ec5392b930"></a>

Email alerts are sent to Workspace admins.

Enable email delivery:

```
UI_BAKERY_CREDITS_ALERT_ENABLED=true
UI_BAKERY_CREDITS_ALERT_EMAIL_ENABLED=true
UI_BAKERY_CREDITS_ALERT_THRESHOLD_PERCENT=20
```

For custom HTML templates over SMTP, configure:

```
UI_BAKERY_MAILING_PROVIDER=smtp
UI_BAKERY_MAILING_TEMPLATES_MODE=custom
UI_BAKERY_MAILING_EMAIL_FROM=admin@uibakery.io
UI_BAKERY_MAILING_NAME_FROM=UI Bakery

UI_BAKERY_SMTP_HOST=sandbox.smtp.mailtrap.io
UI_BAKERY_SMTP_PORT=2525
UI_BAKERY_SMTP_USERNAME=<smtp-username>
UI_BAKERY_SMTP_PASSWORD=<smtp-password>
UI_BAKERY_SMTP_ENCRYPTION=tls
```

For local testing, Mailtrap can be used as the SMTP server. In that case, the email recipient is still the Workspace admin email, but the message is captured in the Mailtrap inbox and is not delivered to the real mailbox.

#### Email template customization <a href="#ea11becf-8a6e-4996-8582-198d3fbfe0c9" id="ea11becf-8a6e-4996-8582-198d3fbfe0c9"></a>

The low-credit email subject and template can be customized with:

```
UI_BAKERY_MAILING_CREDITS_LOW_SUBJECT=UI Bakery usage credits are low
UI_BAKERY_MAILING_CREDITS_LOW_TEMPLATE=<h2>Low credits for organizationName</h2><p>Hello userName, organizationSlug has availableCredits usage credits left from lastTopUpTotalCredits. Remaining: percentageRemaining%.</p>
```

Available template variables:

<table data-header-hidden data-search="false"><thead><tr><th></th><th></th></tr></thead><tbody><tr><td>Variable</td><td>Description</td></tr><tr><td><code>userName</code></td><td>Admin display name or email.</td></tr><tr><td><code>userEmail</code></td><td>Admin email address.</td></tr><tr><td><code>organizationName</code></td><td>Workspace display name.</td></tr><tr><td><code>organizationSlug</code></td><td>Workspace slug.</td></tr><tr><td><code>availableCredits</code></td><td>Remaining usage credits.</td></tr><tr><td><code>lastTopUpTotalCredits</code></td><td>Latest top-up/current balance total in usage credits.</td></tr><tr><td><code>percentageRemaining</code></td><td>Remaining percentage.</td></tr></tbody></table>

Template substitution is plain string replacement. Use `userName`, `organizationName`, etc., directly in the template. Do not use `{{ userName }}` syntax for the SMTP custom template.

Example rendered template:

```
<h2>Low credits for Acme</h2>
<p>Hello Jane Admin, acme has 9.5 usage credits left from 250.0. Remaining: 3.8%.</p>
```

### Sending both email and webhook <a href="#id-2ba44d66-3cb2-480c-a874-4083fd9cfc4a" id="id-2ba44d66-3cb2-480c-a874-4083fd9cfc4a"></a>

Email and webhook can be enabled together:

```
UI_BAKERY_CREDITS_ALERT_ENABLED=true
UI_BAKERY_CREDITS_ALERT_EMAIL_ENABLED=true
UI_BAKERY_CREDITS_ALERT_WEBHOOK_URL=https://example.com/credits-alert-webhook
UI_BAKERY_CREDITS_ALERT_THRESHOLD_CREDITS=10
UI_BAKERY_CREDITS_ALERT_THRESHOLD_PERCENT=20
```

When the threshold is reached, UI Bakery sends an email to Workspace admins and also posts the webhook payload.&#x20;


# 🖥️ MCP server (beta)

Connect external AI agents to build apps and manage your UI Bakery workspace.

Connect the UI Bakery MCP server to Codex, Claude, or another MCP-compatible agent to build apps and manage your UI Bakery workspace directly from the agent.

{% hint style="warning" %}
MCP is in beta. The MCP API, available tools, scopes, permissions, and tool parameters may change.
{% endhint %}

### Connect an MCP client

{% hint style="info" %}
UI Bakery on-premise administrators must [enable the MCP server](/on-premise/additional-configurations/mcp-server) before clients can connect. In the examples below, replace `https://cloud.uibakery.io` with the public URL of your UI Bakery instance.
{% endhint %}

1. Add the endpoint to the client.
2. Complete browser-based OAuth and sign in to UI Bakery.

{% tabs %}
{% tab title="AI agent" %}
**Ask your AI agent to connect**

Copy and send this prompt to an AI coding agent that can configure MCP servers:

```
Connect the UI Bakery MCP server using https://cloud.uibakery.io/api/mcp
```

{% endtab %}

{% tab title="Claude Code" %}

```shell
claude mcp add --transport http uibakery https://cloud.uibakery.io/api/mcp
```

Run `/mcp` inside Claude Code. Select `uibakery`, then complete browser authentication. For on-premise, replace the URL.
{% endtab %}

{% tab title="Codex" %}

```shell
codex mcp add uibakery --url https://cloud.uibakery.io/api/mcp
codex mcp login uibakery
```

{% endtab %}

{% tab title="Cursor" %}

```json
{
  "mcpServers": {
    "uibakery": {
      "url": "https://cloud.uibakery.io/api/mcp"
    }
  }
}
```

Save this as `.cursor/mcp.json` for one project. Save it as `~/.cursor/mcp.json` globally.

Open **Cursor Settings** → **MCP**. Connect `uibakery`, then complete browser authentication. For on-premise, replace the URL.
{% endtab %}
{% endtabs %}

### Agent Capabilities

| Access request       | Scope                 | What it enables                                                                                                                                                   |
| -------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Project management   | `projects:manage`     | View and create apps and modules; inspect and update supported project files and metadata; manage releases and restores; perform Git operations; delete projects. |
| Datasource discovery | `datasources:read`    | View available data sources and inspect their resources and structure. This access is read-only.                                                                  |
| Workspace management | `organization:manage` | View users and invitations; manage invitations, custom roles, and permissions; reset MFA for users.                                                               |

An agent receives only capabilities covered by approved scopes and the authorizing user's UI Bakery permissions. Scopes never bypass UI Bakery project or workspace permissions.

### Example agent tasks

#### **Build and update apps**

You can update your UI Bakery apps directly from your AI agent. Describe the change, wait for the agent to finish, then reload the app in UI Bakery.

```
Add a users table to the "Products Dashboard" app and populate it with data from the "My DB" data source.
```

#### **Manage Git integration**

You can connect projects to Git repositories, create branches, commit changes, and synchronize them.

```
Create a new GitHub repository named "ui-bakery-a" in my account and connect the "A" app to it. Show me the proposed repository and branch settings before applying them.
```

#### **Manage workspace users and roles**

You can create custom roles, configure permissions, and assign roles to workspace users.

```
Create a custom role named "App Viewer" with view-only access to apps "Users", "Products", and "Orders". Assign it to the users listed in the attached "list.xlsx" file.
```

### Permissions and security

* MCP acts on behalf of the authorizing user.
* Existing application and workspace permissions continue to apply.
* Grant clients only the required access groups.
* MCP tool calls are written to UI Bakery audit logs.

{% hint style="danger" %}
Releases, deletes, Git operations, and user or role changes can alter or remove data or access. Review them explicitly before approval.
{% endhint %}

### Troubleshooting

#### The endpoint returns 404 or reports that MCP is disabled

For Cloud, verify the endpoint is exactly `https://cloud.uibakery.io/api/mcp`. For on-premise, follow [Enable MCP server on-premise](https://docs.uibakery.io/on-premise/on-premise-features/mcp-server) and restart the instance after configuration.

#### The OAuth window does not open

Confirm the client supports OAuth for remote MCP servers and uses the public HTTPS endpoint. On-premise administrators should verify `UI_BAKERY_APP_SERVER_NAME` matches the externally reachable host.

#### The connection succeeds, but tools are missing

Check approved scopes and the user's UI Bakery role. Reconnect and authorize only the required additional scope, if necessary.

#### The agent receives a permission denied error

Confirm the authorizing user has the required project or organization permissions. MCP does not elevate access.

### Related documentation

* [Enable MCP server on-premise](/on-premise/additional-configurations/mcp-server)
* [Git source control](https://docs.uibakery.io/on-premise/git-source-control)
* [Release management](https://docs.uibakery.io/concepts/workspace-management/app-environments/release-management)
* [Roles in UI Bakery](https://docs.uibakery.io/concepts/workspace-management/roles-in-ui-bakery)
* [Role permissions](https://docs.uibakery.io/concepts/workspace-management/role-permissions)
* [Audit logs](https://docs.uibakery.io/concepts/workspace-management/audit-logs)


# Tutorial


# Components

**Components** are the building blocks of the application interface. There are a number of prebuilt components available in UI Bakery. You can also build custom components using React, jQuery, ViewJS, or JavaScript.

Check out the articles in this section to learn more :point\_down:

<table data-view="cards"><thead><tr><th data-type="content-ref"></th></tr></thead><tbody><tr><td><a href="/pages/RVRHNwVcK9JNoLZz4a9r">/pages/RVRHNwVcK9JNoLZz4a9r</a></td></tr><tr><td><a href="/pages/btrsSRnQIrJKyyD0LD4f">/pages/btrsSRnQIrJKyyD0LD4f</a></td></tr><tr><td><a href="/pages/lq4oK0sHQIkcdCf3Gc2S">/pages/lq4oK0sHQIkcdCf3Gc2S</a></td></tr><tr><td><a href="/pages/3c7cQRXFV8TTeM1nTGoY">/pages/3c7cQRXFV8TTeM1nTGoY</a></td></tr></tbody></table>


# Components basics

You can access all available components in the **Components** tab of the left side panel. To add any component to your app page, simply drag it from the sidebar and drop into your working area. You can also change the component's size by dragging the resize handlers.

When you select a component, you can configure its **properties** via the right side panel. Here, you can change component structure, adjust settings and styles. You can also remove the component altogether by clicking on the *Bin* icon.

{% embed url="<https://demo.arcade.software/RyVaTzCFWSkoNHGv1Vqp?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}

## Component triggers

Each component has its own triggers that can launch specific events - actions that you assign to them. \
You can find the **Triggers** section in the right side panel along with other component properties. There, you need to select the trigger you need from the dropdown and assign an action to it.

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

One of the basic examples here could be two dependent *Select* components where the values available in the second dropdown depend on the value selected in the first one.

In our example below, we have two dropdowns (*Make* and *Model*), and based on the car make selected in the first dropdown, available car models are displayed in the second one.\
To achieve this, we created a *JavaScript Code* step and assigned it to the parent Select's (*Make dropdown*) **On Change** trigger:

```javascript
if (data === 'Toyota') {
  return ['Highlander', 'Prius', 'Celica']
}
if (data === 'Ford') {
  return ['Falcon', 'Fusion']
}
if (data === 'Mercedes-Benz') {
  return ['SLS-Class', 'CLS-Class', 'CL-Class']
}
```

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

Once you select the car make in the first dropdown, it will filter only its specific models in the second dropdown.

## Component usages

Here, in component properties, you can also see where a specific component is used - whether it’s in other components or actions. The icon will show you if there're any usages at all - you don't have to click it. Usages are grouped by target, with the following path format: `name → property`.&#x20;

If you click on a specific property or path in the *Usages*, you will be taken to the corresponding component or action, and you'll be able to make any adjustments if needed.

{% embed url="<https://demo.arcade.software/uWHmhh2EZm6IeIRTcDkt?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}

## Connecting to data

To connect your data to such components as [**Table**](/reference/working-with-components/table), [**Chart**](/reference/working-with-components/chart), [**List View**](/reference/working-with-components/list-view), and [**Grid View**](/reference/working-with-components/grid-view), you simply need to select the necessary action or variable in the component's **Data** field dropdown.\
The dropdown shows all available actions and variables - *Suggested*, *Page*, and *App* - and you can easily switch between them. The component structure is automatically regenerated based on your selection. \
If you need to undo the changes, you can revert component structure to its previous state by clicking *Undo* in the toast that appears at the top or clicking the *Revert* button next to component structure.

{% hint style="info" %}
The **JS mode** is still available for these components so you can switch to it if you prefer. In this mode, you can bind your actions to components manually, like before, and also manually regenerate component structure.
{% endhint %}

To connect your data to **all other components**, you need to manually reference the necessary action or component property in the component's **Data** field. \
Similarly, you can also change component data by removing the previously connected data and selecting new one. Once selected, you can auto-sync the new action's structure by clicking the [*Generate structure*](/build-from-scratch/getting-started/bind-data-to-ui#the-generate-structure-button-works-in-the-following-way) button. Thus, you won't have to build new component structure from scratch.

{% embed url="<https://demo.arcade.software/EXKLNAWOUMyQvIx6dKpM?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}

### Component name generation on action assignment

Component name is generated automatically when you assign an action to it. Name generation is based on the following rules:

* If an action **has a resource**, for example a Table, then the component name is based on the Table name.\
  For example, *Load Users* action -> *usersTable*.
* If there is **no resource**, then the component name is based on the action name.\
  For example, JavaScript Code step called *loadUsersData* -> *usersDataTable*.

## Accessing component variables

Such components as *Input*, *Date picker*, *File picker*, and complex components like *Form* and *Table* can produce values. When you enter something into an input, select a row in a table or submit a form, you can use component values as **variables.**\
Such variables are available under `{{ui.componentName.*}}`.

You can simply type `{{ui.` in any code or text field in the component or action settings to see a list of all available variables.

{% embed url="<https://demo.arcade.software/ll22EL6ALOgPedZ0gzVl?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}

* Simple components, such as **Input** and **Date picker**, have a value property. It's a reference to the current component value:

```
{{ui.input.value}} // current input value
```

* In a **Form** component, the value key will be a reference to the whole Form object. The keys represent the Form input names and values:

```
{{ui.form.value}} // { name: 'John', age: 30 }
```

* A **Table** component has multiple keys, such as `selectedRow`, `editedRow`, `newRow` and `deletedRow` with the following inners properties:

```
{{ui.table.selectedRow.isSelected}} // true/false
{{ui.table.selectedRow.data}} // selected row {} or null
{{ui.table.editedRow.data}} // edited {} BEFORE the edit
{{ui.table.editedRow.newData}} // edited {} AFTER the edit
{{ui.table.newRow.newData}} // newly created row {}
{{ui.table.deletedRow.data}} // deleted row {}
```

{% hint style="info" %}
These are only a few examples of component properties - more are available.
{% endhint %}

## Show/hide components

UI Bakery allows hiding components by configuring their **Show condition** as `false`.

{% hint style="info" %}
By default, hidden components do not occupy space in the working area. However, if you enable the option to **Preserve space when hidden**, the component's layout will remain intact regardless of its visibility. This ensures that components maintain their fixed positioning irrespective of whether they are visible or hidden.
{% endhint %}

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

The *Show condition* value is determined based on truth evaluation. For example, you can use the state of one component to control the visibility of another component.

Let's say you have a Table component added to your working area and you want to control its visibility with the help of a Checkbox:

1. Add a **Checkbox** component to your canvas.
2. Next, select the Table and set its **Show condition** as `{{ui.checkbox.value}}`.

{% hint style="info" %}
This will evaluate as `true` when the checkbox is selected, and `false` when it is cleared.
{% endhint %}

3. Test it out - select and clear the checkbox to either show/hide your Table component.

{% embed url="<https://demo.arcade.software/SfMI4ay72hxWKpCCJN2X?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}

Learn more about controlling component visibility:point\_down:

{% content-ref url="/pages/3wyuQoCSnxhnxJZRZbcV" %}
[Controlling component's visibility](/concepts/components/work-with-components/control-components-visibility)
{% endcontent-ref %}


# Components methods

**Component methods** provide you with more flexibility when working with components. Using these methods, you can programmatically open and close components, set new or reset current values, and more. \
Component methods are available under `{{ui.componentName.methodName()}}` , and they can accept parameters and return values. You can see a list of component methods via the **App state** tab in the left side panel.

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

In this section, we'll explore two cases of using component methods.

## Opening and closing a modal programmatically

When working with a **Modal** component, you may want it to open via custom interaction - on *Button/Table row click* (using component triggers) or from a *custom JavaScript snippet*.&#x20;

For this purpose, you simply need to add a Modal component to your working area and create a **JavaScript Code** action with the following code:

<pre class="language-javascript"><code class="lang-javascript"><strong>{{ui.myCustomModal.open()}} - open a modal
</strong></code></pre>

```javascript
{{ui.myCustomModal.close()}} - close a modal
```

<figure><img src="/files/1R3VFH6XCW8e2LWepnux" alt=""><figcaption></figcaption></figure>

## Resetting a form after submission

Now, let's review the case how you can reset a **Form** after creating a record and submitting your changes.

### To reset a form:

1. Add a **Form** to the working area.
2. Navigate to its **Triggers** section and select **Create action** for the **O*****n Submit*** trigger.
3. For the first step, select the **Create Row** action type.
4. Next, add another step to it of the **JavaScript Code** type and specify the following code:

```javascript
{{ui.yourForm.reset()}}
```

Done! Now, after submitting the form, it will return to its default condition.

{% embed url="<https://demo.arcade.software/oOJ0ctKiKHhiFc0UeYty?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}


# Components best practices

Explore the best approaches to working with components in UI Bakery.

<table data-view="cards"><thead><tr><th data-type="content-ref"></th></tr></thead><tbody><tr><td><a href="/pages/7GKzROSOkGxsSj40EEGN">/pages/7GKzROSOkGxsSj40EEGN</a></td></tr><tr><td><a href="/pages/cSBC4DlB2k8bRImxJzD9">/pages/cSBC4DlB2k8bRImxJzD9</a></td></tr><tr><td><a href="/pages/RraA1prNmhXlNUSAozcj">/pages/RraA1prNmhXlNUSAozcj</a></td></tr><tr><td><a href="/pages/1ekjQSxYCPANDH7sShiN">/pages/1ekjQSxYCPANDH7sShiN</a></td></tr><tr><td><a href="/pages/P2ONqXC2gmBBvXmNWNNH">/pages/P2ONqXC2gmBBvXmNWNNH</a></td></tr><tr><td><a href="/pages/xXvLQhuCbpH5iVSnqo1K">/pages/xXvLQhuCbpH5iVSnqo1K</a></td></tr><tr><td><a href="/pages/P3CDcdVtqqi4fiuRJwQR">/pages/P3CDcdVtqqi4fiuRJwQR</a></td></tr><tr><td><a href="/pages/5UOWXfk6QbgJIaplN1nE">/pages/5UOWXfk6QbgJIaplN1nE</a></td></tr><tr><td><a href="/pages/yhpmY9mlgl6kxi0JeqBB">/pages/yhpmY9mlgl6kxi0JeqBB</a></td></tr><tr><td><a href="/pages/3wyuQoCSnxhnxJZRZbcV">/pages/3wyuQoCSnxhnxJZRZbcV</a></td></tr></tbody></table>


# Input validation

UI Bakery offers various methods of validating user input. You can use our *built-in validators* or create *custom* ones. In this article, we'll explore them in more details.

{% hint style="success" %}
Here, we talk about [Text](/reference/working-with-components/text), [Text input](/reference/working-with-components/text-input), [Form](/reference/working-with-components/form), and [Detail](/reference/working-with-components/detail) components.
{% endhint %}

## Default validators

UI Bakery features a range of built-in validators for different inputs, such as *Min/Max*, *Required*, *Regexp*, etc. Validation occurs once a user inputs a value. By default, errors are shown after the input loses focus. But you can change this by clearing the **Show error after touched** checkbox, and then errors will be shown immediately.

<figure><img src="/files/3sjs8JD7XNUjpIN81wvo" alt=""><figcaption></figcaption></figure>

You can also create logic based on **validation status**. For this purpose, you can use the `{{ui.input.valid}}` variable that holds a Boolean value indicating the input's validity.

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

### Form/Detail validators

You can configure default validators for *Form/Detail* components' *fields*. To do so, follow the instruction below:

1. Select your component and click on the **field** you want to add validation to.
2. Expand the **Edit** **settings** section and scroll to the list of validators.
3. Set the **Min** setting value as 100, for example, and then input a value less than 100 in the **Id** field.\
   The Form component will react to the input validation, showing an error.

You can adjust this behavior by selecting the **Disable submit when invalid** checkbox which disables submission altogether if the input value is invalid.

{% embed url="<https://demo.arcade.software/4gJadZPdbt6FEIBmNxys?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}

## Custom validators

Besides using built-in validators, UI Bakery also allows creating custom validators. They are executed when the input changes, displaying an error next to the input.

Custom validators work according to the following **rules**:

* When the input's value changes, the validator is executed
* The `{{params}}` variable holds the current input value, enabling the creation of validation conditions
* If the validator returns a *String* or an *array of Strings*, these will be displayed as errors
* If the validator returns `null` or an *empty* value, validation is considered successful - no errors are shown, and the input is deemed valid
* During validation the input is considered invalid
* Validation is complete once all assigned validators have been executed

If the input value is empty, the validator will display the following error:

```javascript
return {{params}} ? null : 'Field is required';
```

Custom validators can be assigned to different components. Check out the instruction below to learn how to create and assign a custom validator to a *Text input* component. The same way, you can do it for other components as well.

### To create a custom validator:

1. Select the **Text input** component and navigate to the **Validation** section.
2. In the **Custom validators** dropdown, click **Create action.**
3. Select a **JavaScript Code** action type and specify the following code to modify the validator condition:

```javascript
return {{params}} ? null : 'Field is required';
```

4. Also, you can add a **Text** component and specify for it the following variables to check input status programmatically:
   1. &#x20;`{{ui.input.valid}}` - indicates whether an input is valid
   2. `{{ui.input.validating}}` - indicates if a validator is currently running

{% embed url="<https://demo.arcade.software/2Mfw6QRc5QQEHZU1yFE2?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}

### Async validators

Custom validators can also execute asynchronous validation operations, for example, making an API request to check if an email address is available. Let's review this example in the instruction below.

#### To create an async validator:

1. Select the **Text input** component and navigate to the **Validation** section.
2. In the **Custom validators** dropdown, click **Create action.**
3. For the first step, add an **HTTP Request** action type to verify if a user with the provided email address exists:

`https://example-data.draftbit.com/users?email={{params}}`

4. And for the second step, add a **JavaScript Code** action type.
5. Specify the following condition to verify the presence of a user from the API response:

```javascript
return {{data.length > 0}} ? 'Email is already taken' : null;
```

If the user list is not empty, the error message that you specified will be displayed.

{% embed url="<https://demo.arcade.software/LTfTKYPbCzhZEgitOncg?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}

### Custom validators for a Form component

You can also assign custom validators to a Form component. These validators receive the entire form object as `{{params}}` input and must return an object where each key is an input name and each value is an error message. This allows the form validator to assign errors to multiple fields in a single run.

To do so, you simply need to create a *JavaScript Code* action with the code below and assign it as a custom validator for the component:

```javascript
return {{params.id}} ? null : { id: 'Field is required', name: 'Field is required' };
```

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

#### Setting errors manually with `setErrors`

You can also set errors manually for a Form. For example, in some cases, the form should be submitted, but based on the API's error response, it should either succeed or display errors.

For this purpose, you can use the `{{ui.form.setErrors()}}` method to set errors accordingly.

<figure><img src="/files/5sEpzdxprFUPlTHD0ilA" alt=""><figcaption></figcaption></figure>

#### Global error for Form/Detail components

Form and Detail components can also display a **global** error message if an error occurs during the *On Submit* action. Follow the instruction below to enable this functionality:

1. Select your **Form** component and navigate to the **Appearance** section.
2. Here, select the **Show error message** checkbox and add your error message (for example, *'Id field is not valid'*).
3. Next, assign a new action for the **On Submit** trigger.
4. Select a **JavaScript Code** action type and specify the following logic to throw a JS error:

```javascript
if ({{ui.form.value.id}} < 100) {
  throw new Error();
}
```

5. Next, click the **Submit** button to submit the form.

{% embed url="<https://demo.arcade.software/EBQxOIouDP1i4vzeixWs?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}


# Linking a Table to a Form/Detail

While building your app, you can use multiple components in UI Bakery, and the best thing is that you can link them to share data and state between them. In this article, we'll explore two of the most common scenarios of linking components.

{% hint style="success" %}
Here, we talk about [Table](/reference/working-with-components/table), [Form](/reference/working-with-components/form), and [Detail](/reference/working-with-components/detail) components.
{% endhint %}

## **Linking a Table to a Detail component**

Let’s say you have a Table and you would like to have a separate form to view the **details** of a specific record from the table. That means you need to connect the Table with the **Detail** component.&#x20;

In our example, we will use a *Products* table and we will add a *Detail* component to display product details. Let's dive in!

### To link Table and Detail:

1. Start by adding a **Table** to your working area.
2. Next, you need to [load your data](/build-from-scratch/getting-started/load-data) and then [bind](/build-from-scratch/getting-started/bind-data-to-ui) it to the Table.
3. Now,  you can add a **Detail** component. \
   By default, it is connected to the previous Action, that’s why you’ll see the same fields as in the previous dataset.
4. Remove this default action from the **Data** field of the **Detail** component.
5. Instead, specify the value of the selected Table row - use the `selectedRow.data` variable.

{% hint style="info" %}
Start typing `ui...` - the autocomplete will suggest all the available options.
{% endhint %}

As simple as that! Now you can check the result - product details will change as you select different records in the Table.

{% @arcade/embed flowId="4FzyuBTIr4XYLGonrV6k" url="<https://app.arcade.software/share/4FzyuBTIr4XYLGonrV6k>" %}

## **Linking a Table to a Form component**

Let's say you have a *Products* table and you would like to have a separate **form**, where you could both see and update record details. In this case, you could use a **Form** component instead of a Detail one. Let’s dive into how you can do that!

### To link Table and Form:

1. Start by adding a **Form** component to the working area.

{% hint style="info" %}
If you're starting anew, check out the [previous instruction](#to-link-table-and-detail) and repeat **steps 1-2** first.
{% endhint %}

2. In its **Data** field, specify the value of the selected Table row - use the `selectedRow.data` variable.

Done! The Table and Form are now linked and have the same structure. If you click on a Table row, the row values will appear in the Form.

If you want to customize it even further to be able to update record details from the form and send it back to the data source, then be sure to check out this [page](/build-from-scratch/getting-started/send-a-form).

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


# Using a single Form to add and update data

When you have two Forms in your working area, one for adding data and the other for updating it, finding space for more components may be challenging. For this purpose, to save some space, you may use a single Form for both data operations. Here's how you can do that.

{% hint style="success" %}
Here, we talk about the [Form](/reference/working-with-components/form) component.
{% endhint %}

## To use a single Form:

1. Drag a **Form** component to your working area.&#x20;
2. Next, to populate the data of a table's selected row, specify the `{{selectedRow.data}}` variable in the form's **Data** field.
3. Create a new action of the **Condition** type and specify the following code in the code field:

```javascript
return [null, undefined].includes({{ui.form.value.id}});
```

3\. Next, add an <mark style="color:green;">**if**</mark> condition:&#x20;

* specify for it a **Create Row** action
* specify the Form value object as `{{ui.form.value}}` in the *Configure Row* section.

4\. Add an <mark style="color:red;">**else**</mark> condition:

* specify for it an **Update Row** action
* configure the identifier in the *Filters* section as `id = {{ui.form.value.id}}`&#x20;
* specify the Form value object as `{{ui.form.value}}` in the *Configure Row* section.

5\. Next, navigate to the **Finish** step of the action and assign your load data action to the *On Success* trigger to update the data in the table.

6\. Finally, assign the **create/update action** you've created before to the *On Submit* trigger of the Form.

{% embed url="<https://demo.arcade.software/6ZPLY4uqkkh2KTSxdH3b?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}


# Searching Table based on input value

The **Table** component allows you to view and interact with your data. Since your data may contain a large number of records, you may want to add a *search bar* to filter the data by a certain column/columns. Let's find out how you can do that.

{% hint style="success" %}
Here, we talk about [Table](/reference/working-with-components/table) and [Text input](/reference/working-with-components/text-input) components.
{% endhint %}

## To search the Table:

1. Drag a **Text input** component and drop it above the Table.
2. Add a descriptive label or placeholder for the Input.

{% hint style="info" %}
It's a good practice to rename components so that they have a unique identifier, and you don't confuse them in the process of building your app. For example, we'll rename the *Text input* component here to **`productCode`**.
{% endhint %}

3. Next, scroll down to its **Triggers** section and assign the action that loads your data to the *On Change* trigger.
4. For the next step, you need add a **filter** to your *load data* action to see only the records corresponding to a certain criteria.
5. Go to the action - set a column that will be used for filtering and reference the input value as the `{{ui.inputName.value}}` variable.

Done! Now, anytime you insert a value into the input, the action will be executed. It will process the filter criteria and return only the required values displayed in the Table.

{% embed url="<https://demo.arcade.software/bO6PVW1dUMtsJB71dPod?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}


# Configuring server-side pagination

If your dataset contains thousands of records, you may struggle with load time and app performance. To improve performance,  you can configure server-side pagination allowing you to load only the records on the current page.

{% hint style="success" %}
Here, we talk about the [Table](/reference/working-with-components/table) component. But the guide is also suitable for [Grid View](/reference/working-with-components/grid-view) and [List view](/reference/working-with-components/list-view) components.
{% endhint %}

It's important to keep in mind that when server-side pagination is enabled, table inline filters and sorting don't work automatically and need to be [implemented separately](#filtering).

In this article, we'll explore two types of server-side pagination:

* [Page-based](#page-based-pagination)
* [Cursor-based](#cursor-based-pagination)

## Page-based pagination

To implement page-based server-side pagination, follow the steps below:

1. For the Table component, select the **Server side pagination** checkbox in the right side panel.
2. Next, create a new action of the **SQL Query** type and use the `{{ui.table.pageSize}}` and `{{ui.table.paginationOffset}}` variables to control the data you need to load:

```sql
SELECT * FROM users LIMIT {{ui.table.pageSize}} OFFSET {{ui.table.paginationOffset}};
```

{% hint style="info" %}
With these variables, you can configure your action to **only load the page that the table requests**, by sending the `pageSize` and `paginationOffset` variables to your API or Load table action.
{% endhint %}

3. Now, assign this action to the *On Page Change* trigger of the Table to ensure the data is reloaded with each table page navigation.
4. Finally, set the Table's **Show loading** setting to true while your *SQL Query action* is loading - add the `{{actions.loadData.loading}}` variable.

Done! Now the Table will display only the records for a specific page.

{% embed url="<https://demo.arcade.software/AMBlfkefLaHWuwlxwuUh?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}

## Cursor-based pagination

Let's review cursor-based pagination based on the Stripe API example. Say you want to select a list of customers from Stripe API. \
To do that, follow the instruction below:

1. For the Table component, select the **Server side pagination** checkbox in the right side panel.
2. Next, create a new action of the **HTTP Request** type and specify the `{{ui.table.pageSize}}` and `{{ui.table.afterCursor}}` variables in **JS mode** - **Query Params**.

```javascript
const param = {
	limit: {{ui.table.pageSize}},
};
if ({{ui.table.afterCursor}}) {
	param.starting_after = {{ui.table.afterCursor}};
}

return param;
```

{% hint style="info" %}
With these variables, you can configure your action to **only load the page that the table requests**, by sending `pageSize` and `afterCursor` variables to your API.
{% endhint %}

3. Now, in *Table settings*, set the following:

* `{{_.last(actions.customers.data.data).id}}` to the **Next cursor** field (the identifier used for the next set of results),
* `{{actions.customers.data.has_more}}` to the **Has next page** field to enable or disable the next page button according to API info,\
  \
  where *actions.customers* is the action we created in **Step 2**.

4. &#x20;Assign your *HTTP Request* action to the **On Page Change** trigger of the table to ensure the data is reloaded with each table page navigation.

{% embed url="<https://demo.arcade.software/DrinfQZlcinobMuwtESk?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}

## Total row count

By default, the Table doesn't know the total number of items and can't disable the *Next page* button if the limit is reached. To display the total item count and make the table more intuitive, you can set the **Total row count** setting. Based on it, the number of pages will be calculated and displayed according to the items per page.

To retrieve the total row count, you simply need to create an action that will retrieve the total number of items or get this info from the API you are using. For example, you can use an action of the **SQL Query** type:

```sql
SELECT COUNT(*) AS AMOUNT FROM orders;
```

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

{% hint style="info" %}
When using a **Load Table** action type, you can obtain the row count using `{{actions.loadData.res.total}}`. In this case, you may not need the additional action to calculate the total number of records and you can use this property to show the pager.
{% endhint %}

## Server-side filtering and sorting

When server-side pagination is enabled, **table filters and sorting don't work automatically**, as the Table cannot filter and sort the data while it's being loaded page by page.

### Filtering

To configure server-side filters, you can use the `{{ui.table.filters}}` variable in your action to send this data to your *API/SQL Query* or *Load Table* action.

When using filters, make sure the SQL query is configured in a way that it returns all records when the filters are **empty***,* instead of trying to search for empty records. For instance, for most SQL databases you would need to use a *LIKE* operator combined with the *%* sign, which represents zero, one, or multiple characters:

```sql
select
  *
from
  users
where
  users.name like CONCAT ('%', {{ ui.table.filters.name}}, '%')
limit
  {{ui.table.pageSize}}
offset
  {{ui.table.paginationOffset}}
```

And don't forget to assign the action with the filter variable to the **On Filters Change** trigger.

{% @arcade/embed flowId="yeBWEuoChuu2NhIpSQzq" url="<https://app.arcade.software/share/yeBWEuoChuu2NhIpSQzq>" %}

### Sorting

To enable sorting, use the `{{ui.table.sortColumn}}` and `{{ui.table.sortDirection}}` variables in your action.

{% hint style="danger" %}
By default, dynamic sorting doesn't work as the **Convert SQL queries to prepared statements** [option is enabled](/reference/working-with-actions/sql-query#use-javascript-to-generate-queries). You need to turn this setting OFF to allow dynamic sorting.\ <mark style="color:red;">But be cautious - disabling this setting can cause SQL injection!</mark>
{% endhint %}

After you disable the *Prepared statement* setting, your **SQL Query** will look like this:

```sql
select
  *
from
  users
where
  users.name like CONCAT ('%', '{{ ui.table.filters.name }}', '%')
order by
  {{ui.table.sortColumn ?? 'id'}} {{ui.table.sortDirection ?? 'asc'}}
limit
  {{ui.table.pageSize}}
offset
  {{ui.table.paginationOffset}}
```

You can set the default sort column and direction in JavaScript using the `??` operator. In our example here, it means - "if `sortColumn` is empty, use `id` column instead".

{% hint style="info" %}
Notice how the *name* filter now requires quotes around the variable name:

`CONCAT('%',`` `<mark style="color:red;">**`'`**</mark>`{{ ui.table.filters.name}}`<mark style="color:orange;">**`'`**</mark>`, '%')`

As the query is not converted to prepared statements anymore, **you need to add proper quotes around string values manually**.
{% endhint %}

And don't forget to assign the action with the sorting variables to the **On Sort Change** trigger.

{% embed url="<https://demo.arcade.software/gGpzAFRlP81GcxV3D64q?embed=&embed_desktop=inline&embed_mobile=tab&show_copy_link=true>" %}

## Troubleshooting

If everything is configured properly, but the action does not return the expected values, check the following:

* [x] The action is assigned to all required triggers - **On Page Change**, **On Filters Change**, **On Sort Change**.
* [x] The same action that handles filters and pagination is connected to the **Data** property of the Table component.
* [x] If you're using dynamic sorting, make sure the **Convert SQL queries to prepared statements** setting is disabled and proper quotes are used around variables.
* [x] Check the **Payload** tab, copy the query with the parameters, and manually run it against the database to ensure it is properly constructed.
* [x] When setting your filters, make sure the query works in the following way:\
  \
  If a value is left *blank* or set to *NULL*, the query should retrieve all records based on that particular filter criterion, rather than just retrieving records with blank or NULL values for that criterion.
* [x] If you notice that some of the variables are sent as **'null'** or **'undefined'** **string** values, adjust your variables interpolation to account conversion of these values to empty strings or other values using the `??` operator. \
  For example: `CONCAT('%',`` `<mark style="color:red;">**`'`**</mark>`{{ ui.table.filters.name ?? '' }}`<mark style="color:orange;">**`'`**</mark>`, '%')`.


# Managing Date object time zones

There is a number of components that allow you to display and enter date and time values, such as *Date & Time*, *Date picker*, *Date & Time picker*. By default, these components use the browser time zone, i.e. **the local time zone of the user** to display and operate with date and time values.

{% hint style="success" %}
Here, we talk about [Date & Time](/reference/working-with-components/date-and-time), [Date picker](/reference/working-with-components/date-picker), and [Date & Time picker](/reference/working-with-components/date-and-time-picker) components.
{% endhint %}

In this article, we'll explore in more details some additional options that will allow you to manage time zones in your app:

* [Display Date & Time in a specific time zone](#displaying-date-and-time-in-a-specific-time-zone)
* [Use default value when no Date is selected](#using-default-value-when-no-date-is-selected)
* [Send Date without time zone conversion (for database data sources)](#sending-date-as-is-with-no-time-zone-conversion-database-data-sources)
* [Send Date without time zone conversion (for HTTP and other data sources)](#sending-date-as-is-with-no-time-zone-conversion-http-and-other-data-sources)

## Displaying Date and Time in a specific time zone

*Date & Time*, *Date picker*, and *Date & Time picker* components can display date and time in a specific time zone. You can achieve this by setting the component's **Timezone** property to the desired time zone. The time zone can be specified as a **UTC offset**:

```javascript
// Timezone is any valid UTC offset
'+01', '+01:00', '+0100', '-01', '-01:00', '-0100'
```

Or as an **IANA** time zone name:

```javascript
// IANA format timezone
'America/Los_Angeles', 'America/New_York', 'Europe/Berlin'
```

Here is an example of the Date & Time picker component that accepts the date in the user +1 hour time zone and displays it in the **America/New\_York** time zone:

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

{% hint style="warning" %}
The time zone settings affect only **the display of date and time**. Under the hood, the Date object is always stored in the browser time zone.
{% endhint %}

## Using default value when no Date is selected

By default, the *Date picker* and *Date & Time picker* components display the current date as their default values when no date is selected. You can change the default value, though, to the one you need, for example, start of month or week.&#x20;

This is the **current date** set as the default value for a Date picker component:

```javascript
moment().startOf('day');
```

<figure><img src="/files/9eS97v0dac3d0FFxChyv" alt=""><figcaption></figcaption></figure>

Alternatively, you can set it to the **start of the month**:

```javascript
moment().startOf('month');
```

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

## Sending Date as is without time zone conversion (Database data sources)

By default, when you use *Create Row* or *Update Row* actions, the Date object is first converted to the UTC time zone and then sent to the database server. That means, that the user value entered by the user in the browser may be different from the value sent to the database.&#x20;

Let's review the following example: the user selects **December 10, 2024 13:00** in the Date picker, but the value sent to the database is **December 10, 2024 12:00** (because of the time shift due to conversion to UTC). \
Now, you can prevent this behavior and send the Date object without any time zone conversion, *just like it is displayed in the browser*. You simply need to go to your *Data source settings* and select the **Ignore Browser Timezone** checkbox. After it's applied, you'll see that the Date is sent without conversion.

{% @arcade/embed flowId="5jz1osktwN8HyM4amv1L" url="<https://app.arcade.software/share/5jz1osktwN8HyM4amv1L>" %}

## Sending Date as is without time zone conversion (HTTP and other data sources)

If you are using an HTTP data source, you can also send the time component of the Date object as is, but it will require some additional steps. Let's explore two ways of how you can do that:

1. **Convert the Date object to the ISO format.**

When sending the date, use the following code to convert the Date object to the ISO format with no time zone offset conversion:

```javascript
{
  createdAt: {{moment(ui.datePicker.value).utcOffset(0, true).toISOString()}}
}
```

After executing the action, you'll see time sent as it is displayed in the browser.

<figure><img src="/files/2qj3okN0t0WpPBxPzGAS" alt=""><figcaption></figcaption></figure>

2. **Set the UTC time zone offset to the Date picker component.**

First, you need to set the component's **Timezone** value to *+00*. After that, use the following code in your action:

```javascript
{
  createdAt: {{moment(ui.datePicker.value).toISOString()}}
}
```

{% hint style="warning" %}
Setting **+00** timezone on a component and using `.utcOffset(0, true)` will result in an additional negative time zone offset.
{% endhint %}


# Role-based Menu component items

{% hint style="success" %}
Here, we talk about [Menu](/reference/working-with-components/menu), [Context menu](/reference/working-with-components/context-menu-button), and [Horizontal menu](/reference/working-with-components/horizontal-menu) components.
{% endhint %}

UI Bakery gives you the ability to configure specific menu items, for Menu components, which users can access based on their [roles](/concepts/workspace-management/roles-in-ui-bakery). \
Below, is an example that you can modify according to the roles in your organization. Once you're ready, switch to *JS mode* in the **Items** property of the Menu component and specify the following code:

```javascript
const userHasAccess = {{user.role}} === 'admin'; // Replace with your actual access condition
const menuItems = [
  {
    title: 'Dashboard',
    route: '/dashboard',
    disabled: false  // This item is always enabled
  },
  {
    title: 'Admin Panel',
    route: '/admin',
    disabled: !userHasAccess  // This item is disabled if the user is not an admin
  }
  // ... other menu items
];

return menuItems;
```

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


# Field types & types recognition

{% hint style="success" %}
Here, we talk about [Table](/reference/working-with-components/table), [Form](/reference/working-with-components/form), and [Detail](/reference/working-with-components/detail) components.
{% endhint %}

Complex components (Table, Form, Detail) support **various column/field types**, such as:

| :capital\_abcd: String                | :calendar\_spiral: Date                     |
| ------------------------------------- | ------------------------------------------- |
| :capital\_abcd: Long Text             | :hourglass: Time                            |
| :link: Link                           | :heavy\_check\_mark: Select/Tag             |
| :1234: Number                         | :ballot\_box\_with\_check: Multiselect/Tags |
| :star: Rating                         | **{;}** JSON                                |
| :heavy\_dollar\_sign: Currency        | :radio\_button: Button                      |
| :chart\_with\_upwards\_trend: Percent | :arrow\_down\_small: Context menu button    |
| :white\_check\_mark: Boolean          | :frame\_photo: Image                        |
| :date: Date & Time                    | :paperclips: File                           |

These field types share their settings among all supported components, meaning that, for example, configuring dropdowns for a Table or Form is exactly the same.

Using these types, you can build a component that will support most of the common use cases.

![](/files/aCdldQUZlUbErmkHFHqU)

## Autogeneration of field types

*Form*, *Table*, *Detail* and some other components support *autogeneration of field types* based on the component data. Autogeneration works in the following cases:

1. **Creating an Action and adding a Component.**

This is the scenario when you first create and run an action, then add a component and assign your action to it. All the fields and their types are generated automatically.

{% hint style="warning" %}
Such components as **Table**, **Chart**, **List View**, and **Grid View** now do not require manual action assignment - you just have to click the Data field dropdown to see all available actions and variables - *Suggested*, *Page*, and *App -* and easily switch between them. The component structure will automatically regenerate based on your selection.
{% endhint %}

2. **Using the Generate structure button.**

Here, you connect your action (state variable or another component property) to an already added component and generate its structure from there. \
This scenario applies to all the components, except for **Table**, **Chart**, **List View**, and **Grid View**,  since they do not require manual structure regeneration.&#x20;

{% hint style="success" %}
**No need to configure similar Components over and over again.**

When generating a structure based on *another Component property*, UI Bakery not only creates the same structure but also copies the properties of all its field types.
{% endhint %}

{% @arcade/embed flowId="aN7DIgq8XkJ9RWGtolxB" url="<https://app.arcade.software/share/aN7DIgq8XkJ9RWGtolxB>" %}

## Manual fields configuration

You can also configure fields and columns of various types manually. Follow this instruction to learn how you can do that:

1. Click the **plus** sign at the bottom of the **Columns** list.
2. In the **Field** panel that pops up, configure the following properties:
   1. If your component is connected to a data source, select a proper field from the *Field* name dropdown. \
      If you can't find the field you need in the list, enter a name manually. This way, a new field/column will be added but not mapped to the connected dataset.
   2. Select a field type from the *Type* dropdown.
   3. Configure the remaining properties as needed.

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

## View/Edit modes

All field types support **View** and **Edit** modes. These modes are extremely helpful when you need to edit data inline in a Table or Detail component without creating a separate Form for it.

This is quite simple: you just need to add your component (say, a *Detail*) and turn on the **Inline editable** toggle for a specific field. Here, you can switch between *Edit modes* (Always/On click) and *Submit triggers* (Blur/Change) options.\
The field you specified will become editable and end-users will be able to quickly adjust field value directly from the component.

Similarly, you can also turn on the **View only** mode for fields, for example, in a Form component. In this case, the fields will be displayed but your end-users won't be able to edit them.

{% @arcade/embed flowId="duPXHOi6mEAgBwQelYPD" url="<https://app.arcade.software/share/duPXHOi6mEAgBwQelYPD>" %}

### View mode placeholder

You can set a placeholder that will be displayed in View mode for nearly all field types, except for *Boolean*, *Image*, *Button*, and *JSON Editor*. You need to open the field's **Common settings** section and add the placeholder - it will be displayed when the field's value is blank.

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

## Fields validation

All field types in UI Bakery support validation but these settings vary from one type to another. You can find validation settings in the field's **Edit** **settings** section.

<figure><img src="/files/8JGuyAxmfoycYWqUIKY5" alt=""><figcaption></figcaption></figure>


# Select/Tag field: Utilizing Tag mapper

Let's say you have a Table with a Select/Tag column, and you want the items that have already been selected and are displayed in the table to not show in the dropdown. It may be useful, for example, when you don't want users to select one item multiple times.

In this case, you can use the **Tag mapper** setting for this field type. Here's an example how you can configure it in your app:

1. Create an action that will **return your table data** - select the *JavaScript Code* type and specify your data in the code, for example:

```javascript
[
  {"id": 1, "tag": 0},
  {"id": 2, "tag": 2},
  {"id": 3, "tag": 1},
]
```

{% hint style="info" %}
We've specified tag values here that match their titles.
{% endhint %}

2. Assign this action to the table.
3. Next, create another action that will **return all available tag options** - select the *JavaScript Code* type and specify the options in the code, for example:

```javascript
return [
  { value: 0, title: 'Innovate' },
  { value: 1, title: 'Synergy' },
  { value: 2, title: 'Dynamic' },
  { value: 3, title: 'Strategic' },
  { value: 4, title: 'Sustainable' },
  ];
```

4. Add this action to the Select/Tag column's **Options** field in the JS mode:

```javascript
{{actions.availableOptions.data}}
```

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

Or, alternatively, you can specify there the following code to additionally display the selected value in the *Edit mode*:

```javascript
const options = [...{{actions.availableOptions.data}}];
if ({{value}}) {
  const valueOption = {{actions.allOptions.data.find(o => o.value === value)}};
  options.unshift(valueOption);
}
return options;
```

5. Finally, create an action that will **return only the options NOT selected** - select the *JavaScript Code* type and specify your code, for example:

```javascript
return {{actions.allOptions.data}}?.filter((option) => {
  return !{{ui.table.value}}.find(item => item.tag === option.value);
});
```

{% hint style="warning" %}
Make sure to toggle on **Reactive trigger, run on components' changes** in the *Setup* step of this action.
{% endhint %}

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

6. Now, click the Select/Tag column and open its **View settings** section.
7. Locate the **Tag mapper** field and specify the following code to return the values not selected in the table:

```javascript
{{actions.allOptions.data.find(i => value === i.value).title}}
```

Now, when you select a value from the dropdown, it will be added to the table and will no longer be available for selection.

{% @arcade/embed flowId="pQPVAmHJMjl7B95rf1go" url="<https://app.arcade.software/share/pQPVAmHJMjl7B95rf1go>" %}


# Expanding component to fit screen/container

{% hint style="success" %}
Here, we talk about [Table](/reference/working-with-components/table) and [Card](/reference/working-with-components/card) components.
{% endhint %}

You may have cases when you need your components to take up all the available width and height of the page regardless of the user's screen size, for example, to avoid getting a scroll bar. In such cases, you can use our **Expand content to fit** feature. Let's explore it in more details.

## Scenario 1 (single component on the page)

Let's say you have added a *Table* to your page and you want it to occupy all the available space. You simply need to select the **Expand content to fit** checkbox in page settings in the right side panel.

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

## Scenario 2 (several components on the page)

Now, let's say you've also added a *Button* to the page, for example, and now you want both Table and Button to occupy all available space. If you try selecting the **Expand content to fit** checkbox now, you'll notice that it's *disabled*.&#x20;

This is because the feature is designed to ensure that a **single** **child** component can stretch to fully occupy its parent container’s available space.

{% hint style="warning" %}
When **multiple** **child** components are present, the layout must accommodate all of them, making it impossible for one element to expand to 100% without conflicting with the others.
{% endhint %}

In such cases, you need to add a **Card** component to the page and put your components inside the Card.&#x20;

### To use components inside the Card:

1. First, select the **Expand content to fit** checkbox in page settings in the right side panel.
2. Next, drag and drop a **Card** component into your working area.&#x20;
3. Now, select the *Expand content to fit* checkbox for the Card as well.
4. Then, drop a **Table** component to the Card body - it should occupy all available space.
5. Add other components you need to the Card header, for example, *Text input -* to add some filters.
6. Additionally, for the Card, you can also **Disable container styles** and set a **transparent** background.\
   The final result will look like your components are not placed inside any additional container.

That's it! Now you get a page that looks excellent on every screen size and is not limited to single component only.

{% @arcade/embed flowId="eXGuk4LpXSgXIXTkJkTA" url="<https://app.arcade.software/share/eXGuk4LpXSgXIXTkJkTA>" %}

## Expand content to fit & Height = Auto combination

Let's say you've added a *Frame Drawer* component and put a Table inside it - you want the table to occupy all available space and have vertical scroll applied within its body. For this purpose, you may try setting the Table's **Height** setting to *Auto*.\
\
:exclamation:However, this combination will result in a <mark style="color:red;">conflict</mark> and vertical scroll in the table won't work. You'll just have to leave the Height setting at **Fixed** to avoid this issue.&#x20;

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


# Controlling component's visibility

{% hint style="success" %}
Here, we talk about the [Table](/reference/working-with-components/table) component as an example, but the guide applies to all available components.
{% endhint %}

We've previously touched upon configuring component visibility using the *Show condition* setting [here](/concepts/components/components-basics#show-hide-components). Check it out if you need a reminder and come back to learn about the two main configurations of this setting:

* [Preserve space when hidden](#preserve-space-when-hidden)
* [Hide mode](#hide-mode)

Just a quick recap how this setting works: basically setting it to `false` hides the component, while setting it to `true` displays it.

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

Now let's dive into its configurations :point\_down:

## Preserve space when hidden

By default, hidden components do not occupy space on the canvas. However, if you want to you can **preserve their space when hidden** by enabling the corresponding setting for a specific component.&#x20;

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

In this case, a component's layout will remain intact regardless of its visibility. This ensures that components maintain their fixed positioning, whether they are visible or hidden.

Check the screen below: the *Preserve space when hidden* setting is **disabled** for a `table` component and **enabled** for a `table2` component.

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

{% hint style="warning" %} <mark style="color:orange;">**Breaking changes**</mark>

If you are using component visibility to switch which component is visible **in the same place** or if you have components that **occupy the same location** in the layout, you must pay attention to their position after the *layout v2* *upgrade* as they may be broken.

If something does break after the update, there is no need to worry since end-users are not affected. However, you will need to manually reconfigure the layouts where something went wrong.
{% endhint %}

## Hide Mode

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

### **Hidden**

*Hidden* is the default value for the Hide mode. When this value is selected, a component is always rendered during page initialization. Even if a component is inside another component which renders children dynamically, it will still be rendered on *page initialization,* and the `On Init` trigger will be activated.

{% hint style="info" %}
For example, if you place a *Table* inside a *Modal*, this component will be rendered during page initialization and the `On Init` trigger will be activated.
{% endhint %}

### **Not rendered**

The *Not rendered* option allows you to completely remove the component from the page when its Show Condition is `false`. This means the component will be rendered each time the Show Condition value changes from `false` to `true`. The `On Init` trigger will be fired each time the component is rendered.

If you have a component that renders children dynamically, then this setting will also affect its children.

{% hint style="info" %}
For example, if you place a *Table* inside a *Modal*, the table will not be rendered during page initialization. Each time you open this modal dialog, the table will be rendered from scratch, and the `On Init` trigger will be activated.
{% endhint %}

### How to approach the Hide mode

Which option to choose in the Hide mode is entirely up to you but we would recommend the following:

* Use the **Hidden** option for all components that should be visible on the page immediately after loading.
* Use the **Not rendered** option for components that display their children dynamically (e.g., *Modal*, *Tabset*, *Stepper*) to [improve app performance](/reference/performance).


# Custom component

{% hint style="warning" %}
The feature is deprecated, please refer to [this article](/concepts/custom-components-2.0) for information on building custom components in UI Bakery.
{% endhint %}

UI Bakery offers a large number of built-in components that you can choose from. Check out  our [Reference](/reference/working-with-components) section for the full list. But it's also possible to create **custom components** if you want to add functionality not present in our Components list. Here, we'll dive into how custom components work and provide you with some examples.&#x20;

## Custom components basics

Custom components can have their own logic and interface that are defined by you. Additionally, they can communicate with other features in UI Bakery by triggering events and receiving data to display. Custom components can be written in pure *JavaScript* or can be imported from custom libraries, such as *jQuery* or *React*.

{% hint style="danger" %}
Custom components are **rendered inside of an iframe**, thus we recommend using them only for **fix-sized elements** and avoiding overlays/popups inside them.

However, you can use [unrestricted custom components](/concepts/components/custom-component/unrestricted-custom-component) to render any HTML or JavaScript without any restrictions - they are not rendered inside of an iframe.
{% endhint %}

### Component anatomy

A  custom component is basically an **HTML page** embedded within an iframe that can contain HTML, CSS, and JavaScript. You can specify its code in the component's **Code** property.

<figure><img src="/files/813gIxzugTopr1a59KHe" alt=""><figcaption></figcaption></figure>

Here is an example of a custom component based on React:

```html
<!-- 3rd party scripts and styles -->
<script src="https://unpkg.com/react@17/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

<!-- custom styles -->
<style>
  body {
    padding: 1rem;
  }

  p {
    margin-top: 0;
  }

  button {
    margin-bottom: 1rem;
  }

  .container {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
  }
</style>

<!-- root element where the component will be rendered -->
<div id="root"></div>

<!-- custom logic -->
<script type="text/babel">
  function CustomComponent() {
  
    // receive data from UI Bakery
    const data = UB.useData();

    return (
      <div className="container">
        <p>Data from UI Bakery: {data.title}</p>
        <button onClick={() => UB.triggerEvent("Data from custom component")}>Trigger Event</button>
        <input onChange={(event) => UB.updateValue(event.target.value)} placeholder="Set state"></input>
      </div>
    );
  }

  const Component = UB.connectReactComponent(CustomComponent);
  ReactDOM.render(<Component />, document.getElementById("root"));
</script>
```

Since the custom component is rendered inside an iframe there are no specific limitations to the code and styles specified by the developer.

### Passing data to a component

To pass data into your custom component you can use the component's **Data** property. You simply need to specify the JavaScript object that contains the necessary data, for example:

```javascript
{
  data: [1,2,3],
  display: 'only_new',
}
```

Additionally, you can also pass data using **JS API** in your actions:

```javascript
ui.customComponent.setData({ ... })
```

* To access this data within the custom component, you can use:

```javascript
const data = UB.useData()
```

* You can also subscribe to updates of the data using:

```javascript
UB.onData(data => {
    console.log('new data', data);
});
```

### Receiving data and triggering actions from a component

If your custom component produces events or needs to trigger an action, you can use the following code:

* ```javascript
  UB.updateValue('Data from custom component');
  ```

Use this code inside the component to set its value. Once executed, the new value will be available as `{{ui.customComponent.value}}`.

* <pre class="language-javascript"><code class="lang-javascript"><strong>UB.triggerEvent('Data from custom component');
  </strong></code></pre>

Use this code inside the component to trigger an action. You also need to subscribe your action to the **On Event** trigger of the custom component. Once the `UB.triggerEvent('data')` is executed, the assigned action will be triggered.

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

The data supplied to the `triggerEvent()` function is available as the `{{ui.customComponent.value}}` variable as well as the `{{params}}` variable in the assigned action.

### jQuery example

Copy and paste the whole example into the custom component Code field:

```html
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>

<style>
  body {
    padding: 1rem;
  }

  p { margin-top: 0 }

  button { margin-bottom: 1rem }

  .container {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
  }
</style>

<div class="container">
  <p>Data from UI Bakery: <span id="uibakeryData"></span></p>
  <button id="triggerEvent">Trigger Event</button>
  <input id="updateValue" placeholder="Set state"/>
</div>

<script>
  $('#triggerEvent').click(() => UB.triggerEvent('Data from custom component'));
  $('#updateValue').change(event => UB.updateValue(event.target.value));

  UB.onData(({ title }) => {
    $('#uibakeryData').text(title);
  });
</script>
```

### React example

Copy and paste the whole example into the custom component Code field:

```html
<script src="https://unpkg.com/react@17/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

<div id="root"></div>

<style>
  body {
    padding: 1rem;
  }

  p { margin-top: 0 }

  button { margin-bottom: 1rem }

  .container {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
  }
</style>

<script type="text/babel">
  function CustomComponent() {
  	const data = UB.useData();

    return (
	  <div className="container">
        <p>Data from UI Bakery: {data.title}</p>
		<button onClick={() => UB.triggerEvent('Data from custom component')}>Trigger Event</button>
      	<input onChange={event => UB.updateValue(event.target.value)} placeholder="Set state"/>
      </div>
    );
  }

  const Component = UB.connectReactComponent(CustomComponent);
  ReactDOM.render(<Component />, document.getElementById('root'));
</script>
```

## Custom components examples

Now that we're done with the basics, let's explore how you can actually create custom components. In this section, we'll review the following examples:

* [Custom Calendar](/concepts/components/custom-component#custom-calendar-example)
* [MUI React library template](/concepts/components/custom-component#mui-react-library-template-example)

### Custom Calendar

In this section, we will create a custom calendar to display appointments:

1. Start by loading your data - create a **JavaScript Code** action step and add your data in the following format:

```javascript
return [
  {
    title: 'New Event',
    start: '2024-12-18T10:00:00',
    end: '2024-12-20T12:00:00',
    allDay: false,
  },
  {
    title: 'Another New Event',
    start: '2024-12-16T10:00:00',
    end: '2024-12-17T12:00:00',
    allDay: false,
  },
];
```

{% hint style="info" %}
This format is required to make sure your events are correctly displayed in the calendar.
{% endhint %}

2. Next, add a **Custom Component** to your working area.
3. Assign your *load data* action to the custom component's **Data** field: `{ events: {{ actions.loadAppointments.data }} }`.

4\. In the component's **Code** field, specify the following code:

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fullcalendar@5.10.1/main.min.css"> 
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@5.10.1/main.min.js"></script>

<div class="container">
  <div id="calendar"></div>
</div>

<style>
   body, html {
     height: 100%;
     padding: 0;
     margin: 0;
   }

  .container {
    background: white;
    padding: 2rem;
    height: 100%;
    overflow: hidden;
    border-radius: 0.25rem;
    border: 0.0625rem solid #dde1eb;
    box-shadow: 0 0.5rem 1rem 0 rgb(44 51 73 / 10%);
  }

  .fc-daygrid-event-harness {
    cursor: pointer;
  }
</style>

<script>
  document.addEventListener('DOMContentLoaded', function() {
    var calendarEl = document.getElementById('calendar');
    var calendar = new FullCalendar.Calendar(calendarEl, {
      initialView: 'dayGridMonth',
      eventClick: (info) => {
        // Update UI variable value
        UB.updateValue({ id: info.event.id });
        // Event triggering
        UB.triggerEvent({ id: info.event.id });
      }
    });
    calendar.render();
    
    // Callback to process new data in custom component
    UB.onData(data => {
      calendar.removeAllEvents();

      const events = data && data.events ? data.events : [];
      if (events && events[0]) {
        // In case of new data, the first event is automatically selected
        UB.updateValue({ id: events[0].id });
        UB.triggerEvent({ id: events[0].id });
      }
      events.forEach(event => {
        calendar.addEvent(event);
      });
    });
  });
</script>
```

And voilà! Your calendar is ready now.

{% @arcade/embed flowId="B21L5zgjmVIV7afEKKIX" url="<https://app.arcade.software/share/B21L5zgjmVIV7afEKKIX>" %}

### MUI React library template example

You can connect and use the [MUI](https://mui.com/) library to build custom components in UI Bakery, for example, a custom **Sign In** form.

To do so, simply copy and paste the following code in the custom component **Code** field:

```html
<!-- React -->
<script src="https://unpkg.com/react@latest/umd/react.development.js" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@latest/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@latest/babel.min.js" crossorigin="anonymous"></script>

<!-- MUI -->
<script src="https://unpkg.com/@mui/material@latest/umd/material-ui.development.js" crossorigin="anonymous"></script>
<!-- Fonts to support Material Design -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/>
<!-- Icons to support Material Design -->
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"/>

<div id="root"></div>

<script type="text/babel">
    const {
        Avatar,
        Button,
        CssBaseline,
        TextField,
        FormControlLabel,
        Checkbox,
        Link,
        Grid,
        Box,
        Typography,
        Container,
        createTheme,
        ThemeProvider
    } = MaterialUI;

    const theme = createTheme();

    function Copyright(props) {
        return (
            <Typography variant="body2" color="text.secondary" align="center" {...props}>
                {'Copyright © '}
                <Link color="inherit" href="https://mui.com/">
                    Your Website
                </Link>{' '}
                {new Date().getFullYear()}
                {'.'}
            </Typography>
        );
    }

    function App() {
        const handleSubmit = (event) => {
            event.preventDefault();
            const data = new FormData(event.currentTarget);
            console.log({
                email: data.get('email'),
                password: data.get('password'),
            });
        };

        return (
            <ThemeProvider theme={theme}>
                <Container component="main" maxWidth="xs">
                    <CssBaseline />
                    <Box
                        sx={{
                            marginTop: 8,
                            display: 'flex',
                            flexDirection: 'column',
                            alignItems: 'center',
                        }}
                    >
                        <Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}/>
                        <Typography component="h1" variant="h5">
                            Sign in
                        </Typography>
                        <Box component="form" onSubmit={handleSubmit} noValidate sx={{ mt: 1 }}>
                            <TextField
                                margin="normal"
                                required
                                fullWidth
                                id="email"
                                label="Email Address"
                                name="email"
                                autoComplete="email"
                                autoFocus
                            />
                            <TextField
                                margin="normal"
                                required
                                fullWidth
                                name="password"
                                label="Password"
                                type="password"
                                id="password"
                                autoComplete="current-password"
                            />
                            <FormControlLabel
                                control={<Checkbox value="remember" color="primary" />}
                                label="Remember me"
                            />
                            <Button
                                type="submit"
                                fullWidth
                                variant="contained"
                                sx={{ mt: 3, mb: 2 }}
                            >
                                Sign In
                            </Button>
                            <Grid container>
                                <Grid item xs>
                                    <Link href="#" variant="body2">
                                        Forgot password?
                                    </Link>
                                </Grid>
                                <Grid item>
                                    <Link href="#" variant="body2">
                                        {"Don't have an account? Sign Up"}
                                    </Link>
                                </Grid>
                            </Grid>
                        </Box>
                    </Box>
                    <Copyright sx={{ mt: 8, mb: 4 }} />
                </Container>
            </ThemeProvider>
        );
    }

    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(<App/>);

</script>
```


# Unrestricted custom component

{% hint style="warning" %}
The feature is deprecated, please refer to [this article](/concepts/custom-components-2.0) for information on building custom components in UI Bakery.
{% endhint %}

When you require a component not available in our Components list, you can develop it by utilizing the **unrestricted custom component**. With it, you can embed any HTML or JavaScript code without any constraints directly into any UI Bakery page.

{% hint style="success" %}
Unlike custom components, unrestricted custom components **are NOT contained within an iframe** and can be used for **displaying overlays, popups**, and other similar elements.
{% endhint %}

## Component anatomy

In contrast to custom components, unrestricted custom components are rendered on the same level as the rest of UI Bakery components.

{% hint style="warning" %}
We advise you to exercise caution while using this type of component since it may disrupt the UI Bakery page layout and styles and potentially access app data.
{% endhint %}

Here is an example of an unrestricted custom component:

<pre class="language-html"><code class="lang-html"><strong>&#x3C;!-- 3rd party scripts and styles -->
</strong>&#x3C;script src="https://unpkg.com/react@17/umd/react.production.min.js" crossorigin>&#x3C;/script>
&#x3C;script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js" crossorigin>&#x3C;/script>
&#x3C;script src="https://unpkg.com/babel-standalone@6/babel.min.js">&#x3C;/script>

&#x3C;!-- root element where the component will be rendered -->
&#x3C;div class="root">&#x3C;/div>

&#x3C;!-- custom styles -->
&#x3C;style>
  .custom-component-container p { margin-top: 0 }
  .custom-component-container button { margin-bottom: 1rem }
  .custom-component-container {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
  }
&#x3C;/style>

&#x3C;!-- custom logic -->
&#x3C;script type="text/babel">
  function CustomComponent() {
    // receive data from UI Bakery
    const data = UB.useData();

    return (
      &#x3C;div className="custom-component-container">
        &#x3C;p>Data from UI Bakery: {data.title}&#x3C;/p>
        &#x3C;button onClick={() => UB.triggerEvent("Data from custom component")}>Trigger Event&#x3C;/button>
        &#x3C;input onChange={(event) => UB.updateValue(event.target.value)} placeholder="Set state">&#x3C;/input>
      &#x3C;/div>
    );
  }

  const Component = UB.connectReactComponent(CustomComponent);
  ReactDOM.render(&#x3C;Component />, UB.container.querySelector('.root'));

  // it's a good practice to destroy all resources you consumed in your custom component.
  UB.onDestroy(() => ReactDOM.unmountComponentAtNode( UB.container.querySelector('.root')));
&#x3C;/script>
</code></pre>

{% hint style="info" %}
UI Bakery will put all script tags with the <mark style="color:blue;">src</mark> attribute to the end of the head tag. All scripts with the same <mark style="color:blue;">src</mark> attributes will be loaded only once. I**f you remove a script, make sure to reload the page.**
{% endhint %}

## Passing data to a component

{% hint style="info" %}
The API and settings for the Unrestricted custom component are the same as those of the [Custom component](/concepts/components/custom-component).
{% endhint %}

To pass data to your custom component you can use a component's **Data** property. You simply need to specify the JavaScript object that contains the necessary data, for example:

```javascript
{
  data: [1,2,3],
  display: 'only_new',
}
```

Additionally, you can pass data using **JS API** in your actions:

```javascript
ui.customComponent.setData({ ... })
```

* To access this data within the custom component, you can use:

```javascript
const data = UB.useData()
```

* You can also subscribe to data updates with the following code:

```javascript
UB.onData(data => {
    console.log('new data', data);
});
```

## Receiving data and triggering actions from a component

If your custom component produces events or needs to trigger an action, you can use the following code:

* ```javascript
  UB.updateValue('Data from custom component');
  ```

Use this code inside a component to set its value. Once executed, the new value will be available as {{`ui.customComponent.value}}`.

* ```javascript
  UB.triggerEvent('Data from custom component');
  ```

Use this code inside a component to trigger an action. You also need to subscribe your action to the **On Event** trigger of the custom component. Once the `UB.triggerEvent('data')` is executed, the assigned action will be triggered.

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

The data supplied to the `triggerEvent()` function is available as the`{{ui.customComponent.value}}` variable as well as the `{{params}}` variable in the assigned action.


# Custom components 2.0

{% hint style="success" %}
Available on *all plans*, both for cloud and on-premise instances in the *Low-code* mode.
{% endhint %}

UI Bakery offers a large number of [built-in components](/reference/working-with-components) that you can use in your applications. What is more, you can also create **custom components** if you want to add functionality not available in our component list.\
**Custom components 2.0** feature allows you to utilize AI and its capabilities to quickly and easily create functional components that you can add to your applications. \
The feature has a convenient *Code Editor*, where the code is split into separate files. You can review and tweak everything right inside the Editor if needed.\
Also new custom components are **reusable**, meaning you can use them across multiple projects, not just one (unlike the previous custom components).

In this article, we'll dive into how this feature works and provide some use case examples as well.

## Overview

You can access the list of all your custom components, as well as create new ones, in the **Library** section of the workspace menu. Here, click the *+ button* and select *Custom component* or click the *Custom Components 2.0* tile at the bottom of the Library list to launch the AI-powered component builder.

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

Give your component a name and proceed to the *Builder* mode to start generating the component.

## Building a component

To build a component with AI, you simply need to type in what you want to create or attach an image of a similar UI as a visual aid. The AI Assistant will start working and you'll be able to see all the steps he takes while generating a component based on your prompts.

Let's explore how the custom component works in more details:point\_down:

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

**a.** This is where you need to type in your prompt or attach an image of what you want to generate.

{% hint style="success" %}
More details about the chat and its features are [here](/build-with-ai/agent#chat).
{% endhint %}

**b.** Shows the process and steps the AI Assistant takes based on your prompt.

**c.** Here you can preview the result component after each iteration the AI takes.

**d.** Click *Revert to this checkpoint* to revert to the previous version to undo any changes you made or start generating again from a specific iteration.

{% hint style="info" %}
Custom components also have *Release history* (accessible via the button in the upper left corner) where you can view the history of its releases and revert to any version if needed.

<img src="/files/B9e67DIdn31i6Mfu4ZzZ" alt="" data-size="original">
{% endhint %}

**e.** In the *Code* tab, you can inspect the generated code and tweak it to better suit your needs.

**f.** *Reload* button allows you to refresh the iframe preview of your app or component - without reloading the entire page.

**g.** You can pass your data to AI in two ways:

* Create and run actions directly from the custom component and ask the AI to use them.

{% hint style="warning" %}
Custom component can only call actions created within the component itself.
{% endhint %}

* Tell the AI which props should come from the host app - you can check or modify the generated `data.json` file. It is located in the *Code* tab and it contains demo settings of the custom component.

{% hint style="info" %}
Only the structure of the data is sent to UI Bakery servers and Open AI.
{% endhint %}

**h.** Click *Release* to [publish the custom component](#publishing-a-component).

That is the basic flow of creating a custom component - as you see it's quite simple. To watch it in action on specific examples, make sure to check out [this section](#use-case-examples).

### AI usage credits

Every month, *free usage credits* are assigned to users - you can use them to generate custom components. Each time you make a request, credits will be deducted from the balance taking into account the *input*, *output*, and *cached input* ratios.

The actual credits balance is displayed in the bottom left corner of the chatbox in the *Builder* mode.

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

This way, you'll be able to see how many credits you have left and, if you've already spent all of them and need more, you'll be able to buy them right from the Builder. \
The <mark style="color:red;">You don't have enough credits to continue</mark> error will be displayed if you try making requests, and either from there or from the credits balance in the chatbox, you can click *Buy credits.* You'll be redirected to the billing page where you can buy more usage credits.

## Working with a component

Custom components are integrated with UI Bakery using special hooks from the `@uibakery/data` library. They allow the components to interact with the application, specifically *receive data*, *call actions*, and *trigger events*.

{% tabs %}
{% tab title="useData" %}
The `useData` hook allows a component to **receive data** passed to it from UI Bakery by accessing a specific property from the shared data object.

* `useData('prop', defaultValue)` - Returns the value for the property key (`prop` ) from the data object. \
  If the data is missing, the default value (`defaultValue`) is returned.
  {% endtab %}

{% tab title="Example" %}

```javascript
import { useData } from '@uibakery/data';

// Get the user's name (defaults to 'Guest')
const userName = useData('user.name', 'Guest');

// Get the full user object
const user = useData('user', {});
```

{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="useLoadAction & useMutateAction" %}
These hooks are designed to work with actions defined in UI Bakery to **load and modify data**.

* `useLoadAction(actionName, defaultValue, params)` - Calls an action to **load** data. Returns an array - `[data, loading, error, refreshData]`.
* `useMutateAction(actionName)` - Calls an action to **modify** (create, update, delete) data. Returns an array - `[mutate, loading, error]`.
  {% endtab %}

{% tab title="Example" %}

```javascript
import { useLoadAction, useMutateAction } from '@uibakery/data';

// Loading a list of products
const [products, isLoading, error, refreshProducts] = useLoadAction('loadProducts', []);

// Action to update a product
const [updateProduct] = useMutateAction('updateProduct');

// Calling the update action
updateProduct({ id: 1, name: 'New Name' });
```

{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="triggerEvent" %}
The `triggerEvent` function allows a component to **send events** to UI Bakery. The user can configure reactions to these events (for example, show a notification, navigate to another page, or execute another action).
{% endtab %}

{% tab title="Example" %}

```javascript
import { triggerEvent } from '@uibakery/data';

// Trigger an event about product creation
triggerEvent({ type: 'productCreated', data: { productId: 123 } });
```

{% endtab %}
{% endtabs %}

### General usage example

```javascript
import { useData, useLoadAction, useMutateAction, triggerEvent } from '@uibakery/data';

function MyComponent() {
  // Receiving data from UI Bakery
  const componentData = useData('someData', {});

  // Loading data via an action
  const [items, loading, error, refreshItems] = useLoadAction('loadItems', []);

  // Action for creating data
  const [createItem] = useMutateAction('createItem');

  const handleCreate = () => {
    const newItem = { name: 'A New Item' };
    // Call the action
    createItem(newItem);
    // Trigger the event
    triggerEvent({ type: 'itemCreated', data: newItem });
  };

  // ... rest of the component code
}
```

## Publishing a component

Once you're ready to release your component, click the *Release* button in the upper right corner of the screen. Select your version, add a description if you want, and choose the environments you want to deploy to.

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

{% hint style="success" %}
If you don't publish your custom component, it will be available only in the *Dev* environment. If you want it to be available in *Staging* and *Prod* as well, you need to deploy it to these environments.
{% endhint %}

## Adding a component to an app

You can add any custom component you created to your application. And, as we've mentioned before, you can add custom components to multiple projects.\
To add a component, click the **Library** tab of the Components section in the app's Builder mode and drag the component you need to the working area.

{% hint style="info" %}
The *Library* tab contains all your created custom components as well as modules.
{% endhint %}

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

In the right side panel, you'll be able to access and modify the custom component's settings as well as set *On Init* and *On Event* triggers. Here also at the top, you can click the **Edit** button to make any changes you need to the component, and after that you can click **Reload** to refresh these changes in the app.

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

## Use case examples

Now, let's review some examples of custom components you can generate in UI Bakery. Watch our interactive demos below and learn how you can build similar components yourself.

### To-do list

{% @arcade/embed flowId="kxRzMK0612AM5qG9wv1u" url="<https://app.arcade.software/share/kxRzMK0612AM5qG9wv1u>" %}

### Know Your Employee (KYE) form

{% @arcade/embed flowId="eGlxuncA16DAba0M6JTh" url="<https://app.arcade.software/share/eGlxuncA16DAba0M6JTh>" %}

### Table with server-side pagination

{% @arcade/embed flowId="Zdbh9KcV6TSxK5dJmZih" url="<https://app.arcade.software/share/Zdbh9KcV6TSxK5dJmZih>" %}


# Data sources

Connecting your data source is an essential step in building your application. UI Bakery allows connecting to a database or any API:

* **databases** (MySQL, PostgreSQL, etc.);
* **external APIs** (Google Sheets, Firebase, etc.);
* **internal APIs**

Data sources in UI Bakery are **global**, meaning that once set up each data source can be used across different applications. If needed, you can manage data sources' [*access permissions*](#managing-data-source-access) for different user roles.

## UI Bakery data sources

You can find the full list of all data sources available here :point\_down:

{% content-ref url="/pages/YK4OawsjmK4IGfLKY0dI" %}
[List of Data sources](/reference/data-sources)
{% endcontent-ref %}

## Connecting a data source

Check out [this article](/build-from-scratch/getting-started/connect-a-data-source) and follow the step-by-step instruction to connect a **new** data source.

## Managing data source access

By default, *Admins* and *Editors* of your workspace have all the permissions needed to manage data sources - they can *add new* data sources, *edit* and *remove* existing ones. \
Workspace members with a **User** role have a **Read-only** permission to all data sources. However, you can still manage access to data sources for Users with a *custom role*.

### **To manage user access to data sources:**

1. On the *Data sources* page, click **Manage access settings**.
2. On the page that opens, click the *pencil* icon next to the role you want to edit.
3. Navigate to the *Data sources* tab and select the data sources which you want to grant access to - **Use** and/or **Edit** them.
4. Next, click **Update role**.

{% hint style="danger" %}
By default, all newly created data sources are available only to the **same roles** which the user who created them has.

If you need to, you can also restrict access to data sources - simply clear the **Use** checkbox for a specific role.
{% endhint %}

{% @arcade/embed flowId="ZyTARyNXbytsDoVQyN1B" url="<https://app.arcade.software/share/ZyTARyNXbytsDoVQyN1B>" %}

## Enabling anonymous access

By default, access to data sources is restricted and managed via roles and permissions. If you have a public app, you can enable **anonymous access** to your data source, but you should be cautious since it will allow any user to query your data.

You can enable/disable anonymous access in the **Data source settings**.

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

Refer to [this section](/reference/security/apps-security#managing-anonymous-access-in-public-applications) to learn more details on how to safely manage anonymous access.

## Whitelisting IP addresses

To be able to connect to your data source in the cloud version, you need to whitelist our IP addresses:

```
52.176.109.125
20.52.252.203
```

This process may differ depending on the database. Below you can find whitelisting instructions to a couple of databases:

* [MySQL](https://support.rackspace.com/how-to/mysql-connect-to-your-database-remotely/)
* [PostgreSQL](https://blog.devart.com/configure-postgresql-to-allow-remote-connection.html) (with `.conf` files)
* [MongoDB](https://docs.atlas.mongodb.com/security/ip-access-list/#whitelist)

## Selecting data source outbound region

By default, when UI Bakery proxies data from your database to the end user, it passes through UI Bakery servers located in central US. This may cause performance degradation if both the user and the final data source are located in other regions.

To enhance performance and reduce data load time, you need to set the **Outbound region** that is closer to both the data source and the user in the **Data source settings**. It will ensure data flowing directly from the source to the user interface.

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


# Data source environments

Data source environments play a pivotal role in managing and segregating data flows in applications. They provide a structured way to differentiate between different stages of application deployment.&#x20;

In UI Bakery, there are **three** distinct **environments** for data sources:

1. **Default**: This is the primary environment used in scenarios where neither Staging nor Prod is enabled. It **serves as a fallback**, ensuring that there's always an environment in action even if no specific environment is set.
2. **Staging**: This is a testing environment, often mirroring the production but used for testing purposes. It provides a sandbox to validate data source configurations, ensuring they function as expected before moving to production.
3. **Production (Prod)**: This is the environment where the production data sources live. It's the environment the end-users interact with.

Both  `Staging` and `Prod` environments can be enabled separately based on your requirements. However, if neither is activated, the system will use the `Default` environment.

## To enable data source environments:

1. Navigate to your data source **settings** and turn on the **Enable environments** toggle.
2. For each environment, configure their settings and click **Connect data source**.

{% hint style="info" %}
You need to configure and connect each environment separately.
{% endhint %}

{% @arcade/embed flowId="AcqH7m5gEMul099RzfB1" url="<https://app.arcade.software/share/AcqH7m5gEMul099RzfB1>" %}

Data source environments are intrinsically linked to [application environments](/concepts/workspace-management/app-environments). When you move your app from one environment to another, the corresponding data source environment is also switched, ensuring data compatibility.

## Testing in Builder mode

When working in Builder mode, users have the flexibility to switch between different environments. This feature is particularly useful for testing queries. This way, users can validate the correctness of their queries across different stages without affecting the end-user experience or data integrity.

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

{% hint style="info" %}
The **environment selection** option is only available when the app is using at least one data source that has environments enabled for it.
{% endhint %}


# Connecting local database via ngrok

If you need to connect your local database but you don’t want to use the on-premise version, you can go for the option of connecting via [ngrok.](https://ngrok.com)

{% hint style="info" %}
If your database is hosted locally or is not accessible from external connections, we recommend using our **on-premise** version. You can set it up easily with a single command:

`curl -k -L -o install.sh https://raw.githubusercontent.com/uibakery/self-hosted/main/install.sh && bash ./install.sh`

Additionally, during the installation process, you will have the option to generate a trial license.

Alternatively, if on-premise installation is not an option, you may try setting up an [SSH tunnel to your database](/concepts/data-sources/ssh-tunneling).
{% endhint %}

:exclamation:We highly recommend the *ngrok* approach for <mark style="color:red;">**testing purposes**</mark> only, as ngrok is a third-party proxy that provides only a temporary connection (40-120 minutes depending on your plan), and re-connection is required.

## **To connect your local database via ngrok:**

1. Create an account at [ngrok](https://ngrok.com) if you do not have one.
2. [Download ngrok](https://ngrok.com/download).
3. Unzip the archive (initial instruction can be found [here](https://dashboard.ngrok.com/get-started/setup)).
4. Open your **Terminal** (MacOS/Linux) or **command line** (Windows) and navigate to the **Downloads** folder (or the folder where the ngrok archive has been saved). Use the following command:\
   &#x20;`cd Downloads`
5. Next, you need to add your authtoken to the default **ngrok.yml** configuration file using this command:

`ngrok config add-authtoken 2qO7FgeP0PKr4eigzL2tdAJsxt8_3tBf8bHFrUiNZgdCEDvrc`

{% hint style="info" %}
You can find your personal token on the [authtoken page](https://dashboard.ngrok.com/get-started/your-authtoken). The token will look like this:\
`20JWDkD3uwe2wuRqhCvuTkQ0LE3_5N6KtiEBDLD3fXZkRHpej`
{% endhint %}

6. If successful, you’ll get the following message: \
   `Authtoken saved to configuration file: /Users/user_name/.ngrok2/ngrok.yml`
7. Now, you can proceed with exposing your local app server or database.\
   Use one of these commands :

```
app server: ./ngrok http 80 (or port your server is hosted on)
mysql: ./ngrok tcp 3306
postgre: ./ngrok tcp 5432
mssql: ./ngrok tcp 1433
mongodb: ./ngrok tcp 27017
```

The output will list a forwarding URL, which will point to your local server - find the **Forwarding line** and copy the host and the port.

8. Next, navigate to **UI Bakery** > **Connect datasource.**
9. Select your data source and specify the copied host and port together with the other database details.
10. Click **Test connection** to check whether the connection can be established.
11. And finally, click **Connect Datasource.**


# SSH Tunneling

You can connect Postgres, MySQL, MSSQL, MongoDB, and other databases that are hosted under a private network via SSH tunnels.

Follow the instruction below to configure SSH tunneling in UI Bakery:

1. Start by navigating to the data source connection window - choose your data source and select the **Enable SSH tunnel** checkbox.

{% hint style="success" %}
For already connected data sources, you just need to open their settings.
{% endhint %}

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

2. Now, you need to configure your *bastion host* to allow UI Bakery to establish an SSH tunnel:
   1. Create a **UI Bakery user** (UI Bakery will connect to your bastion as this user):<br>

      ```bash
      # Use this command if you use Amazon Linux
      sudo adduser uibakery --password NP

      # Use this command if you use any other Linux/Mac
      sudo adduser uibakery --disabled-password
      ```
   2. Next, create the required `authorized_keys` file and configure its permissions:<br>

      <pre class="language-bash"><code class="lang-bash"># Login as root user
      <strong>sudo su
      </strong>
      # Create the authorized_keys file if it does not exist
      mkdir -p /home/uibakery/.ssh 
      touch /home/uibakery/.ssh/authorized_keys 

      # Set required permissions and make uibakery user an owner of this file
      chmod 644 /home/uibakery/.ssh/authorized_keys
      chown uibakery:uibakery /home/uibakery/.ssh/authorized_keys
      </code></pre>
   3. Now, go back to the data source connection window, copy the **SSH public key**, and paste it to the `authorized_keys` file:<br>

      ```bash
      # Use any text editor and insert previously copied ssh public key in authorized_keys file
      vim /home/uibakery/.ssh/authorized_keys
      ```

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

{% hint style="info" %}
The same steps apply for on-premise deployments as well.
{% endhint %}

3. Once connected, return to UI Bakery and specify your **bastion host** and **port number** under the *Enable SSH tunnel* checkbox.

{% hint style="info" %}
Usually, *bastion host* is either the domain or IP address of the virtual machine and *port number* is the SSH port (22) of the server that holds the database.
{% endhint %}

4. Next, specify the **Bastion user** you created in Step 2 (a).

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

5. Finally, scroll up to the *Connection settings* section and specify all the required fields.

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

:information\_source: <mark style="background-color:$info;">Please note that in the</mark> <mark style="background-color:$info;"></mark><mark style="background-color:$info;">**Host**</mark> <mark style="background-color:$info;"></mark><mark style="background-color:$info;">field you either need to specify:</mark>

* *<mark style="background-color:$info;">localhost</mark>* <mark style="background-color:$info;"></mark><mark style="background-color:$info;">(if the bastion and database are on the same virtual machine) or</mark>
* <mark style="background-color:$info;">your</mark> <mark style="background-color:$info;"></mark>*<mark style="background-color:$info;">private network IP address</mark>* <mark style="background-color:$info;"></mark><mark style="background-color:$info;">(if bastion and database are on different virtual machines with mutual access and the 3306 port is open)</mark>

6. Click **Test connection** to check whether the data source can be connected, and then click **Connect Datasource**.


# Actions

**Action** is a piece of business logic implemented in your application. You can use it to load the data from a data source, send the data back, make API calls, navigate to app pages, generate PDF documents, and process any type of data with SQL or JavaScript.

Check out the articles in this section to learn more :point\_down:

<table data-view="cards"><thead><tr><th data-type="content-ref"></th></tr></thead><tbody><tr><td><a href="/pages/bLlzr4TAl1V0I10IgAev">/pages/bLlzr4TAl1V0I10IgAev</a></td></tr><tr><td><a href="/pages/lZH7OiWELyYmYRigHM0N">/pages/lZH7OiWELyYmYRigHM0N</a></td></tr><tr><td><a href="/pages/QHw0L7pcV3EaYolzFtzW">/pages/QHw0L7pcV3EaYolzFtzW</a></td></tr><tr><td><a href="/pages/vZCfLat4umYd8zPk3mlA">/pages/vZCfLat4umYd8zPk3mlA</a></td></tr><tr><td><a href="/pages/l2ble9hHgrqZKSavDdbk">/pages/l2ble9hHgrqZKSavDdbk</a></td></tr><tr><td><a href="/pages/RxWPOnY1C9UToM31yVvp">/pages/RxWPOnY1C9UToM31yVvp</a></td></tr><tr><td><a href="/pages/EJ7VwuYeDQJ4gIfkOAzm">/pages/EJ7VwuYeDQJ4gIfkOAzm</a></td></tr></tbody></table>


# Actions basics

Within any action, you have the ability to include multiple **Action steps**. These steps encompass predefined logic and can take various forms, such as SQL queries, custom JavaScript code, HTTP requests, conditions, navigation, etc. By combining these steps together you can construct functional workflows that enable you to merge requests from different data sources, validate input data or trigger data reloads based on specific conditions. This flexibility allows you to create powerful and adaptable processes to meet your specific needs.

## Creating actions

You can create actions from the **Actions** panel at the bottom of the screen. First, you need to choose between a global or page-specific action, and then click the *plus* sign next to the corresponding section.

From there, based on the data source selected, you'll get the list of all available action types to choose from. It's also possible to create complex actions:

* **Multi-step** actions - all steps are executed sequentially in the defined order. \
  \
  By default, if one step fails, the entire action will also fail. However, you can change this behavior by enabling the **Allow next step execution when this step has failed** setting. Once enabled, the next step will still be executed even if the current step fails. \
  The `{{data}}` variable that would have been passed to the next step will be empty (null), and an error message indicating the nature of the failure will be stored in the `{{error}}` variable.

{% @arcade/embed flowId="yn49AhcTfHz56dSejWKt" url="<https://app.arcade.software/share/yn49AhcTfHz56dSejWKt>" %}

* **Condition** step - allows you to define different paths of execution based on specific conditions or validate the input before executing a request.\
  \
  Conditions are written in plain JavaScript.

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

Once you've created an action, you can assign it to a component's *Data* field. You can learn more about binding your data to UI [here](/build-from-scratch/getting-started/bind-data-to-ui).

You can also configure specific settings for the action execution flow, such as *toasts*, *confirmation dialogs*, and *execution delays*. For more details, refer to [this section](/concepts/actions/additional-action-settings).

### **Action step variables**

In any action step, you can access app variables like `{{ui.component.value}}` or `{{app.env}}`. Moreover, there are several built-in variables available for every action step:

* **`{{data}}`** - the result of the previous step
* **`{{error}}`** - the error response of the previous step
* **`{{params}}`** - incoming action parameters passed in by components, Execute/Loop Action steps, or when calling the action from the code
* **`{{res}}`** - the response of the request if the step follows an HTTP API step
* **`{{steps.name.data/error}}`** - the result of a particular action step

While `{{data}}` and `{{error}}` are specific to each step, `{{params}}` can be accessed in all steps.

## Triggering actions

There are two ways to trigger actions - *automatic* and *manual*:

* **Automatic**
  * *Initial trigger, run on first use in components* - when an action is referenced in the app for the first time, it will be triggered. For example, a table can use `{{action.name.data}}` to load data from the action, in this case, the action is triggered when the table is rendered for the first time.
  * *Reactive trigger, run on components' changes* - when the component values used in **the first action step** change. For example, if the action uses `{{ui.input.value}}`, it will be triggered when the value of the input is modified.

These triggers can be turned on/off in the **Setup** step of the action.

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

Actions that load data are auto-callable. It means that if you've assigned an action to the **Data** **property** of the component, you don't need to trigger the action manually. It will be called automatically when the component is displayed on page load. \
But you can deselect the *Initial trigger* setting, if needed, and manually trigger an action via user interaction (for example, clicking a button) or via code.

* **Manual**

  * *Component trigger* - when an action is connected to a built-in component trigger. For example, the button component has an **On Click** trigger.\
    You can assign actions to component triggers and use them in such cases as, for example, submitting a form, performing a search, or reloading a table with button click.

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

  * *Execute action, Loop action* - action can be triggered by another action. For example, you can create a loop action that will execute another action multiple times.
  * *From code* - an action can be called from any code field using the `await {{actions.actionName.trigger()}}` syntax.\
    You can also pass an argument to an action and it will appear as a `{{data}}` variable in the first action step, for example: `await {{actions.actionName.trigger({ limit: 10 })}}`\
    More examples of specific use cases [here](/concepts/actions/action-basics/use-actions.name.trigger).
  * *On Page Load/On App Load/On App Data* - similar to a component trigger, special app and page triggers are available. These triggers allow you to load an app configuration and reuse it later in page actions and components.\
    More examples of specific use cases [here](/concepts/app-and-page-triggers).
  * *Execute action button* **-** manually run the action in the development mode while you are developing and testing the action.
  * *Proceed from step* - manually run the action from a selected step. This is especially useful during development when you want to re-run only a certain part of the action.

## Referencing the result of a specific action step

You can also reference the result of a specific action step by its name using the syntax `{{steps.stepName.data}}`.  It may come in handy when you want to utilize the outcome of multiple steps in a single step.&#x20;

Here is an example of using the result of two steps - mapping user orders to the user object:

```javascript
const users = {{steps.loadUsers.data}};
const orders = {{steps.loadOrders.data}};

return users.map(user => {
  const userOrders = orders
    .filter(order => order.userId === user.id);

  return {
    ...user,
    userOrders,
  };
});
```

<figure><img src="/files/2h5boQdUsASTWZSzobkj" alt=""><figcaption></figcaption></figure>

{% hint style="success" %}
Double-click the action step to change its name.
{% endhint %}

## Reusing actions

Actions can be called from other actions using the **Execute action** step. It's useful when you want to reload data after having created a new item.\
By default, the result of a step that goes *before* the Execute action step will appear as a `{{data}}` variable in the first step of the action being called. The result of the action being called using the Execute action step will appear in the *next step* that comes right after it.

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

Additionally, you can also trigger another action for **On Success** and **On Error** results in the *Finish* step of the action. For example, if the action is executed successfully you can reload its data with updated values, and if not, you can display an error notification.

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

If you have a global action that you'd like to reuse across multiple applications, you can extract it to the [Actions library](/concepts/actions/actions-library).


# Calling actions from code

Sometimes, you may have to handle the result of another action or execute some actions in bulk using JavaScript. In such cases, you can use the `await actions.action.trigger()` syntax, which returns [Promise](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Promise) and allows you to reuse the result of other actions.

Let’s check a couple of cases where `action.trigger` may come in handy.

## Basic setup

As we've already mentioned before in [Triggering actions](/concepts/actions/action-basics#triggering-actions), you can use `await actions.actionName.trigger()` anywhere in your app actions code:

```javascript
const user = await {{ actions.getCurrentUser.trigger() }};
```

As well as pass custom parameters to the action:

```javascript
const user = await {{ actions.getUser.trigger('email@example.com') }};
```

The parameters will be available as `{{params}}` variable in any action step.

## Bulk actions

### Getting data for a certain record

Let’s review the case when you need to combine the data from several tables about a certain record. As an example, we will use **MySQL** data source and two tables: **Orders** and **Order Details**.&#x20;

#### To combine data from two actions:

1. Create a new action of the **Load Table** type and select the *Order Details* table as a resource. \
   Make sure the action is titled as *loadOrderDetails.*
2. In the **Filters** section, configure selected record field as `Order Number ={{data}}`.
3. Next, create a second action of the **Load Row** type and select the *Orders* table as a resource. \
   Make sure the action is titled as *loadOrder.*
4. In the **Filters** section, configure selected record field as `Order Number ={{data}}`.
5. Now, to combine the data from both actions, create a new action of the **JavaScript code** type.&#x20;
6. Rename the action to *loadDetails* and specify the following code:

<pre class="language-javascript"><code class="lang-javascript"><strong>const orderId = {{data}};
</strong>
const [orderData, orderDetails] = await Promise.all([{{ actions.loadOrder.trigger(orderId) }}, {{ actions.loadOrderDetails.trigger(orderId) }}]);

return {
	...orderData,
  	details: orderDetails,
  	detailsNames: orderDetails.map(item => item.productCode),
  	detailsAmount: orderDetails ? orderDetails.length : 0,
};
</code></pre>

![](/files/Vfz8ns73QpydszAWDc7T)

7. To display the obtained data, add the **Detail** component to your working area.&#x20;
8. Assign the *loadDetails* action to the data field of the Detail component.

{% hint style="info" %}
You'll need to hide the *Details* field and make the *Details names* and *Details amounts* fields visible. For the Details names field, you may also change its view type to **Multiselect/Tags** for better representation.
{% endhint %}

### Getting data for all records

#### To get combined data for all records in a Table:

1. Create a new action of the **Load Table** type and select the *Order Details* table as a resource. \
   Make sure the action is titled as *loadOrderDetails.*
2. In the **Filters** section, сonfigure selected record field as `Order Number ={{data}}`.
3. Next, create a multi-step action - add your *loadOrderDetails* action as the **first step**.

{% hint style="info" %}
For testing purposes, we recommend setting the limit to 10 records.
{% endhint %}

4. For the **second step**, create a new action of the **JavaScript code** type.
5. Rename the action to *applyDetailsToOrder* and specify the following code:

```javascript
async function applyDetailsToOrder(order) {
    const orderDetails = await {{ actions.loadOrderDetails.trigger(order.orderNumber) }};
    return { 
        ...order,
        details: orderDetails,
        detailsNames: orderDetails.map(item => item.productCode),
        detailsAmount: orderDetails ? orderDetails.length : 0,
    };
}

const result = {{ data }}.map(item => applyDetailsToOrder(item));
return Promise.all(result);
```

6. To display the obtained data, add the **Table** component to your working area.&#x20;
7. Assign the *applyDetailsToOrder* action to the data field of the Table component.

{% hint style="info" %}
You'll need to hide the *Details* field and make the *Details names* and *Details amounts* fields visible. For the Details names field, you may also change its view type to **Multiselect/Tags** for better representation.
{% endhint %}

### Getting specific items from the table

Let's say you have a table with lots of records, but you only need to display certain items. We'll show you how you can do that based on the example of an HTTP API data source.

#### To get specific records from the table:

1. Create a new action:\
   \
   a. Select your HTTP API data source and **HTTP request**.\
   b. Set `GET` method and set your URL as:\
   \
   *<https://example-data.draftbit.com/users/{{params>}}*\
   c. Rename your action as *getUser*.
2. Add another action of the **JavaScript code** type.
3. Rename the action to *getUsers* and specify the following code:

```javascript
const usersToGet = [1, 10, 2, 5, 9];
const result = [];
for (const id of usersToGet) {
    const user = await {{ actions.getUser.trigger(id) }};
    result.push(user);
}
return result;
```

4. To display the obtained data, add the **Table** component to your working area.
5. Assign the *getUsers* action to the data field of the Table component.

{% @arcade/embed flowId="NGC57yYvJDygcR3HO7UQ" url="<https://app.arcade.software/share/NGC57yYvJDygcR3HO7UQ>" %}


# Actions management & shortcuts

## Scope

UI Bakery actions can be **page-specific** or **global**:&#x20;

* *Page-specific* actions are executed only on a certain page.

You can easily convert a page-specific action into a global one or the other way around, just by dragging the action to the corresponding folder.

You can also assign a page-specific action to another page, by simply moving it to the global state first and then transferring it to the necessary page.

{% @arcade/embed flowId="KZbKlRSAvqjFjV02W53t" url="<https://app.arcade.software/share/KZbKlRSAvqjFjV02W53t>" %}

* *Global* actions are available across the whole app.

When using a global action, the **value it holds will be retained across page navigations** by default. This means that if you load some configuration settings using a global action, the settings will be accessible on all pages of the app using `{{actions.actionName.data}}`.

## Folders

Folders offer a handy way of structuring your actions and are especially useful when you have a lot of actions in your application. You can drag actions to different folders as well as remove them. You can also add folders inside other folders - make a nested structure.

{% @arcade/embed flowId="oMN3rX5oX4n9NFJRt7A4" url="<https://app.arcade.software/share/oMN3rX5oX4n9NFJRt7A4>" %}

## Usages

From the *Actions* panel, under the **Usages** tab on the right side, you can check where each action is used. Here, you can see whether an action is used in any components, other actions, or references. If you click on a specific component or action, you will be taken to its settings, and you'll be able to make any adjustments if needed.

<figure><img src="/files/7ObgVQewAMndNcKOyDlM" alt=""><figcaption></figcaption></figure>

## Hotkeys

During development, all actions can be run using the `Ctrl + Enter/Cmd + Enter` hotkey.&#x20;

:heavy\_check\_mark:You can quickly navigate to a certain action using `Cmd/Ctrl + click`. Simply point to the action name and use the hotkey - it will take you to the selected action. In the same way, you can navigate to components.

:heavy\_check\_mark:For action steps that have a code editor, such as **JavaScript Code** and **SQL Query**, the following hotkeys are supported:

* `Ctrl + F/Cmd + F` - find in code;
* `Ctrl + G/Cmd + G` - next find result;
* `Shift + Ctrl + F/Cmd + Option + F` - find and replace;
* `Ctrl + L/Cmd + L` - jump to a line;
* `Ctrl + Alt + L/Cmd + Option + L` - format code.


# Actions settings

Each action has a [Setup step](#setup-step-settings) and a [Finish step](#finish-step-settings) that contain additional settings you can configure. In this article, we'll explore these steps and their settings in more details.

## Setup step settings

### Trigger

This section contains automatic action triggers you can turn on/off. You can find more information about them on [this page](/concepts/actions/action-basics#triggering-actions).

### Execution

* **Delay action execution for (ms)** - you can specify the delay before your actions are executed (in ms) to prevent them from running too often.
* **Preserve action value** - if selected, global actions will retain their values, meaning that these values are saved and remain unchanged during page navigations; and the action is not executed again.\
  This feature is particularly useful when you want to store global values that can be reused throughout the user's session. Nonetheless, you have the option to deselect this setting if you prefer the action to refresh its value when you next open the page.

![](/files/GP1ZI1empFhvINHd39Th)

### Dialogs

Turn on the **Show a confirmation alert before execution** toggle and specify your message to configure a confirmation dialog for your users. It may be especially useful when you want to prevent users from executing specific actions by mistake.

![](/files/MkjrQ2aaBYOknTx6HhuY)

## Finish step settings

### Chain actions

Refer to [this page](/concepts/actions/action-basics#reusing-actions) for more information on configuring chain actions.

### Toasts

You can configure *Success* or *Error* toasts for your actions. The Error toast is on by default.

For each toast, you can modify the message displayed, specify its duration or select the **Hide notification only when clicked** checkbox.

![](/files/BcyVgyFV0f17JtSiBCfy)

You can also *use the action result in the toasts*, for example:

* Notify about a successful item addition:

`New customer created {{actions.yourAction.data.id}}`

* Show an error for a failed action:

`Action failed with an error {{actions.yourAction.error}}`

{% @arcade/embed flowId="c0d8n9Npkl4q3oB7DUmF" url="<https://app.arcade.software/share/c0d8n9Npkl4q3oB7DUmF>" %}

#### Customizing error messages

You might want to create customized error messages that will give your users more clarity about the error. In order to do that, you can use the `throw new Error` code and customize the message text.

Let's say you want to throw an error that doesn't allow editing a row in the table on a certain condition. Here's how you can do that:

1. Create a new action of the **JavaScript Code** type.
2. Specify the following code:

```javascript
if (!{{state.allowRowEdit}}) {
    throw new Error ('Row editing is not allowed')
}
```

{% hint style="warning" %}
If you are using one of the predefined actions (Load Table, Create Row, etc.), then create a multistep action with the *JavaScript Code* step as the first and the *predefined action* as the second step.
{% endhint %}

<figure><img src="/files/3DGNrXwCJk7tOjS9nf98" alt=""><figcaption></figcaption></figure>

3. For the **Finish** step, navigate to the Error toast, and refer to the error message as `{{actions.yourAction.error.message}}`.

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

Now, anytime the condition is met and the action fails, the user will see a customized error message.


# Actions library

**Actions Library** is a collection of actions that can be reused in your workspace apps and automations. Actions created in the library have the full power of UI Bakery actions, including the ability to load and send data, trigger other actions, and execute JavaScript code.

Actions created in the library are not directly connected to any specific app. This means that you **cannot access app components or state variables**, but you can use the workspace's data sources, and actions can accept parameters to customize behaviour.

{% hint style="info" %}
Actions created in the library are only accessible using the **Execute Action** step and are not directly available in the app's actions code.
{% endhint %}

## Creating a library action

Let's review an example of creating a library action that will load data from the database. You'll be able to use this action across multiple apps.&#x20;

Our flow will consist of two parts:

1. Creating a basic reusable SQL action
2. Adding variable parameters to it (filtering data)

### To create a basic reusable SQL action:

1. Go to your workspace and click the **Actions Library** link in the bottom left corner of the screen.

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

2. Create a new action of the **SQL Query** type and specify the following code in the query field: `select * from users;`&#x20;
3. Name your action **loadUsers**.\
   Now the action is ready to be used in any app in the workspace.&#x20;
4. Open your app and use the action you've just created via the **Execute Action** step.

Once you run the action, you can see that the data is loaded in the **Result** tab. The action is now complete and ready to be reused in multiple apps and automations.

{% @arcade/embed flowId="G6tx2Am1cPlzt9ciwBm6" url="<https://app.arcade.software/share/G6tx2Am1cPlzt9ciwBm6>" %}

### To add variable parameters to the action:

1. Go back to the Actions Library and open your **loadUsers** action.
2. In the **Default params** section on the right, add the following filter parameter that will be used in the query. Here, also define the *default filter value* in the query so that the action can be executed without passing any parameters and will not fail during development and debugging:

```javascript
{
  filter: ''
}
```

{% hint style="info" %}
Parameters is an arbitrary JavaScript object that can be passed to the action in the runtime.
{% endhint %}

3. Next, specify the following condition in the query that will use the filter parameter you've added:

```javascript
WHERE users.first_name like {{ '%' + params.filter + '%' }}
```

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

4. While testing the action, you can change the parameter value and see how it affects the query result.

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

5. Now, revert the filter to the default empty string value and go back to your app.
6. Select the **Execute Action** step you've created before.
7. In the **Custom action params** field, hardcode some filter value to pass it to the action, for example:

```javascript
{
  filter: 'sammy'
}
```

{% hint style="info" %}
If prompted, click the Reload button to synchronize the latest changes made in the Actions library.
{% endhint %}

8. Next, run the action to observe the filter being applied.

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

9. You can also use the component or state values as the arguments passed in the action.

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

10. Lastly, assign your action to a component to display the data.

## Publishing and environments sync

To use Actions Library in Production or Staging environments, **you need to release the library**. This will create a new version of the library that can be used in Prod or Staging, while you can still modify the library in the Development environment.

### To release the Actions Library:

1. Go to the Actions Library.
2. Click the **Release** button in the upper right corner.
3. In the pop-up window that opens, set a version, add a description if needed, and click **Publish release.**

That's it! Now, if you release your app, UI Bakery will remind you to release the Actions Library as well.

{% @arcade/embed flowId="b4AfXscVnQC6GSt30q8I" url="<https://app.arcade.software/share/b4AfXscVnQC6GSt30q8I>" %}

{% hint style="warning" %}
The Actions Library environment is linked to the app environment, which means that your actions will use the **same data source environments as your app**. For example, if your app is connected to the production database, your library actions will also use the production database in the Production environment.
{% endhint %}

## Moving an Action to the Library

In some cases, you might want to move an action from the app to the Actions Library to make it more abstract and reusable.

### To move an action:

1. Click on the three dots next to the action you want to extract and click **Copy**.
2. Next, go to the Actions Library, click the plus sign in the **Actions** section and select **Paste**.\
   The action will be copied to the library.
3. Modify it, if necessary, and remove all references to UI components or state variables.
4. Go back to the app and replace this action with the **Execute Action** step and add the action you moved to the library.

{% @arcade/embed flowId="epQiGVw1SjoIWW71JPdM" url="<https://app.arcade.software/share/epQiGVw1SjoIWW71JPdM>" %}

## Private actions

During development, you may want to create an action that is not ready to be used in your apps. Or you may want to create an action that is not intended to be used in other apps but can be used in other actions in the library.\
In such cases, you can turn off the **Shared** toggle in the Action settings. This will make the action private and it will not be available in the app's Actions list.

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


# Server actions

Server actions enable you to create **custom backend logic** that runs entirely on the server, unlike regular actions that are executed in the browser. For better understanding, you can think of server actions as API endpoints.

Furthermore, server actions are hidden from the end user. This means that configurations, code, and information specified in server actions are not accessible to the user and are executed and stored on the server. Only parameters sent to the action and the result of the action are accessible.

## Creating a server action

{% hint style="danger" %}
Server actions are subject to **Scheduled Jobs/Webhooks** & **Server Actions** limits. For more details, please refer to the [pricing page](https://uibakery.io/pricing).
{% endhint %}

Server actions can be created, just like regular actions, using the plus sign in the **Actions** panel. Once created, these actions can be assigned to a component trigger or executed via the Execute Action step.\
Server actions, like regular actions, can consist of multiple steps to load, send, and process data using JavaScript or Python.

Let's review an example of creating a basic server action that will accept a value from an input component and multiply it.

### To create a server action:

1. Click on the plus sign in the Actions panel and select **Server action**.
2. Next, select the **JavaScript Code** step.
3. Add a **Text input** component to the page and set its value using the **Params** settings object of the server action:

```javascript
{
  value: {{ui.input.value}},
}
```

4. Update the JavaScript code step to return the newly configured parameters:

```javascript
return {{params}};
```

5. Enter some number in the input form - execute the action and check the result.

{% @arcade/embed flowId="uha4zrzmUs5ZdipI3CXY" url="<https://app.arcade.software/share/uha4zrzmUs5ZdipI3CXY>" %}

6. Now, add another JavaScript code step to your action to multiply the value of the input:

```javascript
return {{data.value}} * 3;
```

&#x20;     Execute the action and check the result.

7. Next, add a button to your page that will execute the action and a Text component to display the result.
8. For the **button**, select your server action as the **On Click** trigger.&#x20;
9. For the **Text component**, add the following reference to the **Value** field:

`Result: **{{actions.multiplyInput.data}}**`

That's it! Now you have a functioning server action fully executed on the server.

{% @arcade/embed flowId="vPbC89rZ3InXbVMABcyQ" url="<https://app.arcade.software/share/vPbC89rZ3InXbVMABcyQ>" %}

## Difference between Client-side actions and Server actions

So what are the differences between server actions and regular actions and in what cases would either of them be better? Let's explore this in more details.

| Feature                                         | Client-side actions                                            | Server actions                                                                                                                                                        |
| ----------------------------------------------- | -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Access to browser context**                   | Full access to browser context                                 | No access to browser context (for example, document, window)                                                                                                          |
| **Security**                                    | Exposed to end user, potentially less secure                   | Hidden from the end user, ensuring secure handling of permissions and user-based logic                                                                                |
| **User variable (`{{user}}`)**                  | Depends on client-side context                                 | Securely defined on the server based on authentication, cannot be falsified                                                                                           |
| **Referencing components, actions, or methods** | Can reference components, actions, and call UI element methods | Cannot reference components, other actions in steps, or call UI element methods (for example, `{{ui.modal.open}}`); values must be specified in the **Params** object |
| **Data sharing between steps**                  | Data is passed to the client for each step                     | Data is not passed between steps via the client; execution happens entirely on the server                                                                             |
| **Execution behavior**                          | Run step-by-step on the client, outcomes shown incrementally   | Run entirely on the server, outcomes shown after completion                                                                                                           |

### When to choose Client-side actions vs. Server actions

| Choose client-side actions                                                       | Choose server actions                                                                      |
| -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| <ul><li>Load and send data between UI components and client-side logic</li></ul> | <ul><li>The secure operation code must remain inaccessible to users at all times</li></ul> |
| <ul><li>Actions with navigations and redirects</li></ul>                         | <ul><li>Extensive data transfer between steps</li></ul>                                    |
| <ul><li>Calling component methods or accessing document/window objects</li></ul> |                                                                                            |


# Logs and debugging

Logs provide you with information on how actions are executed. Once you run an action, you can check out the **Logs** tab at the bottom to access this data.

Here, you can switch views depending on the log levels you need or just manually select specific levels:

* **Default logs level** - Log, Info, Warn, Error
* **Full logs level** - default plus Verbose

<figure><img src="/files/2p59AJQPGgcZzAIvVA8L" alt=""><figcaption></figcaption></figure>

## Debugging actions with `console.log`

When working with the code, it's important to be able to debug it right away. You can add a `console.log` function to your code and troubleshoot right in the **Logs** tab, with no need to open the browser developer console.

Let's say you have a transformer function that is not working properly, and you want the user with the customer number 103 to have a correct value. Add the following `console.log` function to your code, run the action and check the **Logs** tab:

```javascript
return data.map(item => {
  if (item.customerNumber === 103) {
    console.log('Customer #103', item);
  }
  return item;
});
```

![](/files/dcAN7JEHnaVUdENRULkZ)

Now, you can see the result of your `console.log` function in the Logs tab. You can use the same approach and debug your code directly in the app, without the need to open the developer console.

Besides the `console.log` , other standard functions are supported as well, such as `console.info`, `console.warn` and `console.error`.

## Debugging actions performance

You can find details about the action's performance, for example its response time, in the **Result** tab. Simply hover over the question mark icon next to the **Request time** metric.

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

{% hint style="info" %}
Tooltips are available for all data source-based actions.
{% endhint %}

The following metrics are available:

* **Request time** - total time taken to make the request
* **Response size** - the size of the response returned by the server
* **Request sent** - time taken to upload the request to the UI Bakery server
* **Data source roundtrip** - time to send the request from the UI Bakery server to your data source and receive the result back to the UI Bakery server
* **Content download** - time to download the response from the UI Bakery server to a user browser

### UI Bakery performance limits

The following limits are fixed for the cloud version, but can be set up in the on-premise version.

| **Response size**       | 25MB       |
| ----------------------- | ---------- |
| **Request size**        | 50MB       |
| **Timeout**             | 90 seconds |
| **Requests per second** | 3          |


# App & page triggers

UI Bakery allows you to set specific triggers for the whole application and individual pages as well. In this article, we'll describe all the available triggers and how you can configure them, with some use case examples.

App & page triggers are located in the *right side panel* in the Builder, when the app page is in focus.

<figure><img src="/files/6yfZalxPsbBoo3kKUux3" alt=""><figcaption></figcaption></figure>

The following triggers are available:

* **Page triggers**
  * [On Page Load](#on-page-load)
* **App triggers**
  * [On App Load](#on-app-load)
  * [On Page Load](#on-page-load-1)
  * [On App Data](#on-app-data)

Once you assign actions to these triggers, they'll be executed for the entire app or for a specific page.\
If you select the *Delay actions and show loader* checkbox for specific triggers, they will be executed first, if not - the actions assigned to all triggers will be executed in parallel.

## Page triggers

### On Page Load

This trigger is available on both page and app levels. The difference is that on the page level it triggers an event only for the specific individual page you configure it for.&#x20;

Some of the most common use cases here may be the following:

* Fetching page-specific data (for example, loading records for a detail view)
* Redirecting if not authenticated

{% @arcade/embed flowId="KWiBT40DTSefczFenT8G" url="<https://app.arcade.software/share/KWiBT40DTSefczFenT8G>" %}

## App triggers

### On App Load

This trigger fires an event when the application is initialized. It can be useful when you want the system to first make all the necessary requests to the database or API before rendering the application.\
For example, you may have different user roles available, and you first need to learn the role before loading the data specific to that role. Or you may need to fetch configuration data and feature flags first, as well as apply themes and localizations.\
Here, you can check the app localization and theme configuration examples that you can use in your application:point\_down:

{% content-ref url="/pages/GmY8kCVVn22IPUIvjRm2" %}
[Internationalization (i18n) & Localization: Translating UI Bakery Apps](/how-tos/data/connect-external-js-library/internationalization-i18n-and-localization-translating-ui-bakery-apps)
{% endcontent-ref %}

{% content-ref url="/pages/ZU7MRbXZmS9eGWY9vQnZ" %}
[Changing theme from the app](/concepts/theme-editor/change-theme-from-the-app)
{% endcontent-ref %}

### On Page Load

On the app level, this trigger fires an event not for a specific page but for each page in this application. \
For example, you want to track page views for all the pages in your app - you can assign this tracking action for the On Page Load trigger. It will gather this information each time every page in the app is loaded. This is more convenient than assigning the action to the trigger on the page level.

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

### On App Data

This trigger fires a specific event when the *Data* value of your component changes.\
For example, you have embedded another app inside your application and you want the users to be informed of any changes to its Data. You can show an alert for them in this case by assigning your action to this app trigger.\
This way, every time any changes are made to the Data value of the embedded app component, users will see a notification.

{% @arcade/embed flowId="4qqnmnpBmX2ztsBoqzBO" url="<https://app.arcade.software/share/4qqnmnpBmX2ztsBoqzBO>" %}


# UI Bakery variables

**UI Bakery variables** serve as the glue that binds both Action data to UI components/Actions and Component values to Actions/other Components. UI Bakery variables are always wrapped with \
`{{ }}` to distinguish them from JavaScript variables.

You can use component values, action results, state variables, page parameters, etc. as variables.

![](/files/9gwlLfy9E7N1spvsEUlQ)

Variables can be used inside **Component** and **Action properties**. For non-text component properties, you can switch to the `JS` mode to use variables.&#x20;

To start searching for a variable, you just need to type `{{` in the code or text field, and a variables selector will appear.

{% hint style="success" %}
Use **Option ⌥ + Esc** (Mac) or **Ctrl + Space** (Windows/Linux) as shortcuts to open the variables selector.
{% endhint %}

{% @arcade/embed flowId="hHQ1F8IyzfT3ditvOC9F" url="<https://app.arcade.software/share/hHQ1F8IyzfT3ditvOC9F>" %}


# State variables

State variables function as *temporary storage* while users interact with your app. They reset to their initial values upon a page refresh or when the app is closed.

{% hint style="info" %}
For persistent storage that remains intact upon page reloads, consider exploring [Local storage](/concepts/localstorage).
{% endhint %}

State variables come in two **scopes**:

* *App* - maintain their values across different pages
* *Page* - maintain their values only within a specific page

State variables can possess the following **attributes**:

* *Name* - the unique identifier
* *Type*
  * String
  * Number
  * Boolean
  * Object
  * Arrray
* *Initial value* - the starting value of the variable

State variables can be organized using the **folder** structure of the *Actions* panel, allowing you to place relevant variables near the actions and logic they are associated with.

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

## Creating a state variable

You can create a state variable, same as you would create an Action, from the **Actions** panel. You just need to click the *plus sign* and select **State variable**. From there, specify your variable *type*, assign it an initial *value*, and give the variable a meaningful *name*.

That's it! You can now read and write to this variable. Depending on its scope, it will be available either in the whole app or on a particular page.

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

## Working with a state variable

Now that you've created a state variable, let's explore how you can use it in your app. Within the app, all variables can be accessed using the `{{state.<variableName>}}` scope.

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

You can also assign a value to a variable using the **Save to State** **action** or directly through **JavaScript code blocks**. Check out the sections below for more details :point\_down:

### Save to State action

To store a value within an action, follow these steps:

1. Add a new *step* to your action and select **Save to State**.
2. Select the variable you want to modify.
3. Define a new value for the variable, which can either be hardcoded or derived from an available variable.\
   For example,`{{data}}` to reference the result of a previous action step, or `{{ui.component.value}}` to access a component's state.

{% @arcade/embed flowId="B9U2Cqhf8BgqQyfM8nkX" url="<https://app.arcade.software/share/B9U2Cqhf8BgqQyfM8nkX>" %}

### Save and reset variable value with code

You can use state variables in JavaScript code blocks to:

* **Save** state values

```js
state.varName = 'newValue';

// or using setValue method
state.setValue('varName', 'newValue');
```

* **Read** state values

```js
state.varName;

// or using getValue method
state.getValue('varName');
```

* **Reset** a single variable or all variables to the initial value

```js
state.resetValue('varName');
```

```js
// reset all variables, page and app state
state.resetValues();

// reset all page variables
state.resetValues('page');

// reset all app (global) variables
state.resetValues('app');
```

Let's review an example of resetting a state variable value - you have a **Text input** component and you would like it to reset its value after a comment is left.&#x20;

Here's how you can do that:

1. Create a state variable of the **String** type, define its initial value, and name it *Comment*, for example.
2. Assign the variable to the *Text input* component's **Value** field.
3. Create a **Save to State** action that will save the new comment - define its value as `{{data}}`.
4. Assign this action to the **On Change** trigger of the *Text input* component.
5. Next, create another *Save to State* action, that will reset the comment - define its value as `''`.
6. Now, add a **Button** component to the working area and assign the *Reset* action to its **On Click** trigger.

Done! Now, after you leave a comment and click the button, the component will reset its value.

{% @arcade/embed flowId="15HvrIPysPifscjlAkCL" url="<https://app.arcade.software/share/15HvrIPysPifscjlAkCL>" %}


# Local storage

`localStorage` is permanent browser storage, which is available across all browser tabs of your application and after page refresh. You can find more details about it [here](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).

To save data to the `localStorage`, you can use the **Save to Local Storage** action or the **JavaScript Code** action - `setItem`.

After that, you'll be able to use the data from either of these actions with `{{localStorage.getItem('varName')}}`, where `varName` is the name of the variable used in the action.&#x20;

{% @arcade/embed flowId="G4oWCIYYzfaEbZdeKGIp" url="<https://app.arcade.software/share/G4oWCIYYzfaEbZdeKGIp>" %}

## Using localStorage API

<table><thead><tr><th width="309">Method</th><th>Description</th></tr></thead><tbody><tr><td><code>getItem(key)</code></td><td>Retrieves the value associated with the specified key.</td></tr><tr><td><code>async setItem(key, value)</code></td><td>Adds key's value or updates key's value if it already exists. Method throws an error when size quota is exceeded.</td></tr><tr><td><code>async removeItem(key)</code></td><td>Removes the key-value pair with the specified key.</td></tr><tr><td><code>async clear()</code></td><td>Clears all key-value pairs stored in <code>localStorage</code>.</td></tr></tbody></table>

Examples of usage:

```javascript
// Save data to localStorage
await {{localStorage}}.setItem('foo', 'bar');

// Retrieve data from localStorage
const username = {{localStorage}}.getItem('foo');

// Remove data from localStorage
await {{localStorage}}.removeItem('foo');

// Clear all data from localStorage
await {{localStorage}}.clear();
```

## Use case: Saving a draft message

In this example, we will show you how you can use local storage to save a *draft* message so that it's not lost upon page refresh or closing the app.

1. Start with adding a **Text input** component to the working area.
2. Next, create a **Save to Local Storage** action, and specify the variable *name* and *value*.
3. Assign this action to the ***OnChange*** trigger of the Text input component.
4. Finally, assign the `localStorage` variable to the **Value** filed of the Text input:\
   `{{localStorage.getItem('draft')}}`.

{% @arcade/embed flowId="d2MPTYFIoAiFuhAXZYSd" url="<https://app.arcade.software/share/d2MPTYFIoAiFuhAXZYSd>" %}


# Modules

A **module** is a powerful tool that enables the creation of fully fledged applications that can be later reused across other applications within your workspace. It serves as a time-saving solution for complex blocks of logic that need to be reused in multiple applications.&#x20;

A module is created as a separate application, and it can seamlessly communicate with a parent application, sending and receiving events.

## Creating a module

You can create a module from the Workspace dashboard - scroll down to the *Library* section, click the **+ button** and select **Module**.

A new window will open where you should give your module a meaningful name that will be used further in development, and confirm the creation. The newly created module will be added to the *Library* which contains all your modules as well as custom components.

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

From the Library, you can access the module's **settings** to modify the *name* or activate *Public* *mode*. Public mode makes the module available to unauthorised users, which is necessary if the module is used in a public app.

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

## Working with a module

The Builder interface for modules differs from the one used for applications. Unlike applications, modules don’t have pages or render settings. Instead of the `body,` modules have a `moduleContainer`  component which acts as the host for module content. \
The module container's width is customizable. You can set a default width for all modules, which can then be adjusted individually for each instance.

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

You can drag components into the module container, define custom logic for your actions, and integrate third-party libraries as needed. \
Once you are done developing a module, you can [release your changes](/build-from-scratch/getting-started/deploy-your-application-and-invite-users#deploying-an-app) to Staging and/or Production, publish a [Draft release](/concepts/workspace-management/app-environments/release-management#creating-a-draft-release), as well as [revert to an older version](/concepts/workspace-management/app-environments/release-management#restoring-a-release-version) from the Release history.

{% hint style="info" %}
Users can access modules based on their permissions. You can assign permissions to modules just like for regular applications.
{% endhint %}

## Using a module inside an app

Once you've developed and released your module, you can use it inside any of your applications. To **add a module to an app**, follow these steps:

1. Open the app you need and navigate to the **Library** subtab (of the *Components* tab) in the left side panel.
2. Select the module you want to use and drag it into the working area.&#x20;

{% hint style="info" %}
You will notice that unlike regular components that are highlighted in <mark style="color:blue;">blue</mark>, the whole module is highlighted in <mark style="color:green;">green</mark>.
{% endhint %}

3. Click the **Edit module** button in the right side panel to make changes to the module. \
   You will be redirected to a new page where you can make the necessary changes, release them, and get back to the application.
4. Click the **Reload** button in the right side panel to refresh the changes in the module.

{% @arcade/embed flowId="gIp5DuAvlri9bIPWiURu" url="<https://app.arcade.software/share/gIp5DuAvlri9bIPWiURu>" %}

## Communicating with an app

In this section, we'll review two ways of communicating with an app via a module - sending data from the app to the module and vice versa.

### Sending data from an app to a module

You can send the data to a module by calling the `{{ui.module.setData({userId:1})}}` method. Alternatively, you can also set the module data in its **Data** field.

<figure><img src="/files/63LQbgiztkCe1yCeKlfn" alt=""><figcaption></figcaption></figure>

Then, in the module, you can also subscribe to the **On Data** trigger with the last received value accessible in the `{{module.data}}` variable.

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

### Sending data from a module to an app

To send the data from a module to an app, you can call the `{{module.triggerEvent({data:'from module'})}}` method from any module's code step.

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

Then, in the app, you can subscribe to the module's **On Event** trigger, with the last received value accessible in the `{{ui.module.value}}` variable.

<figure><img src="/files/9qaZEuB3oEOeuTnLy1WY" alt=""><figcaption></figcaption></figure>


# Custom JavaScript

UI Bakery also allows you to use custom JS code throughout the application. Check out some examples below of utilizing JavaScript in UI Bakery :point\_down:

<table data-view="cards"><thead><tr><th data-type="content-ref"></th></tr></thead><tbody><tr><td><a href="/pages/pzV7zlEXH7FyV5irnBsA">/pages/pzV7zlEXH7FyV5irnBsA</a></td></tr><tr><td><a href="/pages/jaY9PNQamxaZAwBDTEbc">/pages/jaY9PNQamxaZAwBDTEbc</a></td></tr></tbody></table>


# JavaScript files

JavaScript files help organize custom functions and variables. They're perfect for storing reusable helpers and utilities within components and actions.

You can create a JS file from the *Actions* panel - simply select this option after clicking on the *plus* sign.

<figure><img src="/files/0pg76ASBichVwSZLhOyx" alt=""><figcaption></figcaption></figure>

All top-level variables and functions defined in a JS file are accessible in Components and Actions.

## Scope

JavaScript files are scoped based on their creation context:

* **App-level** files - can be utilized across any component or action within the app
* **Page-level** files - restricted to their specific page

When multiple functions with the same name are defined in a scope, then the most recent definition will take precedence.

## Cross usage

Functions and variables defined in one JS file can also be used in others.&#x20;

{% hint style="success" %}
Since JavaScript files are added to a page as scripts, you can only reference definitions from files loaded earlier.
{% endhint %}

<figure><img src="/files/9Rry8jUIzwO3AmbhHj2e" alt=""><figcaption></figcaption></figure>

## Global and custom libraries

JavaScript files can also contain *custom* libraries in the custom code section as well as *default* libraries like `moment` and `lodash`.

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

## Debugging

You can troubleshoot any issues you have with a JS file by inserting a console log statement and running the function within an action or component, for example:

```javascript
function dateWithLabel(date) {
  console.log('debug!', date);
  return 'Date: ' + moment(date).format('MM/dd/yyyy');
}
```

The logs will appear in the **Logs** panel, prefixed with the file name where the code resides.

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

{% hint style="info" %}
Code that contains invalid JavaScript and cannot be parsed is not incorporated into the page.
{% endhint %}


# Workspace management

In this section, you'll explore everything connected with your workspace, account, seats & roles, editing app interface, release management, and more. Go ahead and check it out :point\_down:

<table data-view="cards"><thead><tr><th data-type="content-ref"></th></tr></thead><tbody><tr><td><a href="/pages/RDg4ihVPHneSagpZQ9u9">/pages/RDg4ihVPHneSagpZQ9u9</a></td></tr><tr><td><a href="/pages/PSib8ogjRNxKVoEG1gMb">/pages/PSib8ogjRNxKVoEG1gMb</a></td></tr><tr><td><a href="/pages/9Lpd5Xt1eDaWoIWectjG">/pages/9Lpd5Xt1eDaWoIWectjG</a></td></tr><tr><td><a href="/pages/RKXmAqrUrTS5h4RFMoEv">/pages/RKXmAqrUrTS5h4RFMoEv</a></td></tr><tr><td><a href="/pages/LKk5gOFri09bO6hXt5sJ">/pages/LKk5gOFri09bO6hXt5sJ</a></td></tr><tr><td><a href="/pages/rBkP9R5kIeODyIo4caSf">/pages/rBkP9R5kIeODyIo4caSf</a></td></tr><tr><td><a href="/pages/P3S9GSn7TKvcXLVP0XqW">/pages/P3S9GSn7TKvcXLVP0XqW</a></td></tr><tr><td><a href="/pages/jjKkAsel2SBlBdgTdzKW">/pages/jjKkAsel2SBlBdgTdzKW</a></td></tr></tbody></table>


# Account & workspace

Under your workspace name, you can access you account and workspace settings, check audit logs, get the latest updates, and more.&#x20;

<figure><img src="/files/7hw1W04O1i56JZUc6MAG" alt=""><figcaption></figcaption></figure>

<details>

<summary>Users &#x26; Permissions</summary>

Invite users to your workspace, manage their roles and permissions, create custom roles and shared permission groups.

[Seats & Shared permission groups in UI Bakery](/concepts/workspace-management/seats-and-shared-permission-groups-in-ui-bakery)

[Roles in UI Bakery](/concepts/workspace-management/roles-in-ui-bakery)

[Role permissions](/concepts/workspace-management/role-permissions)

</details>

<details>

<summary>Profile settings</summary>

Access your [account settings](#account-settings).

</details>

<details>

<summary>Workspace settings</summary>

Access your [workspace settings](#workspace-settings).

</details>

<details>

<summary>Audit logs</summary>

Keep track of changes and efficiently manage the organization.

[Audit logs](/concepts/workspace-management/audit-logs)

</details>

<details>

<summary>External analytics</summary>

Track, measure, and analyze user behavior, performance, and other key metrics from your organization. You can connect *Datadog*, *Google Analytics 4*, or *Google Tag Manager*.

</details>

<details>

<summary>Plans</summary>

Access the billing portal or contact our support to upgrade or cancel your subscription.

</details>

<details>

<summary>Watch intro</summary>

Check out our overview video for a quick introduction to building apps in UI Bakery.

[Video intro](/build-from-scratch/video-intro)

</details>

<details>

<summary>What's new?</summary>

Subscribe to our [changelog](https://changelog.uibakery.io/en) to view the latest UI Bakery updates and stay informed about new features and improvements.

</details>

<details>

<summary>Get a demo</summary>

Book a demo with our tech experts to learn more about what UI Bakery can offer to match your needs.

</details>

## Profile settings

Here, you can change your personal details and email, reset the password, as well as cancel your subscription.

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

### Password reset

Admins of the workspace can help with users' password reset. Here, *two* *options* are available:&#x20;

* Sending a new password reset email
* Generating a direct password reset link

To reset specific users' password, head to the *Users & Permissions* page, click on the three dots next to the user, and select the required option.

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

## Workspace settings

Here, you can change your workspace name and URL, and configure specific settings for your end users, such as:

* [Switch between modes](#switching-between-ai-and-low-code-modes)
* Hide [workspace menu](#hiding-the-menu-for-end-users) and [header](#hiding-the-header-for-end-users)
* [Disable command palette](#disabling-command-palette-for-end-users)
* [Enable 2FA](/concepts/workspace-management/multi-factor-authentication)

<figure><img src="/files/9otpYkaU3LgyhvoeZKRj" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
If you change your URL, make sure to adjust your applications' external links.
{% endhint %}

### Switching between AI and Low-code modes

You can choose your experience with UI Bakery yourself - switch between our two available modes to see which one suits your needs.

{% hint style="info" %}
Read more about the two experiences we offer [here](/#how-it-works).
{% endhint %}

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

### Hiding the menu for end users

You can hide the left side menu completely for your end users. To do so, select the **Hide workspace menu for end-users** checkbox in your Workspace settings.

This is what the end user's dashboard will look like:

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

Users will still be able to access profile settings by clicking on their email. For team members with **Admin** or **Editor** roles, the menu will be displayed as usual.

{% hint style="info" %}
If your users need access to more than one application, you will have to configure a custom menu for navigation.
{% endhint %}

### Hiding the header for end users

You can hide the header completely for your end users. To do so, select the **Hide workspace header for end-users** checkbox in your Workspace settings.

This is what the end user's dashboard will look like:

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

For team members with **Admin** or **Editor** roles, the header will be displayed as usual.

{% hint style="info" %}
You should implement **log out** functionality on your own with the `{{user.logout()}}` method.
{% endhint %}

### Disabling command palette for end users

Users can access the command palette (*Cmd + K/Ctrl + K*) for easier app navigation and search but you can also disable this setting, if needed. To do so, select the **Disable command palette for end-users** checkbox in your Workspace settings.

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

Once disabled, users will no longer be able to access the palette.


# Seats & Shared permission groups in UI Bakery

There are three types of seats available in UI Bakery: **Developer, End-user** & **Shared permission groups**. Developers and End-users be assigned individual roles and permissions, giving you granular control over access to apps, data sources, and pages.

<figure><img src="/files/0JnsJ6fw5yXGXKuWxqUe" alt=""><figcaption></figcaption></figure>

* A team member on a *Developer* seat can develop, deploy and edit apps, as well as manage users (depending on the role assigned).
* A team member on an *End-user* seat can use the applications assigned to his role, but NOT edit or develop them.
* A *Shared permission group* (SPG, unlimited seats) is a special type of custom user role that can include **Use-only** permissions for apps and data sources.

{% hint style="info" %}
Shared permission group is available only in the *Low-code* mode.
{% endhint %}

## Shared permission group, explained

If you have any questions regarding our Shared permission group, check out this section for a quick explanation:bulb:

A Shared permission group, as mentioned before, is basically a special type of custom user role. If you assign users to this group, they will have only **Use** permissions for apps and data sources.

* One SPG can include an **unlimited** number of users - so if you have a large group needing the same permissions, you can use a single Shared Permission Group for them.
* If you assign users ONLY to SPGs, they won't be included in the count of End-user seats.
* You have to pay for each SPG separately - for example, if you have two large groups of *internal* and *external* users (requiring different permissions), you'll need to buy two SPGs for them.
* SPGs are better suited for users needing the same level of access, so opt for standard End-user seats if you need really granular permissions and access.
* You can use both SPGs and End-user seats together, no limitations here. That is actually the approach we recommend to make the most of our pricing options.

To sum it up, Shared permission groups are perfect for large user groups (25 and expanding) with the same permissions.

Make sure to check our [blog post](https://uibakery.io/blog/ui-bakery-pricing-explained) for a more detailed explanation of UI Bakery pricing model based on specific use scenarios. We hope it helps!


# Roles in UI Bakery

For every invited seat in UI Bakery, there are three roles available out of the box:

* **Admin** – can invite and manage other users, change workspace settings, develop and deploy apps.
* **Editor** – can view and develop apps.
* **User** - can use the applications in the End-user mode, and can be a member of a shared permission group.

Apart from default user roles, you can also create and assign [custom roles ](#custom-roles)to your users. Each user can be assigned multiple roles if necessary.

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

To learn more about role permissions, refer to this [article](/concepts/workspace-management/role-permissions).

### Filtering users by role

On the *Users & Permissions* page > *Users* tab, you can filter users by their role making it easier to find exactly who you're looking for. For longer lists, you can also control the number of users displayed per page.

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

## Custom roles

### Creating a custom role

You can create custom roles to manage access permissions to your different apps and data sources. For example, if you have Testers who need access only to Staging and Prod, you can create a specific custom role for them and grant separate access to these environments.

#### To create a custom role:

1. Click your workspace name and select **Users & Permissions**.
2. Next, head to the **Roles** tab and click **Add Custom Role**.
3. Give your role a meaningful name and select the necessary permissions for apps and data sources.
4. Click **Create role** to save it.

Once the role is created, you can assign it to your invited users. Each user can be assigned multiple roles.

{% @arcade/embed flowId="pjaxhZDPckPFehY6GvIP" url="<https://app.arcade.software/share/pjaxhZDPckPFehY6GvIP>" %}

When such users log into UI Bakery, they will have access only to the environments you specified.

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

You can always modify your custom roles, if needed, or delete them when they're no longer necessary.&#x20;

## Redirect after login

It's also possible to specify a **Landing page URL** for specific user roles. By default, such users will be redirected to a path you provide after login or direct domain access. It may come in handy if you want to direct your users to a certain app or landing page.

{% hint style="info" %}
Only relative URLs starting with "/" are supported.
{% endhint %}

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

Redirects also work with both *MFA* and *SSO* enabled.


# Role permissions

UI Bakery provides you with the flexibility of specifying permissions you want to grant to different user roles. Below you'll find more details about all permissions and settings available.

{% @arcade/embed flowId="rw5WfWinhoByNrfTpI71" url="<https://app.arcade.software/share/rw5WfWinhoByNrfTpI71>" %}

## Access to applications

| Permission         | Description                                                                                                                                                                                                                                                                                                                                                    |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Use Prod**       | A role with this permission can *view the Prod environment*.                                                                                                                                                                                                                                                                                                   |
| **Use Staging**    | A role with this permission can *view the Staging environment*.                                                                                                                                                                                                                                                                                                |
| **Use Dev**        | A role with this permission can *view the Dev environment*.                                                                                                                                                                                                                                                                                                    |
| **Develop**        | <p>A role with this permission and <strong>Use Dev</strong> can <em>view the Dev environment and edit applications</em>.</p><p>Additionally, a role with this permission or a role with <strong>Can create app</strong> permission can <em>view Data sources, Database, AI Playground and Actions library</em>.</p>                                            |
| **Deploy Stage**   | A role with this permission and **Use Dev** & **Develop** can *view the Dev environment, edit applications, and deploy them to Staging*.                                                                                                                                                                                                                       |
| **Deploy Prod**    | A role with this permission and **Use Dev** & **Develop** can *view the Dev environment, edit applications, and deploy them to Prod*.                                                                                                                                                                                                                          |
| **Can create app** | <p>A role with this permission can <em>create applications</em>. The apps created will be added with the same permissions as the role that created them.<br><br>Additionally, a role with this permission or a role that has at least one <strong>Develop</strong> permission can <em>view Data sources, Database, AI Playground and Actions library</em>.</p> |

{% hint style="warning" %}
Deleting apps is restricted to **Admin** & **Editor** roles.
{% endhint %}

## Access to data sources

| Permission                | Description                                                                                                                                                                                                                                                                                    |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Use**                   | A role with this permission can *make requests to a data source*.                                                                                                                                                                                                                              |
| **Edit**                  | A role with this permission can *make requests to data sources and change their settings, but **cannot delete** them*.                                                                                                                                                                         |
| **Can create datasource** | A role with this permission can *create data sources*<mark style="background-color:yellow;">.</mark> The data sources created will be added with **Use** and **Edit** permissions, same as the role that created them; and users with this role will be able to view the created data sources. |

{% hint style="warning" %}
Deleting data sources is restricted to **Admin** & **Editor** roles.
{% endhint %}


# Explore the interface

## Workspace dashboard

The left side panel of the workspace dashboard is divided into two sections: **Apps** and **Library**. The Apps section shows all your applications, whereas the Library section displays all your created [modules](/concepts/modules) and custom components.\
From the menu, you can quickly navigate between apps, application pages, modules, and custom components. You can hide the menu bar or expand it.

To create a new app, module or custom component, click the **+ button** next to the Apps or Library section and select the necessary item. You can also import items from Git or from an archive.

To open application settings, click on the three dots next to the app's name. From this menu, you can also **edit** (open in the Builder), **duplicate**, [**export**](/concepts/export-import-an-app) or **delete** an app. The *Edit* option here allows you to quickly open a new project in the **Edit** mode - not just the one selected right now - as well as open it in a new tab. This way you can easily switch between contexts without disrupting your current workflow.\
In the same way, you can also access modules/custom components' settings as well as edit, duplicate, export, or delete them.

In the upper right corner of the dashboard, you can switch between [environments](/concepts/workspace-management/app-environments) - **dev**, **staging** or **prod**. The [**Share** button](/build-from-scratch/getting-started/deploy-your-application-and-invite-users#inviting-users-via-share) here allows you to invite users to your app and manage their access. And the **Edit** button will take you to the Builder where you can edit the currently selected application.&#x20;

{% @arcade/embed flowId="Dey3vpb412bR3VnzxMjN" url="<https://app.arcade.software/share/Dey3vpb412bR3VnzxMjN>" %}

## Builder interface

Depending on whether you selected a specific template or started from scratch, your working area can be either blank or already include some UI elements.

{% hint style="info" %}
UI Bakery app is **live** in Edit mode. That means that you can edit your app and test it right away.
{% endhint %}

Let's have a look at the **Builder interface**:point\_down:

<figure><img src="/files/1uV3iIzwzGycrhmfLGX1" alt=""><figcaption></figcaption></figure>

<details>

<summary>1. Header</summary>

The header contains:

* **UI Bakery icon** - access App settings, Release history, or go back to the workspace menu
* **Connect Git** (available in the *Enterprise* plan)
* **Undo/Redo** buttons
* **Screen adaptive controls**
* Time stamp of the **last saved changes**
* **Preview** app link - click to see how your app looks without leaving the Builder&#x20;
* **Release** button
* **Share** button - invite users to your app, manage their access, and make the app public

</details>

<details>

<summary>2. Left side panel</summary>

**a. Components** - displays a list of all UI components that you can drag and drop to the working area. The *Library* subtab here, displays all your created modules and custom components.

**b. App structure** - shows the current page elements structure.

**c. App state** - shows existing state variables (with the option to manage them), components, actions, and other state variables.

**d. Pages** - contains a list of your app pages, and allows you to create or update any page.

**e. Theme** - custom theme builder.

**f.** [**Database Editor**](/extras/ui-bakery-postgres/database-editor) - here you can manage all your hosted databases.

**g. Data sources** - here you can connect your databases and APIs.

</details>

{% hint style="success" %}
The left side panel is *resizable* - you can adjust its size to suit your workflow and build more comfortably.
{% endhint %}

<figure><img src="/files/1ykW9dfwgfJ7US0UlfwC" alt=""><figcaption></figcaption></figure>

<details>

<summary>3. Working area</summary>

It's the part of the interface where you can assemble your application page from UI components.

</details>

<details>

<summary>4. Right side panel</summary>

It shows various settings and triggers of a UI component, if selected, or page settings.

</details>

<details>

<summary>5. Footer</summary>

**g. Additional info** - links to our docs, community, booking a demo, and more.

**h. Actions** - all app actions.

**i. Logs -** the current app logs.

**j. Git -** to activate Git.

**k. Custom code** - to connect custom scripts, libraries, and styles.

**l. Settings** - to set up some additional app interface settings.

**m. Search** - to search for components, actions, data sources, settings.

**n. AI Assistant** - to access our AI-based chatbot that can quickly help you with technical questions and code generation.

</details>

{% hint style="info" %}
Changes made in the UI Bakery app are saved automatically.
{% endhint %}


# App environments

In UI Bakery, multiple application environments are available:

* **Dev** - default environment where you build your app, write code, add components, run actions, etc. It's not accessible to end-users and therefore nothing you do here affects what users currently see. It's where you can also do some preliminary testing to make sure everything works locally before moving on to the next environment.
* **Staging** - similar to the production environment. Here, you can do the last checks and polish things up. It's the environment used for the testing process during which you can find and fix any issues that come up.
* **Prod** - this is the environment where you make your app live. It's where end users can see how your app works after all the updates and testing. Sometimes you may skip the Staging environment altogether and deploy directly to Prod, although it's not something we'd recommend doing regularly.

From your workspace page, in the upper right corner, you can switch between these environments to see what your app looks like and how it works. The only thing you need to do before that is to make sure your app is first [**deployed**](/build-from-scratch/getting-started/deploy-your-application-and-invite-users#deploying-an-app) to the environment you need.

{% @arcade/embed flowId="qiR31SyVi79s9bby06ea" url="<https://app.arcade.software/share/qiR31SyVi79s9bby06ea>" %}

UI Bakery also supports multi-instance deployment and synchronization. Learn more about it here:point\_down:

{% content-ref url="/pages/CFRPg2AoMP58z8FwApmK" %}
[Manage multi-instance deployment](/on-premise/git-source-control/managing-multi-instance-deployment)
{% endcontent-ref %}


# Release management

UI Bakery deployment process is pretty straightforward - you just need to click the **Release** button, specify the settings and deploy the app. Here, we'll explore how you can manage your app releases, namely creating *draft releases* and *restoring* a specific release *version*, if needed.

## Creating a draft release

While building your app, you can create draft releases. Draft releases allow you to create versions on the *Dev* environment before introducing some major changes to your application, such as switching data sources or introducing new UI.

The draft release flow is just like the regular deployment flow - simply clear both the *Staging* and *Prod* checkboxes. You will notice the Publish release button changing to **Draft release**.&#x20;

Draft releases are also available in the [**Release history**](#restoring-a-release-version) and can be restored as well.

<figure><img src="/files/5nxwKln11rlbS0m9TMdI" alt=""><figcaption></figcaption></figure>

## Restoring a release version

You can access the **Release history** to view the history of all your app releases. It includes both Stage and Prod releases, as well as Draft releases. From there, you can restore any release if you need to revert back to the previous production version.

### To restore a release version:

1. Click on the **Dashboard** icon in the upper left corner and select **Release history**.
2. In the window that opens, click **Restore** next to the version you want to roll back to.
3. Next, confirm your changes.

That's it! The Restore operation will be also added to the Release history.

{% @arcade/embed flowId="VYl5bq3lnykFnWeOA4jP" url="<https://app.arcade.software/share/VYl5bq3lnykFnWeOA4jP>" %}


# Audit logs

As your team grows and more teammates start using your applications on a daily basis, it's important to keep track of changes made or errors received to help you troubleshoot. That's when audit logs come in handy.

{% hint style="success" %}
Audit logs are available to users with an **Admin** role.
{% endhint %}

You can access them by clicking on your workspace name and selecting **Audit logs** in the menu. Audit logs can be filtered by a certain time period, environment, app, or user. You can also select a specific **log level**:

* Log
* Warn
* Error

If you need to load the latest logs, you can just click the *Refresh logs* button. You don't have to reload the page or set the filters.

The following events are tracked in the Audit logs:

{% tabs %}
{% tab title="User" %}

* Log in
* Sign up
* Log out
* Entered wrong password
* Entered wrong MFA
* Requested password reset
* Completed password reset
* Roles assigned
* Invited
* Removed
* Permission denied
  {% endtab %}

{% tab title="Role" %}

* Created
* Updated
* Removed
* (System) updated
  {% endtab %}

{% tab title="App" %}

* Create
* Open (failed attempts included)
* Deploy
* Publicity changed
* Page view
* Delete
  {% endtab %}

{% tab title="Datasource" %}

* Connect
* Update
* Delete
* Request
  {% endtab %}

{% tab title="Builder" %}

* Open
* Exit
* Version overwritten
* History snapshot restored
  {% endtab %}

{% tab title="Action" %}

* Success
* Error
* Request performance metrics
  {% endtab %}

{% tab title="Git" %}

* Connect
* Import
* Disconnect
* Default branch updated
* SSH keys generated
* Branches synced
* Branch created
* Branch deleted
* Commit
* Pull
* Force pull
  {% endtab %}
  {% endtabs %}

{% tabs %}
{% tab title="Automation" %}

* Error
* Success
* Created
* Updated
* Deleted
  {% endtab %}

{% tab title="Database" %}

* Create table
* Delete table
* Duplicate table
* Update table
  {% endtab %}

{% tab title="Admin" %}

* Viewed audit logs
* Password reset link generated
* Password reset requested
* MFA reset
* Reset failed login attempts
* MFA settings updated
  {% endtab %}
  {% endtabs %}

{% hint style="success" %}
In the **Enterprise** version, you have the ability to log request payloads. This feature enables you to track the data sent to databases and APIs. To activate this feature, set the `UI_BAKERY_AUDIT_LOGS_LOG_PAYLOAD` variable to `true`.
{% endhint %}

{% @arcade/embed flowId="x88jMjS8k8hpFLczbfjY" url="<https://app.arcade.software/share/x88jMjS8k8hpFLczbfjY>" %}


# Multi-factor authentication

Multi-factor authentication (MFA) enhances security for your workspace. UI Bakery uses the TOTP (Time-Based One-Time Passwords) 2FA algorithm.

Admins can enable MFA in the **Workspace settings**. Once it is enabled for your workspace, each user will be prompted to set up MFA at their next login. *New* users will be required to set up MFA during the registration process.

<figure><img src="/files/3CooLfXj7vFgwA4I0q0G" alt=""><figcaption></figcaption></figure>

To configure MFA, users need to scan the *QR code* using their authenticator app, such as Google Authenticator or Microsoft Authenticator, and enter the code generated by the app. Users will be prompted to enter the code at every login until they choose to remember the device.

{% hint style="info" %}
If you can't scan the QR code, you can try the *Copy setup key* option.
{% endhint %}

## Resetting MFA

Workspace admins can also reset MFA for users who have lost access to their authenticator app. This option is located in **Users & Permissions** - you just need to click on the three dots next to the specific user. Following the reset, users will be able to set up MFA again.

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

{% hint style="info" %}
With UI Bakery **on-premise**, you have the flexibility of modifying [MFA settings](/on-premise/environment-variables#multi-factor-authentication), including the service name, algorithm type, and the duration for which the code remains valid.
{% endhint %}


# Export & import an app

With UI Bakery, you can export any of your applications as separate ZIP files and then move them to another UI Bakery account, if needed. This gives you more flexibility with your product and allows you to save and move your data without having to recreate apps across different workspaces. Let's dive into how you can do that.

{% hint style="warning" %}
Apps created with UI Bakery are not supposed to be downloaded. The exported file is only the **data model** of your app, not the code.
{% endhint %}

## Exporting an application

Exporting an app is easy - click on the three dots next to the app's name to access its settings and select the **Export** option.

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

That's it! The app will be downloaded as a ZIP archive, and you can use it to move your app to a different workspace.

## Importing an application

Before proceeding to importing an app, check out [this article](/on-premise/git-source-control/managing-multi-instance-deployment#data-sources-consistency-across-instances) if you're planning on importing your app into another instance.

### Importing app from archive

1. In the target workspace's menu, in the *Apps* section, click the **+ button** and select **Import archive**.
2. Upload a ZIP archive of the app exported from UI Bakery.
3. Next, click **Import app**.

<figure><img src="/files/2nVPBiZzktHixBWVSRNY" alt=""><figcaption></figcaption></figure>

The application will be imported to your workspace and you can access it right away.&#x20;

### Importing a linked GitHub repository

If you want to browse through more templates than are available on the [website](https://uibakery.io/templates), you can also check our [repository](https://github.com/uibakery-templates) and use a template from it on your instance.&#x20;

#### To import a repo from UI Bakery:

1. Fork your own copy of the repository.

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

2. Download the copy as a ZIP archive.
3. In the target workspace's menu, in the *Apps* section, click **+ > Import archive**.
4. Upload the ZIP archive of the repo.
5. Click **Import app**.\
   The app will open in the *Edit* mode.
6. Next, connect your app to Git following this [instruction](/concepts/source-control/getting-started#connect-an-application-to-a-git-repository).

That's it! Tailor the app to your specific requirements using UI Bakery's visual interface and deploy your application with ease.

#### To import your own repo:

1. In the target workspace's menu, in the *Apps* section, click the **+ button** and select **Import from Git**.
2. Enter the *SSH URL* for your Git repository.
3. Copy and add the *SSH key* that will appear to your Git repository.
4. Next, click **Import app**.

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

Done! The app will open in the *Edit* mode and it will be *connected to Git*.

***

Sometimes, while importing an app, you may get the following error:

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

It means that your UI Bakery version and app version differ causing a mismatch. To avoid issues like this, you should [update](/on-premise/install-and-update/updating-on-premise-version#update-to-the-latest-version) your UI Bakery instance.


# Mobile layout

In UI Bakery, you can switch between a **desktop** and **mobile** layout for your applications:

* **Desktop**: 100% of the available page space
* **Mobile**: 400 px

The desktop layout is applied by default. To switch to the *mobile* layout, you need to change the adaptive control in the header to the mobile option.

{% hint style="info" %}
Please note that altering the layout of an application changes the layout of all its pages.
{% endhint %}

{% @arcade/embed flowId="TCd7UlGrS6z6YoYxzTI5" url="<https://app.arcade.software/share/TCd7UlGrS6z6YoYxzTI5>" %}

## Configuring component visibility for different layouts

Components can be configured to be visible only on a specific layout. For example, you may not want to include a large table on mobile view.&#x20;

In such cases, you need to select the component, navigate to the **Responsive** setting, and select the layout you want the component to be visible on.

{% @arcade/embed flowId="UrpJELE15n2U5t9BTjU9" url="<https://app.arcade.software/share/UrpJELE15n2U5t9BTjU9>" %}

{% hint style="warning" %}
If you make changes to components on the **desktop** layout, they will not be applied on **mobile**, and vice versa.\
Also, keep in mind that components added to a certain layout do not have the same position on the other layout - they are displayed at the bottom of the page.
{% endhint %}


# Theme editor

Theme editor allows you to create customized themes across your applications that correspond to your company's corporate brandbook. Theme editor is available for workspace *Admins* and *Editors*.

You can use *pre-built UI Bakery themes (**Light/Light 2.0**)* or create your own and use it in your applications.

## Creating a new theme

To create a new theme, follow the steps below:

1. In the Builder mode, navigate to the **Theme** tab in the left side panel and click **+** **Create new theme**.
2. Give it a meaningful name and click **Create**.

<figure><img src="/files/3TdM895GnyNfmCxzBMHA" alt=""><figcaption></figcaption></figure>

Once created, the new theme will be assigned to your current application by default. You can also assign any of pre-built UI Bakery themes to the app if you want - simply select it in the *Themes* list.

## Customizing a theme

The themes you create will be added to the **Custom** themes section. From there, you can edit them according to your needs.&#x20;

### To customize a theme:

1. Click the *pencil* icon for the theme you want to edit.\
   You'll see the Editor open in the right side panel.
2. In the **Editor**, you can customize the following sections:
   1. **Main colors** - the colors of the app \
      The *primary* color represents the main color of your brand and serves as the dominant color in your application. *Support* colors play a secondary role in app's design.
   2. **Canvas** - the main color of the app's background
   3. **Containers** - the look of the containers used in the app, such as background color, radius, border, and shadow
   4. **Components** - the look of the inputs, selects, and buttons across the application
   5. **Text** - text colors
   6. **Web font** - theme font

Any changes you make to the theme are applied and saved automatically. If you need to make corrections to the theme later, you can always edit it again.

{% hint style="danger" %}
Please note that when you are making changes to a theme, they're applied across all the environments. So if you are still working on a theme, we recommend doing that in a test application rather than a production one.
{% endhint %}

{% @arcade/embed flowId="f6uejG4qKNiNufGPLiFt" url="<https://app.arcade.software/share/f6uejG4qKNiNufGPLiFt>" %}

### Use case: Changing theme font

Here we'll show you how you can customize your theme font using [Google Fonts](https://fonts.google.com/). Check out the instruction below:

1. Navigate to the Google Fonts website, find a font you like, and click on it.
2. Next, click on the **Get font** button in the upper right corner and click on **Get embed code**.&#x20;
3. Copy only the following *link* to the font, for example:\
   \
   <https://fonts.googleapis.com/css2?family=Nunito+Sans:ital,opsz,wght@0,6..12,200..1000;1,6..12,200..1000\\&display=swap>" rel="stylesheet"
4. Specify the font's actual *name* (e.g.: Nunito Sans) and set another font as its *fallback*.
5. (Optional) In the **Licence** field, specify the licence to your font if it's under a custom licence.

{% @arcade/embed flowId="yxnjsJRci1rUJFXD0emV" url="<https://app.arcade.software/share/yxnjsJRci1rUJFXD0emV>" %}

## Making custom theme default

The **default** theme is the theme that is used across your UI Bakery workspace by default, meaning that this theme will be applied to all **new** apps automatically. If you don't have any custom themes, the *pre-built UI Bakery* theme you select will be used as the default one.

If you want to make your custom theme default, you simply need to click on the three dots next to the theme's name and select **Make default.**

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

You can also select any theme for each application separately if necessary.


# Changing theme from the app

With UI Bakery, you can build an app in which users can change themes right from it, for example switch between *Light* and *Dark* themes. \
In this article, we'll explore how you can make a theme switcher with a **Select** component and an **Icon** component. Let's dive in!

## Theme switcher with the Select component

To implement it, follow the steps below:

1. First, create a new theme for the **Dark** mode.

{% hint style="success" %}
Go back to [this article](/concepts/theme-editor#creating-a-new-theme) if you need a reminder how to create and customize a theme.
{% endhint %}

2. Next, add a **Select** component to the working area and specify the following *settings* in the right side panel:
   1. For the **Options** field - `{{app.themes}}`
   2. For the **Option title** - select *name*
   3. For the **Option value** - select *id*
3. Create a *changeTheme* action that will consist of **two steps**:
   1. **1st step** - *JavaScript Code* action with the `{{app.setTheme(data)}} return {{data}};` code
   2. **2nd step** - *Save to Local Storage* action with the *userTheme* variable (set its value as `{{data}}`)
4. Assign the *changeTheme* action to the Select component's **On Change** trigger.
5. In the Select component's **Value** field, specify `{{localStorage.userTheme || 'DEFAULT_THEME'}}` , where `'DEFAULT_THEME'` is the id of the *Light* theme (pre-built).
6. Create another action (we'll call it *initTheme*) of the **Execute Action** type that will trigger the *changeTheme* event with the parameters from step 5:\
   `return {{localStorage.userTheme}} || 'DEFAULT_THEME'`
7. Finally, assign the *initTheme* action to the application's **On Page Load** trigger.

Voilà! Now your users will be able to change the theme directly from the app.

{% @arcade/embed flowId="oMQfBHXKt6PNf3r3qrYO" url="<https://app.arcade.software/share/oMQfBHXKt6PNf3r3qrYO>" %}

## Theme switcher with the Icon component

To implement it, follow the steps below:

1. First, create a new theme for the **Dark** mode.

{% hint style="success" %}
Go back to [this article](/concepts/theme-editor#creating-a-new-theme) if you need a reminder how to create and customize a theme.
{% endhint %}

2. Next, add two **Icon** components to the working area and style them to represent *Light* and *Dark* modes (for example, the sun and the moon).
3. For the **On Click** trigger of the icon that corresponds to the Light theme, create a new *switchTheme* action that will consist of **two steps**:
   1. **1st step** - *Save to Local Storage* action with the *theme* variable (set its value as `{{data}}`)
   2. **2nd step** - *JavaScript Code* action with the `{{app.setTheme(data)}};` code
4. Next, let's assign the *Light* theme to the corresponding icon - click the icon, navigate to the **Triggers** section, and open the **Action Arguments** field.
5. Open the **App state** tab in the left side panel, and find the themes' variable under the **app** tab.
6. Copy the **id** of the *Light* theme (for example, *'DEFAULT THEME*') and paste it in the **Action Arguments** field.&#x20;

{% @arcade/embed flowId="crmFZ08O9ll1mQsv7VVq" url="<https://app.arcade.software/share/crmFZ08O9ll1mQsv7VVq>" %}

7. Now, repeat the same for the moon icon corresponding to the *Dark* theme - assign the *switchTheme* action to its **On Click** trigger, and paste the **id** of the Dark theme (for example, *'zHYzWbg3HH'*) into its **Action Arguments** field.
8. Now, for the app's **On App Load** trigger, create a new *loadTheme* action of the **JavaScript Code** type and specify the following code: \
   `{{app.setTheme(localStorage.theme || 'DEFAULT_THEME')}};`, where `'DEFAULT_THEME'` is the id of the *Light* theme (pre-built).
9. (Optional) Configure the **color** of the icon to be changed when used:
   1. Click the **sun** icon representing the *Light* theme, switch the **Color** field into **JS mode,** and specify the following code: \
      `{{localStorage.theme == 'DEFAULT_THEME' ? 'warning': 'white'}}`, where `DEFAULT_THEME` is the id of the Light theme.
   2. Click the **moon** icon representing the *Dark* theme, switch the **Color** field into **JS mode**, and specify the following code: \
      `{{localStorage.theme !== 'DEFAULT_THEME' ? 'warning': 'black'}}`

That's it! Now users will be able to click on the icons to switch between *Light* and *Dark* modes, and the colors of the icons will also change.

{% @arcade/embed flowId="Wx0F0YZHH7ffoUAQG4Pe" url="<https://app.arcade.software/share/Wx0F0YZHH7ffoUAQG4Pe>" %}


# UI Bakery source control

{% hint style="info" %}
Contact our support team about the possibility of enabling Git on your plan.
{% endhint %}

**UI Bakery source control** is a *Git-based* version control system that allows you to manage your app's changes and collaborate with other developers. UI Bakery operates on the Git-API level, which means you can use any Git provider, such as GitHub, GitLab, BitBucket, etc.

**Main features:**

* Parallel development of a single application by multiple developers;
* Multi-instance support - multiple instances of UI Bakery connected to the same Git repository;
* Branch protection - you can protect branches from being changed directly in UI Bakery;
* Consistent development process - maintain the development process that is familiar to your team, including testing, code reviews, and deployment.

## Single instance process walkthrough

A newly created UI Bakery app or an app that is already developed can be connected to a Git repository.

{% stepper %}
{% step %}

### Connecting app to Git

1. Create a new app or open an existing one.
2. Navigate to your Git provider and create a **new** repository. \
   The repository MUST be empty.
3. In UI Bakery, click the **Connect Git** button in the top left corner.
4. Copy the *SSH repository URL* (for example, `git@github.com:user/fictional-octo-happiness.git`) and paste it to the **Git repository URL** field.
5. Next, copy the *SSH key* suggested by UI Bakery and create a new key in the **Deploy keys** settings of your Git provider. \
   For GitHub, follow the steps below:
   1. Open your repository settings and open the **Deploy keys** tab.
   2. Here, click **Add deploy key** and paste the SSH key you've copied before.
   3. Select the **Allow write** **access** checkbox and then click **Add key**.
6. In UI Bakery, click **Push to Git &** **Connect** and wait for the app to be pushed to the Git repository.

Once you connect an app to a Git repository, a new branch is created in the repository. This branch is called **main**. Main branch is protected from being changed directly in UI Bakery.
{% endstep %}

{% step %}

### Making changes to the app

1\. Go to the **main** branch and pull the latest changes.

2\. Create a **new** branch from the main branch.

3\. Make changes to the app.

4\. Commit and push the changes to the Git repository.

5\. Create a pull request to merge the changes to the **main** branch.

6\. Once the PR is approved and merged, pull the changes to the **main** UI Bakery instance.

Once the changes are pulled to the UI Bakery instance, you can review and deploy them using the standard UI Bakery workflow.
{% endstep %}

{% step %}

### Merging conflicts

UI Bakery app is split into multiple files and folders, which makes it easy to avoid merge conflicts. However, if you stumble upon a merge conflict while merging a PR, this means that the **main** branch was changed while you were working on your **feature** branch.

To resolve the conflict, you need to pull the latest changes from the main branch to your feature branch and resolve the conflicts manually using Git provider UI.

#### Merge conflicts involving UI Bakery updates

Sometimes you may encounter issues while working on different branches *before* and *after* updating your UI Bakery version. Below are some suggestions how you can possibly avoid such merge conflicts:

* Try planning your UI Bakery app updates for when you have only a few/no big features in development.
* Arranging actions into folders and utilizing pages for components can also help avoid any possible conflicts.
* After updating UI Bakery, **follow this flow** to make sure the branches created immediately after the update don't contain any unnecessary changes:
  * Update your UI Bakery app
  * Open the project you need and create a *new* branch
  * Do not make any changes in this branch
  * Next, commit, push, and merge this branch to the *main* branch
  * Once merged, pull the changes to the **main UI Bakery instance**
    {% endstep %}

{% step %}

### Restoring the previous version

When Git is connected to your app, it's no longer possible to restore versions from UI Bakery's [release history](/concepts/workspace-management/app-environments/release-management#restoring-a-release-version).\
If you need to revert to the previous version, you need to synchronize your changes with the branch and pull it back to UI Bakery using the following steps:

1. **Git:**\
   — `git revert #{commit_hash}`\
   —  `git push`
2. **UI Bakery:**\
   — Pull branch changes from Git\
   — Release your app

{% hint style="success" %}
It should be noted that Git commits are *separate* from app releases. If, for example, you delete the branch from which the latest app version was released, your app will stay as it was.
{% endhint %}
{% endstep %}
{% endstepper %}

## Sources overview

A typical UI Bakery application is broken down into *files system structure* upon pushing to the Git repository.&#x20;

The following app sources **are stored** in the Git repository:

* App settings
* App pages and components
* App actions with folders structure

The following is **NOT stored** in Git (is a part of the UI Bakery instance):

* Instance data sources
* Deployment history
* Environment variables
* Audit logs


# Git controls overview

Take a quick look at the Git controls in UI Bakery :point\_down:

<figure><img src="/files/3jbHgIP1ImhV65mioDiN" alt=""><figcaption></figcaption></figure>

1. **Branches selector** - switch between branches
2. **Sync branches** - sync the list of branches with UI Bakery
3. **Pull/Force pull** - pull the latest changes from the selected branch/force pull current branch changes to UI Bakery

{% hint style="warning" %}
*Force pull* action overwrites your local uncommitted changes with the current remote branch. It can be helpful in situations where automatic project model updates or conflicting Git changes prevent a standard pull or revert.
{% endhint %}

4. **Commit & Push** - commit and push the changes to the selected branch
5. **Open Pull Request** - create a pull request for the selected branch

## Changing the default branch

Some teams may be working on the same project and it may not be convenient to share the same `main` branch. In such cases, you can set a specific branch as the **default** one for your team only. \
\
You can do this by opening the *Git* tab in the footer panel and clicking the **Set current branch as default** button. Once set, the system will operate in the following way:

* The project will automatically load from this branch.
* It will become the base branch for all new branches.
* It will be used as the default target for `pull` requests.
* It will act the source branch when duplicating projects.
* If Git is disconnected, the state will revert to the default branch's state.
* The default branch cannot be deleted from the database (ID-based).

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


# Migrating your app model to the latest version

To continuously enhance functionality and improve user experience, new versions of UI Bakery may include app model migrations. These migrations modify the app model stored in your Git repositories. Therefore, a manual app model migration is necessary to ensure smooth development process and to prevent developers from encountering merge conflicts.

{% hint style="warning" %}
App model migration is **not strictly required but highly recommended**. If the migration is not performed immediately following an update, it will be done separately in each branch automatically.

Therefore, all developers will see similar changes in their pull requests until the migration changes are merged into the main branch and subsequently into their individual branches.
{% endhint %}

## To migrate your app model:

1. Start by creating a **new** branch named `migrate-release/0.0.0` from the `main` branch.

   The newly created branch will be automatically migrated by UI Bakery.
2. Commit the changes to the **migration** branch.
3. Create a pull request from the `migrate-release/0.0.0` branch to the `main` branch.
4. Merge the pull request into the `main` branch.
5. Switch back to the `main` branch in UI Bakery and pull the latest changes.
6. Using Git, merge the updated `main` branch into any branches under active development.
7. Go to these development branches and pull the changes.

By following this process, you will ensure that your app model is consistently up-to-date, minimizing the risk of conflicts and maintaining smooth development workflow.


# File management

Learn about operations with files and their management in UI Bakery app.

<table data-view="cards"><thead><tr><th data-type="content-ref"></th></tr></thead><tbody><tr><td><a href="/pages/pExESguAANLPHBxsJ1Vl">/pages/pExESguAANLPHBxsJ1Vl</a></td></tr><tr><td><a href="/pages/sL25xHL2ZzJJROOhFgfS">/pages/sL25xHL2ZzJJROOhFgfS</a></td></tr><tr><td><a href="/pages/T1CXVqhtGxDPDd0huM6N">/pages/T1CXVqhtGxDPDd0huM6N</a></td></tr><tr><td><a href="/pages/E5dCtVaizk9HHRCMC1KS">/pages/E5dCtVaizk9HHRCMC1KS</a></td></tr><tr><td><a href="/pages/GzPMWwdQbQ1O6PQlqs1s">/pages/GzPMWwdQbQ1O6PQlqs1s</a></td></tr></tbody></table>


# Working with PDF files

## Generating PDF files

UI Bakery allows you to generate PDF files from your Tables for reporting and other purposes. Let's review how you can do that.

### To generate PDF:

1. Navigate to the **Custom code** tab and specify the following script:

```javascript
<script src="https://unpkg.com/jspdf@2.5.1/dist/jspdf.umd.min.js"></script>
<script src="https://unpkg.com/jspdf-autotable@3.5.25/dist/jspdf.plugin.autotable.js"></script>
```

2. Load the data you need and add a table to display it. (As an example, we'll use the table with *user* data.)
3. Create a new action of the **JavaScript Code** type and add the following code:

```javascript
const doc = new jspdf.jsPDF();

doc.autoTable({
  head: [['ID', 'Name', 'Email', 'Bio']],
  body: {{ui.table.value}}.map(({ id, fullName, email, bio }) => ([id, fullName, email, bio])),
});

doc.save('users.pdf');
```

{% hint style="warning" %}
Please note that this code is **exemplary** and needs to be changed based on your table and necessary file structure.
{% endhint %}

4. Next, add the **Button** component to your working area that will download the generated file.
5. Assign your *JavaScript Code action* to the Button's component **On Click** trigger.

That's it! Now you will be able to download your Table data in the PDF format.

{% @arcade/embed flowId="ZSYMGhAX5CVGfGzmKCYV" url="<https://app.arcade.software/share/ZSYMGhAX5CVGfGzmKCYV>" %}

### Downloading PDF with a JS code action

You can also use the following code in your **JavaScript Code** action to download a PDF file:

```javascript
// Result of the previous step is available as data

const pdf = {{data}};

const pdfstr = await fetch(`data:application/pdf;base64,${pdf}`);
const blobFromFetch = await pdfstr.blob();
const blob = new Blob([blobFromFetch], { type: 'application/pdf' });

const url = URL.createObjectURL(blob);

// Create a temporary link element and trigger the download
const downloadLink = document.createElement('a');
downloadLink.href = url;
downloadLink.download = 'yourfile.pdf'; // Name your file here
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
```

After running the action, the file will be downloaded automatically to your default *Downloads* folder.

## Printing PDF files

Now that you've generated your PDF file, you can also print it directly from the app. Let's review how you can do that.

### To print PDF:

1. Add the **File picker** component to your working area.
2. Next, connect the [Print.js library](https://printjs.crabbly.com/#documentation) in the **Custom Code** tab using the following script:

```javascript
 <script src="https://printjs-4de6.kxcdn.com/print.min.js"></script>
```

3. Create a **JavaScript Code** action and specify the following code:

```javascript
function getBase64(file) {
  return new Promise((resolve, reject) =>{
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () => {
     resolve(reader.result);
    };
    reader.onerror = (error) => {
     reject(error);
    };
  })
}
	
const base64 = await getBase64({{ui.filepicker.value}})
	
printJS({
  printable: base64.replace('data:application/pdf;base64,', ''),
  type: 'pdf',
  base64: true
});
```

4. Now, select the file you want to print in the *File picker* component and execute the action from step 3.

A *Print window* will appear where you can choose your printing options and proceed to printing the file.

{% @arcade/embed flowId="nR2aHD3kqW04L96sWPfe" url="<https://app.arcade.software/share/nR2aHD3kqW04L96sWPfe>" %}


# CSV import & export

## Importing a CSV file

UI Bakery allows importing CSV files to your app and, for example, displaying them in a Table. Let's review how you can do that.

### To display a CSV file in a Table:

1. Add the **File picker** component to the working area.

{% hint style="info" %}
*File picker* component has a special property – **parsedValue**. It allows you to access the CSV file content as an array of objects without uploading it to the server or parsing it separately.
{% endhint %}

2. In component's settings, select the **Parse file** checkbox to allow the system to access file content.
3. Next, select the file you want to upload in the File picker.
4. Now, add the **Table** component and reference File picker using `{{ui.filepicker.parsedValue}}`.
5. Click **Generate structure**.

Done! Your CSV file content will now be displayed in the Table.&#x20;

{% @arcade/embed flowId="6TJcsAP3fMp6hBQc8Gxs" url="<https://app.arcade.software/share/6TJcsAP3fMp6hBQc8Gxs>" %}

## Exporting a CSV file

Along with importing data, you can also as easily export it from your app in a CSV format. In this section, we'll review two ways how you can do that - using a built-in export button (for the *Table* component) and using an action to generate a CSV file.

### Downloading Table data as CSV

Table component in UI Bakery has a **built-in button** to directly download data in a CSV format. You simply need to click it and your exported file will be automatically downloaded.

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

### Generating CSV file from action

In this case, you can use an action consisting of two steps, where the first step is an API or database request. Follow the steps below to see the whole flow:

1. Create a new action that will consist of two steps:
   1. **First step** - *HTTP Request* action where you need to specify your URL (it should return an array of objects).
   2. **Second step** - *Generate File* action where you need to reference the result of the previous step as `{{data}}` variable in the **Generate from** field.
2. Click **Execute action**.

Your exported file will be automatically downloaded to your default *Downloads* folder.

{% @arcade/embed flowId="fiQRKUAtoPIPhMBOQ9DG" url="<https://app.arcade.software/share/fiQRKUAtoPIPhMBOQ9DG>" %}

### Exporting data in different formats

UI Bakery also allows exporting data to other formats, not only to CSV. You simply need to use a **JavaScript Code** action step to achieve this. Check out the code snippets below:point\_down:

* To export data to **.txt:**

```javascript
// Result of the previous step is available as data
const blob = new Blob([{{data}}], { type: 'plain/text' });
const url = URL.createObjectURL(blob);

// Create a temporary link element and trigger the download
const downloadLink = document.createElement('a');
downloadLink.href = url;
downloadLink.download = 'data.txt'; // Name your file here
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);

return blob;
```

* To generate **JSON file** from previous data:

```javascript
// Result of the previous step is available as data
const jsonData = JSON.stringify({{data}});
const blob = new Blob([jsonData], { type: 'application/json' });
const url = URL.createObjectURL(blob);

// Create a temporary link element and trigger the download
const downloadLink = document.createElement('a');
downloadLink.href = url;
downloadLink.download = 'data.json'; // Name your file here
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
```

* For **multipart** requests:

```javascript
const fileName = "your_file_name.extension"; // Replace with your desired file name and extension
const fileContent = {{data}}; // Assuming the file content is received in the 'data' variable

const blob = new Blob([fileContent], { type: "application/octet-stream" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);

return { message: "File download triggered" };
```


# Uploading files using methods

In this article, we'll explore ways of uploading files in UI Bakery using the following methods:

* [Form Data](#uploading-files-via-the-http-action-and-form-data-method)
* [Binary](#uploading-files-via-the-http-action-and-binary-method)
* [Fetch](#uploading-files-via-the-fetch-method)

## Uploading files via the HTTP Action and Form Data method

1. Add the **File picker** component to the working area.
2. Create a new **HTTP Request** action:
   1. Set the target *URL* to the server where you want to upload the file
   2. Set the request method to *POST*
   3. In the *Body*, select **Form Data**, add a new key (the parameter name is typically *file*), and set the parameter type to **file**
   4. Here, also specify the file by referencing the File picker component:`{{ui.filepicker.value}}`&#x20;
3. In your component, select the file you want to upload, and execute the action.

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

## Uploading files via the HTTP Action and Binary method

{% hint style="warning" %}
Before you proceed to the instruction below, ensure your server is configured to **accept** the **binary format** for file uploads.
{% endhint %}

1. Add the **File picker** component to the working area.
2. Create a new **HTTP Request** action:

   1. Set the target *URL* to the server where you want to upload the file
   2. Set the request method to *POST*
   3. In the *Body*, select **Binary**, and provide the object in the following format:

   `{ data: File | Blob | any, filename?: string }`\
   \
   For example, our object will look like this:\
   `{ data: {{ui.filepicker.value}}, filename: 'custom_name.txt' }`
3. In your component, select the file you want to upload, and execute the action.

{% hint style="info" %}
When using *Blob* or *File* content with the **Binary** option, the data will be sent as is. However, for any other content, the system will first attempt to **JSON.stringify** the data - if successful, the resulting string will be sent; if unsuccessful, it will be converted **.toString** before sending.
{% endhint %}

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

### Uploading using a Blob object with binary content

Alternatively, you can create a Blob object with binary content without using File components, and then send it to the server.&#x20;

To achieve this, follow the steps below:

1. Create a **new action**:
   1. For the first step, add the **JavaScript Code** action step and specify the following code:<br>

      ```javascript
      const content = 'Hello, world!';
      const mimeType = 'text/plain';
      return new Blob([content], { type: mimeType });
      ```
   2. For the second step, add the **HTTP Request** action step (*POST, Body - Binary*) to reference the result of the previous step:

```javascript
{ date: {{data}}, filename: 'hello_world.txt' }
```

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

## Uploading files via the Fetch method

1. Add the **File picker** component to the working area.
2. Create a **JavaScript Code** action and specify the following code:

```javascript
const file = {{ui.fileInput.value[0]}};
const formData = new FormData();
formData.append('upload', file);

fetch('url', { method: "POST", body: formData })
```

where `'url'` needs to be replaced with your target URL.

3. Assign the Code action to the **On Change** trigger of the *File picker* component.

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

4. Select the file you want to upload in the File picker.

The action will be executed once you select a file and it will be uploaded to the target URL. Alternatively, you can also use a **Button** component in this example and assign your action to the button's *On Click* trigger.


# Displaying files from Google Drive and Dropbox

UI Bakery allows you to display *images* and *files* from your cloud storage, but they need to be converted first. In this article, we'll show you how you can do that on the examples of Google Drive and Dropbox. Let's dive in :point\_down:

## Displaying images from Google Drive

To use images from Google Drive in your table, you need to convert the link to them following this pattern:

```
https://drive.google.com/thumbnail?id=FILE_ID
```

Let's review the step-by-step instruction here:

1. Click **Share** next to the image you want to use in your table.
2. In the window that opens, set the access to **Anyone with the link** and copy the link.\
   \
   The link will look like this:

[`https://drive.google.com/file/d/1VmwkJGfaT5smm-dOZ-jsDZ3lH9AGxtlQ/view?usp=sharing`](https://drive.google.com/file/d/1VmwkJGfaT5smm-dOZ-jsDZ3lH9AGxtlQ/view?usp=sharing)

3. Next, copy the **file id** from the obtained link and paste it in the required pattern to get the resulting link like this:

[`https://drive.google.com/thumbnail?id=1VmwkJGfaT5smm-dOZ-jsDZ3lH9AGxtlQ`](https://drive.google.com/thumbnail?id=1VmwkJGfaT5smm-dOZ-jsDZ3lH9AGxtlQ)

4. In the Table component, add the link from step 3 to the necessary field.
5. Now, change the field type from **Link** to **Image**.

{% hint style="info" %}
This is necessary to display the final image since a link is recognized by the system as a *Link field type*.
{% endhint %}

That's it! Your image should now be displayed in the Table.

{% @arcade/embed flowId="98CJeEirBVKPWLRaFRZT" url="<https://app.arcade.software/share/98CJeEirBVKPWLRaFRZT>" %}

Since the final image is a thumbnail with the default resolution of around *200 px*, you can also add `&sz=w###-h###` at the end of the link, replacing the hashtags with the **width** and **height** you need. So, your resulting link will look like this:

[`https://drive.google.com/thumbnail?id=1VmwkJGfaT5smm-dOZ-jsDZ3lH9AGxtlQ&sz=w1280-h994`](https://drive.google.com/thumbnail?id=1VmwkJGfaT5smm-dOZ-jsDZ3lH9AGxtlQ\&sz=w1280-h994)

## Displaying PDF files from Google Drive

To display PDF files from Google Drive, you need to convert the link to them following this pattern:

```
https://drive.google.com/uc?id=FILE_ID
```

Let's review the step-by-step instruction how to do that:

1. Click **Share** next to the file you want to use in your table.
2. In the window that opens, set the access to **Anyone with the link** and copy the link.\
   \
   The link will look like this:

[`https://drive.google.com/file/d/1F9NCSMf7MUMdT6aALelPtil3er4LYdub/view?usp=sharing`](https://drive.google.com/file/d/1F9NCSMf7MUMdT6aALelPtil3er4LYdub/view?usp=sharing)

3. Next, copy the **file id** from the obtained link and paste it in the required pattern to get the resulting link like this:

[`https://drive.google.com/uc?id=1F9NCSMf7MUMdT6aALelPtil3er4LYdub`](https://drive.google.com/uc?id=1F9NCSMf7MUMdT6aALelPtil3er4LYdub)

4. Now, in the app create a new action of the **HTTP request** type, select *GET* method, and specify the link from step 3 in the *URL* field.
5. Here in the action, turn on the **Transfrom result** toggle, modify action result with `return {{data.base64}}`, and execute the action.
6. Next, add the **PDF Viewer** component to your working area and assign the newly created action to it.

Done! Now you can display the PDF file in your application.

{% @arcade/embed flowId="h4OEtBtIGpLuRhHySqD3" url="<https://app.arcade.software/share/h4OEtBtIGpLuRhHySqD3>" %}

## Displaying PDF files from Dropbox

In order to display PDF files from Dropbox, you also need to modify their links. Check out the step-by-step instruction below:point\_down:

1. Click **Share** next to the file you want to display and copy its link.\
   The link will look like this:\
   \
   [`https://www.dropbox.com/scl/fi/2hhw9gk99dhp3vqrqqhpc/Sample.pdf?rlkey=icwx5vl2scyh2t9vq0lw1ztbi&st=9skj0sm0&dl=0`](https://www.dl.dropboxusercontent.com/scl/fi/2hhw9gk99dhp3vqrqqhpc/Sample.pdf?rlkey=icwx5vl2scyh2t9vq0lw1ztbi\&st=9skj0sm0\&dl=0)
2. Now, replace <mark style="color:blue;">dropbox.com</mark> with <mark style="color:blue;">dl.dropboxusercontent.com</mark> to get the resulting link like this:\
   \
   [`https://www.dl.dropboxusercontent.com/scl/fi/2hhw9gk99dhp3vqrqqhpc/Sample.pdf?rlkey=icwx5vl2scyh2t9vq0lw1ztbi&st=9skj0sm0&dl=0`](https://www.dl.dropboxusercontent.com/scl/fi/2hhw9gk99dhp3vqrqqhpc/Sample.pdf?rlkey=icwx5vl2scyh2t9vq0lw1ztbi\&st=9skj0sm0\&dl=0)
3. Add the **PDF Viewer** component to the working area and copy the resulting link in the component's **Link to PDF** field.

{% @arcade/embed flowId="FS9yU7A5gV1Ora0hzW1w" url="<https://app.arcade.software/share/FS9yU7A5gV1Ora0hzW1w>" %}


# Parsing and sending XML

In this article, we'll explore ways of parsing and sending XML in UI Bakery. Review the sections below to learn how to do that.

## Parsing an XML string to a JS Object

1. Navigate to the **Custom code** tab and specify the following script:

```javascript
<script src="https://cdnjs.cloudflare.com/ajax/libs/fast-xml-parser/4.3.2/fxparser.min.js" ></script>
```

2. Create an **HTTP Request** action selecting the *GET* method and specifying the necessary *URL*.
3. Next, turn on the **Transform result** toggle to open the mapper field and specify the following code:

```javascript
const xmlContent = atob({{data.base64}});
  
const parser = new XMLParser();
return parser.parse(xmlContent);
```

{% hint style="info" %}
Instead of the *Transform result*, you can add a **JavaScript Code** action as the second step to the HTTP Request action.
{% endhint %}

4. Click **Execute action**.

{% @arcade/embed flowId="qOai5eaBTlOBuvOzd5D1" url="<https://app.arcade.software/share/qOai5eaBTlOBuvOzd5D1>" %}

## Sending an XML string from UI Bakery

1. Navigate to the **Custom code** tab and specify the following scripts:

```javascript
<script src="https://cdnjs.cloudflare.com/ajax/libs/fast-xml-parser/4.3.2/fxparser.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jstoxml@3.2.10/dist/jstoxml.min.js"></script>
```

2. Now, create a new action consisting of *two steps*:
   1. For the **first** step, add a **JavaScript Code** action that will generate an XML from JSON:<br>

      ```javascript
      const jsonData = await {{actions.requestData.trigger()}};
      return jstoxml.toXML(jsonData);

      //where requestData is an action that returns data to be sent as XML (for example, HTTP Request or Load Table action)
      ```
   2. For the **second** step, add an **HTTP Request** action that will send the request:\
      \
      — Select *POST* method and specify the *URL*\
      — For **Headers** - specify `Content-Type` `application/xml` \
      — For **Body** - `{{data}}`, as the result of the first step
3. Click **Execute action**.

{% @arcade/embed flowId="lfYWhJMpsOns5iOZX3Zx" url="<https://app.arcade.software/share/lfYWhJMpsOns5iOZX3Zx>" %}


# Styling

Learn how to adjust the style and appearance of your components.

<table data-view="cards"><thead><tr><th data-type="content-ref"></th></tr></thead><tbody><tr><td><a href="/pages/Pq4MQXwpVa3fzL7wNBub">/pages/Pq4MQXwpVa3fzL7wNBub</a></td></tr></tbody></table>


# Modifying components with CSS

In UI Bakery, you can adjust the style of components using *custom CSS*. By default, a component's name (for example, *usersTable*) is added as a CSS class to the component class making it possible to modify it with CSS.

## Adding new CSS styles

To add new styles, you need to specify a new `<style>` tag with the desired styles in the **Custom code** section, for example:

```html
<style>
  .no-shadow nb-card {
    box-shadow: none;
  }
  
  .background-new nb-card-body {
    background: aquamarine;
  }
</style>
```

Once added, you need to apply these styles to a component in the **Styling** section of the component's settings.

### Dynamic CSS classes

You can also define custom classes using code to apply them dynamically. Here, two formats are available:

* an **array of strings** representing custom classes
* an **object** where keys are custom classes and values determine whether to apply a custom class

```javascript
// css classes as a list of strings

['class-name', {{ui.form.valid}} && 'valid'];

// or as an object
{
  'class-name': true,
  // will apply the 'valid' class only when form is valid
  'valid': {{ui.form.valid}}
}
```

Check out the use cases below showing in more details how you can modify components with custom CSS in UI Bakery:point\_down:

## Use case: Changing Table colors

You can also use custom CSS to customize component colors, say change colors in the Table. The flow is pretty much the same - navigate to the *Custom code* tab and add the necessary styles there.&#x20;

Below is an example of the style you can add to change Table colors:

```html
<style>
  ub-smart-table nb-card,
  ub-smart-table datatable-selection,
  ub-smart-table .datatable-footer,
  ub-smart-table ub-bulk-edit-buttons {
    /* background colors of card, title and etc. */
    background-color: beige;
  }

  ub-smart-table ub-smart-table-header-cell,
  ub-smart-table ub-smart-table-header-data-cell,
  ub-smart-table ub-smart-table-header-action-cell {
    /* table header background-color (columns names, filters, row-add and etc) */
    background-color: beige;
  }
  
  ub-smart-table .datatable-body-row.active ub-smart-table-body-cell p,
  ub-smart-table .datatable-body-row.active ub-smart-table-body-action-cell * {
    /* table texts colors */
    color: white !important;
  }
  
  ub-smart-table nb-card,
  ub-smart-table nb-card-header,
  ub-smart-table .datatable-footer,
  ub-smart-table ub-smart-table-header-data-cell,
  ub-smart-table ub-smart-table-header-action-cell,
  ub-smart-table ub-smart-table-body-cell,
  ub-smart-table .datatable-body-cell.action-cell {
    /* main border color */
  	border-color: black !important; 
  }

  ub-smart-table ub-smart-table-body-cell,
  ub-smart-table ub-smart-table-body-action-cell {
    /* default row background-color */
    background-color: pink !important;
  }

  ub-smart-table .datatable-body-row.active ub-smart-table-body-cell,
  ub-smart-table .datatable-body-row.active ub-smart-table-body-action-cell {
    /* selected row background-color */
    background-color: aquamarine !important;
  }

  ub-smart-table .datatable-body-row.active:hover ub-smart-table-body-cell,
  ub-smart-table .datatable-body-row.active:hover ub-smart-table-body-action-cell {
    /* selected-hovered row background-color */
    background-color: skyblue !important; 
  }

  ub-smart-table .datatable-body-row:hover ub-smart-table-body-cell,
  ub-smart-table .datatable-body-row:hover ub-smart-table-body-action-cell {
    /* hovered row background-color */
    background-color: aqua !important;
  }
</style>
```

<figure><img src="/files/7QGgZoiYnwDPoFBM5vXG" alt=""><figcaption></figcaption></figure>

### Use case: Changing page background color

Navigate to the *Custom code* tab and specify the following style to change page background:

```html
<style>
  nb-layout .layout {
    background: yellow;
  }
</style>
```

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

You can experiment with custom CSS and change the styles till you get the final look you need.

### Use case: Modifying List View items

You can also use CSS to control the styling of the component and apply different styles to its parts, for example, apply different colors to List View items. Check out the instruction below:point\_down:

1. Navigate to the *Custom code* tab and specify the following style:

```html
<style>
  .blue nb-card {
    background: lightblue!important;
  }
  
  .green nb-card {
    background: lightgreen!important;
  }
</style>
```

2. Now, select a **Card** inside the List View, and navigate to its *Styling* section.
3. Here, add your custom classes using the `{{index}}` variable, for example:

```javascript
{
  'blue': {{index % 2 == 0}},
  'green': {{index % 2 == 1}},
}
```

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


# Layout & navigation

Learn how to structure your app for better user experience.

<table data-view="cards"><thead><tr><th data-type="content-ref"></th></tr></thead><tbody><tr><td><a href="/pages/DgbF4Zw6IggkO5Ft0kEm">/pages/DgbF4Zw6IggkO5Ft0kEm</a></td></tr><tr><td><a href="/pages/KpbIwoGjDTSvlFySlZrS">/pages/KpbIwoGjDTSvlFySlZrS</a></td></tr><tr><td><a href="/pages/dsgyjJUsm9FfxEIfuw5l">/pages/dsgyjJUsm9FfxEIfuw5l</a></td></tr></tbody></table>


# Adding navigation to application

UI Bakery supports both [in-app](#in-app-navigation) and [external](#external-navigation) navigation. In this article, we'll explore these options in more details.

## In-App Navigation

There are a number of ways to configure navigation inside the application:

* [Using the Menu component](#navigation-using-menu-component)
* [Using global app header/sidebar](#navigation-using-global-app-header-sidebar)
* [Using the **Link/Button** component](#navigation-using-link-and-button-components)
* [Using the **Navigation** action step](/how-tos/layout-and-navigation/read-query-params-from-url)

You can configure navigation both to an **internal** application page as well as to an **external** website.

### Navigation using Menu component

You can configure navigation using the **Menu** and **Horizontal menu** components.&#x20;

To do so, simply add the component to the working area. In its *Items* property resource selector, your existing app pages will be selected by default. You can add more pages to the app, select custom icons for them, and edit existing ones - all changes will be synced instantly.&#x20;

If you need a manual setup, you can switch to the JS mode and map the *{{app.menuItems}}* to configure the Menu exactly how you want it.

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

You can also add an item to your menu that will [redirect users to an **external link**](#use-case-menu-item-redirect).

### Navigation using global app header/sidebar&#x20;

Another way to configure custom navigation in the app is by using our *reusable header* and *sidebar* options. You can select either of them in the right side panel, place a Menu component inside, and that's it. You don't have to add them to each page separately since they save their state across all app pages.

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

Check out the pages below to learn more:point\_down:

{% content-ref url="/pages/ToKwj1S2RuVfJM4d0537" %}
[Reusable header](/reference/working-with-components/reusable-header)
{% endcontent-ref %}

{% content-ref url="/pages/XylC1MLrdSUo0TyQhUJz" %}
[Reusable sidebar](/reference/working-with-components/reusable-sidebar)
{% endcontent-ref %}

### Navigation using Link & Button components

When using **Link** or **Button** components, you can pass dynamic parameters to a Detail page configuring the **Query params** property of the component. As an example, we'll review the case when you want to display the selected object on a child page on button click.

Check out the instruction below:point\_down:

As a prerequisite, you already have a **Table** and **Button** components added to your working area.

1. First, navigate to the **Pages** tab in the left side panel and create a new page (we'll name it *Customer* since it will display customer details).
2. Add a **Detail** component on this page to display customer details.
3. Now, go back to the *Home* page, select the **Button** component and set the *Customer* page as the URL in the right side panel.
4. Next, in the **Query params** property, switch to *JS mode* and configure the primary key of the object:

```javascript
{
  id: {{ui.customersTable.selectedRow.data.customer_id}}
}
```

In our example, the currently selected customer id will be sent as an **id** query parameter to the *Customer* page.

3. Next, create a new action of the **Load Row** type for your current Table.
4. In the *Filters* section, for *customer\_id* specify the following variable to receive the **URL query parameter** on the child page:

`{{activeRoute.queryParams.id}}`

5. Assign the *Load Row* action to the Detail component on the Customer page.

That's it! Now, if you select a row in the Table and then click the **Show customer** button, you will navigate to the child page with the selected customer displayed in the Detail component.

{% @arcade/embed flowId="ccxu1usQdj3qvXsa2zeE" url="<https://app.arcade.software/share/ccxu1usQdj3qvXsa2zeE>" %}

## External navigation

External navigation can be also configured using components like Button, Menu, etc. Here, we'll review two use cases of configuring redirect to an external page.

### Use case: Redirect following a trigger

1. Add a **Button** component to your working area.
2. Create a **JavaScript Code** action and specify the following code adding your external link:

```javascript
const a = document.createElement('a');
a.href = 'https://google.com';
a.target = '_top';
a.click();
```

or the following code to open the link in a **new tab**:

```javascript
window.open('https://google.com');
return 1;
```

3. Assign this action to the button's **On Click** trigger.

{% hint style="warning" %}
Please be aware that when running this code on certain browsers, you may get a **Blocked popup** warning message. This is especially likely to occur when the code is executed using the **Execute action** button. \
However, if the code is executed as a result of user interaction (e.g. button click), it should function correctly.
{% endhint %}

### Use case: Menu item external redirect

1. Select your Menu component and switch to *JS mode* in the **Items** property.
2. Now, modify this property and add a new item that will redirect to an external page:

```javascript
{
    title: 'Documents',
    link: '/docs'
  }
```

3. Next, navigate to the **Triggers** section of the component and create a new action for the *On Item Click* trigger.
4. Select a **JavaScript Code** action step and add the following code:

```javascript
if (data.link === '/docs') {
	window.open('https://docs.uibakery.io/');
}
return {{data}}
```

{% hint style="info" %}
In this code, we use *UI Bakery Docs* as an example. Replace the URL with the one you want to open on clicking the new menu item.
{% endhint %}

Now, when clicking the new menu item, the external link will open in a **new** tab.

5. **(Optional)** If you want the external link to open in the **same** tab, you need to add the `'_parent`' parameter to the following function:

```javascript
window.open('https://docs.uibakery.io/', '_parent')
```

{% @arcade/embed flowId="jQkD79J3aTXhnwQU3ET7" url="<https://app.arcade.software/share/jQkD79J3aTXhnwQU3ET7>" %}


# Reading query params from URL

This navigation use case focuses on how you can pass and read query params from the URL. Let's say you have a *Table* component and *on row select* you want users to be transferred to the *Details* page of a specific record. This can be achieved using the **Navigation** action step.&#x20;

Check out the instruction below:point\_down:

1. First, navigate to the **Pages** tab in the left side panel and create a new page (we'll name it *Customer* since it will display customer details).
2. Select your *Table* component and navigate to the **Triggers** section.
3. For the *On Row Select* trigger, click **Create action** to create a new action navigating to the *Customer* page.
4. Select the **Navigate** action step and set the destination page as `{{routes.customer.url}}`.
5. Next, in the **Query params** specify the following:\
   `id = {{ui.customersTable.selectedRow.data.customer_id}}`.
6. Now, go back to the *Customer* page and add a **Detail** component there to display the data of a selected record from the table.
7. Create a new action of the **Load Row** type for your current Table.
8. In the *Filters* section, for *customer\_id* specify the following variable to receive the **URL query parameter** on the child page:

   `{{activeRoute.queryParams.id}}`
9. Assign the *Load Row* action to the **Detail** component.

That's it! Now, when selecting a row in the Table, you'll immediately navigate to the child page with the selected customer displayed.

{% hint style="info" %}
This option can be also useful when creating a redirect after a form submit.
{% endhint %}

{% @arcade/embed flowId="L7qBNzNLI8RR0Xky1CMD" url="<https://app.arcade.software/share/L7qBNzNLI8RR0Xky1CMD>" %}


# Hiding UI Bakery loader in the Embedded mode

You might want to hide the UI Bakery loader from your application, for example when embedding it in a third-party solution. Here's how you can do this:

1. Open your app's *settings* and click on the **Embed URL** link. \
   It will open in a separate tab.
2. Here, add the `?uib_skip_init_loader` parameter to the page URL.
3. Click **Enter.**

The page will reload without the UI Bakery loader.

{% @arcade/embed flowId="PDngxH6WIiEVzGhuRMEb" url="<https://app.arcade.software/share/PDngxH6WIiEVzGhuRMEb>" %}

{% hint style="warning" %}
The UI Bakery loader is configured in a way that it is displayed until the application and resources are fully loaded. The approach suggested above *hides the loader on the resources loading*. \
This means that the loader will be hidden in the **Share** mode and on the **Account** pages, but will still be available in the Builder and in the menu.
{% endhint %}


# Data

Learn how to handle additional data operations in UI Bakery app.

<table data-view="cards"><thead><tr><th data-type="content-ref"></th></tr></thead><tbody><tr><td><a href="/pages/8T3zYU8czVER3y9Oy6uP">/pages/8T3zYU8czVER3y9Oy6uP</a></td></tr><tr><td><a href="/pages/iUdhCvKr8ZxJqGb1SDKJ">/pages/iUdhCvKr8ZxJqGb1SDKJ</a></td></tr><tr><td><a href="/pages/OjHF7n0IY2H1ecygyXp5">/pages/OjHF7n0IY2H1ecygyXp5</a></td></tr><tr><td><a href="/pages/SLO2VIrVtGTrvYVYejk9">/pages/SLO2VIrVtGTrvYVYejk9</a></td></tr></tbody></table>


# Managing user data with the {{user.email}} variable

The `{{user.email}}` variable can be used in cases when you need to load data that belongs to a particular user or manage component visibility based on specific user roles. Check out the sections below to review these cases.

## Loading data belonging to the current user

Let's say you want users to have access only to the data specific to them (for example, about their orders) based on their email. This is how you can do that:point\_down:

1. First, create an action that will load the data of the current user.\
   It can be an **SQL Query** action step referencing the current user email:

```sql
select * from users where users.email="user.email";
```

2. Turn on the **Transform result** toggle and specify there `return {{data[0]}}` , as the action will return an array of users with only a single item in it.

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

3. Now, after loading your user, you can fill their information (for example, about their orders) using a **Load Table** action step.
4. In the **Filters** section of the *Load Table* action, configure a filter that will reference the action from step 1 holding additional information about the current user:

`id = {{actions.getUser.id}}`

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

That's it! Now when different users login into the application, they will see only their orders' data.

## Managing component visibility based on the user's role

Let's say you want to make certain components visible only to specific user roles - you can do that using the **Condition** setting of a component. We'll show how you can do that reviewing the use case of a *Text input* component visible only to the *Admin*.

In our case, we want the Admins of the workspace to be able to perform the search by customers and look for specific customer records. For this purpose, we've added a *Text input* component to the working area.&#x20;

Next, to set component visibility we've specified the following condition in the component's *Condition* setting:

```javascript
{{user.role}} === 'admin'
```

And that's it! Now, any user with the Admin role will be able to see the customer search input, while for regular users the component will be hidden.

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


# Using JS libraries

In UI Bakery, you can use either your own or any public JS library. \
Also, we have a number of libraries included out of the box - [moment.js](https://momentjs.com/docs/#/use-it/), [lodash](https://lodash.com/), and [i18next](https://www.i18next.com/). You can use these libraries straight away without any additional configurations.

In this article, we'll explore how you can use the included libraries and connect other external libraries via the *Custom code* tab.

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

## Libraries included in UI Bakery

The *moment.js* and *lodash* libraries are preloaded in the JavaScript Code step. You can use them to manipulate dates and other values.\
For example, to transform date to a specific format:

```javascript
return {{data}}.map(item => {
  return {
    ...item, // put all the keys from the original object
    created_at: moment(item.created_at).format('MMMM Do YYYY, h:mm:ss a')
  };
});
```

Or to get deep value from an object:

```javascript
return _.get({ data }, 'birth.place.country', 'n/a');
```

We'll dive into more details about these libraries as well as *i18next* in the following sections.

### Using moment.js

To use [moment.js](https://momentjs.com/docs/#/use-it/) in your app, you don't need to configure it additionally as it comes pre-built. Simply follow the steps below:

1. In your app, create a new action of the **JavaScript Code** type and specify the following code:

```javascript
return {{ moment().format("dddd, MMMM Do YYYY, h:mm:ss a") }};
```

2. Run the action and check the **Result** tab - you will see the actual date displayed.

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

#### Utilizing moment.js plugins

If you need to utilize some additional plugins, you need to set up both the library and the plugins you need. \
Below is an example of how you can add a *moment-timezone* plugin that helps with timezone manipulations. Check it out:point\_down:

1. Navigate to the **Custom code** tab and specify there the script tags of *moment.js* and the *timezone* *plugin*:

<pre class="language-html"><code class="lang-html"><strong>&#x3C;script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js">&#x3C;/script>
</strong>
&#x3C;script src="https://cdn.jsdelivr.net/npm/moment-timezone@0.5.40/moment-timezone.min.js">&#x3C;/script>
</code></pre>

2. Now, create a **JavaScript Code** action and specify the following code requiring both of the libraries:

```javascript
var moment = require('moment'); require('moment-timezone');

const now = moment();
return now.tz('America/Los_Angeles').format('ha z')
```

3. Run the action and check the **Result** tab.

### Using lodash

To use [lodash](https://lodash.com/) in your app, just like with moment.js, you don't need to configure it additionally as it comes pre-built. \
Below is an example of using lodash when you need to filter a table by several keys of the selected row of another table. You just need to create a **JavaScript Code** action step and specify the following sample code:

```javascript
const data = {{ ui.table.selectedRow.data }};
return _.filter(
  {{ actions.list2.data }},
  _.matches({ 'userId': data.id, 'taskType': data.taskType }),
);
```

### Using i18next

[i18next](https://www.i18next.com/) also comes pre-built so no additional configurations are required. You can use this library to add translations to your application.&#x20;

Check out this example of utilizing i18next in your app:point\_down:

{% content-ref url="/pages/GmY8kCVVn22IPUIvjRm2" %}
[Internationalization (i18n) & Localization: Translating UI Bakery Apps](/how-tos/data/connect-external-js-library/internationalization-i18n-and-localization-translating-ui-bakery-apps)
{% endcontent-ref %}

## External third-party JS libraries

As we mentioned before, you can also connect any external JS library you need and use it in your app. In this section, we'll review an example how you can do that.

{% hint style="info" %}
If you need to use a *Node.js npm* library on the **server side**, see [UI Bakery Automations](/extras/automations).
{% endhint %}

### Use case: Add a confetti blast upon Form submission

Here we'll use the [canvas-confetti library](https://github.com/catdad/canvas-confetti) as an example - upon successful Form submission, users will get a confetti blast.

Follow the instruction below to learn how to do that:

1. Start by locating the library you need on a public JavaScript CDN, for example, <https://www.jsdelivr.com/>.&#x20;
2. Click on the library to open it and copy the provided *JavaScript tag*:&#x20;

```html
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.3/dist/confetti.browser.min.js"></script>
```

We'll be using <https://www.jsdelivr.com/package/npm/canvas-confetti> library as an example.

<details>

<summary><mark style="color:red;"><strong>IMPORTANT:</strong></mark> <strong>Expand and read the information below before proceeding to the next step.</strong></summary>

**It is essential to ensure that you are loading the appropriate build for browser usage.** The recommended approach is to load the minified UMD build from a CDN as opposed to the `index.js` file. This is because the index.js file may contain `imports` and `require` calls that require preprocessing before they can be utilized in the browser.

The naming conventions for builds that are suitable for loading in the browser vary among libraries, but are typically named following the "*umd/\<library>.min.js*", "*browser/\<library>.min.js*" or simply "<*library>.min.js*" format in the root folder.

For example, when using React:

🟢 You would want to use the file located at <https://cdn.jsdelivr.net/npm/react@17.0.1/umd/react.production.min.js>

🔴 Instead of <https://cdn.jsdelivr.net/npm/react@17.0.1/index.js>, which contains require calls.

It is important to note that **some libraries may not have a browser-compatible** build available. An example of such library - <https://www.jsdelivr.com/package/npm/@datasert/cronjs-matcher>.

Linking to the incorrect file may result in errors such as "<mark style="color:red;">require is not a function</mark>" or "<mark style="color:red;">module is undefined</mark>" in the browser console when starting your application. A comprehensive list of libraries and their respective browser-compatible builds can be found on the <https://www.jsdelivr.com/> website.

Additionally, some libraries may be available as ESM builds and not available as UMD builds. If a library is not working as expected, you can check for the availability of ESM builds on <https://www.jsdelivr.com/>, such as the example of <https://www.npmjs.com/package/yup> library:

🟢 ESM **will work** - `import yup from '`[`https://cdn.jsdelivr.net/npm/yup@0.32.11/+esm`](https://cdn.jsdelivr.net/npm/yup@0.32.11/+esm)`'`

🔴 UMD generated by jsdelivr **will not work** - <https://cdn.jsdelivr.net/npm/yup@0.32.11/lib/util/printValue.min.js>

</details>

3. Now, navigate to the **Custom code** tab in the Builder's footer and paste the copied tag there.
4. Add a **Form** component to the working area.
5. Next, create a new action consisting of two steps:
   1. For the **first step**, add a *Create Row* action.
   2. For the **second step**, add a *JavaScript Code* action and specify the following code:

```javascript
confetti({
  particleCount: 500,
  spread: 300,
  origin: { y: 0.6 }
});
```

{% hint style="info" %}
There are several [confetti code options](https://www.kirilv.com/canvas-confetti/) listed on the site so you can choose whichever suits you best.
{% endhint %}

6. Finally, navigate to the **Triggers** section of the Form and assign your newly created action to the *On Submit* trigger.
7. Submit your changes and enjoy the confetti! :tada:

{% @arcade/embed flowId="zgdinSd1e50KeBmFXE7a" url="<https://app.arcade.software/share/zgdinSd1e50KeBmFXE7a>" %}


# Internationalization (i18n) & Localization: Translating UI Bakery Apps

In this example, we will add the possibility to switch to *German* in the app. Check out the instruction below:point\_down:

{% hint style="success" %}
The feature allows you to translate not only the app content but also text that is not user controlled, such as interaction, hints, selection, pagination, messages, and validation.
{% endhint %}

1. Navigate to the **App triggers** section in the right side panel and create a new action for the *On App Load* trigger.
2. Select the **JavaScript Code** type and specify the following code to initialize all the translations using `i18n.init({...})`:

{% hint style="warning" %}
The **default interpolation** in UI Bakery is `{ prefix: '{', suffix: '}' }`. Therefore, you should use single curly braces to insert your values into the string.
{% endhint %}

```javascript
i18n.init({
  lng: app.locale,
  ns: ['translation', 'uibakery'],
  defaultNs: ['translation'],
  resources: {
    en: {
      translation: {
        english: 'English',
        german: 'German',
        welcome: 'Welcome!',
        total: 'There are {total} users in total',
      },
      uibakery: {
        interactions: {
          ok: 'OK',
          cancel: 'Cancel',
          edit: 'Edit',
          save: 'Save',
          upload: 'Upload',
          now: 'Now',
        },
        text: {
          ampm: 'Am/Pm',
          hours: 'Hr',
          minutes: 'Min',
          seconds: 'Sec',
        },
        hints: {
          add_new_row: 'Add new row',
          cancel_adding_row: 'Cancel adding row',
          cancel_edit: 'Cancel edit',
          cancel_editing_row: 'Cancel row edit',
          cancel_rows_edit: 'Cancel rows edit',
          cancel_row_addition: 'Cancel row addition',
          confirm_and_save_added_row: 'Confirm and save added row',
          confirm_and_save_edited_rows: 'Confirm and save edited rows',
          confirm_and_save_row_edit: 'Confirm and save row edit',
          delete_row: 'Delete row',
          download_data_as: 'Download data as CSV',
          edit_data: 'Edit data',
          edit_multiple_rows: 'Edit multiple rows (Bulk edit)',
          edit_row: 'Edit row',
          reload_data: 'Reload Data',
        },
        selection: {
          clear_selection: 'Clear selection',
          select_all: 'Select all',
          reset_all: 'Reset all',
          selected: 'selected',
        },
        pagination: {
          items: 'items',
          page: 'Page',
          of: 'of',
          show_items: 'Show {value} items',
        },
        messages: {
          no_data_to_display: 'No data to display',
          no_file_selected: 'No file selected',
          not_available: 'N/A',
        },
        validation: {
          this_field_is_required: 'This field is required',
          enter_valid_email_address: 'Enter a valid email address',
          please_match_requested_format: 'Please match the requested format',
          enter_valid_url: 'Enter a valid url',
          color_is_not_valid: 'Color is not valid',
          invalid_json: 'Invalid JSON',
          date_must_be_on_or_after: 'Date must be on or after {date}',
          date_must_be_on_or_before: 'Date must be on or before {date}',
          some_of_uploaded_files_are_larger_than: 'Some of uploaded files are larger than {size}Mb',
          uploaded_file_is_larger_than: 'Uploaded file is larger than {size}Mb',
          invalid_date_format: 'Invalid date: "{value}", date should be in "{dateFormat}" format',
          this_field_must_contain_between_characters: 'This field must contain between {min} and {max} characters',
          this_field_must_contain_at_least_characters: 'This field must contain at least {min} characters',
          this_field_must_be_less_than_or_equal_characters: 'This field must be less than or equal to {max} characters',
          this_field_must_be_between: 'This field must be between {min} and {max}',
          this_field_must_be_greater_than_or_equal: 'This field must be greater than or equal to {min}',
          this_field_must_be_less_than_or_equal: 'This field must be less than or equal to {max}',
          only_user_defined_mime_types_are_allowed: 'Only user-defined MIME types are allowed',
          only_expected_file_types_are_allowed: 'Only {expectedType} files are allowed',
          no_text_selected: 'No text selected',
          the_selected_text_is_already_annotated: 'The selected text is already annotated',
        },
      },
    },
    de: {
      translation: {
        english: 'Englisch',
        german: 'Deutsch',
        welcome: 'Willkommen!',
        total: 'Es gibt insgesamt {total} benutzer',
      },
      uibakery: {
        interactions: {
          ok: 'OK',
          cancel: 'Abbrechen',
          edit: 'Bearbeiten',
          save: 'Speichern',
          upload: 'Hochladen',
          now: 'Jetzt',
        },
        text: {
          ampm: 'Am/Pm',
          hours: 'Std.',
          minutes: 'Min.',
          seconds: 'Sek.',
        },
        hints: {
          add_new_row: 'Neue Zeile hinzufügen',
          cancel_adding_row: 'Zeilenhinzufügen abbrechen',
          cancel_edit: 'Bearbeiten abbrechen',
          cancel_editing_row: 'Zeilenbearbeitung abbrechen',
          cancel_rows_edit: 'Zeilenbearbeitung abbrechen',
          cancel_row_addition: 'Zeilenhinzufügen abbrechen',
          confirm_and_save_added_row: 'Hinzufügen der Zeile bestätigen und speichern',
          confirm_and_save_edited_rows: 'Bearbeitete Zeilen bestätigen und speichern',
          confirm_and_save_row_edit: 'Zeilenbearbeitung bestätigen und speichern',
          delete_row: 'Zeile löschen',
          download_data_as: 'Daten als CSV herunterladen',
          edit_data: 'Daten bearbeiten',
          edit_multiple_rows: 'Mehrere Zeilen bearbeiten (Massenbearbeitung)',
          edit_row: 'Zeile bearbeiten',
          reload_data: 'Daten neu laden',
        },
        selection: {
          clear_selection: 'Auswahl aufheben',
          select_all: 'Alle auswählen',
          reset_all: 'Alle zurücksetzen',
          selected: 'Ausgewählte',
        },
        pagination: {
          items: 'Elemente',
          page: 'Seite',
          of: 'von',
          show_items: '{value} Elemente anzeigen',
        },
        messages: {
          no_data_to_display: 'Keine Daten zum Anzeigen',
          no_file_selected: 'Keine Datei ausgewählt',
          not_available: 'N/A',
        },
        validation: {
          this_field_is_required: 'Dieses Feld ist ein Pflichtfeld',
          enter_valid_email_address: 'Geben Sie eine gültige E-Mail-Adresse ein',
          please_match_requested_format: 'Bitte beachten Sie das gewünschte Format',
          enter_valid_url: 'Geben Sie eine gültige URL ein',
          color_is_not_valid: 'Farbe ist ungültig',
          invalid_json: 'Ungültiges JSON',
          date_must_be_on_or_after: 'Datum muss am oder nach {date} liegen',
          date_must_be_on_or_before: 'Das Datum muss am oder vor {date} liegen',
          some_of_uploaded_files_are_larger_than: 'Einige der hochgeladenen Dateien sind größer als {size} MB',
          uploaded_file_is_larger_than: 'Die hochgeladene Datei ist größer als {size} MB',
          invalid_date_format: 'Ungültiges Datum: "{value}". Das Datum sollte im Format "{dateFormat}" angegeben werden',
          this_field_must_contain_between_characters: 'Dieses Feld muss zwischen {min} und {max} Zeichen enthalten',
          this_field_must_contain_at_least_characters: 'Dieses Feld muss mindestens {min} Zeichen enthalten',
          this_field_must_be_less_than_or_equal_characters: 'Dieses Feld muss kleiner oder gleich {max} Zeichen sein',
          this_field_must_be_between: 'Dieses Feld muss zwischen {min} und {max} liegen',
          this_field_must_be_greater_than_or_equal: 'Dieses Feld muss größer oder gleich {min} sein',
          this_field_must_be_less_than_or_equal: 'Dieses Feld muss kleiner oder gleich {max} sein',
          only_user_defined_mime_types_are_allowed: 'Nur benutzerdefinierte MIME-Typen sind zulässig',
          only_expected_file_types_are_allowed: 'Nur {expectedType}-Dateien sind zulässig',
          no_text_selected: 'Kein Text ausgewählt',
          the_selected_text_is_already_annotated: 'Der ausgewählte Text ist bereits kommentiert',
        },
      },
    },
  },
});
```

For a **Form**, for example, you may also want to add translation for the *Submit* button. Simply add this field to the code above (e.g.: `submit: 'Einreichen'`) and add `{{i18n.t('submit')}}` to the *Submit button text*'s value in the Form settings.

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

3. Add a **Select** component to the working area and add the languages you need to the *Options* field, for example:

```javascript
[
  {
    "value": "en",
    "title": {{i18n.t('english')}}
  },
  {
    "value": "de",
    "title": {{i18n.t('german')}}
  }
]
```

{% hint style="success" %}
`window.i18next.t` is now `i18n.t`.
{% endhint %}

4. In the component's *Value* field, specify `{{i18n.language}}`.
5. Next, navigate to the Select component's *Triggers* section and create a new action for the **On Change** trigger.
6. Select the **JavaScript Code** step and specify the following code:

```javascript
app.setLocale(data)
```

7. Finally, add translations anywhere you need to the text by using the `i18n.t` function.\
   In our example here, we've added translation to the *Heading* component:

`{{i18n.t('welcome')}}`

Done! Now when you select a different language in the Select component, your app will be translated automatically.

{% @arcade/embed flowId="knwlk5ydhQppnUsUQFfE" url="<https://app.arcade.software/share/knwlk5ydhQppnUsUQFfE>" %}

## Date, Time & Number localization

You can also localize Date, Time (including hours, minutes, and seconds), and Numeric formats. You can do this by selecting the *Application locale* in the App settings. English is the default option but you can adjust it to match your language needs.

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

Also, for such field types as Number, Currency & Percent, there's now the *Application default* option in the **Locale** field (View settings section). It matches the global App locale setting, and is selected by default.

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


# Implementing row-level security

UI Bakery allows you to control user access to specific table rows for security purposes. This can be achieved by implementing role-based access in the table.&#x20;

Let's consider a scenario with the *products* table:

<table><thead><tr><th width="61">id</th><th width="129">category_id</th><th width="385">product_name</th><th>price</th></tr></thead><tbody><tr><td>1</td><td>101</td><td>Laptop</td><td>$999</td></tr><tr><td>2</td><td>102</td><td>Smartphone</td><td>$599</td></tr><tr><td>3</td><td>103</td><td>Smartwatch</td><td>$199</td></tr><tr><td>4</td><td>102</td><td>Camera</td><td>$449</td></tr></tbody></table>

Here, each category is associated with a specific user, so basically users should be able to see only the products within their assigned category. The *user\_categories* table could look like this:

<table><thead><tr><th width="267.3333333333333">user_email</th><th width="113">category_id</th><th>name</th></tr></thead><tbody><tr><td>alice@example.com</td><td>101</td><td>Alice</td></tr><tr><td>bob@example.com</td><td>102</td><td>Bob</td></tr><tr><td>jane@example.com</td><td>103</td><td>Jane</td></tr></tbody></table>

This is the case when you would want to implement row-level security to ensure that users only see the products that are allowed for them. To do so, you can filter the product categories based on the currently logged-in user:

```sql
SELECT p.*
FROM products p
JOIN user_categories uc ON p.category_id = uc.category_id
WHERE uc.user_email = {{ user.email }}
```

This query would ensure that when user Alice accesses product data, they would only see the products within the category assigned to them (for Alice it's *category\_id = 101*).

{% hint style="success" %}
By default, UI Bakery ensures that the parameterized request received by the server matches the currently logged-in user's email (`{{user.email}}` ) for security purposes, meaning that this variable cannot be altered from the client side.
{% endhint %}


# Copying to clipboard

One of the most common functionalities you may need in your app is the ability for users to quickly copy text or other content to their device’s clipboard, making it easy to paste the content elsewhere. In UI Bakery, you can do this via a *JavaScript Code* action step.

Let's say you have a Button that returns a link - on click you want this link copied to the clipboard. Follow the steps below to implement this:point\_down:

1. Create a new action of the **JavaScript Code** type and specify the following code:

```javascript
await navigator.clipboard
  .writeText(link)
  .then(() => {
    alert("successfully copied");
  })
  .catch(() => {
  	alert("something went wrong");
  })
```

It will notify users whether the action was successful or not.

2. Assign this action to the Button's component **On Click** trigger.

That's it! Now when clicking the button, the link will be copied to the users' clipboard.

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




---

[Next Page](/llms-full.txt/1)

