Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
What is UI Bakery and how does it work?
Imagine building your internal app as your idea flows?
Meet UI Bakery 🍩 - 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 and 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.
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.
Connect a data source - Use native connectors to databases (PostgreSQL, MySQL, MongoDB), business apps (Stripe, Hubspot, Airtable), or any HTTP API.
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.
Publish the app and invite users - Deploy your web updates and share the app with users.
Start from a prompt - Describe your app, attach visual examples, if you want, and AI will generate it in minutes.
Connect your data - Use our hosted databases or connect your own.
Customize the app - Edit React code directly or ask AI to make the necessary changes.
Publish the app - Release the app, choose the environments to deploy to, and share it with specific users or make it public.
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
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.
Explore the best approaches to working with components in UI Bakery.
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.
Now, when selecting a row in the table, the data from this row will be displayed separately in the form.
Full React code access
Security
SOC 2, SSO, RBAC, audit logs
SOC 2, SSO, RBAC, audit logs
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.
Go to your action (for example, Load Users) and add another action step of the JavaScript Code type.
Next, write your code in the JavaScript field. In our case, we added the following code:
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.
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.
That's it! Now let's proceed to the 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 👇
Next step is to learn how you can modify the data in the Form and push it back to the data source.
Select your Form and navigate to the Triggers section.
Select On Submit in the first dropdown menu, and next click Create action in the Select action menu.
For the new action, select your data source and the appropriate step, for example, Update row.
Choose the table, if needed, and specify the field and values that you want to update.
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:
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.
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:
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.
In the Action to execute dropdown, select the action that loads your data.
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.
Build interfaces of any complexity utilizing our components, without the need to learn CSS and JS frameworks.
Effortlessly CRUD your data, add conditions, iterate through it, and debug with UI Bakery Actions.
Invite your users and instantly ship mission-critical updates to them.
Want to know more about low-code building with UI Bakery? Go ahead and check out this overview video👇
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 👇
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.
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.
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.
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.
You can also try our and features and let the AI build the application or components you need quickly and easily.
Ready to dive in? Go ahead then:
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.
Go to the Data Sources tab in the left side panel and click Connect.
UI Bakery also allows you to use custom JS code throughout the application. Check out some examples below of utilizing JavaScript in UI Bakery 👇
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 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 for even more templates!
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.
We have a separate article on exporting/importing apps if you feel like exploring it further
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 👇
Learn how to adjust the style and appearance of your components.
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.
Click Create Action in the Actions tab.
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:
Open your app's settings and click on the Embed URL link. It will open in a separate tab.
Here, add the ?uib_skip_init_loader parameter to the page URL.
Click
Learn about operations with files and their management in UI Bakery app.
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.
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.
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.
🎉 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.
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 [email protected] 🤓 Our team is always willing to help.
In the window that opens, select the data source type.
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.
UI Bakery doesn’t store your data. We only keep the encrypted credentials to access a data source.
Next, click Test connection to check if the configuration is correct.
(Optional) If you choose not to connect your data source immediately, you can use test MySQL or HTTP data sources instead.
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.
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.
Keep in mind that modifying or deleting a data source impacts all applications that use it.

Select the data source you need to load data from.
Depending on the data source selected, specify the necessary parameters.
Next, click Execute action. Your data will be displayed in the Result tab and errors (if any) will appear in the Logs.
The page will reload without the UI Bakery loader.
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.

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



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 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.
Also, if you deselect both the environments, you can create a Draft release. Read more about draft releases in .
As we've already mentioned before, all your applications are private by default. The access to your apps is managed based on . 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.
If necessary, you can also make your application public. There are two ways how you can do that:
Simply open the app's settings and turn on the Public toggle.
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.
Before inviting users to your app, make sure you first understand the , & 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.
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.
From there, you can also manage user access and share workspace and embed URLs.
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.
In this section, we'll explore two cases of using component methods.
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.
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:
Now, let's review the case how you can reset a Form after creating a record and submitting your changes.
Add a Form to the working area.
Navigate to its Triggers section and select Create action for the On Submit trigger.
For the first step, select the Create Row action type.
Next, add another step to it of the
Done! Now, after submitting the form, it will return to its default condition.
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.
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.
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.
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.
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.
In such cases, you need to add a Card component to the page and put your components inside the Card.
First, select the Expand content to fit checkbox in page settings in the right side panel.
Next, drag and drop a Card component into your working area.
Now, select the Expand content to fit checkbox for the Card as well.
Then, drop a Table component to the Card body - it should occupy all available space.
That's it! Now you get a page that looks excellent on every screen size and is not limited to single component only.
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. ❗However, this combination will result in a conflict and vertical scroll in the table won't work. You'll just have to leave the Height setting at Fixed to avoid this issue.
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.
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.
Draft releases are also available in the and can be restored as well.
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.
Click on the Dashboard icon in the upper left corner and select Release history.
In the window that opens, click Restore next to the version you want to roll back to.
Next, confirm your changes.
That's it! The Restore operation will be also added to the Release history.
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.
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.
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.
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.
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.
Variables can be used inside Component and Action properties. For non-text component properties, you can switch to the JS mode to use variables.
To start searching for a variable, you just need to type {{ in the code or text field, and a variables selector will appear.
Use Option ⌥ + Esc (Mac) or Ctrl + Space (Windows/Linux) as shortcuts to open the variables selector.
Learn how to structure your app for better user experience.
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.
All top-level variables and functions defined in a JS file are accessible in Components and Actions.
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.
Functions and variables defined in one JS file can also be used in others.
Since JavaScript files are added to a page as scripts, you can only reference definitions from files loaded earlier.
JavaScript files can also contain custom libraries in the custom code section as well as default libraries like moment and lodash.
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:
The logs will appear in the Logs panel, prefixed with the file name where the code resides.
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.
In our case, we simply selected the Load Users action in the Data field of our Table component.
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.
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.
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.
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.
Here, we talk about Menu, Context menu, and Horizontal menu components.
UI Bakery gives you the ability to configure specific menu items, for Menu components, which users can access based on their roles. 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:
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;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.
Navigate to the Custom code tab and specify the following script:
Create an HTTP Request action selecting the GET method and specifying the necessary URL.
Next, turn on the Transform result toggle to open the mapper field and specify the following code:
Click Execute action.
Navigate to the Custom code tab and specify the following scripts:
Now, create a new action consisting of two steps:
For the first step, add a JavaScript Code action that will generate an XML from JSON:
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
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.
Here, we talk about the Form component.
Drag a Form component to your working area.
Next, to populate the data of a table's selected row, specify the {{selectedRow.data}} variable in the form's Data field.
Create a new action of the Condition type and specify the following code in the code field:
3. Next, add an if condition:
specify for it a Create Row action
specify the Form value object as {{ui.form.value}} in the Configure Row section.
4. Add an else condition:
specify for it an Update Row action
configure the identifier in the Filters section as id = {{ui.form.value.id}}
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.
There may be a case when you want the app to keep sending the same request until the result is ready. For example, you may want the API to keep returning 202 status code until the data is ready, and then return a 200 with all the data.
Below is the approach we suggest to implement this kind of logic:
Create a new action of the HTTP Request type (called here apiCall):
Specify the method and URL.
Select the Transform result toggle and in the Modify the result field add return {{res}};.
This will return the status of the API request as, by default, the HTTP step returns the body of the response.
Create another action of the JavaScript Code type (called here callApiWithRetries) and specify the following code:
Here, the action is called programmatically with actions.apiCall.trigger(), which allows you to easier control retry logic.
But you can also make this retry action generic and execute it providing actionName via params. To do so, you simply need to change the code to actions[params.actionName].trigger():
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 👇
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👇
Locate the Text input component in the Components tab and drop it to the working area.
Give your component a meaningful name and specify any other parameters you need in the right hand panel.
Here, also navigate to the Triggers section and for the On Change trigger select your loadUsers action.
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:
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.
The feature is deprecated, please refer to for information on building custom components in UI Bakery.
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.
Unlike custom components, unrestricted custom components
Here, we talk about the component as an example, but the guide applies to all available components.
We've previously touched upon configuring component visibility using the Show condition setting . Check it out if you need a reminder and come back to learn about the two main configurations of this setting:
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.
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.
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
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.
Check out the instruction below👇
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).
Select your Table component and navigate to the Triggers
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:
Create an action that will return your table data - select the JavaScript Code type and specify your data in the code, for example:
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.
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.
In UI Bakery, there are three distinct environments for data sources:
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.
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.
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👇
Create a new action of the JavaScript Code type and specify the following code:
It will notify users whether the action was successful or not.
Let's say you have a website or application embedded in the UI Bakery Iframe component and you need to communicate with it - send or receive data. You can easily do this using the JavaScript postMessage function.
Follow the instruction below for details:
Give your Iframe component a unique name to send the data. This name will be added as a component class allowing you to query it with JavaScript.
Create a JavaScript Code action and specify the following code to query the Iframe and send messages to it:
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.
Learn about custom code operations in UI Bakery to add more customization to your applications.
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.
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.
Take a quick look at the Git controls in UI Bakery 👇
Branches selector - switch between branches
Sync branches - sync the list of branches with the Git repository
Pull - pull the latest changes from the selected branch
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.
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👇
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.
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.
Now, if you're ready to proceed, move on to the then. If you'd prefer not to for now, check out the section below 👇
Learn how to handle additional data operations in UI Bakery app.
{{ui.formName.value.fieldName}}{{ui.formName.value}}<script src="https://cdnjs.cloudflare.com/ajax/libs/fast-xml-parser/4.3.2/fxparser.min.js" ></script>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.
Commit the changes to the migration branch.
Create a pull request from the migrate-release/0.0.0 branch to the main branch.
Merge the pull request into the main branch.
Switch back to the main branch in UI Bakery and pull the latest changes.
Using Git, merge the updated main branch into any branches under active development.
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.
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.
If you have any questions regarding our Shared permission group, check out this section for a quick explanation💡
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 for a more detailed explanation of UI Bakery pricing model based on specific use scenarios. We hope it helps!









function dateWithLabel(date) {
console.log('debug!', date);
return 'Date: ' + moment(date).format('MM/dd/yyyy');
}async function makeApiCallWithRetry(maxRetries = 10, delay = 2000) {
let retries = 0;
while (retries < maxRetries) {
const response = await {{actions.apiCall.trigger()}}
// If the response is 200, return the data
if (response.status === 200) {
return response.body;
}
// If the response is 202, retry after a delay
if (response.status === 202) {
retries++;
await new Promise(res => setTimeout(res, delay));
} else {
throw new Error(`Unexpected status code: ${response.status}`);
}
}
}
return await makeApiCallWithRetry();actions.apiCall.trigger() -> actions[params.actionName].trigger()

Add other components you need to the Card header, for example, Text input - to add some filters.
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.


{{data}}Click Execute action.
In contrast to custom components, unrestricted custom components are rendered on the same level as the rest of UI Bakery components.
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.
Here is an example of an unrestricted custom component:
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:
Additionally, you can pass data using JS API in your actions:
To access this data within the custom component, you can use:
You can also subscribe to data updates with the following code:
If your custom component produces events or needs to trigger an action, you can use the following code:
Use this code inside a component to set its value. Once executed, the new value will be available as {{ui.customComponent.value}}.
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.
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.
Just a quick recap how this setting works: basically setting it to false hides the component, while setting it to true displays it.
Now let's dive into its configurations 👇
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.
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.
Breaking changes
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.
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.
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.
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.
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.
In our example, we will use a Products table and we will add a Detail component to display product details. Let's dive in!
Start by adding a Table to your working area.
Next, you need to load your data and then bind it to the Table.
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.
Remove this default action from the Data field of the Detail component.
Instead, specify the value of the selected Table row - use the selectedRow.data variable.
As simple as that! Now you can check the result - product details will change as you select different records in the Table.
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!
Start by adding a Form component to the working area.
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.
A role with this permission can view the Staging environment.
Use Dev
A role with this permission can view the Dev environment.
Develop
A role with this permission and Use Dev can view the Dev environment and edit applications.
Additionally, a role with this permission or a role with Can create app permission can view Data sources, Database, AI Playground and Actions library.
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
A role with this permission can create applications. The apps created will be added with the same permissions as the role that created them. Additionally, a role with this permission or a role that has at least one Develop permission can view Data sources, Database, AI Playground and Actions library.
Deleting apps is restricted to Admin & Editor roles.
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. 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.
Deleting data sources is restricted to Admin & Editor roles.
Use Prod
A role with this permission can view the Prod environment.
Use Staging
console.logWhen 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:
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.
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.
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
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

For the On Row Select trigger, click Create action to create a new action navigating to the Customer page.
Select the Navigate action step and set the destination page as {{routes.customer.url}}.
Next, in the Query params specify the following:
id = {{ui.customersTable.selectedRow.data.customer_id}}.
Now, go back to the Customer page and add a Detail component there to display the data of a selected record from the table.
Create a new action of the Load Row type for your current Table.
In the Filters section, for customer_id specify the following variable to receive the URL query parameter on the child page:
{{activeRoute.queryParams.id}}
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.
Assign this action to the table.
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:
Add this action to the Select/Tag column's Options field in the JS mode:
Or, alternatively, you can specify there the following code to additionally display the selected value in the Edit mode:
Finally, create an action that will return only the options NOT selected - select the JavaScript Code type and specify your code, for example:
Make sure to toggle on Reactive trigger, run on components' changes in the Setup step of this action.
Now, click the Select/Tag column and open its View settings section.
Locate the Tag mapper field and specify the following code to return the values not selected in the table:
Now, when you select a value from the dropdown, it will be added to the table and will no longer be available for selection.
{{actions.availableOptions.data}}To learn more about role permissions, refer to this article.
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.
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.
Click your workspace name and select Users & Permissions.
Next, head to the Roles tab and click Add Custom Role.
Give your role a meaningful name and select the necessary permissions for apps and data sources.
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.
When such users log into UI Bakery, they will have access only to the environments you specified.
You can always modify your custom roles, if needed, or delete them when they're no longer necessary.
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.
Redirects also work with both MFA and SSO enabled.
Drag a Text input component and drop it above the Table.
Add a descriptive label or placeholder for the Input.
Next, scroll down to its Triggers section and assign the action that loads your data to the On Change trigger.
For the next step, you need add a filter to your load data action to see only the records corresponding to a certain criteria.
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.
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.
Navigate to your data source settings and turn on the Enable environments toggle.
For each environment, configure their settings and click Connect data source.
Data source environments are intrinsically linked to application environments. When you move your app from one environment to another, the corresponding data source environment is also switched, ensuring data compatibility.
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.
That's it! Now when clicking the button, the link will be copied to the users' clipboard.
await navigator.clipboard
.writeText(link)
.then(() => {
alert("successfully copied");
})
.catch(() => {
alert("something went wrong");
})Similarly, you can also subscribe to events from the Iframe via a JavaScript Code action step with the following code:
And you need to assign this action to the On Page Load trigger to bind the listener to the new messages when the app loads.
const iframe = document.querySelector('.uniqueName > iframe').contentWindow;
iframe.postMessage('message', { foo: 'bar' })const iframe = document.querySelector('.uniqueName > iframe').contentWindow;
iframe.on('message', (data) => {
console.log(data);
});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 to the environment you need.
UI Bakery also supports multi-instance deployment and synchronization. Learn more about it here👇
Open Pull Request - create a pull request for the selected branch
Commit & Push - commit and push the changes to the selected 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).

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.
Now, after loading your user, you can fill their information (for example, about their orders) using a Load Table action step.
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}}
That's it! Now when different users login into the application, they will see only their orders' data.
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.
Next, to set component visibility we've specified the following condition in the component's Condition setting:
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.
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 🙂 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!
If that is the case for you, have a look at a couple of common methods demonstrating how you can mock your data in UI Bakery.








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.
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.
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.
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 .
You can also configure specific settings for the action execution flow, such as toasts, confirmation dialogs, and execution delays. For more details, refer to .
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
While {{data}} and {{error}} are specific to each step, {{params}} can be accessed in all steps.
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}}
These triggers can be turned on/off in the Setup step of the action.
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.
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.
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.
Here is an example of using the result of two steps - mapping user orders to the user object:
Double-click the action step to change its name.
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.
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.
If you have a global action that you'd like to reuse across multiple applications, you can extract it to the .
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' for different user roles.
You can find the full list of all data sources available here 👇
Check out and follow the step-by-step instruction to connect a new data source.
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.
On the Data sources page, click Manage access settings.
On the page that opens, click the pencil icon next to the role you want to edit.
Navigate to the Data sources tab and select the data sources which you want to grant access to - Use and/or Edit them.
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.
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.
Refer to to learn more details on how to safely manage anonymous access.
To be able to connect to your data source in the cloud version, you need to whitelist our IP addresses:
This process may differ depending on the database. Below you can find whitelisting instructions to a couple of databases:
(with .conf files)
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.
Under your workspace name, you can access you account and workspace settings, check audit logs, get the latest updates, and more.
Here, you can change your personal details and email, reset the password, as well as cancel your subscription.
Admins of the workspace can help with users' password reset. Here, two options are available:
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.
Here, you can change your workspace name and URL, and configure specific settings for your end users, such as:
Hide and
If you change your URL, make sure to adjust your applications' external links.
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:
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.
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:
For team members with Admin or Editor roles, the header will be displayed as usual.
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.
Once disabled, users will no longer be able to access the palette.
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.
When retrieving a list of items, a lot of APIs may return an object that has a structure similar to this:
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.
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:
We simply need to add a JS code action step to our action and specify the following code:
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.
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:
But you need a particular property, not the whole entity, so you need it to look like this:
To transform a list of objects like this, use a separate code action step with the following JavaScript function:
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:
In the same way, you can do calculations, rename properties or add additional fields to the response.
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:
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.
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.
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:
ℹ️ 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:
If you are not completely sure whether the server returns a valid JSON string, you can extend it to this version:
Available in the Low-code mode.
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, 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.
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.
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.
During app building, the AI can also read and analyze logs to address any issues that occur.
You can choose between creating & connecting a hosted database or connecting your own data source to access the data you need.
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.
Click on the Connect Data tab in the top left corner to see the list of all available data sources, both hosted and user. Here, you can also see which data source is active now, switch to a different data source, or .
The AI will have access only to the data sources you authorize.
In the Database Editor tab on the left, you can manage your current database as well as all other hosted databases, all in one place.
You can create and delete databases.
You can switch between different databases using the dropdown in the header.
You can see a list of all tables with their data.
You can add new tables and rows, duplicate, edit and delete existing ones.
Tokens for the Custom App feature work in the same way as for Custom components 2.0. You can find more details here👇
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.
You can also share the app with specific users or make it public from the Share button popup.
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.
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:
Start by navigating to the data source connection window - choose your data source and select the Enable SSH tunnel checkbox.
For already connected data sources, you just need to open their settings.
Now, you need to configure your bastion host to allow UI Bakery to establish an SSH tunnel:
Create a UI Bakery user (UI Bakery will connect to your bastion as this user):
Next, create the required authorized_keys file and configure its permissions:
Once connected, return to UI Bakery and specify your bastion host and port number under the Enable SSH tunnel checkbox.
Next, specify the Bastion user you created in Step 2 (a).
Finally, scroll up to the Connection settings section and specify all the required fields.
ℹ️ Please note that in the Host field you either need to specify:
localhost (if the bastion and database are on the same virtual machine) or
your private network IP address (if bastion and database are on different virtual machines with mutual access and the 3306 port is open)
Click Test connection to check whether the data source can be connected, and then click Connect Datasource.
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.
❗We highly recommend the ngrok approach for testing purposes 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.
Create an account at if you do not have one.
.
Unzip the archive (initial instruction can be found ).
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:
ngrok config add-authtoken 2qO7FgeP0PKr4eigzL2tdAJsxt8_3tBf8bHFrUiNZgdCEDvrc
If successful, you’ll get the following message:
Authtoken saved to configuration file: /Users/user_name/.ngrok2/ngrok.yml
Now, you can proceed with exposing your local app server or database. Use one of these commands :
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.
Next, navigate to UI Bakery > Connect datasource.
Select your data source and specify the copied host and port together with the other database details.
Click Test connection to check whether the connection can be established.
And finally, click Connect Datasource.
UI Bakery actions can be page-specific or global:
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.
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 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.
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.
During development, all actions can be run using the Ctrl + Enter/Cmd + Enter hotkey.
✔️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.
✔️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;
Each action has a Setup step and a Finish step that contain additional settings you can configure. In this article, we'll explore these steps and their settings in more details.
This section contains automatic action triggers you can turn on/off. You can find more information about them on .
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.
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.
Refer to for more information on configuring chain actions.
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.
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}}
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:
Create a new action of the JavaScript Code type.
Specify the following code:
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.
For the Finish step, navigate to the Error toast, and refer to the error message as {{actions.yourAction.error.message}}.
Now, anytime the condition is met and the action fails, the user will see a customized error message.
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.
The following triggers are available:
Page triggers
App triggers
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.
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.
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
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👇
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.
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.
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.
A module is created as a separate application, and it can seamlessly communicate with a parent application, sending and receiving events.
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.
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.
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.
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 to Staging and/or Production, publish a , as well as from the Release history.
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:
Open the app you need and navigate to the Library subtab (of the Components tab) in the left side panel.
Select the module you want to use and drag it into the working area.
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.
Click the Reload button in the right side panel to refresh the changes in the module.
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.
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.
Then, in the module, you can also subscribe to the On Data trigger with the last received value accessible in the {{module.data}} variable.
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.
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.
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.
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.
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.
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.
Before proceeding to importing an app, check out if you're planning on importing your app into another instance.
In the target workspace's menu, in the Apps section, click the + button and select Import archive.
Upload a ZIP archive of the app exported from UI Bakery.
Next, click Import app.
The application will be imported to your workspace and you can access it right away.
If you want to browse through more templates than are available on the , you can also check our and use a template from it on your instance.
Fork your own copy of the repository.
Download the copy as a ZIP archive.
In the target workspace's menu, in the Apps section, click + > Import archive.
Upload the ZIP archive of the repo.
Click Import app. The app will open in the Edit mode.
That's it! Tailor the app to your specific requirements using UI Bakery's visual interface and deploy your application with ease.
In the target workspace's menu, in the Apps section, click the + button and select Import from Git.
Enter the SSH URL for your Git repository.
Copy and add the SSH key that will appear to your Git repository.
Next, click Import app
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:
It means that your UI Bakery version and app version differ causing a mismatch. To avoid issues like this, you should your UI Bakery instance.
In UI Bakery, you can implement custom hotkeys for your application if necessary. You can do this using UI Bakery actions and the hotkeys-js library. Check it out👇
Start by connecting the hotkeys-js library - navigate to the Custom code tab and add the following script tag:
Next, set up a global action to define the hotkeys:
Click on the app area or select your current page in the Pages tab to access the Settings of the current page (right side panel).
For the On App Load/On Page Load trigger, click Create action.
Add a JavaScript Code action and define your hotkeys and action calls in the code, for example:
Now, test the hotkey - run the action, focus the app area, and press the E keyboard key. This should open the browser alert.
The hotkeys will only work when the app area is focused. If the hotkeys don't work, try clicking on the app area to focus it.
You can also refer to for additional information on defining hotkeys.
You can also further extend the code above and define a hotkey-action pairing assuming that you've already configured the actions that you'd like to trigger with the hotkeys, for example:
After defining and setting everything up, you need to reload the app to test the final setup. The hotkeys action will be executed upon app/page load, defining the hotkeys to activate the app's actions.
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.
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.
Here, we talk about , , and components.
Complex components (Table, Form, Detail) support various column/field types, such as:
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 and allows you to reuse the result of other actions.
Let’s check a couple of cases where action.trigger may come in handy.
As we've already mentioned before in , you can use await actions.actionName.trigger() anywhere in your app actions code:
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.
State variables come in two scopes:
App - maintain their values across different pages
In this article, we'll explore ways of uploading files in UI Bakery using the following methods:
Audit logs are available on Business and Enterprise plans (plus the legacy Team plan).
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.
Audit logs are available to users with an
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:
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:
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.
Let's consider a scenario with the products table:
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.
{{ui.myCustomModal.open()}} - open a modal{{ui.myCustomModal.close()}} - close a modal{{ui.yourForm.reset()}}const xmlContent = atob({{data.base64}});
const parser = new XMLParser();
return parser.parse(xmlContent);<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/[email protected]/dist/jstoxml.min.js"></script>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)UB.updateValue('Data from custom component');UB.triggerEvent('Data from custom component');<!-- 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>
<!-- root element where the component will be rendered -->
<div class="root"></div>
<!-- custom styles -->
<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;
}
</style>
<!-- custom logic -->
<script type="text/babel">
function CustomComponent() {
// receive data from UI Bakery
const data = UB.useData();
return (
<div className="custom-component-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 />, 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')));
</script>{
data: [1,2,3],
display: 'only_new',
}ui.customComponent.setData({ ... })const data = UB.useData()UB.onData(data => {
console.log('new data', data);
});return data.map(item => {
if (item.customerNumber === 103) {
console.log('Customer #103', item);
}
return item;
});[
{"id": 1, "tag": 0},
{"id": 2, "tag": 2},
{"id": 3, "tag": 1},
]return [
{ value: 0, title: 'Innovate' },
{ value: 1, title: 'Synergy' },
{ value: 2, title: 'Dynamic' },
{ value: 3, title: 'Strategic' },
{ value: 4, title: 'Sustainable' },
];const options = [...{{actions.availableOptions.data}}];
if ({{value}}) {
const valueOption = {{actions.allOptions.data.find(o => o.value === value)}};
options.unshift(valueOption);
}
return options;return {{actions.allOptions.data}}?.filter((option) => {
return !{{ui.table.value}}.find(item => item.tag === option.value);
});{{actions.allOptions.data.find(i => value === i.value).title}}select * from users where users.email="user.email";{{user.role}} === 'admin'return [null, undefined].includes({{ui.form.value.id}});{
length: 10,
records: []
}<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/hotkeys.min.js"></script>Next, connect your app to Git following this instruction.

























cd DownloadsNext, you need to add your authtoken to the default ngrok.yml configuration file using this command:
authorized_keys file:




Smartphone
$599
3
103
Smartwatch
$199
4
102
Camera
$449
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:
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:
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).
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.
1
101
Laptop
$999
2
102
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# 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# Login as root user
sudo su
# 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# Use any text editor and insert previously copied ssh public key in authorized_keys file
vim /home/uibakery/.ssh/authorized_keysSELECT p.*
FROM products p
JOIN user_categories uc ON p.category_id = uc.category_id
WHERE uc.user_email = {{ user.email }}{{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
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.
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.
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.








Ctrl + L/Cmd + L - jump to a line;Ctrl + Alt + L/Cmd + Option + L - format code.

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:
Once you select the car make in the first dropdown, it will filter only its specific models in the second dropdown.
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.
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.
To connect your data to such components as Table, Chart, List View, and 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.
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 button. Thus, you won't have to build new component structure from scratch.
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.
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.
Simple components, such as Input and Date picker, have a value property. It's a reference to the current component 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:
A Table component has multiple keys, such as selectedRow, editedRow, newRow and deletedRow with the following inners properties:
UI Bakery allows hiding components by configuring their Show condition as false.
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:
Add a Checkbox component to your canvas.
Next, select the Table and set its Show condition as {{ui.checkbox.value}}.
Test it out - select and clear the checkbox to either show/hide your Table component.
Learn more about controlling component visibility👇
✔️ Select/Tag
🔢 Number
☑️ Multiselect/Tags
⭐ Rating
{;} JSON
💲 Currency
🔘 Button
📈 Percent
🔽 Context menu button
✅ Boolean
🖼️ Image
📅 Date & Time
🖇️ 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.
Form, Table, Detail and some other components support autogeneration of field types based on the component data. Autogeneration works in the following cases:
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.
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.
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.
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.
You can also configure fields and columns of various types manually. Follow this instruction to learn how you can do that:
Click the plus sign at the bottom of the Columns list.
In the Field panel that pops up, configure the following properties:
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.
Select a field type from the Type dropdown.
Configure the remaining properties as needed.
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.
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.
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.
🔠 String
🗓️ Date
🔠 Long Text
⌛ Time
🔗 Link
As well as pass custom parameters to the action:
The parameters will be available as {{params}} variable in any action step.
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.
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.
In the Filters section, configure selected record field as Order Number ={{data}}.
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.
In the Filters section, configure selected record field as Order Number ={{data}}.
Now, to combine the data from both actions, create a new action of the JavaScript code type.
Rename the action to loadDetails and specify the following code:
To display the obtained data, add the Detail component to your working area.
Assign the loadDetails action to the data field of the Detail component.
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.
In the Filters section, сonfigure selected record field as Order Number ={{data}}.
Next, create a multi-step action - add your loadOrderDetails action as the first step.
For the second step, create a new action of the JavaScript code type.
Rename the action to applyDetailsToOrder and specify the following code:
To display the obtained data, add the Table component to your working area.
Assign the applyDetailsToOrder action to the data field of the Table component.
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.
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.
Add another action of the JavaScript code type.
Rename the action to getUsers and specify the following code:
To display the obtained data, add the Table component to your working area.
Assign the getUsers action to the data field of the Table component.
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.
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.
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.
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 👇
To store a value within an action, follow these steps:
Add a new step to your action and select Save to State.
Select the variable you want to modify.
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.
You can use state variables in JavaScript code blocks to:
Save state values
Read state values
Reset a single variable or all variables to the initial value
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.
Here's how you can do that:
Create a state variable of the String type, define its initial value, and name it Comment, for example.
Assign the variable to the Text input component's Value field.
Create a Save to State action that will save the new comment - define its value as {{data}}.
Assign this action to the On Change trigger of the Text input component.
Next, create another Save to State action, that will reset the comment - define its value as ''.
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.
Add the File picker component to the working area.
Create a new HTTP Request action:
Set the target URL to the server where you want to upload the file
Set the request method to POST
In the Body, select Form Data, add a new key (the parameter name is typically file), and set the parameter type to file
Here, also specify the file by referencing the File picker component:{{ui.filepicker.value}}
In your component, select the file you want to upload, and execute the action.
Before you proceed to the instruction below, ensure your server is configured to accept the binary format for file uploads.
Add the File picker component to the working area.
Create a new HTTP Request action:
Set the target URL to the server where you want to upload the file
Set the request method to POST
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' }
In your component, select the file you want to upload, and execute the action.
Alternatively, you can create a Blob object with binary content without using File components, and then send it to the server.
To achieve this, follow the steps below:
Create a new action:
For the first step, add the JavaScript Code action step and specify the following code:
For the second step, add the HTTP Request action step (POST, Body - Binary) to reference the result of the previous step:
Add the File picker component to the working area.
Create a JavaScript Code action and specify the following code:
where 'url' needs to be replaced with your target URL.
Assign the Code action to the On Change trigger of the File picker component.
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.
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:
Log in
Sign up
Log out
Entered wrong password
Entered wrong MFA
Requested password reset
Completed password reset
Roles assigned
Invited
Removed
Permission denied
Created
Updated
Removed
(System) updated
Create
Open (failed attempts included)
Deploy
Publicity changed
Connect
Update
Delete
Request
Open
Exit
Version overwritten
History snapshot restored
Success
Error
Request performance metrics
Error
Success
Created
Updated
Deleted
Create table
Delete table
Duplicate table
Update table
Viewed audit logs
Password reset link generated
Password reset requested
MFA reset
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.
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:
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:
After the spreadsheet is created, you can connect it as a data source and create actions to retrieve and write data to it.
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:
Install JSON-server package: \
Create db.json file with similar format:\
Run JSON server:\
Don't forget that you can always use UI Bakery's Test data sources which are available right in the Data Source connect dialog.
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👇
NgRok is a product that creates a secure tunnel from your data source to the internet. Learn more👇

To create a new theme, follow the steps below:
In the Builder mode, navigate to the Theme tab in the left side panel and click + Create new theme.
Give it a meaningful name and click Create.
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.
The themes you create will be added to the Custom themes section. From there, you can edit them according to your needs.
Click the pencil icon for the theme you want to edit. You'll see the Editor open in the right side panel.
In the Editor, you can customize the following sections:
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.
Canvas - the main color of the app's background
Containers - the look of the containers used in the app, such as background color, radius, border, and shadow
Components - the look of the inputs, selects, and buttons across the application
Text - text colors
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.
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.
Here we'll show you how you can customize your theme font using Google Fonts. Check out the instruction below:
Navigate to the Google Fonts website, find a font you like, and click on it.
Next, click on the Get font button in the upper right corner and click on Get embed code.
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"
Specify the font's actual name (e.g.: Nunito Sans) and set another font as its fallback.
(Optional) In the Licence field, specify the licence to your font if it's under a custom licence.
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.
You can also select any theme for each application separately if necessary.































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.
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.
Our flow will consist of two parts:
Creating a basic reusable SQL action
Adding variable parameters to it (filtering data)
Go to your workspace and click the Actions Library link in the bottom left corner of the screen.
Create a new action of the SQL Query type and specify the following code in the query field: select * from users;
Name your action loadUsers. Now the action is ready to be used in any app in the workspace.
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.
Go back to the Actions Library and open your loadUsers action.
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:
Next, specify the following condition in the query that will use the filter parameter you've added:
While testing the action, you can change the parameter value and see how it affects the query result.
Now, revert the filter to the default empty string value and go back to your app.
Select the Execute Action step you've created before.
In the Custom action params field, hardcode some filter value to pass it to the action, for example:
Next, run the action to observe the filter being applied.
You can also use the component or state values as the arguments passed in the action.
Lastly, assign your action to a component to display the data.
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.
Go to the Actions Library.
Click the Release button in the upper right corner.
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.
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.
In some cases, you might want to move an action from the app to the Actions Library to make it more abstract and reusable.
Click on the three dots next to the action you want to extract and click Copy.
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.
Modify it, if necessary, and remove all references to UI components or state variables.
Go back to the app and replace this action with the Execute Action
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.
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.
To add new styles, you need to specify a new <style> tag with the desired styles in the Custom code section, for example:
Once added, you need to apply these styles to a component in the Styling section of the component's settings.
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
Check out the use cases below showing in more details how you can modify components with custom CSS in UI Bakery👇
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.
Below is an example of the style you can add to change Table colors:
Navigate to the Custom code tab and specify the following style to change page background:
You can experiment with custom CSS and change the styles till you get the final look you need.
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👇
Navigate to the Custom code tab and specify the following style:
Now, select a Card inside the List View, and navigate to its Styling section.
Here, add your custom classes using the {{index}} variable, for example:
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 tokens.
On the homepage, click Create new app.
Paste this prompt in the chatbox:
Submit your prompt and wait while the AI creates your app.
Try it out in the Preview mode.
Congratulations! You've just built your first app.
UI Bakery runs on third-party large language models (LLMs) that process your prompts and any visual images to build applications. The AI uses tokens to measure the effort required to complete each task. Tokens are used during the following phases:
Planning
Generating
More complex requests naturally require more tokens.
The amount of tokens you have left is displayed in the bottom left corner of the chatbox.
As a new cloud user, you'll start with a free token allowance on the Free plan. Once you use them up, you need to upgrade your plan to get additional tokens and continue building. You can find details about available plans on .
UI Bakery allows you to choose from creating & connecting a hosted database or connecting your own data source.
You can connect a data source yourself via the Connect Data tab in the top left corner where you need to choose from a hosted database or your own. You can find more info about data sources and how to connect them on these pages: , .
To create a hosted database, you simply need to select this option and give your database a name. You can manage all your hosted databases via the Database Editor tab in the left side panel.
Check out to learn more about working with hosted databases.
You can also 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.
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 click Enter:
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.
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.
Note that restoring your app to a previous version doesn't use any tokens.
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.
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.
Let's change the name of the company in the sidebar.
Click the Code tab at the top.
Use the Search bar to look for the component you need to update (in our case, sidebar).
Locate the 'Acme corporation' text and change it to 'UI Bakery'.
Go back to the Preview tab to see the changes applied.
This action also doesn't use up any tokens.
Now it's time to publish your app!
Click the Release button in the top right corner of the screen. Select your version, add a description if you want, and choose the environments you want to deploy to.
You can also share the app with specific users or make it public from the Share button popup.
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 . Our team is always willing to help 🤓.
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.
Server actions are subject to Scheduled Jobs/Webhooks & Server Actions limits. For more details, please refer to the .
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.
Click on the plus sign in the Actions panel and select Server action.
Next, select the JavaScript Code step.
Add a Text input component to the page and set its value using the Params settings object of the server action:
Update the JavaScript code step to return the newly configured parameters:
Enter some number in the input form - execute the action and check the result.
Now, add another JavaScript code step to your action to multiply the value of the input:
Execute the action and check the result.
Next, add a button to your page that will execute the action and a Text component to display the result.
For the button, select your server action as the On Click trigger.
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.
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.
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 👇
To use images from Google Drive in your table, you need to convert the link to them following this pattern:
Let's review the step-by-step instruction here:
Click Share next to the image you want to use in your table.
In the window that opens, set the access to Anyone with the link and copy the link. The link will look like this:
Next, copy the file id from the obtained link and paste it in the required pattern to get the resulting link like this:
In the Table component, add the link from step 3 to the necessary field.
Now, change the field type from Link to Image.
That's it! Your image should now be displayed in the Table.
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:
To display PDF files from Google Drive, you need to convert the link to them following this pattern:
Let's review the step-by-step instruction how to do that:
Click Share next to the file you want to use in your table.
In the window that opens, set the access to Anyone with the link and copy the link. The link will look like this:
Next, copy the file id from the obtained link and paste it in the required pattern to get the resulting link like this:
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.
Here in the action, turn on the Transfrom result toggle, modify action result with return {{data.base64}}, and execute the action.
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.
In order to display PDF files from Dropbox, you also need to modify their links. Check out the step-by-step instruction below👇
Click Share next to the file you want to display and copy its link. The link will look like this:
Now, replace dropbox.com with dl.dropboxusercontent.com to get the resulting link like this:
Add the PDF Viewer component to the working area and copy the resulting link in the component's Link to PDF field.
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.
Add the File picker component to the working area.
In component's settings, select the Parse file checkbox to allow the system to access file content.
Next, select the file you want to upload in the File picker.
Now, add the Table component and reference File picker using {{ui.filepicker.parsedValue}}.
Click
Done! Your CSV file content will now be displayed in the Table.
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.
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.
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:
Create a new action that will consist of two steps:
First step - HTTP Request action where you need to specify your URL (it should return an array of objects).
Second step - Generate File action where you need to reference the result of the previous step as {{data}} variable in the Generate from field.
Your exported file will be automatically downloaded to your default Downloads folder.
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👇
To export data to .txt:
To generate JSON file from previous data:
For multipart requests:
UI Bakery allows you to generate PDF files from your Tables for reporting and other purposes. Let's review how you can do that.
Navigate to the Custom code tab and specify the following script:
Load the data you need and add a table to display it. (As an example, we'll use the table with user data.)
Create a new action of the JavaScript Code type and add the following code:
Please note that this code is exemplary and needs to be changed based on your table and necessary file structure.
Next, add the Button component to your working area that will download the generated file.
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.
You can also use the following code in your JavaScript Code action to download a PDF file:
After running the action, the file will be downloaded automatically to your default Downloads folder.
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.
Add the File picker component to your working area.
Next, connect the in the Custom Code tab using the following script:
Create a JavaScript Code action and specify the following code:
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.
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.
Here, we talk about Date & Time, Date picker, and Date & Time picker components.
In this article, we'll explore in more details some additional options that will allow you to manage time zones in your app:
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:
Or as an IANA time zone name:
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:
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.
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.
This is the current date set as the default value for a Date picker component:
Alternatively, you can set it to the start of the month:
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.
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.
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:
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:
After executing the action, you'll see time sent as it is displayed in the browser.
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:
Setting +00 timezone on a component and using .utcOffset(0, true) will result in an additional negative time zone offset.
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.
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.
Examples of usage:
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.
Start with adding a Text input component to the working area.
Next, create a Save to Local Storage action, and specify the variable name and value.
Assign this action to the OnChange trigger of the Text input component.
Finally, assign the
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;
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 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,
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,
};
});52.176.109.125
20.52.252.203if (data === 'Toyota') {
return ['Highlander', 'Prius', 'Celica']
}
if (data === 'Ford') {
return ['Falcon', 'Fusion']
}
if (data === 'Mercedes-Benz') {
return ['SLS-Class', 'CLS-Class', 'CL-Class']
}{{ui.input.value}} // current input value{{ui.form.value}} // { name: 'John', age: 30 }{{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 {}const user = await {{ actions.getCurrentUser.trigger() }};const user = await {{ actions.getUser.trigger('[email protected]') }};const orderId = {{data}};
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,
};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);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;state.varName = 'newValue';
// or using setValue method
state.setValue('varName', 'newValue');state.varName;
// or using getValue method
state.getValue('varName');state.resetValue('varName');// reset all variables, page and app state
state.resetValues();
// reset all page variables
state.resetValues('page');
// reset all app (global) variables
state.resetValues('app');const content = 'Hello, world!';
const mimeType = 'text/plain';
return new Blob([content], { type: mimeType });{ date: {{data}}, filename: 'hello_world.txt' }const file = {{ui.fileInput.value[0]}};
const formData = new FormData();
formData.append('upload', file);
fetch('url', { method: "POST", body: formData })npm install -g json-server{
"posts": [
{ "id": 1, "title": "json-server", "author": "typicode" }
],
"comments": [
{ "id": 1, "body": "some comment", "postId": 1 }
],
"profile": { "name": "typicode" }
}json-server --watch db.jsonconst 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);
});
{
allFilms: Object { films: Array[6] }
}return {{data}}.allFilms.films;[
{
id: 25,
price: 1000,
user: {
name: 'John',
email: '[email protected]'
}
}
][
{
id: 25,
price: 1000,
name: 'John',
email: '[email protected]'
}
]return {{data}}.map(item => {
return {
id: item.id,
price: item.price,
name: item.user.name,
email: item.user.email
};
});return {{data}}.map(item => {
return {
...item,
...item.user
};
});return {{data[0]}};JSON.parse(data['your_item'])const yourItem = data['your_item'] ? JSON.parse(data['your_item']) : {};const yourItem = {};
try {
yourItem = JSON.parse(data['your_item']);
} catch (e) {}
if (!{{state.allowRowEdit}}) {
throw new Error ('Row editing is not allowed')
}hotkeys('E', function(event, handler){
event.preventDefault();
alert('E');
});hotkeys('ctrl+a,ctrl+b,r,f', async function (event, handler){
switch (handler.key) {
case 'ctrl+a':
await actions.hotkeyA.trigger();
break;
case 'ctrl+b':
await actions.hotkeyB.trigger();
break;
case 'r':
alert('you pressed r!');
break;
case 'f':
alert('you pressed f!');
break;
}
});<style>
.no-shadow nb-card {
box-shadow: none;
}
.background-new nb-card-body {
background: aquamarine;
}
</style>https://drive.google.com/thumbnail?id=FILE_ID








// 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}}
}<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><style>
nb-layout .layout {
background: yellow;
}
</style><style>
.blue nb-card {
background: lightblue!important;
}
.green nb-card {
background: lightgreen!important;
}
</style>{
'blue': {{index % 2 == 0}},
'green': {{index % 2 == 1}},
}Page view
Delete
Reset failed login attempts
MFA settings updated






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
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
Load and send data between UI components and client-side logic
The secure operation code must remain inaccessible to users at all times
Actions with navigations and redirects
Extensive data transfer between steps
Calling component methods or accessing document/window objects
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
Click Execute action.

localStorage{{localStorage.getItem('draft')}}getItem(key)
Retrieves the value associated with the specified key.
async setItem(key, value)
Adds key's value or updates key's value if it already exists. Method throws an error when size quota is exceeded.
async removeItem(key)
Removes the key-value pair with the specified key.
async clear()
Clears all key-value pairs stored in localStorage.
You can configure navigation both to an internal application page as well as to an external website.
You can configure navigation using the Menu and Horizontal menu components.
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.
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.
You can also add an item to your menu that will redirect users to an external link.
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.
Check out the pages below to learn more👇
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👇
As a prerequisite, you already have a Table and Button components added to your working area.
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).
Add a Detail component on this page to display customer details.
Now, go back to the Home page, select the Button component and set the Customer page as the URL in the right side panel.
Next, in the Query params property, switch to JS mode and configure the primary key of the object:
In our example, the currently selected customer id will be sent as an id query parameter to the Customer page.
Next, create a new action of the Load Row type for your current Table.
In the Filters section, for customer_id specify the following variable to receive the URL query parameter on the child page:
{{activeRoute.queryParams.id}}
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.
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.
Add a Button component to your working area.
Create a JavaScript Code action and specify the following code adding your external link:
or the following code to open the link in a new tab:
Assign this action to the button's On Click trigger.
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.
Select your Menu component and switch to JS mode in the Items property.
Now, modify this property and add a new item that will redirect to an external page:
Next, navigate to the Triggers section of the component and create a new action for the On Item Click trigger.
Select a JavaScript Code action step and add the following code:
Now, when clicking the new menu item, the external link will open in a new tab.
(Optional) If you want the external link to open in the same tab, you need to add the '_parent' parameter to the following function:
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.
A newly created UI Bakery app or an app that is already developed can be connected to a Git repository.
Create a new app or open an existing one.
Navigate to your Git provider and create a new repository. The repository MUST be empty.
In UI Bakery, click the Connect Git button in the top left corner.
Copy the SSH repository URL (for example, [email protected]:user/fictional-octo-happiness.git) and paste it to the Git repository URL field.
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:
Open your repository settings and open the Deploy keys tab.
Here, click Add deploy key and paste the SSH key you've copied before.
Select the
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.
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.
A typical UI Bakery application is broken down into files system structure upon pushing to the Git repository.
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
In the upper right corner of the dashboard, you can switch between environments - dev, staging or prod. The Share button 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.
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.
Let's have a look at the Builder interface👇
The left side panel is resizable - you can adjust its size to suit your workflow and build more comfortably.
To implement it, follow the steps below:
First, create a new theme for the Dark mode.
Go back to this article if you need a reminder how to create and customize a theme.
Next, add a Select component to the working area and specify the following settings in the right side panel:
For the Options field - {{app.themes}}
For the Option title - select name
For the Option value - select id
Create a changeTheme action that will consist of two steps:
1st step - JavaScript Code action with the {{app.setTheme(data)}} return {{data}}; code
2nd step - Save to Local Storage action with the userTheme variable (set its value as {{data}})
Assign the changeTheme action to the Select component's On Change trigger.
In the Select component's Value field, specify {{localStorage.userTheme || 'DEFAULT_THEME'}} , where 'DEFAULT_THEME' is the id of the Light theme (pre-built).
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'
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.
To implement it, follow the steps below:
First, create a new theme for the Dark mode.
Go back to this article if you need a reminder how to create and customize a theme.
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).
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:
1st step - Save to Local Storage action with the theme variable (set its value as {{data}})
2nd step - JavaScript Code action with the {{app.setTheme(data)}}; code
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.
Open the App state tab in the left side panel, and find the themes' variable under the app tab.
Copy the id of the Light theme (for example, 'DEFAULT THEME') and paste it in the Action Arguments field.
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.
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).
(Optional) Configure the color of the icon to be changed when used:
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.
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.
























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.
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.
In this article, we'll explore two types of server-side pagination:
To implement page-based server-side pagination, follow the steps below:
For the Table component, select the Server side pagination checkbox in the right side panel.
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:
Now, assign this action to the On Page Change trigger of the Table to ensure the data is reloaded with each table page navigation.
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.
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:
For the Table component, select the Server side pagination checkbox in the right side panel.
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.
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.
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.
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:
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.
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:
And don't forget to assign the action with the filter variable to the On Filters Change trigger.
To enable sorting, use the {{ui.table.sortColumn}} and {{ui.table.sortDirection}} variables in your action.
By default, dynamic sorting doesn't work as the Convert SQL queries to prepared statements . You need to turn this setting OFF to allow dynamic sorting. But be cautious - disabling this setting can cause SQL injection!
After you disable the Prepared statement setting, your SQL Query will look like this:
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".
And don't forget to assign the action with the sorting variables to the On Sort Change trigger.
If everything is configured properly, but the action does not return the expected values, check the following:
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.
Here, we talk about Text, Text input, Form, and Detail components.
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.
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.
You can configure default validators for Form/Detail components' fields. To do so, follow the instruction below:
Select your component and click on the field you want to add validation to.
Expand the Edit settings section and scroll to the list of validators.
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.
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 input value is empty, the validator will display the following error:
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.
Select the Text input component and navigate to the Validation section.
In the Custom validators dropdown, click Create action.
Select a JavaScript Code action type and specify the following code to modify the validator condition:
Also, you can add a Text component and specify for it the following variables to check input status programmatically:
{{ui.input.valid}} - indicates whether an input is valid
{{ui.input.validating}} - indicates if a validator is currently running
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.
Select the Text input component and navigate to the Validation section.
In the Custom validators dropdown, click Create action.
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}}
And for the second step, add a JavaScript Code action type.
Specify the following condition to verify the presence of a user from the API response:
If the user list is not empty, the error message that you specified will be displayed.
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:
setErrorsYou 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.
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:
Select your Form component and navigate to the Appearance section.
Here, select the Show error message checkbox and add your error message (for example, 'Id field is not valid').
Next, assign a new action for the On Submit trigger.
Select a JavaScript Code
Next, click the Submit button to submit the form.
{
filter: ''
}WHERE users.first_name like {{ '%' + params.filter + '%' }}{
filter: 'sammy'
}{
value: {{ui.input.value}},
}return {{params}};return {{data.value}} * 3;// 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;// 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);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" };// 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();{
id: {{ui.customersTable.selectedRow.data.customer_id}}
}const a = document.createElement('a');
a.href = 'https://google.com';
a.target = '_top';
a.click();window.open('https://google.com');
return 1;{
title: 'Documents',
link: '/docs'
}if (data.link === '/docs') {
window.open('https://docs.uibakery.io/');
}
return {{data}}window.open('https://docs.uibakery.io/', '_parent')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.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.https://drive.google.com/uc?id=FILE_ID<script src="https://unpkg.com/[email protected]/dist/jspdf.umd.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/jspdf.plugin.autotable.js"></script>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');// 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); <script src="https://printjs-4de6.kxcdn.com/print.min.js"></script>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
});// Timezone is any valid UTC offset
'+01', '+01:00', '+0100', '-01', '-01:00', '-0100'// IANA format timezone
'America/Los_Angeles', 'America/New_York', 'Europe/Berlin'moment().startOf('day');moment().startOf('month');{
createdAt: {{moment(ui.datePicker.value).utcOffset(0, true).toISOString()}}
}{
createdAt: {{moment(ui.datePicker.value).toISOString()}}
}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.
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
git revert #{commit_hash}
git pushUI Bakery: — Pull branch changes from Git — Release your app
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.

nullDuring validation the input is considered invalid
Validation is complete once all assigned validators have been executed








Available on all plans, both for cloud and on-premise instances in the Low-code mode.
UI Bakery offers a large number of built-in 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.
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.
Give your component a name and proceed to the Builder mode to start generating the 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👇
a. This is where you need to type in your prompt or attach an image of what you want to generate.
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.
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.
Custom component can only call actions created within the component itself.
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.
h. Click Release to .
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 .
Every month, free tokens are assigned to users - you can use them to generate custom components. Each time you make a request, tokens will be deducted from the balance taking into account the input, output, and cached input ratios.
The actual tokens balance is displayed in the bottom left corner of the chatbox in the Builder mode.
This way, you'll be able to see how many tokens 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 Not enough credits on your account error will be displayed if you try making requests, and either from there or from the tokens balance in the chatbox, you can click Buy. You'll be redirected to the billing page where you can buy more tokens. It's possible to buy 10M tokens in a single transaction.
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.
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.
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].
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).
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.
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.
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.
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.
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.
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, lodash, and i18next. 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.
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:
Or to get deep value from an object:
We'll dive into more details about these libraries as well as i18next in the following sections.
To use in your app, you don't need to configure it additionally as it comes pre-built. Simply follow the steps below:
In your app, create a new action of the JavaScript Code type and specify the following code:
Run the action and check the Result tab - you will see the actual date displayed.
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👇
Navigate to the Custom code tab and specify there the script tags of moment.js and the timezone plugin:
Now, create a JavaScript Code action and specify the following code requiring both of the libraries:
Run the action and check the Result tab.
To use 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:
also comes pre-built so no additional configurations are required. You can use this library to add translations to your application.
Check out this example of utilizing i18next in your app👇
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.
Here we'll use the as an example - upon successful Form submission, users will get a confetti blast.
Follow the instruction below to learn how to do that:
Start by locating the library you need on a public JavaScript CDN, for example, .
Click on the library to open it and copy the provided JavaScript tag:
We'll be using library as an example.
Now, navigate to the Custom code tab in the Builder's footer and paste the copied tag there.
Add a Form component to the working area.
Next, create a new action consisting of two steps:
For the first step, add a Create Row
Finally, navigate to the Triggers section of the Form and assign your newly created action to the On Submit trigger.
Submit your changes and enjoy the confetti! 🎉
SELECT * FROM users LIMIT {{ui.table.pageSize}} OFFSET {{ui.table.paginationOffset}};const param = {
limit: {{ui.table.pageSize}},
};
if ({{ui.table.afterCursor}}) {
param.starting_after = {{ui.table.afterCursor}};
}
return param;SELECT COUNT(*) AS AMOUNT FROM orders;select
*
from
users
where
users.name like CONCAT ('%', {{ ui.table.filters.name}}, '%')
limit
{{ui.table.pageSize}}
offset
{{ui.table.paginationOffset}}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}}return {{params}} ? null : 'Field is required';return {{params}} ? null : 'Field is required';return {{data.length > 0}} ? 'Email is already taken' : null;return {{params.id}} ? null : { id: 'Field is required', name: 'Field is required' };if ({{ui.form.value.id}} < 100) {
throw new Error();
}






For example, when using React:
🟢 You would want to use the file located at https://cdn.jsdelivr.net/npm/[email protected]/umd/react.production.min.js
🔴 Instead of https://cdn.jsdelivr.net/npm/[email protected]/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 "require is not a function" or "module is undefined" 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/[email protected]/+esm'
🔴 UMD generated by jsdelivr will not work - https://cdn.jsdelivr.net/npm/[email protected]/lib/util/printValue.min.js
For the second step, add a JavaScript Code action and specify the following code:


In this example, we will add the possibility to switch to German in the app. Check out the instruction below👇
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.
Navigate to the App triggers section in the right side panel and create a new action for the On App Load trigger.
Select the JavaScript Code type and specify the following code to initialize all the translations using i18n.init({...}):
The default interpolation in UI Bakery is { prefix: '{', suffix: '}' }. Therefore, you should use single curly braces to insert your values into the string.
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.
Add a Select component to the working area and add the languages you need to the Options field, for example:
window.i18next.t is now i18n.t.
In the component's Value field, specify {{i18n.language}}.
Next, navigate to the Select component's Triggers section and create a new action for the On Change trigger.
Select the JavaScript Code step and specify the following code:
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.
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.
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.
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' });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', {});import { triggerEvent } from '@uibakery/data';
// Trigger an event about product creation
triggerEvent({ type: 'productCreated', data: { productId: 123 } });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
}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')
};
});return _.get({ data }, 'birth.place.country', 'n/a');return {{ moment().format("dddd, MMMM Do YYYY, h:mm:ss a") }};<script src="https://cdn.jsdelivr.net/npm/[email protected]/moment.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/moment-timezone.min.js"></script>var moment = require('moment'); require('moment-timezone');
const now = moment();
return now.tz('America/Los_Angeles').format('ha z')const data = {{ ui.table.selectedRow.data }};
return _.filter(
{{ actions.list2.data }},
_.matches({ 'userId': data.id, 'taskType': data.taskType }),
);<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js"></script>confetti({
particleCount: 500,
spread: 300,
origin: { y: 0.6 }
});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',
},
},
},
},
});[
{
"value": "en",
"title": {{i18n.t('english')}}
},
{
"value": "de",
"title": {{i18n.t('german')}}
}
]app.setLocale(data)


The feature is deprecated, please refer to this article for information on building custom components in UI Bakery.
UI Bakery offers a large number of built-in components that you can choose from. Check out our Reference 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.
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.
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 to render any HTML or JavaScript without any restrictions - they are not rendered inside of an iframe.
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.
Here is an example of a custom component based on React:
Since the custom component is rendered inside an iframe there are no specific limitations to the code and styles specified by the developer.
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:
Additionally, you can also pass data using JS API in your actions:
To access this data within the custom component, you can use:
You can also subscribe to updates of the data using:
If your custom component produces events or needs to trigger an action, you can use the following code:
Use this code inside the component to set its value. Once executed, the new value will be available as {{ui.customComponent.value}}.
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.
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.
Copy and paste the whole example into the custom component Code field:
Copy and paste the whole example into the custom component Code field:
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:
In this section, we will create a custom calendar to display appointments:
Start by loading your data - create a JavaScript Code action step and add your data in the following format:
Next, add a Custom Component to your working area.
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:
And voilà! Your calendar is ready now.
You can connect and use the 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:
<!-- 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>{
data: [1,2,3],
display: 'only_new',
}ui.customComponent.setData({ ... })const data = UB.useData()UB.onData(data => {
console.log('new data', data);
});UB.updateValue('Data from custom component');UB.triggerEvent('Data from custom component');<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><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>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,
},
];<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/main.min.css">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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><!-- 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>
