--- url: https://docs.lowdefy.com/md/learn/introduction.md --- # Introduction $ pnpx lowdefy@5 init && pnpx lowdefy@5 dev Built with four primitives The UI. 114 components you compose into pages. The logic. 70 functions evaluated live in config. The behavior. Respond to clicks, loads, and changes. The data. Databases, APIs, and AI, server-side. What you can build Start here One command to a running app, one edit to make it yours. Build a complete app — pages, requests, data, and deployment. The mental model — four primitives and one state loop. Batteries included, ecosystem attached --- url: https://docs.lowdefy.com/md/tutorial/tutorial-start.md --- # 1. Getting started In this tutorial, we will be creating a simple ticketing app that allows users to file new tickets and see a list of outstanding tickets. The app reads product data from [DummyJSON](https://dummyjson.com/) and will write the ticket data to an [SQLite](https://www.sqlite.org/) database. ### Requirements The Lowdefy CLI (Command Line Interface) is needed to run the development server. To run the Lowdefy CLI you need to install Node.js at version 24 or greater. If you don't have it installed, download Node.js from https://nodejs.org/en/download/, and follow the installation steps for your computer. All of the default settings given by the installer are fine for what we need. You will also need a text editor to modify the Lowdefy configuration files. Lowdefy also requires [`pnpm`](https://pnpm.io/) to be installed. We recommend making activating [Corepack](https://nodejs.org/api/corepack.html), even though it is an experimental feature, as this is the simplest way to install `pnpm`. Alternative methods to install `pnpm` can also be found [here](https://pnpm.io/installation). To activate Corepack, run the following in your computer's command line interface (Windows CMD, Terminal on MacOS). $ corepack enable ##### YAML files If you don't have any experience using YAML, please familiarize yourself YAML before continuing. [This video is a good introduction to YAML.](https://www.youtube.com/embed/cdLNKUoMc6c?origin=https://docs.lowdefy.com) Lowdefy apps are written using YAML files. YAML files are useful for storing structured data, like the configuration of all of the elements of your app. YAML files focus on being easily readable by humans, this means they don't use lots of syntactic elements like brackets that make it difficult for humans to read, but instead use __indentation to indicate structure__. While this does make the file easier to read, this means care has to be taken that the data structure is as you intended. > Lowdefy apps can also be defined using JSON files, or a mix of YAML and JSON. We find YAML files more convenient to write. #### 1.1. Create a project directory Create a directory (folder) on your computer where you would like to create the configuration files for your project. We will be referring to this directory as the project directory. #### 1.2. Open a command line interface Open your computer's command line interface and change directory (`cd`) to the project directory. #### 1.3. Initialize an app Use the Lowdefy CLI to initialize your project. We recommend using [`pnpx`](https://pnpm.io/6.x/pnpx-cli) to run the Lowdefy CLI, since this will always run the latest version of the CLI. Run the following in your terminal: $ pnpx lowdefy@5 init This will create two files in your current working directory. The first file, called `lowdefy.yaml` is the starting point of your app's configuration. The second, called `.gitignore`, is a hidden file that tells `git`, a version control tool, not to version or upload some specific files. > __Error: A "lowdefy.yaml" file already exists__: Try running the command in a new directory or consider deleting the _lowdefy.yaml_ file in your current working directory. #### 1.4. Start the development server Run: $ pnpx lowdefy@5 dev Your browser should open on http://localhost:3000, and you should see the following: Made by a Lowdefy 🤖 > __Error: Could not find "lowdefy.yaml"__: Make sure your current working directory contains the _lowdefy.yaml_ file. You can verify this by running the `dir` (Windows) or `ls` (MacOS) command. #### 1.5. Open the configuration file Open the `lowdefy.yaml` file using a text editor like [VS Code](https://code.visualstudio.com/download). The content of the file should look like this: ```yaml lowdefy: 7.1.0 name: Lowdefy starter pages: - id: welcome type: PageHeaderMenu properties: title: Welcome slots: content: justify: center blocks: - id: content_card type: Card style: maxWidth: 800 blocks: - id: content type: Result properties: title: Welcome to your Lowdefy app subTitle: We are excited to see what you are going to build icon: name: heart color: '#f00' slots: extra: blocks: - id: docs_button type: Button properties: size: large title: Let's build something events: onClick: - id: link_to_docs type: Link params: url: https://docs.lowdefy.com newWindow: true footer: blocks: - id: footer type: Paragraph properties: type: secondary content: | Made by a Lowdefy 🤖 ``` This configuration completely describes the app you are running. #### 1.6. Make some changes Let's modify the button to link to the Lowdefy Discord community instead of to the Lowdefy docs. First, change `Let's build something` to `Join Lowdefy on Discord`, then save the file. Your page should automatically refresh and you should see the changed text of the button. Next, update the link url to the Lowdefy Discord link, replace `https://docs.lowdefy.com` with [`https://discord.gg/QQY9eJ7A2D`](https://discord.gg/QQY9eJ7A2D), then save the file. If you now click the button it should open a new window linking to the Lowdefy Discord Channel - Join our community to ask questions or learn more regarding Lowdefy ❤️ ### What happened The Lowdefy CLI helps you develop a Lowdefy app. We used the `pnpx lowdefy@5 init` command to initialize a new project. This created all the essential files. We also used the `pnpx lowdefy@5 dev` command to start a development server. The development server runs a Lowdefy app locally on your computer, which can be accessed at http://localhost:3000. The development server watches your configuration files, and if any of them changes it "builds" (compiles the configuration together for the server to serve) the configuration again and refreshes the browser to show the changes. ### Up next Let's see how easy it is to create a new page. --- url: https://docs.lowdefy.com/md/tutorial/tutorial-create-page.md --- # 2. Creating a page Let's create a page for a web form where users can log a new ticket. #### 2.1. Create `new-ticket.yaml` Create a new YAML file in the project directory (the same directory as the `lowdefy.yaml` file) called `new-ticket.yaml`, with the following content: ```yaml id: new-ticket type: PageHeaderMenu properties: title: New ticket # The title in the browser tab. layout: justify: center # Center the contents of the page. blocks: - id: content_card type: Card layout: size: 800 # Set the size of the card so it does not fill the full screen. gap: 16 # Make a 16px gap between all blocks in this card. blocks: - id: page_heading type: Title properties: content: Log a ticket # Change the title on the page. level: 3 # Make the title a little smaller (an html `

`). ``` #### 2.2. Update `lowdefy.yaml` Change the `lowdefy.yaml` to look like this: ```yaml name: lowdefy-project-template lowdefy: 7.1.0 pages: ################ -------- Copy from here -------- ################ - _ref: new-ticket.yaml ################ -------- Copy to here ---------- ################ - id: welcome type: PageHeaderMenu properties: title: Welcome # ... ``` > Indentation is important #### 2.3. Navigate to the new page Your browser should reload, and you should see a link in the header menu to the booking page. If you click on that link it should take you to a page that looks like this: Log a ticket If you click on the link in the menu, you should see that your browser path (the part after `localhost:3000` or `example.com`) changes from `welcome` to `new-ticket`. ### What happened - We created a new page with id `new-ticket`. - We used the [`_ref` operator](/_ref) to reference configuration in another file. - That page can now be found at the `/new-ticket` route. - A link to that page was created in the menu. These links are in the order of the pages. ## Menus A menu is created by default from all the pages in your app, in the order that they appear in the pages array. Often more control is needed over the menu. You might want to group menu items into different groups, change the title, exclude a page or add an icon. To do this, we can define a menu in the `menus` section of the `lowdefy.yaml` file. #### 2.4 - Add the menu configuration Copy the following and add it to your `lowdefy.yaml` file just before the pages section: ```yaml ################ -------- Copy from here -------- ################ menus: - id: default links: - id: new-ticket type: MenuLink properties: icon: Siren title: New ticket pageId: new-ticket - id: welcome type: MenuLink properties: icon: home title: Home pageId: welcome ################ -------- Copy to here ---------- ################ pages: - _ref: new-ticket.yaml # ... ``` The menu links will now have icons and titles. > If you would like to see how your config should look at this point, you can find it [here](https://github.com/lowdefy/lowdefy-example-tutorial/tree/main/02-creating-a-page). ### Up next In the next section we will add some more blocks to our page to create a form to capture the ticket data. --- url: https://docs.lowdefy.com/md/tutorial/tutorial-add-blocks.md --- # 3. Adding blocks We would like to add an input form with the following fields: - A title for the ticket. - The type of ticket. This should be a selection from a list of types. - A description of the ticket. There should also be submit and reset buttons at the bottom of the page. #### 3.1. Add some input blocks Copy the following blocks and add them in the card's blocks array. ###### `new-ticket.yaml` ```yaml id: new-ticket # ... blocks: - id: content_card # ... blocks: - id: page_heading type: Title properties: content: Log a ticket # Change the title on the page. level: 3 # Make the title a little smaller (an html `

`). ################ ------- Copy from here -------- ################ - id: ticket_title type: TextInput properties: title: Title - id: ticket_type type: ButtonSelector properties: title: Ticket type options: # Set the allowed options - Suggestion - Complaint - Question - id: ticket_description type: TextArea properties: title: Description - id: reset_button type: Button layout: span: 12 # Set the size of the button (span 12 of 24 columns) properties: title: Reset block: true # Make the button fill all the space available to it type: default # Make the button a plain button icon: Eraser - id: submit_button type: Button layout: span: 12 properties: title: Submit block: true type: primary # Make the button a primary button with color icon: save ################ -------- Copy to here ---------- ################ ``` Your new-ticket page should look something like this: Log a ticket > If you would like to see how your config should look at this point, you can find it [here](https://github.com/lowdefy/lowdefy-example-tutorial/tree/main/03-adding-blocks). ### What happened We added 3 form input blocks to the page. For each of those blocks, we set the `title` property, and for the ticket type selector we set a list of ticket types to the `options` property. We also added reset button and submit button. We set a few more properties on the buttons to set their layout and appearance. ### How it works Lowdefy apps are made from blocks. These blocks can be the page layout with header and menu, a piece of text, a chart or table, tabs or even a popup message or icon. You specify which block is rendered with the `type` field. There are 4 block categories, namely display, input, container, and list. All blocks need to have an `id` that identifies the block. Blocks of category input and list use this `id` as the object key to represent a value in page state, but more on this later. ### Up next Currently our form doesn't do very much. In the next section we will use [actions](/events-and-actions) and [operators](/operators) to make it more interactive. --- url: https://docs.lowdefy.com/md/tutorial/tutorial-actions-operators.md --- # 4. Interactive pages with actions and operators If you have been following along, you can continue with your current config. Else, you can find the config from the previous section [here](https://github.com/lowdefy/lowdefy-example-tutorial/tree/main/03-adding-blocks). ## Configuring the reset button The reset button should reset all the user's inputs. To do this, we can add a [Reset action](/Reset) to the reset button. #### 4.1. Configure the reset button Copy the following into the definition of the reset button: ##### `new-ticket.yaml` ```yaml id: new-ticket # ... blocks: - id: content_card # ... blocks: - id: page_heading # ... # ... # ... - id: reset_button type: Button layout: span: 12 properties: title: Reset block: true icon: Eraser ################ -------- Copy from here -------- ################ events: onClick: - id: reset type: Reset ################ -------- Copy to here ---------- ################ - id: submit_button type: Button # ... ``` If you add some inputs into the form and click the reset button, those inputs should be cleared. ### How it works Blocks can define events, which they can trigger when certain events happen on the page. You can then define a array of actions, that are executed sequentially when a block triggers an event. The button triggers an event called `onClick` when it gets clicked. We can add an array of action definitions that will be executed when this action is fired. We only added one action, the [`Reset` action](/Reset), that resets the page state as it was when it first loaded. ## Required fields We should validate the form to make sure the user input data is clean for the app to work as expected. The `ticket_title` and `ticket_type` fields should be compulsory, and the user should not be able to submit the ticket if they are not completed. #### 4.2. Mark fields as required To make the fields required, add a required property to the input blocks like this: ```yaml - id: ticket_title type: TextInput required: true properties: title: Title ``` A red asterisk should appear next to each input field, looking like this: We can now add a [`Validate` action](/Validate) to the submit button. This will validate the inputs and give an error if any inputs are not filled in. #### 4.3. Add a Validate action Add the validate action like this: ##### `new-ticket.yaml` ```yaml id: new-ticket # ... blocks: - id: content_card # ... blocks: - id: page_heading # ... # ... # ... - id: submit_button type: Button layout: span: 12 properties: title: Submit block: true color: primary variant: solid icon: save ################ -------- Copy from here -------- ################ events: onClick: - id: validate type: Validate ################ -------- Copy to here ---------- ################ ``` Now if we click the submit button and all our inputs aren't complete, a message pops up saying we have validation errors on the page. All the fields that have not been completed are highlighted in red. The result should look and work like this. The examples in this tutorial are live versions of the tutorial app, so you can verify that they work like your own app. Log a ticket ### How it works The [Validate action](/Validate) we added to the submit button evaluates all the input blocks on the page, and gives an error if any of them fail the validation. This will also stop the execution of any actions after that action. For example, the app won't insert the data into our database if a Request action is used after a failing Validate action. The first time a Validate action is called on a page it sets a flag that tells all the input blocks to show their validation errors. ## Hiding blocks We would like to ask the user if they had purchased the product within the last month, for which they are formulating a complaint. To do this, we will add a input block with a `visible` property. This property expects a boolean value. In Lowdefy, we can make use of [operators](/operators) to evaluate logic. We'll use the [`_eq`](/_eq) operator to test if the ticket type is equal to `Complaint`. #### 4.4. Add a block with a visible condition Add the following [`ButtonSelector`](/ButtonSelector) to your app. ##### `pages/new-ticket.yaml` ```yaml id: new-ticket # ... blocks: - id: content_card # ... blocks: # ... - id: ticket_description type: TextArea properties: title: Description ################ -------- Copy from here -------- ################ - id: purchase_in_last_month type: ButtonSelector visible: # Test if block should be visible to the user _eq: # Equals operator - _state: ticket_type # Get the ticket_type value from state. - Complaint properties: title: Did you purchase this product within the last month? label: colon: false # Hide the label colon options: - Yes - No ################ -------- Copy to here ---------- ################ - id: reset_button type: Button ``` If you select the "Complaint" ticket type, the selector confirming if you purchased the product within the last month should appear. ### How it works If the visible field value is `false`, the block won't be rendered. To change this value while our app is running, we use an operator. [Operators](/operators) evaluate every time something in the app changes, like when a user provides an input. We used the [`_eq`](/_eq) operator to check the ticket type is equal to 'Complaint'. To get the value of the `ticket_type` input, we use the [`_state`](/_state) operator. This get a value from the page's [`state`](/page-and-app-state). The page `state` is an object to temporary store page data. All inputs automatically write their values to `state` under the object key of the block `id`, and you can also set values to the page `state` by using the [`SetState`](/SetState) action. ## Validation rules We would like to add a validation rule on the `purchase_in_last_month` block to warn users that the product that they have purchased may not be eligible for replacement. #### 4.5. Add a validation rule Add the following to your app ##### `pages/new-ticket.yaml` ```yaml id: new-ticket # ... blocks: - id: content_card # ... blocks: # ... - id: purchase_in_last_month type: ButtonSelector visible: # Test if block should be visible to the user _eq: # Equals operator - _state: ticket_type # Get the ticket_type value from state. - Complaint ################ ------- Copy from here -------- ################ validate: # Show a warning that shows before validate is called # and does not block Validate action. - status: warning message: If you have had the product for over a month, we may not be able to replace it. pass: _eq: - _state: confirm_restart - Yes ################ -------- Copy to here ---------- ################ properties: title: Did you purchase this product within the last month? label: colon: false # Hide the label colon options: - Yes - No ``` Your app should now show a warning message if `purchase_in_last_month` is not yes. ### How it works The `validate` field contains an array of validation rules that can be evaluated using operators. If the pass field receives `true`, the rule passes and nothing happens. If the rule fails and the status is error, the `Validate` action will fail, like it did for required fields. If the status is warning, a warning will be shown to the user, but the `Validate` action will still pass. Your final new ticket page should look and work like this: Log a ticket > You can find the final configuration files for this section [here](https://github.com/lowdefy/lowdefy-example-tutorial/tree/main/04-interactive-pages). ## Up next Our app doesn't save the form data anywhere when the submit button is clicked. In the next section we will set up a connection and a request to save the data in a local SQLite database. --- url: https://docs.lowdefy.com/md/tutorial/tutorial-requests-api.md --- # 5. API / HTTP requests If you have been following along, you can continue with your current config. Else, you can find the config from the previous section [here](https://github.com/lowdefy/lowdefy-example-tutorial/tree/main/04-interactive-pages). We will now add a `AxiosHttp` [connection and request](/connections-and-requests) to get the list of products that tickets can be logged for from an external API endpoint. ## Configuring the AxiosHttp Connection and Request We want to add a [`Selector`](/Selector) block to our form that will allow the user to select which product they are logging a ticket about, from a preset list of options. This list of preset product options will be obtained from the [DummyJSON](https://dummyjson.com/) API. For this, we will make use of an [`AxiosHttp`](/AxiosHttp) connection as described in the following steps: #### 5.1. Adding a new connection to the app In your `lowdefy.yaml` file, add the following: ##### `lowdefy.yaml` ```yaml name: lowdefy-project-template version: 7.1.0 ################ -------- Copy from here -------- ################ connections: - id: dummy_api type: AxiosHttp properties: baseURL: https://dummyjson.com/ ################ ------- Copy to here ----------- ################ menus: # ... ``` #### 5.2. Adding a request to the page In your `new-ticket.yaml` file, add the following request: ##### `pages/new-ticket.yaml` ```yaml id: new-ticket type: PageHeaderMenu ################ -------- Copy from here -------- ################ requests: - id: get_products type: AxiosHttp connectionId: dummy_api properties: url: /products ################ ------- Copy to here ----------- ################ properties: title: New ticket # The title in the browser tab. layout: justify: center # Center the contents of the page blocks: # ... ``` #### 5.3. Triggering the request on page load In your `new-ticket.yaml` file, add an onMount event that calls `get_products` request: ##### `pages/new-ticket.yaml` ```yaml requests: - id: get_products type: AxiosHttp connectionId: dummy_api properties: url: /products ################ -------- Copy from here -------- ################ events: onMount: - id: fetch_products type: Request params: get_products ################ ------- Copy to here ----------- ################ properties: title: New ticket # The title in the browser tab. layout: justify: center # Center the contents of the page blocks: # ... ``` #### 5.4. Using the request response as selector options In your `new-ticket.yaml` file, add the a Selector block and populate it with the results from the `get_products` request. We will make use of [`_array.map`](/_array#map_title) operator to get the product titles from the response obtained from the request. This is done as follows: ##### `pages/new-ticket.yaml` ```yaml id: new-ticket # ... blocks: - id: content_card # ... blocks: - id: page_heading type: Title properties: content: Log a ticket # Change the title on the page. level: 3 # Make the title a little smaller (an html `

`). - id: ticket_title type: TextInput properties: title: Title - id: ticket_type type: ButtonSelector properties: title: Ticket type options: # Set the allowed options - Suggestion - Complaint - Question ################ ------- Copy from here -------- ################ - id: product type: Selector required: true properties: title: Product options: # Map over API response to create an array of product titles _array.map: # use dot notation to access value in response object properties - _request: get_products.data.products - _function: __args: 0.title ################ ------- Copy to here -------- ################ - id: ticket_description type: TextArea properties: title: Description ``` ### What happened We defined the [`AxiosHttp`](/AxiosHttp) connection to be used to obtain the list of products that tickets can be created for. We also defined an [`AxiosHttp`](/AxiosHttp) request, to fetch the products data when the page is mounted. To do this, we used the onMount event to execute the Request action to make the API call. We then mapped the request response to the product selector options by using the [_request](/_request) and [_array.map](/_array#map_title) operators. > You can find the final configuration files for this section [here](https://github.com/lowdefy/lowdefy-example-tutorial/tree/main/05-requests-api). ### Up next Next we will save the form data to a SQLite database file. --- url: https://docs.lowdefy.com/md/tutorial/tutorial-requests-sql.md --- # 6. Saving data to SQL If you have been following along, you can continue with your current config. Else, you can find the config from the previous section [here](https://github.com/lowdefy/lowdefy-example-tutorial/tree/main/05-requests-api). We will now add a `SQLite` [connection and request](/connections-and-requests) to our app to save user tickets to the SQLite database file. ## Configuring the SQLite Connection and Request We will be saving the data from our form in a SQLite database, using the [`Knex`](/SQLite) connection. To do this, we will first need to set up an SQLite database as described in the following steps: #### 6.1. Setting up the SQLite database We will make use of an SQLite database with a table called `tickets`, that has the following columns: - ticket_title - ticket_type - ticket_description - product - purchase_in_last_month - created_date The database used in this tutorial can be downloaded here. Alternatively, install [SQLite](https://www.sqlite.org/) on your computer and use the following to create the needed table: ``` CREATE TABLE tickets ( ticket_title varchar(100), ticket_type varchar(50), ticket_description varchar(200), product varchar(100), purchase_in_last_month real, created_date real ); ``` #### 6.2. Setting app environment variables Create a file called `.env` in your project directory with the following content: ##### `.env` ``` LOWDEFY_SECRET_SQLITE_FILENAME= __ABSOLUTE_PATH_TO_SQLITE_DB_FILE__ ``` #### 5.2.3. Setting the connection environment variable secrets In your `lowdefy.yaml` file, add the following: ##### `lowdefy.yaml` ```yaml name: lowdefy-project-template version: 7.1.0 connections: # ... ################ -------- Copy from here -------- ################ - id: knex type: Knex properties: client: sqlite connection: filename: _secret: SQLITE_FILENAME ################ ------- Copy to here ----------- ################ menus: # ... ``` #### 6.3. Creating a save data request In your `new-ticket.yaml` file, add the following request: ##### `pages/new-ticket.yaml` ```yaml id: new-ticket type: PageHeaderMenu requests: # ... ################ -------- Copy from here -------- ################ - id: save_new_ticket type: KnexRaw connectionId: knex payload: _state: true properties: query: | INSERT INTO tickets ( ticket_title, ticket_type, ticket_description, product, purchase_in_last_month, created_date) VALUES( :ticket_title, :ticket_type, :ticket_description, :product, :purchase_in_last_month, :created_date) parameters: ticket_title: _payload: ticket_title ticket_type: _payload: ticket_type ticket_description: _payload: ticket_description product: _payload: product purchase_in_last_month: _payload: purchase_in_last_month created_date: _date: now ################ ------- Copy to here ----------- ################ properties: title: New ticket # The title in the browser tab. layout: justify: center # Center the contents of the page blocks: # ... ``` #### 6.4. Triggering the save data request In your `new-ticket.yaml` file, add the following action to the submit button: ##### `pages/new-ticket.yaml` ```yaml id: new-ticket # ... blocks: - id: content_card # ... blocks: - id: page_heading # ... # ... # ... - id: submit_button type: Button #... events: onClick: - id: validate type: Validate ################ -------- Copy from here -------- ################ - id: save_new_ticket # Make a request to the SQLite database type: Request params: save_new_ticket - id: reset # Reset the form once data has been submitted type: Reset ################ ------- Copy to here ----------- ################ ``` If you click the submit button, your form data should be added into the tickets table. ### What happened We set up the table and column names we will be using in our SQLite database. We need to do this to use the `SQLite` connection. We defined the `Knex` connection, with the SQLite client. We also defined a `KnexRaw` request, to save the data to our SQLite database, and called that request when clicking the submit button. This was done by making use of the `onCLick` event on the button to execute the [`Request`](/Request) action to make the API call. > You can find the final configuration files for this section [here](https://github.com/lowdefy/lowdefy-example-tutorial/tree/main/06-requests-sql). ### Up next Next, we will add a new page to display all open tickets an a table. --- url: https://docs.lowdefy.com/md/tutorial/tutorial-display-data-page.md --- # 7. Display ticket data If you have been following along, you can continue with your current config. Else, you can find the config from the previous section [here](https://github.com/lowdefy/lowdefy-example-tutorial/tree/main/06-requests-sql). In this section we will add a page with a table to view the tickets that have been saved to our database. We will make use of the concepts shown throughout the previous steps of this tutorial. If you would like to skip this, the final config can be found [here](https://github.com/lowdefy/lowdefy-example-tutorial/tree/main/07-display-data) and you can move on to [Next Steps](/tutorial-next-steps). #### 7.1. Create `view-tickets.yaml` Create a new YAML file in the project directory (the same directory as the `lowdefy.yaml` file) called `view-tickets.yaml`. In this file we will add an `AgGridBalham` table block, using [Ag-Grid](https://www.ag-grid.com/), that will be used to display the tickets that are currently in our SQLite table. We shall start with the basic page structure, as follows: ```yaml id: view-tickets type: PageHeaderMenu properties: title: View tickets # The title in the browser tab. layout: justify: center # Center the contents of the page. blocks: - id: content_card type: Card layout: size: 1000 # Set the size of the card so it does not fill the full screen. gap: 28 # Make a 28px gap between all blocks in this card. blocks: - id: page_heading type: Title properties: content: View Tickets # Change the title on the page. level: 3 # Make the title a little smaller (an html `

`). ``` #### 7.2. Add request to `view-tickets.yaml` Next we shall add a request to fetch the tickets currently in the tickets table in the SQLite database. To do this, we shall create a `fetch_tickets` request and call it when the page is mounted. ```yaml id: view-tickets type: PageHeaderMenu ################ -------- Copy from here -------- ################ requests: - id: fetch_tickets type: KnexRaw connectionId: knex properties: query: SELECT * FROM "tickets" events: onMount: - id: fetch_tickets type: Request params: fetch_tickets ################ -------- Copy to here -------- ################ properties: title: View tickets # The title in the browser tab. #... ``` ### 7.3. Add an `AgGridBalham` block We will add an `AgGridBalham` table block to our page to display the tickets that have been saved in the tickets table. We want to display five columns in our table; `ticket_title`, `created_date`, `ticket_type`, `product` and `ticket_description`. The `rowData` used for our table will be the result from our `fetch_tickets` request and is accessed by making use of the [_request](/_request) operator. In order to display the `created_date` in a more compact format, we will make use of the [_nunjucks](/_nunjucks) operator. Add the following config to the `view-tickets.yaml` filename: ```yaml #... blocks: - id: page_heading type: Title properties: content: View Tickets # Change the title on the page. level: 3 # Make the title a little smaller (an html `

`). ################ -------- Copy from here -------- ################ - id: view_tickets_table type: AgGridBalham properties: enableCellTextSelection: true pagination: true rowData: _request: fetch_tickets defaultColDef: sortable: true resizable: true filter: true columnDefs: - headerName: Title field: ticket_title width: 200 filter: false - headerName: Date Created field: created_date width: 140 cellRenderer: _function: __nunjucks: template: | {{ created_date | date('DD MMM YYYY') }} on: __args: 0.data - headerName: Ticket Type field: ticket_type width: 150 filter: false - headerName: Product field: product width: 200 filter: false - headerName: Ticket Description field: ticket_description width: 250 filter: false ################ -------- Copy to here -------- ################ ``` #### 7.4. Update `lowdefy.yaml` Last step is to add the page to our app. In order to do this, we need to update the `lowdefy.yaml`. Change the `lowdefy.yaml` to include `view-tickets.yaml` in the list of pages and the list of menu items: ```lowdefy.yaml #... menus: - id: default links: - id: welcome type: MenuLink properties: icon: home title: Home pageId: welcome - id: new-ticket type: MenuLink properties: icon: Siren title: New ticket pageId: new-ticket ################ -------- Copy from here -------- ################ - id: view-tickets type: MenuLink properties: icon: Database title: View tickets pageId: view-tickets ############### -------- Copy to here ---------- ################ pages: - _ref: new-ticket.yaml ################ -------- Copy from here -------- ################ - _ref: view-tickets.yaml ################ -------- Copy to here ---------- ################ - id: welcome type: PageHeaderMenu properties: title: Welcome # ... ``` ### What happened We added a new page in order to view the tickets saved in our database. We defined a `KnexRaw` request, to read the ticket data from our SQLite database, and called that request when the page was mounted. The data from this request was then displayed in an `AgGridBalham` table block, which we added to the new page. Lastly, we linked this new page to our app. --- url: https://docs.lowdefy.com/md/tutorial/tutorial-next-steps.md --- # Next steps Congratulations! You have completed the tutorial and built a basic functioning app, which can request data from an API, as well as validate and save user input to a database. Next, consider joining the Lowdefy community, and exploring Lowdefy in more detail. ### Join the community Whether Get help with any problems you have, share you ideas, ask questions, and show off the apps you have built, join and hlep us grow the Lowdefy community. - [Github Discussions](https://github.com/lowdefy/lowdefy/discussions) - [Lowdefy Discord](https://discord.gg/QQY9eJ7A2D) - [Lowdefy Twitter](https://twitter.com/lowdefy) ### Understand the concepts To get a more in-depth understanding of how everything works, you can start [here](/overview). ### Play with blocks The docs are interactive, so you can adjust a block's properties and see how that affects the block in real time. Go look at what you can do with a [`Button`](/Button). ### Look at an example app One of the best ways to learn is by doing things for yourself. You can clone an example app that is similar to something you want to build, and start experimenting from there. ###### CRUD example This example shows patterns to implement a data admin app which allows users to view, create new, edit and delete data records. - [Example demo.](https://example-crud.lowdefy.com) - [App source code.](https://github.com/lowdefy/lowdefy-example-crud) ###### Survey example This is a simple customer survey example built with Lowdefy. With this example we demonstrate how simple it is to define a public webform and thank you page in Lowdefy. - [Example demo.](https://example-survey.lowdefy.com) - [App source code.](https://github.com/lowdefy/lowdefy-example-survey) ###### Case management (ticketing) system example This example focuses on building a rich UI for a hypothetical case management app, in a customer relations setting. - [Example demo.](https://example-case-management.lowdefy.com) - [App source code.](https://github.com/lowdefy/lowdefy-example-case-management) ###### Movies reporting example This example demonstrates useful patterns for building a BI report/dashboard pages in Lowdefy. It connects to a MongoDB database with the Atlas Movies sample dataset pre-loaded. - [Example demo.](https://example-reporting.lowdefy.com) - [App source code.](https://github.com/lowdefy/lowdefy-example-reporting) --- url: https://docs.lowdefy.com/md/concepts/overview.md --- # Overview A Lowdefy app is a [Hono](https://hono.dev) server with a [Vite](https://vite.dev)-built React client, that interprets / evaluates Lowdefy config. Lowdefy apps are written as YAML configuration files, which are easy to read, write and understand - and can be managed in source control. This makes Lowdefy quick to learn, maintain and pick up where others left off. Lowdefy can be considered as another level of abstraction on top of the web stack. The Lowdefy schema is designed to be a standardized, declarative and flexible expression of application parameters and logic. This allows developers to focus on translating the business problem into configuration. The Lowdefy schema abstracts app definitions into 5 categories: - [**Blocks**](/blocks): The building blocks of the UI, the client-side React component to render. Blocks are usually stateless since all app state is managed by the Lowdefy engine. - [**Operators**](/operators): The application logic is defined using operator functions, which can be used both server-side and client-side. These are synchronous methods, which are continuously evaluated every time the app state changes. - [**Actions**](/events-and-actions): Asynchronous client-side methods triggered by events, such as clicking a button or initializing the page state. These methods usually apply an action like setting state, using the [SetState](/SetState) action, or calling a request, using the [Request](/Request) action. - [**Connections and Requests**](/connections-and-requests): Server-side methods, which allow a Lowdefy app to make server requests to other services like APIs or databases. Requests are evoked by the [Request](/Request) action, and are only called after all user authentication and authorization checks have passed. Requests consume a payload from the client, and have access to server-side secrets like passwords and API keys. - [**Auth Providers and Adapters**](/users-introduction): Lowdefy implements [Auth.js](https://authjs.dev) for authentication. Auth.js allows for the configuration of a wide range of authentication providers and adapters, which can be configured in Lowdefy apps. The diagram below provides an overview of the Lowdefy app schematic: ![Lowdefy App Diagram](/images/lowdefy_app_schema.png) Lowdefy offers a large number of default actions, blocks, operators, connections, and auth integrations - but also provides a [plugin interface](/plugins) for the extension of new types. All plugins are installed as [npm](https://www.npmjs.com/) modules, which means you can extend your Lowdefy application by any existing npm module - enabling developers to make use of anything that is available in the Javascript ecosystem. Since Lowdefy apps are self-hosted Node.js applications, they do not ship with a built in data store and typically connect to one or more APIs or databases. Server instances are also stateless, which simplifies deployment. You can deploy your app using Docker or any hosting provider that runs Node.js servers. --- url: https://docs.lowdefy.com/md/concepts/lowdefy-schema.md --- # Lowdefy App Schema A Lowdefy app is written as YAML or JSON files, which are combined together using the [`_ref`](/_ref) operator when the app is built into a configuration object that describes the app. This object has different sections that describe different parts of the Lowdefy app. > A good understanding of YAML is needed before starting with Lowdefy. If you don't have any experience using YAML, you can find a good introduction video here. The root schema for a Lowdefy app is: - `lowdefy: string`: __Required__ - The Lowdefy version number that the app uses. This is required and cannot be a reference to another file. - `name: string`: A name for the application. - `version: string`: The version number of the app that you are building. This is optional and allows you indicate the version of your app. - `slug: string`: A kebab-case identifier for the app, available via the [`_app`](/_app) operator. Must match the pattern `^[a-z][a-z0-9]*(-[a-z0-9]+)*$` (lowercase letters and digits, hyphen-separated, must start with a letter). - `description: string`: A free-form description of the app. - `license: string`: A [SPDX license ID](https://spdx.org/licenses/). You can use this to indicate the project's license if you are licensing your project under a specific software license. If you wish to indicate to others that you do not grant the right to use your project, you can use `UNLICENSED` for this field. How you share your Lowdefy config is up to you. - `cli: object`: An object with configuration for the CLI. - `config: object`: An object with app configuration like the home page pageId. - `auth: object` An object with authentication and authorization configuration. - `global: object`: A data object that can be accessed anywhere in the app using the [`_global`](/_global) operator. - `events: object`: App events, `onInit` and `onInitAsync`, that run once each time the app loads in the browser. See [App events](/events-and-actions#app-events). - `connections: object[]`: An array of [`connection`](/connections-and-requests) objects. - `plugins: object[]`: An array of `plugin` objects to customize and add block types. - `modules: object[]`: An array of module entries. Each module references a reusable configuration package distributed via GitHub or local file paths. See [Modules](/modules) for details. - `menus: object[]`: An array of menu objects. - `pages: object[]`: An array of page objects. All of the above fields (along with the framework version and the build git SHA) are also available at runtime via the [`_app`](/_app) operator. Pages are made up of blocks. Blocks have the following basic schema: - `id: string`: An identifier for a block. - `type: string`: __Required__ - This is the block type identifier and defines which block will render. - `properties: object`: The settings passed to a block component. - `layout`: Control how the block will be rendered in the page flow, or in other words, how the block is placed in relation to other blocks. See [layout](/layout-overview) for more details. - `blocks: array`: An array of blocks to render within this block. Find the more detailed block schema [here](/blocks). >

YAML file extensions

Both files with the .yaml and .yml file extensions are supported as YAML files.

JSON instead of YAML

Since you can reference JSON files, you can build your app using JSON instead of YAML files. The lowdefy.yaml file needs to be a YAML file, but all other configuration can be in referenced JSON files. It also makes sense to use JSON instead of YAML if you are generating configuration using code.

## Config The config object has the following properties: - `environments: object`: The deployment environments of the app, keyed by name (for example `prod`, `staging`, `local`). Each build is for one of them, named by the `LOWDEFY_ENVIRONMENT` environment variable, and the current environment supplies the defaults for everything environment-specific. See [Deployment environments](/deployment-environments). Each environment has: - `url: string`: The deployment origin, for example `https://staging.example.com`. Default for the auth canonical URL (`BETTER_AUTH_URL`) and the MCP resource URIs, notification links and logos (`RenderNotification` `serverUrl`), and where crons are forwarded to. - `cron.secret: string`: The **name** of the Lowdefy secret holding this environment's `CRON_SECRET` — a plain string, not a `_secret` operator (environment settings are resolved at build time, where `_secret` is not evaluated). The value is read at runtime from `LOWDEFY_SECRET_` on the deployment that forwards. An environment with a `cron.secret` has its schedules forwarded to it from the environment Vercel fires crons on. - `cron.enabled: boolean`: Set `false` to register no cron jobs for this environment. Defaults to `true`. - `email.filter: object`: The delivery filter (`replaceAddress`, `allowlist`, `regex`) applied to every `SMTPMailSend` and `SendGridMailSend` request in this environment, unless the connection sets its own `filter` (`filter: false` on a connection turns filtering off). - `email.enabled: boolean`: Set `false` to send no email from `SMTPMailSend` and `SendGridMailSend` requests in this environment (auth emails still send). Defaults to `true`. - `sentry.enabled: boolean`: Set `false` to turn Sentry off, server and client, in this environment. Defaults to `true`. - `guards: object`: Pin secrets and environment variables to a pattern kept in config. `guards.secrets` maps a Lowdefy secret name (`LOWDEFY_SECRET_`) and `guards.env` an environment variable name to a regular expression; in this environment the build fails unless each value matches (a missing value fails too), so changing a guarded value also takes a config change. See [Guards](/deployment-environments). - `posthog.enabled: boolean`: Set `false` to turn PostHog off in this environment: `PostHogInit` behaves as `enabled: false` (no key needed) and every PostHog action is a no-op. Defaults to `true`. - `environment: string`: The name of the environment this build is for, used when the `LOWDEFY_ENVIRONMENT` environment variable is not set; the variable always wins. When `environments` is declared it must be one of its names. Server log lines carry it as `environment`, `_app: environment` reads it, and Sentry reports under it unless `logger.sentry.environment` is set. - `basePath: string`: Set the base path to serve the Lowdefy application from. This will route all pages under `https://example.com//` instead of the default `https://example.com/`. The basePath value must start with "/". - `homePageId: string`: The id of the page that will load when a visitor navigates to the home route, in other words, when the visitor navigates to `yourdomain.com` or `yourdomain.com/`. The home page cannot have placeholders in its [`path`](/page-paths). - `dependencyTracking: boolean`: Defaults to `true`. The browser re-evaluates only the blocks whose inputs changed (the state paths, requests and globals their operators read) instead of every block on the page after each change. Set to `false` to evaluate every block on every update, as earlier versions did. - `experimental: object`: Opt in to features that are not yet stable. An experimental feature may change, or be removed, in a minor release. The build fails on a key it does not know. - `mobile: boolean`: Build the `mobile` section, so the app runs as a native iOS and Android app, and allow `offline` on requests. Defaults to `false`. See [Mobile Apps](/mobile-apps). - `requestTimeout: number`: Maximum time in milliseconds a request may run before the server returns a timeout. This protects against requests that hang on an upstream call (database, SMTP, an external API) running all the way to the host's function limit — important on serverless platforms that bill by execution duration. Defaults to `30000` (30 seconds). Set to `0` to disable. Agent streaming routes are exempt, since they are long-lived by design. - `security: object`: Opt-in browser security settings. See [Security](#security) below. - `contentSecurityPolicy: boolean | object`: Send a Content-Security-Policy header with app pages. Set `true` for the default policy, or an object with: - `reportOnly: boolean`: Send the policy as `Content-Security-Policy-Report-Only`, so the browser reports what it would block without blocking it. Defaults to `false`. - `directives: object`: Sources to add to the default policy, keyed by directive name, for example `script-src: ['https://www.googletagmanager.com']`. - `trustedProxies: string[]`: IP addresses and CIDR ranges (for example `10.0.0.0/8`) of the reverse proxies and load balancers in front of a self-hosted server. The server takes each request's client address from its connection, and reads `X-Forwarded-For` only when that connection comes from a trusted proxy, skipping trusted hops from the right. Auth rate limits, sessions and request logs use this address. Defaults to none, so a client cannot choose its own address. Not used on Vercel, where the platform supplies the client address. See [Behind a reverse proxy](/docker#behind-a-reverse-proxy). ## Security ### Content Security Policy A [Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP) tells the browser which scripts, styles, images and connections a page may use. It is off by default. Turn it on with `config.security.contentSecurityPolicy`: ```yaml config: security: contentSecurityPolicy: true ``` The server then sends this policy with every page: | Directive | Sources | | --------- | ------- | | `default-src` | `'self'` | | `script-src` | `'self'` and a nonce | | `style-src` | `'self' 'unsafe-inline'` | | `img-src` | `'self' data: blob:` | | `font-src` | `'self' data:` | | `connect-src` | `'self' https://api.lowdefy.net`, and the page's own websocket origin when the app has websockets | | `object-src` | `'none'` | | `base-uri` | `'self'` | | `frame-ancestors` | `'self'` | Each page render draws a new random nonce. Lowdefy's own inline scripts, and every ` # ... ``` --- url: https://docs.lowdefy.com/md/concepts/custom-styling.md --- # Custom Styling Lowdefy provides several ways to style blocks and customize the look of your app: - **`class`** — Apply CSS class names (including Tailwind CSS utility classes) to blocks. - **`style`** — Apply inline CSS styles to blocks. Use `.`-prefixed keys to target specific parts (CSS slots). - **`properties.theme`** — Override antd design tokens per block. See [Theming](/theming) for details. - **`public/styles.css`** — A global CSS file for custom styles. ## The `class` property The `class` property applies CSS class names to a block. [Tailwind CSS](https://tailwindcss.com/) is always available in Lowdefy apps, so you can use any Tailwind utility classes directly. `class` can be a string, an array of strings, an object keyed by the block's CSS keys, or an operator that computes the classes at runtime: ```yaml # String — applied to the block wrapper - id: my_card type: Card class: "p-4 shadow-lg rounded-xl" # Array — all classes joined - id: my_card type: Card class: - p-4 - shadow-lg - rounded-xl # Object — target specific parts of the block using CSS keys (. prefix) - id: my_card type: Card class: .element: "shadow-lg" .header: "bg-gray-100" .body: "p-8" # Operator — the block's classes are computed at runtime - id: status_badge type: Box class: _if: test: _eq: - _state: status - error then: bg-red-100 text-red-700 else: bg-green-100 text-green-700 ``` An operator's result may be a string, an array of strings, or an object of `{ class: boolean }` where each class with a truthy value is applied: ```yaml - id: status_badge type: Box class: _if: test: _eq: - _state: status - error then: bg-red-100: true text-red-700: true else: bg-green-100 ``` An operator at the root of `class` computes the classes of the block wrapper. To compute a specific part's classes, place the operator under that CSS key: ```yaml - id: my_card type: Card class: .header: _if: test: _state: highlighted then: bg-yellow-100 else: bg-gray-100 ``` > **Note:** An operator's result is never expanded into a map of CSS keys — a root operator always sets the block wrapper's classes. A result with dot-prefixed keys (`{ .element: p-4 }`) is reported as a config error, and those keys are not applied as classes. Tailwind responsive prefixes work as expected: ```yaml - id: my_box type: Box class: "p-4 sm:p-8 lg:p-16" ``` ### Theme-aware Tailwind classes Lowdefy bridges antd design tokens to Tailwind, so utility classes like `text-primary` and `bg-bg-container` automatically reflect your theme configuration (including dark mode): ```yaml - id: themed_card type: Box class: "bg-bg-container text-text-primary border border-border rounded-lg p-4" ``` See [Theming](/theming) for the full list of bridged tokens and how to add custom Tailwind tokens via `theme.tailwind`. ## The `style` property The `style` property applies inline CSS styles to a block. When used with flat CSS properties (no `.` prefix), it targets the block's layout wrapper: ```yaml - id: my_block type: Box style: maxWidth: 800 margin: "0 auto" ``` To target specific parts of a block, use `.`-prefixed **CSS slot keys**. Each block declares which slots it supports in its `meta.cssKeys`. Common keys include `.block` (layout wrapper), `.element` (main component), `.header`, `.body`, `.footer`, etc. ```yaml - id: my_card type: Card style: .element: borderRadius: 12 .header: backgroundColor: "#f5f5f5" fontWeight: bold .body: padding: 32 ``` You can mix flat CSS (which targets `.block`) with explicit slot keys: ```yaml - id: my_card type: Card style: .block: flex: 1 .element: borderRadius: 12 ``` Each block's documentation page lists its supported CSS keys. > **Note:** The responsive `style.sm`, `style.md` breakpoint syntax from v4 has been removed. Use Tailwind responsive classes via `class` instead. ## CSS keys Each block declares named style targets in its `meta.cssKeys`. For example, the [Card](/Card) block supports: `element`, `header`, `body`, `cover`, `actions`, `extra`. Both `class` (object form) and `style` use `.`-prefixed versions of these keys to target specific parts of a block. The `.` prefix mirrors CSS selector convention and avoids confusion with CSS property names. To find the CSS keys for a block, check the block's documentation page. ## Global styles with `public/styles.css` To add global custom CSS to your app, create a file named `styles.css` in your app's `public` folder. This replaces the `styles.less` file used in v4. ```css /* public/styles.css */ h1 { font-size: 32px; color: #2ac8bb; } .my-custom-class { border: 2px solid var(--ant-color-primary); border-radius: 8px; } ``` Additional CSS files can be imported: ```css /* public/styles.css */ @import 'other.css'; ``` ## CSS variables Antd v6 uses CSS custom properties (CSS variables) for all its design tokens. You can reference these in your custom CSS or inline styles: ```css /* Access antd theme tokens as CSS variables */ .my-class { color: var(--ant-color-primary); background: var(--ant-color-bg-container); border: 1px solid var(--ant-color-border); font-size: var(--ant-font-size); } ``` These variables are automatically set based on your app's theme configuration. See [Theming](/theming) for how to configure theme tokens. ## Migration from v4 | v4 | v5 | |---|---| | `public/styles.less` | `public/styles.css` | | `@primary-color: #2ac8bb;` | `theme.antd.token.colorPrimary: '#2ac8bb'` in `lowdefy.yaml` | | `style.sm.padding: 32` | `class: "p-8 sm:p-4"` | | `properties.bodyStyle: {...}` | `style: { .body: {...} }` | | `@import 'antd/dist/antd.dark.less';` | `theme.antd.algorithm: dark` in `lowdefy.yaml` | See the [Migration Guide](/v4-to-v5) for full details. --- url: https://docs.lowdefy.com/md/concepts/theming.md --- # Theming Lowdefy uses the [Ant Design](https://ant.design/) design token system for theming. This allows you to customize colors, typography, spacing, and more at the app level, component type level, or individual block level. ## App-level theme Configure your app's theme in `lowdefy.yaml` under `theme.antd`: ```yaml lowdefy: 7.1.0 theme: antd: token: colorPrimary: '#6366f1' colorSuccess: '#22c55e' colorWarning: '#f59e0b' colorError: '#ef4444' fontSize: 14 borderRadius: 8 ``` ### Seed tokens Seed tokens are the base values from which all other tokens are derived. The most commonly used seed tokens are: | Token | Type | Description | |---|---|---| | `colorPrimary` | string | Primary brand color | | `colorSuccess` | string | Success state color | | `colorWarning` | string | Warning state color | | `colorError` | string | Error state color | | `colorInfo` | string | Info state color | | `colorBgBase` | string | Base background color | | `colorTextBase` | string | Base text color | | `fontSize` | number | Base font size (px) | | `borderRadius` | number | Base border radius (px) | | `wireframe` | boolean | Enable wireframe style | | `fontFamily` | string | Font family | From these seed tokens, antd derives hundreds of map tokens (e.g. `colorPrimaryHover`, `colorBgContainer`, `fontSizeLG`) automatically. ## Algorithms Algorithms transform seed tokens into map tokens. Antd provides three built-in algorithms: ```yaml # Dark mode theme: antd: algorithm: dark # Compact mode theme: antd: algorithm: compact # Combine multiple algorithms theme: antd: algorithm: - dark - compact ``` | Algorithm | Description | |---|---| | `default` | Default light theme | | `dark` | Dark color scheme | | `compact` | Reduced spacing and smaller sizes | ## Per-component-type overrides Override design tokens for all instances of a component type: ```yaml theme: antd: token: colorPrimary: '#6366f1' components: Button: fontWeight: 700 paddingInline: 24 Card: headerBg: '#f5f5f5' bodyPadding: '24px' Input: activeBorderColor: '#6366f1' ``` Each antd component has its own set of component tokens. See the individual block documentation pages for commonly used tokens, or the [antd component token reference](https://ant.design/components/overview/) for the full list. ## Per-block overrides Override design tokens for a single block instance using `properties.theme`: ```yaml - id: special_button type: Button properties: title: Special Button theme: fontWeight: 700 paddingInline: 24 primaryColor: '#6366f1' ``` Under the hood, `properties.theme` wraps the block in a scoped [ConfigProvider](/ConfigProvider) with the specified component tokens. ### Per-item variants in `Menu` antd's `Menu` owns the token scope for all its items, so you can't override tokens per item. Instead, set `properties.danger: true` on a `MenuLink` to switch that single item onto the `dangerItem*` token set, and theme the whole danger state at the block level: ```yaml - id: actions_menu type: Menu properties: mode: vertical theme: dangerItemColor: '#cf1322' dangerItemHoverColor: '#ff4d4f' dangerItemSelectedBg: '#fff1f0' links: - id: edit type: MenuLink properties: title: Edit icon: edit - id: delete type: MenuLink properties: title: Delete icon: delete danger: true ``` For per-item visual tweaks beyond `danger`, use `class` (Tailwind / arbitrary CSS) or `style` on the menu item — both support dot-prefixed slot keys (`.element`, `.icon`, `.label`, `.popup`). See [Custom styling](/custom-styling). ## ConfigProvider block For scoped theming of a group of blocks, use the [ConfigProvider](/ConfigProvider) container block: ```yaml - id: dark_section type: ConfigProvider properties: algorithm: dark token: colorPrimary: '#a855f7' blocks: - id: themed_card type: Card properties: title: Dark Themed Card - id: themed_button type: Button properties: title: Click Me ``` ConfigProvider creates a theme scope — all child blocks inherit the specified theme tokens and algorithm. ## The `_theme` operator Access seed tokens in expressions using the `_theme` operator: ```yaml # String form — get a single token color: _theme: colorPrimary # Object form — with default value background: _theme: key: colorBgContainer default: '#ffffff' # Get all tokens allTokens: _theme: all: true ``` The `_theme` operator resolves seed tokens from `theme.antd.token`. For derived tokens (like hover/active variants), use CSS variables instead: `var(--ant-color-primary-hover)`. See the [`_theme` operator reference](/_theme) for full documentation. ## CSS variables All antd design tokens are available as CSS variables with the `--ant-` prefix. This is useful in `public/styles.css` or when using CSS-based customizations: ```css .my-custom-class { color: var(--ant-color-primary); background: var(--ant-color-bg-container); border: 1px solid var(--ant-color-border); border-radius: var(--ant-border-radius); } ``` Token names are converted from camelCase to kebab-case for the CSS variable name (e.g. `colorPrimary` → `--ant-color-primary`). ## Dark mode Lowdefy supports three dark mode behaviors, configured via `theme.darkMode`: ```yaml theme: darkMode: system # 'system' (default), 'light', or 'dark' ``` | Value | Behavior | |---|---| | `system` | Follows the OS dark mode preference and updates live when it changes. Users can override with `SetDarkMode`. This is the default. | | `light` | Forces light mode. `SetDarkMode` preferences are stored but have no visual effect. | | `dark` | Forces dark mode. `SetDarkMode` preferences are stored but have no visual effect. | You can also scope dark mode to a section using [ConfigProvider](/ConfigProvider): ```yaml - id: dark_section type: ConfigProvider properties: algorithm: dark blocks: # All blocks here will use dark theme ``` ### Easy dark mode toggle Add a built-in dark mode toggle to your page layout with a single property: ```yaml - id: my_page type: PageHeaderMenu # or PageSiderMenu properties: darkModeToggle: true ``` This renders a moon/sun icon in the header that cycles through light, dark, and system preferences. The preference is persisted to `localStorage`. For custom toggle implementations, see the programmatic approach below. ### Programmatic dark mode toggle Use the `SetDarkMode` action with `_media: darkMode` to let users control dark mode at runtime: ```yaml - id: dark_mode_toggle type: Button properties: hideTitle: true icon: _if: test: _media: darkMode then: theme-light else: theme-dark events: onClick: - id: toggle type: SetDarkMode ``` Without params, `SetDarkMode` cycles through `light`, `dark`, and `system`. You can also set a specific preference: ```yaml - id: set_system type: SetDarkMode params: darkMode: system ``` The user's preference is persisted to `localStorage` and survives page refreshes. Use `_media: darkModePreference` to read the current preference (`'system'`, `'light'`, or `'dark'`), and `_media: darkMode` for the effective boolean state. `SetDarkMode` automatically merges the `dark` algorithm with any existing algorithm configured in `theme.antd.algorithm`. For example, if your app uses `algorithm: compact`, toggling dark mode produces `[compact, dark]`. See the [`SetDarkMode` action reference](/SetDarkMode) and [`_media` operator reference](/_media) for full documentation. ### Tip: Use CSS variables for dark-mode-safe colors When styling blocks with custom colors (e.g. `cellStyle` in AgGrid, or `style` on any block), avoid hardcoded hex values — they won't adapt to dark mode. Use antd CSS variables instead: ```yaml # Bad — hardcoded colors look washed out in dark mode cellStyle: backgroundColor: '#e6f7ff' # Good — antd palette tokens adapt automatically cellStyle: backgroundColor: var(--ant-blue-1) ``` Useful palette variables: `var(--ant-blue-1)`, `var(--ant-green-1)`, `var(--ant-orange-1)`, `var(--ant-red-1)`, etc. For semantic colors, use `var(--ant-color-primary-bg)`, `var(--ant-color-success-bg)`, `var(--ant-color-warning-bg)`, `var(--ant-color-error-bg)`. ### Customizing base colors per mode Antd's default dark algorithm uses pure black (`#000`) for `colorBgLayout`, and the default light algorithm uses a stark off-white. If you want softer, more modern base surfaces, use `lightToken` and `darkToken` on `theme.antd`. These are merged on top of `theme.antd.token` only when the matching mode is active, so you get full per-mode control without juggling two separate theme files: ```yaml theme: antd: token: # Shared — applied in both modes colorPrimary: '#6366f1' borderRadius: 8 lightToken: # Merged when light mode is active colorBgLayout: '#fafafa' colorBgContainer: '#ffffff' darkToken: # Merged when dark mode is active colorBgLayout: '#0f1117' colorBgContainer: '#18181b' colorBgElevated: '#1f2937' darkMode: system ``` Tokens to customize for a softer base surface: | Token | Where it shows up | |---|---| | `colorBgLayout` | Page background behind the layout — also applied to the `` element to prevent navigation flash | | `colorBgContainer` | Card, Table, Input, Modal body fill | | `colorBgElevated` | Popover, Dropdown, Tooltip-host surfaces | | `colorText` | Primary text color | | `colorTextSecondary` | Secondary / muted text | The pre-hydration script reads `darkToken.colorBgLayout` and `lightToken.colorBgLayout` directly, so the `` background matches your configured color on first paint — there is no flash of `#000` or `#fff` before hydration. If you don't set a `lightToken.colorBgLayout`, light mode keeps the browser default white (today's behavior). ### Per-mode component overrides `colorBg*` seed tokens don't reach every component — Layout's header/sider and Menu have their own component tokens (`Layout.headerBg`, `Layout.siderBg`, `Menu.darkItemBg`, etc.). Use `darkComponents` and `lightComponents` alongside `darkToken`/`lightToken` to soften these surfaces in the active mode only: ```yaml theme: antd: darkToken: colorBgLayout: '#0f172a' colorBgContainer: '#111a2e' darkComponents: Layout: bodyBg: '#0f172a' headerBg: '#0b1120' siderBg: '#0b1120' triggerBg: '#111a2e' triggerColor: '#e2e8f0' Menu: darkItemBg: '#0b1120' darkSubMenuItemBg: '#0b1120' darkPopupBg: '#111a2e' darkItemSelectedBg: '#1e293b' darkItemHoverBg: '#111a2e' ``` `darkComponents` / `lightComponents` are merged per-component (deep at the component name level) on top of the shared `components`, so you only need to specify the per-mode deltas. See antd's [design token reference](https://ant.design/docs/react/customize-theme#theme) for the full list of tokens you can override. ## Tailwind theme bridge (`theme.tailwind`) Lowdefy automatically bridges antd design tokens to Tailwind CSS theme variables. This means Tailwind utility classes like `text-primary` or `bg-bg-container` resolve to the current antd theme — including when you switch to dark mode. ### Default bridged tokens The following Tailwind tokens are mapped to antd CSS variables by default: | Tailwind class prefix | Maps to antd token | |---|---| | `text-primary`, `bg-primary`, `border-primary` | `colorPrimary` | | `*-primary-hover` | `colorPrimaryHover` | | `*-primary-active` | `colorPrimaryActive` | | `*-primary-bg` | `colorPrimaryBg` | | `*-success` | `colorSuccess` | | `*-warning` | `colorWarning` | | `*-error` | `colorError` | | `*-info` | `colorInfo` | | `*-text-primary` | `colorText` | | `*-text-secondary` | `colorTextSecondary` | | `*-bg-container` | `colorBgContainer` | | `*-bg-layout` | `colorBgLayout` | | `*-border` | `colorBorder` | | `rounded`, `rounded-sm`, `rounded-lg` | `borderRadius` variants | | `text-sm`, `text-lg` (font-size) | `fontSize` variants | | `font-sans` | `fontFamily` | This means you can write: ```yaml - id: themed_box type: Box class: "bg-bg-container text-text-primary border border-border rounded-lg p-4" ``` And the colors will automatically match your antd theme, including dark mode. ### Adding custom Tailwind tokens Use `theme.tailwind` in `lowdefy.yaml` to add custom tokens or override bridge defaults: ```yaml theme: antd: token: colorPrimary: '#7c3aed' tailwind: color: accent: '#7c3aed' surface: '#f8fafc' on-surface: '#1e293b' spacing: section: '3rem' ``` You can then use these in `class`: ```yaml - id: my_section type: Box class: "bg-surface text-on-surface p-section" ``` Custom tokens are deep-merged with the default bridge, so all antd-bridged tokens remain available. ### Overriding bridge defaults To change how a bridge token resolves, set the same key in `theme.tailwind`: ```yaml theme: tailwind: color: primary: '#ff4d4f' # Now a static color, no longer follows antd token ``` ## Icons (`theme.icons`) Lowdefy draws icons with [Lucide](https://lucide.dev). An icon name is a semantic name (`edit`), a Lucide name in PascalCase (`Pencil`), or a qualified name (`lucide:Pencil`). The [Icon](/Icon) block describes the three forms and the per-icon settings. Use semantic names anywhere an icon name is accepted, and in HTML with `data-icon`: ```yaml - id: edit_button type: Button properties: title: Edit icon: edit - id: note type: Html properties: html: ' Saved just now' ``` `theme.icons` sets the icon defaults for the whole app: ```yaml theme: icons: set: lucide size: 1em strokeWidth: 1.75 nonScalingStroke: true aliases: invoice: Receipt edit: tabler:Pencil include: - Flag ``` | Key | Default | Description | | --- | --- | --- | | `set` | `lucide` | The default icon set. PascalCase names resolve in this set first, then in Lucide. Set it to an installed [icon set](/plugins-icon-sets). | | `size` | `1em` | Default width and height. `1em` follows the font size, so icons fit text, buttons and menus. | | `strokeWidth` | `2` | Default line width, in units of the icon's 24px grid. | | `nonScalingStroke` | `false` | Keep the line width constant in screen pixels at any size. | | `aliases` | | Add semantic names, or point a built-in one at another icon. | | `include` | | Icon names to bundle that only appear at runtime. | An icon's own `size`, `strokeWidth` and `nonScalingStroke` win over these defaults: `icon: { name: edit, strokeWidth: 1 }`. ### Semantic names Each built-in semantic name draws a Lucide icon: | Name | Lucide icon | Name | Lucide icon | | --- | --- | --- | --- | | `add` | Plus | `info` | Info | | `add-circle` | CirclePlus | `italic` | Italic | | `arrow-down` | ArrowDown | `link` | Link | | `arrow-left` | ArrowLeft | `list` | List | | `arrow-right` | ArrowRight | `loading` | LoaderCircle | | `arrow-up` | ArrowUp | `location` | MapPin | | `attach` | Paperclip | `lock` | Lock | | `bell` | Bell | `login` | LogIn | | `bold` | Bold | `logout` | LogOut | | `bot` | Bot | `mail` | Mail | | `calendar` | Calendar | `menu` | Menu | | `camera` | Camera | `message` | MessageSquare | | `chart` | ChartColumn | `minus` | Minus | | `check` | Check | `more` | Ellipsis | | `chevron-down` | ChevronDown | `more-vertical` | EllipsisVertical | | `chevron-left` | ChevronLeft | `phone` | Phone | | `chevron-right` | ChevronRight | `print` | Printer | | `chevron-up` | ChevronUp | `rating-high` | FaceSlightlySmiling | | `chevrons-left` | ChevronsLeft | `rating-low` | FaceSlightlyFrowning | | `chevrons-right` | ChevronsRight | `refresh` | RefreshCw | | `clear` | CircleX | `remove` | CircleMinus | | `clock` | Clock | `save` | Save | | `close` | X | `search` | Search | | `copy` | Copy | `send` | Send | | `delete` | Trash | `settings` | Settings | | `document` | FileText | `share` | Share2 | | `download` | Download | `sidebar-collapse` | PanelLeftClose | | `drag` | GripVertical | `sidebar-expand` | PanelLeftOpen | | `edit` | Pencil | `sort` | ArrowUpDown | | `error` | CircleX | `star` | Star | | `external-link` | ExternalLink | `strikethrough` | Strikethrough | | `file` | File | `success` | CircleCheck | | `filter` | Funnel | `tag` | Tag | | `folder` | Folder | `theme-dark` | Moon | | `globe` | Globe | `theme-light` | Sun | | `grid` | LayoutGrid | `theme-system` | Monitor | | `heart` | Heart | `unchanged` | CirclePause | | `help` | CircleQuestionMark | `unlock` | LockOpen | | `hide` | EyeOff | `upload` | Upload | | `highlight` | Highlighter | `user` | User | | `history` | RotateCcwClock | `users` | Users | | `home` | House | `view` | Eye | | `icon-missing` | CircleAlert | `warning` | TriangleAlert | | `image` | Image | | | Ant Design icon names also work and map to the same icons: `check-circle` (CircleCheck), `clock-circle` (Clock), `close-circle` (CircleX), `down` (ChevronDown), `ellipsis` (Ellipsis), `environment` (MapPin), `file-text` (FileText), `info-circle` (Info), `left` (ChevronLeft), `plus` (Plus), `printer` (Printer), `reload` (RefreshCw), `right` (ChevronRight), `setting` (Settings), `up` (ChevronUp). ### Blocks and Ant Design components follow semantic names Blocks choose their own icons by semantic name: the date pickers use `calendar`, input validation uses `error`, `success`, `warning` and `loading`, selectors use `chevron-down`, `clear` and `check`, and the dark mode toggle uses `theme-light`, `theme-dark` and `theme-system`. Ant Design's own icons go through the same names: the close X on modals, drawers, tags and alerts (`close`), alert and message status icons (`success`, `info`, `warning`, `error`), collapse and menu arrows, pagination arrows, and the button spinner (`loading`). So an alias restyles your icons and the built-in ones together. This makes every close button a circled X: ```yaml theme: icons: aliases: close: CircleX ``` A few Ant Design visuals are not icons and stay as Ant Design draws them: Spin, Progress, QRCode, the Empty and Result illustrations, the date picker panel arrows, and the Switch and Timeline loading states. ### Aliases Alias names are lowercase kebab-case. An alias target is a set name (`Receipt`) or a qualified name (`tabler:Pencil`), never another semantic name. A semantic name resolves through, from lowest to highest precedence: 1. the built-in map above, 2. the `semantic` maps of the default set's plugins, in `plugins` order, 3. `theme.icons.aliases`. ### Icon sets `theme.icons.set` selects an installed [icon set](/plugins-icon-sets). PascalCase names then resolve in that set first and fall back to Lucide, and the set's own semantic map re-skins the block and Ant Design icons it covers. Semantic names the set leaves out keep their Lucide icons. A qualified name such as `lucide:Pencil` always resolves in its own set. ### Bundling The build bundles only the icons your config names. An icon name that only exists at runtime — read from state or a request — is not in the config, so list it under `theme.icons.include` to bundle it. A name kept in config data, such as `icon: warning` on a status enum that a template turns into `data-icon="{{ status.icon }}"`, is already in the config and gets bundled. In production each page loads only its own icons: the names in its config and `_js` functions, the icons its blocks draw, and the icons in `menus` and `global`, which every page can show. The first time a page draws a bundled icon that is not one of its own, such as a name from `include`, from an endpoint or from another page, it loads the rest of the app's icons. The icon keeps its space, empty, until they arrive, and stays loaded for the rest of the visit. A page whose Dynamic content names such an icon loads them before it first renders. The dev server loads every icon. An unknown icon name at an icon property fails the build, with a suggestion. Old react-icons names such as `AiOutlineUser` fail too; [V6 to V7](/v6-to-v7) shows how to migrate them. ### Icons in HTML In any HTML string, `` renders an icon. [HTML attributes](/html-attributes) describes it with the other attributes HTML understands: tooltips, popovers, links and status tags. ## TLDR - Configure app theme via `theme.antd` in `lowdefy.yaml`. - Seed tokens (`colorPrimary`, `fontSize`, etc.) drive all derived tokens automatically. - Use `algorithm: compact` for compact mode. Use `theme.darkMode` for dark mode control. - Dark mode defaults to `system` (follows OS preference). Set to `light` or `dark` to lock. - Override tokens per component type via `theme.antd.components`. - Override tokens per block via `properties.theme`. - Use [ConfigProvider](/ConfigProvider) block for scoped theming of block groups. - Access tokens in expressions with the `_theme` operator. - Icons are Lucide. Use semantic names (`icon: edit`) or Lucide names (`Pencil`), and `data-icon` in HTML; set app-wide `size` and `strokeWidth`, and add names, with `theme.icons`. See [HTML attributes](/html-attributes) for everything HTML understands. - Access derived tokens in CSS via `var(--ant-color-primary-hover)`. - Tailwind utility classes (`text-primary`, `bg-bg-container`, etc.) automatically reflect the antd theme. - Add custom Tailwind tokens via `theme.tailwind` in `lowdefy.yaml`. --- url: https://docs.lowdefy.com/md/concepts/html-attributes.md --- # HTML attributes Every block that renders HTML understands a small set of `data-*` attributes: `Html`, `ClickableHtml`, `DangerousHtml`, HTML properties such as Tooltip titles, Card titles and Alert messages, ag-grid cell HTML, and the `EventLog` block. They give HTML the icons, tooltips, links, status tags, dates, numbers, avatars, copy buttons, truncation, text tones and confirmations that apps otherwise build by hand with inline styles and templates, and they look and behave the same in every app. The attributes work on HTML from anywhere: page config, `_nunjucks` templates, request results and messages that API endpoints build on the server. | Attribute | On | Effect | |---|---|---| | `data-icon="edit"` | any element | Renders the icon inside the element. | | `data-tooltip="Text"` | any element | Shows a tooltip on hover and keyboard focus. | | `data-popover="id"` | any element | Click, Enter or Space toggles a popover showing the element with `data-popover-content="id"`. | | `data-event="onName"` | any element | `ClickableHtml` only: fires the block event `onName`, when the block's `dataEvents` lists it. | | `data-page-id="page"` | `` | A link to a page of the app, opened without reloading the app. | | `data-url-query="key=value"` | `` | The link's query string. | | `data-link` | `` | Opens an app path without reloading the app. | | `data-new-tab` | `` | Opens the link in a new tab. | | `data-tag="success"` | any element | A tinted status tag. | | `data-status="success"` | any element | A status dot before the text. | | `data-time="relative"` | `{{ name }} ``` Lowdefy sets the `href` (including the app's `basePath`), and a plain click opens the page without reloading the app, the same as a [`Link`](/Link) action with `pageId` and `urlQuery`. Middle-click, Ctrl or Cmd-click, "copy link" and hover preview work as for any link. `data-url-query` is a query string (`a=1&b=2`); its values reach the page's `_url_query` the same way the `Link` action's `urlQuery` does. For a page with a [`path`](/page-paths), give the values of its placeholders in `data-path-params`, a JSON object, the same as the `Link` action's `pathParams`: ```html {{ title }} ``` Lowdefy builds the page's URL from its path, so this link opens `/tickets/support/1234` for a page with `path: tickets/{space}/{ticket_id}`. The page reads the values with [`_path_params`](/_path_params). Use single quotes around the attribute so the JSON can use double quotes. When `data-path-params` is not a JSON object, the link is not set and a warning is logged to the browser console. When `data-path-params` is a JSON object as written in your config, the build checks that it gives every placeholder of the page a value. An attribute that only becomes JSON when the HTML renders is checked when the link is set, and a missing value fails with an error naming the page and the placeholder. The build checks every `data-page-id` it can read in your config, including HTML built by API endpoints, and warns when a page does not exist, with a suggestion for a likely typo. Values built from templates, such as `data-page-id="{{ page }}"`, are not checked. Inside a module, build the page id with `_module.pageId` (for example in a `_string.concat`); the build checks the resolved id. Use `data-page-id` to write a link. When you already have a whole URL, for example a notification's stored `link` field, `data-link` opens it without reloading the app. The path is relative to the app, like the `Link` action's `url`, so Lowdefy adds the `basePath`. The build does not check `data-link` paths. ```html Ticket 42 ``` Only hrefs that start with a single `/` and have no `#` are handled; everything else stays a normal link. Plain `` links without `data-link` keep reloading the page, because paths like `/api/…` or files in `public/` are not app pages. `data-new-tab` opens any link in a new tab. HTML is sanitised, which removes `target` attributes, so write `data-new-tab` instead of `target="_blank"`: ```html Open report Help ``` `data-link="false"` and `data-new-tab="false"` turn the attribute off, so a template can write `data-new-tab="{{ new_tab }}"`. In `ClickableHtml`, a link that also has `data-event` fires the event and does not navigate. In an ag-grid cell, the grid's `onRowClick` still fires when a link in the row is clicked, as it does for `link` cells. ## Status tags and dots `data-tag` renders the element as a tinted tag, and `data-status` puts a coloured dot before its text. Both look the same as the ag-grid block's `tag` cells and follow the app theme and dark mode. ```html Approved On hold Syncing {{ category }} ``` The value is one of: - **A tone:** `success`, `processing`, `info`, `warning`, `error`, `default`, or an Ant Design preset colour: `red`, `volcano`, `orange`, `gold`, `yellow`, `lime`, `green`, `cyan`, `blue`, `geekblue`, `purple`, `magenta`. Tones use theme colours. - **A CSS colour:** `#1677ff`, `rgb(22 119 255)`, or a theme variable such as `var(--ant-color-primary)`. - **Anything else, or nothing:** the colour is picked from the value, or from the element's text when the attribute has no value, so the same text always gets the same colour. `data-tag="{{ category_id }}"` keeps a colour stable when the category is renamed. The text is the label, so colour is never the only signal. Override the look with your own styles: the built-in styles have no specificity, so any class, Tailwind utility or inline style wins. ## Dates and times `data-time` formats the date in the element's `datetime` attribute, or in its text when it has none, in the app's locale and the browser's time zone: ```html ``` | Value | Renders | |---|---| | `relative` | `3 hours ago`, kept current while the page is open. Hovering shows the exact date and time. | | `date` | `Sep 25, 2026` | | `datetime`, or no value | `Sep 25, 2026, 3:30 PM` | | `time` | `3:30 PM` | | anything else | a [dayjs format](https://day.js.org/docs/en/display/format), such as `D MMM YYYY` | The date can be an ISO string, anything dayjs parses, or epoch milliseconds (12 or more digits). A value that is not a date leaves the text as it is and logs a console warning, so put a readable fallback inside the element. Dates from the server are best sent as ISO strings, so they show in the reader's time zone instead of the server's. ## Numbers `data-format` formats the element's number, with the same formatting as the ag-grid block's `number` cells: ```html {{ total }} {{ ratio }} {{ views }} {{ size }} ``` | Value | Example | |---|---| | `number`, or no value | `1234.5` → `1,234.5` | | `currency` | `1234.5` with `data-currency="EUR"` → `€1,234.50` in English. `data-currency` (a three-letter currency code) is required. | | `percent` | `0.123` → `12.3%` (the value is a fraction) | | `compact` | `1500` → `1.5K` | | `bytes` | `1536` → `1.5 kB` (base 1000) | `data-decimals="2"` fixes the number of decimals. The text must be a plain decimal number such as `-1234.5`; anything else, like `1,234` or `N/A`, is left as it is. Formatted numbers use tabular figures, so columns line up. ## Avatars `data-avatar` renders a person's initials in a theme colour picked from their name, the same initials and colour as the ag-grid block's `avatar` cells. No image service is called. ```html {{ name }} ``` On an ``, the initials replace the image when it has no `src` or fails to load. The avatar is 24px by default; set `style="--lf-avatar-size: 40px; --lf-avatar-font-size: 16px"` to resize it. Next to the visible name the avatar is hidden from screen readers; on its own it is announced by the name. ## Copy to clipboard `data-copy` adds a copy button to the element. It copies the attribute's value, or the element's text when the attribute has no value: ```html {{ api_key }} Ticket #{{ ticket_id }} ``` After a copy the icon shows a check, its tooltip says "Copied" and screen readers hear "Copied". When the copied value differs from the text, the button's label shows it in full ("Copy: 42"), with invisible characters such as zero-width spaces and direction marks spelled out as `[U+200B]`, so the HTML cannot show one text and copy another unnoticed. A copy click only copies: it does not trigger a surrounding `data-event`, link or grid row click. Copying needs a secure (HTTPS or localhost) page. The button labels use the `client.copy`, `client.copyValue`, `client.copied` and `client.copyFailed` [translation keys](/i18n). ## Truncation `data-truncate` clamps text to a number of lines and ends it with an ellipsis. The full text shows in a tooltip, but only when the text is actually cut off: ```html
{{ title }}
{{ description }}
``` No value or `1` is one line; `2` to `6` clamp to that many lines. Truncation makes the element a block, so put it on block content such as a `div` or `p`, not on a word in a sentence. On a table cell or list item it breaks the table or list layout, so truncate a `
` inside the cell instead. A `data-copy` button on a truncated element is placed after it, so it is never cut off. Screen readers still read the full text. A `title` or `data-tooltip` on the element replaces the automatic tooltip. ## Text tone `data-tone` colours text with a theme tone, so it follows the theme and dark mode: ```html Added {{ created }} Failing ``` | Tone | Colour | |---|---| | `secondary` | Secondary text | | `tertiary` | Tertiary text, for supplementary details such as timestamps | | `quaternary` | Quaternary text, for placeholders such as an empty-value dash | | `success`, `warning`, `error`, `info` | Ant Design's status text colours | On the default light theme `tertiary`, `quaternary` and the status tones fall below WCAG AA contrast for body text. Use them for supplementary text and short labels whose words carry the meaning ("Failing"), never as the only way information is shown. On an element that also has `data-tag`, `data-tone` sets the text colour. In page config you can also use the Tailwind theme classes (`text-text-secondary`); in HTML built by templates, requests or API endpoints use `data-tone`, because Tailwind never sees those strings. ## Confirm before an event In `ClickableHtml`, `data-confirm` on a `data-event` element asks before the event fires: ```yaml - id: rows type: ClickableHtml properties: dataEvents: - onDelete html: > events: onDelete: - id: delete type: CallAPI params: endpointId: delete-row payload: id: { _event: id } ``` A click, Enter or Space opens a confirmation with the message, OK and Cancel. Only OK fires the event, once. Cancel, Escape and a click outside close it without firing. Focus moves to Cancel when it opens, so a stray key press never confirms; Tab to OK and press Enter to confirm. Focus returns to the element afterwards; after OK inside a popover, it returns to the popover's trigger. Clicking the element again while its confirmation is open keeps it open. With no value the message is "Are you sure?" (the `client.confirm` [translation key](/i18n)); OK and Cancel follow the app's locale. The event object includes `confirm` along with the element's other `data-*` attributes. A confirm inside a popover keeps the popover open until OK fires the event. `data-confirm` has no effect in `Html`, or on an element without `data-event`. `data-confirm` only asks where the markup says so. For an event that deletes or changes data, list it in `dataEvents` with `confirm`, so every click on it asks whatever the markup says: `confirm: true` asks with the element's `data-confirm` message or "Are you sure?", and a string is the message, in place of the element's. `confirm` asks whenever it is set to anything but `false`, so an empty message or one an operator could not find (`null`) still asks, with the element's or the default message. When the block's HTML changes while a confirmation is open, the confirmation closes without firing. An event's `data-*` values can name a record by its position (`data-index="{{ loop.index0 }}"`), and new HTML can put another record there, so OK could otherwise act on a record the user did not choose. Click the element again to confirm against the new HTML. ## Plugin blocks A block plugin that renders HTML gets these attributes by rendering through `renderHtml` or `HtmlComponent` from `@lowdefy/block-utils`. A block that runs its own sanitiser and sets `innerHTML` directly does not. --- url: https://docs.lowdefy.com/md/reference/cli.md --- # The CLI The Lowdefy CLI is used to develop a Lowdefy app locally, and to build Lowdefy apps for deployment. We recommend running the CLI using `pnpx`, to always use the latest version: ``` pnpx lowdefy@5 ``` or, to use a specific version: ``` pnpx lowdefy@version ``` Alternative, you can install the CLI globally or to a Javascript project (with a `package.json` file) via pnpm. To install the CLI globally run: ``` pnpm add -g lowdefy ``` The CLI can then be run using `lowdefy` as the executable name: ``` lowdefy ``` # CLI commands ## build The `build` command runs a Lowdefy build. This builds a production Lowdefy app in the server directory, which can then be started using the `lowdefy start` command. The options are: - `--config-directory `: Change the config directory. The default is the current working directory. - `--disable-telemetry`: Disable telemetry. - `--log-level `: The minimum severity of logs to show in the CLI output. Options are `debug`, `info`, `warn` or `error`. The default is `info`. - `--no-client-build`: Do not run the Vite client build, and only build the Lowdefy config. Used in some deployment scripts where the client build is done as a separate step. The previous `--no-next-build` flag still works as a deprecated alias. - `--ref-resolver `: Path to a JavaScript file containing a `_ref` resolver function to be used as the app default `_ref` resolver. - `--server-directory `: Change the server directory, the directory in which the production server is placed. The default is `/.lowdefy/server`. - `--skip-codemod-check`: Suppress warnings about pending codemod upgrades. - `--skip-plugin-checks`: Skip every [plugin build check rule](/lowdefy-schema#plugin-check-rules) for this run. Each build logs a warning that they were skipped. Built-in build checks still run. Use it while a fix ships for a plugin rule that is wrong or too strict. ## data list The `data list` command prints each [journey data set](/journey-data-sets) in `tests/data/` with the number of documents it loads and the users it names. - `--config-directory `: Change the config directory. The default is the current working directory. - `--disable-telemetry`: Disable telemetry. - `--log-level `: The minimum severity of logs to show in the CLI output. Options are `debug`, `info`, `warn` or `error`. The default is `info`. ## dev The `dev` command starts a Lowdefy development server, running locally. It can be accessed in a browser at [http://localhost:3000](http://localhost:3000). The CLI watches the file system, and rebuilds the app and reloads served pages every time a change is made to any of the files in the project directory. The `dev` command should not be used to serve a production app, the `build` and `start` commands should be used instead. - `--android`: _Experimental._ Open the app in Lowdefy Dev on an Android emulator, or on a phone connected by USB with USB debugging on. Starts the first virtual device when no device is connected, installs Lowdefy Dev, and connects it to the dev server through `adb reverse`, so the phone reaches it on `localhost`. Needs `adb` from the Android SDK (on your `PATH`, or under `ANDROID_HOME`). Until Lowdefy Dev builds are released, set `LOWDEFY_DEV_APP_ANDROID` to the path of a Lowdefy Dev `.apk`. - `--config-directory `: Change the config directory. The default is the current working directory. - `--dev-directory `: Change the dev directory, the directory in which the development server is placed. The default is `/.lowdefy/dev`. - `--disable-telemetry`: Disable telemetry. - `--exit-with-pid `: Stop the server when the process with this id exits. For test runners and scripts that start the server. The `LOWDEFY_EXIT_WITH_PID` environment variable does the same, and passes through wrappers such as `pnpm exec` or a secrets manager. - `--ios`: _Experimental._ Open the app in Lowdefy Dev on an iPhone simulator. Uses a booted simulator, or boots the newest iPhone, installs Lowdefy Dev and opens the dev server in it. Needs macOS with Xcode. Until Lowdefy Dev builds are released, set `LOWDEFY_DEV_APP_IOS` to the path of a Lowdefy Dev simulator build (`.app`). - `--lan [interface]`: _Experimental._ Let a phone on the local network connect. The server also listens on the first private network address, or on the network interface you name (for example `--lan en0`), and prints the interface it chose with a QR code to scan with Lowdefy Dev. The code carries a pairing token that lasts until the server stops; a request from another machine without it is refused. The dev tools and the headless user of the [agent tools](/ai-agent-docs) answer only on your own computer, even with the token. - `--log-level `: The minimum severity of logs to show in the CLI output. Options are `debug`, `info`, `warn` or `error`. The default is `info`. - `--mock-user [user]`: Start the dev server authenticated as a mock user, bypassing the login flow. Pass a JSON user object to set the identity and roles (e.g. `--mock-user '{"sub":"dev","roles":["admin"]}'`), or use the bare flag for a default user with no roles. This is the same mechanism as `auth.dev.mockUser`. Dev server only. See [Auth Configuration](/auth-configuration#mock-user-for-testing-dev-server-only). - `--no-open`: Do not open a new tab in the default browser. - `--port `: Change the port the server is hosted at. The default is `3000`. A port you set (with this option, the `PORT` environment variable, or `cli.port`) is used or the command fails; only the default moves to the next free port when it is taken. - `--ref-resolver `: Path to a JavaScript file containing a `_ref` resolver function to be used as the app default `_ref` resolver. - `--watch `: A list of paths to files or directories that should be watched for changes. Globs are supported. Specify each path to watch separated by spaces. - `--watch-ignore `: A list of paths to files or directories that should be ignored by the file watcher. Globs are supported. Specify each path to watch separated by spaces. - `--skip-codemod-check`: Suppress warnings about pending codemod upgrades. - `--skip-plugin-checks`: Skip every [plugin build check rule](/lowdefy-schema#plugin-check-rules) for this run. Each build logs a warning that they were skipped. Built-in build checks still run. Use it while a fix ships for a plugin rule that is wrong or too strict. The server stops when the `lowdefy dev` process stops, however it stops, including when it is killed outright. One app runs one dev server. The running server records itself in `.lowdefy/instance.json` (its pid, port, URL, state and whether a person or the [Lowdefy hub](#hub) started it), and a second `lowdefy dev` for the same app exits before touching anything, naming the server already running. A record copied from another checkout (for example into a git worktree) is ignored. ## emails The `emails` command previews the [notification](/notifications) emails your app renders, using [React Email](https://react.email/)'s preview server. It builds the app, renders each notification from its `testData`, and opens the preview so you can iterate on templates without sending real mail. It warns when a template renders a data key your `testData` is missing (which would show as an empty section), and errors if the app has no `notifications:` section. The preview tooling (`react-email`) is installed just-in-time by this command — it is never a dependency of your app or its production server. - `--config-directory `: Change the config directory. The default is the current working directory. - `--disable-telemetry`: Disable telemetry. - `--log-level `: The minimum severity of logs to show in the CLI output. Options are `debug`, `info`, `warn` or `error`. The default is `info`. - `--port `: Change the port the email preview server is hosted at. The default is `3001`. - `--ref-resolver `: Path to a JavaScript file containing a `_ref` resolver function to be used as the app default `_ref` resolver. - `--server-directory `: Change the server directory, the directory in which the production server is placed. The default is `/.lowdefy/server`. ## agent-setup The `agent-setup` command sets up a project for AI coding agents. Run it as `npx lowdefy@ agent-setup`. At the project root it writes `.mcp.json` registering the [`lowdefy` MCP server](/ai-agent-docs) — `lowdefy mcp`, run over stdio through npx at the version of the CLI running `agent-setup` — Lowdefy's guidance as a block between `` and `` markers in `AGENTS.md` (or in `CLAUDE.md` when the project has only that), a root `CLAUDE.md` importing `@AGENTS.md`, and Lowdefy's skills in both `.agents/skills/` and `.claude/skills/`. Existing files are merged, not overwritten: each run rewrites only the block, and refreshes a skill only while neither of its two copies was edited. Nothing it writes names an app, so every app in a repository writes the same files. When the CLI's version differs from the app's, it still writes, and warns with the `npx lowdefy@ agent-setup` command that matches them. It refuses apps on `lowdefy: local`. When several `package.json` scripts run `lowdefy dev`, it asks you to set [`cli.devScript`](#configuration). Before writing `.mcp.json` it runs the pinned version's `lowdefy mcp` through npx, and refuses a version npm cannot serve (unpublished, or from before `lowdefy mcp`); the refusal prints the `claude mcp add --scope local` command that runs your own build of the CLI for you only. See [Setting up a project](/ai-agent-docs). - `--config-directory `: Change the config directory. The default is the current working directory. - `--disable-telemetry`: Disable telemetry. - `--log-level `: The minimum severity of logs to show in the CLI output. Options are `debug`, `info`, `warn` or `error`. The default is `info`. - `--project-directory `: Change the directory where agent files are written. The default is the nearest ancestor directory containing `.git`. - `--user`: Instead of setting up this project, register `lowdefy mcp` as `lowdefy` for every Claude Code session of your user (`claude mcp add --scope user lowdefy -- npx ...`, or `cmd /c npx ...` on Windows), pinned to this CLI's version, replacing an earlier `lowdefy` or `lowdefy-docs` registration. Needs no app. The version must be published with `lowdefy mcp` (until Lowdefy 7 is released, `npx lowdefy@experimental agent-setup --user`). If the add fails, the existing registration stays and the error prints the commands to run. Without Claude Code on your `PATH`, it prints the entry to add to your agent client. ## mcp The `mcp` command runs the Lowdefy MCP server for coding agents over stdio. Agent clients start it themselves from `.mcp.json` or their user configuration (see `agent-setup`), one per session — you do not run it by hand. It routes every tool call to the dev server of the app and git checkout the agent is working in, and starts that server through the [hub](#hub) when it is not running. See [AI agent docs](/ai-agent-docs). ## hub The Lowdefy hub is a small per-user background process that runs dev servers for coding agents. `lowdefy mcp` starts it when an agent first needs a dev server, and it exits on its own after ten idle minutes. It gives each app its own port (from 4100 up, kept across restarts), runs the app's own dev script, and stops a server when no agent session has used it for 30 minutes and no browser tab has it open, or when its directory is removed. It only ever stops servers it started: a `lowdefy dev` you run in a terminal is used by agents but never stopped by them. - `lowdefy hub status`: List the dev servers the hub runs. - `lowdefy hub start [directory] [--restart] [--clean]`: Start (or return) an app's dev server through the hub. `--clean` deletes the build directory first. - `lowdefy hub stop [directory] [--all]`: Stop a dev server the hub runs, or all of them. - `lowdefy hub logs [directory] [--lines ] [--grep ]`: Print the recent output of a dev server the hub runs. - `lowdefy hub ps`: List the Lowdefy servers running on this machine (dev and production servers started with the CLI), the process that owns each, and whether that owner is gone. - `lowdefy hub prune [--kill]`: Stop Lowdefy servers whose owner is gone, such as a test server whose test run was killed. It only lists them unless `--kill` is passed. It also lists servers whose `lowdefy` CLI was left running after whatever started it was killed (for example a `pnpm exec` wrapper killed outright), and servers left running by an older Lowdefy version, in both cases only when nothing but package-manager wrappers is left above them, as well as a dev server's Vite process left running after an older dev server was killed. A server with a live owner (a terminal, a script still running, the hub) is never stopped. The hub prunes servers whose owner is gone each time it starts. - `lowdefy hub trust [directory]`: Let `lowdefy mcp` start and query the dev servers of the git repository `directory` is in from any agent session, not only sessions started in it. It covers all of the repository's git worktrees. Run it yourself, in an interactive terminal: it refuses without one, so an agent cannot trust a repository. It refuses a directory outside git. - `lowdefy hub untrust [directory]`: Undo `hub trust`. Also removes an entry whose directory is gone, given its old path. - `lowdefy hub trusted`: List the trusted repositories by their git directory (`.../repo/.git`), marking those that are missing. When a `package.json` in an app's checkout lists `lowdefy` but no `node_modules` up to the checkout root has it (a fresh git worktree, for example), `lowdefy hub start` and `lowdefy mcp` do not run its dev script; they answer with the install command to run, and where. See [AI agent docs](/ai-agent-docs). The hub keeps its state in `~/.lowdefy/hub`, and running servers record themselves in `~/.lowdefy/servers` (set `LOWDEFY_HOME` to move both). It starts servers with the script `cli.devScript` names, or the one `package.json` script that runs `lowdefy dev`, so wrappers such as a secrets manager apply to agent-started servers too. ## init The `init` command initializes a Lowdefy application. It creates the `lowdefy.yaml` and `.gitignore` in the working directory. - `--disable-telemetry`: Disable telemetry. - `--log-level `: The minimum severity of logs to show in the CLI output. Options are `debug`, `info`, `warn` or `error`. The default is `info`. ## init-docker The `init-docker` command initializes a Dockerfile in the config directory that can be used to build a Docker image of the Lowdefy app. - `--config-directory `: Change the config directory. The default is the current working directory. - `--disable-telemetry`: Disable telemetry. - `--log-level `: The minimum severity of logs to show in the CLI output. Options are `debug`, `info`, `warn` or `error`. The default is `info`. ## docker-output The `docker-output` command assembles a minimal production runtime at `.lowdefy/docker` from an already-built app. It traces the server's runtime dependency graph and copies only the files the server actually imports, keeping the Docker image small. Run it after `lowdefy build`; the Dockerfile created by `init-docker` runs it for you. - `--config-directory `: Change the config directory. The default is the current working directory. - `--server-directory `: Change the server directory. The default is `.lowdefy/server` in the config directory. - `--disable-telemetry`: Disable telemetry. - `--log-level `: The minimum severity of logs to show in the CLI output. Options are `debug`, `info`, `warn` or `error`. The default is `info`. ## init-vercel The `init-vercel` command initializes the installation scripts needed to deploy an app on [Vercel](https://vercel.com). It creates a directory called deploy, and a script called vercel.install.sh. It also creates a README file with instructions on how to configure Vercel. - `--config-directory `: Change the config directory. The default is the current working directory. - `--disable-telemetry`: Disable telemetry. - `--log-level `: The minimum severity of logs to show in the CLI output. Options are `debug`, `info`, `warn` or `error`. The default is `info`. ## test The `test` command runs the app's config tests — every journey in `tests/journeys/`, sub-folders included, except folders whose name starts with `_` (such as `_candidates/`). It starts the development server headless on a free port, runs every journey through the dev server's journey route, prints `PASS`/`FAIL` per journey with the failing step, stops the server, and exits with code `1` if any journey failed. A `PASS` line shows the journey's tier, rank, rate and failures over the usage window when it has production evidence. A journey with `deprecated: true` is skipped and listed as `SKIP deprecated`. See [Config Tests](/config-tests). - `--config-directory `: Change the config directory. The default is the current working directory. - `--dev-directory `: Change the dev directory, the directory in which the development server is placed. The default is `/.lowdefy/dev`. - `--disable-telemetry`: Disable telemetry. - `[paths...]`: Journey files, directories (read with their sub-folders) or quoted globs such as `'tests/journeys/review/**'` to run instead of the whole suite, anywhere under the config directory, candidates in `tests/journeys/_candidates/` included. A glob that matches nothing exits with code `1`. - `--tag `: Only run journeys whose `tags` include this tag. Repeat it to run the journeys carrying any of the tags. Exits with code `1` if no journey matches. - `--filter `: Only run journeys whose `name` contains this string (case-insensitive). Repeat it to run the journeys matching any of the strings. Exits with code `1` if no journey matches. - `--tier `: Only run the journeys in this popularity tier of the selection, ranked by recent production use: `common` (p50, the happy paths), `wide` (p80), `edge` (p95) or `full` (every journey, the default). Journeys with no counts for their current steps run in every tier. A tiered run never records as the suite's run. Exits with code `1` when the selection has fewer than 100 journey matches in the usage window, or no evidence. See [Usage and tiers](/config-tests#usage-and-tiers). - `--usage-window `: The calendar months recent use is ranked over, for `--tier` and the `PASS` line, such as `6m`. The default is `3m`. - `--lint`: Check the journeys for the lint rules (L1 placeholders, L2 unasserted actions, L3 fixed waits, L4 writes without data, L5 named data set users, L6 final assertion, L7 pointless `as`) and run nothing. Exits with code `1` on any lint error. See [Lint](/config-tests#lint). - `--log-level `: The minimum severity of logs to show in the CLI output. Options are `debug`, `info`, `warn` or `error`. The default is `info`. - `--port `: The port to start the development server on. If it is in use, the next free port is used. The default is `3000`. - `--ref-resolver `: Path to a JavaScript file containing a `_ref` resolver function to be used as the app default `_ref` resolver. - `--repeat `: Run each journey `n` times (1 to 10) and classify it `PASS`, `FLAKY` or `FAIL`. Exits with code `1` on any `FLAKY` or `FAIL`. See [Replaying candidates](/config-tests#replaying-candidates). - `--url `: Run the journeys against an already running development server (for example `--url http://localhost:3000` while `lowdefy dev` is running) instead of starting one. ## journeys harden The `journeys harden` command measures whether journeys fail when the feature they walk breaks. It runs each journey once, lists the config mutants on what the runs exercised, runs each mutant against the journeys that reached it, only in their own browsers, and prints progress lines as it goes, reports every mutant no journey noticed with its source line, and a score per journey, in `.lowdefy/test/mutation.json`. It exits with code `0` even with survivors. See [Mutants](/config-tests#mutants-lowdefy-journeys-harden). - `[paths...]`: Journey files, directories or globs to harden instead of the whole suite, as for `test`. - `--filter `: Only journeys whose `name` contains this string. - `--page `: Only mutants on these pages, and endpoint mutants a journey touching them called. Journeys whose last measured run touched none of the pages are left out of the baseline too (never when there is no record for them, the journey changed, or the record was measured on a different config; the log says which). Records match by a hash of the built config, not by server start, so run `lowdefy test` after your last config edit. - `--operators `: Only these operators, comma separated: `drop-action`, `skip-validate`, `flip-visible`, `swap-if`, `drop-payload`, `retarget-link`, `drop-block`, `drop-step`. - `--max `: Run at most `n` mutants. The default is `200`; `0` means no cap. - `--seed `: The sample's seed; another seed draws another sample. The default is `0`. - `--workers `: Journey runs at once, 1 to 16. The default is `4`. - `--mutant `: Run only this mutant against the journeys on its path, without replacing the report. - `--list`: Print the sampled mutants and a time estimate, and run nothing. - `--json`: Print the report as JSON. - `--url `, `--port `, `--config-directory`, `--dev-directory`, `--ref-resolver`, `--log-level`, `--disable-telemetry`: As for `test`. ## journeys variants The `journeys variants` command writes edge-case candidates of one journey — other roles (`role`), another organization (`tenant`), empty and large data (`empty`, `volume`), bad input (`negative`), a reload mid-flow (`interrupt`) and a double click (`double-submit`) — to `tests/journeys/_candidates/variants//-.yaml`, and replays each three times. See [Variants](/config-tests#variants-lowdefy-journeys-variants). - ``: The journey file to vary. - `--name `: The journey to vary, when the file holds several. - `--kinds `: Only these kinds, comma separated. The `role` and `tenant` kinds read the users and fixtures of the journey's data set. - `--empty-data `: The data set the `empty` variant runs on: one with no rows, and a user with the journey user's name. - `--volume-data `: The data set the `volume` variant runs on: one with many rows, and a user with the journey user's name. - `--no-run`: Write the variants without replaying them. - `--url `, `--port `, `--config-directory`, `--dev-directory`, `--ref-resolver`, `--log-level`, `--disable-telemetry`: As for `test`. ## journeys scope The `journeys scope` command prints what a change touched, for a coding agent exploring a pull request: each page the change reached, why (its own config, a request, an endpoint, a connection or a websocket it uses, an app-wide artifact; changes that travel through `_ref` show on every page that refs them), the blocks added, changed or removed, and who can open the page, with the data set users under `tests/data/` it admits. It builds the merge base and the working tree in full with the installed dev server's builder and needs no running dev server; when none is installed, it installs one first without starting it. See [Exploring a pull request](/explore-pull-request). - `--base `: Compare with the merge base of this branch or commit and `HEAD`, such as the pull request's base branch. Without it, every page of the head is listed. - `--json`: Print the scope as JSON instead of the summary. - `--config-directory`, `--dev-directory`, `--ref-resolver`, `--log-level`, `--disable-telemetry`: As for `test`. - `--skip-plugin-checks`: Skip every plugin build check rule in both builds. As for `build`. ## journeys session The `journeys session []` command reads the sessions the development server recorded in `.lowdefy/traces/dev/`, or, with `--source production`, the production cache [`journeys pull posthog`](#journeys-pull-posthog) writes. Without an id it lists them, newest first: the session id, its time span, the pages visited, and how many interactions it holds and how many failed. With an id it prints that session as a log, one line per interaction with what the app did in response, such as `click save → Validate failed [priority]`, to write journeys from. A production log never shows a typed value, and shows clicked text only when it is text from the app's config. An id not in the window exits with code `1`, naming the newest sessions. Agents connected to the development server read dev sessions with the `lowdefy_journey_session` tool. Set `LOWDEFY_DEV_RECORD=false` to turn dev recording off. See [Session logs](/config-tests#session-logs). - `--source `: `dev` (the default) or `production`. - `--since `: Dev: only sessions with records at or after this time, a duration back from now (`30m`, `2h`, `7d`) or an ISO date. Production: the window ending today, a number of days such as `30d` (the default) or a start date, at most 30 days. - `--from `, `--to `: Production only. An explicit window of whole UTC days instead of `--since`. - `--json`: Print the list or the log as JSON on stdout. - `--config-directory `: Change the config directory. The default is the current working directory. - `--dev-directory `: Change the dev directory, where the development server's build is read from. The default is `/.lowdefy/dev`. - `--disable-telemetry`: Disable telemetry. - `--log-level `: The minimum severity of logs to show in the CLI output. Options are `debug`, `info`, `warn` or `error`. The default is `info`. ## journeys pull posthog The `journeys pull posthog` command reads the app's production analytics from its PostHog project, one UTC day at a time, and writes them as interaction traces to `.lowdefy/traces/production/.jsonl`, with a `.manifest.json` beside each day. `journeys session`, `journeys evidence` and `journeys coverage` read that cache with `--source production`. See [Production journeys](/config-tests#production-journeys). It reads three environment variables (the app's `.env` file is loaded first): - `POSTHOG_PROJECT_ID`: The PostHog project the app sends its analytics to. - `POSTHOG_API_HOST`: The project's API host, an `https://` URL such as `https://eu.posthog.com`. There is no default, because the wrong region answers as if the key were wrong. - `POSTHOG_PERSONAL_API_KEY`: Your own PostHog personal API key, scoped to the project with the **Query Read** scope. Export it in your shell or put it in the app's `.env`; never commit it. It is sent only to `POSTHOG_API_HOST` and never logged or written to a manifest. A day older than yesterday is final: it is pulled once and skipped afterwards. Today and yesterday are pulled again on every run, because PostHog accepts late events. Day files older than 400 days are removed at the start of each pull. Person and organisation ids are hashed with a random salt kept in `.lowdefy/traces/production/salt`, which never leaves your machine. Only the pull creates the salt. `journeys session`, `journeys coverage` and `journeys evidence` read a day only when it was pulled under this salt: a day pulled under another one (the salt file deleted, or the cache copied from another machine) reads as missing, with the pull that fetches it again. Clicked text is never written. Each clicked element's text is stored as a token, a hash under the same salt, so clicks on the same text can be grouped and counted without it. `journeys session`, `journeys coverage` and `journeys evidence` turn a token back into text only when it is the hash of a string in the app's config: the built pages, menus, i18n messages, block plugins' default messages and antd's locale strings for the app's locales. They collect those strings with one full build of the app by the installed development server (run `lowdefy dev` once first), cached until the config changes. Any other text, such as a customer's name in a grid cell or a label built from values, stays a token. Pull, session and coverage read at most 30 UTC days at a time, and refuse a longer window; `journeys evidence` is not capped. The first pull or production read after upgrading from a version that stored clicked text deletes the day files pulled that way, any `tests/journeys/_candidates/production/` an earlier version wrote, and `.lowdefy/test/coverage.json`, which the next pull and coverage write again. Committed journeys are never changed. - `--since `: The days to pull, ending today: a number of days such as `30d`, or a start date. The default is `30d`, and the longest window is 30 days. - `--from `, `--to `: An explicit window of whole UTC days instead of `--since`, at most 30 days. Pass both. - `--environment `: Only events whose `environment` super property is this, for a project that receives several environments. By default nothing is filtered. - `--include-test-accounts`: Include the events the project's test-account filter leaves out. - `--org-property `: The person property holding the organisation id. The default is `org_id`. - `--roles-property `: The person property holding the roles. The default is `roles`. - `--page-size `: Rows read per query, at most `50000`. The default is `10000`. - `--max-rows `: Stop before the pull would read more rows than this, and say to shorten `--since`. The default is `500000`. - `--refetch`: Pull final days again. - `--config-directory `: Change the config directory. The default is the current working directory. - `--disable-telemetry`: Disable telemetry. - `--log-level `: The minimum severity of logs to show in the CLI output. Options are `debug`, `info`, `warn` or `error`. The default is `info`. When PostHog rate limits the pull for longer than a minute, or the project's hourly query budget for personal keys runs out, the pull stops, keeps the days it has written, and says when to run it again. The next run picks up where it stopped. ## journeys evidence The `journeys evidence` command works out how much production use backs each journey in `tests/journeys/`, by calendar month: the sessions that did what the journey does, the people and organisations behind them, and how many of them failed. It reads every final day of the production cache (today and yesterday are not final until a later pull), not a window, and gaps in the cache are fine. A month already counted from at least as many final days is not read again, so a routine refresh reads the current month. Days pulled with different filters (project, environment, test accounts) are refused. See [Evidence](/config-tests#evidence). A journey's click text counts only when it is text from the app's config, as production text is read: a journey clicking a grid cell by a customer's name is backed exactly as one clicking it with no text, so no command confirms whether production showed a value. Without `--refresh` it prints what would change and writes nothing. With `--refresh` it rewrites the `evidence` key of each journey whose numbers changed, and nothing else in the file. It is the only command that writes `evidence`. When a journey's steps change what it matches in production, its counted months move to a deprecated flow that is still counted, and the new flow is counted from the cache. Afterwards it lists the journeys no production session backs over the last 3 months, beside their mutation numbers. It never removes a journey. - `--refresh`: Write the changed `evidence` keys. - `--source `: Where use is read from. Only `production` for now, the default. - `--config-directory`, `--dev-directory`, `--disable-telemetry`, `--log-level`: As for [`journeys session`](#journeys-session). ## journeys usage The `journeys usage` command reports which journeys real use leans on most. It ranks the journeys in `tests/journeys/` by their recent rate: their production sessions over the final days their months hold in the usage window, the last 3 calendar months ending at the newest month any selected journey has. Each journey shows its popularity tier, its sessions and failures over the window and all time, a line per month with that month's persons and organisations, and its deprecated flows with their recent use. Below that it lists the production flows no journey covers, from the report [`journeys coverage`](#journeys-coverage) writes, ranked by their sessions in coverage's window and not tiered, when coverage grouped them into flows; otherwise it says why it lists none. See [Usage and tiers](/config-tests#usage-and-tiers). A tier is a cut through the selected journeys, ranked by rate, most first: `common` holds the shortest run of journeys reaching 50% of the summed rates, `wide` 80% and `edge` 95%, and `full` holds every journey. Tiers nest and never split journeys with equal rates. A journey edited since the last refresh, or never refreshed, is `unranked` and in every tier. A journey with `deprecated: true` is in no tier. Fewer than 100 journey matches in the window refuses every tier but `full`. - `[paths...]`, `--filter `, `--tag `: The journeys to report on, selected as [`test`](#test) selects them. Tiers are cut over this selection alone. - `--tier `: Only the journeys in `common`, `wide`, `edge` or `full` (the default, every journey). - `--usage-window `: The calendar months recent use is ranked over, such as `3m`, the default. - `--json`: Print the report as JSON, for agents. - `--config-directory`, `--dev-directory`, `--disable-telemetry`, `--log-level`: As for [`journeys session`](#journeys-session). ## journeys coverage The `journeys coverage` command reports what real use no journey in `tests/journeys/` covers yet, five ways, each with its uncovered items ranked by use, and writes the report with the production profile to `.lowdefy/test/coverage.json`. A journey's click text counts only when it is text from the app's config; any other text in a journey reads as no text, so the click matches that block and column's clicks. A journey with `deprecated: true` is never run, so it covers nothing and is left out. When the development server recorded a full test run, the interaction measure adds the share that run drove, and failure coverage counts only failures a passing journey produced. The flow measure groups sessions into flows only when the window holds 100,000 rows or more; below that it is left out, the summary says why and points to [`journeys session --source production`](#journeys-session), and the report's `flowGrouping` records it. See [Coverage](/config-tests#coverage). - `--group`: Group sessions into flows whatever the window holds. - `--no-group`: Never group sessions into flows. - `--json`: Print the report as JSON instead of the summary. - `--source `: Where use is read from. Only `production` for now, the default. - `--since `, `--from `, `--to `: The production window, as for [`journeys pull posthog`](#journeys-pull-posthog), at most 30 days. - `--config-directory`, `--dev-directory`, `--disable-telemetry`, `--log-level`: As for [`journeys session`](#journeys-session). ## mobile > **Experimental.** The `mobile` commands, and `lowdefy dev --ios`, `--android` and `--lan`, need `config.experimental.mobile: true` in `lowdefy.yaml`: without it the build fails on the `mobile` section. Commands and flags may change in a minor release. See [Mobile Apps](/mobile-apps). The `mobile` commands build, submit and update the app's phone shells with [EAS](https://expo.dev/eas), from your own Expo, Apple and Google accounts. A shell is declared under `mobile.shells` in `lowdefy.yaml`. Each command builds the app, then writes the shell's Expo project to `.lowdefy/mobile//project`: a copy of the published `@lowdefy/shell` at the app's Lowdefy version, with its own install, plus `shell.json`, `eas.json` (a build and submit profile per environment in `config.environments` that has a `url`, and a `development` profile) and the icon. Each build profile pins EAS to the pnpm version that installed the project, and reads EAS's `production` environment variables for an environment named `prod` or `production`, `preview` for any other. Each submit profile carries the shell's `ios.ascAppId`, the App Store Connect app id, so `submit` and `release` run without asking for it. The folder is generated; do not commit it. Every EAS command runs from it, through `npx eas-cli@` with `EAS_NO_VCS=1`, so nothing needs to be installed globally. ``` lowdefy mobile config [--shell ] [--env ] [--dry-run] lowdefy mobile build [--shell ] [--env ] [--platform ios|android|all] [--profile development] [--non-interactive] [--local] [--dry-run] lowdefy mobile submit [--shell ] [--env ] [--platform ios|android|all] [--non-interactive] [--dry-run] lowdefy mobile update [--shell ] [--env ] [--platform ios|android|all] [--rollout | --promote | --rollback] [--dry-run] lowdefy mobile release [--shell ] [--env ] [--platform ios|android|all] [--non-interactive] [--no-wait --auto-submit] [--rollout ] [--dry-run] lowdefy mobile checklist [--shell ] [--env ] ``` Every `mobile` command takes: - `--shell `: The shell to work on, a `mobile.shells` id. Needed when the app declares more than one. - `--env `: The environment, a key of `config.environments`. The binary opens its `url` and listens on the EAS channel of the same name. The default is `production`. - `--dry-run`: Print the EAS commands, with their working directory and environment variables, and run none. The project is still written. - `--config-directory`, `--disable-telemetry`, `--log-level`, `--ref-resolver`, `--server-directory`, `--skip-plugin-checks`: As for [`build`](#build). ### mobile config Writes the shell's project and prints its runtime version, `-`, or `--` for a shell with [plugins](#shell-plugins). Over-the-air updates reach only binaries with the same runtime version, so it changes only when the shell's native code, capability profile or plugins do. For a shell with plugins, it first reads each plugin's published `package.json` (`pnpm view`, from the app's directory, so the registries in the app's `.npmrc` apply) and checks its peers. It then adds each plugin and its native peers to the project's dependencies, writes the plugins into `shell.json`, and, after the install, refuses plugins the shell's profile must not take and type names that clash. Removing a plugin from `lowdefy.yaml` removes it from the project on the next run. - `--dry-run`: Read and check the plugins, print the dependencies it would add and the install, and run nothing. ### mobile build Raises the store version, then runs `eas build --profile `. The shell's `version` gives the major and minor; each build reads the latest finished build on the channel for each store and uses the next patch, or `..0` for a first build or a new `version`. Both stores get the same version. Build numbers are kept by EAS. - `--platform `: `ios`, `android` or `all`. The default is `all`. - `--non-interactive`: Run without prompts, with the credentials stored in EAS, and queue the build without waiting for it. For CI. Without it the build is interactive, as the first build of a shell must be. - `--local`: Build on this machine (`eas build --local`; a Mac for iOS). - `--profile development`: Build a development client of the shell, for internal distribution, which connects to `lowdefy dev`. It takes no store version. ### mobile submit Uploads the latest build to TestFlight and Google Play internal testing (`eas submit --latest`), and nothing more. Review and promotion are steps for a person (see [`mobile checklist`](#mobile-checklist)). - `--platform `, `--non-interactive`: As for `mobile build`. ### mobile update Sends the shell's JavaScript to installed binaries on the environment's channel as an over-the-air update (`eas update`). Only `@lowdefy/shell` code travels this way; the app's pages always come from its server. The update's message records the shell version, profile and runtime version: `lowdefy-shell@ profile= runtime=`. - `--rollout `: Send the update to this percentage of devices. - `--promote`: Roll the latest update for this runtime version out to every device. - `--rollback`: Republish the update before the latest one for this runtime version. A later `release` of the same Lowdefy version sends the newer shell again, so roll back while you fix forward. - `--platform `: As for `mobile build`. ### mobile release What CI runs. For each store it reads the latest finished build on the channel and compares its runtime version with the shell's: - No build, or a different runtime version: raise the store version, build and submit. - The same runtime version, and the latest update carries a different shell version, or none: send an update. - Otherwise: nothing. It prints one line per store saying which and why, and exits with 0. Any EAS failure exits with 1. Release always runs EAS without prompts, so the first build and submission of a shell are done by hand first. - `--no-wait --auto-submit`: Queue each build and let EAS submit it when it finishes, so the CI job does not wait. `--no-wait` needs `--auto-submit`; without them release waits for each build, then submits it. - `--rollout `: Passed to the update. - `--platform `: As for `mobile build`. `--non-interactive` is accepted and changes nothing. - `--dry-run`: Print the reads, and for each store both paths with the condition that picks each. ### mobile checklist Prints, filled in with the shell's own ids, the steps the stores still need a person for: creating the store records, the first build and submission, making a `private` app private, copying the Play app signing key into `android.sha256`, the privacy questions for the shell's profile and plugins, what each shell plugin declares, and, for `private` and `store` shells, review and promotion of each native release. It runs no EAS command. ### Environment variables - `EXPO_TOKEN`: Signs EAS in without `eas login`, for CI. - `SENTRY_AUTH_TOKEN`: Optional. Uploads source maps on `build` and `update`. Both pass through from your environment and are never written into the project. ### Shell plugins A shell can carry native code its capability profile does not, such as a Bluetooth printer or a device-management SDK, by listing Lowdefy shell plugins under `mobile.shells[].plugins`. Each is an npm package that declares the native modules, config plugins, permissions, privacy reasons and collected data it brings. ```yaml plugins: - name: '@example/lowdefy-printer' version: 2.1.0 mobile: shells: - id: field profile: field plugins: - name: '@example/lowdefy-printer' version: 2.1.0 typePrefix: Printer - name: '@example/lowdefy-mdm' version: 1.0.3 options: tenant: field-team ``` Each entry takes: - `name: string`: __Required__ - The npm package name, from the public registry or one set in the app's `.npmrc`. - `version: string`: __Required__ - An exact version, like `2.1.0` or `2.1.0-beta.1`. Ranges, dist-tags, git, `file:` and `workspace:` are refused, so one name and version is always the same native code. - `typePrefix: string`: A prefix added to the plugin's type names, as for app [plugins](/lowdefy-schema). - `options: object`: Passed to the plugin's config plugins. Options are baked into the binary and uploaded to EAS, so they never hold secrets. The build checks that: - A shell lists each plugin once. - A package has one version across root `plugins` and every shell's `plugins`, since the app runs one version of a plugin. - A mobile page uses a plugin's native blocks and actions only when every shell lists that plugin, since every shell opens every mobile page. The error names the page, the block or action and the shell that lacks the plugin. - A plugin's native block or action type is not named like a built-in native type, such as `Box` or `SetState`. Set a `typePrefix`. A shell with plugins gets its own runtime version, so it builds and updates on its own instead of sharing its profile's builds and updates. A change of a plugin's version or options needs a native build. `mobile config` installs the plugins into the shell's project. It fails, naming the shell and plugin, when: - The package or version is not on the registry. - The plugin's `expo` peer range does not include the shell's Expo version. - A native peer is not an exact version, or a peer (`expo` included) is not marked optional. - The plugin links a native module the shell already carries in a capability profile, such as `expo-camera`. Pick the profile that carries it. - The plugin asks for a permission the shell's profile leaves out, such as the camera on `basic`. The error names the profile to pick. - A plugin type name clashes with a built-in native type or another plugin's. Set a `typePrefix`. `mobile checklist` lists each plugin with the permissions, privacy reasons and data it declares, and joins its data to the privacy answers. It reads the plugins from the configured project, so run `mobile config` first. #### Writing a shell plugin A Lowdefy shell plugin is an npm package that exports: - `./shell`: CommonJS, an object with the native code it brings: `capabilities`, `modules` (`{ [package]: nativeModuleName }`), `plugins` (Expo config plugin entries, or a function of the entry's `options` that returns them), `permissions` (iOS `Info.plist` keys and Android permissions), `privacyReasons` (`{ category, reasons }` for each required-reason API) and `dataCollected` (`{ type, purpose }`). - `./native`: Optional. Its native block and action types, as named exports `blocks` and `actions`. - `./types`: Optional. Its type names, with the native ones under `native: { blocks, actions, blockMetas, icons }`, so the build knows them. A native-only plugin, such as a wrapped device-management SDK, leaves it out and adds no types. A plugin in root `plugins` must export it. Its native modules are `peerDependencies` at exact versions, beside an `expo` peer range, and every peer is marked optional in `peerDependenciesMeta`, so the web server's install leaves the native code out: ```json { "name": "@example/lowdefy-printer", "version": "2.1.0", "exports": { "./shell": "./shell.cjs", "./native": "./native.js", "./types": "./types.js" }, "peerDependencies": { "expo": ">=58.0.0 <59.0.0", "react-native-ble-plx": "3.5.0" }, "peerDependenciesMeta": { "expo": { "optional": true }, "react-native-ble-plx": { "optional": true } } } ``` `options` are written into `shell.json`, uploaded to EAS and baked into the binary, so they never hold secrets. A plugin that needs a secret reads an EAS environment variable in its config plugin. An SDK on Apple's list of commonly used SDKs also needs its own signed privacy manifest inside the SDK. ## upgrade The `upgrade` command upgrades a Lowdefy app to a newer version by walking you through migration prompts that handle breaking changes. It fetches the latest `@lowdefy/codemods` package, resolves the version chain from your current version to the target, and presents migration prompts phase by phase. ``` pnpx lowdefy@5 upgrade pnpx lowdefy@5 upgrade --to 6.0.0 ``` - `--to `: Target version to upgrade to. Defaults to the latest stable release. - `--plan`: Show the upgrade plan without executing. - `--resume`: Resume a previously interrupted upgrade. - `--config-directory `: Change the config directory. The default is the current working directory. - `--log-level `: The minimum severity of logs to show in the CLI output. Options are `debug`, `info`, `warn` or `error`. The default is `info`. ### How it works 1. Reads your current version from `lowdefy.yaml`. 2. Queries npm for the latest stable Lowdefy version (or uses `--to`). 3. Fetches `@lowdefy/codemods@latest` — the codemods package containing all migration prompts. 4. Resolves the upgrade chain — which intermediate versions need codemods. 5. Presents migration prompts phase by phase. Each prompt can be copied to clipboard for use with any AI coding tool, viewed as a manual guide, or skipped for later. 6. Updates `lowdefy.yaml` after each phase and suggests a git commit. See the [Upgrade Guide](/upgrade-guide) for more detail on how the upgrade system works. ## start The `start` command starts a Lowdefy production server. To start a Lowdefy server, tha app should first be built using the `build` command. - `--config-directory `: Change the config directory. The default is the current working directory. - `--disable-telemetry`: Disable telemetry. - `--exit-with-pid `: Stop the server when the process with this id exits. For test runners and scripts that start the server. The `LOWDEFY_EXIT_WITH_PID` environment variable does the same, and passes through wrappers such as `pnpm exec` or a secrets manager. - `--log-level `: The minimum severity of logs to show in the CLI output. Options are `debug`, `info`, `warn` or `error`. The default is `info`. - `--port `: Change the port the server is hosted at. The default is `3000`. - `--server-directory `: Change the server directory, the directory in which the production server is placed. The default is `/.lowdefy/server`. The server stops when the `lowdefy start` process stops, however it stops, including when it is killed outright. A server run directly with `node`, as in a Docker image, is not affected. #### Examples Run the dev server, watching a relative directory for file changes: ```txt pnpx lowdefy@5 dev --watch ../other-project ``` Run the dev server, ignoring the public directory: ```txt pnpx lowdefy@5 dev --watch-ignore public/** ``` # Module Fetching When your app uses [modules](/modules), the build automatically fetches module sources during the build phase. GitHub modules are downloaded as tarballs and cached in `.lowdefy/modules/`. Multiple module entries from the same monorepo share a single cache entry. Local `file:` sources are read directly from disk. For private GitHub repositories, set the `GITHUB_TOKEN` environment variable: ``` GITHUB_TOKEN=ghp_your_token_here ``` Without `GITHUB_TOKEN`, the CLI uses the token of the `gh` CLI when one is signed in. To read a private repository with an SSH key, such as a read-only deploy key, set `GITHUB_SSH_KEY` to the private key. When the GitHub API cannot find a repository or refuses the token, the build fetches it with `git` over SSH using that key. See [modules](/modules) for details. In development mode (`lowdefy dev`), local module sources (`file:` paths) are watched for changes and trigger rebuilds automatically. # Configuration All the CLI options can either be set as command line options, or the `cli` config object in your `lowdefy.yaml` file. Options set as command line options take precedence over options set in the `lowdefy.yaml` file. The config in the `lowdefy.yaml` cannot be referenced using the `_ref` operator, but need to be set in the file itself. Options set in the `lowdefy.yaml` should be defined in camelCase. The options that can be set are: - `devDirectory: string`: Change the dev directory, the directory in which the development server is placed. The default is `/.lowdefy/dev`. - `devScript: string`: The `package.json` script the [hub](#hub) runs to start this app's dev server for coding agents. Required when several scripts run `lowdefy dev`. - `disableTelemetry: boolean`: Disable telemetry. - `logLevel: enum`: The minimum severity of logs to show in the CLI output. Options are `debug`, `info`, `warn` or `error`. The default is `info`. - `noOpen`: Do not open a new tab in the default browser. - `port: number`: Change the port the server is hosted at. The default is `3000`. - `refResolver: string`: Path to a JavaScript file containing a `_ref` resolver function to be used as the app default `_ref` resolver. - `serverDirectory: string`: Change the server directory, the directory in which the production server is placed. The default is `/.lowdefy/server`. - `watch: string[]`: A list of paths to files or directories that should be watched for changes. - `watchIgnore: string[]`: A list of paths to files or directories that should be ignored by the file watcher. Globs are supported. - `skipCodemodCheck: boolean`: Suppress warnings about pending codemod upgrades during build and dev. The `--config-directory` option cannot be set from the `lowdefy.yaml` file. # Telemetry The CLI collects usage and error information to help us fix bugs, prioritize features, and understand how Lowdefy is being used. All telemetry can be disabled by setting the `disableTelemetry` flag in `cli` config object in your `lowdefy.yaml` file (this cannot be a reference to another file), or by using the `--disable-telemetry` command line flag.: ###### `lowdefy.yaml` ```yaml lowdefy: LOWDEFY_VERSION cli: disableTelemetry: true ``` We collect the following information: - The CLI version. - The Lowdefy version of your app. - A random local app id (stored locally in your project folder at `.lowdefy/cli.json`). - The CLI command used. - Your IP address. - Error messages and stack traces for any errors. --- url: https://docs.lowdefy.com/md/concepts/lowdefy-api.md --- # Lowdefy APIs Lowdefy APIs allow you to create custom server-side API endpoints within your Lowdefy application. These endpoints can execute complex server-side logic, orchestrate multiple database or external API calls, perform data transformations, and implement business workflows that require server-side processing. APIs are particularly useful when you need to: - Coordinate multiple requests in a specific sequence. - Implement complex business logic that should run on the server. - Transform or aggregate data from multiple sources. - Create reusable endpoints that can be called from multiple pages. - Build webhook endpoints for external services. - Serve private files at stable links, such as inline images. - Keep sensitive operations and data on the server. ## How Lowdefy APIs Work Lowdefy APIs are defined at the root level of your configuration, similar to connections and pages. Each API endpoint consists of a `routine` i.e. a sequence of steps and control structures that define the execution flow. When an API endpoint is called from the client using the [`CallAPI`](/CallAPI) action, the routine executes on the server with access to connections, secrets, and server-side operators. The API execution flow: 1. Client calls the API endpoint using the [`CallAPI`](/CallAPI) action with an optional payload. 2. Server checks authorization based on the auth configuration in `lowdefy.yaml`. 3. The endpoint routine executes sequentially (or in parallel where specified). 4. Each step can make requests to connections, transform data, or control flow. 5. The endpoint returns a response to the client. ## API Endpoint Definition API endpoints are defined in the `api` array at the root of your Lowdefy configuration. Each endpoint should have an `id` that is unique among all API endpoints in the app. The `endpointId` is used by the [`CallAPI`](/CallAPI) action to specify which endpoint to execute. The schema for a Lowdefy API is: - `id: string`: **Required** - A unique identifier for the API endpoint. - `type: string`: **Required** - Either `Api` (callable from client pages and other endpoints) or `InternalApi` (callable only from other endpoints, not from client pages). - `routine: array/object`: **Required** - The routine to execute. **Operators are evaluated**. - `async: boolean`: **Optional** - Respond with `{ accepted: true }` immediately and run the routine in the background. See [Async Endpoints](#async-endpoints). - `payloadSchema: object`: **Optional** - A JSON Schema every payload sent to this endpoint must match. Declaring it turns validation on for every caller, and it is required before an endpoint can be exposed as an MCP or agent tool. See [Payload Schema](#payload-schema). - `webhook: boolean`: **Optional** - Make this endpoint a third-party webhook receiver — it takes the HTTP request raw instead of the CallAPI envelope. See [Webhook Endpoints](#webhook-endpoints). - `link: boolean`: **Optional** - Serve this endpoint at a stable link: a `GET` on `/api/endpoints//` runs the routine as the signed-in caller and redirects to the URL it returns. See [Link Endpoints](#link-endpoints). - `schedules: array | object`: **Optional** - Cron schedules that run the routine on a timer. See [Scheduled Endpoints](#scheduled-endpoints-cron). Each item is an object with a `cron` expression and an optional `payload` object. With `config.environments` declared, `schedules` can instead be an object keyed by environment name (plus an optional `default`), see [Schedules per environment](#schedules-per-environment). ###### API definition example: ```yaml lowdefy: '7.1.0' connections: - id: users type: MongoDBCollection properties: collection: users databaseUri: _secret: MONGODB_URI api: - id: get_user_data type: Api routine: - id: fetch_user type: MongoDBFindOne connectionId: users properties: query: user_id: _payload: user_id - :return: _step: fetch_user pages: # ... your pages ``` ## Authentication and Authorization Lowdefy APIs use the same [authorization configuration](/protected-pages-apis) as pages. By default, API endpoints are public (accessible without authentication), just like pages. Authorization is configured in the `auth.api` section of the `lowdefy.yaml` file. Unlike requests which are defined on a specific page and inherit the authorization of the page they are defined on, API endpoints can be called from any page in the app. Thus they do not inherit the authorization of the page they are called from, and authorization should be configured separately. ```yaml lowdefy: '7.1.0' auth: # Configure which APIs require authentication api: protected: true public: - health_check roles: admin: - admin_api - manage_users manager: - manager_reports - team_data api: # Your API definitions # ... ``` ## Scheduled Endpoints (Cron) An endpoint can declare `schedules` to run its routine on a timer instead of (or in addition to) being called from the client. This is currently supported on [Vercel deployments](/vercel), where each schedule becomes a native Vercel cron job. ```yaml api: - id: purge_stale_conversations type: Api schedules: - cron: '0 6 * * *' payload: { mode: full } - cron: '*/15 * * * *' payload: { mode: incremental } routine: - id: purge type: MongoDBDeleteMany connectionId: conversations properties: filter: updatedAt: $lt: _payload: cutoff ``` Each schedule item has: - `cron: string`: **Required** - A 5-field cron expression (`minute hour day-of-month month day-of-week`), evaluated in **UTC**. Named values like `MON`/`JAN` are not supported, and day-of-month and day-of-week are mutually exclusive (one must be `*`). Cron expressions must be unique within an endpoint. - `payload: object`: **Optional** - The payload passed to the routine for this schedule, available via the `_payload` operator. Different schedules can pass different payloads to the same routine. When a schedule fires, the routine runs as a **system context**: there is no authenticated user, so `_user` is `undefined`. The routine still has full access to connections, requests, operators and secrets — write scheduled routines so they do not depend on a logged-in user. Because cron delivery is best-effort and not retried, design scheduled routines to be idempotent. To test a scheduled routine locally, run it as a system context with the dev server's `lowdefy_run_endpoint` MCP tool with `system: true` — see [Docs for AI Agents](/ai-agent-docs#running-endpoints). See [Deploy with Vercel](/vercel) for how schedules become cron jobs, how to secure them with `CRON_SECRET`, and the applicable plan limits. ### Schedules per environment Vercel fires cron jobs only on the **production** deployment, so a staging or develop deployment never runs its schedules on its own. Declare your deployment environments once under [`config.environments`](/deployment-environments) and set `LOWDEFY_ENVIRONMENT` on each deployment. Every environment with a `cron.secret` has its schedules registered on production too; when one fires, production pings that environment's own `/api/cron/` at its `url` (fire-and-forget) so the environment runs its own code. `cron.secret` is the **name** of a Lowdefy secret, a plain string — not a `_secret` operator. ```yaml config: environments: prod: url: https://example.com # Vercel fires crons here staging: url: https://staging.example.com cron: secret: STAGING_CRON_SECRET # Lowdefy secret holding staging's CRON_SECRET develop: url: https://develop.example.com cron: secret: DEVELOP_CRON_SECRET enabled: false # register no crons for develop ``` With environments declared, `schedules` can be keyed by environment name. A `default` list applies to every environment that has no entry of its own, and an empty list turns the crons off for that environment: ```yaml api: - id: purge_stale_conversations type: Api schedules: default: - cron: '*/15 * * * *' staging: - cron: '0 * * * *' # hourly on staging develop: [] # off on develop routine: [] ``` A plain array still works and applies to every environment. Environment names in `schedules` must be declared in `config.environments` (`default` is reserved). A build with no current environment runs the `default` schedules. This shape also works through module vars: a module endpoint with `schedules: { _module.var: tick_schedule }` needs no change — the app sets `tick_schedule: { default: [...], staging: [...] }` in its module vars (a module var that pins `type: array` for its schedule var must drop the pin to accept the keyed form). The production deployment needs one secret per forwarded environment: the environment variable `LOWDEFY_SECRET_` (for example `LOWDEFY_SECRET_STAGING_CRON_SECRET`) set to that environment's `CRON_SECRET`. Forwarded requests carry the firing cron in `x-vercel-cron-schedule` and the environment name in `x-lowdefy-cron-environment`; the target resolves its own schedules from those. See [Deployment environments](/deployment-environments) for the other environment settings and [Deploy with Vercel](/vercel) for the routes involved. ## Payload Schema An endpoint can declare a `payloadSchema` — a [JSON Schema](https://json-schema.org/) describing the payload it accepts. A declared schema is a contract, not documentation: **every** payload is validated against it before the routine starts, whatever the caller is — the [`CallAPI`](/CallAPI) action from a page, an MCP `tools/call`, an [agent tool call](/ai-agent-docs), a nested `CallApi` from another endpoint (including a [detached](#detached-endpoint-calls) one), or a scheduled run's `schedule.payload`. Every `_payload` read in the routine can then rely on the shape. ```yaml api: - id: create_order type: Api payloadSchema: type: object required: [sku, quantity] properties: sku: { type: string } quantity: { type: number, minimum: 1 } routine: - id: insert type: MongoDBInsertOne connectionId: orders properties: doc: sku: _payload: sku quantity: _payload: quantity ``` A payload that does not match is refused before the routine runs, with a message naming the endpoint, the failing location and the reason, for example: ``` Payload for endpoint "create_order" does not match its payloadSchema at /quantity: must be number. ``` The reason says what would be accepted where the schema says it: an `enum` miss lists the allowed values, an `additionalProperties: false` miss names the unexpected properties, and a `pattern` miss quotes the pattern. When a location fails both its `type` and one of `enum`, `pattern`, `required` or `additionalProperties`, that keyword is reported. Further failures are counted at the end, e.g. `(and 2 more)`. ``` Payload for endpoint "create_order" does not match its payloadSchema at /status: must be equal to one of the allowed values (draft, placed). Payload for endpoint "create_order" does not match its payloadSchema at (root): must NOT have additional properties (colour). ``` Who built the payload decides what kind of error the refusal is. - **An outside caller** sent it: an API client calling `POST /api/endpoints/` without naming a page, an MCP tool call, or an agent tool call (the model wrote the arguments). The refusal is the caller's mistake, not a fault: a `UserError`, logged at warn level only and never reported as a server error. - The REST call answers `400` with `{ name: 'UserError', message }`. - The MCP tool call answers `isError: true` carrying the same message, so the model can correct the arguments and retry. - The agent tool call surfaces the message to the model as a tool error. - **The app's own config** built it: a page's [`CallAPI`](/CallAPI) action, a nested `CallApi` step (including a [detached](#detached-endpoint-calls) one), a Dynamic block's endpoint, an auth or agent hook, or a scheduled run's `schedule.payload`. The config breaks its own endpoint's contract, so the refusal is a `ConfigError`: logged as a server error with its config location, and a journey that triggers it fails. - A page's `CallAPI` fails with the `ConfigError`; its `catch` actions see `_error.name` as `ConfigError`, and outside development the message is the generic server error message. - A nested `CallApi` step fails its routine, a detached target fails its run, and a scheduled run fails, each with the message in the logs. There is **no opt-out** — no `validate: false`, no strict mode, no per-caller exemption. If you do not want a payload validated, do not declare a `payloadSchema`. The schema cannot be combined with `webhook`: a webhook routine receives the raw `{ body, query, headers }` transport envelope, never the `payloadSchema` shape, so declaring both is a build error. Validate a webhook body with a [`ValidateSchema` step](#validating-data-as-a-routine-step) instead. ## Async Endpoints An endpoint with `async: true` responds with `{ accepted: true }` immediately and runs its routine in the background. The caller does not wait for the routine to finish — the same way `async: true` [actions](/events-and-actions) do not block the rest of an action chain. ```yaml api: - id: rebuild_search_index type: Api async: true routine: # long-running work ... ``` - Authorization is checked **before** the call is accepted — an unauthorized caller gets an error, not `{ accepted: true }`. - The routine's result never reaches the caller. Completion or failure is written to the server logs; anything else the caller needs to see must be written by the routine itself (for example a status document in a database). - `async: true` also applies to scheduled runs — the cron trigger is acknowledged immediately and the routine runs in the background. - On serverless deployments the background work still runs inside the same function invocation, so it remains bounded by the function's `maxDuration` — see [Deploy with Vercel](/vercel). To run work in a separate invocation with a fresh time budget, use a [detached endpoint call](#detached-endpoint-calls). ## Link Endpoints An endpoint with `link: true` serves a file at a URL that does not expire, so a page can save `` in rich text or a record and the browser loads it later with the signed-in session. This lets a bucket stay private: the link endpoint checks the caller and redirects to a fresh presigned URL on every load. - A `GET` on `/api/endpoints//` runs the routine as the session caller. The endpoint's `auth` config applies as on any other endpoint. - The payload is `{ path, query }`: `path` is the rest of the URL after the endpoint id, decoded and joined with `/` (it may be empty), and `query` is the URL's query parameters. A [`payloadSchema`](#payload-schema) validates it. - Endpoint ids can hold slashes, so the longest link endpoint id the URL starts with wins: with `files` and `files/inline` both declared, `/api/endpoints/files/inline/a.png` runs `files/inline` with path `a.png`. - Only an endpoint with `link: true` answers a `GET`, and a `CallAPI` (a `POST`) to a link endpoint is refused as if it did not exist. - `link` is allowed on `type: Api` only, and cannot be combined with `webhook` or `async`. ```yaml id: files/inline type: Api link: true routine: - :if: _not: _regex: pattern: '^log-events/' on: _payload: path :then: :reject: Not an inline image. :status: 404 - id: presign type: AwsS3PresignedGetObject connectionId: files properties: key: _payload: path expires: 300 - :return: _step: presign ``` The endpoint answers: | Outcome | Status | Body | | ----------------------------------------------------------------------------------------------- | ----------------------- | ----------------------------------------------------- | | The routine returned a URL | `302` to that URL | None | | No link endpoint matches the URL | `404` | `{ "error": "Not found." }` | | The caller is not signed in and the endpoint is protected | `401` | None | | The caller's roles do not allow the endpoint | `403` | None | | A [`:reject`](/:reject), also one raised in an endpoint the routine calls | Its `:status`, or `400` | None | | A failed [`ValidateSchema` step](#validating-data-as-a-routine-step) (with `throwOnInvalid` on) | `400` | None | | The payload does not match the endpoint's `payloadSchema` | `400` | None | | The routine returned anything but a URL, or any other error | `500` | `{ "error": "Link failed." }`, nothing from the error | The `:return` must be a non-empty string: an absolute `http` or `https` URL, or a path on the app starting with `/`. Any other value answers `500` and logs a config error naming the endpoint. Every answer carries `Cache-Control: private, no-store`, so a redirect to a short-lived URL is never cached. On an app with auth, a signed-out caller gets `401` for an unknown link id too, so link ids cannot be found by trying them. A link endpoint **must have no side effects**. Any page, email or document that embeds the URL can make the browser send the `GET`, with no click and no confirmation, so the routine should only read and sign. Session cookies are `SameSite=Lax`, so the browser does not send them on an image load from another site: a link embedded in another site's page loads as signed out. Use a `public` link endpoint for a file other sites may embed. Never build the redirect host from the payload: a link that redirects to a host its `path` or `query` names is an open redirect. To save these links from the editor, set `imageSrc` on [`TiptapInput`](/TiptapInput) or [`TiptapMentionInput`](/TiptapMentionInput) to a `_function` that returns the link for the uploaded key: ```yaml imageSrc: _function: __string.concat: - /api/endpoints/files/inline/ - __uri.encode: __args: 0.key ``` Encode the key, so a file name with `#`, `?` or `%` stays one path. The endpoint receives it decoded. ## Webhook Endpoints An endpoint with `webhook: true` is a receiver for third-party webhooks (AWS SNS, Azure Event Grid, Stripe, GitHub, ...). It is served on the same `POST /api/endpoints/` URL as every other endpoint, but the request is handled raw: - The routine's `payload` is `{ body, query, headers }` — the caller's own body format (parsed as JSON when possible, the raw string otherwise; content-type is ignored since services like SNS post JSON as `text/plain`), plus the URL query parameters and request headers. - The routine's `:return` value is sent back **verbatim** as the response body — webhook handshakes like Event Grid's `{ validationResponse }` require exact response shapes, so no `{ error, response, status }` envelope is applied. - The routine has no user session (`_user` resolves to undefined), and the endpoint's `auth` config is not applied. It runs untrusted, so it can only `CallApi` public endpoints, until a [`webhook.verify`](#verifying-the-sender) request passes. Without a verifier, **authenticating the caller is the routine's own first step**, typically a shared-secret query parameter (`?t=`) compared against a stored value, or a signature header. Because the session check never applies, a webhook endpoint must resolve to public under `auth.api` — the build fails if it is protected by `auth.api.protected` or roles; list it in `auth.api.public` (or remove the flag) to make the intent explicit. ```yaml id: stripe-webhook type: Api webhook: true routine: - :if: _ne: - _payload: query.t - _secret: STRIPE_WEBHOOK_TOKEN :then: - :return: error: unauthorized - id: handle_event type: MongoDBInsertOne connectionId: stripe_events properties: doc: _payload: body - :return: received: true ``` :::warning A `webhook: true` endpoint is publicly reachable by design — never declare it on an endpoint that does not authenticate its caller inside the routine. ::: ### Verifying the sender Set `webhook` to `{ verify: }` to check the sender before the routine runs. The verifier is a request (`connectionId`, `type`, `properties`) run against the incoming request. When it returns `true` or `{ verified: true }`, the routine runs as a trusted system context, so it can `CallApi` protected endpoints. When it returns anything else or throws, the endpoint answers `401` and the routine never runs. A verifier whose own service can not be reached (it throws a `ServiceError`) fails the request with an error instead, so an outage is never reported as a forged webhook. A `webhook: true` endpoint with no verifier runs untrusted throughout. The verifier's `_payload` is `{ body, rawBody, query, headers }`: - `body`: the parsed body, as the routine sees it. - `rawBody`: the request body exactly as received, as a string. Senders such as GitHub (`X-Hub-Signature-256`), Stripe (`Stripe-Signature`) and Slack (`X-Slack-Signature`) sign the exact bytes they post, and serialising the parsed `body` again does not reproduce them (key order, whitespace and unicode escapes differ), so a signature check must use `rawBody`. A body that is not JSON arrives as `rawBody` too, so the verifier can refuse it. - `query` and `headers`: the URL query parameters and the request headers (lower-case names). `rawBody` is given only to the verifier. The routine's payload stays `{ body, query, headers }`. A signature check is usually a small request in a project plugin. This one checks GitHub's `X-Hub-Signature-256`, an HMAC-SHA256 of the body with the webhook secret: ```js import crypto from 'crypto'; function GitHubSignature({ request }) { const expected = `sha256=${crypto .createHmac('sha256', request.secret) .update(request.rawBody, 'utf8') .digest('hex')}`; const received = request.signature ?? ''; if (expected.length !== received.length) { return false; } return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received)); } GitHubSignature.schema = {}; GitHubSignature.meta = { checkRead: false, checkWrite: false }; export default GitHubSignature; ``` ```yaml id: github-webhook type: Api webhook: verify: connectionId: github_signature type: GitHubSignature properties: rawBody: _payload: rawBody signature: _payload: headers.x-hub-signature-256 secret: _secret: GITHUB_WEBHOOK_SECRET routine: - id: save_event type: MongoDBInsertOne connectionId: github_events properties: doc: event: _payload: headers.x-github-event body: _payload: body - :return: received: true ``` ### Answering with a status A webhook endpoint answers with an HTTP status that says how the run ended, so the sender can tell its own mistake from an outage: | Outcome | Status | Body | | ----------------------------------------------------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------- | | The routine finished | `200` | Its `:return` value, or `{ "ok": true }` when it returned nothing | | A [`:return`](/:return) with `:content_type: text/plain` | `200` | The returned string as plain text (see [Answering with plain text](#answering-with-plain-text)) | | A [`:reject`](/:reject) with `:status` | That status (400 to 499) | `:body` as given, or the error body below | | A [`:reject`](/:reject) without `:status` | `400` | `{ "error": { "code": "rejected", "message": } }`, or `:body` when given | | A failed [`ValidateSchema` step](#validating-data-as-a-routine-step) (with `throwOnInvalid` on) | `400` | `{ "error": { "code": "invalid_request", "message": } }` | | The `verify` request did not pass | `401` | `{ "error": { "code": "unauthorized", "message": "Webhook verification failed." } }` | | Any other uncaught error | `500` | `{ "error": { "code": "internal_error", "message": "Webhook failed." } }`, with nothing from the error itself | A `:reject` or a failed `ValidateSchema` step in an endpoint the routine calls with `CallApi` sets the answer the same way, so the work can live in an `InternalApi` endpoint the webhook calls. `:status` and `:body` are accepted only on a `:reject` in a webhook endpoint or an `InternalApi` endpoint (and `:status` alone in a [link endpoint](#link-endpoints)); on any other endpoint they are a build error. An error a `:catch` handles does not change the answer. A `:reject` is never caught, so it always sets it. ```yaml id: support-tickets type: Api webhook: verify: _ref: support/verify_signature.yaml routine: - id: check_body type: ValidateSchema properties: schema: _ref: support/ticket_schema.json data: _payload: body - id: find_ticket type: MongoDBFindOne connectionId: tickets properties: query: _id: _payload: body.ticket_id - :if: _eq: - _step: find_ticket - null :then: :reject: Ticket not found. :status: 404 :body: error: code: not_found ticket_id: _payload: body.ticket_id - :return: ticket: _step: find_ticket ``` ### Answering with plain text Some senders check a new subscription with a handshake that wants a plain-text reply. Microsoft Graph, for one, posts to the notification URL with `?validationToken=` and expects a `200` whose body is exactly the token, with `Content-Type: text/plain`, within 10 seconds. Set `:content_type: text/plain` on the [`:return`](/:return) that answers it: the endpoint answers `200` with the returned string as the body, `Content-Type: text/plain; charset=UTF-8` and `X-Content-Type-Options: nosniff`. ```yaml id: graph-notifications type: Api webhook: true routine: - :if: _ne: - _payload: query.validationToken - null :then: :return: _payload: query.validationToken :content_type: text/plain # A change notification: check its clientState, then handle body.value. - :return: received: true ``` `text/plain` is the only content type, and the returned value must be a string; any other value fails the run with the `500` answer above. `:content_type` is accepted only on a `:return` in a webhook endpoint, since the `:return` of an endpoint the webhook calls with `CallApi` is a step result, not the answer. Anywhere else it is a build error. A `:return` without `:content_type` answers JSON as before. Endpoints without the flag are completely unaffected — the standard CallAPI envelope, auth config, and response shape apply exactly as before. ## Routines Routines define the execution logic of your API endpoint. Routines are defined as a nested combination of routines (subroutines). A routine can be one of: - **Request**: A Lowdefy request definition. - **Control**: A control structure, which often contain nested subroutines. - **Array of Subroutines**: An array of subroutines to execute sequentially. ### Requests [Requests and connections](/connections-and-requests) can be used in API endpoints. Requests use the same connections defined in the `connections` section of the `lowdefy.yaml` file. The `payload` property should not be defined for requests used in an API endpoint, the payload is specified by the [CallAPI](/CallAPI) action. A single request definition is a valid routine definition. ```yaml id: log_message type: Api routine: id: insert_log_message type: MongoDBInsertOne connectionId: logs properties: message: _payload: message doc: timestamp: _date: now ``` ### Arrays of Subroutines Subroutines in an array execute one after another: ```yaml id: my_endpoint type: Api routine: - id: step_1 type: MongoDBFindOne connectionId: users properties: query: _id: _payload: user_id - id: step_2 type: MongoDBInsertOne connectionId: audit-log properties: doc: user_id: _step: step_1._id action: 'user_viewed' timestamp: _date: now - :return: user: _step: step_1 logged: true ``` Arrays can also be nested. This is useful when referencing shared subroutines that might be an array ```yaml # my_endpoint.yaml id: my_endpoint type: Api routine: - _ref: shared_routine.yaml - :return: user: _step: step_1 logged: true ``` ```yaml # shared_routine.yaml - id: step_1 type: MongoDBFindOne connectionId: users properties: query: _id: _payload: user_id - id: step_2 type: MongoDBInsertOne connectionId: audit-log properties: doc: user_id: _step: step_1._id action: 'user_viewed' timestamp: _date: now ``` ### Control Structures Control structures allow you to implement complex logic flows within your API routines. The following controls can be used in API routines: - [`:for`](/for) - Iterate over an array sequentially. - [`:if`](/if) - Execute different routines based on a condition. - [`:log`](/log) - Output messages to the server console. - [`:parallel`](/parallel) - Execute multiple routines simultaneously. - [`:parallel_for`](/parallel_for) - Iterate over an array with concurrent processing. - [`:reject`](/reject) - Return a user-facing error response. Not caught by [`:try`](/try)/`:catch` — it flows past every enclosing `:catch`, though `:finally` still runs. - [`:return`](/return) - Return a successful response with data. - [`:set_state`](/set_state) - Set values in server-side state. - [`:switch`](/switch) - Handle multiple conditions with different outcomes. - [`:throw`](/throw) - Throw a system error that can be caught. - [`:try`](/try) - Handle errors with catch and finally blocks. ## Calling Other Endpoints API endpoints can call other endpoints server-side using `CallApi` steps. This enables you to compose complex workflows from smaller, reusable endpoints without HTTP overhead. A `CallApi` step has: - `id: string`: **Required** - A unique step id within the routine. - `type: CallApi`: **Required** - Identifies this as an endpoint call step. - `properties.endpointId: string`: **Required** - The id of the target endpoint. **Operators are evaluated**. - `properties.payload: object`: Optional payload to pass to the target endpoint. **Operators are evaluated**. - `properties.detached: boolean`: Optional - Fire-and-forget: dispatch the call and continue immediately, running the target in a separate server invocation. See [Detached Endpoint Calls](#detached-endpoint-calls). - `properties.organization: string`: Optional - Run the target bound to this organization id, so the [tenant wall](/organizations#the-tenant-wall) filters and stamps its requests with it. Accepted only in a trusted system run. See [Running in One Organization](#running-in-one-organization). **Operators are evaluated**. - `properties.caller: { id: string, name: string }`: Optional, only with `organization` - A named stand-in caller the target sees as `_user`. **Operators are evaluated**. The called endpoint runs in an isolated context — it has its own `_step` results and `_payload`. Its internal step results do not appear in the calling endpoint's `_step` namespace. Only the value returned by the called endpoint's `:return` is stored as the step result. ```yaml api: - id: process_order type: Api routine: - id: validate type: MongoDBFindOne connectionId: orders properties: query: _id: _payload: order_id - id: send_notification type: CallApi properties: endpointId: send_email payload: to: _step: validate.customer_email subject: 'Order received' - :return: order: _step: validate email_sent: _step: send_notification # Contains the :return value from send_email - id: send_email type: Api routine: - id: send type: SendGridMail connectionId: email properties: to: _payload: to subject: _payload: subject - :return: success: true ``` Endpoint calls can be nested up to 10 levels deep. Exceeding this limit throws an error — this prevents accidental infinite recursion. Connection plugin resolvers can also invoke endpoints from inside their JS code using the `callApi` function on the resolver argument bag. The semantics — depth cap, isolated routine context, caller's user identity, `InternalApi` reachable — match the `CallApi` step. See [Connection and Request Plugins](/plugins-connections) for the resolver-side API. ### Running in One Organization Under the `tenant` organizations policy every walled request needs a caller organization. A scheduled run, an auth hook or a verified webhook has no caller, so a walled request in it fails closed. To do work for one organization from such a run, call the endpoint that does it with `organization`: ```yaml - id: process_installation type: CallApi properties: endpointId: process_github_event organization: _step: installation.organization_id caller: id: github name: GitHub payload: event: _payload: body ``` - The target runs as a system run bound to that organization. Every walled request inside it, and inside endpoints it calls in turn, is filtered and stamped with the organization, as for a signed-in member. - `_user` inside the target is the stand-in caller when `caller` is named: `{ id, name, organization_id, system: true }`, with the bound organization and no roles. `system: true` is how config tells it from a signed-in member; no other caller carries it (a JWT strategy can not map a claim to `system`, and a dev mock user can not set it). Change logs whose `meta` reads `_user` record it as the user. Without `caller`, `_user` is null. - `organization` and `caller` are accepted only in a trusted system run: a scheduled run, an auth hook, a webhook whose `verify` request passed (see [Webhook Endpoints](#webhook-endpoints)), or a detached run dispatched from one. The same step in a signed-in caller's routine, a strategy (API key) caller's, or a webhook with no passing verifier is refused, because the bound run is trusted as the system. - A run that is already bound may restate its organization (and caller), but not name another. - A `caller` without `organization`, or a static value of the wrong shape, is a build error. - `detached: true` carries the binding: the detached run is bound to the same organization and caller. Read the organization id from a row the routine already trusts (an installation record found by the verified webhook's payload, a schedule's list of organizations), never straight from an unverified request. ### Detached Endpoint Calls A `CallApi` step with `detached: true` does not wait for — or ever see — the target's result. The step dispatches the call and immediately continues, storing `{ detached: true, endpointId }` as its step result. The target runs as a new HTTP request to the deployment itself, which matters on serverless hosts: it executes in its **own** function invocation with a fresh `maxDuration` budget, so chained detached calls can process work that outlives any single invocation, without needing a queue. ```yaml - id: spawn_worker type: CallApi properties: endpointId: process_batch detached: true payload: batch_id: _step: create_batch._id ``` Detached calls differ from normal `CallApi` steps in important ways: - **Same identity**: the target runs as whoever dispatched it — the user who called the parent endpoint, or a system context when the parent was a scheduled run, hook or verified webhook (bound to the same organization and stand-in caller when the call names [`organization`](#running-in-one-organization)). The target and its nested calls are authorized against that identity exactly as a synchronous `CallApi` would be, so a detached call reaches nothing the caller could not call directly. `InternalApi` endpoints are callable. - **At-most-once, no retry**: if the dispatch or the target fails, nothing retries it. Design targets to be idempotent. The target's outcome exists only in the server logs and whatever its routine writes. - **Accepted, then run**: the `/api/detached` route answers `202` as soon as the `CRON_SECRET` check passes, and runs the target after the response under the platform's `waitUntil` (bounded by the target's own `maxDuration`). The dispatching invocation's request settles at once instead of staying in flight (and holding its instance's memory) while the target runs, so a chain of detached calls is a chain of separate invocations, not nested in-flight requests. The outcome is logged as `detached_run_done` (with the routine's status) or `detached_run_failed`, which includes a target whose `auth` refuses the caller. - **Requires `CRON_SECRET`**: the dispatch authenticates against the deployment's own `/api/detached` route with the `CRON_SECRET` environment variable (the same secret that secures cron, fail closed). The step fails with a config error if it is not set. - **No depth cap across detached calls**: each detached target starts at call depth 0, so the 10-level nesting limit does not protect against detached recursion. An endpoint that (directly or indirectly) detaches back into itself will loop forever — spawning a new invocation each time. ## Running Agents As A Routine Step Routines can run an [agent](/agents-introduction) to completion using `CallAgent` steps. The agent runs headlessly — no chat UI, no streaming — looping through its tools until it finishes, and the result is stored as the step result. This lets endpoints delegate open-ended, multi-step work to an agent, while the surrounding routine stays explicit. For a single model call without tools, prefer the one-shot `GenerateText` / `GenerateObject` [request types](/Anthropic) — they are cheaper and more predictable. A `CallAgent` step has: - `id: string`: **Required** - A unique step id within the routine. - `type: CallAgent`: **Required** - Identifies this as an agent call step. - `properties.agentId: string`: **Required** - The id of the agent to run. **Operators are evaluated**. - `properties.prompt: string`: **Required** - The task prompt for the agent run. **Operators are evaluated**. - `properties.files: object[]`: Images and documents the model reads beside the prompt. Each entry is `{ url, mediaType }`: `url` is a link the model provider can fetch (a presigned link to a private file), and `mediaType` its type, like `image/png` or `application/pdf`. An `image/*` file is sent as an image, anything else as a document. The prompt and the files go to the model as one user message. A provider that takes that media type as a link fetches it itself (the main providers do for images and PDFs); for any other media type the server downloads the file first and sends its content. The server downloads only an `https:` link to a public address, checked on every redirect, within the agent's `fileDownload` limits (20 MB and 30 seconds by default). Either way the link must still be valid when the step runs. Which media types a model reads depends on the provider. **Operators are evaluated**. The step result contains: - `text: string`: The agent's final text output. - `finishReason: string`: Why the run ended (`stop`, `length`, `tool-calls`, ...). - `usage: object`: Accumulated token usage across all steps (`{ inputTokens, outputTokens, totalTokens, reasoningTokens, cacheReadTokens, cacheWriteTokens }`). - `toolCalls: object[]`: The tool calls the agent made (`{ toolCallId, toolName, input }`). - `toolResults: object[]`: The corresponding tool results (`{ toolCallId, toolName, output }`). ```yaml agents: - id: research_agent type: ClaudeAgent connectionId: claude properties: model: claude-haiku-4-5 instructions: You research signup data and summarize findings. maxSteps: 5 tools: - lookup-signups api: - id: daily-research type: Api routine: - id: research type: CallAgent properties: agentId: research_agent prompt: Summarize yesterday's signups and flag anomalies. - id: send_report type: SendGridMail connectionId: email properties: to: team@example.com subject: Daily signup report text: _step: research.text ``` To let the agent see files, pass them with `files`. Here an endpoint reads a ticket, signs a link to its screenshot with an S3 request and hands it to the agent. The storage key comes from the ticket, not from the payload, so a caller names a ticket and cannot have any file in the bucket signed: ```yaml api: - id: scope-ticket type: Api routine: - id: get_ticket type: MongoDBFindOne connectionId: tickets properties: query: _id: _payload: ticket_id - id: screenshot_link type: AwsS3PresignedGetObject connectionId: files properties: key: _step: get_ticket.screenshot_key expires: 600 - id: scope type: CallAgent properties: agentId: scope_agent prompt: _string.concat: - 'Scope this ticket: ' - _step: get_ticket.description files: - url: _step: screenshot_link mediaType: image/png ``` Headless agent runs differ from interactive chat in a few ways: - **Tool approval is not supported.** Tools configured with `confirm: true` auto-execute — there is no client to approve them. The build emits a warning when a `CallAgent` step references an agent with confirm tools. - **No page context.** The `update-page-state` tool is not available, and no conversation title is generated. - **Hooks still fire.** Agent [server hooks](/agent-server-hooks) (`onStart`, `onStepFinish`, `onFinish`, etc.) run as endpoint calls — useful for logging and persistence. `dataParts` returned by `onFinish` endpoints are ignored (there is no stream to write to), and the `messages` in the `onFinish` payload are model messages rather than UI messages. - **Authorization is per tool endpoint.** Each tool call authorizes against that endpoint's `auth` config with the caller's identity. In a system context with no user, tools calling protected endpoints will fail. Agent tool and hook endpoint calls share the same depth cap of 10 as `CallApi`, so mutually recursive agent and endpoint configurations terminate with an error instead of looping forever. ## Validating Data As A Routine Step API routines can validate any value against a JSON Schema using `ValidateSchema` steps. This is useful for guarding endpoint inputs that need stricter checks than the endpoint's request schema enforces, or for asserting the shape of data returned by a prior step before it flows downstream. `ValidateSchema` is a built-in step. It requires no `connectionId` and no plugin install. It runs against the shared Ajv instance used by the rest of Lowdefy, so all standard JSON Schema features are available: - All formats from [ajv-formats](https://ajv.js.org/packages/ajv-formats.html) (`email`, `uri`, `uuid`, `date`, `date-time`, `ipv4`, `ipv6`, …). - The `instanceof`, `transform`, and `regexp` keywords from [ajv-keywords](https://ajv.js.org/packages/ajv-keywords.html). - The `errorMessage` keyword from [ajv-errors](https://ajv.js.org/packages/ajv-errors.html) for custom error messages. See [ajv.js.org](https://ajv.js.org/json-schema.html) for the full keyword reference. A `ValidateSchema` step has: - `id: string`: **Required** - A unique step id within the routine. - `type: ValidateSchema`: **Required** - Identifies this as a validation step. - `properties.schema: object`: **Required** - The JSON Schema to validate against. **Operators are evaluated**, so `_ref` and `_var` work for sourcing schemas from files or app state. - `properties.data: any`: **Required** - The value to validate. **Operators are evaluated**. - `properties.throwOnInvalid: boolean`: Optional, default `true`. When `true`, invalid data short-circuits the routine with an error response and the AJV errors attached as `error.cause`. When `false`, the routine continues and the validation result is exposed as the step result for downstream branching. The step always records `{ valid, errors }` as its result, available downstream as `_step..valid` and `_step..errors`. ###### Guard endpoint input (default throw) ```yaml api: - id: create_user type: Api routine: - id: validate_input type: ValidateSchema properties: schema: type: object required: [email, name] additionalProperties: false properties: email: { type: string, format: email } name: { type: string, minLength: 1, maxLength: 80 } age: { type: integer, minimum: 0 } data: _payload: true - id: insert_user type: MongoDBInsertOne connectionId: app_db properties: collection: users doc: _payload: true - :return: userId: _step: insert_user.insertedId ``` If the request payload is missing `email`, the routine errors out with the AJV failure details and `insert_user` never runs. ###### Inspect-and-branch (`throwOnInvalid: false`) Set `throwOnInvalid: false` to keep going on failure — then use `:if` to react to `_step..valid`: ```yaml routine: - id: check_input type: ValidateSchema properties: schema: type: object required: [email] properties: email: { type: string, format: email } data: _payload: true throwOnInvalid: false - :if: _eq: - _step: check_input.valid - false :then: - :reject: message: 'Invalid input' errors: _step: check_input.errors - id: send_welcome type: SendgridMailSend connectionId: mailer properties: to: _payload: email template: welcome ``` ###### Validate a prior step's output `schema` and `data` are both operator-evaluated, so any value reachable in the routine context can be checked — payload, prior step results, items in a loop, etc. ```yaml routine: - id: load_profile type: MongoDBFindOne connectionId: app_db properties: collection: profiles query: _id: _payload: userId - id: assert_profile_shape type: ValidateSchema properties: schema: type: object required: [_id, tier] properties: _id: { type: string } tier: { type: string, enum: [free, pro, enterprise] } data: _step: load_profile ``` ## Validating Dynamic Content As A Routine Step `ValidateDynamic` checks block config against a [dynamic blocks policy](/dynamic-page-content#dynamic-blocks-policies) with the same function a Dynamic block runs at page get, so content that passes here renders there. `DescribeDynamicPolicy` returns what a policy allows. Both are built-in steps: no `connectionId`, no plugin install. A `ValidateDynamic` step has: - `id: string`: **Required** - A unique step id within the routine. - `type: ValidateDynamic`: **Required** - `properties.policy: string`: **Required** - The policy id, as a literal string. - `properties.blocks: object[]`: **Required** - The content to check. **Operators are evaluated**, so read it with `_step` or `_payload`. - `properties.throwOnInvalid: boolean`: Optional, default `true`. When `true`, invalid content ends the routine with an error. When `false`, the routine continues. The step result is `{ valid, errors, blocks }`. Each error has `path` (a dot path into the content, such as `blocks.2.properties.title`), `rule` (such as `policy.blocks`, `policy.urls`, `limits.depth` or `schema`) and `message`. In the endpoint a Dynamic block calls, `_step: .blocks` of a passing step with the block's policy is returned as config. A `DescribeDynamicPolicy` step has `id`, `type: DescribeDynamicPolicy` and `properties.policy`. Its result lists the policy's `blocks` (each with its properties schema), `actions` and `operators` (each with its params schema), `endpoints`, `requests`, `links`, `state`, `html` and `limits`. ###### Generate, check and store a form ```yaml api: - id: generate_form type: Api routine: - id: vocabulary type: DescribeDynamicPolicy properties: policy: generated_form - id: attempt type: GenerateObject connectionId: model properties: # prompt built from _payload.description and _step.vocabulary - id: check type: ValidateDynamic properties: policy: generated_form blocks: _step: attempt.blocks throwOnInvalid: false - :if: _step: check.valid :then: - id: store type: MongoDBInsertOne connectionId: forms properties: doc: blocks: _step: check.blocks - :return: valid: true :else: - :return: valid: false errors: _step: check.errors ``` ## Rendering Notifications As A Routine Step API routines render a [notification](/notifications) to an email with `RenderNotification` steps. The step renders one notification item against a template defined in the app's `notifications:` section and returns the rendered content — it does **not** store or send anything. Storing a record, sending the email, and tracking delivery are ordinary routine steps you compose around it (or that the [`modules-mongodb` notifications module](https://github.com/lowdefy/modules-mongodb) composes for you). `RenderNotification` is a built-in step. It requires no `connectionId` and no plugin install. A `RenderNotification` step has: - `id: string`: **Required** - A unique step id within the routine. - `type: RenderNotification`: **Required** - Identifies this as a render step. - `properties.notificationId: string`: **Required** - The id of the notification in the `notifications:` section to render. **Operators are evaluated**. - `properties.data: object`: **Required** - The data for **one** notification (the recipient and template data). Must be a single object — to render a batch, iterate with a [`:for`](/for) control and render one item per step. **Operators are evaluated**. - `properties.serverUrl: string`: The absolute origin used to build link URLs (for example `https://myapp.com`). Defaults to the `url` of the current environment in [`config.environments`](/lowdefy-schema), and on the dev server (`lowdefy dev`) to the request origin. Required when the item carries page links and neither is available. - `properties.landingPage: string | object`: The page to route email links through, by page id (for example `notifications-link`) or `{ pageId, pathParams }` when its path has parameters. Its URL path is built from the page's path like any other link, so a landing page with a `path` works. It lets a landing page mark the notification read before redirecting. A value that names no page, such as a URL path (`/notifications/link`), fails the step: v7 changed this property from a path to a page id (see [Version 6 to Version 7](/v6-to-v7)). When unset, links go directly to their target pages. - `properties.recordId: string`: The record id embedded in landing-page link URLs. Required when `landingPage` is set and the item has links — usually a `_uuid` minted earlier in the routine. The step result contains: - `subject: string`: The interpolated subject line. - `title: string`: The interpolated title (falls back to `subject`). - `preview: string`: A short preview string for inbox listings (the template's `preview`, else a markdown-stripped excerpt of the message). - `html: string`: The rendered email as HTML. - `text: string`: The rendered email as plain text. - `data: object`: The item with its links resolved to URLs. ```yaml api: - id: notify-task-assigned type: Api routine: - id: render type: RenderNotification # Render one item; see /notifications for the notifications: section. properties: notificationId: task-assigned data: _payload: item serverUrl: https://myapp.com - id: send type: SMTPMailSend connectionId: smtp properties: to: _payload: item.contact.email subject: _step: render.subject html: _step: render.html text: _step: render.text ``` See [Notifications](/notifications) for the `notifications:` section, the built-in templates, and how link URLs are composed. ## Internal API Endpoints Endpoints with `type: InternalApi` are only callable from other endpoints via `CallApi` steps. They cannot be called from client pages using the `CallAPI` action, and HTTP requests to them return a "does not exist" error. Use `InternalApi` for endpoints that contain sensitive server-side logic that should never be triggered directly from the client — for example, sending emails, processing payments, or auditing events. ```yaml api: - id: charge_payment type: InternalApi routine: - id: charge type: AxiosHttp connectionId: payment-gateway properties: data: amount: _payload: amount token: _payload: payment_token - :return: _step: charge - id: checkout type: Api # Callable from the client routine: - id: process_payment type: CallApi properties: endpointId: charge_payment # Can call InternalApi endpoints payload: amount: _payload: total payment_token: _payload: token - :return: payment: _step: process_payment ``` If a client-side `CallAPI` action targets an `InternalApi` endpoint, the build produces a warning in dev mode and an error in production builds. ## Operators The following operators are specific to API endpoints: - [`_item`](/_item) - Access current item in :for and :parallel_for loops. - [`_state`](/_state) - Access server-side state set with :set_state. - [`_step`](/_step) - Access results from requests in previous steps. API endpoints also have access to all operators available on the server, including: - [`_payload`](/_payload) - Access the payload sent from the client. - [`_secret`](/_secret) - Access secrets from environment variables or secrets configuration. - [`_user`](/_user) - Access authenticated user information. ### Accessing Step Results Use the [`_step`](/_step) operator to access results from previously executed steps: ```yaml - id: get_user type: MongoDBFindOne connectionId: users properties: query: email: _payload: email - id: get_orders type: MongoDBAggregation connectionId: orders properties: pipeline: - $match: user_id: _step: get_user._id # Access the _id from the get_user request ``` ## CallAPI Action Use the `CallAPI` action to call your API endpoints from pages. Read more about the `CallAPI` action [here](/CallAPI). ###### Basic example: ```yaml blocks: - id: user_id_input type: TextInput properties: title: User ID - id: fetch_user_btn type: Button properties: title: Fetch User Data events: onClick: - id: call_user_api type: CallAPI params: endpoint_id: get_user_data payload: user_id: _state: user_id_input - id: set_user_data type: SetState params: user_data: _actions: call_user_api.response.user - id: user_display type: Descriptions properties: items: _state: user_data ``` ###### Error handling example: ```yaml events: onClick: try: - id: call_process_data_api type: CallAPI params: endpoint_id: process_data payload: data: _state: form_data - id: show_success type: DisplayMessage params: content: 'Data processed successfully!' status: success - id: update_state type: SetState params: result: _actions: call_process_data_api.response catch: - id: update_state type: SetState params: error_message: _actions: call_process_data_api.error.message ``` ### \_api Operator The [`_api`](/_api) operator returns the response value of an API call. If the API has not yet been called, or is still executing, the returned value is `null`. ###### Using an API response: ```yaml blocks: - id: loading_text type: Html visible: _api: fetch_data.loading properties: html: '
Loading...
' - id: error_alert type: Alert visible: _not: _api: fetch_data.success properties: type: error message: _api: fetch_data.error.message - id: data_display type: Descriptions properties: items: _api: fetch_data.response ``` ## Complex Example: Order Processing API Here's a comprehensive example showing various features: ```yaml lowdefy: '7.1.0' auth: api: protected: true roles: user: - process_order api: - id: process_order type: Api routine: # Validate order - id: validate_order type: MongoDBFindOne connectionId: orders properties: query: _id: _payload: order_id status: 'pending' user_id: _user: id # Ensure user owns this order - :if: _not: _step: validate_order :then: :reject: 'Order not found or already processed' # Process items in parallel - :parallel_for: item :in: _step: validate_order.items :do: - id: check_inventory type: MongoDBFindOne connectionId: products properties: query: _id: _item: item.product_id inventory: $gte: _item: item.quantity - :if: _not: _step: check_inventory :then: :throw: 'Insufficient inventory' # Try payment processing - :try: - id: process_payment type: AxiosHttp connectionId: payment-gateway properties: data: amount: _step: validate_order.total currency: 'USD' source: _payload: payment_token :catch: - id: mark_payment_failed type: MongoDBUpdateOne connectionId: orders properties: filter: _id: _payload: order_id update: $set: status: 'payment_failed' failed_at: _date: now - :reject: 'Payment processing failed' # Update order status - id: complete_order type: MongoDBUpdateOne connectionId: orders properties: filter: _id: _payload: order_id update: $set: status: 'completed' payment_id: _step: process_payment.data.id completed_at: _date: now # Update inventory - :for: item :in: _step: validate_order.items :do: - id: reduce_inventory type: MongoDBUpdateOne connectionId: products properties: filter: _id: _item: item.product_id update: $inc: inventory: -1 # Send confirmation email - id: send_confirmation type: SendGridMail connectionId: email-service properties: to: _step: validate_order.customer_email template_id: 'order_confirmation' dynamic_template_data: order_id: _payload: order_id total: _step: validate_order.total # Return success - :return: success: true order_id: _payload: order_id payment_id: _step: process_payment.data.id message: 'Order processed successfully' ``` ### TLDR - Lowdefy APIs create custom server-side endpoints that execute complex logic. - Routines define the execution flow using arrays, requests and control structures. - Control structures like [`:if`](/:if), [`:for`](/:for), [`:parallel`](/:parallel), and [`:try`](/:try) enable sophisticated flows. - Requests make requests to connections and can access results using [`_step`](/_step). - `CallApi` steps call other endpoints server-side with isolated `_step` and `_payload` namespaces. - `InternalApi` endpoints are server-only — callable from other endpoints but not from client pages. - The [`CallAPI`](/CallAPI) action is used to invoke APIs from the client with payloads. - Server-side execution provides access to secrets, connections, and server operators. - Responses are controlled with [`:return`](/:return) (success) and [`:reject`](/:reject) (user errors). - A [`:reject`](/:reject) is a reply, not an exception, and is never caught by [`:try`](/:try)/`:catch`; [`:throw`](/:throw) is. --- url: https://docs.lowdefy.com/md/concepts/mobile-apps.md --- # Mobile Apps (Experimental) :::warning **Mobile apps are experimental.** Turn them on with `config.experimental.mobile: true`. The `mobile` section, the native blocks and actions, the `lowdefy mobile` commands and the phone flags of `lowdefy dev` may change, or be removed, in a minor release. They have run on the iOS simulator; Android devices and real store submissions are still being tested. ::: A Lowdefy app can run on iPhones and Android phones as a native app. The phone app is a **shell**, built from `@lowdefy/shell` (Expo and React Native), that opens one Lowdefy app. It uses the same YAML, server, requests, API endpoints and auth as the web app. Pages for phones are declared under `mobile` in `lowdefy.yaml` and render with native blocks, which have the same names, properties and events as the web blocks. ## Turn it on ```yaml lowdefy: 7.1.0 config: experimental: mobile: true environments: production: url: https://app.example.com mobile: menus: tabs: - pageId: today icon: CalendarDays - pageId: settings icon: Settings pages: - _ref: mobile/today.yaml - _ref: mobile/settings.yaml shells: - id: main name: Example icon: assets/icon.png version: '1.0' profile: basic ios: bundleId: com.example.app android: package: com.example.app ``` Without `config.experimental.mobile: true`, a `mobile` section, or `offline` on a request, fails the build: ``` Mobile apps are experimental. Set "config.experimental.mobile: true" in lowdefy.yaml to build the "mobile" section. ``` An app without a `mobile` section builds and runs exactly as before, with or without the flag. ## The mobile section - `pages: object[]`: The pages a phone shows. A page can be phone-only, or the same `_ref` as a web page (a shared page), which is built once for both. A page on a phone may only use the types a shell carries; the build names any other. - `menus: object`: How the phone navigates. `tabs` (2 to 5 items, each `pageId`, `icon` or `sfSymbol`, `title`), a `drawer`, or `stack: true` for a single stack of pages. - `homePageId: string`: The page the app opens on. Defaults to the first menu item, or else the first mobile page. - `minShellVersion: string`: The oldest shell version that may open the app. An older shell shows an "update the app" screen. - `events: object`: App events (`onInit`, `onInitAsync`) that run on phones, in place of the root `events`. See [App events on phones](/events-and-actions). - `shells: object[]`: The native apps built from this app. Each has an `id`, `name`, `icon`, store `version`, a capability `profile`, optional `extensions`, and `ios`, `android` and `eas` settings. See [`lowdefy mobile`](/cli#mobile). - `auth: object`: Phone sign-in settings, such as how long a phone's session lasts. - `notifications`, `share`, `widgets`, `shortcuts`, `background`: Push notification categories and the endpoints that register a phone, the page shared items open, Home Screen widgets, app icon shortcuts and background fetch. ## Capability profiles A shell's `profile` fixes the native modules it carries, and so the permissions it asks for and what the store listing declares: | Profile | Carries | | --- | --- | | `basic` | Native blocks, sign-in, biometric unlock, dictation, push and local notifications, haptics, share, Photos and Files pickers, read aloud, keep awake, icon shortcuts and background fetch. | | `field` | `basic`, plus the camera (photos, codes, documents), location while in use, maps, voice notes and screen brightness. | | `field-nfc` | `field`, plus reading and writing NFC tags. | | `field-tracking` | `field`, plus location in the background and geofences. | `extensions` add `share` (receive shared items), `widgets` (iOS widgets), `contacts` or `calendar` on top of a profile. A native action used on a shell that does not carry it fails with a message that names the profile it needs. ## Offline requests A request with `offline: queue` is kept on the phone while it has no signal, and sent in order when the signal comes back. The action resolves `{ queued: true, id }`. The payload is evaluated once, when the request is queued. The web ignores `offline`. It also needs `config.experimental.mobile: true`. ```yaml requests: - id: save_note type: MongoDBUpdateOne connectionId: jobs offline: queue payload: key: _uuid: true ``` ## Sign-in Phones hold ordinary sessions of the app's auth, sent as bearer tokens. The default sign-in is a native email-code screen. Passkeys, SSO and two-factor sign-in open in the system browser sheet and return to the app. Sign-out removes everything the phone cached for that person. ## Develop on a phone - `lowdefy dev --ios` opens the app in Lowdefy Dev on an iPhone simulator (macOS with Xcode). - `lowdefy dev --android` opens it on an Android emulator, or a phone connected by USB. - `lowdefy dev --lan` lets a phone on the local network pair by QR code. The [agent tools](/ai-agent-docs) take a `device` to screenshot, inspect and drive a phone. See [The CLI](/cli#dev). ## Build and release `lowdefy mobile config | build | submit | update | release | checklist` builds, submits and updates shells with [EAS](https://expo.dev/eas), from your own Expo, Apple and Google accounts. `release` builds and submits when native code changed, and sends an over-the-air update when only JavaScript changed. See [`lowdefy mobile`](/cli#mobile). ## Not yet supported - Android devices and real EAS store runs are not yet fully tested. - Released builds of Lowdefy Dev. Until they ship, point `LOWDEFY_DEV_APP_IOS` or `LOWDEFY_DEV_APP_ANDROID` at a build. - Offline reads (offline data), and Android widgets. --- url: https://docs.lowdefy.com/md/concepts/notifications.md --- # Notifications Lowdefy can render branded notification emails from config. You define a notification's content once in a `notifications:` section — as a template with interpolated properties — and render it from an API routine with the `RenderNotification` step. Rendering is all the framework does: **storing a notification, deduplicating, sending the email, and tracking delivery are composed in your API routines**, either by hand or with a notifications module. This split is deliberate. Rendering an email safely (turning template properties and data into HTML, with a guarantee that user data can never inject links or markup) and composing link URLs are the two things config alone cannot do, so they live in the framework. Everything else — inserting a record, sending, retrying — is ordinary [requests](/connections-and-requests) and [API routines](/lowdefy-api), which you already have. A consequence worth knowing: because storing is just a request and sending is just a request, notifications work with any database and any email provider Lowdefy connects to. ## The `notifications:` section `notifications:` is a root section. Each entry is `{ id, type, properties }`, where **the template is the type**: ```yaml notifications: - id: task-assigned type: NotificationEmail properties: subject: 'New task: {{ task.title }}' title: Task assigned to you message: | Hi {{ contact.name }}, **{{ task.title }}** has been assigned to you. metadata: - label: Due value: '{{ task.due_date }}' button: label: View task testData: contact: name: Jane Doe task: title: Review the quarterly report due_date: 2026-07-15 links: button: pageId: task-view urlQuery: _id: T-001 ``` The definition fields are: - `id: string`: **Required** - A unique notification id, referenced by the `RenderNotification` step. - `type: string`: **Required** - The template type — a built-in template (`NotificationEmail`, `DigestEmail`, `AlertEmail`) or a custom template plugin. - `properties: object`: **Required** - The template's content. `properties.subject` is always required; the rest depend on the template type. - `theme: object`: Per-notification overrides of the `app.email` theme (see [Theming](#theming)). - `testData: object`: Sample data used to render the notification in the [email preview](#previewing-emails). Not used at runtime. ### Module notifications Modules can ship notification templates: `module.lowdefy.yaml` accepts a `notifications:` section, and each template's id scopes to `{entry}/{id}` (installing `user-admin` gives `user-admin/invite-user`), so the same module installed twice never collides. Module content references its own templates with [`_module.notificationId`](/_module) — in a `RenderNotification` step or a dispatch payload — and app-level config reaches a module's template with the object form (`_module.notificationId: { id: invite-user, module: user-admin }`) or the literal scoped string. This lets a module that drives a notification flow (like user invites) ship its default email instead of every app hand-writing it. Module notifications resolve `_module.var` in their properties, so template copy can be configured through the module's vars. ### Interpolation Template property strings interpolate against the render data with [Nunjucks](https://mozilla.github.io/nunjucks/) — `{{ task.title }}` just works, including `{% if %}` conditionals. This applies inside `notifications:` properties only. Interpolated values are **inert** — they can never carry markup or links unless you mark them with the `markdown` filter (below). A task title of `[click here](https://evil.example)` renders as literal text, not a link. Only the markdown you write in the template formats; the data you interpolate is escaped. This closes the injection risk of putting user data into a branded email. There is no operator evaluation inside template properties — they are data templates, not operator config. Build-time operators like [`_ref`](/references-and-templates) still work as everywhere else. #### Markdown the app wrote When a value is markdown your app wrote on purpose — a release note body, a templated summary — mark it with the `markdown` filter so it renders as headings, lists, links and images: ```yaml properties: subject: '{{ title }}' message: "{{ intro }}\n\n{{ body | markdown }}" ``` `intro` stays escaped; `body` renders as markdown. Use the filter only on markdown your app itself wrote, never on text a user typed: a filtered value can carry links and images into the email. Raw HTML stays escaped even in a filtered value, so a `

- id: purify_allow_target_label type: Markdown properties: content: "**With ADD_ATTR to allow target attribute:**" - id: purify_allow_target type: DangerousHtml properties: html: Link with target=_blank allowed DOMPurifyOptions: ADD_ATTR: - target ``` ```yaml - id: style_padded type: DangerousHtml style: .element: padding: 20 background: var(--ant-color-fill-quaternary) borderRadius: 8 border: 1px solid var(--ant-color-border) properties: html: '

Content inside a styled DangerousHtml container with padding and background.

' - id: style_max_width type: DangerousHtml style: .element: maxWidth: 400 margin: 0 auto padding: 16 background: var(--ant-color-primary-bg) borderRadius: 8 textAlign: center properties: html: '

Centered container with max-width constraint.

' - id: style_shadow type: DangerousHtml style: .element: padding: 20 borderRadius: 12 boxShadow: var(--ant-box-shadow) background: var(--ant-color-bg-container) properties: html: '

Elevated Card

A DangerousHtml block styled with a box shadow for an elevated card appearance.

' - id: style_border_left type: DangerousHtml style: .element: borderLeft: 4px solid var(--ant-color-primary) paddingLeft: 16 paddingTop: 8 paddingBottom: 8 properties: html: '

Highlighted with a primary color left border accent.

' ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `html` | string | - | Content to be rendered as Html. The Html block attributes (data-icon, data-tooltip, data-popover, data-page-id links, data-new-tab, data-tag, data-status, data-time, data-format, data-avatar, data-copy, data-truncate, data-tone) work here too, unless DOMPurifyOptions removes data attributes. | | `DOMPurifyOptions` | object | - | Customize DOMPurify options. Options are only applied when the block is mounted, thus any parsed settings is only applied at first render. | No events defined. | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The DangerousHtml element. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/dangerousmarkdown.md --- # DangerousMarkdown Render Markdown content without sanitization. ## DangerousMarkdown This block renders markdown **and** allows embedded HTML to be rendered inline. Regular markdown features like **bold**, *italic*, ~~strikethrough~~, and [links](https://lowdefy.com) all work as expected. - Unordered list item - Another item 1. Ordered item one 2. Ordered item two ```yaml - id: basic_text type: DangerousMarkdown properties: content: > ## DangerousMarkdown This block renders markdown **and** allows embedded HTML to be rendered inline. Regular markdown features like **bold**, *italic*, ~~strikethrough~~, and [links](https://lowdefy.com) all work as expected. - Unordered list item - Another item 1. Ordered item one 2. Ordered item two ``` Text with blue bold and green and red underlined inline HTML. Use highlighted text to draw attention to specific words. Mix markdown **bold** with HTML italic purple seamlessly.
Styled Container

HTML divs with gradients, borders, and custom spacing render inside DangerousMarkdown.

```yaml - id: html_colored_text type: DangerousMarkdown properties: content: > Text with blue bold and green and red underlined inline HTML. Use highlighted text to draw attention to specific words. Mix markdown **bold** with HTML italic purple seamlessly. - id: html_styled_div type: DangerousMarkdown properties: content: >
Styled Container

HTML divs with gradients, borders, and custom spacing render inside DangerousMarkdown.

``` ## Project Status Report Here is a summary with **markdown headings** and HTML elements combined:
12
Completed
5
In Progress
3
Pending
> **Note:** The cards above use HTML `div` elements with inline styles for layout. Regular markdown continues below the HTML blocks without issue. ```yaml - id: mixed_content type: DangerousMarkdown properties: content: > ## Project Status Report Here is a summary with **markdown headings** and HTML elements combined:
12
Completed
5
In Progress
3
Pending
> **Note:** The cards above use HTML `div` elements with inline styles for layout. Regular markdown continues below the HTML blocks without issue. ```
Feature Markdown DangerousMarkdown MarkdownWithCode
Basic markdown Yes Yes Yes
Inline HTML No Yes No
Code highlighting No No Yes
DOMPurify sanitization N/A Yes N/A
```yaml - id: html_table_styled type: DangerousMarkdown properties: content: >
Feature Markdown DangerousMarkdown MarkdownWithCode
Basic markdown Yes Yes Yes
Inline HTML No Yes No
Code highlighting No No Yes
DOMPurify sanitization N/A Yes N/A
```
Info: This is an informational callout created with HTML inside DangerousMarkdown.
Success: Your deployment completed without errors.
Warning: Only render trusted content with DangerousMarkdown. Never pass user input directly.
Error: Connection to the database failed. Check your credentials and try again.
```yaml - id: callout_info type: DangerousMarkdown properties: content: >
Info: This is an informational callout created with HTML inside DangerousMarkdown.
- id: callout_success type: DangerousMarkdown properties: content: >
Success: Your deployment completed without errors.
- id: callout_warning type: DangerousMarkdown properties: content: >
Warning: Only render trusted content with DangerousMarkdown. Never pass user input directly.
- id: callout_error type: DangerousMarkdown properties: content: >
Error: Connection to the database failed. Check your credentials and try again.
``` **Deployment Status:** Active Pending Failed Archived **Technology Stack:** React Next.js MongoDB YAML ```yaml - id: badges_status type: DangerousMarkdown properties: content: > **Deployment Status:** Active Pending Failed Archived - id: badges_tags type: DangerousMarkdown properties: content: > **Technology Stack:** React Next.js MongoDB YAML ``` By default, DOMPurify removes dangerous elements like ` ``` ```yaml - id: code_html type: MarkdownWithCode properties: content: > ```html Lowdefy App

My Application

``` ``` ```nunjucks {# Lowdefy docs page template #} _ref: path: templates/general.yaml.njk vars: pageId: {{ pageId }} pageTitle: {{ pageTitle }} section: {{ section }} fullWidth: true content: - id: intro type: Markdown properties: content: | {{ description }} {% for example in examples %} - id: example_{{ loop.index }} type: {{ example.block_type }} properties: {{ example.properties | dump | safe }} {% endfor %} ``` ```yaml - id: code_nunjucks type: MarkdownWithCode properties: content: | ```nunjucks {# Lowdefy docs page template #} _ref: path: templates/general.yaml.njk vars: pageId: {{ pageId }} pageTitle: {{ pageTitle }} section: {{ section }} fullWidth: true content: - id: intro type: Markdown properties: content: | {{ description }} {% for example in examples %} - id: example_{{ loop.index }} type: {{ example.block_type }} properties: {{ example.properties | dump | safe }} {% endfor %} ``` ``` ## Building a Form with Lowdefy Follow these steps to create a simple contact form. ### 1. Create the Page Start by defining a page in your `lowdefy.yaml`: ```yaml pages: - id: contact type: PageHeaderMenu properties: title: Contact Us ``` ### 2. Add Form Fields Add input blocks inside the page: ```yaml blocks: - id: name type: TextInput properties: label: title: Your Name - id: message type: TextArea properties: label: title: Message ``` ### 3. Handle Submission Use an action to process the form data: ```javascript // The _state operator accesses form values // _state.name returns the value of the "name" TextInput // _state.message returns the value of the "message" TextArea ``` > **Tip:** Use the `Validate` action before `Request` to ensure all required fields are filled in. For the complete guide, see the [Lowdefy documentation](https://docs.lowdefy.com). ```yaml - id: mixed_tutorial type: MarkdownWithCode properties: content: > ## Building a Form with Lowdefy Follow these steps to create a simple contact form. ### 1. Create the Page Start by defining a page in your `lowdefy.yaml`: ```yaml pages: - id: contact type: PageHeaderMenu properties: title: Contact Us ``` ### 2. Add Form Fields Add input blocks inside the page: ```yaml blocks: - id: name type: TextInput properties: label: title: Your Name - id: message type: TextArea properties: label: title: Message ``` ### 3. Handle Submission Use an action to process the form data: ```javascript // The _state operator accesses form values // _state.name returns the value of the "name" TextInput // _state.message returns the value of the "message" TextArea ``` > **Tip:** Use the `Validate` action before `Request` to ensure all required fields are filled in. For the complete guide, see the [Lowdefy documentation](https://docs.lowdefy.com). ``` ### Configuration vs Code The same logic expressed in YAML config and JavaScript: **Lowdefy YAML:** ```yaml - id: greeting type: Paragraph properties: content: _if: test: _state: logged_in then: _string.concat: - 'Welcome back, ' - _state: username - '!' else: Please log in to continue. ``` **Equivalent JavaScript:** ```javascript function getGreeting(state) { if (state.logged_in) { return `Welcome back, ${state.username}!`; } return 'Please log in to continue.'; } ``` ```yaml - id: multi_language type: MarkdownWithCode properties: content: | ### Configuration vs Code The same logic expressed in YAML config and JavaScript: **Lowdefy YAML:** ```yaml - id: greeting type: Paragraph properties: content: _if: test: _state: logged_in then: _string.concat: - 'Welcome back, ' - _state: username - '!' else: Please log in to continue. ``` **Equivalent JavaScript:** ```javascript function getGreeting(state) { if (state.logged_in) { return `Welcome back, ${state.username}!`; } return 'Please log in to continue.'; } ``` ``` **skipHtml: false (default)** HTML tags like `` are escaped and shown as text in the output. This is the default, safe behavior. **skipHtml: true** With skipHtml enabled, any HTML tags like bold are completely removed from the output rather than shown as escaped text. ```yaml - id: skip_html_false type: MarkdownWithCode properties: content: > **skipHtml: false (default)** HTML tags like `` are escaped and shown as text in the output. This is the default, safe behavior. - id: skip_html_true type: MarkdownWithCode properties: skipHtml: true content: > **skipHtml: true** With skipHtml enabled, any HTML tags like bold are completely removed from the output rather than shown as escaped text. ``` ### Styled Code Container The `element` style key wraps the entire block: ```yaml style: .element: backgroundColor: var(--ant-color-success-bg) padding: 16 borderRadius: 8 ``` ```yaml - id: style_element type: MarkdownWithCode style: .element: backgroundColor: var(--ant-color-success-bg) padding: 16 borderRadius: 8 border: 1px solid var(--ant-color-success-border) properties: content: | ### Styled Code Container The `element` style key wraps the entire block: ```yaml style: .element: backgroundColor: var(--ant-color-success-bg) padding: 16 borderRadius: 8 ``` ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `content` | string | - | Content in markdown format. | | `skipHtml` | boolean | `false` | By default, HTML in markdown is escaped. When true all HTML code in the markdown will not be rendered. | No events defined. | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The MarkdownWithCode element. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/menu.md --- # Menu Horizontal or vertical navigation menu with nested items. ```yaml - id: mode_horizontal type: Menu properties: mode: horizontal links: - id: mh_home type: MenuLink properties: title: Home icon: home - id: mh_about type: MenuLink properties: title: About - id: mh_services type: MenuGroup properties: title: Services links: - id: mh_consulting type: MenuLink properties: title: Consulting - id: mh_development type: MenuLink properties: title: Development - id: mh_contact type: MenuLink properties: title: Contact - id: mode_vertical type: Menu style: width: 256 properties: mode: vertical links: - id: mv_dashboard type: MenuLink properties: title: Dashboard icon: home - id: mv_users type: MenuLink properties: title: Users icon: user - id: mv_settings type: MenuGroup properties: title: Settings icon: settings links: - id: mv_profile type: MenuLink properties: title: Profile - id: mv_security type: MenuLink properties: title: Security - id: mode_inline type: Menu style: width: 256 properties: mode: inline defaultOpenKeys: - mi_navigation links: - id: mi_navigation type: MenuGroup properties: title: Navigation icon: grid links: - id: mi_option1 type: MenuLink properties: title: Option 1 - id: mi_option2 type: MenuLink properties: title: Option 2 - id: mi_tools type: MenuGroup properties: title: Tools icon: Wrench links: - id: mi_option3 type: MenuLink properties: title: Option 3 - id: mi_option4 type: MenuLink properties: title: Option 4 ``` ```yaml - id: lt_icons_menu type: Menu style: width: 256 properties: mode: vertical links: - id: lt_mail type: MenuLink properties: title: Mail icon: mail - id: lt_calendar type: MenuLink properties: title: Calendar icon: calendar - id: lt_settings type: MenuLink properties: title: Settings icon: settings - id: lt_dividers_menu type: Menu style: width: 256 properties: mode: vertical links: - id: lt_general type: MenuLink properties: title: General icon: settings - id: lt_profile type: MenuLink properties: title: Profile icon: user - id: lt_solid_divider type: MenuDivider - id: lt_files type: MenuLink properties: title: Files icon: file - id: lt_dashed_divider type: MenuDivider properties: dashed: true - id: lt_logout type: MenuLink properties: title: Logout icon: logout - id: lt_nested_menu type: Menu style: width: 256 properties: mode: inline defaultOpenKeys: - lt_parent links: - id: lt_parent type: MenuGroup properties: title: Navigation icon: grid links: - id: lt_child_group type: MenuGroup properties: title: Sub Group links: - id: lt_sub1 type: MenuLink properties: title: Sub Item 1 - id: lt_sub2 type: MenuLink properties: title: Sub Item 2 - id: lt_direct type: MenuLink properties: title: Direct Item - id: lt_danger_menu type: Menu style: width: 256 properties: mode: vertical links: - id: lt_edit type: MenuLink properties: title: Edit icon: edit - id: lt_copy type: MenuLink properties: title: Copy icon: copy - id: lt_danger_divider type: MenuDivider - id: lt_delete type: MenuLink properties: title: Delete icon: delete danger: true ``` ```yaml - id: sk_menu type: Menu properties: mode: horizontal selectedKeys: - sk_active links: - id: sk_home type: MenuLink properties: title: Home - id: sk_active type: MenuLink properties: title: Active Page - id: sk_other type: MenuLink properties: title: Other - id: dok_menu type: Menu style: width: 256 properties: mode: inline defaultOpenKeys: - dok_group1 - dok_group2 links: - id: dok_group1 type: MenuGroup properties: title: Group 1 icon: folder links: - id: dok_item1 type: MenuLink properties: title: Item 1 - id: dok_item2 type: MenuLink properties: title: Item 2 - id: dok_group2 type: MenuGroup properties: title: Group 2 icon: FolderOpen links: - id: dok_item3 type: MenuLink properties: title: Item 3 - id: sk_multiple_menu type: Menu style: width: 256 properties: mode: vertical selectedKeys: - sk_multi_b links: - id: sk_multi_a type: MenuLink properties: title: Dashboard icon: home - id: sk_multi_b type: MenuLink properties: title: Reports icon: chart - id: sk_multi_c type: MenuLink properties: title: Settings icon: settings ``` ```yaml - id: io_collapsed_menu type: Menu style: width: 80 properties: mode: inline collapsed: true links: - id: io_home type: MenuLink properties: title: Home icon: home - id: io_users type: MenuLink properties: title: Users icon: user - id: io_settings type: MenuLink properties: title: Settings icon: settings - id: io_indent_menu type: Menu style: width: 280 properties: mode: inline inlineIndent: 40 defaultOpenKeys: - io_ind_group links: - id: io_ind_group type: MenuGroup properties: title: Deep Indent icon: sidebar-collapse links: - id: io_ind_a type: MenuLink properties: title: Indented Item A - id: io_ind_b type: MenuLink properties: title: Indented Item B - id: io_expand_icon_menu type: Menu style: width: 256 properties: mode: inline expandIcon: chevron-right defaultOpenKeys: - io_exp_group links: - id: io_exp_group type: MenuGroup properties: title: Custom Expand Icon icon: grid links: - id: io_exp_a type: MenuLink properties: title: Sub Item A - id: io_exp_b type: MenuLink properties: title: Sub Item B ``` ```yaml - id: sd_fast_menu type: Menu style: width: 256 properties: mode: vertical subMenuOpenDelay: 0 subMenuCloseDelay: 0 links: - id: sd_fast_group type: MenuGroup properties: title: Instant Open icon: Zap links: - id: sd_fast_a type: MenuLink properties: title: No Delay Open - id: sd_fast_b type: MenuLink properties: title: No Delay Close - id: sd_slow_menu type: Menu style: width: 256 properties: mode: vertical subMenuOpenDelay: 0.5 subMenuCloseDelay: 1 links: - id: sd_slow_group type: MenuGroup properties: title: Slow Submenu icon: clock links: - id: sd_slow_a type: MenuLink properties: title: 0.5s Open Delay - id: sd_slow_b type: MenuLink properties: title: 1s Close Delay - id: sd_force_render_menu type: Menu style: width: 256 properties: mode: vertical forceSubMenuRender: true links: - id: sd_force_group type: MenuGroup properties: title: Pre-rendered Submenu icon: RefreshCw links: - id: sd_force_a type: MenuLink properties: title: Already in DOM - id: sd_force_b type: MenuLink properties: title: Before Visible ``` ```yaml - id: tsa_click_menu type: Menu style: .element: width: 256px properties: mode: vertical triggerSubMenuAction: click links: - id: tsa_reports type: MenuGroup properties: title: Reports icon: chart links: - id: tsa_sales type: MenuLink properties: title: Sales - id: tsa_stock type: MenuLink properties: title: Stock - id: tsa_settings type: MenuLink properties: title: Settings icon: settings ``` ```yaml - id: css_tailwind_menu type: Menu class: shadow-md rounded-lg style: width: 256 properties: mode: vertical links: - id: css_tw_home type: MenuLink properties: title: Home icon: home - id: css_tw_reports type: MenuLink properties: title: Reports icon: chart - id: css_tw_settings type: MenuLink properties: title: Settings icon: settings - id: css_inline_menu type: Menu style: width: 256 border: "1px solid #d9d9d9" borderRadius: 8 padding: 4 properties: mode: vertical links: - id: css_in_dashboard type: MenuLink properties: title: Dashboard icon: Gauge - id: css_in_analytics type: MenuLink properties: title: Analytics icon: ChartLine - id: css_dark_bg type: Box class: bg-gradient-to-b from-slate-900 to-slate-800 p-4 rounded-lg blocks: - id: css_dark_menu type: Menu properties: mode: horizontal theme: dark links: - id: css_dk_home type: MenuLink properties: title: Home icon: home - id: css_dk_explore type: MenuLink properties: title: Explore icon: Compass - id: css_dk_profile type: MenuLink properties: title: Profile icon: user - id: css_link_style_menu type: Menu style: width: 256 properties: mode: vertical links: - id: css_ls_normal type: MenuLink properties: title: Normal Item - id: css_ls_styled type: MenuLink style: fontWeight: bold fontStyle: italic properties: title: Bold Italic Item - id: css_ls_colored type: MenuLink style: color: "#1677ff" properties: title: Blue Text Item ``` ```yaml - id: tt_colors_menu type: Menu properties: mode: horizontal theme: itemSelectedColor: "#722ed1" horizontalItemSelectedColor: "#722ed1" itemHoverColor: "#9254de" horizontalItemHoverColor: "#9254de" selectedKeys: - tt_active links: - id: tt_home type: MenuLink properties: title: Home icon: home - id: tt_active type: MenuLink properties: title: Active icon: star - id: tt_settings type: MenuLink properties: title: Settings icon: settings - id: tt_sizing_menu type: Menu style: width: 280 properties: mode: vertical theme: itemHeight: 50 iconSize: 18 itemBorderRadius: 12 itemPaddingInline: 24 fontSize: 16 links: - id: tt_sz_home type: MenuLink properties: title: Dashboard icon: home - id: tt_sz_reports type: MenuLink properties: title: Reports icon: chart - id: tt_sz_settings type: MenuLink properties: title: Settings icon: settings - id: tt_dark_custom_menu type: Menu style: width: 256 properties: mode: vertical theme: itemBg: "#1a1a2e" itemColor: "#e0e0e0" itemHoverBg: "#16213e" itemHoverColor: "#00d4ff" itemSelectedBg: "#0f3460" itemSelectedColor: "#00d4ff" selectedKeys: - tt_dc_analytics links: - id: tt_dc_home type: MenuLink properties: title: Home icon: home - id: tt_dc_analytics type: MenuLink properties: title: Analytics icon: ChartLine - id: tt_dc_team type: MenuLink properties: title: Team icon: users - id: tt_danger_menu type: Menu style: width: 256 properties: mode: vertical theme: dangerItemColor: "#cf1322" dangerItemHoverColor: "#ff4d4f" dangerItemSelectedColor: "#fff" dangerItemSelectedBg: "#ff4d4f" links: - id: tt_dg_files type: MenuLink properties: title: My Files icon: file - id: tt_dg_divider type: MenuDivider - id: tt_dg_delete type: MenuLink properties: title: Delete Account icon: delete danger: true ``` Admin Panel Manage your blog posts here. Use the sidebar to navigate between sections. **Recent Activity:** - 3 new posts published this week - 12 comments pending review - 2 pages updated ```yaml - id: admin_layout type: Box layout: direction: row gap: 0 blocks: - id: admin_sidebar type: Box layout: flex: 0 0 256px blocks: - id: admin_logo type: Markdown class: px-4 py-3 font-bold text-lg border-b border-border properties: content: Admin Panel - id: admin_menu type: Menu style: width: 256 properties: mode: inline defaultOpenKeys: - adm_content selectedKeys: - adm_posts links: - id: adm_overview type: MenuLink properties: title: Overview icon: Gauge - id: adm_content type: MenuGroup properties: title: Content icon: document links: - id: adm_posts type: MenuLink properties: title: Posts - id: adm_pages type: MenuLink properties: title: Pages - id: adm_media type: MenuLink properties: title: Media - id: adm_users type: MenuGroup properties: title: Users icon: users links: - id: adm_all_users type: MenuLink properties: title: All Users - id: adm_roles type: MenuLink properties: title: Roles - id: adm_divider type: MenuDivider - id: adm_danger_zone type: MenuLink properties: title: Danger Zone icon: warning danger: true events: onSelect: - id: adm_select_msg type: DisplayMessage params: content: _string.concat: - "Navigated to: " - _event: key status: info - id: admin_main type: Card layout: flex: 1 1 auto properties: title: Posts size: small blocks: - id: admin_welcome type: Markdown properties: content: > Manage your blog posts here. Use the sidebar to navigate between sections. **Recent Activity:** - 3 new posts published this week - 12 comments pending review - 2 pages updated ``` ## Configuration Guide Learn how to configure your Lowdefy application with YAML configuration files. **Topics covered:** - App configuration - Page definitions - Block properties - Event handling ```yaml - id: docs_header_box type: Box class: border-b border-border blocks: - id: docs_nav_menu type: Menu properties: mode: horizontal selectedKeys: - docs_guides theme: horizontalItemSelectedColor: "#1677ff" horizontalItemHoverColor: "#4096ff" activeBarHeight: 3 links: - id: docs_getting_started type: MenuLink properties: title: Getting Started icon: Rocket - id: docs_guides type: MenuLink properties: title: Guides icon: Book - id: docs_api_ref type: MenuGroup properties: title: API Reference links: - id: docs_rest type: MenuLink properties: title: REST API - id: docs_graphql type: MenuLink properties: title: GraphQL - id: docs_api_divider type: MenuDivider - id: docs_changelog type: MenuLink properties: title: Changelog - id: docs_examples type: MenuLink properties: title: Examples icon: CodeXml events: onClick: - id: docs_click_msg type: DisplayMessage params: content: _string.concat: - "Opening: " - _event: key status: success - id: docs_content_card type: Card properties: title: Guides size: small blocks: - id: docs_content type: Markdown properties: content: > ## Configuration Guide Learn how to configure your Lowdefy application with YAML configuration files. **Topics covered:** - App configuration - Page definitions - Block properties - Event handling ``` ```yaml - id: ip_per_item_class_menu type: Menu style: width: 280 properties: mode: vertical links: - id: ip_dashboard type: MenuLink class: rounded-lg properties: title: Dashboard icon: Gauge - id: ip_reports type: MenuLink class: .element: rounded-lg .icon: text-blue-500 .label: font-semibold properties: title: Reports icon: chart - id: ip_settings type: MenuLink style: .element: borderLeft: "3px solid #1677ff" .label: letterSpacing: 0.05em properties: title: Settings icon: settings - id: ip_disabled_tooltip_extra_menu type: Menu style: width: 280 properties: mode: vertical links: - id: ip_search type: MenuLink properties: title: Search icon: search extra: ⌘K tooltip: Open command palette - id: ip_history type: MenuLink properties: title: History icon: history disabled: true tooltip: Coming soon - id: ip_divider type: MenuDivider - id: ip_delete type: MenuLink properties: title: Delete account icon: delete danger: true extra: irreversible - id: ip_popup_class_menu type: Menu style: width: 280 properties: mode: vertical links: - id: ip_group type: MenuGroup class: .popup: shadow-2xl rounded-lg properties: title: Settings icon: settings links: - id: ip_general type: MenuLink properties: title: General - id: ip_security type: MenuLink properties: title: Security ``` ```yaml - id: ks_menu type: Menu style: width: 256 properties: mode: vertical links: - id: ks_home type: MenuLink properties: title: Home icon: home shortcut: mod+1 - id: ks_search type: MenuLink properties: title: Search icon: search shortcut: mod+k - id: ks_settings type: MenuLink properties: title: Settings icon: settings shortcut: mod+, events: onSelect: - id: ks_select_msg type: DisplayMessage params: content: _string.concat: - "Selected: " - _event: key status: info ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `expandIcon` | string \| object | - | Menu expand icon. | | `expandIcon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `expandIcon.color` | string | - | Icon color. | | `expandIcon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `expandIcon.rotate` | number | - | Number of degrees to rotate the icon. | | `expandIcon.spin` | boolean | - | Continuously spin the icon with animation. | | `expandIcon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `expandIcon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `expandIcon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `expandIcon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `menuId` | string | - | App menu id used to get menu links. | | `mode` | string | `"vertical"` | Type of menu to render. Enum: `vertical`, `horizontal`, `inline`. | | `selectedKeys` | array | - | Array with the keys of currently selected menu items. | | `defaultOpenKeys` | array | - | Array with the keys of default opened sub menus. | | `collapsed` | boolean | `false` | Collapse the inline menu. | | `inlineIndent` | number | `24` | Indent width for each sub menu level in pixels (inline mode only). | | `forceSubMenuRender` | boolean | `false` | Render submenu into DOM before it becomes visible. | | `nestedGroupAsGroup` | boolean | `true` | Render nested MenuGroups as always-visible headed sections (antd item groups). Set false to render them as collapsible submenus at every depth. | | `subMenuCloseDelay` | number | - | Delay time to hide submenu when mouse leaves (in seconds). | | `subMenuOpenDelay` | number | - | Delay time to show submenu when mouse enters (in seconds). | | `triggerSubMenuAction` | string | `"hover"` | Open and close submenus on hover or on click. Enum: `hover`, `click`. | | `tooltip` | boolean \| object | `true` | Tooltip of menu items in a collapsed inline menu. Set false to hide it, or an object to configure it. | | `tooltip.placement` | string | `"right"` | Position of the tooltip relative to the menu item. Enum: `top`, `left`, `right`, `bottom`, `topLeft`, `topRight`, `bottomLeft`, `bottomRight`, `leftTop`, `leftBottom`, `rightTop`, `rightBottom`. | | `tooltip.color` | string | - | Background color of the tooltip. | | `theme` | string \| object | - | The Menu color theme, light or dark, or antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design menu tokens](https://ant.design/components/menu#design-token). | | `theme.dropdownWidth` | number | `160` | Width of dropdown submenus. | | `theme.zIndexPopup` | number | `1050` | Z-index for popup submenus. | | `theme.itemBorderRadius` | number | `8` | Border radius for menu items. | | `theme.subMenuItemBorderRadius` | number | `4` | Border radius for submenu items. | | `theme.itemColor` | string | - | Text color of menu items. | | `theme.itemHoverColor` | string | - | Text color when hovering menu items. | | `theme.horizontalItemHoverColor` | string | - | Text color when hovering horizontal menu items. | | `theme.itemSelectedColor` | string | - | Text color of selected menu items. | | `theme.horizontalItemSelectedColor` | string | - | Text color of selected horizontal menu items. | | `theme.itemDisabledColor` | string | - | Text color of disabled menu items. | | `theme.dangerItemColor` | string | - | Text color of danger menu items. | | `theme.dangerItemHoverColor` | string | - | Text color when hovering danger items. | | `theme.dangerItemSelectedColor` | string | - | Text color of selected danger items. | | `theme.dangerItemSelectedBg` | string | - | Background color of selected danger items. | | `theme.dangerItemActiveBg` | string | - | Background color of active danger items. | | `theme.itemBg` | string | - | Background color of menu items. | | `theme.itemHoverBg` | string | - | Background color when hovering menu items. | | `theme.subMenuItemBg` | string | - | Background color of submenu items. | | `theme.itemActiveBg` | string | - | Background color of active menu items. | | `theme.itemSelectedBg` | string | - | Background color of selected menu items. | | `theme.horizontalItemSelectedBg` | string | - | Background color of selected horizontal items. | | `theme.horizontalItemHoverBg` | string | - | Background color of hovered horizontal items. | | `theme.horizontalItemBorderRadius` | number | `0` | Border radius for horizontal menu items. | | `theme.activeBarWidth` | number | `0` | Width of active indicator bar. | | `theme.activeBarHeight` | number | `2` | Height of active indicator bar. | | `theme.activeBarBorderWidth` | number | `1` | Border width of active indicator bar. | | `theme.itemHeight` | number | `40` | Height of menu items. | | `theme.itemMarginInline` | number | `4` | Horizontal margin between items. | | `theme.itemMarginBlock` | number | `4` | Vertical margin between items. | | `theme.itemPaddingInline` | number | `16` | Horizontal padding of menu items. | | `theme.collapsedWidth` | number | `80` | Width of collapsed inline menu. | | `theme.popupBg` | string | - | Background color of popup submenus. | | `theme.groupTitleColor` | string | - | Text color of group titles. | | `theme.groupTitleFontSize` | number | `14` | Font size of group titles. | | `theme.iconSize` | number | `14` | Icon size in menu items. | | `theme.iconMarginInlineEnd` | number | `10` | Margin after icon in menu items. | | `theme.collapsedIconSize` | number | `16` | Icon size in collapsed menu. | | `theme.darkItemColor` | string | - | Text color in dark theme. | | `theme.darkItemBg` | string | `"#001529"` | Background color in dark theme. | | `theme.darkSubMenuItemBg` | string | `"#000c17"` | Submenu background in dark theme. | | `theme.darkItemSelectedColor` | string | - | Selected item text color in dark theme. | | `theme.darkItemSelectedBg` | string | - | Selected item background in dark theme. | | `theme.darkItemHoverBg` | string | - | Hover background in dark theme. | | `theme.darkItemHoverColor` | string | - | Hover text color in dark theme. | | `theme.darkGroupTitleColor` | string | - | Group title color in dark theme. | | `theme.darkItemDisabledColor` | string | - | Disabled item color in dark theme. | | `theme.darkPopupBg` | string | `"#001529"` | Popup background in dark theme. | | `theme.darkDangerItemColor` | string | - | Danger item color in dark theme. | | `theme.darkDangerItemSelectedBg` | string | - | Selected danger item background in dark theme. | | `theme.darkDangerItemHoverColor` | string | - | Hover danger item color in dark theme. | | `theme.darkDangerItemSelectedColor` | string | - | Selected danger item text color in dark theme. | | `theme.darkDangerItemActiveBg` | string | - | Active danger item background in dark theme. | | `links` | array | - | | | `links.$.id` | string | - | Menu item id. | | `links.$.type` | string | `"MenuLink"` | Menu item type. Enum: `MenuDivider`, `MenuLink`, `MenuGroup`. | | `links.$.pageId` | string | - | Page to link to. | | `links.$.style` | object \| string \| array | - | CSS styles for the menu item. Use a flat object for the item wrapper, or use dot-prefixed slot keys (`.element`, `.icon`, `.label`) to target specific parts. | | `links.$.class` | string \| array \| object | - | CSS classes for the menu item (including Tailwind utilities). Flat string/array applies to the item wrapper. Use an object with dot-prefixed slot keys (`.element`, `.icon`, `.label`, `.popup` — popup only on MenuGroup) to target specific parts. | | `links.$.properties` | object | - | properties from menu item. | | `links.$.properties.title` | string | - | Menu item title. | | `links.$.properties.icon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to customize icon on menu item. | | `links.$.properties.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `links.$.properties.icon.color` | string | - | Icon color. | | `links.$.properties.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `links.$.properties.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `links.$.properties.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `links.$.properties.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `links.$.properties.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `links.$.properties.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `links.$.properties.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `links.$.properties.danger` | boolean | `false` | Apply danger style (MenuLink only). Switches the item onto the `dangerItem*` token set — theme via `properties.theme.dangerItemColor` etc. | | `links.$.properties.disabled` | boolean | `false` | Disable the menu item (blocks clicks and applies a greyed style). | | `links.$.properties.tooltip` | string | - | Tooltip text shown on hover when the menu is collapsed. Maps to antd item `title`. | | `links.$.properties.extra` | string | - | Free-form right-aligned label on a MenuLink (e.g. a status hint like "beta" or "soon"). For real keyboard shortcuts use `shortcut` instead — it renders a kbd badge AND wires the key handler. When both are set, `shortcut` sits to the far right of `extra`. | | `links.$.properties.dashed` | boolean | `false` | Whether the divider line is dashed (MenuDivider only). | | `links.$.properties.shortcut` | string | - | Keyboard shortcut for this menu item. Renders a kbd badge floated to the far right of the item AND wires the key handler (fires onSelect when pressed). Use "mod" for Cmd/Ctrl. | | `links.$.links` | array | - | | | `links.$.links.$.id` | string | - | Menu item id. | | `links.$.links.$.type` | string | `"MenuLink"` | Menu item type. Enum: `MenuDivider`, `MenuLink`, `MenuGroup`. | | `links.$.links.$.style` | object \| string \| array | - | CSS styles for the menu item. Use a flat object for the item wrapper, or dot-prefixed slot keys (`.element`, `.icon`, `.label`). | | `links.$.links.$.class` | string \| array \| object | - | CSS classes for the menu item. Flat applies to the item wrapper; use dot-prefixed slot keys to target parts. | | `links.$.links.$.pageId` | string | - | Page to link to. | | `links.$.links.$.properties` | object | - | properties from menu item. | | `links.$.links.$.properties.title` | string | - | Menu item title. | | `links.$.links.$.properties.icon` | string \| object | - | Icon name or Icon block properties. | | `links.$.links.$.properties.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `links.$.links.$.properties.icon.color` | string | - | Icon color. | | `links.$.links.$.properties.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `links.$.links.$.properties.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `links.$.links.$.properties.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `links.$.links.$.properties.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `links.$.links.$.properties.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `links.$.links.$.properties.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `links.$.links.$.properties.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `links.$.links.$.properties.danger` | boolean | `false` | Apply danger style (MenuLink only). | | `links.$.links.$.properties.disabled` | boolean | `false` | Disable the menu item. | | `links.$.links.$.properties.tooltip` | string | - | Tooltip text shown when the menu is collapsed. | | `links.$.links.$.properties.extra` | string | - | Free-form right-aligned label on a MenuLink. For real keybindings use `shortcut`; when both are set, `shortcut` sits to the right of `extra`. | | `links.$.links.$.properties.dashed` | boolean | `false` | Whether the divider line is dashed. | | `links.$.links.$.properties.shortcut` | string | - | Keyboard shortcut for this menu item. Renders a kbd badge floated to the far right of the item AND wires the key handler (fires onSelect when pressed). Use "mod" for Cmd/Ctrl. | | `links.$.links.$.links` | array | - | | | `links.$.links.$.links.$.id` | string | - | Menu item id. | | `links.$.links.$.links.$.type` | string | `"MenuLink"` | Menu item type. Enum: `MenuDivider`, `MenuLink`. | | `links.$.links.$.links.$.style` | object \| string \| array | - | CSS styles for the menu item. Use a flat object or dot-prefixed slot keys. | | `links.$.links.$.links.$.class` | string \| array \| object | - | CSS classes for the menu item. | | `links.$.links.$.links.$.pageId` | string | - | Page to link to. | | `links.$.links.$.links.$.properties` | object | - | properties from menu item. | | `links.$.links.$.links.$.properties.title` | string | - | Menu item title. | | `links.$.links.$.links.$.properties.icon` | string \| object | - | Icon name or Icon block properties. | | `links.$.links.$.links.$.properties.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `links.$.links.$.links.$.properties.icon.color` | string | - | Icon color. | | `links.$.links.$.links.$.properties.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `links.$.links.$.links.$.properties.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `links.$.links.$.links.$.properties.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `links.$.links.$.links.$.properties.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `links.$.links.$.links.$.properties.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `links.$.links.$.links.$.properties.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `links.$.links.$.links.$.properties.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `links.$.links.$.links.$.properties.danger` | boolean | `false` | Apply danger style (MenuLink only). | | `links.$.links.$.links.$.properties.disabled` | boolean | `false` | Disable the menu item. | | `links.$.links.$.links.$.properties.tooltip` | string | - | Tooltip text shown when the menu is collapsed. | | `links.$.links.$.links.$.properties.extra` | string | - | Free-form right-aligned label on a MenuLink. For real keybindings use `shortcut`. | | `links.$.links.$.links.$.properties.dashed` | boolean | `false` | Whether the divider line is dashed. | | `links.$.links.$.links.$.properties.shortcut` | string | - | Keyboard shortcut. Renders a kbd badge floated to the far right and wires the key handler. Use "mod" for Cmd/Ctrl. | | Event | Event Data | Description | | --- | --- | --- | | `onSelect` | `{ key, keyPath }` | Trigger action when menu item is selected. | | `onClick` | `{ key, keyPath }` | Trigger action when menu item is clicked. | | `onToggleMenuGroup` | `{ openKeys }` | Trigger action when mobile menu group is opened. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Menu element. | | `/expandIcon` | The expand icon in the Menu. | | `/icon` | Deprecated alias for `itemIcon`. | | `/itemIcon` | The icon shown in each menu item. | | `/item` | The Menu item wrapper (li). | | `/popup` | The floating popup of a submenu in vertical and horizontal modes. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/message.md --- # Message Global message notification displayed at the top of the page. Operation completed successfully Something went wrong Please check your input Here is some useful information Loading data... ```yaml - id: status_success_msg type: Message properties: status: success content: Operation completed successfully - id: status_error_msg type: Message properties: status: error content: Something went wrong - id: status_warning_msg type: Message properties: status: warning content: Please check your input - id: status_info_msg type: Message properties: status: info content: Here is some useful information - id: status_loading_msg type: Message properties: status: loading content: Loading data... duration: 3 - id: btn_status_success type: Button layout: flex: 0 0 auto properties: title: Success color: primary variant: outlined icon: check-circle events: onClick: - id: call_status_success type: CallMethod params: blockId: status_success_msg method: open - id: btn_status_error type: Button layout: flex: 0 0 auto properties: title: Error color: danger variant: outlined icon: close-circle events: onClick: - id: call_status_error type: CallMethod params: blockId: status_error_msg method: open - id: btn_status_warning type: Button layout: flex: 0 0 auto properties: title: Warning color: default variant: outlined icon: warning events: onClick: - id: call_status_warning type: CallMethod params: blockId: status_warning_msg method: open - id: btn_status_info type: Button layout: flex: 0 0 auto properties: title: Info color: primary variant: text icon: info events: onClick: - id: call_status_info type: CallMethod params: blockId: status_info_msg method: open - id: btn_status_loading type: Button layout: flex: 0 0 auto properties: title: Loading color: default variant: outlined icon: loading events: onClick: - id: call_status_loading type: CallMethod params: blockId: status_loading_msg method: open ``` Gone in 1 second Default duration (4.5s) Stays for 10 seconds Will not auto-close (duration 0) ```yaml - id: dur_short_msg type: Message properties: status: info content: Gone in 1 second duration: 1 - id: dur_default_msg type: Message properties: status: info content: Default duration (4.5s) - id: dur_long_msg type: Message properties: status: info content: Stays for 10 seconds duration: 10 - id: dur_sticky_msg type: Message properties: status: warning content: Will not auto-close (duration 0) duration: 0 - id: btn_dur_short type: Button layout: flex: 0 0 auto properties: title: 1 second color: default variant: outlined events: onClick: - id: call_dur_short type: CallMethod params: blockId: dur_short_msg method: open - id: btn_dur_default type: Button layout: flex: 0 0 auto properties: title: Default (4.5s) color: default variant: outlined events: onClick: - id: call_dur_default type: CallMethod params: blockId: dur_default_msg method: open - id: btn_dur_long type: Button layout: flex: 0 0 auto properties: title: 10 seconds color: default variant: outlined events: onClick: - id: call_dur_long type: CallMethod params: blockId: dur_long_msg method: open - id: btn_dur_sticky type: Button layout: flex: 0 0 auto properties: title: Sticky (duration 0) color: default variant: outlined events: onClick: - id: call_dur_sticky type: CallMethod params: blockId: dur_sticky_msg method: open ``` File saved Notification sent Custom colored star icon ```yaml - id: icon_save_msg type: Message properties: status: success content: File saved icon: save - id: icon_bell_msg type: Message properties: status: info content: Notification sent icon: bell - id: icon_star_msg type: Message properties: status: info content: Custom colored star icon icon: name: star color: "#faad14" - id: btn_icon_save type: Button layout: flex: 0 0 auto properties: title: Save Icon color: primary variant: outlined icon: save events: onClick: - id: call_icon_save type: CallMethod params: blockId: icon_save_msg method: open - id: btn_icon_bell type: Button layout: flex: 0 0 auto properties: title: Bell Icon color: primary variant: outlined icon: bell events: onClick: - id: call_icon_bell type: CallMethod params: blockId: icon_bell_msg method: open - id: btn_icon_star type: Button layout: flex: 0 0 auto properties: title: Star Icon (colored) color: default variant: outlined icon: star events: onClick: - id: call_icon_star type: CallMethod params: blockId: icon_star_msg method: open ``` Bold content in message Visit Lowdefy for more info Important: Please review ```yaml - id: html_bold_msg type: Message properties: status: success content: Bold content in message - id: html_link_msg type: Message properties: status: info content: Visit Lowdefy for more info - id: html_styled_msg type: Message properties: status: warning content: 'Important: Please review' - id: btn_html_bold type: Button layout: flex: 0 0 auto properties: title: Bold HTML color: default variant: outlined events: onClick: - id: call_html_bold type: CallMethod params: blockId: html_bold_msg method: open - id: btn_html_link type: Button layout: flex: 0 0 auto properties: title: HTML Link color: default variant: outlined events: onClick: - id: call_html_link type: CallMethod params: blockId: html_link_msg method: open - id: btn_html_styled type: Button layout: flex: 0 0 auto properties: title: Styled HTML color: default variant: outlined events: onClick: - id: call_html_styled type: CallMethod params: blockId: html_styled_msg method: open ``` Custom background color Larger font with custom padding Shadow and rounded corners ```yaml - id: mstyle_bg_msg type: Message properties: status: success content: Custom background color style: .element: border: "1px solid #b7eb8f" - id: mstyle_font_msg type: Message properties: status: info content: Larger font with custom padding style: .element: fontSize: 16 padding: 12px 24px - id: mstyle_shadow_msg type: Message properties: status: warning content: Shadow and rounded corners style: .element: boxShadow: 0 4px 12px rgba(0, 0, 0, 0.15) borderRadius: 12 - id: btn_mstyle_bg type: Button layout: flex: 0 0 auto properties: title: Custom Background color: primary variant: outlined events: onClick: - id: call_mstyle_bg type: CallMethod params: blockId: mstyle_bg_msg method: open - id: btn_mstyle_font type: Button layout: flex: 0 0 auto properties: title: Large Font color: primary variant: outlined events: onClick: - id: call_mstyle_font type: CallMethod params: blockId: mstyle_font_msg method: open - id: btn_mstyle_shadow type: Button layout: flex: 0 0 auto properties: title: Shadow + Rounded color: primary variant: outlined events: onClick: - id: call_mstyle_shadow type: CallMethod params: blockId: mstyle_shadow_msg method: open ``` Default message ```yaml - id: override_msg type: Message properties: status: info content: Default message - id: btn_override_success type: Button layout: flex: 0 0 auto properties: title: Open as Success color: primary variant: solid icon: check-circle events: onClick: - id: call_override_success type: CallMethod params: blockId: override_msg method: open args: - status: success content: Overridden to success at call time! duration: 3 - id: btn_override_error type: Button layout: flex: 0 0 auto properties: title: Open as Error color: danger variant: solid icon: close-circle events: onClick: - id: call_override_error type: CallMethod params: blockId: override_msg method: open args: - status: error content: Overridden to error at call time! duration: 3 - id: btn_override_icon type: Button layout: flex: 0 0 auto properties: title: Open with Custom Icon color: default variant: solid icon: Rocket events: onClick: - id: call_override_icon type: CallMethod params: blockId: override_msg method: open args: - status: info content: Launched with a rocket icon! icon: Rocket duration: 3 ``` Close me to trigger the onClose event ```yaml - id: onclose_msg type: Message properties: status: info content: Close me to trigger the onClose event duration: 3 events: onClose: - id: onclose_set_state type: SetState params: messageClosed: true - id: onclose_display type: DisplayMessage params: content: onClose event fired! status: success - id: btn_onclose type: Button layout: flex: 0 0 auto properties: title: Open Message with onClose color: primary variant: outlined icon: Megaphone events: onClick: - id: call_onclose type: CallMethod params: blockId: onclose_msg method: open ``` Message with shadow via Tailwind class Message with rounded corners via Tailwind class Message with inline border style ```yaml - id: css_shadow_msg type: Message class: .element: shadow-lg properties: status: success content: Message with shadow via Tailwind class - id: css_rounded_msg type: Message class: .element: rounded-full properties: status: info content: Message with rounded corners via Tailwind class - id: css_custom_msg type: Message style: .element: border: "2px solid #1677ff" borderRadius: 8 properties: status: info content: Message with inline border style - id: btn_css_shadow type: Button layout: flex: 0 0 auto properties: title: Shadow Class color: default variant: outlined events: onClick: - id: call_css_shadow type: CallMethod params: blockId: css_shadow_msg method: open - id: btn_css_rounded type: Button layout: flex: 0 0 auto properties: title: Rounded Class color: default variant: outlined events: onClick: - id: call_css_rounded type: CallMethod params: blockId: css_rounded_msg method: open - id: btn_css_custom type: Button layout: flex: 0 0 auto properties: title: Inline Border Style color: default variant: outlined events: onClick: - id: call_css_custom type: CallMethod params: blockId: css_custom_msg method: open ``` Thank you for your feedback! Please fill in all required fields. ```yaml - id: form_card type: Card properties: title: Submit Feedback size: small blocks: - id: form_name type: TextInput required: true properties: label: title: Name placeholder: Your name - id: form_rating type: RatingSlider required: true properties: label: title: Rating - id: form_comments type: TextArea properties: label: title: Comments placeholder: Tell us what you think rows: 3 - id: form_submit_msg type: Message properties: status: success content: Thank you for your feedback! duration: 3 events: onClose: - id: form_close_set type: SetState params: feedbackSubmitted: true - id: form_error_msg type: Message properties: status: error content: Please fill in all required fields. duration: 4 - id: form_actions type: Box layout: gap: 8 justify: flex-end blocks: - id: form_cancel type: Button layout: flex: 0 0 auto properties: title: Cancel color: default variant: outlined events: onClick: - id: form_cancel_reset type: ResetValidation - id: form_cancel_msg type: DisplayMessage params: content: Form cleared status: info - id: form_submit type: Button layout: flex: 0 0 auto properties: title: Submit Feedback color: primary variant: solid icon: send events: onClick: - id: form_validate type: Validate params: - form_name - form_rating - id: form_open_success type: CallMethod params: blockId: form_submit_msg method: open ``` Upload your project files below. Supported formats: PDF, DOCX, XLSX. Maximum file size: 10 MB. Uploading file... File uploaded successfully! Upload failed. Please try again. ```yaml - id: upload_card type: Card properties: title: Upload Documents size: small blocks: - id: upload_description type: Markdown properties: content: | Upload your project files below. Supported formats: PDF, DOCX, XLSX. Maximum file size: 10 MB. - id: upload_progress_msg type: Message properties: status: loading content: Uploading file... duration: 0 - id: upload_success_msg type: Message properties: status: success content: File uploaded successfully! icon: CloudUpload duration: 3 - id: upload_fail_msg type: Message properties: status: error content: Upload failed. Please try again. duration: 5 - id: upload_actions type: Box layout: gap: 8 justify: flex-start blocks: - id: upload_start type: Button layout: flex: 0 0 auto properties: title: Start Upload color: primary variant: solid icon: upload events: onClick: - id: upload_show_progress type: CallMethod params: blockId: upload_progress_msg method: open - id: upload_complete type: Button layout: flex: 0 0 auto properties: title: Simulate Success color: green variant: outlined icon: check-circle events: onClick: - id: upload_show_success type: CallMethod params: blockId: upload_success_msg method: open - id: upload_fail type: Button layout: flex: 0 0 auto properties: title: Simulate Failure color: danger variant: outlined icon: close-circle events: onClick: - id: upload_show_fail type: CallMethod params: blockId: upload_fail_msg method: open ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `content` | string | - | The content of the message - supports html. | | `duration` | number | `3` | Time(seconds) before auto-dismiss, don't dismiss if set to 0. | | `pauseOnHover` | boolean | `true` | Pause the auto-dismiss timer while the mouse is over the message. | | `icon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to customize message icon. | | `icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `icon.color` | string | - | Icon color. | | `icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `icon.rotate` | number | - | Number of degrees to rotate the icon. | | `icon.spin` | boolean | - | Continuously spin the icon with animation. | | `icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `status` | string | `"success"` | Message status type. Enum: `success`, `error`, `info`, `warning`, `loading`. | | `theme` | object | - | Has no effect: messages render in the app-wide message holder, outside the block. Set these Message design tokens for the whole app in `lowdefy.yaml` under `theme.antd.components.Message`. See [Ant Design message tokens](https://ant.design/components/message#design-token). | | `theme.zIndexPopup` | number | `1080` | Z-index of the message popup. | | `theme.contentBg` | string | - | Background color of the message content. | | `theme.contentPadding` | string | - | Padding of the message content. Calculated from controlHeightLG, fontSize, lineHeight, and paddingSM. | | Event | Event Data | Description | | --- | --- | --- | | `onClick` | \- | Trigger actions when the message is clicked. | | `onClose` | \- | Trigger actions when message is closed. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Message element. | | `/icon` | The icon in the Message. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/notification.md --- # Notification Notification message displayed in the corner of the page. ```yaml - id: notif_status_success type: Notification layout: span: 0 properties: status: success title: Changes Saved description: Your changes have been saved successfully. - id: notif_status_error type: Notification layout: span: 0 properties: status: error title: Save Failed description: Failed to save your changes. Please try again. - id: notif_status_warning type: Notification layout: span: 0 properties: status: warning title: Disk Space Low description: Server storage is running below 10% capacity. - id: notif_status_info type: Notification layout: span: 0 properties: status: info title: Update Available description: A new version of the application is available. - id: btn_status_success type: Button layout: flex: 0 0 auto properties: title: Success color: primary variant: outlined icon: check-circle events: onClick: - id: open_status_success type: CallMethod params: blockId: notif_status_success method: open - id: btn_status_error type: Button layout: flex: 0 0 auto properties: title: Error color: danger variant: outlined icon: close-circle events: onClick: - id: open_status_error type: CallMethod params: blockId: notif_status_error method: open - id: btn_status_warning type: Button layout: flex: 0 0 auto properties: title: Warning color: default variant: outlined icon: warning events: onClick: - id: open_status_warning type: CallMethod params: blockId: notif_status_warning method: open - id: btn_status_info type: Button layout: flex: 0 0 auto properties: title: Info color: primary variant: text icon: info events: onClick: - id: open_status_info type: CallMethod params: blockId: notif_status_info method: open ``` ```yaml - id: notif_place_top_right type: Notification layout: span: 0 properties: status: info title: Top Right description: Default placement position. placement: topRight - id: notif_place_top_left type: Notification layout: span: 0 properties: status: info title: Top Left description: Appears at the top left corner. placement: topLeft - id: notif_place_bottom_right type: Notification layout: span: 0 properties: status: info title: Bottom Right description: Appears at the bottom right corner. placement: bottomRight - id: notif_place_bottom_left type: Notification layout: span: 0 properties: status: info title: Bottom Left description: Appears at the bottom left corner. placement: bottomLeft - id: btn_place_top_right type: Button layout: flex: 0 0 auto properties: title: Top Right color: default variant: outlined events: onClick: - id: open_place_top_right type: CallMethod params: blockId: notif_place_top_right method: open - id: btn_place_top_left type: Button layout: flex: 0 0 auto properties: title: Top Left color: default variant: outlined events: onClick: - id: open_place_top_left type: CallMethod params: blockId: notif_place_top_left method: open - id: btn_place_bottom_right type: Button layout: flex: 0 0 auto properties: title: Bottom Right color: default variant: outlined events: onClick: - id: open_place_bottom_right type: CallMethod params: blockId: notif_place_bottom_right method: open - id: btn_place_bottom_left type: Button layout: flex: 0 0 auto properties: title: Bottom Left color: default variant: outlined events: onClick: - id: open_place_bottom_left type: CallMethod params: blockId: notif_place_bottom_left method: open ``` ```yaml - id: notif_dur_short type: Notification layout: span: 0 properties: status: info title: Quick Notice description: Disappears in 2 seconds. duration: 2 - id: notif_dur_default type: Notification layout: span: 0 properties: status: info title: Default Duration description: Auto-closes after 4.5 seconds (default). - id: notif_dur_long type: Notification layout: span: 0 properties: status: info title: Extended Duration description: Stays visible for 10 seconds. duration: 10 - id: notif_dur_sticky type: Notification layout: span: 0 properties: status: warning title: Persistent description: Will not auto-close (duration 0). Must be dismissed manually. duration: 0 - id: btn_dur_short type: Button layout: flex: 0 0 auto properties: title: 2 seconds color: default variant: outlined events: onClick: - id: open_dur_short type: CallMethod params: blockId: notif_dur_short method: open - id: btn_dur_default type: Button layout: flex: 0 0 auto properties: title: Default (4.5s) color: default variant: outlined events: onClick: - id: open_dur_default type: CallMethod params: blockId: notif_dur_default method: open - id: btn_dur_long type: Button layout: flex: 0 0 auto properties: title: 10 seconds color: default variant: outlined events: onClick: - id: open_dur_long type: CallMethod params: blockId: notif_dur_long method: open - id: btn_dur_sticky type: Button layout: flex: 0 0 auto properties: title: Sticky (duration 0) color: default variant: outlined events: onClick: - id: open_dur_sticky type: CallMethod params: blockId: notif_dur_sticky method: open ``` ```yaml - id: notif_progress type: Notification layout: span: 0 properties: status: info title: Export started description: The progress bar counts down to when this notification closes. Hover to pause it. duration: 8 showProgress: true - id: btn_progress type: Button layout: flex: 0 0 auto properties: title: Show Progress Bar color: default variant: outlined events: onClick: - id: open_progress type: CallMethod params: blockId: notif_progress method: open ``` ```yaml - id: notif_icon_string type: Notification layout: span: 0 properties: status: success title: Download Complete description: Your file has been downloaded successfully. icon: download - id: notif_icon_colored type: Notification layout: span: 0 properties: status: info title: Starred Item description: Item has been added to your favorites. icon: name: star color: "#faad14" - id: notif_icon_sized type: Notification layout: span: 0 properties: status: warning title: Session Expiring description: Your session will expire in 5 minutes. icon: name: lock color: "#fa8c16" size: 28 - id: btn_icon_string type: Button layout: flex: 0 0 auto properties: title: String Icon color: primary variant: outlined icon: download events: onClick: - id: open_icon_string type: CallMethod params: blockId: notif_icon_string method: open - id: btn_icon_colored type: Button layout: flex: 0 0 auto properties: title: Colored Icon color: default variant: outlined icon: star events: onClick: - id: open_icon_colored type: CallMethod params: blockId: notif_icon_colored method: open - id: btn_icon_sized type: Button layout: flex: 0 0 auto properties: title: Sized Icon color: default variant: outlined icon: lock events: onClick: - id: open_icon_sized type: CallMethod params: blockId: notif_icon_sized method: open ``` ```yaml - id: notif_close_icon_string type: Notification layout: span: 0 properties: status: info title: Custom Close description: This notification has a custom close icon. closeIcon: SquareX duration: 0 - id: notif_close_icon_styled type: Notification layout: span: 0 properties: status: warning title: Styled Close description: Close icon with custom color and size. closeIcon: name: close-circle color: "#ff4d4f" size: 18 duration: 0 - id: notif_close_icon_arrow type: Notification layout: span: 0 properties: status: success title: Arrow Close description: Uses an arrow icon instead of the default close. closeIcon: arrow-right duration: 0 - id: btn_close_icon_string type: Button layout: flex: 0 0 auto properties: title: Square Close color: default variant: outlined events: onClick: - id: open_close_icon_string type: CallMethod params: blockId: notif_close_icon_string method: open - id: btn_close_icon_styled type: Button layout: flex: 0 0 auto properties: title: Styled Close color: default variant: outlined events: onClick: - id: open_close_icon_styled type: CallMethod params: blockId: notif_close_icon_styled method: open - id: btn_close_icon_arrow type: Button layout: flex: 0 0 auto properties: title: Arrow Close color: default variant: outlined events: onClick: - id: open_close_icon_arrow type: CallMethod params: blockId: notif_close_icon_arrow method: open ``` ```yaml - id: notif_btn_confirm type: Notification layout: span: 0 properties: status: info title: Action Required description: Please review and confirm the pending changes. duration: 0 button: title: Confirm icon: check color: primary variant: solid size: small - id: notif_btn_retry type: Notification layout: span: 0 properties: status: error title: Upload Failed description: The file could not be uploaded. Please try again. duration: 0 button: title: Retry icon: refresh color: danger variant: solid size: small - id: notif_btn_undo type: Notification layout: span: 0 properties: status: success title: Item Deleted description: The record has been removed from the database. duration: 0 button: title: Undo color: default variant: outlined size: small - id: btn_trigger_confirm type: Button layout: flex: 0 0 auto properties: title: With Confirm color: primary variant: outlined events: onClick: - id: open_btn_confirm type: CallMethod params: blockId: notif_btn_confirm method: open - id: btn_trigger_retry type: Button layout: flex: 0 0 auto properties: title: With Retry color: danger variant: outlined events: onClick: - id: open_btn_retry type: CallMethod params: blockId: notif_btn_retry method: open - id: btn_trigger_undo type: Button layout: flex: 0 0 auto properties: title: With Undo color: default variant: outlined events: onClick: - id: open_btn_undo type: CallMethod params: blockId: notif_btn_undo method: open ``` ```yaml - id: notif_html_bold type: Notification layout: span: 0 properties: status: success title: Deployment Complete description: Deployed to production. Visit the site to verify. - id: notif_html_styled type: Notification layout: span: 0 properties: status: warning title: Maintenance Scheduled description: 'Downtime: Saturday 2:00 AM - 6:00 AM UTC.' - id: notif_html_list type: Notification layout: span: 0 properties: status: info title: Release Notes description:
  • Fixed login timeout issue
  • Added dark mode support
  • Improved dashboard performance
duration: 0 - id: btn_html_bold type: Button layout: flex: 0 0 auto properties: title: Bold and Link color: default variant: outlined events: onClick: - id: open_html_bold type: CallMethod params: blockId: notif_html_bold method: open - id: btn_html_styled type: Button layout: flex: 0 0 auto properties: title: Styled HTML color: default variant: outlined events: onClick: - id: open_html_styled type: CallMethod params: blockId: notif_html_styled method: open - id: btn_html_list type: Button layout: flex: 0 0 auto properties: title: HTML List color: default variant: outlined events: onClick: - id: open_html_list type: CallMethod params: blockId: notif_html_list method: open ``` ```yaml - id: notif_style_border type: Notification layout: span: 0 properties: status: success title: Custom Border description: This notification has a left green border. style: .element: borderLeft: "4px solid #52c41a" - id: notif_style_bg type: Notification layout: span: 0 properties: status: warning title: Custom Background description: This notification has a custom background color. style: .element: background: rgba(250, 173, 20, 0.1) - id: notif_style_shadow type: Notification layout: span: 0 properties: status: info title: Heavy Shadow description: This notification has an elevated shadow. style: .element: boxShadow: 0 6px 24px rgba(0, 0, 0, 0.2) - id: btn_style_border type: Button layout: flex: 0 0 auto properties: title: Left Border color: default variant: outlined events: onClick: - id: open_style_border type: CallMethod params: blockId: notif_style_border method: open - id: btn_style_bg type: Button layout: flex: 0 0 auto properties: title: Custom Background color: default variant: outlined events: onClick: - id: open_style_bg type: CallMethod params: blockId: notif_style_bg method: open - id: btn_style_shadow type: Button layout: flex: 0 0 auto properties: title: Heavy Shadow color: default variant: outlined events: onClick: - id: open_style_shadow type: CallMethod params: blockId: notif_style_shadow method: open ``` ```yaml - id: notif_override_base type: Notification layout: span: 0 properties: status: info title: Default Title description: Default notification description. - id: btn_override_success type: Button layout: flex: 0 0 auto properties: title: Override as Success color: primary variant: solid icon: check-circle events: onClick: - id: open_override_success type: CallMethod params: blockId: notif_override_base method: open args: - status: success title: Overridden Title description: Status, title, and description overridden at call time. - id: btn_override_error type: Button layout: flex: 0 0 auto properties: title: Override as Error color: danger variant: solid icon: close-circle events: onClick: - id: open_override_error type: CallMethod params: blockId: notif_override_base method: open args: - status: error title: Error Override description: This error notification was overridden at call time. - id: btn_override_duration type: Button layout: flex: 0 0 auto properties: title: Override Duration color: default variant: outlined icon: clock events: onClick: - id: open_override_duration type: CallMethod params: blockId: notif_override_base method: open args: - duration: 1 title: Quick Flash description: This closes after just 1 second. ``` ```yaml - id: notif_events_demo type: Notification layout: span: 0 properties: status: info title: Interactive Notification description: Click the notification body or close it to trigger events. duration: 0 events: onClick: - id: events_click_msg type: DisplayMessage params: content: Notification body clicked! status: info onClose: - id: events_close_msg type: DisplayMessage params: content: Notification was closed. status: success - id: notif_events_close_action type: Notification layout: span: 0 properties: status: warning title: Session Timeout description: Your session is about to expire. Close to extend. duration: 0 button: title: Extend Session color: primary variant: solid size: small events: onClose: - id: events_extend_msg type: DisplayMessage params: content: Session extended for 30 minutes. status: success - id: notif_events_click_nav type: Notification layout: span: 0 properties: status: success title: New Comment description: Click this notification to view the comment. duration: 8 events: onClick: - id: events_nav_msg type: DisplayMessage params: content: Navigating to comments section... status: info - id: btn_events_demo type: Button layout: flex: 0 0 auto properties: title: Open Interactive color: primary variant: solid events: onClick: - id: open_events_demo type: CallMethod params: blockId: notif_events_demo method: open - id: btn_events_close_action type: Button layout: flex: 0 0 auto properties: title: Session Timeout color: default variant: outlined events: onClick: - id: open_events_close type: CallMethod params: blockId: notif_events_close_action method: open - id: btn_events_click_nav type: Button layout: flex: 0 0 auto properties: title: Click to Navigate color: default variant: outlined events: onClick: - id: open_events_click_nav type: CallMethod params: blockId: notif_events_click_nav method: open ``` ```yaml - id: notif_css_tailwind type: Notification layout: span: 0 class: shadow-2xl properties: status: success title: Tailwind Shadow description: This notification uses a Tailwind shadow-2xl class. - id: notif_css_rounded type: Notification layout: span: 0 class: rounded-xl properties: status: info title: Rounded Corners description: Notification with extra-rounded corners via Tailwind. - id: notif_css_inline type: Notification layout: span: 0 style: .element: borderRadius: 12 border: "2px solid #1890ff" properties: status: info title: Inline Style description: This notification uses inline style with a blue border. - id: btn_css_tailwind type: Button layout: flex: 0 0 auto properties: title: Tailwind Shadow color: default variant: outlined events: onClick: - id: open_css_tailwind type: CallMethod params: blockId: notif_css_tailwind method: open - id: btn_css_rounded type: Button layout: flex: 0 0 auto properties: title: Rounded Corners color: default variant: outlined events: onClick: - id: open_css_rounded type: CallMethod params: blockId: notif_css_rounded method: open - id: btn_css_inline type: Button layout: flex: 0 0 auto properties: title: Inline Style color: default variant: outlined events: onClick: - id: open_css_inline type: CallMethod params: blockId: notif_css_inline method: open ``` **Customer:** Sarah Johnson **Items:** 3 items totaling $247.50 **Status:** Pending fulfillment ```yaml - id: order_card type: Card properties: title: "Order #12847" size: small blocks: - id: order_status_text type: Markdown properties: content: | **Customer:** Sarah Johnson **Items:** 3 items totaling $247.50 **Status:** Pending fulfillment - id: notif_order_shipped type: Notification layout: span: 0 properties: status: success title: Order Shipped description: "Order #12847 has been shipped. Tracking number: 1Z999AA10123456784" icon: Rocket duration: 0 button: title: View Tracking color: primary variant: solid size: small events: onClose: - id: order_close_msg type: DisplayMessage params: content: Notification dismissed. status: info - id: notif_order_cancelled type: Notification layout: span: 0 properties: status: error title: Order Cancelled description: "Order #12847 has been cancelled. Refund of $247.50 initiated." icon: Ban duration: 0 - id: order_actions type: Box layout: gap: 8 justify: flex-end blocks: - id: btn_order_cancel type: Button layout: flex: 0 0 auto properties: title: Cancel Order color: danger variant: outlined icon: Ban events: onClick: - id: order_cancel_notif type: CallMethod params: blockId: notif_order_cancelled method: open - id: btn_order_ship type: Button layout: flex: 0 0 auto properties: title: Mark as Shipped color: primary variant: solid icon: Rocket events: onClick: - id: order_ship_notif type: CallMethod params: blockId: notif_order_shipped method: open ``` Monitor system health and receive real-time alerts. Click a button below to simulate different alert types. ```yaml - id: alerts_card type: Card properties: title: System Monitor size: small blocks: - id: alerts_description type: Markdown properties: content: > Monitor system health and receive real-time alerts. Click a button below to simulate different alert types. - id: notif_alert_cpu type: Notification layout: span: 0 properties: status: warning title: High CPU Usage description: CPU usage has exceeded 90% on server web-prod-03. Consider scaling up or optimizing workloads. icon: name: Gauge color: "#fa8c16" duration: 0 placement: topRight button: title: View Details color: primary variant: outlined size: small events: onClick: - id: alert_cpu_click type: DisplayMessage params: content: Opening server dashboard... status: info - id: notif_alert_deploy type: Notification layout: span: 0 properties: status: success title: Deployment Successful description: Version 2.4.1 deployed to production. All health checks passing. icon: check-circle duration: 8 events: onClose: - id: alert_deploy_close type: DisplayMessage params: content: Deployment alert acknowledged. status: success - id: notif_alert_security type: Notification layout: span: 0 properties: status: error title: Security Alert description: Multiple failed login attempts detected from IP 192.168.1.42. Account temporarily locked. icon: name: lock color: "#ff4d4f" duration: 0 button: title: Block IP color: danger variant: solid size: small events: onClose: - id: alert_security_close type: SetState params: securityAlertDismissed: true - id: alerts_actions type: Box layout: gap: 8 blocks: - id: btn_alert_cpu type: Button layout: flex: 0 0 auto properties: title: CPU Warning color: default variant: outlined icon: Gauge events: onClick: - id: trigger_alert_cpu type: CallMethod params: blockId: notif_alert_cpu method: open - id: btn_alert_deploy type: Button layout: flex: 0 0 auto properties: title: Deploy Success color: primary variant: outlined icon: check-circle events: onClick: - id: trigger_alert_deploy type: CallMethod params: blockId: notif_alert_deploy method: open - id: btn_alert_security type: Button layout: flex: 0 0 auto properties: title: Security Alert color: danger variant: outlined icon: lock events: onClick: - id: trigger_alert_security type: CallMethod params: blockId: notif_alert_security method: open ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `bottom` | number | `24` | Has no effect: notifications open in the app-wide notification holder, which keeps them 24px from the bottom of the viewport. To move every bottom notification, set the `--notification-bottom` CSS variable on `.ant-notification` in `public/styles.css`, for example `.ant-notification { --notification-bottom: 80px; }`. | | `closable` | boolean | `true` | Whether to show the close button. | | `button` | object | - | Button object to customize the close button. Closes the notification and triggers the onClose event when clicked. | | `description` | string | - | The content of notification box - supports html. | | `duration` | number | `4.5` | Time in seconds before Notification is closed. When set to 0 or null, it will never be closed automatically. | | `icon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to customize notification icon. | | `icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `icon.color` | string | - | Icon color. | | `icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `icon.rotate` | number | - | Number of degrees to rotate the icon. | | `icon.spin` | boolean | - | Continuously spin the icon with animation. | | `icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `closeIcon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to customize close icon. | | `closeIcon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `closeIcon.color` | string | - | Icon color. | | `closeIcon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `closeIcon.rotate` | number | - | Number of degrees to rotate the icon. | | `closeIcon.spin` | boolean | - | Continuously spin the icon with animation. | | `closeIcon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `closeIcon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `closeIcon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `closeIcon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `title` | string | - | The title of notification box - supports html. | | `pauseOnHover` | boolean | `true` | Pause the auto-close timer while the mouse is over the notification. | | `placement` | string | `"topRight"` | Position of Notification. Enum: `top`, `topLeft`, `topRight`, `bottom`, `bottomLeft`, `bottomRight`. | | `role` | string | `"alert"` | How screen readers announce the notification. `alert` interrupts the reader immediately, `status` waits until it is idle. Enum: `alert`, `status`. | | `showProgress` | boolean | `false` | Show a progress bar counting down to when the notification closes. | | `top` | number | `24` | Has no effect: notifications open in the app-wide notification holder, which keeps them 24px from the top of the viewport. To move every top notification, set the `--notification-top` CSS variable on `.ant-notification` in `public/styles.css`, for example `.ant-notification { --notification-top: 80px; }`. | | `status` | string | `"success"` | Notification status type. Enum: `success`, `error`, `info`, `warning`. | | `theme` | object | - | Has no effect: notifications render in the app-wide notification holder, outside the block. Set these Notification design tokens for the whole app in `lowdefy.yaml` under `theme.antd.components.Notification`. See [Ant Design notification tokens](https://ant.design/components/notification#design-token). | | `theme.zIndexPopup` | number | `1100` | Z-index of the notification popup. | | `theme.width` | number | `384` | Width of the notification box. | | `theme.progressBg` | string | - | Background gradient for the auto-close progress bar. Defaults to a gradient from colorPrimaryBorderHover to colorPrimary. | | Event | Event Data | Description | | --- | --- | --- | | `onClose` | \- | Trigger actions when notification is closed. | | `onClick` | \- | Trigger actions when notification is clicked. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Notification element. | | `/title` | The Notification title. | | `/description` | The Notification description. | | `/actions` | The area holding the Notification button. | | `/progress` | The auto-close progress bar, shown with showProgress. | | `/closeIcon` | The close icon in the Notification. | | `/icon` | The icon in the Notification. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/paragraph.md --- # Paragraph Text paragraph with copyable, ellipsis, and text style options. Lowdefy is a config-driven web framework. Build internal tools, client portals, and web apps with YAML or JSON. This paragraph supports bold, italic, and links via HTML. A longer paragraph showing how body text renders at its default size. Lowdefy makes it easy to build internal tools, client portals, and web applications using YAML or JSON configuration files. No need to learn complex frontend frameworks or manage build tooling. ```yaml - id: basic_plain type: Paragraph properties: content: Lowdefy is a config-driven web framework. Build internal tools, client portals, and web apps with YAML or JSON. - id: basic_html type: Paragraph properties: content: This paragraph supports bold, italic, and links via HTML. - id: basic_long type: Paragraph properties: content: A longer paragraph showing how body text renders at its default size. Lowdefy makes it easy to build internal tools, client portals, and web applications using YAML or JSON configuration files. No need to learn complex frontend frameworks or manage build tooling. ``` Default paragraph text with the standard color. Secondary paragraph text with a muted appearance. Success paragraph indicating a positive outcome. Warning paragraph to alert the user about something. Danger paragraph highlighting a critical issue. ```yaml - id: type_default type: Paragraph properties: content: Default paragraph text with the standard color. - id: type_secondary type: Paragraph properties: content: Secondary paragraph text with a muted appearance. type: secondary - id: type_success type: Paragraph properties: content: Success paragraph indicating a positive outcome. type: success - id: type_warning type: Paragraph properties: content: Warning paragraph to alert the user about something. type: warning - id: type_danger type: Paragraph properties: content: Danger paragraph highlighting a critical issue. type: danger ``` Strong (bold) paragraph text for emphasis. Italic paragraph text often used for citations or side notes. Underlined paragraph text to draw attention. Deleted (strikethrough) text indicating removed content. Marked (highlighted) text to call out key information. Ctrl + K ```yaml - id: deco_strong type: Paragraph properties: content: Strong (bold) paragraph text for emphasis. strong: true - id: deco_italic type: Paragraph properties: content: Italic paragraph text often used for citations or side notes. italic: true - id: deco_underline type: Paragraph properties: content: Underlined paragraph text to draw attention. underline: true - id: deco_delete type: Paragraph properties: content: Deleted (strikethrough) text indicating removed content. delete: true - id: deco_mark type: Paragraph properties: content: Marked (highlighted) text to call out key information. mark: true - id: deco_keyboard type: Paragraph properties: content: Ctrl + K keyboard: true ``` Bold and italic text for maximum emphasis. Underlined and highlighted to draw strong attention. This feature is deprecated and will be removed in a future version. Critical security vulnerability detected. ```yaml - id: combined_strong_italic type: Paragraph properties: content: Bold and italic text for maximum emphasis. strong: true italic: true - id: combined_underline_mark type: Paragraph properties: content: Underlined and highlighted to draw strong attention. underline: true mark: true - id: combined_warning_italic type: Paragraph properties: content: This feature is deprecated and will be removed in a future version. type: warning italic: true underline: true - id: combined_danger_strong type: Paragraph properties: content: Critical security vulnerability detected. type: danger strong: true ``` Disabled paragraph text appears faded. This section is currently unavailable. The content will be accessible once the feature has been enabled by your administrator. Disabled bold text still shows the faded style. ```yaml - id: disabled_default type: Paragraph properties: content: Disabled paragraph text appears faded. disabled: true - id: disabled_long type: Paragraph properties: content: This section is currently unavailable. The content will be accessible once the feature has been enabled by your administrator. disabled: true - id: disabled_strong type: Paragraph properties: content: Disabled bold text still shows the faded style. disabled: true strong: true ``` const app = lowdefy.init({ config }) npx lowdefy@latest dev { "type": "TextInput", "properties": { "label": "Name" } } ```yaml - id: code_inline type: Paragraph properties: content: const app = lowdefy.init({ config }) code: true - id: code_command type: Paragraph properties: content: npx lowdefy@latest dev code: true - id: code_json type: Paragraph properties: content: '{ "type": "TextInput", "properties": { "label": "Name" } }' code: true ``` Click the copy icon to copy this text. Copy this paragraph (custom text is copied instead) Hover the copy icon to see custom tooltips Two-state copy icons that change after clicking npm install lowdefy The copy button sits before this text. ```yaml - id: copyable_simple type: Paragraph properties: content: Click the copy icon to copy this text. copyable: true - id: copyable_custom_text type: Paragraph properties: content: Copy this paragraph (custom text is copied instead) copyable: text: This is the custom text that gets copied to clipboard instead of the visible content. - id: copyable_tooltips type: Paragraph properties: content: Hover the copy icon to see custom tooltips copyable: tooltips: - Click to copy text - Text copied! - id: copyable_icons type: Paragraph properties: content: Two-state copy icons that change after clicking copyable: icon: - copy - check - id: copyable_code type: Paragraph properties: content: npm install lowdefy code: true copyable: true - id: copyable_actions_start type: Paragraph properties: content: The copy button sits before this text. copyable: true actions: placement: start ``` Copy this text and a message will appear. API Key shown here (actual key copied) Copy this and state will track it. ```yaml - id: copyable_event type: Paragraph properties: content: Copy this text and a message will appear. copyable: true events: onCopy: - id: copyable_event_msg type: DisplayMessage params: content: Text copied to clipboard! status: success - id: copyable_event_custom type: Paragraph properties: content: API Key shown here (actual key copied) copyable: text: sk-abc123def456 tooltips: - Copy API key - API key copied! events: onCopy: - id: copyable_event_custom_msg type: DisplayMessage params: content: API key copied to clipboard. status: info - id: copyable_event_set_state type: Paragraph properties: content: Copy this and state will track it. copyable: true events: onCopy: - id: copyable_event_set type: SetState params: lastCopied: Copied text tracked via onCopy event. ``` This is a very long paragraph that will be truncated when it exceeds the width of its container. The overflow text is replaced with an ellipsis to indicate there is more content available beyond what is currently visible. This paragraph demonstrates multi-line ellipsis with two rows. It contains enough text to span several lines, and after reaching the specified row limit, the remaining content will be hidden behind an ellipsis. Users can click the expand button to reveal the full text. This is useful for previews of long-form content. Three-line ellipsis with a custom suffix. Lowdefy makes it easy to build internal tools, client portals, and web applications using YAML or JSON configuration files. No need to learn complex frontend frameworks, manage build tooling, or write boilerplate code. Just define your UI in config and Lowdefy takes care of the rest. ```yaml - id: ellipsis_single type: Paragraph properties: content: This is a very long paragraph that will be truncated when it exceeds the width of its container. The overflow text is replaced with an ellipsis to indicate there is more content available beyond what is currently visible. ellipsis: true - id: ellipsis_rows_2 type: Paragraph properties: content: This paragraph demonstrates multi-line ellipsis with two rows. It contains enough text to span several lines, and after reaching the specified row limit, the remaining content will be hidden behind an ellipsis. Users can click the expand button to reveal the full text. This is useful for previews of long-form content. ellipsis: rows: 2 expandable: true - id: ellipsis_rows_3_suffix type: Paragraph properties: content: Three-line ellipsis with a custom suffix. Lowdefy makes it easy to build internal tools, client portals, and web applications using YAML or JSON configuration files. No need to learn complex frontend frameworks, manage build tooling, or write boilerplate code. Just define your UI in config and Lowdefy takes care of the rest. ellipsis: rows: 3 expandable: true suffix: " /Read More" ``` Collapsible ellipsis lets readers expand the full text and fold it back. Lowdefy apps are defined in YAML with blocks, operators, actions and connections, and the framework handles rendering, state and server-side logic. Hover over this single line to read the full text in a tooltip. The rest of the sentence is cut off with an ellipsis when it does not fit. ```yaml - id: ellipsis_collapsible type: Paragraph properties: content: Collapsible ellipsis lets readers expand the full text and fold it back. Lowdefy apps are defined in YAML with blocks, operators, actions and connections, and the framework handles rendering, state and server-side logic. ellipsis: rows: 2 expandable: collapsible symbol: - Read more - Show less - id: ellipsis_tooltip type: Paragraph properties: content: Hover over this single line to read the full text in a tooltip. The rest of the sentence is cut off with an ellipsis when it does not fit. ellipsis: tooltip: true ``` Click expand to trigger an event. Lowdefy makes it easy to build internal tools, client portals, and web applications using YAML or JSON configuration files. No need to learn complex frontend frameworks, manage build tooling, or write boilerplate code. Expanding this paragraph will update state. This is useful for tracking which sections the user has read or interacted with. You can use the ellipsis state to drive conditional logic elsewhere in the app. Expandable paragraph with a suffix and event. Lowdefy apps are defined in YAML with blocks, operators, actions, and connections. The framework handles rendering, state management, and server-side logic so you can focus on building features. ```yaml - id: ellipsis_event type: Paragraph properties: content: Click expand to trigger an event. Lowdefy makes it easy to build internal tools, client portals, and web applications using YAML or JSON configuration files. No need to learn complex frontend frameworks, manage build tooling, or write boilerplate code. ellipsis: rows: 2 expandable: true events: onExpand: - id: ellipsis_event_msg type: DisplayMessage params: content: Content expanded! status: info - id: ellipsis_event_set_state type: Paragraph properties: content: Expanding this paragraph will update state. This is useful for tracking which sections the user has read or interacted with. You can use the ellipsis state to drive conditional logic elsewhere in the app. ellipsis: rows: 2 expandable: true events: onExpand: - id: ellipsis_event_set type: SetState params: sectionExpanded: true - id: ellipsis_event_set_msg type: DisplayMessage params: content: State updated - sectionExpanded is now true. status: success - id: ellipsis_event_suffix type: Paragraph properties: content: Expandable paragraph with a suffix and event. Lowdefy apps are defined in YAML with blocks, operators, actions, and connections. The framework handles rendering, state management, and server-side logic so you can focus on building features. ellipsis: rows: 1 expandable: true suffix: " [...]" events: onExpand: - id: ellipsis_event_suffix_msg type: DisplayMessage params: content: Full text revealed. status: info ``` Paragraph styled with Tailwind classes for background, padding, and border radius. Paragraph with a left border accent using Tailwind utility classes. PARAGRAPH WITH WIDE LETTER SPACING This paragraph has increased line height for better readability. When you have dense text content, increasing the line height makes it easier for users to follow lines of text across the page. A blockquote-style paragraph using inline styles with a left border and padding. ```yaml - id: css_tailwind_bg type: Paragraph class: bg-bg-layout p-3 rounded-lg properties: content: Paragraph styled with Tailwind classes for background, padding, and border radius. - id: css_tailwind_border type: Paragraph class: border-l-4 border-blue-500 pl-3 properties: content: Paragraph with a left border accent using Tailwind utility classes. type: secondary italic: true - id: css_inline_spacing type: Paragraph properties: content: PARAGRAPH WITH WIDE LETTER SPACING style: .element: letterSpacing: 3px textTransform: uppercase fontSize: 12px - id: css_inline_line_height type: Paragraph properties: content: This paragraph has increased line height for better readability. When you have dense text content, increasing the line height makes it easier for users to follow lines of text across the page. style: .element: lineHeight: 2 - id: css_inline_blockquote type: Paragraph properties: content: A blockquote-style paragraph using inline styles with a left border and padding. italic: true style: .element: borderLeft: "3px solid #1677ff" paddingLeft: 12px ``` Custom heavy font weight for strong text (fontWeightStrong 900). Custom teal success color applied via theme tokens. Custom orange warning color via theme override. console.log("custom monospace font") ```yaml - id: theme_bold_weight type: Paragraph properties: content: Custom heavy font weight for strong text (fontWeightStrong 900). strong: true theme: fontWeightStrong: 900 - id: theme_custom_colors type: Paragraph properties: content: Custom teal success color applied via theme tokens. type: success theme: colorSuccess: "#13c2c2" - id: theme_warning_color type: Paragraph properties: content: Custom orange warning color via theme override. type: warning theme: colorWarning: "#fa541c" - id: theme_code_font type: Paragraph properties: content: console.log("custom monospace font") code: true theme: fontFamilyCode: '"JetBrains Mono", "Fira Code", monospace' ``` Lowdefy is a config-driven web framework that lets you build internal tools, client portals, and web apps using YAML. No frontend expertise required. Over 50 pre-built blocks are available out of the box, including form inputs, charts, tables, and layout components. Install the CLI to get started. npx lowdefy@latest dev Note: Lowdefy requires Node.js 24 or later. Check your version with node /version. ```yaml - id: article_card type: Card properties: title: Getting Started with Lowdefy size: small blocks: - id: article_intro type: Paragraph properties: content: Lowdefy is a config-driven web framework that lets you build internal tools, client portals, and web apps using YAML. No frontend expertise required. - id: article_highlight type: Paragraph properties: content: Over 50 pre-built blocks are available out of the box, including form inputs, charts, tables, and layout components. mark: true strong: true - id: article_code_label type: Paragraph properties: content: Install the CLI to get started. type: secondary - id: article_code_snippet type: Paragraph properties: content: npx lowdefy@latest dev code: true copyable: true events: onCopy: - id: article_copy_msg type: DisplayMessage params: content: Command copied! Paste it into your terminal. status: success - id: article_note type: Paragraph properties: content: "Note: Lowdefy requires Node.js 24 or later. Check your version with node /version." type: warning italic: true ``` All services are operational. Last checked 2 minutes ago. API response times are elevated. The team is investigating the root cause. Database cluster is unreachable. Automatic failover has been triggered. For real-time updates, subscribe to the status page or contact support. Incident reports are published within 30 minutes of detection. Historical uptime data is available in the admin dashboard. Maintenance window scheduled for Sunday 02:00-04:00 UTC. ```yaml - id: status_card type: Card properties: title: System Status size: small blocks: - id: status_ok type: Paragraph properties: content: All services are operational. Last checked 2 minutes ago. type: success strong: true - id: status_degraded type: Paragraph properties: content: API response times are elevated. The team is investigating the root cause. type: warning - id: status_outage type: Paragraph properties: content: Database cluster is unreachable. Automatic failover has been triggered. type: danger strong: true - id: status_detail type: Paragraph properties: content: For real-time updates, subscribe to the status page or contact support. Incident reports are published within 30 minutes of detection. Historical uptime data is available in the admin dashboard. ellipsis: rows: 2 expandable: true events: onExpand: - id: status_expand_msg type: DisplayMessage params: content: Showing full status details. status: info - id: status_disabled type: Paragraph properties: content: Maintenance window scheduled for Sunday 02:00-04:00 UTC. disabled: true ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `actions` | object | - | Configure the bar of action buttons, such as copy and expand. | | `actions.placement` | string | `"end"` | Place the action buttons before or after the text. Enum: `start`, `end`. | | `code` | boolean | `false` | Apply code style. | | `content` | string | - | Paragraph text content - supports html. | | `copyable` | boolean \| object | `false` | Provide copy text button. | | `copyable.text` | string | - | Paragraph text to copy when clicked. | | `copyable.icon` | string \| object \| array | - | Copy icon, can be an array or two icons for before and after clicked. | | `copyable.tooltips` | string \| array | - | Tooltip text, can be an array or two strings for before and after clicked. | | `delete` | boolean | `false` | Apply deleted (strikethrough) style. | | `disabled` | boolean | `false` | Apply disabled style. | | `ellipsis` | boolean \| object | `false` | Display ellipsis when text overflows a single line. | | `ellipsis.rows` | number | - | Max rows of content. | | `ellipsis.expandable` | boolean \| string | - | Show a button that expands the hidden content. Set to 'collapsible' to also allow collapsing it again. Enum: `true`, `false`, `collapsible`. | | `ellipsis.defaultExpanded` | boolean | `false` | Start with the content expanded. | | `ellipsis.suffix` | string | - | Suffix of ellipses content. | | `ellipsis.symbol` | string \| array | - | Text of the expand button, or an array of two strings for the expand and collapse buttons. | | `ellipsis.tooltip` | boolean \| string | - | Show a tooltip when the text is truncated. true shows the full text, a string shows that text. | | `italic` | boolean | `false` | Apply italic style. | | `keyboard` | boolean | `false` | Apply keyboard key style. | | `mark` | boolean | `false` | Apply marked (highlighted) style. | | `strong` | boolean | `false` | Apply strong (bold) style. | | `type` | string | `"default"` | Additional types. Don't specify for default. Enum: `success`, `default`, `secondary`, `warning`, `danger`. | | `underline` | boolean | `false` | Apply underline style. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design typography tokens](https://ant.design/components/typography#design-token). | | `theme.titleMarginBottom` | string \| number | `"0.5em"` | Margin bottom of title, as a CSS length or a number of pixels. | | `theme.titleMarginTop` | string \| number | `"1.2em"` | Margin top of title, as a CSS length or a number of pixels. | | `theme.fontWeightStrong` | number | `600` | Font weight for strong text. | | `theme.fontFamilyCode` | string | - | Font family for code style text. | | `theme.colorText` | string | - | Default text color. | | `theme.colorTextSecondary` | string | - | Secondary text color. | | `theme.colorSuccess` | string | - | Success text color. | | `theme.colorWarning` | string | - | Warning text color. | | `theme.colorError` | string | - | Danger/error text color. | | `theme.colorTextDisabled` | string | - | Disabled text color. | | `theme.colorLink` | string | - | Link color. | | Event | Event Data | Description | | --- | --- | --- | | `onExpand` | `{ expanded }` | Trigger action when the ellipsis expand or collapse button is clicked. | | `onCopy` | \- | Trigger action when copy text is clicked. | | `onTextSelection` | \- | Trigger action when text is selected and pass selected text to the event object. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Paragraph element. | | `/copyableIcon` | The copyable icon in the Paragraph. | | `/actions` | The bar of action buttons, such as copy and expand, next to the text. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/pollingtimer.md --- # PollingTimer A headless timer that renders nothing and triggers its `onTick` event every `interval` milliseconds while it is running. Use it to poll a request until a background job completes, or to refresh a page on a schedule. Start and stop it with the `start`, `stop` and `toggle` methods, or set `autoStart` to start it on mount. The timer always stops when the block unmounts. ## Poll a request until a job is done Start the timer when a background job is kicked off, fetch the job status on every tick, and stop the timer with the `stop` method once the job reports it is done: ```yaml requests: - id: create_job type: MongoDBInsertOne connectionId: jobs properties: doc: status: queued - id: get_job type: MongoDBFindOne connectionId: jobs payload: job_id: _state: job_id properties: query: _id: _payload: job_id blocks: - id: job_poller type: PollingTimer properties: interval: 3000 events: onTick: - id: fetch_job type: Request params: get_job - id: stop_when_done type: CallMethod skip: _ne: - _request: get_job.status - done params: blockId: job_poller method: stop - id: start_job type: Button properties: title: Start job events: onClick: - id: create_job type: Request params: create_job - id: set_job_id type: SetState params: job_id: _request: create_job.insertedId - id: start_polling type: CallMethod params: blockId: job_poller method: start ``` ## Timing The next tick is scheduled `interval` ms after the `onTick` actions finish, so a slow request never causes ticks to overlap or pile up. Stopping the timer while `onTick` actions are running lets them finish, but no further tick is scheduled. `pauseWhenHidden` pauses the timer while `document.visibilityState` is `hidden`, so a visible browser window that has lost focus keeps ticking. The page `onVisible` and `onHidden` events also fire when the window gains or loses focus, so use them instead when you want to react to focus changes, for example to refresh data once when the user returns. The engine keeps a history of every event it runs, so each tick adds an `onTick` entry. For a timer that runs for a long time, prefer a longer `interval` or a `maxTicks` limit. ```yaml - id: job_timer type: PollingTimer properties: interval: 1000 events: onTick: - id: job_timer_set_status type: SetState params: job_status: _if: test: _gte: - _event: tick - 5 then: done else: running - id: job_timer_stop_when_done type: CallMethod skip: _ne: - _state: job_status - done params: blockId: job_timer method: stop - id: job_timer_controls type: Box layout: gap: 8 blocks: - id: job_timer_start type: Button properties: title: Start Job icon: CirclePlay events: onClick: - id: job_timer_set_running type: SetState params: job_status: running - id: job_timer_call_start type: CallMethod params: blockId: job_timer method: start - id: job_timer_status type: Span properties: content: _nunjucks: template: "Job status: {{ status }}" on: status: _if_none: - _state: job_status - not started ``` ```yaml - id: manual_timer type: PollingTimer properties: interval: 1000 events: onTick: - id: manual_timer_set_tick type: SetState params: manual_ticks: _event: tick - id: manual_timer_controls type: Box layout: gap: 8 blocks: - id: manual_timer_start type: Button properties: title: Start icon: CirclePlay events: onClick: - id: manual_timer_call_start type: CallMethod params: blockId: manual_timer method: start - id: manual_timer_stop type: Button properties: title: Stop icon: CirclePause events: onClick: - id: manual_timer_call_stop type: CallMethod params: blockId: manual_timer method: stop - id: manual_timer_toggle type: Button properties: title: Toggle icon: ArrowRightLeft events: onClick: - id: manual_timer_call_toggle type: CallMethod params: blockId: manual_timer method: toggle - id: manual_timer_ticks type: Span properties: content: _nunjucks: template: "Ticks: {{ ticks }}" on: ticks: _state: manual_ticks ``` ```yaml - id: limited_timer type: PollingTimer properties: interval: 1000 autoStart: true maxTicks: 5 events: onTick: - id: limited_timer_set_tick type: SetState params: limited_ticks: _event: tick - id: limited_timer_ticks type: Span properties: content: _nunjucks: template: "Ticks: {{ ticks }} of 5" on: ticks: _state: limited_ticks ``` ```yaml - id: background_timer type: PollingTimer properties: interval: 2000 autoStart: true pauseWhenHidden: false events: onTick: - id: background_timer_set_tick type: SetState params: background_ticks: _event: tick - id: background_timer_ticks type: Span properties: content: _nunjucks: template: "Ticks while this tab was open or hidden: {{ ticks }}" on: ticks: _state: background_ticks ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `interval` | integer | - | Milliseconds to wait after the `onTick` actions of one tick finish before the next tick fires. Changing it while the timer is waiting restarts the wait. | | `autoStart` | boolean | `false` | Start ticking when the block mounts. Off by default, so the timer is started with the `start` method. | | `maxTicks` | integer | - | Stop the timer after this many ticks. The `onTick` event of the last tick is still triggered. Unlimited by default. | | `pauseWhenHidden` | boolean | `true` | Pause ticking while the browser tab is hidden (`document.visibilityState`), and resume when it becomes visible again. Ticks missed while hidden are not fired on resume; the next tick fires `interval` ms after the tab is visible. | | Event | Event Data | Description | | --- | --- | --- | | `onTick` | `{ tick }` | Trigger actions on every tick while the timer is running. The next tick is scheduled `interval` ms after the `onTick` actions finish, so ticks never overlap. | No CSS keys defined. No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/progress.md --- # Progress Progress indicator in line, circle, and dashboard styles. ```yaml - id: type_line type: Progress properties: type: line percent: 60 - id: type_circle type: Progress layout: flex: 0 0 auto properties: type: circle percent: 75 - id: type_dashboard type: Progress layout: flex: 0 0 auto properties: type: dashboard percent: 80 ``` ```yaml - id: percent_0 type: Progress properties: percent: 0 - id: percent_30 type: Progress properties: percent: 30 - id: percent_75 type: Progress properties: percent: 75 - id: percent_100 type: Progress properties: percent: 100 ``` ```yaml - id: status_normal type: Progress properties: percent: 50 status: normal - id: status_active type: Progress properties: percent: 50 status: active - id: status_success type: Progress properties: percent: 100 status: success - id: status_exception type: Progress properties: percent: 70 status: exception ``` ```yaml - id: showinfo_true type: Progress properties: percent: 65 showInfo: true - id: showinfo_false type: Progress properties: percent: 65 showInfo: false - id: showinfo_circle_true type: Progress layout: flex: 0 0 auto properties: type: circle percent: 65 showInfo: true - id: showinfo_circle_false type: Progress layout: flex: 0 0 auto properties: type: circle percent: 65 showInfo: false ``` ```yaml - id: stroke_solid_color type: Progress properties: percent: 60 strokeColor: "#722ed1" - id: stroke_gradient type: Progress properties: percent: 80 strokeColor: 0%: "#108ee9" 100%: "#87d068" - id: stroke_warm_gradient type: Progress properties: percent: 65 strokeColor: 0%: "#ff4d4f" 100%: "#faad14" - id: stroke_circle_color type: Progress layout: flex: 0 0 auto properties: type: circle percent: 75 strokeColor: "#eb2f96" ``` ```yaml - id: trail_light_blue type: Progress properties: percent: 60 strokeColor: "#1677ff" trailColor: "#e6f4ff" - id: trail_light_green type: Progress properties: percent: 75 strokeColor: "#52c41a" trailColor: "#f6ffed" - id: trail_circle type: Progress layout: flex: 0 0 auto properties: type: circle percent: 70 strokeColor: "#722ed1" trailColor: "#f9f0ff" ``` ```yaml - id: strokewidth_thin type: Progress properties: percent: 60 strokeWidth: 4 - id: strokewidth_default type: Progress properties: percent: 60 - id: strokewidth_thick type: Progress properties: percent: 60 strokeWidth: 20 - id: strokewidth_circle type: Progress layout: flex: 0 0 auto properties: type: circle percent: 75 strokeWidth: 12 ``` ```yaml - id: linecap_round type: Progress properties: percent: 50 strokeLinecap: round strokeWidth: 12 - id: linecap_square type: Progress properties: percent: 50 strokeLinecap: square strokeWidth: 12 - id: linecap_circle_round type: Progress layout: flex: 0 0 auto properties: type: circle percent: 50 strokeLinecap: round - id: linecap_circle_square type: Progress layout: flex: 0 0 auto properties: type: circle percent: 50 strokeLinecap: square ``` ```yaml - id: success_line type: Progress properties: percent: 80 success: percent: 30 - id: success_line_high type: Progress properties: percent: 100 success: percent: 60 - id: success_circle type: Progress layout: flex: 0 0 auto properties: type: circle percent: 90 success: percent: 50 ``` ```yaml - id: steps_3 type: Progress properties: percent: 66 steps: 3 - id: steps_5 type: Progress properties: percent: 60 steps: 5 - id: steps_8 type: Progress properties: percent: 75 steps: 8 - id: steps_colored type: Progress properties: percent: 80 steps: 5 strokeColor: "#52c41a" ``` ```yaml - id: gradient_line type: Progress properties: percent: 80 strokeColor: from: "#108ee9" to: "#87d068" - id: gradient_circle type: Progress properties: type: circle percent: 90 strokeColor: 0%: "#108ee9" 100%: "#87d068" ``` ```yaml - id: percent_inner_start type: Progress properties: percent: 45 size: - -1 - 20 percentPosition: align: start type: inner - id: percent_inner_center type: Progress properties: percent: 65 size: - -1 - 20 percentPosition: align: center type: inner - id: percent_outer_center type: Progress properties: percent: 30 percentPosition: align: center type: outer ``` ```yaml - id: size_small_line type: Progress properties: percent: 50 size: small - id: size_circle_small type: Progress properties: type: circle percent: 50 size: small - id: circle_steps type: Progress properties: type: dashboard percent: 60 steps: count: 8 gap: 4 ``` ```yaml - id: width_small type: Progress layout: flex: 0 0 auto properties: type: circle percent: 70 width: 80 - id: width_default type: Progress layout: flex: 0 0 auto properties: type: circle percent: 70 width: 132 - id: width_large type: Progress layout: flex: 0 0 auto properties: type: circle percent: 70 width: 200 ``` ```yaml - id: gap_small type: Progress layout: flex: 0 0 auto properties: type: dashboard percent: 60 gapDegree: 30 - id: gap_default type: Progress layout: flex: 0 0 auto properties: type: dashboard percent: 60 gapDegree: 75 - id: gap_large type: Progress layout: flex: 0 0 auto properties: type: dashboard percent: 60 gapDegree: 150 ``` ```yaml - id: gappos_top type: Progress layout: flex: 0 0 auto properties: type: dashboard percent: 65 gapPosition: top - id: gappos_bottom type: Progress layout: flex: 0 0 auto properties: type: dashboard percent: 65 gapPosition: bottom - id: gappos_left type: Progress layout: flex: 0 0 auto properties: type: dashboard percent: 65 gapPosition: left - id: gappos_right type: Progress layout: flex: 0 0 auto properties: type: dashboard percent: 65 gapPosition: right ``` ```yaml - id: css_tailwind_margin type: Progress class: my-4 mx-8 properties: percent: 70 - id: css_tailwind_dark_bg type: Box class: bg-bg-layout p-4 rounded-lg blocks: - id: css_on_dark type: Progress properties: percent: 65 strokeColor: "#69b1ff" trailColor: rgba(255,255,255,0.1) - id: css_inline_max_width type: Progress style: .element: maxWidth: 300 properties: percent: 55 - id: css_inline_circle_shadow type: Progress layout: flex: 0 0 auto style: .element: filter: drop-shadow(0 4px 6px rgba(0,0,0,0.15)) properties: type: circle percent: 85 strokeColor: "#52c41a" ``` ```yaml - id: theme_default_color type: Progress properties: percent: 70 theme: defaultColor: "#722ed1" - id: theme_remaining_color type: Progress properties: percent: 55 theme: defaultColor: "#722ed1" remainingColor: "#f9f0ff" - id: theme_circle_text type: Progress layout: flex: 0 0 auto properties: type: circle percent: 88 theme: circleTextColor: "#1677ff" circleTextFontSize: 1.5em - id: theme_line_border_radius type: Progress properties: percent: 70 strokeWidth: 16 theme: lineBorderRadius: 0 ``` **report-q4.pdf** - 2.4 MB **data-export.csv** - 8.1 MB **image-corrupted.png** - Upload failed ```yaml - id: upload_card type: Card properties: title: Upload Documents size: small blocks: - id: upload_file_1_row type: Box layout: gap: 8 align: center blocks: - id: upload_file_1_icon type: Icon layout: flex: 0 0 auto properties: name: file size: 20 color: "#1677ff" - id: upload_file_1_info type: Box layout: flex: 1 1 auto blocks: - id: upload_file_1_name type: Markdown properties: content: "**report-q4.pdf** - 2.4 MB" - id: upload_file_1_progress type: Progress properties: percent: 100 status: success showInfo: true - id: upload_file_2_row type: Box layout: gap: 8 align: center blocks: - id: upload_file_2_icon type: Icon layout: flex: 0 0 auto properties: name: file size: 20 color: "#1677ff" - id: upload_file_2_info type: Box layout: flex: 1 1 auto blocks: - id: upload_file_2_name type: Markdown properties: content: "**data-export.csv** - 8.1 MB" - id: upload_file_2_progress type: Progress properties: percent: 62 status: active strokeColor: 0%: "#108ee9" 100%: "#87d068" - id: upload_file_3_row type: Box layout: gap: 8 align: center blocks: - id: upload_file_3_icon type: Icon layout: flex: 0 0 auto properties: name: file size: 20 color: "#ff4d4f" - id: upload_file_3_info type: Box layout: flex: 1 1 auto blocks: - id: upload_file_3_name type: Markdown properties: content: "**image-corrupted.png** - Upload failed" - id: upload_file_3_progress type: Progress properties: percent: 35 status: exception - id: upload_actions type: Box layout: gap: 8 justify: flex-end blocks: - id: upload_retry_btn type: Button layout: flex: 0 0 auto properties: title: Retry Failed color: danger variant: outlined icon: refresh size: small events: onClick: - id: retry_msg type: DisplayMessage params: content: Retrying failed upload... status: info - id: upload_more_btn type: Button layout: flex: 0 0 auto properties: title: Upload More color: primary variant: solid icon: upload size: small events: onClick: - id: upload_msg type: DisplayMessage params: content: File picker opened status: info ``` **3 of 5 steps complete** Complete the remaining steps to finish setting up your account. ~~Create account~~ ~~Verify email~~ ~~Add profile photo~~ **Connect workspace** Invite team members ```yaml - id: onboarding_card type: Card properties: title: Account Setup size: small blocks: - id: onboarding_overall type: Box layout: gap: 8 align: center justify: center blocks: - id: onboarding_circle type: Progress layout: flex: 0 0 auto properties: type: circle percent: 60 width: 100 strokeColor: "#1677ff" - id: onboarding_desc type: Markdown layout: flex: 1 1 auto properties: content: | **3 of 5 steps complete** Complete the remaining steps to finish setting up your account. - id: onboarding_divider type: Divider - id: onboarding_step_1 type: Box layout: gap: 8 align: center blocks: - id: onboarding_step_1_check type: Icon layout: flex: 0 0 auto properties: name: check-circle size: 18 color: "#52c41a" - id: onboarding_step_1_label type: Markdown layout: flex: 1 1 auto properties: content: ~~Create account~~ - id: onboarding_step_1_bar type: Progress layout: flex: 0 0 200px properties: percent: 100 status: success showInfo: false - id: onboarding_step_2 type: Box layout: gap: 8 align: center blocks: - id: onboarding_step_2_check type: Icon layout: flex: 0 0 auto properties: name: check-circle size: 18 color: "#52c41a" - id: onboarding_step_2_label type: Markdown layout: flex: 1 1 auto properties: content: ~~Verify email~~ - id: onboarding_step_2_bar type: Progress layout: flex: 0 0 200px properties: percent: 100 status: success showInfo: false - id: onboarding_step_3 type: Box layout: gap: 8 align: center blocks: - id: onboarding_step_3_check type: Icon layout: flex: 0 0 auto properties: name: check-circle size: 18 color: "#52c41a" - id: onboarding_step_3_label type: Markdown layout: flex: 1 1 auto properties: content: ~~Add profile photo~~ - id: onboarding_step_3_bar type: Progress layout: flex: 0 0 200px properties: percent: 100 status: success showInfo: false - id: onboarding_step_4 type: Box layout: gap: 8 align: center blocks: - id: onboarding_step_4_icon type: Icon layout: flex: 0 0 auto properties: name: clock size: 18 color: "#1677ff" - id: onboarding_step_4_label type: Markdown layout: flex: 1 1 auto properties: content: "**Connect workspace**" - id: onboarding_step_4_bar type: Progress layout: flex: 0 0 200px properties: percent: 40 status: active showInfo: false - id: onboarding_step_5 type: Box layout: gap: 8 align: center blocks: - id: onboarding_step_5_icon type: Icon layout: flex: 0 0 auto properties: name: clock size: 18 color: "#d9d9d9" - id: onboarding_step_5_label type: Markdown layout: flex: 1 1 auto properties: content: Invite team members - id: onboarding_step_5_bar type: Progress layout: flex: 0 0 200px properties: percent: 0 showInfo: false trailColor: "#f0f0f0" - id: onboarding_actions type: Box layout: gap: 8 justify: flex-end blocks: - id: onboarding_skip_btn type: Button layout: flex: 0 0 auto properties: title: Skip for Now color: default variant: text events: onClick: - id: skip_msg type: DisplayMessage params: content: You can complete setup later from your profile. status: info - id: onboarding_continue_btn type: Button layout: flex: 0 0 auto properties: title: Continue Setup color: primary variant: solid icon: arrow-right events: onClick: - id: continue_msg type: DisplayMessage params: content: Connecting workspace... status: loading ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `type` | string | `"line"` | Set type of progress display. Enum: `line`, `circle`, `dashboard`. | | `showInfo` | boolean | `true` | Whether to display the progress value and the status icon. | | `percent` | number | `0` | Set the completion percentage. | | `status` | string | `"normal"` | Set the status of the Progress. Enum: `success`, `exception`, `normal`, `active`. | | `strokeLinecap` | string | `"round"` | Set the style of the progress linecap. Enum: `round`, `butt`, `square`. | | `strokeColor` | string \| array \| object | - | Color of progress bar. An object like `{ from: "#108ee9", to: "#87d068" }` or `{ "0%": "#108ee9", "100%": "#87d068" }` renders a gradient, and an array colors each step when steps is set. | | `success` | object | - | Segmented success percent configuration. | | `success.percent` | number | `0` | Segmented success percent. | | `success.strokeColor` | string | - | Color of the success segment. | | `trailColor` | string | - | Color of unfilled part. | | `strokeWidth` | number | - | Thickness of the progress bar. In pixels for line progress, and as a percentage of the canvas width for circle and dashboard progress. | | `width` | number | `120` | Set the canvas width of the circular progress. Same as a number size. | | `size` | number \| string \| array \| object | - | Size of the progress. 'small' or 'medium', a number (the circle diameter, or both the width and the height of a line or of each step), or `[width, height]` or `{ width, height }` for line progress. | | `percentPosition` | object | - | Position of the progress value on line progress. | | `percentPosition.align` | string | `"end"` | Horizontal position of the value. Enum: `start`, `center`, `end`. | | `percentPosition.type` | string | `"outer"` | Show the value inside or outside the bar. Enum: `inner`, `outer`. | | `gapDegree` | number | `75` | The gap degree of half circle. | | `gapPosition` | string | `"bottom"` | The gap position of circle and dashboard progress. Enum: `top`, `bottom`, `left`, `right`. | | `steps` | number \| object | - | Number of steps for a segmented progress bar. For circle and dashboard progress, an object `{ count, gap }` also sets the gap between steps. | | `steps.count` | number | - | Number of steps. | | `steps.gap` | number | `2` | Gap between steps of circle and dashboard progress. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design progress tokens](https://ant.design/components/progress#design-token). | | `theme.defaultColor` | string | `"#1677ff"` | Default color of the progress bar. | | `theme.remainingColor` | string | `"rgba(0,0,0,0.06)"` | Color of the unfilled portion of the progress bar. | | `theme.circleTextColor` | string | `"rgba(0,0,0,0.88)"` | Text color inside the circular progress. | | `theme.circleTextFontSize` | string | `"1em"` | Font size of text inside the circular progress. | | `theme.circleIconFontSize` | string | `"1.167em"` | Font size of icons inside the circular progress. | | `theme.lineBorderRadius` | number | `100` | Border radius of the linear progress bar. | | `theme.colorSuccess` | string | - | Color used for success status. | | `theme.colorError` | string | - | Color used for exception status. | No events defined. | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Progress element. | | `/rail` | The unfilled part of the progress bar. | | `/track` | The filled part of the progress bar. | | `/indicator` | The progress value text or status icon. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/qrcode.md --- # QRCode QR code generator with customizable size, color, and error level. ```yaml - id: qr_basic_url type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com - id: qr_basic_text type: QRCode layout: flex: 0 0 auto properties: value: Hello, World! - id: qr_basic_long_url type: QRCode layout: flex: 0 0 auto properties: value: https://docs.lowdefy.com/tutorial-start ``` ```yaml - id: qr_size_small type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com size: 80 - id: qr_size_default type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com size: 160 - id: qr_size_large type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com size: 240 ``` ```yaml - id: qr_color_default type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com - id: qr_color_blue type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com color: "#1677ff" - id: qr_color_green type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com color: "#52c41a" - id: qr_color_purple type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com color: "#722ed1" ``` ```yaml - id: qr_bg_light_blue type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com color: "#1677ff" bgColor: "#e6f4ff" - id: qr_bg_light_green type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com color: "#135200" bgColor: "#d9f7be" - id: qr_bg_dark_navy type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com color: "#ffffff" bgColor: "#001529" - id: qr_bg_transparent type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com bgColor: transparent ``` ```yaml - id: qr_ec_low type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com errorLevel: L - id: qr_ec_medium type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com errorLevel: M - id: qr_ec_quartile type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com errorLevel: Q - id: qr_ec_high type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com errorLevel: H ``` ```yaml - id: qr_icon_small type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com size: 200 errorLevel: H icon: https://lowdefy.com/favicon-32x32.png iconSize: 24 - id: qr_icon_default type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com size: 200 errorLevel: H icon: https://lowdefy.com/favicon-32x32.png iconSize: 40 - id: qr_icon_large type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com size: 200 errorLevel: H icon: https://lowdefy.com/favicon-32x32.png iconSize: 48 - id: qr_icon_wide type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com size: 200 errorLevel: H icon: https://lowdefy.com/favicon-32x32.png iconSize: width: 48 height: 32 ``` ```yaml - id: qr_bordered_true type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com bordered: true - id: qr_bordered_false type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com bordered: false - id: qr_bordered_colored type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com bordered: true color: "#1677ff" bgColor: "#e6f4ff" ``` ```yaml - id: qr_status_active type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com status: active - id: qr_status_expired type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com status: expired - id: qr_status_loading type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com status: loading - id: qr_status_scanned type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com status: scanned ``` ```yaml - id: qr_type_canvas type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com type: canvas - id: qr_type_svg type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com type: svg ``` ```yaml - id: qr_margin_0 type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com marginSize: 0 bordered: true - id: qr_margin_2 type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com marginSize: 2 bordered: true - id: qr_margin_6 type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com marginSize: 6 bordered: true ``` ```yaml - id: qr_version_1 type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com minVersion: 1 - id: qr_version_5 type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com minVersion: 5 - id: qr_version_10 type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com minVersion: 10 ``` ```yaml - id: qr_css_shadow type: QRCode layout: flex: 0 0 auto class: shadow-lg properties: value: https://lowdefy.com - id: qr_css_rounded type: QRCode layout: flex: 0 0 auto class: shadow-xl rounded-2xl overflow-hidden properties: value: https://lowdefy.com color: "#1677ff" bgColor: "#f0f5ff" - id: qr_css_inline_border type: QRCode layout: flex: 0 0 auto style: .element: borderColor: "#1677ff" borderWidth: 2 properties: value: https://lowdefy.com bordered: true - id: qr_css_inline_shadow type: QRCode layout: flex: 0 0 auto style: .element: boxShadow: 0 4px 12px rgba(0, 0, 0, 0.15) borderRadius: 16 properties: value: https://lowdefy.com ``` ```yaml - id: qr_theme_radius type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com theme: borderRadiusLG: 24 - id: qr_theme_container type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com theme: colorSplit: "#1677ff" - id: qr_theme_padding type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com theme: padding: 24 borderRadiusLG: 16 - id: qr_theme_cover type: QRCode layout: flex: 0 0 auto properties: value: https://lowdefy.com status: expired theme: QRCodeCoverBackgroundColor: rgba(0, 0, 0, 0.8) QRCodeTextColor: "#ffffff" ``` Present this QR code at the entrance for check-in. ```yaml - id: ticket_card type: Card layout: flex: 0 0 380px properties: title: Tech Conference 2026 size: small blocks: - id: ticket_details type: Descriptions properties: size: small column: 1 items: - label: Date value: March 28, 2026 - label: Venue value: Convention Center, Hall B - label: Ticket value: "General Admission #4821" - id: ticket_qr_row type: Box layout: align: center gap: 8 blocks: - id: ticket_qr type: QRCode layout: flex: 0 0 auto properties: value: TICKET:CONF2026:GA:4821:VALID size: 180 errorLevel: H bordered: false - id: ticket_hint type: Paragraph properties: content: Present this QR code at the entrance for check-in. type: secondary ``` ```yaml - id: wifi_card type: Card layout: flex: 0 0 340px properties: title: Guest Wi-Fi size: small blocks: - id: wifi_qr_row type: Box layout: align: center gap: 8 blocks: - id: wifi_qr type: QRCode layout: flex: 0 0 auto properties: value: WIFI:T:WPA;S:OfficeGuest;P:welcome2026;; size: 200 color: "#1677ff" bgColor: "#e6f4ff" errorLevel: Q bordered: false - id: wifi_details type: Descriptions properties: size: small column: 1 items: - label: Network value: OfficeGuest - label: Password value: welcome2026 - id: wifi_actions type: Box layout: gap: 8 justify: flex-end blocks: - id: wifi_refresh_btn type: Button layout: flex: 0 0 auto properties: title: Generate New Password color: primary variant: outlined icon: refresh events: onClick: - id: wifi_refresh_msg type: DisplayMessage params: content: New Wi-Fi password generated. status: success ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `value` | string | - | Scanned text. | | `size` | integer | `160` | QRCode size in pixels. | | `color` | string | `"#000000"` | QRCode color. | | `bgColor` | string | `"transparent"` | QRCode background color. | | `errorLevel` | string | `"M"` | Error correction level. Enum: `L`, `M`, `Q`, `H`. | | `icon` | string | - | Icon URL in the center of the QR code. | | `iconSize` | integer \| object | `40` | Icon size in pixels, or `{ width, height }` for an icon that is not square. | | `iconSize.width` | integer | - | Icon width in pixels. | | `iconSize.height` | integer | - | Icon height in pixels. | | `marginSize` | number | `0` | Margin size of the QR code in modules. | | `minVersion` | integer | `1` | Minimum QR code version (1-40). Higher versions support more data. | | `type` | string | `"canvas"` | Render type. Enum: `canvas`, `svg`. | | `bordered` | boolean | `true` | Whether has border style. | | `status` | string | `"active"` | QRCode status. Enum: `active`, `expired`, `loading`, `scanned`. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design qr-code tokens](https://ant.design/components/qr-code#design-token). | | `theme.QRCodeTextColor` | string | - | Text color displayed on the QR code overlay. | | `theme.QRCodeCoverBackgroundColor` | string | - | Background color of the cover overlay shown when expired, loading, or scanned. | | `theme.borderRadiusLG` | number | `8` | Border radius of the QR code container. | | `theme.colorText` | string | - | Text color. | | `theme.colorBgContainer` | string | - | Background color of the QR code container. | | `theme.colorSplit` | string | - | Border color when bordered is true. | | `theme.lineWidth` | number | `1` | Border width. | | `theme.padding` | number | `12` | Padding inside the QR code container. | | Event | Event Data | Description | | --- | --- | --- | | `onRefresh` | \- | Trigger action when expired QR code refresh button is clicked. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The QRCode element. | | `/cover` | The overlay shown over the QR code when status is expired, loading or scanned. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/s3download.md --- # S3Download __Deprecated__ — use the provider-neutral Download block with `downloadPolicyRequestId` instead. S3Download remains as an alias so existing apps keep working. Download files from AWS S3. Requires an AwsS3Bucket connection and an AwsS3PresignedGetObject request. The s3GetPolicyRequestId property must reference the request id. If files were uploaded with S3Upload, its fileList value can be mapped directly to this block. > The S3Download block requires an AwsS3Bucket connection and an AwsS3PresignedGetObject request. The examples below show configuration structure but will not download files without a configured connection. ```yaml - id: basic_download type: S3Download properties: s3GetPolicyRequestId: download_file fileList: - key: report.pdf type: application/pdf name: report.pdf - key: photo.jpg type: image/jpeg name: photo.jpg ``` ```yaml - id: single_file type: S3Download properties: s3GetPolicyRequestId: download_file fileList: - key: invoice_2026_001.pdf type: application/pdf name: invoice_2026_001.pdf ``` ```yaml - id: multi_type type: S3Download properties: s3GetPolicyRequestId: download_file fileList: - key: documents/contract.pdf type: application/pdf name: contract.pdf - key: images/logo.png type: image/png name: logo.png - key: data/export.csv type: text/csv name: export.csv - key: archives/backup.zip type: application/zip name: backup.zip ``` ```yaml - id: styled_download type: S3Download style: .element: background: "#f6ffed" border: 1px solid borderRadius: 8 padding: 12 properties: s3GetPolicyRequestId: download_file fileList: - key: report.pdf type: application/pdf name: Quarterly Report.pdf - key: summary.pdf type: application/pdf name: Executive Summary.pdf ``` ```yaml - id: themed_download type: S3Download properties: s3GetPolicyRequestId: download_file theme: actionsColor: "#f5222d" controlItemBgHover: "#f9f0ff" colorIcon: "#f5222d" fontSize: 18 fileList: - key: contracts/nda.pdf type: application/pdf name: Non-Disclosure Agreement.pdf - key: contracts/sla.pdf type: application/pdf name: Service Level Agreement.pdf - key: contracts/msa.pdf type: application/pdf name: Master Services Agreement.pdf ``` Full page config with connection, request, and S3Download block: ```yaml # lowdefy.yaml connections: - id: project_files type: AwsS3Bucket properties: accessKeyId: _secret: FILES_S3_ACCESS_KEY_ID secretAccessKey: _secret: FILES_S3_SECRET_ACCESS_KEY region: us-east-1 bucket: _secret: FILES_S3_BUCKET # page config id: document_portal type: PageSiderMenu blocks: - id: documents_card type: Card properties: title: Project Documents blocks: - id: download_files type: S3Download properties: s3GetPolicyRequestId: download_file fileList: - key: projects/alpha/proposal.pdf type: application/pdf name: Project Alpha - Proposal.pdf - key: projects/alpha/budget.xlsx type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet name: Project Alpha - Budget.xlsx requests: - id: download_file type: AwsS3PresignedGetObject connectionId: project_files payload: key: _event: file.key type: _event: file.type properties: key: _payload: key responseContentType: _payload: type ``` Upload and download files on the same page by mapping the S3Upload fileList to S3Download: ```yaml # lowdefy.yaml connections: - id: attachments type: AwsS3Bucket properties: accessKeyId: _secret: FILES_S3_ACCESS_KEY_ID secretAccessKey: _secret: FILES_S3_SECRET_ACCESS_KEY region: us-east-1 bucket: _secret: FILES_S3_BUCKET write: true # page config id: file_management type: PageSiderMenu blocks: - id: upload_card type: Card properties: title: Upload Files blocks: - id: upload_files type: S3Upload properties: s3PutPolicyRequestId: upload_policy requests: - id: upload_policy type: AwsS3PresignedPutObject connectionId: attachments payload: filename: _event: filename properties: key: _payload: filename - id: download_card type: Card properties: title: Download Uploaded Files blocks: - id: download_files type: S3Download properties: s3GetPolicyRequestId: download_policy # Map fileList directly from S3Upload state fileList: _state: upload_files requests: - id: download_policy type: AwsS3PresignedGetObject connectionId: attachments payload: key: _event: file.key type: _event: file.type properties: key: _payload: key responseContentType: _payload: type ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `fileList` | array | - | List of files to be downloaded. If files were uploaded using an S3Upload block, the fileList value can just be mapped to this field. | | `fileList.$.key` | string | - | S3 file key. | | `fileList.$.lastModified` | string | - | File last modified date. | | `fileList.$.name` | string | - | File name. | | `fileList.$.size` | number | - | File size in bytes. | | `fileList.$.type` | string | - | File MIME type. | | `s3GetPolicyRequestId` | string | - | Id of a request of type AwsS3PresignedGetObject that defines which S3 bucket and file to download. | | `showRemoveIcon` | boolean | `false` | Show a remove (×) icon next to each file in the list. Clicking it fires the `onRemove` event; the file is not removed from `fileList` automatically — the action handler is responsible. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/upload#design-token). See [Ant Design upload tokens](https://ant.design/components/upload#design-token). | | `theme.actionsColor` | string | - | Color of action icons (download, preview, remove). | | `theme.pictureCardSize` | number | - | Size of list items in card type (affects both picture-card and picture-circle). | | `theme.controlItemBgHover` | string | - | Background color of file item on hover. | | `theme.colorIcon` | string | - | Color of file icons. | | `theme.fontSize` | number | - | Font size of file name text. | | `theme.borderRadiusSM` | number | - | Border radius of file list items. | | Event | Event Data | Description | | --- | --- | --- | | `onChange` | \- | Triggered when the upload state is changing. | | `onRemove` | `{ file }` | Triggered when a file remove icon is clicked. The file is NOT removed automatically — the handler is responsible for updating `fileList` (e.g. via `SetState`). | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The S3Download element. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/download.md --- # Download Download files from any storage provider — AWS S3 (and S3-compatible services), Google Cloud Storage, or Azure Blob Storage. Requires a download request (AwsS3PresignedGetObject, GcsSignedGetUrl, or AzureBlobDownloadSas) referenced by the downloadPolicyRequestId property. Clicking a file resolves a URL through the request on demand — set `public: true` on the request for stable, non-expiring URLs to publicly readable objects. If files were uploaded with an Upload block, its fileList value can be mapped directly to this block. > The Download block requires a storage connection and a download request. The examples below show configuration structure but will not download files without a configured connection. ```yaml - id: basic_default type: Download properties: downloadPolicyRequestId: download_policy_request fileList: - key: reports/report.pdf name: report.pdf size: 1024 type: application/pdf ``` ```yaml - id: removable type: Download properties: downloadPolicyRequestId: download_policy_request showRemoveIcon: true fileList: - key: reports/report.pdf name: report.pdf - key: reports/summary.xlsx name: summary.xlsx ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `downloadPolicyRequestId` | string | - | Id of a download request (e.g. AwsS3PresignedGetObject, GcsSignedGetUrl, AzureBlobDownloadSas) that returns a URL for the file to download. | | `fileList` | array | - | List of files to be downloaded. If files were uploaded using an Upload block, the fileList value can just be mapped to this field. | | `fileList.$.key` | string | - | Storage object key. | | `fileList.$.lastModified` | string | - | File last modified date. | | `fileList.$.name` | string | - | File name. | | `fileList.$.size` | number | - | File size in bytes. | | `fileList.$.type` | string | - | File MIME type. | | `showRemoveIcon` | boolean | `false` | Show a remove (×) icon next to each file in the list. Clicking it fires the `onRemove` event; the file is not removed from `fileList` automatically — the action handler is responsible. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/upload#design-token). See [Ant Design upload tokens](https://ant.design/components/upload#design-token). | | `theme.actionsColor` | string | - | Color of action icons (download, preview, remove). | | `theme.pictureCardSize` | number | - | Size of list items in card type (affects both picture-card and picture-circle). | | `theme.controlItemBgHover` | string | - | Background color of file item on hover. | | `theme.colorIcon` | string | - | Color of file icons. | | `theme.fontSize` | number | - | Font size of file name text. | | `theme.borderRadiusSM` | number | - | Border radius of file list items. | | Event | Event Data | Description | | --- | --- | --- | | `onChange` | \- | Triggered when the upload state is changing. | | `onRemove` | `{ file }` | Triggered when a file remove icon is clicked. The file is NOT removed automatically — the handler is responsible for updating `fileList` (e.g. via `SetState`). | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Download element. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/search.md --- # Search A full-text search command palette (Cmd+K / Ctrl+K) with keyboard navigation, result highlighting, and grouped results. Uses [MiniSearch](https://lucaong.github.io/minisearch/) for the search engine and antd Modal for the overlay. Two ways to provide data: pre-built index file via `indexUrl` (generated at build time, best for static content), or runtime `documents` array (indexed client-side, good for dynamic content). See the guide and examples below. ## Generating a Search Index The Search block supports two data sources. For most apps, a **pre-built index file** is the best approach — it's generated once at build time and loaded on-demand when the user opens the search modal. ### Option 1: Pre-built index file (recommended) Create a JavaScript transformer that runs during `lowdefy build`. It receives all resolved pages, extracts searchable text, builds a [MiniSearch](https://lucaong.github.io/minisearch/) index, and writes it to `public/search-index.json`. **Step 1 — Write the transformer:** ```javascript // templates/buildSearchIndex.js import MiniSearch from 'minisearch'; import fs from 'fs'; import path, { dirname } from 'path'; import { fileURLToPath } from 'url'; function buildSearchIndex(pages) { // Extract searchable content from each page const documents = pages.filter(Boolean).map((page) => ({ id: page.id, pageId: page.id, title: page.properties?.title ?? page.id, section: 'Docs', // or derive from menus description: '', // extract from page content as needed })); const miniSearch = new MiniSearch({ fields: ['title', 'description'], storeFields: ['title', 'pageId', 'section'], }); miniSearch.addAll(documents); // The index JSON format the Search block expects const index = { engine: 'minisearch', version: 1, options: { fields: ['title', 'description'], storeFields: ['title', 'pageId', 'section'], idField: 'id', }, searchDefaults: { boost: { title: 2 }, fuzzy: 0.2, prefix: true, }, resultDefaults: { title: 'title', description: 'description', pageId: 'pageId', }, data: JSON.parse(JSON.stringify(miniSearch)), }; const dir = path.join(dirname(fileURLToPath(import.meta.url)), '../public'); fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(path.join(dir, 'search-index.json'), JSON.stringify(index)); } export default buildSearchIndex; ``` **Step 2 — Wire it into lowdefy.yaml:** ```yaml # lowdefy.yaml pages: _ref: path: pages.yaml transformer: templates/buildSearchIndex.js ``` The transformer runs during every build. The output file (`public/search-index.json`) is served as a static asset and fetched by the Search block when the modal opens. **Step 3 — Add the Search block:** ```yaml - id: site_search type: Search properties: indexUrl: /search-index.json events: onSelect: - id: navigate type: Link params: pageId: _event: pageId ``` ### Option 2: Runtime documents (no build step) For small datasets or dynamic content (e.g., from a request), pass documents directly. The block indexes them client-side: ```yaml - id: feature_search type: Search properties: documents: - id: users title: User Management body: Create, edit, and deactivate user accounts. category: Admin - id: billing title: Billing body: View invoices and update payment methods. category: Finance fields: [title, body] storeFields: [title, category, id] searchOptions: boost: title: 2 fuzzy: 0.2 result: title: title description: body category: category ``` You can also load documents from a request and pass them via state: ```yaml properties: documents: _state: searchDocuments fields: [title, body] storeFields: [title, id] ``` ### Index JSON format The `indexUrl` JSON file has this structure: | Key | Type | Description | | ---------------- | -------- | -------------------------------------------------------------------------------------------- | | `engine` | `string` | Always `"minisearch"` | | `version` | `number` | Schema version (currently `1`) | | `options` | `object` | MiniSearch constructor options (`fields`, `storeFields`, `idField`) | | `searchDefaults` | `object` | Default search options (`boost`, `fuzzy`, `prefix`, `combineWith`) | | `resultDefaults` | `object` | Maps index fields to result UI (`title`, `description`, `category`, `pageId`, `url`, `icon`) | | `groups` | `array` | Result grouping definitions (`label`, `match`, `icon`) | | `data` | `object` | Serialized MiniSearch index (from `JSON.parse(JSON.stringify(miniSearch))`) | See the [MiniSearch API docs](https://lucaong.github.io/minisearch/classes/MiniSearch.MiniSearch.html) for all available field and search options. ```yaml - id: example_search_prebuilt type: Search properties: indexUrl: /search-index.json placeholder: Search pages... result: title: title description: snippet category: section pageId: pageId groups: - label: Concepts match: section: Concepts icon: Lightbulb - label: Blocks match: section: Blocks icon: PanelsTopLeft events: onSelect: - id: navigate type: Link params: pageId: _event: pageId ``` ```yaml - id: example_search_runtime type: Search properties: documents: - id: users title: User Management body: Create, edit, and deactivate user accounts. Assign roles and permissions. category: Admin - id: billing title: Billing & Invoices body: View invoices, update payment methods, and download receipts. category: Finance - id: reports title: Analytics Reports body: Dashboard with charts for revenue, signups, and engagement metrics. category: Analytics - id: settings title: App Settings body: Configure notifications, theme, language, and integration API keys. category: Admin - id: api-keys title: API Keys body: Generate and revoke API keys for third-party integrations. category: Admin fields: - title - body storeFields: - title - category - id searchOptions: boost: title: 2 fuzzy: 0.2 result: title: title description: body category: category groups: - label: Admin match: category: Admin icon: settings - label: Finance match: category: Finance icon: DollarSign - label: Analytics match: category: Analytics icon: chart placeholder: Search features... label: Find shortcut: mod+k events: onSelect: - id: show_result type: DisplayMessage params: content: _string.concat: - "Selected: " - _event: title ``` ```yaml - id: example_search_custom_shortcut type: Search properties: indexUrl: /search-index.json shortcut: mod+j label: Quick Find placeholder: Quick find... events: onSelect: - id: navigate type: Link params: pageId: _event: pageId ``` ```yaml - id: example_search_analytics type: Search properties: indexUrl: /search-index.json events: onSearch: - id: track_search type: DisplayMessage params: content: _string.concat: - 'Search: "' - _event: value - '" (' - _event: resultCount - " results)" onSelect: - id: track_click type: DisplayMessage params: content: _string.concat: - Clicked " - _event: title - '" for query "' - _event: query - '"' - id: navigate type: Link params: pageId: _event: pageId ``` ```yaml - id: example_search_sections type: Search properties: indexUrl: /search-index.json recentResults: true recentResultsCount: 4 footer: Enter to open · mod+Enter to ask result: title: title description: snippet category: section pageId: pageId groups: - label: Concepts match: section: Concepts limit: 3 seeAll: label: See all pageId: docs-search urlQuery: q: "{query}" - label: Blocks match: section: Blocks limit: 3 pinned: title: "Ask the assistant: {query}" icon: Sparkles shortcut: mod+enter events: onSelect: - id: navigate type: Link params: pageId: _event: pageId onSeeAll: - id: see_all type: Link params: pageId: _event: pageId urlQuery: _event: urlQuery onPinned: - id: ask type: DisplayMessage params: content: _string.concat: - "Ask: " - _event: query ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `indexUrl` | string \| array | - | URL or array of URLs to pre-built search index JSON files. | | `documents` | array | - | Array of document objects for client-side indexing. | | `fields` | array | - | Fields to index. Required when using documents, optional for indexUrl. | | `storeFields` | array | - | Fields to store in the index and return in results. | | `searchOptions` | object | - | MiniSearch search options. | | `searchOptions.boost` | object | - | Field boost factors for relevance scoring. | | `searchOptions.fuzzy` | number | - | Fuzzy matching tolerance as a fraction of term length (0 to 1). | | `searchOptions.prefix` | boolean | `true` | Enable prefix matching. | | `searchOptions.combineWith` | string | `"OR"` | How to combine search terms. Enum: `OR`, `AND`. | | `label` | string | `"Search"` | Trigger button text. | | `icon` | string \| object | `"search"` | Trigger button icon: a semantic name like `search`, a Lucide icon name like `Search`, or a set-qualified name like `tabler:Search`, or properties of an Icon block. | | `icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `icon.color` | string | - | Icon color. | | `icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `icon.rotate` | number | - | Number of degrees to rotate the icon. | | `icon.spin` | boolean | - | Continuously spin the icon with animation. | | `icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `showShortcut` | boolean | `true` | Show keyboard shortcut badge on the trigger button. | | `placeholder` | string | `"Search..."` | Search input placeholder text. | | `shortcut` | string | `"mod+k"` | Keyboard shortcut to open the modal. Renders a shortcut badge on the trigger button (see showShortcut). Use "mod" for Cmd on Mac, Ctrl elsewhere. | | `width` | number | `640` | Modal width in pixels. | | `maxResults` | number | `20` | Maximum number of results displayed. When any group sets a `limit`, it instead caps each group that sets none, and "Other". | | `result` | object | - | Maps index fields to result UI slots. | | `result.title` | string | `"title"` | Field name for result title. | | `result.description` | string | - | Field name for result description/snippet. | | `result.category` | string | - | Field name for result grouping category. | | `result.icon` | string | - | Field name for the result icon. The field holds an icon name, or Icon properties such as `{ name, color }`. Results without it show the document icon. | | `result.pageId` | string | - | Field name for Lowdefy page navigation. | | `result.url` | string | - | Field name for external URL navigation. | | `groups` | array | - | Result grouping definitions. | | `groups.$.label` | string | - | Group display label. | | `groups.$.match` | object | - | Field/value pairs to match results to this group. | | `groups.$.icon` | string | - | Icon for the group header. | | `groups.$.limit` | number | - | Number of results shown for the group. The heading then shows the number of matches, and a "See all N" row follows the shown rows when there are more. | | `groups.$.seeAll` | object \| string | - | The "See all N" row of a group with a `limit`. An object fires `onSeeAll` with `{ group, count, query, pageId, urlQuery }`; a string is the name of the event to fire instead, with the same payload. | | `groups.$.seeAll.label` | string | `"See all"` | Row text before the count. | | `groups.$.seeAll.pageId` | string | - | Page id sent in the event, for a Link action. | | `groups.$.seeAll.urlQuery` | object | - | Url query sent in the event. `{query}` in a string is replaced by the typed search term. | | `pinned` | object | - | An action row kept last in the list while a query is typed. It is selected when nothing matches, reached with the arrow keys, and fires `onPinned` with `{ query }`. | | `pinned.title` | string | - | Row text. `{query}` is replaced by the typed search term. | | `pinned.icon` | string \| object | `"search"` | Row icon: a semantic name, a Lucide icon name, or properties of an Icon block. | | `pinned.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `pinned.icon.color` | string | - | Icon color. | | `pinned.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `pinned.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `pinned.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `pinned.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `pinned.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `pinned.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `pinned.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `pinned.shortcut` | string | - | Keyboard shortcut that selects the row from any row, such as `mod+enter`. Shown as a badge on the row. | | `pinned.event` | string | - | Name of the event to fire instead of `onPinned`. | | `footer` | string | - | A line of key hints shown below the results. | | `noResultsMessage` | string | `"No results found."` | Message shown when no results match. | | `highlightMatches` | boolean | `true` | Highlight matched terms in results. | | `recentSearches` | boolean | `true` | Show recent searches when input is empty. | | `recentSearchesKey` | string | `"search"` | localStorage key prefix for recent searches. Recent searches are kept per app and per signed-in user, and cleared at sign-out. | | `recentSearchesCount` | number | `5` | Maximum number of recent searches stored. | | `recentResults` | boolean | `false` | Remember the results a person selects and list them when the input is empty, in place of recent searches. Kept per app and per signed-in user, like recent searches, and cleared at sign-out. | | `recentResultsCount` | number | `4` | Maximum number of recent results stored and listed. | | `theme` | object | - | Antd design token overrides for the trigger Button, the search Modal and its Input, e.g. `borderRadius`, `colorPrimary`, `colorBgElevated` or `fontSize`. See [antd design tokens](https://ant.design/components/overview#design-token). | | Event | Event Data | Description | | --- | --- | --- | | `onSelect` | `{ query, resultCount }` | Trigger actions when a search result is selected. Result item stored fields are spread into the event object. | | `onSearch` | `{ value, resultCount }` | Trigger actions when the search query changes. | | `onPinned` | `{ query }` | Trigger actions when the pinned row is selected, by click, Enter or its shortcut. Fires the event named by `pinned.event` instead when that is set. | | `onSeeAll` | `{ group, count, query, pageId, urlQuery }` | Trigger actions when a group's "See all" row is clicked and the group's `seeAll` is an object. When `seeAll` is a string, the event of that name fires instead. | | `onOpen` | \- | Trigger actions when the search modal opens. | | `onClose` | \- | Trigger actions when the search modal closes. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The search trigger button and modal root. | | `/trigger` | The trigger button. | | `/triggerBadge` | The keyboard shortcut badge on the trigger. | | `/modal` | The antd Modal container. | | `/input` | The search input field. | | `/results` | The scrollable results list. | | `/group` | A result group container. | | `/groupHeading` | A group heading label. | | `/groupCount` | The match count in a group heading (shown when the group has a limit). | | `/seeAll` | A group's "See all N" row. | | `/pinned` | The pinned last row. | | `/footer` | The footer line of key hints. | | `/item` | A result item row. | | `/itemIcon` | The icon in a result item. | | `/itemTitle` | The title in a result item. | | `/itemDescription` | The description/snippet in a result item. | | `/highlight` | Highlighted match text (<mark> elements). | | `/empty` | The empty state message. | | `/loading` | The loading spinner container. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/skeleton.md --- # Skeleton Rectangular skeleton loading placeholder. ```yaml - id: basic_line type: Skeleton layout: flex: 0 0 auto properties: width: 200 height: 16 - id: basic_block type: Skeleton layout: flex: 0 0 auto properties: width: 120 height: 40 - id: basic_tall type: Skeleton layout: flex: 0 0 auto properties: width: 80 height: 80 ``` ```yaml - id: active_thin type: Skeleton layout: flex: 0 0 auto properties: width: 250 height: 12 - id: active_medium type: Skeleton layout: flex: 0 0 auto properties: width: 300 height: 16 - id: active_thick type: Skeleton layout: flex: 0 0 auto properties: width: 180 height: 24 ``` ```yaml - id: avatar_row type: Box layout: gap: 12 align: center blocks: - id: avatar_circle type: SkeletonAvatar layout: flex: 0 0 auto properties: size: large shape: round - id: avatar_lines type: Box layout: gap: 8 blocks: - id: avatar_name type: Skeleton properties: width: 180 height: 14 - id: avatar_desc type: Skeleton properties: width: 120 height: 14 ``` ```yaml - id: paragraph_row_container type: Box layout: gap: 10 blocks: - id: para_row_1 type: Skeleton properties: width: 100% height: 14 - id: para_row_2 type: Skeleton properties: width: 100% height: 14 - id: para_row_3 type: Skeleton properties: width: 100% height: 14 - id: para_row_4 type: Skeleton properties: width: 60% height: 14 ``` ```yaml - id: width_full type: Skeleton properties: width: 100% height: 16 - id: width_half type: Skeleton properties: width: 50% height: 16 - id: width_quarter type: Skeleton properties: width: 25% height: 16 - id: width_px type: Skeleton properties: width: 300 height: 16 ``` ```yaml - id: height_thin type: Skeleton layout: flex: 0 0 auto properties: width: 200 height: 8 - id: height_default type: Skeleton layout: flex: 0 0 auto properties: width: 200 height: 16 - id: height_medium type: Skeleton layout: flex: 0 0 auto properties: width: 200 height: 32 - id: height_thick type: Skeleton layout: flex: 0 0 auto properties: width: 200 height: 48 ``` ```yaml - id: pct_100 type: Skeleton properties: width: 100% height: 14 - id: pct_80 type: Skeleton properties: width: 80% height: 14 - id: pct_60 type: Skeleton properties: width: 60% height: 14 - id: pct_40 type: Skeleton properties: width: 40% height: 14 ``` ```yaml - id: card_skeleton type: Box style: padding: 16 border: 1px solid borderRadius: 8 maxWidth: 360 layout: gap: 12 blocks: - id: card_image type: Skeleton properties: width: 100% height: 180 - id: card_title type: Skeleton properties: width: 70% height: 20 - id: card_desc_1 type: Skeleton properties: width: 100% height: 14 - id: card_desc_2 type: Skeleton properties: width: 100% height: 14 - id: card_desc_3 type: Skeleton properties: width: 50% height: 14 ``` ```yaml - id: style_rounded type: Skeleton layout: flex: 0 0 auto properties: width: 200 height: 20 style: .element: borderRadius: 10 - id: style_square type: Skeleton layout: flex: 0 0 auto properties: width: 60 height: 60 style: .element: borderRadius: 8 - id: style_circle type: Skeleton layout: flex: 0 0 auto properties: width: 60 height: 60 style: .element: borderRadius: 30 ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `height` | number \| string | - | Height of the skeleton. | | `width` | number \| string | - | Width of the skeleton. | No events defined. | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Skeleton element. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/skeletonavatar.md --- # SkeletonAvatar Avatar-shaped skeleton loading placeholder. ```yaml - id: shape_round type: SkeletonAvatar layout: flex: 0 0 auto properties: shape: round - id: shape_square type: SkeletonAvatar layout: flex: 0 0 auto properties: shape: square ``` ```yaml - id: size_small_round type: SkeletonAvatar layout: flex: 0 0 auto properties: size: small shape: round - id: size_medium_round type: SkeletonAvatar layout: flex: 0 0 auto properties: size: medium shape: round - id: size_large_round type: SkeletonAvatar layout: flex: 0 0 auto properties: size: large shape: round ``` ```yaml - id: size_small_square type: SkeletonAvatar layout: flex: 0 0 auto properties: size: small shape: square - id: size_medium_square type: SkeletonAvatar layout: flex: 0 0 auto properties: size: medium shape: square - id: size_large_square type: SkeletonAvatar layout: flex: 0 0 auto properties: size: large shape: square ``` ```yaml - id: active_round_small type: SkeletonAvatar layout: flex: 0 0 auto properties: size: small shape: round - id: active_round_medium type: SkeletonAvatar layout: flex: 0 0 auto properties: size: medium shape: round - id: active_round_large type: SkeletonAvatar layout: flex: 0 0 auto properties: size: large shape: round - id: active_square_small type: SkeletonAvatar layout: flex: 0 0 auto properties: size: small shape: square - id: active_square_medium type: SkeletonAvatar layout: flex: 0 0 auto properties: size: medium shape: square - id: active_square_large type: SkeletonAvatar layout: flex: 0 0 auto properties: size: large shape: square ``` ```yaml - id: custom_tiny type: SkeletonAvatar layout: flex: 0 0 auto properties: size: small shape: round style: .element: width: 16 height: 16 - id: custom_xlarge type: SkeletonAvatar layout: flex: 0 0 auto properties: size: large shape: round style: .element: width: 64 height: 64 borderRadius: 32 - id: custom_xxlarge type: SkeletonAvatar layout: flex: 0 0 auto properties: size: large shape: round style: .element: width: 96 height: 96 borderRadius: 48 ``` ```yaml - id: avatar_text_row type: Box layout: gap: 12 align: center blocks: - id: avatar_icon type: SkeletonAvatar layout: flex: 0 0 auto properties: size: large shape: round - id: avatar_text_col type: Box layout: gap: 6 blocks: - id: avatar_name_line type: Skeleton properties: width: 160 height: 14 - id: avatar_desc_line type: Skeleton properties: width: 100 height: 12 - id: avatar_square_row type: Box layout: gap: 12 align: center blocks: - id: avatar_sq_icon type: SkeletonAvatar layout: flex: 0 0 auto properties: size: large shape: square - id: avatar_sq_col type: Box layout: gap: 6 blocks: - id: avatar_sq_line_1 type: Skeleton properties: width: 200 height: 14 - id: avatar_sq_line_2 type: Skeleton properties: width: 140 height: 14 ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `size` | string | `"medium"` | Size of the skeleton. Enum: `small`, `medium`, `large`. | | `shape` | string | `"round"` | Shape of the skeleton. Enum: `square`, `round`. | No events defined. | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The SkeletonAvatar element. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/skeletonbutton.md --- # SkeletonButton Button-shaped skeleton loading placeholder. ```yaml - id: shape_default type: SkeletonButton layout: flex: 0 0 auto properties: size: medium width: 120 - id: shape_round type: SkeletonButton layout: flex: 0 0 auto properties: size: medium width: 120 shape: round ``` ```yaml - id: size_small type: SkeletonButton layout: flex: 0 0 auto properties: size: small width: 80 - id: size_medium type: SkeletonButton layout: flex: 0 0 auto properties: size: medium width: 100 - id: size_large type: SkeletonButton layout: flex: 0 0 auto properties: size: large width: 120 ``` ```yaml - id: size_small_round type: SkeletonButton layout: flex: 0 0 auto properties: size: small width: 80 shape: round - id: size_medium_round type: SkeletonButton layout: flex: 0 0 auto properties: size: medium width: 100 shape: round - id: size_large_round type: SkeletonButton layout: flex: 0 0 auto properties: size: large width: 120 shape: round ``` ```yaml - id: active_small type: SkeletonButton layout: flex: 0 0 auto properties: size: small width: 80 - id: active_medium type: SkeletonButton layout: flex: 0 0 auto properties: size: medium width: 100 - id: active_large type: SkeletonButton layout: flex: 0 0 auto properties: size: large width: 120 - id: active_round type: SkeletonButton layout: flex: 0 0 auto properties: size: medium width: 120 shape: round ``` ```yaml - id: block_small type: SkeletonButton properties: size: small - id: block_medium type: SkeletonButton properties: size: medium - id: block_large type: SkeletonButton properties: size: large ``` ```yaml - id: block_small_round type: SkeletonButton properties: size: small shape: round - id: block_medium_round type: SkeletonButton properties: size: medium shape: round - id: block_large_round type: SkeletonButton properties: size: large shape: round ``` ```yaml - id: custom_px_80 type: SkeletonButton layout: flex: 0 0 auto properties: size: medium width: 80 - id: custom_px_160 type: SkeletonButton layout: flex: 0 0 auto properties: size: medium width: 160 - id: custom_px_240 type: SkeletonButton layout: flex: 0 0 auto properties: size: medium width: 240 - id: custom_pct_50 type: SkeletonButton properties: size: medium width: 50% ``` ```yaml - id: btn_group_row type: Box layout: gap: 8 blocks: - id: btn_group_1 type: SkeletonButton layout: flex: 0 0 auto properties: size: medium width: 100 shape: round - id: btn_group_2 type: SkeletonButton layout: flex: 0 0 auto properties: size: medium width: 80 shape: round - id: btn_group_3 type: SkeletonButton layout: flex: 0 0 auto properties: size: medium width: 60 shape: round ``` ```yaml - id: style_custom_radius type: SkeletonButton layout: flex: 0 0 auto properties: size: medium width: 140 style: .element: borderRadius: 4 - id: style_no_radius type: SkeletonButton layout: flex: 0 0 auto properties: size: medium width: 140 style: .element: borderRadius: 0 - id: style_circle type: SkeletonButton layout: flex: 0 0 auto properties: size: large width: 40 style: .element: borderRadius: 20 ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `shape` | string | `"default"` | Shape of the skeleton; round gives fully rounded ends. Enum: `default`, `round`. | | `size` | string | `"medium"` | Size of the skeleton. Enum: `small`, `medium`, `large`. | | `width` | number \| string | - | Width of the skeleton. | No events defined. | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The SkeletonButton element. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/skeletoninput.md --- # SkeletonInput Input-shaped skeleton loading placeholder. ```yaml - id: size_small type: SkeletonInput properties: size: small - id: size_medium type: SkeletonInput properties: size: medium - id: size_large type: SkeletonInput properties: size: large ``` ```yaml - id: active_small type: SkeletonInput properties: size: small - id: active_medium type: SkeletonInput properties: size: medium - id: active_large type: SkeletonInput properties: size: large ``` ```yaml - id: no_label_small type: SkeletonInput properties: size: small label: false - id: no_label_medium type: SkeletonInput properties: size: medium label: false - id: no_label_large type: SkeletonInput properties: size: large label: false ``` ```yaml - id: width_full type: SkeletonInput properties: size: medium width: 100% - id: width_half type: SkeletonInput properties: size: medium width: 50% - id: width_fixed type: SkeletonInput properties: size: medium width: 300 ``` ```yaml - id: label_narrow type: SkeletonInput properties: size: medium labelWidth: 15% - id: label_wide type: SkeletonInput properties: size: medium labelWidth: 50% - id: label_fixed type: SkeletonInput properties: size: medium labelWidth: 120 ``` ```yaml - id: label_height_small type: SkeletonInput properties: size: medium labelHeight: 12 - id: label_height_default type: SkeletonInput properties: size: medium labelHeight: 20 - id: label_height_tall type: SkeletonInput properties: size: medium labelHeight: 28 ``` ```yaml - id: input_height_short type: SkeletonInput properties: size: medium inputHeight: 24 - id: input_height_default type: SkeletonInput properties: size: medium - id: input_height_tall type: SkeletonInput properties: size: medium inputHeight: 48 ``` ```yaml - id: combined_compact type: SkeletonInput properties: size: small labelWidth: 20% labelHeight: 12 width: 60% - id: combined_full type: SkeletonInput properties: size: large labelWidth: 40% labelHeight: 24 inputHeight: 44 width: 100% - id: combined_no_label type: SkeletonInput properties: size: medium label: false inputHeight: 40 width: 80% ``` ```yaml - id: form_skeleton type: Box layout: gap: 16 blocks: - id: form_row_1 type: Box layout: gap: 16 direction: row blocks: - id: form_first_name type: SkeletonInput properties: size: medium labelWidth: 40% - id: form_last_name type: SkeletonInput properties: size: medium labelWidth: 40% - id: form_email type: SkeletonInput properties: size: medium labelWidth: 20% - id: form_notes type: SkeletonInput properties: size: medium labelWidth: 15% inputHeight: 80 - id: form_submit type: SkeletonButton properties: size: medium width: 120 ``` ```yaml - id: style_rounded_input type: SkeletonInput properties: size: medium style: .input: borderRadius: 8 - id: style_custom_bg type: SkeletonInput properties: size: medium label: false style: .element: maxWidth: 400 ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `label` | boolean | `true` | Render the label skeleton. Set to false for an input without a label. | | `size` | string | `"medium"` | Size of the skeleton. Enum: `small`, `medium`, `large`. | | `width` | number \| string | - | Width of the skeleton. | | `labelHeight` | number \| string | - | Height of the skeleton. | | `labelWidth` | number \| string | - | Width of the skeleton. | | `inputHeight` | number \| string | - | Height of the skeleton. | No events defined. | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The outer container. | | `/input` | The input skeleton. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/skeletonparagraph.md --- # SkeletonParagraph Paragraph-shaped skeleton loading placeholder. ```yaml - id: basic_default type: SkeletonParagraph ``` ```yaml - id: rows_1 type: SkeletonParagraph properties: lines: 1 - id: rows_2 type: SkeletonParagraph properties: lines: 2 - id: rows_3 type: SkeletonParagraph properties: lines: 3 - id: rows_4 type: SkeletonParagraph properties: lines: 4 - id: rows_6 type: SkeletonParagraph properties: lines: 6 - id: rows_8 type: SkeletonParagraph properties: lines: 8 ``` ```yaml - id: width_100 type: SkeletonParagraph properties: width: 100% lines: 3 - id: width_75 type: SkeletonParagraph properties: width: 75% lines: 3 - id: width_50 type: SkeletonParagraph properties: width: 50% lines: 3 ``` ```yaml - id: width_px_400 type: SkeletonParagraph properties: width: 400 lines: 3 - id: width_px_300 type: SkeletonParagraph properties: width: 300 lines: 3 - id: width_px_200 type: SkeletonParagraph properties: width: 200 lines: 3 ``` ```yaml - id: active_default type: SkeletonParagraph properties: lines: 4 - id: active_short type: SkeletonParagraph properties: lines: 2 width: 60% ``` ```yaml - id: single_full type: SkeletonParagraph properties: lines: 1 width: 100% - id: single_half type: SkeletonParagraph properties: lines: 1 width: 50% ``` ```yaml - id: article_skeleton type: Box layout: gap: 16 blocks: - id: article_title type: Skeleton properties: width: 60% height: 24 - id: article_subtitle type: Skeleton properties: width: 40% height: 16 - id: article_body type: SkeletonParagraph properties: lines: 6 - id: article_body_2 type: SkeletonParagraph properties: lines: 4 ``` ```yaml - id: card_with_para type: Box style: padding: 16 border: 1px solid borderRadius: 8 maxWidth: 400 layout: gap: 12 blocks: - id: card_header_row type: Box layout: gap: 12 align: center direction: row blocks: - id: card_avatar type: SkeletonAvatar layout: flex: 0 0 auto properties: size: large shape: round - id: card_header_lines type: Box layout: gap: 6 blocks: - id: card_name type: Skeleton properties: width: 140 height: 14 - id: card_date type: Skeleton properties: width: 80 height: 12 - id: card_body type: SkeletonParagraph properties: lines: 3 ``` ```yaml - id: style_narrow type: SkeletonParagraph properties: lines: 3 style: .element: maxWidth: 300 - id: style_padded type: SkeletonParagraph properties: lines: 3 style: .element: padding: 16 border: 1px solid borderRadius: 8 ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `lines` | number | `4` | Number of paragraph lines of the skeleton. | | `width` | number \| string | - | Width of the skeleton. | No events defined. | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The SkeletonParagraph element. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/statistic.md --- # Statistic Statistic display with prefix, suffix, and formatting. ```yaml - id: value_number type: Statistic layout: flex: 0 0 auto properties: title: Active Users value: 112893 - id: value_decimal type: Statistic layout: flex: 0 0 auto properties: title: Account Balance value: 112893.12 precision: 2 - id: value_string type: Statistic layout: flex: 0 0 auto properties: title: Status value: Running - id: value_version type: Statistic layout: flex: 0 0 auto properties: title: Version value: v4.2.1 ``` ```yaml - id: prefix_dollar type: Statistic layout: flex: 0 0 auto properties: title: Revenue value: 24500 prefix: $ - id: suffix_percent type: Statistic layout: flex: 0 0 auto properties: title: Completion value: 93.5 suffix: "%" - id: suffix_unit type: Statistic layout: flex: 0 0 auto properties: title: Response Time value: 42 suffix: ms - id: prefix_suffix_price type: Statistic layout: flex: 0 0 auto properties: title: Price value: 49.99 prefix: $ suffix: /mo ``` ```yaml - id: prefixicon_like type: Statistic layout: flex: 0 0 auto properties: title: Feedback value: 1128 prefixIcon: ThumbsUp - id: prefixicon_arrow_up type: Statistic layout: flex: 0 0 auto properties: title: Growth value: 11.28 suffix: "%" prefixIcon: name: arrow-up color: "#3f8600" - id: prefixicon_arrow_down type: Statistic layout: flex: 0 0 auto properties: title: Decline value: 9.3 suffix: "%" prefixIcon: name: arrow-down color: "#cf1322" - id: suffixicon_rise type: Statistic layout: flex: 0 0 auto properties: title: Revenue Trend value: 12500 prefix: $ suffixIcon: name: TrendingUp color: "#3f8600" - id: suffixicon_info type: Statistic layout: flex: 0 0 auto properties: title: Sessions value: 8920 suffixIcon: info ``` ```yaml - id: precision_0 type: Statistic layout: flex: 0 0 auto properties: title: Total Sales value: 142587 precision: 0 - id: precision_1 type: Statistic layout: flex: 0 0 auto properties: title: Average Rating value: 4.7 precision: 1 - id: precision_2 type: Statistic layout: flex: 0 0 auto properties: title: Average Price value: 49.99 precision: 2 prefix: $ ``` ```yaml - id: sep_default type: Statistic layout: flex: 0 0 auto properties: title: Default (Comma) value: 1234567.89 precision: 2 - id: sep_space type: Statistic layout: flex: 0 0 auto properties: title: Space Separator value: 1234567.89 precision: 2 groupSeparator: " " - id: sep_european type: Statistic layout: flex: 0 0 auto properties: title: European Format value: 1234567.89 precision: 2 groupSeparator: . decimalSeparator: "," ``` ```yaml - id: timer_countdown type: Statistic layout: span: 12 properties: title: Launch in value: _date: 2030-01-01T00:00:00.000Z timer: type: countdown format: D [days] HH:mm:ss events: onFinish: - id: timer_countdown_finished type: DisplayMessage params: content: We have launched. - id: timer_countup type: Statistic layout: span: 12 properties: title: Incident open for value: _date: 2026-01-01T00:00:00.000Z timer: type: countup format: D [days] HH:mm ``` ```yaml - id: loading_true type: Statistic layout: flex: 0 0 auto properties: title: Fetching Data value: 0 loading: true - id: loading_false type: Statistic layout: flex: 0 0 auto properties: title: Data Loaded value: 12345 loading: false - id: loading_with_prefix type: Statistic layout: flex: 0 0 auto properties: title: Loading Revenue value: 52800 prefix: $ loading: true ``` ```yaml - id: html_bold_title type: Statistic layout: flex: 0 0 auto properties: title: Important Metric value: 98.7 suffix: "%" - id: html_colored_title type: Statistic layout: flex: 0 0 auto properties: title: 'Active Connections' value: 1024 - id: html_icon_title type: Statistic layout: flex: 0 0 auto properties: title: Server Status ✅ value: Healthy ``` ```yaml - id: css_tailwind_bg type: Statistic layout: flex: 0 0 auto class: bg-bg-layout p-4 rounded-lg border border-border properties: title: Monthly Revenue value: 52800 prefix: $ - id: css_tailwind_blue type: Statistic layout: flex: 0 0 auto class: bg-bg-layout p-4 rounded-lg border border-border properties: title: Active Users value: 2847 prefixIcon: users - id: css_tailwind_center type: Statistic layout: flex: 0 0 auto class: text-center bg-bg-layout p-4 rounded-lg border border-border properties: title: Avg. Score value: 94.2 suffix: "%" - id: css_inline_value type: Statistic layout: flex: 0 0 auto properties: title: Error Rate value: 0.12 suffix: "%" style: .element: padding: 16px 24px borderRadius: 8px border: "1px solid #ffa39e" .value: color: "#cf1322" fontSize: 28 ``` ```yaml - id: theme_large_title type: Statistic layout: flex: 0 0 auto properties: title: Large Title Size value: 42 theme: titleFontSize: 20 - id: theme_large_content type: Statistic layout: flex: 0 0 auto properties: title: Large Value Font value: 99.9 suffix: "%" theme: contentFontSize: 36 - id: theme_custom_colors type: Statistic layout: flex: 0 0 auto properties: title: Custom Color Theme value: 1250 prefix: $ theme: colorText: "#722ed1" colorTextDescription: "#531dab" ``` _Last updated 2 minutes ago_ ```yaml - id: dashboard_card type: Card properties: title: System Overview size: small blocks: - id: dashboard_row type: Box layout: gap: 16 blocks: - id: dash_revenue type: Statistic layout: flex: 1 1 0 class: bg-bg-layout p-4 rounded-lg properties: title: Total Revenue value: 126560 prefix: $ precision: 0 prefixIcon: name: CircleDollarSign color: "#52c41a" - id: dash_users type: Statistic layout: flex: 1 1 0 class: bg-bg-layout p-4 rounded-lg properties: title: Active Users value: 8920 prefixIcon: name: users color: "#1677ff" - id: dash_errors type: Statistic layout: flex: 1 1 0 class: bg-bg-layout p-4 rounded-lg properties: title: Error Rate value: 0.12 suffix: "%" precision: 2 prefixIcon: name: Bug color: "#ff4d4f" - id: dashboard_updated type: Markdown properties: content: _Last updated 2 minutes ago_ ``` ```yaml - id: sales_card type: Card properties: title: Q4 Sales Report size: small blocks: - id: sales_stats type: Box layout: gap: 16 blocks: - id: sales_total type: Statistic layout: flex: 1 1 0 properties: title: Total Sales value: 284300 prefix: $ precision: 0 prefixIcon: name: arrow-up color: "#3f8600" theme: contentFontSize: 28 - id: sales_orders type: Statistic layout: flex: 1 1 0 properties: title: Orders value: 1523 prefixIcon: ShoppingCart - id: sales_avg type: Statistic layout: flex: 1 1 0 properties: title: Avg. Order Value value: 186.67 prefix: $ precision: 2 - id: sales_actions type: Box layout: gap: 8 justify: flex-end blocks: - id: sales_export_btn type: Button layout: flex: 0 0 auto properties: title: Export Report icon: download color: primary variant: outlined events: onClick: - id: sales_export_msg type: DisplayMessage params: content: Report exported successfully. status: success - id: sales_share_btn type: Button layout: flex: 0 0 auto properties: title: Share icon: share color: primary variant: solid events: onClick: - id: sales_share_msg type: DisplayMessage params: content: Share link copied to clipboard. status: success ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `decimalSeparator` | string | `"."` | Decimal separator. | | `groupSeparator` | string | `","` | Group separator. | | `loading` | boolean | `false` | Control the loading status of Statistic. | | `precision` | number | - | Number of decimals to display. | | `prefix` | string | - | Prefix text, priority over prefixIcon. | | `prefixIcon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to customize icon which prefix the statistic. | | `prefixIcon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `prefixIcon.color` | string | - | Icon color. | | `prefixIcon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `prefixIcon.rotate` | number | - | Number of degrees to rotate the icon. | | `prefixIcon.spin` | boolean | - | Continuously spin the icon with animation. | | `prefixIcon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `prefixIcon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `prefixIcon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `prefixIcon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `suffix` | string | - | Suffix text, priority over suffixIcon. | | `suffixIcon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to customize icon which suffix the statistic. | | `suffixIcon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `suffixIcon.color` | string | - | Icon color. | | `suffixIcon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `suffixIcon.rotate` | number | - | Number of degrees to rotate the icon. | | `suffixIcon.spin` | boolean | - | Continuously spin the icon with animation. | | `suffixIcon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `suffixIcon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `suffixIcon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `suffixIcon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `timer` | object | - | Show a live timer instead of a static value. `value` is the target time for a countdown, or the start time for a count up, as a date or a timestamp in milliseconds. | | `timer.type` | string | - | Count down to value, or count up from value. Enum: `countdown`, `countup`. | | `timer.format` | string | `"HH:mm:ss"` | Format of the time, using the tokens Y, M, D, H, m, s and S, like `D [days] HH:mm:ss`. | | `title` | string | - | Title to describe the component - supports html. | | `value` | number \| string | - | Value to display. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design statistic tokens](https://ant.design/components/statistic#design-token). | | `theme.titleFontSize` | number | `14` | Font size of the statistic title. | | `theme.contentFontSize` | number | `24` | Font size of the statistic value. | | `theme.contentFontWeight` | string | - | Font weight of the statistic value. | | `theme.contentFontFamily` | string | - | Font family of the statistic value. | | `theme.colorText` | string | - | Default text color for the statistic value. | | `theme.colorTextDescription` | string | - | Color for the statistic title text. | | Event | Event Data | Description | | --- | --- | --- | | `onFinish` | \- | Trigger actions when a countdown timer reaches zero. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Statistic element. | | `/title` | The Statistic title. | | `/content` | The value section, with the prefix, value and suffix. | | `/prefix` | The prefix of the value. | | `/prefixIcon` | The prefix icon in the Statistic. | | `/suffix` | The suffix of the value. | | `/suffixIcon` | The suffix icon in the Statistic. | | `/value` | The Statistic value. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/steps.md --- # Steps Navigation steps bar, guiding users through the steps of a task. ```yaml - id: basic_steps type: Steps properties: current: 1 items: - title: Finished description: This is a description. - title: In Progress description: This is a description. - title: Waiting description: This is a description. - id: basic_no_description type: Steps properties: current: 0 items: - title: Step 1 - title: Step 2 - title: Step 3 - id: basic_four_steps type: Steps properties: current: 2 items: - title: Login - title: Verification - title: Pay - title: Done ``` ```yaml - id: small_steps type: Steps properties: size: small current: 1 items: - title: Finished - title: In Progress - title: Waiting - id: small_with_description type: Steps properties: size: small current: 0 items: - title: Step 1 description: Brief info - title: Step 2 description: Brief info - title: Step 3 description: Brief info ``` ```yaml - id: icon_steps type: Steps properties: current: 1 items: - title: Login icon: user - title: Verification icon: FileUser - title: Done icon: FaceSlightlySmiling - id: icon_custom_color type: Steps properties: current: 0 items: - title: Upload icon: name: CloudUpload color: "#1677ff" - title: Process icon: name: loading color: "#faad14" - title: Complete icon: name: check color: "#52c41a" ``` ```yaml - id: vertical_steps type: Steps properties: orientation: vertical current: 1 items: - title: Finished description: This is a description. - title: In Progress description: This is a description. - title: Waiting description: This is a description. - id: vertical_small type: Steps properties: orientation: vertical size: small current: 2 items: - title: Order Placed description: Your order has been submitted. - title: Payment Confirmed description: Payment received successfully. - title: Shipping description: Your package is on its way. - title: Delivered ``` ```yaml - id: error_status type: Steps properties: current: 1 status: error items: - title: Finished description: This is a description. - title: In Progress description: This is a description. - title: Waiting description: This is a description. - id: mixed_status type: Steps properties: current: 2 items: - title: Passed status: finish - title: Failed status: error - title: In Review - title: Pending ``` ```yaml - id: navigation_steps type: Steps properties: type: navigation current: 1 items: - title: Step 1 subTitle: (00:00:05) description: This is a description. - title: Step 2 subTitle: (00:01:02) description: This is a description. - title: Step 3 description: This is a description. ``` ```yaml - id: inline_steps type: Steps properties: type: inline current: 1 items: - title: Step 1 - title: Step 2 - title: Step 3 ``` ```yaml - id: panel_steps type: Steps properties: type: panel current: _state: panel_current items: - title: Step 1 description: This is a description. - title: Step 2 description: This is a description. - title: Step 3 description: This is a description. events: onChange: - id: panel_set_state type: SetState params: panel_current: _event: current - id: panel_steps_icons type: Steps properties: type: panel current: _state: panel_icons_current items: - title: Account description: Create your account icon: user - title: Settings description: Configure preferences icon: settings - title: Review description: Review and submit icon: check - title: Done description: All finished events: onChange: - id: panel_icons_set_state type: SetState params: panel_icons_current: _event: current ``` ```yaml - id: dot_steps type: Steps properties: type: dot current: 1 items: - title: Finished description: This is a description. - title: In Progress description: This is a description. - title: Waiting description: This is a description. - id: dot_steps_vertical type: Steps properties: type: dot orientation: vertical current: 1 items: - title: Finished description: This is a description. This is a description. - title: In Progress description: This is a description. This is a description. - title: Waiting description: This is a description. - id: dot_steps_small type: Steps properties: progressDot: true size: small current: 1 items: - title: Finished - title: In Progress - title: Waiting ``` ```yaml - id: max_count_steps type: Steps properties: current: 5 maxCount: 5 items: - title: Draft - title: Review - title: Legal - title: Finance - title: Approval - title: Signed - title: Filed - title: Archived ``` ```yaml - id: progress_steps type: Steps properties: current: 1 percent: 60 items: - title: Finished - title: In Progress - title: Waiting - id: progress_steps_25 type: Steps properties: current: 0 percent: 25 items: - title: Upload - title: Process - title: Done ``` ```yaml - id: label_vertical type: Steps properties: titlePlacement: vertical current: 1 items: - title: Step 1 description: Description - title: Step 2 description: Description - title: Step 3 description: Description ``` ```yaml - id: clickable_steps type: Steps properties: current: _state: clickable_current items: - title: Step 1 description: Click to go here - title: Step 2 description: Click to go here - title: Step 3 description: Click to go here events: onChange: - id: clickable_set_state type: SetState params: clickable_current: _event: current ``` ```yaml - id: theme_primary type: Steps properties: current: 1 items: - title: Finished - title: In Progress - title: Waiting theme: colorPrimary: "#722ed1" - id: theme_icon_size type: Steps properties: current: 1 items: - title: Step 1 - title: Step 2 - title: Step 3 theme: iconSize: 40 iconFontSize: 18 - id: theme_combined type: Steps properties: current: 1 items: - title: Start description: Begin here - title: Middle description: Keep going - title: End description: Almost done theme: colorPrimary: "#52c41a" iconSize: 36 descriptionMaxWidth: 200 finishIconBorderColor: "#52c41a" ``` ```yaml - id: wizard_card type: Card properties: title: Create Account size: small blocks: - id: wizard_steps type: Steps properties: current: _state: wizard_step items: - title: Account icon: user - title: Profile icon: IdCard - title: Confirm icon: check events: onChange: - id: wizard_set_step type: SetState params: wizard_step: _event: current - id: wizard_divider type: Divider - id: wizard_description type: Markdown properties: content: _if: test: _eq: - _state: wizard_step - 0 then: | **Step 1: Account Details** Enter your email address and create a password to get started. else: _if: test: _eq: - _state: wizard_step - 1 then: > **Step 2: Profile Information** Tell us a bit about yourself so we can personalize your experience. else: | **Step 3: Confirmation** Review your information and confirm to complete registration. - id: wizard_actions type: Box layout: gap: 8 justify: flex-end blocks: - id: wizard_prev_btn type: Button layout: flex: 0 0 auto properties: title: Previous color: default variant: outlined disabled: _eq: - _state: wizard_step - 0 events: onClick: - id: wizard_prev_action type: SetState params: wizard_step: _subtract: - _state: key: wizard_step default: 0 - 1 - id: wizard_next_btn type: Button layout: flex: 0 0 auto properties: title: _if: test: _eq: - _state: wizard_step - 2 then: Submit else: Next color: primary variant: solid events: onClick: - id: wizard_next_action type: SetState skip: _eq: - _state: wizard_step - 2 params: wizard_step: _sum: - _state: key: wizard_step default: 0 - 1 - id: wizard_done_msg type: DisplayMessage skip: _not: _eq: - _state: wizard_step - 2 params: content: Account created successfully! status: success ``` ```yaml - id: order_card type: Card properties: title: "Order #12345" size: small blocks: - id: order_steps type: Steps properties: orientation: vertical current: 2 items: - title: Order Placed description: March 10, 2026 at 2:30 PM icon: ShoppingCart - title: Payment Confirmed description: March 10, 2026 at 2:31 PM icon: CreditCard - title: Shipped description: March 12, 2026 - Tracking number available icon: Car - title: Delivered description: Estimated March 15, 2026 icon: home ``` ```yaml - id: css_tailwind_bg type: Steps class: bg-bg-layout p-4 rounded-lg properties: current: 1 items: - title: Draft - title: Review - title: Published - id: css_inline_border type: Steps properties: current: 1 items: - title: Upload - title: Process - title: Complete style: .element: padding: 16px border: "1px solid #d9d9d9" borderRadius: 8 ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `current` | number | `0` | Index of the current step, counting from 0. | | `initial` | number | `0` | Starting index of the steps, counting from 0. | | `status` | string | `"process"` | Status of current step. Enum: `wait`, `process`, `finish`, `error`. | | `size` | string | `"default"` | Size of the steps. Enum: `default`, `small`. | | `type` | string | `"default"` | Type of steps. Enum: `default`, `dot`, `inline`, `navigation`, `panel`. | | `orientation` | string | `"horizontal"` | Orientation of the step bar. Enum: `horizontal`, `vertical`. | | `titlePlacement` | string | `"horizontal"` | Place title and description horizontal or vertical. Enum: `horizontal`, `vertical`. | | `maxCount` | integer | - | Maximum number of steps to show, at least 3. Hidden steps collapse into disabled ellipsis steps around the current step. | | `percent` | number | - | Progress circle percentage of current step in process status (only works with type default). | | `progressDot` | boolean | `false` | Steps with progress dot style. Same as `type: dot`. | | `variant` | string | `"filled"` | Style variant of the steps. Enum: `filled`, `outlined`. | | `responsive` | boolean | `true` | Change to vertical direction when screen width smaller than 532px. | | `items` | array | - | List of step items. | | `items.$.title` | string | - | Title of the step - supports html. | | `items.$.subTitle` | string | - | Subtitle of the step - supports html. | | `items.$.description` | string | - | Description of the step - supports html. | | `items.$.icon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to use as the step icon. | | `items.$.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `items.$.icon.color` | string | - | Icon color. | | `items.$.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `items.$.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `items.$.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `items.$.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `items.$.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `items.$.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `items.$.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `items.$.status` | string | - | Status of this step, overrides the current step status. Enum: `wait`, `process`, `finish`, `error`. | | `items.$.disabled` | boolean | `false` | Disable click on this step. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design steps tokens](https://ant.design/components/steps#design-token). | | `theme.colorPrimary` | string | `"#1677ff"` | Primary color for the active step. | | `theme.dotCurrentSize` | number | `10` | Size of the current dot in dot style. | | `theme.dotSize` | number | `8` | Size of dots in dot style. | | `theme.iconFontSize` | number | `14` | Font size of the step icon. | | `theme.iconSize` | number | `32` | Size of the step icon. | | `theme.iconSizeSM` | number | `24` | Size of the small step icon. | | `theme.iconTop` | number | `-0.5` | Top position of the step icon. | | `theme.descriptionMaxWidth` | number | - | Max width of each step description. Unset by default in antd 6, so descriptions take the width of their step. | | `theme.titleLineHeight` | number | `32` | Deprecated in antd 6 and has no effect. | | `theme.customIconSize` | number | `32` | Size of the container of a custom step icon. | | `theme.customIconFontSize` | number | `24` | Font size of a custom step icon. | | `theme.navArrowColor` | string | `"rgba(0, 0, 0, 0.25)"` | Color of the navigation arrow. | | `theme.navContentMaxWidth` | string | `"unset"` | Max width of navigation step content. | | `theme.finishIconBorderColor` | string | `"#e6f4ff"` | Border color of finished step icon. | | `theme.waitIconBorderColor` | string | `"transparent"` | Border color of waiting step icon. | | `theme.waitIconColor` | string | `"rgba(0, 0, 0, 0.65)"` | Color of waiting step icon. | | Event | Event Data | Description | | --- | --- | --- | | `onChange` | `{ current }` | Triggered when a step is clicked. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Steps element. | | `/icon` | The icon in the Steps. | | `/item` | Each step. | | `/itemTitle` | The title of each step. | | `/itemSubtitle` | The subtitle of each step. | | `/itemContent` | The description of each step. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/table.md --- # Table A virtualised, keyboard-accessible data table for record lists, reports, pickers, trees and queues: typed cells, sorting, column filters, search, resizing, reordering, pinning, grouping with aggregates, row selection with bulk actions, inline editing, saved views and server-side data in blocks through `MongoDBTableQuery`. Its value is its UI state, `{ view, selected, expanded }`, so `_state: .selected` is the selection and `_state: .view` the current view. Every TableLight config is a valid Table config, and the AgGrid blocks remain supported alongside it. ## Choosing a table block Lowdefy has four table blocks. Pick by what the table is for, not by how many features you might need one day: `TableLight` and `Table` share one column model, so a table that outgrows `TableLight` changes `type` and keeps its config. | Block | Use it for | Value | Rows | | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | [`TableLight`](/TableLight) | The odd table on a page: recent orders on a dashboard, the lines of an invoice. Typed cells, header sort, row links, row buttons, a summary row. | none | tens to hundreds; a dev warning above 1,000 | | `Table` | Record lists, reports, pickers, trees and queues: filters, search, resize, reorder, pin, grouping, selection, bulk actions, inline editing, saved views, server-side data. | `{ view, selected, expanded }` | any number (virtualised; server mode for collections too big for the browser) | | [`TableInput`](/TableInput) | Editing a list of rows in a form: invoice lines, recipe ingredients, the steps of a plan. | the changes to `data`, `{ updated, added, removed, moved?, order? }` | as `Table` (client data only) | | [`AgGrid`](/AgGrid) | Existing AG Grid tables, and AG Grid options that `Table` does not have. | `AgGrid*Input` holds the rows | any number | The AgGrid blocks are not deprecated and nothing needs to move. Column config moves over by hand when you want it to: the `cell` option names match AgGrid's `cell` keys, so lift `cell.type` to the column's `type`. `Table` takes every `TableLight` property with the same meaning. One difference: `pagination` is off by default on `Table`, which scrolls any number of rows, and on by default on `TableLight`, which shows a pager when there are more rows than `pageSize`. ## How Table works `Table` is an input block whose value is its UI state, not its data: ```yaml view: # what the user sees: columns, sort, filter, search, grouping, density columns: [{ key: name, width: 260, pinned: start }, { key: stage }, { key: amount }] sort: [{ key: amount, desc: true }] filter: { key: stage, op: in, value: [lead, qualified] } search: acme selected: [d1, d7] # the selected row keys expanded: [] # the expanded tree rows or detail rows ``` - `_state: deals.selected` is always the selection and `_state: deals.view` always the current view, with no `onChange` wiring. - Setting them sets the table: `SetState: { deals.selected: [] }` clears the selection, `SetState: { deals.view.filter: { key: stage, op: eq, value: won } }` filters it, and `Reset` returns to `defaultView`. - Rows come from `data`: a list of rows (client mode), or `{ mode: server, request }` or `{ mode: server, endpoint }` (server mode, below). - `rowKey` names the field that identifies a row: `_id` by default, then `id`. Selection, expansion, events and edits all use it, so give rows a key. Rows with neither field get a key per row object, which does not survive a refetch. The table loads its code (TanStack Table and Virtual, about 47 kB gzipped) the first time a `Table` or `TableInput` mounts, showing its loading skeleton until then (see [Loading states](#loading-states)). A page without one does not load it. ## What is on by default A `Table` with only `columns` and `data` already does what users expect of a table. Features that need your intent, a place to save something, or page chrome are one key away. | Feature | Default | Turn it off or on | | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | | Sort by header click (Shift+click adds a level) | on | `sortable: false` on a column, or `defaultColumn: { sortable: false }` | | Column resize by dragging a header edge | on | `resizable: false`, or `defaultColumn: { resizable: false }` | | Column reorder by dragging a header | on | `reorderable: false` | | Header menu: sort, filter, group, pin, freeze, autosize, hide, Columns… | on | `headerMenu: false` | | Column filters (from the header menu; the header shows an icon while one applies) | on | `filterable: false`, or `defaultColumn: { filterable: false }` | | Column manager (Columns… in the header menu) | on | follows `headerMenu` | | Sticky header | on | `stickyHeader: false` | | Virtualisation | `auto`: rows above 200, columns above 20 or wider than twice the viewport | `virtual: true` or `virtual: false` | | Keyboard navigation, focus ring, ARIA grid roles | on | `keyboard: false` (the roles stay) | | Copy with Cmd/Ctrl+C | on | always on, also with `keyboard: false` | | Text wrap | off: text stays on one line | `wrap` or `ellipsis` on a column, `view.wrap`, or the toolbar Wrap toggle (`toolbar.density`) | | Hover highlight, empty state, loading states | on | `emptyText`, the `empty` slot, `loading`, the block's `skeleton` | | Summary footer | on when a column has an `aggregate` (or the view has `aggregates`) | `summary: false`; `summaryRow` shows a row the app computed instead | | Height | grows with its rows up to `maxHeight` (600px), then scrolls | `height` for a fixed height | | Toolbar | off | `toolbar: true` (every item) or `toolbar: { search: true, ... }` | | Row selection | off | `rowSelection: { type: checkbox }` | | Grouping | off | `groupable: true` on columns | | Editing | off | `editable: true` on columns | | Pagination | off | `pagination: true` | | View persistence | off | `persist: { key }` | | Saved views, server mode | off | `views`, `data: { mode: server }` | Changes a user makes with the default features (sort, widths, order, filters, hidden columns) live in the table value for the session. `Reset` and `SetState` still control them, and they outlive a page reload only with `persist`. ## Loading states The table shows what it is doing while data loads, the same way in `Table`, `TableInput` and `TableLight`, so switching `type` looks the same. It reads one signal: `loading`. Lowdefy sets it while the block's or a parent's `onMount` actions run, and you set it with the `loading` property. | `loading` | Rows | The table shows | | --------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | true | none yet | **Initial:** the real header over skeleton rows that fill the body | | true | rows on screen | **Refreshing:** the rows stay, and a thin progress bar runs under the header | | false | none (`[]`, or `data` null or missing) | **Empty:** "No rows" (`emptyText`), or "No matching rows" with _Clear filters_ when a filter or search is set | | false | rows | **Ready** | **Empty states.** With no rows and no filter or search, blocks in the `empty` slot replace "No rows" (else `emptyText`). When a filter or search hides every row, the table says "No matching rows" and offers _Clear filters_; blocks in the `emptyFiltered` slot replace both, for a view whose filter is the point, such as a saved list, where clearing it would throw the list away: ```yaml slots: empty: blocks: - id: no_deals type: Paragraph properties: content: No deals yet. emptyFiltered: blocks: - id: no_matching_deals type: Paragraph properties: content: No deals match this view right now. - id: edit_filter type: Button properties: title: Edit filter ``` The wrapper keeps `data-lf-empty="filtered"` whenever a filter or search hides every row, whichever is shown. **Wire `loading` to the request.** A request that runs again makes `_request` return `null` until the new response lands (unless the `Request` action sets `holdValue: true`). The table holds its rows while `loading` is true, so a refetch never flashes empty, either way: ```yaml - id: deals type: Table properties: loading: _request_details: deals_list.0.loading # the latest call of the request data: _request: deals_list columns: [name, stage, amount] ``` Set `loading` as the property rather than the block's own `loading` key. Either way the blocks in the table's slots (toolbar buttons, bulk actions, the empty state) keep only their own loading: the table's loading, including a page `onMount` that fetches its rows, never disables them, so a "New deal" or refresh button stays usable while the rows load. Without `loading`, a table whose request failed or has not run yet shows its empty state, never a skeleton that never ends. A table on a page whose `onMount` fetches its data is loading until those actions finish, so it shows the skeleton meanwhile. - **Skeleton rows** are shaped like their cells, so nothing jumps when the rows land: text bars of varied width, short end-aligned bars for numbers, a circle and a bar for avatars and people, pills for tags and statuses, squares for booleans and checkboxes, one small square per button (none for `showOn: hover` buttons, which are hidden until hover anyway) and a thin bar for progress; square avatars (`shape: square`) load as squares. They fill the table's height (`height`, or `maxHeight` for a table that grows with its rows), at most a page when paginated, at the current density. A shimmer band in the theme's fill colour runs across them, as on the Skeleton block, and stops when the user prefers reduced motion. - **Timing.** The skeleton shows 120 ms after loading starts, so a fast response never flashes it, and once shown it stays at least 300 ms, so it never flickers. A key, click or focus in the table during that time shows the rows at once, so a header click or an arrow key acts on the rows. The progress bar fades in after the same 120 ms. - **View changes.** A sort, filter, search, group or view tab the user picks keeps the old rows on screen with the progress bar; if the new rows take longer than 300 ms, the old rows dim until they arrive. A background refetch or a server `refresh` never dims. - **While the first rows load** the toolbar's search, filters, views and slot blocks stay usable; Export, the select-all checkbox and the bulk bar wait for rows, and the record count shows a placeholder until it has a count (while refreshing it keeps the count of the rows on screen). The grid is `aria-busy`, and a polite live region says "Loading rows", then "N rows loaded". - **The table's own code.** `Table` and `TableInput` load their code on first mount. Until it arrives the block shows this same skeleton, built from the column config, so the swap to the table moves nothing. - **Server mode.** Rows not loaded yet (a fast scroll) are skeleton rows. An expanded group shows a spinner in its chevron and skeleton rows until its rows land. A block of rows that fails to load is one row, "Couldn't load rows · Retry": Retry loads just that block again, and the rows already loaded stay. The table shows the failure itself, so the page shows no error message for it; the error is still logged. - **Trees.** A lazy expand (`onRowExpand` with `needsChildren`) is awaited: the chevron spins and a skeleton child row shows until the children arrive. If the actions fail, the row collapses and its chevron shows the error. - **Actions.** Cell saves, row moves and TableInput edits show their own saving and error markers. Export spins while the file builds (in server mode, while the `onExport` actions run). The filter builder, column filters, column manager, header menu and cell editor open at once, with a spinner inside them the first time their code loads. **A different loading look.** A block's `skeleton` config replaces the whole table while the block is loading, as for any Lowdefy block (with the block's own `loading` key, or while `onMount` runs): ```yaml - id: deals type: Table skeleton: type: Skeleton properties: height: 320 properties: data: _request: deals_list columns: [name, stage, amount] ``` ## Table types ### Embedded table A few lines on a record page. No toolbar, no persistence; virtualisation stays off below 200 rows. ```yaml - id: line_items type: Table properties: data: _request: get_invoice.0.lines size: compact columns: - key: product - key: qty type: number width: 90 aggregate: sum - key: price type: currency cell: currency: EUR - key: total type: currency cell: currency: EUR aggregate: sum ``` This is also a valid `TableLight`. Use `TableLight` when it needs nothing more. ### Record list (CRM index view) A list of deals from a MongoDB collection, loaded in blocks as the user scrolls, with row links, a toolbar, saved views, bulk actions, hover buttons and one editable column. The requests come first, then the table. ```yaml id: deals type: PageHeaderMenu properties: title: Deals requests: # The rows. The table fires this request with { startRow, endRow, view, groupPath }; the # payload reads them from the event and the properties from the payload. - id: deals_page type: MongoDBTableQuery connectionId: deals payload: view: _event: view startRow: _event: startRow endRow: _event: endRow groupPath: _event: groupPath timezone: # date filters compare the user's days _js: 'return Intl.DateTimeFormat().resolvedOptions().timeZone;' properties: pipeline: # always runs first: the view can only narrow it - $match: org_id: _user: organization.id archived: $ne: true # The allowlist, keyed by column key. The bulk request below uses the same fields, so they # live in one file: # name: { type: text, search: true } # stage: { type: status, groupable: true } # owner: { type: avatar, path: owner.name, search: true, groupable: true } # amount: { type: currency } # updated: { type: date } fields: _ref: deals/table_fields.yaml # Rows return _id and the fields' paths only; add the paths cells read on their own. returnFields: - owner.avatar user: _user: true timezone: _payload: timezone view: _payload: view startRow: _payload: startRow endRow: _payload: endRow groupPath: _payload: groupPath # Saved views: the user's own, and the ones shared in the organization. - id: deal_views type: MongoDBAggregation connectionId: table_views properties: pipeline: - $match: table: deals org_id: _user: organization.id $or: - owner_id: _user: id - shared: true - $sort: title: 1 - $project: _id: 0 id: $_id title: 1 view: 1 shared: 1 locked: # only the owner may save over or delete a view $ne: - $owner_id - _user: id - id: save_deal_view type: MongoDBUpdateOne connectionId: table_views payload: id: _state: view_id title: _event: title shared: _event: shared view: _event: view properties: filter: _id: _payload: id owner_id: _user: id update: $set: title: _payload: title shared: _payload: shared view: _payload: view $setOnInsert: table: deals org_id: _user: organization.id options: upsert: true - id: delete_deal_view type: MongoDBDeleteOne connectionId: table_views payload: id: _event: id properties: filter: _id: _payload: id owner_id: _user: id # Inline edit of the stage column. - id: update_deal_stage type: MongoDBUpdateOne connectionId: deals payload: _id: _event: rowKey stage: _event: value properties: filter: _id: _payload: _id org_id: _user: organization.id update: $set: stage: _payload: stage updated: _date: now # A hover button. - id: archive_deal type: MongoDBUpdateOne connectionId: deals payload: _id: _event: rowKey properties: filter: _id: _payload: _id org_id: _user: organization.id update: $set: archived: true # A bulk action over the selection: a list of keys, or every row matching the view. - id: assign_deals type: MongoDBTableChanges connectionId: deals payload: selected: _state: deals_table.selected timezone: _js: 'return Intl.DateTimeFormat().resolvedOptions().timeZone;' properties: filter: org_id: _user: organization.id fields: # what the bulk write may set, keyed by field path owner.name: type: text owner.id: type: text queryFields: # the table's MongoDBTableQuery fields, to read an all-matching selection _ref: deals/table_fields.yaml user: _user: true timezone: _payload: timezone selection: _payload: selected set: owner.name: _user: name owner.id: _user: id events: onMount: - id: fetch_views type: Request params: deal_views blocks: - id: deals_table type: Table properties: height: calc(100vh - 220px) data: mode: server request: deals_page rowLink: pageId: deal urlQuery: _id: _id rowSelection: type: checkbox views: _request: deal_views activeView: _state: active_view persist: key: deals toolbar: views: true search: true quickFilters: [stage] filter: true sort: true group: true columns: true density: true defaultView: sort: - key: updated desc: true columns: - key: name title: Deal width: 260 pinned: start - key: stage type: status groupable: true editable: true options: lead: Lead qualified: label: Qualified color: processing won: label: Won color: success lost: label: Lost color: error - key: owner field: owner.name type: avatar groupable: true cell: srcField: owner.avatar # returned through returnFields - key: amount type: currency aggregate: sum - key: updated type: date cell: relative: true - key: actions type: buttons title: '' width: 56 pinned: end sortable: false filterable: false cell: showOn: hover buttons: - eventName: onArchive title: Archive icon: Archive hideTitle: true slots: toolbarStart: blocks: - id: new_deal type: Button properties: title: New deal icon: add size: small events: onClick: - id: go_new type: Link params: pageId: new-deal bulkActions: blocks: - id: assign_to_me type: Button properties: title: Assign to me size: small events: onClick: - id: assign type: Request params: assign_deals - id: refresh type: CallMethod params: blockId: deals_table method: refresh - id: clear type: CallMethod params: blockId: deals_table method: clearSelection events: onCellEdit: - id: save_stage type: Request params: update_deal_stage onArchive: - id: archive type: Request params: archive_deal - id: drop_row type: CallMethod params: blockId: deals_table method: applyTransaction args: - remove: - _event: rowKey onViewSelect: - id: remember_view type: SetState params: active_view: _event: id onViewSave: # Save passes the active view's id; Save as passes none, so it gets a new one. - id: view_id type: SetState params: view_id: _if_none: - _event: id - _uuid: true - id: save_view type: Request params: save_deal_view - id: refetch_views type: Request params: deal_views - id: select_saved type: SetState params: active_view: _state: view_id onViewDelete: - id: delete_view type: Request params: delete_deal_view - id: refetch_after_delete type: Request params: deal_views ``` The connections are `MongoDBCollection` connections to the `deals` and `table_views` collections with `write: true`. What each part does: - **Server mode.** The table fetches rows 0 to 200 on mount, then each block of `blockSize` rows (default 200) as it scrolls into view, keeping at most `maxBlocks` blocks (default 20). A sort, filter, search or grouping change fetches from the top, and the old rows stay on screen, dimmed, until the new ones land. See [MongoDBTableQuery](/MongoDB) for the request, and Security below. - **Row links.** A plain click opens the deal, Cmd/Ctrl+click or a middle click opens it in a new tab, and Enter on a focused row follows the link. Clicks on buttons, links, menus and editors in a cell never trigger the row. A click on a block in one of the table's slots, such as "Assign to me", runs that block's event and not the table's (the innermost block with actions handles a click, unless an event sets `bubble: true`), and the table still fires its own events for the methods that button calls, such as `onSelectionChange` after `clearSelection`. - **Saved views.** The tabs show `views`. A view `id` is a string, a number or a MongoDB ObjectId (`{ _oid }`, matched by its hex, so `id: $_id` works too); this example uses string ids so that Save as can pick one before the request runs. Selecting a tab loads its `view` (parts it leaves out come from `defaultView`) and fires `onViewSelect`. When the current view differs from the tab's, a strip offers Save, Save as and Discard; Save fires `onViewSave` with the tab's `id`, Save as asks for a title (and whether to share it) and fires `onViewSave` without an `id`; a table with `onViewSaveAs` actions fires that instead (see below). A tab's menu offers Delete, which fires `onViewDelete`; `viewMenu` replaces it with the app's own items (see below). The table never stores views itself: update `views` and, for a new view, `activeView`. `locked: true` hides Save and disables the default Delete for that tab. Changing the search or collapsing groups never marks a view as changed. On a phone (below 768px, antd's `md` breakpoint) the tabs are one select showing the active view, with its menu and the changed-view strip beside it. - **Quick filters.** `quickFilters: [stage]` puts a Stage chip in the toolbar. A column with `options` opens a checkbox list that writes one `in` condition, ANDed with the rest of the filter. A list of more than 8 options has a search box at its top that narrows the list by label as the user types (case-insensitive); ticked options stay listed, and Escape clears the search. A column without `options` opens its column filter. - **`persist`.** Keeps the user's current view and active tab in `localStorage` under `lowdefy-table:deals`, per app and per signed-in user, so a returning user sees what they left. The view is cleared when the user signs out. A persisted view wins over `activeView` when the page loads. - **Bulk actions.** While rows are selected, a bar below the table shows "N selected", "Select all M matching", Clear, and the `bulkActions` slot. Blocks in the toolbar and bulk action slots sit side by side at their content width, like the table's own buttons (set `gap` on the slot, or `layout.flex` on a block, to change that). The blocks in it read `_state: deals_table.selected`, and "Assign to me" sends it to `assign_deals`, one `updateMany` (see below). - **Hover buttons.** `showOn: hover` shows the buttons only on the hovered row, or the row focused with the keyboard. They mount only there, which keeps scrolling fast. The event carries `{ row, rowKey, value, button, buttonIndex }`. `applyTransaction` removes the archived row without a full refetch; in server mode a remove also refetches the visible rows. - **Inline editing.** Double-click, Enter, F2 or typing on a stage cell opens a select of its `options`. The committed value shows at once with a saving marker while `onCellEdit` runs. If an action fails (a request error, or a `Throw`), the cell reverts and shows the error message. After success the new value shows until the row changes in `data`. **Bulk assign to every matching row.** In server mode most rows are not in the browser, so the header checkbox, and "Select all M matching" in the bulk bar, select every row the view matches as `{ all: true, except, filter, search }`: the view's filter and search, and the keys the user unticked since. Changing the filter or search clears such a selection, so it always describes the rows the user sees. `MongoDBTableChanges` with `selection` (bulk mode) resolves either shape on the server: a key list becomes `{ _id: { $in: [...] } }`, and an all-matching selection is validated and compiled against `queryFields` exactly as `MongoDBTableQuery` compiles the view, with `{ _id: { $nin: except } }`. The base `filter` is one clause of the `updateMany` filter, so a crafted selection can only narrow it, and `set` / `unset` can write only `fields`, never a scope field. The response is `{ matchedCount, modifiedCount }`. `fields` and `queryFields` are different key spaces: `queryFields` are the `MongoDBTableQuery` fields keyed by column key (`owner`, with `path: owner.name`), `fields` are keyed by the field path written (`owner.name`). Keep the query fields in one file and `_ref` it from both requests. To edit more columns, give each one its own request and pick it with the column key: `params: { _string.concat: [update_deal_, { _event: column.key }] }`. Do not build the `$set` field name from the event: the browser could then write any field. Alternatively, save the edit with [MongoDBTableChanges](/MongoDB), which checks the field against its `fields` allowlist and coerces the value to the field type. **Your own Save as dialog.** Give the table `onViewSaveAs` actions and Save as fires `onViewSaveAs { view }`, the current view, in place of its title and Shared popover, so saving a new view can ask for more: a unique name, a description, a preview of how many rows it matches. The table shows no popover; the app opens its dialog, stores the view and then updates `views` and `activeView`, as after `onViewSave`. Save (with an `id`) still fires `onViewSave`. Without `onViewSaveAs` actions, Save as works as above. ```yaml - id: deals_table type: Table events: onViewSaveAs: - id: keep_view type: SetState params: new_view: view: _event: view title: '' description: '' - id: open_save_view type: CallMethod params: blockId: save_view_modal method: toggleOpen - id: save_view_modal type: Modal properties: title: Save view events: onOk: - id: insert_view type: Request params: insert_view # stores new_view, then refetches views and sets active_view blocks: - id: new_view.title type: TextInput required: true - id: new_view.description type: TextArea ``` **Your own view tab menu.** `viewMenu` sets the items of the active view tab's menu in place of Delete view, for example when views are archived rather than deleted because other features read them. Each item is `{ key, label?, danger?, confirm? }`; picking one fires `onViewMenu { key, id }` with the view's `id`. `confirm: true` asks "Are you sure?" first, and a string asks that message. `locked` does not disable these items. An empty `viewMenu` hides the menu. ```yaml viewMenu: - key: rename label: Rename - key: duplicate label: Duplicate - key: archive label: Archive danger: true confirm: Archive this view? Lists that use it keep working. ``` ### Peek drawer A click opens the row in a drawer; Cmd/Ctrl+click still follows `rowLink`. ```yaml - id: deals_table type: Table properties: data: _request: deals rowLink: pageId: deal urlQuery: _id: _id columns: - key: name title: Deal - key: stage type: tag - key: amount type: currency events: onRowClick: - id: set_peek type: SetState params: peek: _event: row - id: open_peek type: CallMethod params: blockId: peek_drawer method: setOpen args: - open: true - id: peek_drawer type: Drawer properties: title: _state: peek.name blocks: - id: peek_amount type: Statistic properties: title: Amount value: _state: peek.amount ``` For a page with a [`path`](/page-paths), give its placeholder values in `pathParams`, also as paths in the row: `rowLink: { pageId: ticket, pathParams: { space: space_id, ticket_id: _id } }`. Table links are block properties, so on a public page a link to a protected page with a path cannot be built while the user is logged out (see [`Link`](/Link)). With `onRowClick` defined, a plain click runs it instead of following `rowLink`. `onRowClick` receives `{ row, rowKey, index }`; `index` is the row's position in `data`, whatever the sort (with `tree.childrenField`, in the depth-first list of every row). In server mode it is the row's index in the rows the request matches (inside a group, in the group), and it is `null` for a row that is not in `data`, such as one added with `applyTransaction`. ### Picker Choose rows and read the selection from state. No events needed. ```yaml - id: pick_contacts type: Table properties: data: _request: contacts height: 360 rowSelection: type: checkbox preserve: true # keep keys of selected rows that a filter or search hides toolbar: search: true columns: - key: name type: avatar cell: nameField: name - key: email type: email - id: add_to_campaign type: Button properties: title: _string.concat: - 'Add ' - _array.length: _if_none: - _state: pick_contacts.selected - [] - ' contacts' events: onClick: - id: add type: Request params: add_contacts # payload: { ids: { _state: pick_contacts.selected } } ``` - `type: radio` picks one row; `selected` is then a list of at most one key. - Space on a focused row toggles it, and Cmd/Ctrl+C copies the selected rows as tab-separated text. - `onSelectionChange` fires with `{ selected, rows }` when the selection changes; `rows` are the selected row objects. - "Select all M matching" in the bulk bar writes `{ all: true, except: [], filter, search }` in client mode too: every row matching the view's filter and search, except the unticked keys. Rows that arrive later and match are selected too, and a filter or search change clears the selection. A picker that needs a list of keys can read the selected row objects from `onSelectionChange` `rows`, or handle both shapes. ### Grouped report with totals ```yaml - id: sales_report type: Table properties: data: _request: sales toolbar: group: true columns: true export: true defaultView: group: - key: region - key: rep aggregates: deals: count columns: - key: region groupable: true - key: rep groupable: true - key: deals field: name title: Deal - key: revenue type: currency aggregate: sum - key: margin type: percent aggregate: avg - key: closed type: date aggregate: latest ``` - Only columns with `groupable: true` group. The Group toolbar button, the header menu ("Group by this column") and the `setGroup` method set `view.group`. - Groups follow the sort on their column, else the column's `options` order, else first appearance. Empty values group as "(Empty)", last. - Each group header shows its count and, under their columns, the aggregates in effect: the view's `aggregates` over each column's own `aggregate` (`null` in the view turns a column's default off). The summary footer shows the same aggregates over every filtered row. - The current group header sticks to the top while its rows scroll. - Collapsed groups are `view.collapsedGroups`, the JSON of the group's value path: `'["EMEA"]'`, `'["EMEA","Ada"]'`, with `null` for empty values. The `expandAllGroups` and `collapseAllGroups` methods open and close every group. - A group's checkbox (with `rowSelection`) selects its rows. Space on a group header does the same. - `exportCsv` exports every row in the current order, including the rows of collapsed groups. A cell that starts with `=`, `+`, `-`, `@`, a tab or a carriage return gets a leading `'`, so a spreadsheet opens it as text rather than running it as a formula (CSV injection); plain numbers such as `-12.5` stay numbers. Aggregates: `sum`, `avg`, `min`, `max`, `count`, `countDistinct`, `countEmpty`, `countNotEmpty`, `percentEmpty` (a fraction), `earliest` and `latest`. In client mode `min` and `max` follow the column's sort order, so they work on text and enum columns too. `MongoDBTableQuery` allows `sum` and `avg` on numeric fields, `min` and `max` on numeric and text fields (text compared by code point unless `options.collation` sets a locale), `earliest` and `latest` on dates, and `countDistinct` only on fields with `groupable: true`. Server mode groups the same way through `MongoDBTableQuery`: the first request returns the groups with their counts and aggregates, and a group's rows load when it is opened. Server groups start collapsed, open one at a time, and have no group checkbox. #### A total row from the request Some totals can not be computed from the rows: a response rate's total is the total responses over the total invited, not the sum or average of the rows' rates. Compute the row in the request and pass it as `summaryRow`: ```yaml - id: outcomes type: Table properties: data: _request: outcomes.0.rows summaryRow: _request: outcomes.0.total columns: - key: region - key: invited type: number - key: rate type: percent ``` - The footer shows the row in place of the aggregates. Each column renders it as it renders a body row, with its `type`, `cell`, `rules` and tooltip; action columns (`buttons`, `menu`) stay empty. Group headers keep their aggregates. - It is not a data row. Sorting, filtering, search, selection and the record count leave it out, and it stays in view under `height` like the aggregate footer. - `exportCsv` adds it as the last line. In server mode the app writes the file in `onExport`, so the app adds it there. - `summary: false` hides it, as it hides the aggregates. ### Tree Rows nested under a parent, with a chevron in the first column. ```yaml - id: accounts type: Table properties: data: _request: accounts # flat rows with a parent_id tree: parentField: parent_id # or childrenField: children for nested rows rowSelection: type: checkbox cascade: true # selecting a row selects its descendants defaultView: sort: - key: name columns: - key: name width: 260 - key: balance type: currency ``` - Give exactly one of `childrenField` (nested rows) or `parentField` (flat rows that name their parent's key). Rows whose parent is not in `data`, and rows in a cycle, are roots. - Right expands a row and Left collapses it (or moves to its parent). The expanded row keys are the `expanded` part of the value, so `SetState: { accounts.expanded: [assets] }` opens a row. - A sort sorts within each level. A filter or search keeps the ancestors of matching rows. - Trees are client mode only, and are never grouped. Rows in a tree cannot be dragged. Load children on demand with `lazy`: a row whose `hasChildrenField` (default `hasChildren`) is true shows a chevron before its children are in `data`, and expanding it fires `onRowExpand { row, rowKey, expanded, needsChildren }`. `needsChildren` is true only when that row is expanded and none of its children are in `data` yet, so skip the load on every other expand and collapse. A row whose request returns no children asks again on its next expand. ```yaml - id: folders type: Table properties: data: _state: folder_rows tree: parentField: parent lazy: true columns: - key: name events: onRowExpand: - id: load_children type: Request params: child_folders # payload: { parent: { _event: rowKey } } skip: _not: _event: needsChildren - id: add_children type: SetState skip: _not: _event: needsChildren params: folder_rows: _array.concat: - _state: folder_rows - _request: child_folders ``` ### Queue A triage list: status tabs with counts, single-key actions, and focus that moves on to the next row after each one. ```yaml - id: tickets_queue type: Table properties: data: _request: tickets height: 520 keyboard: next: true toolbar: views: true search: true views: - id: open title: Open locked: true count: _request: ticket_counts.0.open view: filter: key: status op: eq value: open - id: mine title: Assigned to me locked: true count: _request: ticket_counts.0.mine view: filter: and: - key: status op: eq value: open - key: assignee op: eq value: $user: id user: _user: true columns: - key: subject width: 320 - key: assignee field: assignee.id hidden: true - key: priority type: tag options: [urgent, high, normal] - key: created type: datetime cell: relative: true - key: actions type: buttons title: '' width: 190 sortable: false cell: buttons: - eventName: onAssign title: Take (a) key: a - eventName: onClose title: Close (c) key: c events: onAssign: - id: assign type: Request params: assign_ticket # payload: { _id: { _event: rowKey } } - id: refetch type: Request params: [tickets, ticket_counts] onClose: - id: close type: Request params: close_ticket - id: refetch_closed type: Request params: [tickets, ticket_counts] ``` - A button or menu item with `key: a` fires when `a` is pressed on its focused row: the first shown and enabled one with that key, with the same payload as a click. Keys are case-sensitive, and only a bare key counts (no Cmd, Ctrl or Alt). On an editable cell typing opens the editor instead. - With `keyboard.next`, focus moves to the row that followed once the event's actions finish, even when the action removed the row from the list. A failed action keeps focus where it was. - `count` on a view shows on its tab. The table does not count rows itself: `ticket_counts` is any request that returns the counts. - `{ $user: id }` in a filter reads the table's `user` property, set to `{ _user: true }`. In server mode the request resolves `$user` on the server instead (see Security). ### Editable list in a form `TableInput` edits rows that belong to a form. Its value is never the rows, only the changes made to them, so a long list keeps a small state and saves with one request. See [TableInput](/TableInput) for the full value and every editing key. The ingredients of a recipe, stored as an array in the recipe document and ordered by a `position` field: ```yaml id: recipe type: PageHeaderMenu requests: - id: get_recipe type: MongoDBAggregation connectionId: recipes payload: recipe_id: _url_query: id properties: pipeline: - $match: _id: _payload: recipe_id org_id: _user: organization.id - $project: title: 1 items: $sortArray: input: $items sortBy: position: 1 - id: save_items type: MongoDBTableChanges connectionId: recipes payload: recipe_id: _url_query: id changes: _state: items properties: array: documentId: _payload: recipe_id path: items filter: org_id: _user: organization.id positionField: position fields: ingredient: type: text qty: type: number unit: type: tag changes: _payload: changes events: onMount: - id: fetch type: Request params: get_recipe blocks: - id: items type: TableInput properties: data: _request: get_recipe.0.items addRow: true addRowText: Add ingredient rowDrag: positionField: position rowActions: delete: true defaultView: sort: - key: position columns: - key: ingredient width: 260 editable: true validate: - pass: op: notEmpty message: Name the ingredient. - key: qty type: number width: 110 editable: true default: 1 - key: unit type: tag editable: true default: g options: [g, ml, pcs] - id: save type: Button properties: title: Save events: onClick: - id: save_items type: Request params: save_items # Rows the save named but did not find (another user deleted them, or they are outside # the filter) are in unmatchedKeys: nothing was written for them. - id: check_saved type: Throw params: throw: _gt: - _array.length: _if_none: - _request: save_items.unmatchedKeys - [] - 0 message: Some ingredients were changed or removed by someone else. Reload the recipe and try again. - id: refetch type: Request params: get_recipe - id: reset type: CallMethod params: blockId: items method: resetChanges ``` - Editing a cell writes `updated: { : { : value } }`, only the changed fields; an edit back to the original value drops out. "+ Add ingredient" appends a row with each column's `default` (and a position after the last row) to `added` and opens its first editable cell. Its `rowKey` is always a new temporary key: a `default` on the key column is ignored, so added rows never share a key. The delete button (and Delete or Backspace with `deleteRows: true`) adds the key to `removed`. - `rowDrag.positionField` gives a dragged row one new position (with `pagination`, positions and indices count across every page), the midpoint between its new neighbours, in `moved: { : position }`. A move is one field on one item. Only when two neighbours are closer than 1e-6 is the list renumbered in steps of 1024, which reports every changed position. Dragging is blocked while the table is sorted by anything but the position field ascending, filtered or grouped; the handle says why. Alt+Shift+Up/Down moves the focused row. - In array mode `MongoDBTableChanges` compiles the changes to `$set` with `arrayFilters` on the changed items, `$pull` for removed ones and `$push` for new ones (new items get an ObjectId `_id`). Only the `fields` listed can be written, values are coerced to the field type, and the base `filter` scopes the document. - Treat a non-empty `unmatchedKeys` in the response as a failed save: those rows matched nothing, so nothing was written for them (the rest of the save was). The `Throw` stops the actions and shows the message; the changes stay in the table. - After saving, refetch `data` and call `resetChanges`, which clears the changes and the undo history. Collection mode, where every row is a document: leave out `array`. The changes compile to one `bulkWrite` of `updateOne` (the changed dot paths and the position, in one `$set`), `insertOne` and `deleteOne`, each scoped by the base `filter`. The `filter` and `insertDefaults` fields are the rows' scope, and the table can never write them: `fields` that name them are refused, new rows are stamped with the `filter`'s equality conditions (here `org_id`), and a row value can never override an `insertDefaults` value: ```yaml - id: save_contacts type: MongoDBTableChanges connectionId: contacts payload: changes: _state: contacts_input properties: filter: org_id: _user: organization.id fields: name: type: text email: type: email role: type: tag insertDefaults: # org_id comes from the filter created: _date: now created_by: _user: id changes: _payload: changes ``` The `fields` keys are the `TableInput` column `field` paths, the keys of the changes, not the column keys `MongoDBTableQuery` uses. Numeric row keys match in both forms (a key `5` arrives as `"5"` in `updated` and matches a document `_id` of `5` or `"5"`); set `rowKeyType` if a collection holds both as different rows. Without a `positionField`, `rowDrag: true` records the full key `order` after a move instead. Array mode applies it by reordering the items on the server; collection mode needs a `positionField` to save an order. The response's `insertedKeys` maps each added row's temporary key to the key it got. See [MongoDBTableChanges](/MongoDB) for the response, row key types and limits. ## Columns A column is an object, or just its key: ```yaml columns: - name # key and field "name", title "Name" - key: owner_name field: owner.name # a dot path into the row title: Owner - key: amount type: currency cell: currency: EUR width: 140 aggregate: sum ``` | Key | Meaning | | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `key` | The column id, unique in the table, used in the view, filters, events and `MongoDBTableQuery` `fields`. Defaults to `field`. Two columns showing the same field need their own keys. | | `field` | Dot path of the value in each row. Defaults to `key`. `TableInput` records edits under it, and `MongoDBTableChanges` `fields` are keyed by it. | | `title`, `headerTooltip` | Header text and a header tooltip; both support html. The title defaults to the key in sentence case. | | `type`, `cell` | The cell type (below) and its options. | | `width`, `minWidth`, `maxWidth`, `flex` | Pixels (default width 160, minimum 48). `flex` grows the column into spare width by that weight. | | `align` | `start`, `center` or `end`; number, currency and percent default to `end`. | | `pinned` | `start` or `end`: stays in view while the table scrolls sideways. | | `wrap`, `ellipsis` | Text stays on one line by default. `wrap: true` wraps it, `ellipsis: 2` clamps it to two lines with the full text on hover; both make rows as tall as their content. | | `hidden` | Declared but hidden: users can show it from the column manager. | | `sortable`, `filterable`, `resizable`, `groupable`, `editable` | Per-column switches, defaulting to `defaultColumn` (sortable, filterable and resizable on; groupable and editable off). `editable` also takes `{ when: }`. | | `searchable` | Include the column in the search. When any column sets it, search reads only those; otherwise every visible column. | | `aggregate` | The column's default aggregate for group headers and the summary footer. | | `options` | Labels and colours for enum values (below). | | `tooltip` | A plain-text hover tooltip: a nunjucks template with `value` and `row`, `{ template }` or `{ field }`. | | `rules` | Conditional formatting (below). | | `validate`, `required`, `default` | Editing: checks before an edit commits, and a `TableInput` new row's value (ignored on the key column, which always gets a new temporary key). | | `children` | Columns grouped under a shared header (the group needs a `title`). | | `collapsible`, `collapsed` | On a group: a chevron in its header collapses and expands it, and `collapsed: true` starts it collapsed (below). | | `showWhen` | In a collapsible group: `open`, `closed` or `always` (the default), when the column or inner group shows (below). | `columns` is data, so it can come from a request: `columns: { _request: deal_fields }`. Columns added to config later appear in stored views as hidden, and unknown keys in a stored view are dropped. ### Collapsible column groups A wide report can open narrow: a group with `collapsible: true` collapses to a summary column and expands to its breakdown columns from the chevron in its header. ```yaml columns: - key: project - key: outcomes title: Outcomes collapsible: true collapsed: true children: - key: total type: number showWhen: closed - key: completed type: number - key: opted_out type: number showWhen: open - key: not_reached type: number showWhen: open ``` - A collapsed group shows its children with `showWhen: closed` or `always`; an expanded one those with `open` or `always`. Here the table opens with Project, Total and Completed, and the chevron swaps Total for Opted out and Not reached. - `showWhen` follows the nearest collapsible group above it. An inner group with `showWhen: open` hides with every column under it while the outer group is collapsed, and can be collapsible itself. - A collapsible group needs a child that shows when it is collapsed, so its header and chevron stay on the table. - The collapsed groups are the view's `collapsedColumnGroups`, by group `key` (without one, `group:`). They are saved and persisted with the view, and `SetState` on it collapses and expands groups. - A column its group hides is not displayed, searched or exported, and stays visible in the view. The column manager lists it as unavailable rather than letting the user tick it on. ### Cell types The type decides how a value renders, sorts, filters, aggregates, exports and edits. | `type` | Shows | `cell` options | Sorts as | Editor | | ------------------------------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | --------------------------- | | `text` | the value (the default) | | text | text input | | `number`, `currency`, `percent` | `Intl.NumberFormat` text; `percent` shows 0.25 as 25% | `format` (`number`, `currency`, `percent`, `compact`), `locale`, `currency` (default `USD`), `currencyDisplay`, `decimals`, `minDecimals`, `maxDecimals`, `notation`, `useGrouping`, `negative` (`parentheses`), `prefix`, `suffix`, `signColor`, `positiveColor`, `negativeColor`, `zeroColor`, `color`, `thresholds` + `colors` | number | number input | | `date`, `datetime` | a dayjs format (default `YYYY-MM-DD` / `YYYY-MM-DD HH:mm`), or relative time with the date on hover | `format`, `relative` | time | date picker | | `boolean` | a label or an icon | `trueLabel`, `falseLabel`, `trueColor`, `falseColor`, `trueIcon`, `falseIcon` | false before true | switch | | `tag`, `status` | a tinted tag, or a dot and a label | `colorMap`, `colorFrom`, `default` | option order, else text | select from `options` | | `tags` | a list of tags: the ones that fit the column whole, then a +N count (`max` caps how many show) | `colorMap`, `colorFrom`, `default`, `max` | text | multi-select from `options` | | `avatar` | initials or an image, and the name; `descriptionField` adds a second line (needs default or comfortable rows)| `nameField`, `srcField`, `idField`, `descriptionField`, `shape`, `link` | text | none | | `people` | overlapping avatars with a +N count | `nameField`, `srcField`, `idField`, `shape`, `max` (default 3) | text | none | | `link` | a link that navigates by itself | `pageId`, `href`, `urlQuery`, `pathParams`, `newTab`, `home`, `back`, `input`, `labelField` | text | none | | `email`, `phone`, `url` | `mailto:`, `tel:` and external links | `label`, `labelField`, `newTab` (url) | text | text input | | `relation` | related records as chips linking to their page | `labelField`, `pageId`, `href`, `urlQuery`, `pathParams`, `newTab` | text | none | | `progress` | a bar | `max` (default 100), `suffix`, `color`, `thresholds`, `colors`, `showValue`, `nullLabel` | number | none | | `rating` | stars | `max`, `color` | number | stars | | `image` | a lazy thumbnail | `width`, `height`, `shape`, `alt`, `altField` | text | none | | `html` | a nunjucks template, or a field holding HTML | `template` | text | none | | `json` | a one-line preview | | its JSON text | none | | `buttons` | row buttons | `buttons`, `showOn` | not sortable | none | | `menu` | a row menu | `items`, `icon`, `title`, `placement` | not sortable | none | - Values in `urlQuery`, `pathParams` and every `...Field` option are paths in the row. For `people` they are paths in each person, and for `relation` paths in each related record, where `urlQuery` defaults to `{ _id: _id }` when neither `urlQuery` nor `pathParams` is set. - A `url` value with a scheme other than http or https, such as `javascript:`, shows as text. - An `html` cell's template gets `value` and `row` and is compiled once per column. Values are escaped: use `| safe` to insert HTML held in a field. The output is sanitised, and `data-event` attributes fire block events. - `buttons` and `menu` cells hold no data: they are left out of filters, search and exports. Each button or menu item fires the block event named by its `eventName` with `{ row, rowKey, value, button: { eventName, title }, buttonIndex }` or `{ row, rowKey, value, item: { eventName, title }, itemIndex }`. The keys match the AgGrid buttons and menu cells: `title` / `titleField`, `icon` / `iconField`, `hidden` / `hiddenField`, `disabled` / `disabledField`, `type`, `variant`, `color`, `size`, `shape`, `danger`, `ghost`, `hideTitle`, `tooltip`, `iconPlacement`, and `key` (a single-key shortcut). `hidden` and `disabled` also take `{ when: }`. An icon-only button shows its title as a tooltip. `showOn: hover` shows the buttons only on the hovered row or the row focused with the keyboard (touch screens always show them). - A `link` cell navigates by itself and also fires `onCellLink { link, row, value }`; do not add a `Link` action to that event. ### Options: labels and colours for enum values `options` gives the labels and colours of `tag`, `tags` and `status` values, the choices of their filters and editors, and the labels of their group headers and exports. It takes a list of values or `{ value, label, color, icon }`, or a map from value to a label or `{ label, color, icon }`: ```yaml - key: stage type: status options: lead: Lead qualified: { label: Qualified, color: processing } won: { label: Won, color: success } lost: { label: Lost, color: error } ``` Colours are the antd presets (`blue`, `green`, `gold`, ...), the status names (`success`, `processing`, `warning`, `error`) or any CSS colour; the presets and status names follow the theme and dark mode. Enum columns sort and group in option order. A value without an option, in a column without `options` or colour keys, gets a colour picked from the value, so the same value always has the same colour. `options` can come from a global: `options: { _global: enums.deal_stages }`. ## Conditions One condition language serves filters (`view.filter`, the filter builder, quick filters), `rules`, `rowRules`, `editable.when`, `expandable.rowExpandable.when`, button and menu `hidden` / `disabled`, and `validate`. A condition is a leaf `{ key, op, value }`, or a group `{ and: [...] }` / `{ or: [...] }` of conditions, nested to any depth (the filter builder edits three levels). ```yaml filter: and: - { key: stage, op: in, value: [lead, qualified] } - or: - { key: owner, op: eq, value: { $user: id } } - { key: amount, op: gte, value: 10000 } - { key: created, op: within, value: { last: 30, unit: day } } ``` `key` names a column (its type picks the comparison), or, when no column has that key, a field path in the row. In a column's `rules`, `validate` and `editable.when`, a leaf without `key` tests the column's own value. | Operators | For | | ------------------------------------------------------------------------------ | ------------------------------------------------------------------ | | `eq`, `ne`, `in`, `nin`, `empty`, `notEmpty` | every type | | `contains`, `notContains`, `startsWith`, `endsWith` | text, email, phone, url, link, html, relation, tag, status, avatar | | `gt`, `gte`, `lt`, `lte`, `between` (`value: [from, to]`) | number, currency, percent, progress, rating | | `before`, `after`, `between`, `within` | date, datetime | | `isTrue`, `isFalse` | boolean | | `in` (has any of), `nin` (has none of), `contains` (has the value), `eq`, `ne` | tags, people | - Text comparisons ignore case. `empty` matches null, a missing value, `''` and `[]`. - Numbers compare as numbers, so numeric strings work. - A `date` column compares whole days: `eq: 2026-03-01` is that day, and `lte: 2026-03-01` includes all of it. A `datetime` column compares the exact instant for `before`, `after` and `between`; `eq` compares the day for both. - `within` takes `{ last: n, unit }` or `{ next: n, unit }` with unit `day`, `week`, `month` or `year`: from the start of the day n units ago to the end of today, or from the start of today to the end of the day n units ahead. - `before` and `after` take a date or the same relative date, resolved each time the filter runs, so a saved view stays true as time passes: `{ key: last_contacted, op: before, value: { last: 3, unit: month } }` reads "before 3 months ago" and keeps rows dated before the start of that day, and `after` with `{ next: 2, unit: week }` keeps rows dated after the end of the day 2 weeks from now. A relative bound compares whole days on `date` and `datetime` columns alike. The filter builder offers Date or Relative for both, and a date column's filter menu picks `within`, `before` or `after` under Relative. - `isFalse` matches every value that is not `true`, including empty values. - Records in a list (`people`, `relation`) compare by `_id`, then `id`, `value`, `name` or `label`, and text operators read their label. - `{ $user: path }` as a value reads the table's `user` property, which blocks need because they do not see the session: set `user: { _user: true }`. A shared saved view with `{ $user: id }` then means "mine" for every user. In server mode the filter goes to the request, which resolves `$user` on the server from its own `user` property. **The filter in words.** The toolbar's Filter button shows the filter as one sentence in its tooltip, built from the column titles and option labels, for example "Stage is any of Lead or Won and Name contains CX and (Amount greater than 1000 or Owner has me)". Groups past the first level go in brackets, relative dates read "before 3 months ago", `$user` values read "me", and a key that names no column reads "an unknown field, ,", so a view whose column was removed is still explained. Conditions still being built are left out. The sentence comes from `describeCondition({ condition, fields })` in `@lowdefy/helpers`, a plain function with no React; `MongoDBTableQuery` returns the same sentence as `sentence` when its `fields` carry the same `title` and `options`. ### Conditional formatting ```yaml rowRules: - when: { key: overdue, op: isTrue } className: row-overdue columns: - key: score type: number rules: - when: { op: gte, value: 8 } color: success - when: { op: lt, value: 5 } color: error style: { fontWeight: 600 } ``` Every rule whose `when` holds applies, in order: class names add up, and a later rule's `color` and `style` win. `color` is a text colour (the status names follow the theme). A rule without `when` always applies. Relative dates in rules are resolved when the column config compiles, not re-evaluated as time passes. ## The view The view is one serialisable object, the `view` part of the value: | Key | Holds | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `columns` | `[{ key, width, pinned, hidden }]` in display order | | `collapsedColumnGroups` | keys of collapsed column groups (`collapsible` groups) | | `sort` | `[{ key, desc }]`, outermost first | | `filter` | a condition, or null | | `search` | the search text: rows match when every word appears (ignoring case) in the display text of the searched columns | | `group` | `[{ key }]` of groupable columns, outermost first | | `collapsedGroups` | keys of collapsed row groups | | `aggregates` | `{ : \| null }` over the columns' own `aggregate` | | `density` | `compact` (32px rows), `default` (40px) or `comfortable` (52px); starts from `size` | | `wrap` | `true` wraps the text of text-like columns (text, email, phone, url, link, html, relation) without their own `wrap` or `ellipsis`; rows grow to their content. The toolbar density control has a Wrap toggle | | `pageSize` | rows per page with `pagination`; defaults to the `pageSize` property | - `defaultView` sets the initial view and the one `Reset` returns to. Each part missing from the value falls back to `defaultView`, then to the column defaults. `defaultView.columns` entries merge over the column config, and columns it leaves out keep their defaults. - While the column layout equals the configured one, the derived view leaves `columns` out, so columns added to config keep appearing until the user changes the layout. - `SetState` on any part loads it: `SetState: { deals.view.sort: [{ key: amount, desc: true }] }`. So do the methods `setFilter`, `clearFilters`, `setSearch` and `setGroup`. - `openFilter` opens the filter builder from a button elsewhere on the page (a list header's Edit filter, an `emptyFiltered` slot), anchored to the toolbar's Filter button as a click on it does: `CallMethod: { blockId: deals, method: openFilter }`. With `args: [{ key: stage }]` it first adds an empty condition on that filterable column and focuses it. It needs `toolbar.filter`; without it the method logs a warning and does nothing. On a phone (below 768px) the toolbar's filter builder opens as a full-height sheet, with the record count at its foot, instead of a popover. - `onChange` fires after the user changes the view or the selection, with `{ value, cause }`; `cause` is `sort`, `filter`, `search`, `columns`, `select`, `group`, `aggregate`, `expand`, `density`, `wrap` (the toolbar Wrap toggle) or `view` (a saved view loaded). It does not fire for `SetState`, `Reset` or the initial value. - To share a view in a link, `persist: { key: view, storage: url }` writes a compact encoding of the view to the `view` query parameter as it changes (with `history.replaceState`), and reads it on load. `persist: { key, storage: local }` (the default storage) keeps the view in `localStorage` under `lowdefy-table:`, per app and per signed-in user, and clears it at sign-out or when another user signs in on the browser; a private window or blocked storage just means no persistence. The selection is never persisted. Persisted views outlive config changes (unknown keys are dropped and new columns appended hidden), which is why persistence is opt-in. A saved view can carry `wrap` and `pageSize` like any other part: `defaultView: { pageSize: 25 }` or a view tab with `wrap: true`. There is no page size picker; set `view.pageSize` with `SetState` to change it. ## Keyboard Click a cell, or Tab into the table, and the focused cell shows a ring. | Key | Does | | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Arrow keys | Move between cells (Up from the first row reaches the header) | | Home / End | First / last cell of the row | | Cmd/Ctrl+Home / End | First / last cell of the table | | PageUp / PageDown | Up or down by a screen of rows | | Enter | On a header: sort (Shift+Enter adds a level). On a row: follow `rowLink` or fire `onRowClick`. On an editable cell: edit. On a group header: open or close it | | Space | Toggle the row's checkbox (or a group's rows, on a group header) | | Right / Left | In a tree or on a group header: expand / collapse | | Enter, F2, or typing | Open the cell editor (typing starts with that character) | | Enter, Tab, Shift+Tab | In an editor: commit (Tab moves to the next editable cell and opens it) | | Esc | In an editor: cancel. In a control inside a cell: back to the cell | | Alt+Shift+Up / Down | Move the focused row (`rowDrag`) | | Alt+Down, Shift+F10, context menu key | Open the focused header's menu | | Cmd/Ctrl+C | Copy the selected rows, or the focused cell, as tab-separated displayed text. A text selection copies as usual | | Cmd/Ctrl+F | Focus the toolbar search (with `toolbar.search`) | | A letter | Fire the row button or menu item with that `key` | | Cmd/Ctrl+Z, Cmd/Ctrl+Shift+Z or Ctrl+Y | `TableInput`: undo / redo | | Delete or Backspace | `TableInput` with `deleteRows`: delete the focused row | | Cmd/Ctrl+V | `TableInput`: paste tab-separated text over the cells from the focused one | Skeleton rows (loading rows, and server rows not loaded yet) are not cells you can focus: an arrow key onto one moves focus to that row once it loads. In the column manager, Alt+Up / Down on a column's handle moves it. `keyboard: false` turns off cell navigation (arrows, Home/End, PageUp/PageDown, Enter and Space on cells), tree Right/Left and `TableInput` paste. Copy, Cmd/Ctrl+F, the header menu keys, the keys of a focused group header, single-key row actions and the editing keys keep working, and the grid roles stay for screen readers. ## Performance - **Virtualisation.** With `virtual: auto` the table renders only the rows in view once there are more than 200 display rows (groups count), and only the columns in view once there are more than 20 scrolling columns or the table is wider than twice its viewport. Pinned columns always render. Scrolling never re-renders the engine or touches Lowdefy state. - **Fixed heights are fastest.** Rows are one fixed height from the density (32, 40 or 52px) or `rowHeight`. A column with `wrap: true` or `ellipsis` above 1 makes rows as tall as their content: heights are measured as rows render, and column virtualisation turns off, since a row's height then depends on every cell in it. Expandable detail rows are measured too. - **Rich cells.** Hover buttons mount only on the hovered row or the row focused with the keyboard. During a fast scroll, avatar, tag, button and other rich cells show their text and upgrade when the scroll settles. Templates compile once per column, and the template compiler (about 36 kB gzipped) loads only for a table with an `html` template, a template tooltip or `expandable`. - **Data updates.** Rows are compared by key, so a refetch re-renders only rows whose content changed. `rowVersionField`, a dot path such as `updated.timestamp`, compares rows by key and that field instead of by content; a row without the field is compared by content. `applyTransaction` adds, updates or removes rows without replacing `data`. - **Big client data.** Sort, filter and group of 100k rows stay under the 60fps budget in the benchmark (text sort keys and the first search are built in time slices, without blocking). Keep `data` a plain request result: an `_js` or `_function` in it runs on every page update. - **Server mode** is for data the browser should not hold. `blockSize` (default 200) sets the rows per request and `maxBlocks` (default 20) the blocks kept; loads wait for a fast scroll to settle. - **TableLight renders every row** with antd's Table: keep it to hundreds of rows. It logs a development warning above 1,000. ## Server mode security In server mode the browser sends the view, and the view is user input. `MongoDBTableQuery` is built so that it can only narrow what the base query allows: - **The allowlist.** Only keys in `fields` can be sorted, filtered, searched, grouped or aggregated, only with the operators and aggregates their `type` allows, and only with values of that type. Anything else is refused before the query runs. Regex input is escaped, and the view can not send `$where`, `$expr` or pipeline stages. - **The base pipeline runs first.** Put tenant, ownership and permission scoping in `pipeline`, evaluated on the server with `_user`, never in the payload. On a tenant connection the tenant scope comes before it. - **Only allowed fields leave the server.** With `project: true` (the default) each row returns only `_id`, the `fields` paths and the `returnFields` paths, even when the documents hold more. List in `returnFields` the paths cells read without a field of their own: an avatar `srcField`, a link or relation `labelField`, a `rowKey` other than `_id`. `project: false` returns what the base pipeline leaves, for a pipeline that ends in its own `$project`. - **`user: { _user: true }`** on the request resolves `{ $user: path }` filter values on the server from the session. A browser-sent user value is never used, and a missing one throws instead of matching empty fields. - **Bounded requests.** One view may have at most 200 filter conditions and groups (nested at most 8 deep), 1000 filter values in all (500 per `in` / `nin`), strings of at most 200 characters, 10 sort keys and 5 group levels, and a request returns at most `maxRows` (default 1000) rows or groups. The aggregation stops after `options.maxTimeMS`, 10 seconds by default. A request over a limit throws before it runs. - **Selections.** An all-matching selection `{ all: true, except, filter, search }` is not a list of keys. `MongoDBTableChanges` with `selection` recomputes the set on the server: its filter and search are validated against `queryFields` like a view, and the base `filter` is ANDed in, so a crafted selection can not widen a bulk action beyond what the user can read. - **Saves.** `MongoDBTableChanges` writes only the `fields` listed, at their paths, with coerced values, scoped by its base `filter`; row keys and values that are operator objects are refused. Fields that overlap a `filter` or `insertDefaults` field are refused, so a save can not move a row out of scope, and new rows are stamped with the `filter`'s equalities. Every column the user can sort or filter must be in `fields`, or the request fails when they try. Set `sortable: false` and `filterable: false` on columns the request does not allow, and give `toolbar.search` only to tables whose request has `search: true` fields. Server search matches the raw field values, not the display labels a client-side search reads. **Rows from an API endpoint.** When the rows may only be read behind an endpoint's own checks, give `data.endpoint` in place of `data.request`. The table calls the endpoint with `CallAPI`, with the fetch event `{ startRow, endRow, view, groupPath, selected }` as its payload, and reads the routine's response as it reads the request's, `{ rows, total, groups?, aggregates? }`. Give exactly one of `request` and `endpoint`. A response without `rows` and `total` shows the table's own error row. ```yaml # The table data: mode: server endpoint: deals-rows # api/deals-rows.yaml id: deals-rows type: Api routine: # The endpoint's own checks run first, for example a :reject for a caller without access. - id: deals_page type: MongoDBTableQuery connectionId: deals properties: pipeline: - $match: org_id: _user: organization.id fields: _ref: deals/table_fields.yaml user: _user: true view: _payload: view startRow: _payload: startRow endRow: _payload: endRow groupPath: _payload: groupPath - :return: _step: deals_page ``` **Fields from the page.** `data.payload` adds fields to every fetch event, beside `startRow`, `view` and the rest, for a filter the page keeps outside the table's view, such as a region picked above the table. Kept out of the view, it never ends up in a saved view's filter. The table's own fields win on a clash, and a changed payload starts the rows again from the top. Read the fields in the request or routine with `_event` or `_payload`, and check them there like any other input. ```yaml data: mode: server endpoint: deals-rows payload: region: _state: region ``` The fetch event carries no time zone, so a routine that filters dates by the user's days sets `timezone` itself, for example from the user's profile. **Time zones and indexes.** The browser compares date filters by the user's local days, the server by the days of the request's `timezone` (UTC by default): pass the user's time zone, as `deals_page` does. The request sorts before it pages, so an index on the base match, filter and sort fields (for example `{ org_id: 1, updated: -1, _id: 1 }`) serves each block; without one, MongoDB sorts every matching document for every block. Add indexes for the sorts a large table offers. ## Enrichment tables Enrichment tables compute columns from other columns, per row: an `enrichment` column calls a provider (an API endpoint of the app), an `ai` column runs a prompt, a `formula` column fills a template in the browser, and an `extract` column reads a value out of another column's result. Users add columns and rows at runtime, run a column, a row or a selection, and open a cell to see its raw result. The run queue lives in the rows, in MongoDB: see [MongoDB enrichment run queue](/MongoDB) for the three requests and the worker endpoint. ```yaml - id: leads_table type: Table properties: providers: # the catalogue the add-column picker offers; each maps to enrich_ _ref: leads/providers.yaml addColumn: true # the "+" header and its picker addRow: true # "+ New row" importCsv: true # the toolbar's Import button: CSV files of at most 50 MB and 100,000 rows inputFieldPrefix: values # user input columns keep their values at values. columns: _request: get_columns # declared and user-defined columns, merged on the server data: _request: leads ``` The enrichment feature loads in its own chunk, only for tables that use it (an enrichment, ai, extract, `status` or user-defined column, `providers`, `addColumn`, `addRow` or `importCsv`). Formula columns alone do not load it. **Column kinds.** An `ai` column takes `prompt`, `inputs` (every column the prompt uses), `provider` (default `ai`, the app's `enrich_ai` endpoint) and `output: { type, options? }`: the answer is `text`, `number`, `boolean`, `tag` or `tags`, and `tag` and `tags` take `options`, the answers allowed, each its text or `{ value, color }` (the picker shows the options as chips in one field, each starting on a distinct tone, with a colour button of preset tones and a remove button; a user-defined column's option colours are tone names, never CSS values, since they are painted in every viewer's browser). A user-defined column's options without a colour (plain strings written through an API, or a column saved before options had colours) get the tones the picker would give them, in option order, so they look like a picker-made column; a declared column's options without a colour stay neutral, as the config author wrote them. An `enrichment` column takes a `provider` from `providers`, `inputs` mapped to columns or literal values, and `output`, the path of its value in the provider result. A catalogue provider with id `ai` is the AI kind's provider: the picker shows it once, as the AI entry. **Inputs read stored values.** An enrichment or ai column's inputs (and an AI prompt's placeholders) read input and data columns (fields the server can read) or other enrichment and ai columns (their result, once done). Formula and extract columns compute in the browser and are never stored, so the server can not send them to a provider: the picker does not offer them, and a column that reads one is refused (a declared column is a config error, a user-defined one an error column). To send a combined value to a provider, map each part as an input; to read a nested result, map the enrichment column itself and pick the path in your provider endpoint. A formula template can read any column. **Templates are placeholders.** Formula templates and AI prompts only take `{{ column }}` placeholders (a column key or a dot path), filled in as plain text. They are user content shared between users, and a template engine would run them as code, so the Table refuses tags (`{% %}`), comments (`{# #}`) and expressions (`{{ name | upper }}`), and so should the endpoint that saves a column. Fill prompts on the server with plain string replacement, never `_nunjucks`. A placeholder is `{{ key }}` or `{{ key.path }}` (a dot path into that column's or input's value), optionally with whitespace control (`{{- key -}}`); a key starts with a letter, `_` or `$` and may hold `-`, but not end with one. The server that fills prompts must accept exactly the placeholders the Table does, with the same pattern, so no placeholder the Table accepts is left unfilled (the reference app's `check_column` and `enrich_ai` use it). **User-defined columns.** Columns with `userDefined: true` get Rename, Edit, Duplicate, Insert and Delete in their header menu (each shown when the table has the event it fires). A user-defined column whose config is invalid (an unknown provider or answer type, an input column that was deleted) renders as an error column instead of breaking the table: its cells show "Invalid column: ", its header is marked, and its menu offers Edit column and Delete column. A declared column with an invalid config is a config error. A user-defined column is one user's content rendered in every viewer's browser, so the Table only takes text-safe config from it: its type must be one of `text`, `email`, `phone`, `url`, `number`, `currency`, `percent`, `progress`, `rating`, `date`, `datetime`, `boolean`, `tag`, `tags`, `status` or `json` (an `html`, `image`, `avatar`, `people`, `link`, `relation` or action type makes it an error column; with no type it is `text`, whatever `defaultColumn` sets), and its `cell`, `rules`, `validate` and template tooltips are ignored (a `{ field }` tooltip is kept). The add-column picker offers only these types. Check stored columns against the same list on the server, as the reference app's column check does. **CSV import.** The Import button parses the file in the browser and suggests a column for each header: one whose key or title matches it (ignoring case, spaces and punctuation), then a common synonym ("Website" or "URL" for a domain column, "Employer" or "Organisation" for a company, "Role" for a job title, "Full name" for a name, "E-mail" for email), then a close spelling. Suggestions that are not a column's own name are marked until the user picks another column, and headers without a match become new text columns. **Values at field paths.** `onRowAdd` `values` and `onImport` `rows` carry every value at its column's `field` path. New input columns from the picker or a CSV import carry their `field` too, under `inputFieldPrefix` (`values.notes` with `inputFieldPrefix: values`), so the endpoint that stores them only has to accept the paths of its `fields` allowlist. Build that allowlist on the server, with the user input columns added: ```yaml fields: _js: fn: | const fields = { ...args.declared }; args.columns .filter((column) => column.userDefined === true && column.kind === 'input') .forEach((column) => { fields[column.key] = { type: column.type ?? 'text', path: `values.${column.key}` }; }); return fields; args: declared: _ref: leads/fields.yaml columns: _step: load_columns ``` **New rows.** "+ New row" fires `onRowAdd { rowKey, values }`, and the row shows at the end of the table, saving, under `rowKey`, a new random UUID. Insert the row under that key (as its `_id`, or at the field the Table's `rowKey` property names) and the saved row takes the saving row's place the moment it reaches `data`, from a live push or a refetch, even before the event ends. A row stored under a key of its own (a generated ObjectId) shows beside the saving row until the event ends. An app that uses `rowKey` should key every row with a UUID `_id`, imported rows too (`_uuid: true`), and leave `rowKeyType` on MongoDBEnrichmentEnqueue and MongoDBTableChanges at its default, `auto` (`objectId` refuses UUID keys). UUIDs carry no order, so sort the rows by a creation time, with a tiebreak for rows inserted together (their position in an import batch), and index those fields. The key comes from the browser: check its format on the server, and only ever insert with it (`MongoDBInsertOne`, `MongoDBInsertMany`), never an upsert, replace or update keyed by it, so a key that is taken fails the insert instead of overwriting another user's or tenant's row. **Read columns on the server.** Endpoints that enqueue runs or run the worker read the table's columns themselves (declared columns plus the stored user columns), never from the event payload: a browser can send any column config. Check every user column when it is saved (its key, provider, inputs, prompt and template), as the enrichment reference app's column check does. **Errors users can read.** A cell's `error` is shown to every user of the table: in the cell's tooltip ("Failed after 3 attempts" and the message, shortened) and whole in the details panel. Store messages a user can act on, not the error a connection threw, which names the app's connection and carries the service's response (`company_api: Server returned error 500...`). In the worker's `:catch` and in provider endpoints, map a thrown error to its status (`Provider error (500)`, `Rate limited by the provider (429)`, `The provider did not respond`) and leave the full error to the server log, as the reference app's `run_cell` and `enrich_company_lookup` do. **Loading.** Wire `loading` to the rows request (`_request_details: leads.0.loading`) and reload columns and rows with `holdValue: true` after a column or row change, so the table keeps its rows and columns on screen while they reload; the first load shows the table's skeleton rows. The picker, details panel and import dialog open at once, with a spinner while their code loads. **Raw results on demand.** A provider's raw answer is often many times the size of the value, and the table only shows it in the details panel. A table can leave it out of its rows (strip `_enrich..raw` in the rows request) and set `rawEndpoint` to an API endpoint that returns one cell's raw result: the details panel calls it with `{ rowKey, key }` (the row's key and the column key) when it opens a finished enrichment or ai cell whose row carries no `raw`, shows "Loading the raw result…" meanwhile, and calls it again when the cell finishes a new run. The endpoint returns `{ raw }`; scope its read as you scope the rows. Extract columns read the raw result from the row, so keep the fields they read in the rows; a raw result loaded from the endpoint shows without the add-as-column buttons. The reference app's `enrichment_raw_endpoint` page and `leads_cell_raw` endpoint show the pattern. **Live results.** Push cell updates to the table with `applyTransaction({ update })` from a websocket (`MongoDBChangeStream` on the rows) that sends each changed row's `fullDocument` (projected to the fields the table shows, `_enrich` whole). The default shallow merge replaces each top-level field, so a cell whose rerun found nothing loses its old `value`, `raw` and `inputHash`, as the server unset them. Keep `merge: 'deep'` for partial patches you build yourself: a deep merge never removes a key, so a formula, an extract column or the stale marker would keep reading what the server removed. ## Moving from TableLight or AgGrid - **From TableLight:** change `type: TableLight` to `type: Table`. Every property keeps its meaning; set `pagination: true` if you relied on TableLight's automatic pager. - **From AgGrid:** move column config by hand. `field` stays `field`, `headerName` becomes `title`, `cell.type` becomes the column's `type` and the rest of `cell` stays. Row events carry `{ row, rowKey, index }`, and button and menu events the same payloads as AgGrid's button and menu cells. A total row in `pinnedBottomRowData` becomes `summaryRow`. `AgGridInput*` holds the rows as its value; `TableInput` holds the changes, saved with `MongoDBTableChanges`. ```yaml - id: deals_basic type: Table properties: columns: - key: name title: Deal width: 220 - key: stage - key: owner - key: amount type: number data: - _id: d1 name: Acme renewal stage: proposal owner: Ada amount: 12000 - _id: d2 name: Globex expansion stage: qualified owner: Grace amount: 48000 - _id: d3 name: Initech pilot stage: lead owner: Alan amount: 3500 ``` ```yaml - id: deals_typed type: Table properties: columns: - key: name title: Deal width: 200 pinned: start - key: stage type: status options: lead: Lead qualified: label: Qualified color: processing won: label: Won color: success lost: label: Lost color: error - key: owner type: avatar cell: nameField: owner - key: amount type: currency cell: currency: EUR decimals: 0 - key: probability type: progress width: 140 - key: labels type: tags options: - value: renewal color: blue - value: strategic color: purple - value: at-risk label: At risk color: red - key: updated type: date cell: format: D MMM YYYY - key: website type: url cell: label: Visit data: - _id: d1 name: Acme renewal stage: won owner: Ada Lovelace amount: 12000 probability: 100 labels: - renewal updated: 2026-09-21 website: https://example.com - _id: d2 name: Globex expansion stage: qualified owner: Grace Hopper amount: 48000 probability: 60 labels: - strategic updated: 2026-09-02 website: https://example.org - _id: d3 name: Initech pilot stage: lead owner: Alan Turing amount: 3500 probability: 20 labels: - at-risk - renewal updated: 2026-08-15 - _id: d4 name: Umbrella upsell stage: lost owner: Ada Lovelace amount: 9000 probability: 0 labels: [] updated: 2026-07-30 ``` ```yaml - id: deals_selection type: Table properties: rowSelection: type: checkbox defaultView: sort: - key: amount desc: true density: compact columns: - key: name title: Deal width: 220 pinned: start - key: stage - key: owner - key: amount type: number data: - _id: d1 name: Acme renewal stage: proposal owner: Ada amount: 12000 - _id: d2 name: Globex expansion stage: qualified owner: Grace amount: 48000 - _id: d3 name: Initech pilot stage: lead owner: Alan amount: 3500 - id: deals_selected type: Span properties: content: _string.concat: - "Selected: " - _json.stringify: - _state: deals_selection.selected - space: 0 ``` ```yaml - id: contacts_toolbar type: Table properties: toolbar: search: true quickFilters: - status filter: true sort: true columns: true density: true export: true columns: - key: name width: 180 - key: email type: email width: 220 - key: company - key: status type: tag options: - value: active label: Active color: green - value: churned label: Churned color: red - value: trial label: Trial color: gold - key: signed_up type: date cell: relative: true data: - _id: c1 name: Ada Lovelace email: ada@example.com company: Acme status: active signed_up: 2026-01-12 - _id: c2 name: Grace Hopper email: grace@example.com company: Globex status: trial signed_up: 2026-09-20 - _id: c3 name: Alan Turing email: alan@example.com company: Initech status: churned signed_up: 2025-06-01 - _id: c4 name: Katherine Johnson email: katherine@example.com company: Acme status: active signed_up: 2026-04-03 slots: toolbarStart: blocks: - id: contacts_toolbar_new type: Button properties: title: New contact icon: add size: small ``` ```yaml - id: notes_paged type: Table properties: pagination: true defaultView: pageSize: 2 wrap: true columns: - key: title width: 160 - key: note width: 260 data: - _id: n1 title: Kick-off note: Agree the scope, the owners of each workstream and the first review date. - _id: n2 title: Design review note: Walk through the flows with support and sales before the build starts. - _id: n3 title: Launch note: Announce in the changelog and email every customer on the beta list. ``` ```yaml - id: deals_links type: Table properties: rowLink: pageId: deal urlQuery: _id: _id columns: - key: name title: Deal - key: owner data: - _id: d1 name: Acme renewal owner: Ada - _id: d2 name: Globex expansion owner: Grace ``` ```yaml - id: tickets_bulk type: Table properties: rowSelection: type: checkbox columns: - key: subject width: 260 - key: requester - key: priority type: tag options: - value: high label: High color: red - value: normal label: Normal color: blue - key: actions type: buttons title: "" width: 96 pinned: end sortable: false cell: showOn: hover buttons: - eventName: onReply title: Reply icon: mail hideTitle: true - eventName: onClose title: Close icon: check hideTitle: true data: - _id: t1 subject: Cannot export the monthly invoice requester: Ada priority: high - _id: t2 subject: How do I add a second address? requester: Grace priority: normal - _id: t3 subject: Login link expired requester: Alan priority: normal slots: bulkActions: blocks: - id: tickets_bulk_close type: Button properties: title: Close selected size: small events: onClick: - id: tickets_bulk_record type: SetState params: tickets_bulk_closed: _state: tickets_bulk.selected events: onReply: - id: tickets_bulk_reply type: SetState params: tickets_bulk_last: _string.concat: - "Reply to " - _event: row.requester onClose: - id: tickets_bulk_close_one type: SetState params: tickets_bulk_last: _string.concat: - "Close " - _event: rowKey - id: tickets_bulk_status type: Span properties: content: _string.concat: - "Last action: " - _if_none: - _state: tickets_bulk_last - none - " · Closed: " - _json.stringify: - _if_none: - _state: tickets_bulk_closed - [] - space: 0 ``` ```yaml - id: deals_grouped type: Table properties: defaultView: group: - key: region - key: owner columns: - key: name title: Deal width: 220 - key: region groupable: true - key: owner groupable: true - key: amount type: number aggregate: sum - key: probability type: percent aggregate: avg data: - _id: d1 name: Acme renewal region: EMEA owner: Ada amount: 12000 probability: 0.9 - _id: d2 name: Globex expansion region: EMEA owner: Grace amount: 48000 probability: 0.6 - _id: d3 name: Initech pilot region: AMER owner: Alan amount: 3500 probability: 0.2 - _id: d4 name: Umbrella upsell region: EMEA owner: Ada amount: 9000 probability: 0.4 ``` ```yaml - id: response_totals type: Table properties: columns: - key: region - key: invited type: number - key: completed type: number - key: rate title: Response rate type: percent cell: decimals: 1 data: - _id: r1 region: North invited: 400 completed: 120 rate: 0.3 - _id: r2 region: South invited: 100 completed: 60 rate: 0.6 summaryRow: region: Total invited: 500 completed: 180 rate: 0.36 ``` ```yaml - id: outcomes_groups type: Table properties: columns: - key: region - key: outcomes title: Outcomes collapsible: true collapsed: true children: - key: total type: number showWhen: closed - key: completed type: number - key: refused type: number showWhen: open - key: not_reached type: number showWhen: open data: - _id: r1 region: North total: 400 completed: 120 refused: 80 not_reached: 200 - _id: r2 region: South total: 100 completed: 60 refused: 10 not_reached: 30 ``` ```yaml - id: accounts_tree type: Table properties: tree: parentField: parent rowSelection: type: checkbox cascade: true defaultView: sort: - key: name columns: - key: name title: Account width: 220 - key: balance type: currency data: - _id: assets name: Assets balance: 128000 - _id: cash name: Cash balance: 28000 parent: assets - _id: receivables name: Receivables balance: 100000 parent: assets - _id: liabilities name: Liabilities balance: 42000 - _id: payables name: Payables balance: 42000 parent: liabilities - id: accounts_tree_value type: Span properties: content: _string.concat: - "Expanded: " - _json.stringify: - _state: accounts_tree.expanded - space: 0 ``` ```yaml - id: folders_lazy type: Table properties: tree: parentField: parent lazy: true columns: - key: name title: Folder width: 260 data: _if_none: - _state: folders_lazy_rows - [] events: onMount: - id: folders_lazy_init type: SetState params: folders_lazy_rows: - _id: invoices name: Invoices hasChildren: true - _id: recipes name: Recipes hasChildren: true onRowExpand: - id: folders_lazy_load type: SetState skip: _not: _event: needsChildren params: folders_lazy_rows: _array.concat: - _state: folders_lazy_rows - - _id: _string.concat: - _event: rowKey - "-2025" name: "2025" parent: _event: rowKey - _id: _string.concat: - _event: rowKey - "-2026" name: "2026" parent: _event: rowKey ``` ```yaml - id: recipes_expandable type: Table properties: expandable: template: |
{{ row.name }}: {{ row.method }}
rowExpandable: when: key: method op: notEmpty columns: - key: name title: Recipe width: 200 - key: minutes type: number data: - _id: r1 name: Sourdough loaf minutes: 1440 method: Mix, rest overnight, shape and bake at 250°C. - _id: r2 name: Pancakes minutes: 20 method: Whisk the batter and fry in a hot pan. - _id: r3 name: Toast minutes: 3 ``` ```yaml - id: deals_editing type: Table properties: columns: - key: name title: Deal width: 220 editable: true validate: - pass: op: notEmpty message: A deal needs a name. - key: stage type: status editable: true options: lead: Lead won: label: Won color: success lost: label: Lost color: error - key: amount type: currency editable: when: key: stage op: ne value: won - key: close_date type: date editable: true data: - _id: d1 name: Acme renewal stage: won amount: 12000 close_date: 2026-10-01 - _id: d2 name: Globex expansion stage: lead amount: 48000 close_date: 2026-11-15 events: onCellEdit: - id: deals_editing_wait type: Wait params: ms: 600 - id: deals_editing_record type: SetState params: deals_editing_last: _string.concat: - _event: rowKey - " " - _event: column.key - " = " - _json.stringify: - _event: value - id: deals_editing_value type: Span properties: content: _string.concat: - "Last edit: " - _if_none: - _state: deals_editing_last - none ``` ```yaml - id: steps_drag type: Table properties: rowDrag: positionField: position defaultView: sort: - key: position columns: - key: title title: Step - key: position type: number width: 110 data: - _id: s1 title: Plan position: 1024 - _id: s2 title: Build position: 2048 - _id: s3 title: Ship position: 3072 events: onRowMove: - id: steps_drag_record type: SetState params: steps_drag_last: rowKey: _event: rowKey position: _event: position - id: steps_drag_value type: Span properties: content: _string.concat: - "Last move: " - _json.stringify: - _if_none: - _state: steps_drag_last - none - space: 0 ``` ```yaml - id: tickets_queue type: Table properties: toolbar: views: true keyboard: next: true views: - id: open title: Open count: 2 locked: true view: filter: key: status op: eq value: open - id: waiting title: Waiting count: 1 locked: true view: filter: key: status op: eq value: waiting columns: - key: subject width: 260 - key: status type: tag options: - open - waiting - key: actions type: buttons title: "" width: 200 sortable: false cell: buttons: - eventName: onAssign title: Assign (a) key: a - eventName: onSnooze title: Snooze (s) key: s data: - _id: t1 subject: Refund for a double charge status: open - _id: t2 subject: Change the billing email status: open - _id: t3 subject: Waiting for a screenshot status: waiting events: onAssign: - id: tickets_queue_assign type: SetState params: tickets_queue_last: _string.concat: - "Assigned " - _event: rowKey onSnooze: - id: tickets_queue_snooze type: SetState params: tickets_queue_last: _string.concat: - "Snoozed " - _event: rowKey - id: tickets_queue_value type: Span properties: content: _string.concat: - "Focus a row and press a or s. Last: " - _if_none: - _state: tickets_queue_last - none ``` ```yaml - id: invoices_rules type: Table properties: rowRules: - when: key: overdue op: isTrue style: fontWeight: 600 columns: - key: number title: Invoice - key: total type: currency rules: - when: op: gte value: 10000 color: success - key: due type: date rules: - when: key: overdue op: isTrue color: error - key: overdue type: boolean hidden: true data: - _id: i1 number: INV-1001 total: 12500 due: 2026-08-01 overdue: true - _id: i2 number: INV-1002 total: 800 due: 2026-10-30 overdue: false ``` ```yaml - id: leads_enrichment type: Table properties: addColumn: true addRow: true importCsv: true rowSelection: type: checkbox providers: - id: findEmail title: Find email description: A work email from a name and a domain. inputs: - key: domain title: Domain required: true - key: name title: Name outputs: - path: email title: Email type: email columns: - key: name kind: input editable: true width: 150 - key: domain kind: input editable: true width: 140 - key: email kind: enrichment type: email provider: findEmail inputs: domain: column: domain name: column: name output: email width: 200 - key: segment kind: ai type: tag prompt: Which market segment is {{ domain }} in? inputs: domain: column: domain output: type: tag options: - smb - mid-market - enterprise userDefined: true width: 140 - key: linkedin kind: extract source: email path: profile.linkedin userDefined: true - key: greeting kind: formula template: Hi {{ name }}, about {{ domain }} userDefined: true width: 220 data: - _id: l1 name: Ada domain: acme.com _enrich: email: status: ok value: ada@acme.com inputHash: 1d27590e6f1c9b raw: email: ada@acme.com profile: linkedin: in/ada segment: status: ok value: enterprise - _id: l2 name: Grace domain: globex.com _enrich: email: status: running segment: status: queued - _id: l3 name: Alan domain: initech.com _enrich: email: status: queued - _id: l4 name: Linus domain: umbrella.com _enrich: email: status: error error: The provider timed out. - _id: l5 name: Margaret _enrich: email: status: empty error: "Missing input: domain" - _id: l6 name: Ivan domain: hooli.com _enrich: email: status: ok value: ivan@old-domain.com inputHash: 0 ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `columns` | array \| null | - | The columns, in default order. | | `defaultColumn` | object | - | Defaults applied to every column. | | `defaultColumn.sortable` | boolean | `true` | Sort by clicking the header (Shift+click adds to a multi-sort). Defaults to `defaultColumn.sortable` (true). | | `defaultColumn.filterable` | boolean | `true` | Offer the column in filters: the header menu "Filter…" item and the filter builder. | | `defaultColumn.resizable` | boolean | `true` | Resize the column by dragging the right edge of its header. | | `defaultColumn.groupable` | boolean | `false` | Allow grouping rows by this column (view `group`, the `setGroup` method, the header menu). Default false. | | `defaultColumn.editable` | boolean \| object | `false` | Edit cells of this column: `true`, or `{ when: Condition }` to allow it per row. The editor comes from the type: text, email, phone and url edit as text; number, currency and percent as a number; date and datetime with a date picker; boolean with a switch; tag and status select from `options`; tags select several; rating with stars. Enter, F2, double-click or typing opens it; Enter, Tab or leaving commits; Esc cancels. Table fires onCellEdit; TableInput writes its value. | | `defaultColumn.editable.when` | object | - | Condition tested against the row (and the cell value, for leaves without `key`) that decides whether the cell is editable. | | `defaultColumn.width` | number | - | Column width in pixels. Default 160. | | `defaultColumn.minWidth` | number | - | Minimum width in pixels when resizing. Default 48. | | `defaultColumn.maxWidth` | number | - | Maximum width in pixels when resizing. | | `defaultColumn.ellipsis` | integer \| boolean | - | Clamp the text to this many lines (`true` is one) with an ellipsis; the full text shows on hover. More than one line makes rows as tall as their content. | | `defaultColumn.wrap` | boolean | `false` | Wrap long text; rows grow to fit (measured row heights, which turn column virtualisation off). Text stays on one line by default. | | `data` | array \| object \| null | - | The rows, or `{ mode: server, request, blockSize }` to load rows from a request in blocks as the table scrolls. In server mode the table fires the request with the event `{ startRow, endRow, view: { sort, filter, search, group, aggregates }, groupPath, selected }` (read it in the request `payload` with `_event`) and expects `{ rows, total, groups?, aggregates? }`, the contract of `MongoDBTableQuery`. Sorting, filtering and grouping then run on the server. Rows need a `rowKey`. In place of `request`, `endpoint` names an API endpoint: the table calls it with the event as its payload, and its routine returns the same `{ rows, total }`. `payload` adds the page's own fields to every fetch event. | | `data.mode` | string | - | Load rows from `request`. Enum: `server`. | | `data.request` | string | - | Id of the request on the page that returns `{ rows, total }`. Give this or `endpoint`. | | `data.endpoint` | string | - | Id of an API endpoint whose routine returns `{ rows, total }`, called with the fetch event as its payload. Give this or `request`; use it for rows a page request may not read, behind the endpoint's own checks. | | `data.payload` | object | - | Fields added to every fetch event, beside `startRow`, `view` and the rest, for a filter the page keeps outside the table's view (a region picked above the table). The table's own fields win on a clash. A change starts the rows again from the top. | | `data.blockSize` | integer | `200` | Rows per request. Blocks load as they scroll into view. | | `data.maxBlocks` | integer | `20` | Blocks kept in the cache; the least recently used are dropped (and refetched when they come back into view). | | `rowKey` | string | - | The row field that identifies each row. Defaults to `_id`, then `id`. Rows with neither get a key per row object, which does not survive a refetch. | | `rowVersionField` | string | - | Dot path to a field that changes whenever a row changes (for example `updated.timestamp`). When set, rows are compared by key and this field instead of by content; a row without it is compared by content. | | `user` | object | - | The user object for `$user` values in filters, `rules`, `rowRules` and button `hidden`/`disabled` conditions, usually `{ _user: true }`. Blocks do not see the session, so conditions read `$user` from this property. | | `defaultView` | object | - | The initial and reset view. Parts missing from the table value fall back to this, then to the column defaults. | | `defaultView.columns` | array | - | Column order, widths, pinning and visibility. | | `defaultView.columns.$.key` | string | - | | | `defaultView.columns.$.width` | number | - | | | `defaultView.columns.$.pinned` | string | - | Pin the column to the start or end, where it stays visible while scrolling. Enum: `start`, `end`. | | `defaultView.columns.$.hidden` | boolean | - | | | `defaultView.collapsedColumnGroups` | array | - | Keys of the collapsed collapsible column groups, over the groups' own `collapsed`. A group's key is its `key`, else `group:` (`group:2.0` for the first group inside the third). | | `defaultView.sort` | array | - | Sort order: `[{ key, desc }]`. | | `defaultView.sort.$.key` | string | - | | | `defaultView.sort.$.desc` | boolean | - | | | `defaultView.filter` | object \| null | - | Filter condition: `{ and: [...] }` / `{ or: [...] }` groups of `{ key, op, value }` leaves. Operators depend on the column type; `{ $user: path }` values read the `user` property. Date `within` takes a relative date `{ last \| next: n, unit: day \| week \| month \| year }`, and `before` and `after` take a date or a relative date, so `{ op: before, value: { last: 3, unit: month } }` keeps meaning "before 3 months ago". | | `defaultView.search` | string \| null | - | Search text: rows match when every word appears (case-insensitive) in the display text of the searched columns. | | `defaultView.group` | array | - | Group rows by these columns, outermost first: `[{ key }]`. Only columns with `groupable: true` group; groups follow the sort on their column, else option order, else first appearance, and empty values group as "(Empty)" last. | | `defaultView.group.$.key` | string | - | | | `defaultView.collapsedGroups` | array | - | Keys of collapsed groups: the JSON of the group value path, for example `["lead"]` or `["EMEA","Ada"]` (empty values are `null`). | | `defaultView.aggregates` | object | - | Aggregates by column key, for example `{ amount: sum, deals: count }`, over the columns' own `aggregate`. `null` turns a column default off. Group headers show them under their columns. | | `defaultView.density` | string | - | Row density: compact 32px, default 40px, comfortable 52px rows. Defaults to `size`. Enum: `compact`, `default`, `comfortable`. | | `defaultView.wrap` | boolean | - | Wrap the text of text-like columns (text, email, phone, url, link, html, relation) that set no `wrap` or `ellipsis` of their own; rows grow to their content. The toolbar density control has a Wrap toggle. | | `defaultView.pageSize` | integer | - | Rows per page when pagination is on. Defaults to the `pageSize` property. | | `rowSelection` | object | - | Turn on row selection. The selection is the `selected` part of the table value. | | `rowSelection.type` | string | `"checkbox"` | Multiple (checkbox) or single (radio) selection. Enum: `checkbox`, `radio`. | | `rowSelection.preserve` | boolean | `false` | Keep selected keys of rows that leave `data`. Always on in server mode. | | `rowSelection.cascade` | boolean | `false` | In a tree, selecting or clearing a row also selects or clears all its descendants. | | `rowSelection.selectable` | object | - | A condition (`{ key, op, value }`, or `and` / `or` lists) tested against the row. Rows that fail it have a disabled checkbox, are skipped by the header checkbox and by select-all, and are left out of its checked state. An `{ all: true }` selection lists the rows it rejects in `except`; in server mode those are the loaded rows, and the server resolves the rest. | | `tree` | object | - | Show rows as a tree (client data only): rows are indented under their parent with an expand chevron in the first column, Right and Left expand and collapse, sorting sorts within each level and a filter keeps the ancestors of matching rows. The expanded row keys are the `expanded` part of the table value. Give exactly one of `childrenField` or `parentField`. | | `tree.childrenField` | string | - | Dot path to each row's child rows (nested data). | | `tree.parentField` | string | - | Dot path to the row key of each row's parent (flat data). Rows whose parent is not in `data` are roots. | | `tree.lazy` | boolean | `false` | Load children on demand: rows whose `hasChildrenField` is true show a chevron before their children are loaded, expanding a row fires `onRowExpand` with `needsChildren: true` until its children are in `data`, and the app adds them (for example a request whose result is merged into the data with `parentField` set). | | `tree.hasChildrenField` | string | `"hasChildren"` | With `lazy`, the field that marks rows with children that are not loaded yet. | | `tree.indent` | number | `20` | Indent per level in pixels. | | `expandable` | object | - | Expandable rows: a chevron in the first column opens a detail row below the row, as high as its content. The expanded row keys are the `expanded` part of the table value. | | `expandable.template` | string | - | Nunjucks HTML for the detail row, rendered with `row` and `rowKey`. Output is escaped: use `\| safe` to insert HTML from a field. The HTML is sanitised. | | `expandable.rowExpandable` | object | - | Which rows can expand. | | `expandable.rowExpandable.when` | object | - | A condition (`{ key, op, value }`, or `and` / `or` lists) tested against the row. | | `rowLink` | object | - | Make rows links. A plain click navigates, Cmd/Ctrl or middle click opens a new tab, and Enter on a focused row follows it. Values in `urlQuery` and `pathParams` are row paths. | | `rowLink.pageId` | string | - | The page to open. | | `rowLink.href` | string | - | A URL to open instead of a page. | | `rowLink.urlQuery` | object | - | Query parameters; each value is a path in the row, like `{ _id: _id }`. | | `rowLink.pathParams` | object | - | Values for the page path's placeholders; each value is a path in the row, like `{ ticket_id: _id }`. | | `rowLink.input` | object | - | Input for the page. | | `rowLink.newTab` | boolean | - | Always open in a new tab. | | `rowRules` | array | - | Conditional row formatting: `[{ when, className, style, color }]`, where `when` conditions name columns by `key`. | | `size` | string | `"default"` | Row density: compact 32px, default 40px, comfortable 52px rows. The view starts at this density (`view.density` overrides it). Enum: `compact`, `default`, `comfortable`. | | `bordered` | boolean | `false` | Draw borders between cells. | | `height` | number \| string | - | A fixed table height (px number or CSS size); the rows scroll under a sticky header and the summary row stays in view. Without it the table grows with its rows up to `maxHeight`. | | `maxHeight` | number \| string | `600` | Maximum height when `height` is not set. | | `rowHeight` | number | - | Row height in pixels. Overrides the density height. | | `virtual` | - | `"auto"` | Virtualise rows and columns. `auto` virtualises rows above 200 and columns above 20 or when the table is wider than twice its viewport. Enum: `auto`, `true`, `false`. | | `stickyHeader` | boolean | `true` | Keep the header visible while the table scrolls. | | `rowDrag` | boolean \| object | - | Reorder rows by dragging a handle in a leading column, or with Alt+Shift+ArrowUp/Down on a focused row. Not available while the table is sorted (except ascending by the position field), filtered or grouped, in a tree or in server mode; the handle is disabled with a tooltip saying why. Table fires onRowMove; TableInput records the move in its value. `true`, or `{ positionField }` for fractional positions. | | `rowDrag.positionField` | string | - | Dot path of a numeric position field the rows are ordered by. A move gives only the moved row a new position: the midpoint of its new neighbours (a neighbour ∓ 1024 at the ends), renumbering the list in steps of 1024 only when there is no room left. | | `headerMenu` | boolean | `true` | Show the column menu button in each header (on hover or focus): sort, filter, pin, freeze, autosize, hide and the column manager. | | `reorderable` | boolean | `true` | Reorder columns by dragging their headers. | | `keyboard` | boolean \| object | `true` | Keyboard navigation between cells. Grid roles stay on when off, and so do Ctrl/Cmd+C copy and the keys of a focused group header. An object turns it on with options. | | `keyboard.next` | boolean | `false` | After a row button, menu item or single-key action completes, move focus to the next row (queue and triage lists). | | `toolbar` | boolean \| object | `false` | The toolbar above the table. `true` turns on every item; an object turns on the listed items. | | `toolbar.views` | boolean | `false` | Saved view tabs (needs `views`), with the unsaved changes strip. | | `toolbar.search` | boolean | `false` | A search box that writes `view.search` (Cmd/Ctrl+F focuses it while the table has focus). | | `toolbar.quickFilters` | array | - | Column keys to show as quick filter chips. Columns with `options` get a checkbox list (an `in` condition), with a search box over its labels when it has more than 8 options; others open the column filter. | | `toolbar.filter` | boolean | `false` | A Filter button that edits the whole `view.filter`. | | `toolbar.sort` | boolean | `false` | A Sort button to add, remove, reorder and flip sort levels. | | `toolbar.group` | boolean | `false` | A Group button to pick and order group levels (groupable columns). | | `toolbar.columns` | boolean | `false` | A Columns button that opens the column manager. | | `toolbar.density` | boolean | `false` | A compact / default / comfortable density toggle, with a Wrap toggle for `view.wrap`. | | `toolbar.export` | boolean | `false` | An Export button that downloads the view as CSV. | | `views` | array \| null | - | Saved views, from any source (often a request). Selecting a tab loads its view; changes show an unsaved changes strip with Save, Save as and Discard. | | `viewMenu` | array | - | The items of the active view tab's menu, in place of Delete view: for example Rename, Duplicate and Archive. Clicking one fires `onViewMenu` with its `key` and the view `id`. Without it the menu has Delete view, firing `onViewDelete` (disabled on a `locked` view). | | `viewMenu.$.key` | string | - | The item key `onViewMenu` receives. | | `viewMenu.$.label` | string | - | The item text. Defaults to the key. | | `viewMenu.$.danger` | boolean | - | Show the item in the danger colour. | | `viewMenu.$.confirm` | boolean \| string | - | Ask before firing: `true` asks "Are you sure?", a string asks that message. | | `activeView` | string \| number \| object \| null | - | The id of the active saved view (an ObjectId id matches by its hex). Defaults to the first view. Changing it selects that view. | | `persist` | object | - | Keep the user's view between visits. Off by default. The selection is never persisted. | | `persist.key` | string | - | Storage key (localStorage) or query parameter name (url). | | `persist.storage` | string | `"local"` | `local` keeps the view in localStorage, per app and per signed-in user and cleared at sign-out (none in a private window or with blocked storage); `url` writes a compact encoding to the query string, replacing history. Enum: `local`, `url`. | | `emptyText` | string | `"No rows"` | What to show when there are no rows - supports html. | | `loading` | boolean | `false` | Show the loading state: skeleton rows without data, a progress bar with it. | | `pagination` | boolean | `false` | Show the rows in pages of `pageSize` with a pager below the table. Off by default, unlike TableLight: the Table scrolls any number of rows virtually. `true` means what it means on TableLight: pages, with the pager always shown. | | `pageSize` | integer | `50` | Rows per page when `pagination` is on (`view.pageSize` overrides it). | | `providers` | array | - | The enrichment providers columns can call (`kind: enrichment`), the catalogue the add-column picker offers. Each maps, on the server, to the app's `enrich_` endpoint, so a column only calls what the app exposes. | | `providers.$.id` | string | - | The provider id, the column `provider`. | | `providers.$.title` | string | - | The name in the picker. | | `providers.$.description` | string | - | A line under the name in the picker. | | `providers.$.icon` | - | - | An icon for the provider. | | `providers.$.inputs` | array | - | The inputs, `[{ key, title, type, required }]`, mapped to columns or literals in the picker. | | `providers.$.outputs` | array | - | Paths in the result a column can show, `[{ path, title, type }]`; the picker sets the column type from it. | | `providers.$.cost` | number | - | The cost of one call, for the app to show. | | `addColumn` | boolean \| object | - | Show a "+" at the end of the header that opens the add-column picker (onColumnAdd). `true` offers every kind; `{ kinds: [...] }` only those (`input`, `formula`, `enrichment`, `ai`, `extract`). | | `addColumn.kinds` | array | - | The column kinds the picker offers. | | `addRow` | boolean | `false` | Show a "+ New row" row under the table that opens an inline editor for the input columns; Enter adds the row through onRowAdd. | | `addRowText` | string | `"New row"` | Text of the new-row row. | | `inputFieldPrefix` | string | - | Where user-defined input columns added in the picker or by a CSV import keep their values: under this path, then the column key (with `values`, a `notes` column stores at `values.notes`, so a column can never name another field of the row). The column is sent with that `field`, and onRowAdd / onImport values sit at it. Without it, at the key. | | `rawEndpoint` | string | - | The id of an API endpoint the details panel calls for a finished enrichment or ai cell's raw result when the row's `_enrich.` carries none, so rows can leave the raw answers out. Its payload is `{ rowKey, key }` (the row's key and the column key); it returns `{ raw }`. | | `importCsv` | boolean | `false` | Show an Import button in the toolbar: a CSV file (at most 50 MB and 100,000 rows) is parsed in the browser, in slices so the page stays responsive, its headers mapped to input columns (or new text columns), and the rows sent through onImport in batches of 500. | | `summary` | boolean | `true` | Show the summary footer when `summaryRow` is set or any aggregate is in effect: a column `aggregate`, or one the view sets in `view.aggregates`. `false` hides it. | | `summaryRow` | object \| null | - | A row the app computed, such as a report total from the request, shown in the summary footer in place of the aggregates. Each column renders it like a body row, with its `type`, `cell` and `rules`; action columns stay empty. It is not a data row: sorting, filtering, search, selection and the record count leave it out, and `exportCsv` adds it as the last line (in server mode `onExport` produces the file). Group headers keep their aggregates. | | Event | Event Data | Description | | --- | --- | --- | | `onChange` | `{ value, cause }` | Trigger when the table value changes through the table: a sort, a filter or search, a column change (resize, reorder, pin, hide, column manager), or a selection. | | `onSelectionChange` | `{ selected, rows }` | Trigger when the row selection changes. "Select all matching" in the bulk bar (and, in server mode, the header checkbox) selects every row the view matches as `{ all: true, except, filter, search }`: every row matching that filter and search except the `except` keys, so a request can resolve it from the value alone. Changing the filter or search clears such a selection. | | `onRowExpand` | `{ row, rowKey, expanded, needsChildren }` | Trigger when a tree row or an expandable row is expanded or collapsed. With `tree.lazy`, load the row's children here when `needsChildren` is true (skip the load action otherwise) and add them to `data`. | | `onExport` | `{ view, filename, formatted }` | Server mode: trigger when `exportCsv` is called. The browser only holds the loaded blocks, so produce the file from the view, for example with a request and a download action. | | `onRowClick` | `{ row, rowKey, index }` | Trigger when a row is clicked, or activated with Enter. Clicks on buttons, links, menus and `data-event` elements in a cell, and clicks that end a text selection, do not trigger it. With `rowLink`, a plain click runs onRowClick instead of following the link. | | `onRowDoubleClick` | `{ row, rowKey, index }` | Trigger when a row is double clicked. | | `onCellClick` | `{ row, rowKey, column, value }` | Trigger actions when a cell is clicked. Clicks on controls in the cell do not trigger it. | | `onViewSelect` | `{ id }` | Trigger when a saved view tab is selected, after its view loads. | | `onViewSave` | `{ view, id, title, shared }` | Trigger when the user saves the current view: Save (with the active view `id`) or Save as (no `id`, a new view), unless the block has `onViewSaveAs` actions. The app stores views; update `views` (and `activeView`) with the result. | | `onViewSaveAs` | `{ view }` | Trigger when the user clicks Save as, in place of the built-in title and Shared popover, when the block has actions on it. Open your own dialog (for example a modal asking for a name and description), store the view, then update `views` and `activeView` as after `onViewSave`. | | `onViewMenu` | `{ key, id }` | Trigger when the user picks a `viewMenu` item on a saved view tab, after its confirmation when the item has `confirm`. | | `onViewDelete` | `{ id }` | Trigger when the user deletes a saved view from its tab menu (the default menu, without `viewMenu`). | | `onCellEdit` | `{ row, rowKey, column, value, previous }` | Trigger when an edited cell commits. The cell shows the new value with a saving indicator while the event runs. When the actions fail (a Request error or a Throw), the cell reverts and shows the error message. After success the new value shows until the row changes in `data`. Without this event, edits only show in the table. | | `onRowMove` | `{ row, rowKey, fromIndex, toIndex, beforeKey, afterKey, position, positions }` | Trigger when a row is dropped at a new place (`rowDrag`): a drag of its handle, or Alt+Shift+ArrowUp/Down. The rows reorder at once with a saving indicator on the handle while the event runs; when the actions fail the order reverts and the handle shows the error message. After success the new order shows until `data` changes. With `rowDrag.positionField`, save `position` on the moved row (`positions` holds every row whose position changed: normally just this one, all rows when the list had to be renumbered). Without it, save the order from `beforeKey` / `afterKey`. | | `onColumnAdd` | `{ column, position }` | Trigger when a column is added: the add-column picker (`addColumn`), Duplicate or Insert left / right in a user-defined column's header menu, or "Add as column" in the cell details panel. The picker stays open, pending, while the event runs, and shows the error when the actions fail. Store the column and add it to `columns`. | | `onColumnUpdate` | `{ column, previous }` | Trigger when a user-defined column is renamed (inline in its header) or edited (the picker). The rename or picker shows it pending while the event runs and the error when it fails. | | `onColumnDelete` | `{ column }` | Trigger when a user-defined column is deleted from its header menu, after the confirmation. The dialog stays open, pending, while the event runs. | | `onColumnRun` | `{ column, mode, selection }` | Trigger to run an enrichment or ai column: Run in its header menu (all rows, empty cells, errors or stale cells) or "Run selected" in the bulk bar. Enqueue the cells, for example with MongoDBEnrichmentEnqueue; their states then show in the cells. | | `onRowRun` | `{ row, rowKey, columns }` | Trigger when a row's run button is clicked (the trailing column, shown on hover): run every enrichment and ai column of the row. | | `onCellRun` | `{ row, rowKey, column }` | Trigger when one cell is rerun: Rerun in the cell details panel, or the rerun button of a stale cell. | | `onRowAdd` | `{ rowKey, values }` | Trigger when a row is added with "+ New row" (`addRow`). The row shows at the end of the table, marked saving, under `rowKey` while the event runs; after it the row comes from `data` (add it there, for example by refetching). Save the row under `rowKey` (as its `_id`, or at the field the `rowKey` property names): the saved row then replaces the saving one as soon as `data` has it, even while the event still runs; a row saved under another key shows beside the saving row until the event settles. When the actions fail, the row goes and the editor shows the error with the values kept. | | `onImport` | `{ rows, newColumns, batchIndex, batchCount, total }` | Trigger for each batch of rows a CSV import sends (`importCsv`), 500 rows at a time, each awaited before the next; a failed batch stops the import and shows its error. Insert the rows, for example with MongoDBInsertMany, and create `newColumns` once. | | `onCellLink` | `{ link, row, value }` | Triggered when a link, avatar link or relation cell is clicked. The link navigates by itself; this event is for anything else to do. | | `onCellButton` | `{ row, rowKey, value, button, buttonIndex }` | Documentation reference - the event fired is the `eventName` of each button in a `buttons` cell. Define any number of named events on the block, such as `onEdit`. | | `onCellMenuItem` | `{ row, rowKey, value, item, itemIndex }` | Documentation reference - the event fired is the `eventName` of each item in a `menu` cell. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The table root element. | | `/header` | The header row group. | | `/row` | Every body row. | | Slot | Description | | --- | --- | | `toolbarStart` | Blocks at the start of the toolbar, for example a "New deal" button. Blocks sit side by side at their content width (8px gap, wrapping); the slot's `gap`, `align` and `justify` and a block's `layout.flex` still apply. | | `toolbarEnd` | Blocks at the end of the toolbar, side by side like `toolbarStart`. | | `bulkActions` | Blocks in the bulk action bar, shown while rows are selected, side by side at the end of the bar like `toolbarStart`. | | `empty` | Blocks shown instead of the empty state when there are no rows and no filter or search. | | `emptyFiltered` | Blocks shown instead of "No matching rows" and Clear filters when a filter or search hides every row, for example "No deals match this view right now." with an Edit filter button, so the view keeps its filter. | --- url: https://docs.lowdefy.com/md/display-blocks/tablelight.md --- # TableLight A light table for the odd table on a page: a few columns and tens to hundreds of rows, with typed cells (numbers, dates, tags, people, links and more), sorting by header click, row links, row buttons and menus, conditional formatting and a summary row. It renders the Ant Design Table and adds no dependencies. Its config is a subset of the Table block's, so a table that outgrows it changes `type: TableLight` to `type: Table`. ## When to use TableLight `TableLight` is for the quick table here and there: a list of recent orders on a dashboard, the line items of an invoice, a short list of team members with an Edit button. It renders every row, so keep it to tens or hundreds of rows. In development it logs a console warning above 1,000 rows. It has no value and keeps no state an app can read. For column filters, resizing, grouping, selection, editing, saved views, virtualisation or server-side data, use [`Table`](/Table). TableLight's config is a strict subset of Table's, so switching is a change of `type`. The [Table guide](/Table) compares TableLight, Table, TableInput and AgGrid and shows each kind of table as config. A key only Table supports stops the block with an error such as `Block "TableLight" property "columns.0.filterable" is not supported. Use Table for column filters.` The Table-only keys are `rowSelection`, `toolbar`, `views`, `activeView`, `defaultView`, `persist`, `virtual`, `headerMenu`, `reorderable`, `stickyHeader`, `rowHeight`, `maxHeight`, `rowDrag`, `tree`, `expandable`, `keyboard` and `rowVersionField` on the block; `filterable`, `resizable`, `groupable`, `editable`, `validate`, `required`, `default`, `searchable`, `flex` and `maxWidth` on columns; and `width` and `minWidth` on `defaultColumn`. ## Columns A column is an object, or just its key: ```yaml columns: - name # key and field "name", title "Name" - key: owner_name # title "Owner name" field: owner.name - key: amount type: currency cell: currency: EUR aggregate: sum ``` - `key` is the column id and `field` the dot path of the value in the row. Each defaults to the other. - `title` defaults to the key in sentence case, and supports html. `headerTooltip` adds a tooltip to the header. - `type` picks the cell type (below) and `cell` holds that type's options. The option names match the AgGrid `cell` keys, so a column moves over by lifting `cell.type` to `type`. - `align` is `start`, `center` or `end`; number, currency and percent columns default to `end`. - `width` and `minWidth` are in pixels. `pinned: start` or `pinned: end` keeps a column in view while the table scrolls sideways. - Text stays on one line. `wrap: true` wraps it and `ellipsis: 2` clamps it to two lines with the full text on hover. A clamp needs a column `width`. - `children` groups columns under a shared header. - `hidden: true` declares a column without showing it. ## Cell types | type | shows | options in `cell` | | ------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `text` | the value (the default) | | | `number`, `currency`, `percent` | formatted with `Intl.NumberFormat` | `format`, `locale`, `currency`, `decimals`, `minDecimals`, `maxDecimals`, `notation`, `useGrouping`, `negative`, `prefix`, `suffix`, `signColor`, `positiveColor`, `negativeColor`, `zeroColor`, `color`, `thresholds` + `colors` | | `date`, `datetime` | a dayjs format, or relative time with the date on hover | `format`, `relative` | | `boolean` | a label or an icon | `trueLabel`, `falseLabel`, `trueColor`, `falseColor`, `trueIcon`, `falseIcon` | | `tag`, `tags`, `status` | tinted tags, or a dot and a label | `colorMap`, `colorFrom`, `default`, `max` (tags) | | `avatar`, `people` | initials or an image; `people` overlaps several with a +N count | `nameField`, `srcField`, `idField`, `shape`, `link` (avatar), `max` (people) | | `link` | a link that navigates by itself | `pageId`, `href`, `urlQuery`, `pathParams`, `newTab`, `home`, `back`, `input`, `labelField` | | `email`, `phone`, `url` | `mailto:`, `tel:` and external links | `label`, `labelField`, `newTab` (url) | | `relation` | related records as chips, linked to their page | `labelField`, `pageId`, `href`, `urlQuery`, `pathParams`, `newTab` | | `progress`, `rating` | a bar, or stars | `max`, `suffix`, `color`, `thresholds`, `colors`, `showValue`, `nullLabel` (progress) | | `image` | a lazy thumbnail | `width`, `height`, `shape`, `alt`, `altField` | | `html` | a template or a field holding HTML | `template` | | `json` | a one-line preview | | | `buttons` | row buttons | `buttons`, `showOn` | | `menu` | a row menu | `items`, `icon`, `title`, `placement` | Values in `urlQuery`, `pathParams` and every `...Field` option are paths in the row. For `people` they are paths in each person, and for `relation` paths in the related record, where `urlQuery` defaults to `{ _id: _id }` when neither `urlQuery` nor `pathParams` is set. A `url` value with a scheme other than http or https, such as `javascript:`, shows as text, not as a link. ### Options: labels and colours for enum values `options` gives the labels and colours of `tag`, `tags` and `status` values. It takes a list, or a map from value to a label or `{ label, color, icon }`: ```yaml - key: stage type: status options: lead: Lead qualified: { label: Qualified, color: processing } won: { label: Won, color: success } lost: { label: Lost, color: error } ``` Colours are the antd presets (`blue`, `green`, `gold`, ...), the status names (`success`, `processing`, `warning`, `error`) or any CSS colour. The presets and status names follow the theme and dark mode. An enum column sorts in the order its options are declared. A value without an option, in a column without `options` or colour keys, gets a colour picked from the value, so the same value always has the same colour. ### HTML templates An `html` cell renders a [nunjucks](https://mozilla.github.io/nunjucks/) template with `value` and `row`, compiled once per column: ```yaml - key: summary type: html cell: template: '{{ row.title }} {{ value }}' ``` Values are escaped. Use `| safe` to insert HTML held in a field. The output is sanitised, and `data-event` attributes fire block events as in other HTML. ## Conditional formatting `rules` on a column colour or style cells whose value meets a condition. `rowRules` does the same for rows: ```yaml rowRules: - when: { key: overdue, op: isTrue } className: row-warning columns: - key: score type: number rules: - when: { op: gte, value: 8 } color: success - when: { op: lt, value: 5 } color: error ``` A condition is `{ key, op, value }`, or `{ and: [...] }` / `{ or: [...] }` of conditions. In a column rule, a condition without `key` tests the column's own value. Every rule that holds applies, in order. | operators | for | | ------------------------------------------------------------------------------------------------------ | ------------------------------ | | `eq`, `ne`, `in`, `nin`, `empty`, `notEmpty` | every type | | `contains`, `notContains`, `startsWith`, `endsWith` | text, links, tags and statuses | | `gt`, `gte`, `lt`, `lte`, `between` (`value: [from, to]`) | numbers | | `before`, `after`, `between`, `within` (`value: { last: 7, unit: day }` or `{ next: 1, unit: month }`) | dates; `eq` compares by day | | `isTrue`, `isFalse` | booleans | | `in` (any of), `nin` (none of), `contains` (has the value) | tags and people | Text comparisons ignore case, and `empty` matches null, a missing value, an empty string and an empty list. To compare with the current user, use the `_user` operator in the value, for example `value: { _user: id }`. `hidden` and `disabled` on buttons and menu items take the same conditions: `hidden: { when: { key: locked, op: isTrue } }`. ## Sorting Clicking a header sorts ascending, then descending, then back to the data order. The column type sets the order: numbers numerically, dates by time, enums by option order and text alphabetically, ignoring case and reading numbers in text as numbers. Empty values always sort last. Set `sortable: false` on a column, or `defaultColumn: { sortable: false }` for the whole table. ## Rows, clicks and links `rowKey` names the field that identifies a row, and defaults to `_id`, then `id`. Rows with neither get a key per row object, which does not survive a refetch. `rowLink` makes every row a link: ```yaml rowLink: pageId: order urlQuery: _id: _id ``` For a page with a [`path`](/page-paths), give its placeholder values in `pathParams`, also as paths in the row: `rowLink: { pageId: ticket, pathParams: { space: space_id, ticket_id: _id } }`. Table links are block properties, so on a public page a link to a protected page with a path cannot be built while the user is logged out (see [`Link`](/Link)). A plain click navigates, and a Cmd/Ctrl or middle click opens a new tab. With `onRowClick` defined as well, a plain click runs `onRowClick` (to open a drawer, say) and only a modified click follows the link. `onRowClick` receives `{ row, rowKey, index }`, where `index` is the row's position in `data`, whatever the sort. `onCellClick` receives `{ row, rowKey, column: { key, field }, value }`. Clicks on buttons, links, menus and `data-event` elements in a cell, and a mouse drag that selects text, trigger neither. A `link` cell navigates by itself. It also fires `onCellLink` with `{ link, row, value }`, so do not add a `Link` action to that event. ## Buttons and menus ```yaml - key: actions type: buttons cell: showOn: hover buttons: - eventName: onEdit title: Edit icon: edit - eventName: onArchive title: Archive icon: Archive hideTitle: true - key: more type: menu cell: items: - eventName: onDelete title: Delete danger: true ``` Each button or menu item fires the block event named by its `eventName`, with `{ row, rowKey, value, button: { eventName, title }, buttonIndex }` or `{ row, rowKey, value, item: { eventName, title }, itemIndex }`. The button keys are the AgGrid buttons cell keys: `title` / `titleField`, `icon` / `iconField`, `hidden` / `hiddenField`, `disabled` / `disabledField`, `type`, `variant`, `color`, `size`, `shape`, `danger`, `ghost`, `hideTitle`, `tooltip` and `iconPlacement`. An icon-only button shows its title as a tooltip. `showOn: hover` hides the buttons until the row is hovered or has focus. Touch screens always show them. ## Summary row, pagination and layout - A column with `aggregate` adds a summary row: `sum`, `avg`, `min`, `max`, `count`, `countDistinct`, `countEmpty`, `countNotEmpty`, `percentEmpty`, `earliest` or `latest`, calculated over every row. `summary: false` hides it. - Pages show only when there are more rows than `pageSize` (default 50). `pagination: false` shows every row; `pagination: true` always shows the pager. - `height` fixes the body height: rows scroll under a sticky header, and the summary row stays in view. - `size` is `compact`, `default` or `comfortable`. `bordered` and `emptyText` (default "No rows") do what they say. ## Loading states TableLight shows loading the way [`Table`](/TableGuide#loading-states) does, so switching `type` looks the same. With no rows yet and `loading` true (the property, or Lowdefy's own while `onMount` actions run) it shows the real header over skeleton rows shaped like the cells, filling `height` (or 600px) and at most a page. With rows, a refetch keeps them on screen, even while `_request` returns null, and a thin progress bar runs under the header. The skeleton shows after 120 ms and stays at least 300 ms, so fast responses never flash it. There is no spinner overlay. ```yaml - id: recent_orders type: TableLight properties: loading: _request_details: recent_orders.0.loading data: _request: recent_orders columns: [number, customer, total] ``` A block `skeleton` config replaces the table while the block loads, as for any block. ```yaml - id: basic_table type: TableLight properties: columns: - name - key: email type: email - key: role - key: joined type: date data: - _id: 1 name: Sarah Johnson email: sarah@example.com role: Engineer joined: 2023-04-12 - _id: 2 name: Alex Chen email: alex@example.com role: Product joined: 2022-11-01 - _id: 3 name: Maria Garcia email: maria@example.com role: Design joined: 2024-02-20 ``` ```yaml - id: orders_table type: TableLight properties: rowLink: pageId: order urlQuery: _id: _id columns: - key: number title: Order - key: customer type: avatar - key: status type: tag options: pending: label: Pending color: warning shipped: label: Shipped color: processing delivered: label: Delivered color: success cancelled: label: Cancelled color: error - key: total type: currency cell: currency: USD aggregate: sum - key: placed type: date cell: relative: true data: - _id: o1 number: ORD-1042 customer: Sarah Johnson status: delivered total: 129.97 placed: 2026-09-20T10:15:00 - _id: o2 number: ORD-1043 customer: Alex Chen status: shipped total: 54.5 placed: 2026-09-24T16:40:00 - _id: o3 number: ORD-1044 customer: Maria Garcia status: pending total: 310 placed: 2026-09-27T08:05:00 - _id: o4 number: ORD-1045 customer: Jordan Rivera status: cancelled total: 18.99 placed: 2026-09-27T12:30:00 ``` ```yaml - id: cell_types_table type: TableLight properties: columns: - key: company type: relation cell: labelField: name - key: owners type: people - key: stage type: status options: - value: lead label: Lead color: default - value: qualified label: Qualified color: processing - value: won label: Won color: success - key: labels type: tags cell: max: 2 - key: probability type: percent - key: health type: progress - key: rating type: rating - key: active type: boolean - key: website type: url - key: phone type: phone data: - _id: d1 company: _id: c1 name: Acme Ltd owners: - name: Ann Lee - name: Bob Stone - name: Cy Dunn - name: Di Park stage: won labels: - enterprise - renewal - priority probability: 0.9 health: 82 rating: 4 active: true website: acme.example.com phone: +1 555 010 2000 - _id: d2 company: _id: c2 name: Globex owners: - name: Bob Stone stage: qualified labels: - smb probability: 0.4 health: 35 rating: 3 active: false website: https://globex.example.com phone: +1 555 010 3000 ``` ```yaml - id: rules_table type: TableLight properties: size: compact bordered: true rowRules: - when: key: overdue op: isTrue style: fontStyle: italic columns: - key: task - key: score type: number rules: - when: op: gte value: 8 color: success - when: op: lt value: 5 color: error - key: summary type: html ellipsis: 2 width: 260 cell: template: "{{ row.task }}: {{ value }}" - key: overdue type: boolean cell: trueLabel: Overdue falseLabel: On time trueColor: error data: - _id: t1 task: Onboarding score: 9 summary: Welcome pack sent and first call booked. overdue: false - _id: t2 task: Renewal score: 4 summary: Waiting on the signed order form from procurement. overdue: true - _id: t3 task: Upsell score: 6 summary: Demo scheduled for next week. overdue: false ``` ```yaml - id: actions_table type: TableLight properties: columns: - key: name - key: email type: email - key: actions type: buttons align: end cell: showOn: hover buttons: - eventName: onEdit title: Edit icon: edit - eventName: onArchive title: Archive icon: Archive hideTitle: true hidden: when: key: archived op: isTrue - key: more title: "" type: menu width: 48 cell: items: - eventName: onDuplicate title: Duplicate - eventName: onDelete title: Delete danger: true data: - _id: u1 name: Sarah Johnson email: sarah@example.com archived: false - _id: u2 name: Alex Chen email: alex@example.com archived: true events: onEdit: - id: edit_message type: DisplayMessage params: content: _string.concat: - "Edit " - _event: row.name ``` ```yaml - id: grouped_table type: TableLight properties: height: 200 pagination: false columns: - key: region pinned: start width: 140 - title: Q1 children: - key: jan type: currency aggregate: sum width: 120 - key: feb type: currency aggregate: sum width: 120 - key: mar type: currency aggregate: sum width: 120 - title: Q2 children: - key: apr type: currency aggregate: sum width: 120 - key: may type: currency aggregate: sum width: 120 - key: jun type: currency aggregate: sum width: 120 data: - _id: 1 region: North jan: 12000 feb: 13500 mar: 14100 apr: 12900 may: 15200 jun: 16000 - _id: 2 region: South jan: 9800 feb: 10100 mar: 9900 apr: 11200 may: 11800 jun: 12500 - _id: 3 region: East jan: 15300 feb: 14900 mar: 16200 apr: 17100 may: 16800 jun: 18200 - _id: 4 region: West jan: 8700 feb: 9200 mar: 9800 apr: 10400 may: 10900 jun: 11300 - _id: 5 region: Central jan: 11100 feb: 11600 mar: 12000 apr: 12500 may: 13100 jun: 13400 - _id: 6 region: Islands jan: 4300 feb: 4100 mar: 4600 apr: 4900 may: 5200 jun: 5600 ``` ```yaml - id: empty_table type: TableLight properties: emptyText: No orders yet. columns: - number - key: total type: currency data: [] ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `data` | array \| null | - | The rows to show. | | `rowKey` | string | - | The row field that identifies each row. Defaults to `_id`, then `id`. Rows with neither get a key per row object, which does not survive a refetch. | | `columns` | array \| null | - | The columns, in order. | | `defaultColumn` | object | - | Defaults applied to every column. | | `defaultColumn.sortable` | boolean | `true` | Sort by clicking a header. | | `defaultColumn.wrap` | boolean | `false` | Wrap long text. | | `defaultColumn.ellipsis` | integer | - | Clamp text to this many lines. | | `user` | object | - | The user object for `$user` values in `rules`, `rowRules` and button `hidden`/`disabled` conditions, usually `{ _user: true }`. Blocks do not see the session, so conditions read `$user` from this property. | | `rowLink` | object | - | Make rows links. A plain click navigates, Cmd/Ctrl or middle click opens a new tab. Values in `urlQuery` and `pathParams` are row paths. | | `rowLink.pageId` | string | - | The page to open. | | `rowLink.href` | string | - | A URL to open instead of a page. | | `rowLink.urlQuery` | object | - | Query parameters; each value is a path in the row, like `{ _id: _id }`. | | `rowLink.pathParams` | object | - | Values for the page path's placeholders; each value is a path in the row, like `{ ticket_id: _id }`. | | `rowLink.input` | object | - | Input for the page. | | `rowLink.newTab` | boolean | - | Always open in a new tab. | | `rowRules` | array | - | Conditional row formatting: `[{ when, className, style, color }]`, where `when` conditions name columns by `key`. | | `size` | string | `"default"` | Row density. Enum: `compact`, `default`, `comfortable`. | | `bordered` | boolean | `false` | Draw borders around cells. | | `height` | number \| string | - | A fixed body height; the rows scroll under a sticky header and the summary row stays in view. | | `emptyText` | string | - | What to show when there are no rows - supports html. Defaults to "No rows". | | `loading` | boolean | `false` | Show the loading state: skeleton rows while there are no rows yet, a progress bar under the header while rows are shown. The rows stay while `data` is null during a refetch. Lowdefy sets loading while onMount actions run. | | `pagination` | boolean | - | Pages are shown only when there are more rows than `pageSize`. `false` shows every row on one page; `true` always shows the pager. | | `pageSize` | integer | `50` | Rows per page. | | `summary` | boolean | `true` | Show the summary row when a column declares an `aggregate`. `false` hides it. | | Event | Event Data | Description | | --- | --- | --- | | `onRowClick` | `{ row, rowKey, index }` | Trigger actions when a row is clicked. Clicks on buttons, links, menus and `data-event` elements in a cell, and clicks that end a text selection, do not trigger it. With `rowLink`, a plain click runs onRowClick instead of following the link. | | `onCellClick` | `{ row, rowKey, column, value }` | Trigger actions when a cell is clicked. Clicks on controls in the cell do not trigger it. | | `onCellLink` | `{ link, row, value }` | Triggered when a link, avatar link or relation cell is clicked. The link navigates by itself; this event is for anything else to do. | | `onCellButton` | `{ row, rowKey, value, button, buttonIndex }` | Documentation reference - the event fired is the `eventName` of each button in a `buttons` cell. Define any number of named events on the block, such as `onEdit`. | | `onCellMenuItem` | `{ row, rowKey, value, item, itemIndex }` | Documentation reference - the event fired is the `eventName` of each item in a `menu` cell. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The TableLight element. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/tag.md --- # Tag Tag with preset and custom colors, icons, and closable option. ```yaml - id: title_simple type: Tag layout: flex: 0 0 auto properties: title: Feature - id: title_html_bold type: Tag layout: flex: 0 0 auto properties: title: Important Update - id: title_html_styled type: Tag layout: flex: 0 0 auto properties: title: 'NEW' - id: title_fallback type: Tag layout: flex: 0 0 auto ``` ```yaml - id: color_status_row type: Box layout: gap: 8 blocks: - id: color_success type: Tag layout: flex: 0 0 auto properties: title: success color: success - id: color_processing type: Tag layout: flex: 0 0 auto properties: title: processing color: processing - id: color_error type: Tag layout: flex: 0 0 auto properties: title: error color: error - id: color_warning type: Tag layout: flex: 0 0 auto properties: title: warning color: warning - id: color_default type: Tag layout: flex: 0 0 auto properties: title: default color: default - id: color_named_row type: Box layout: gap: 8 blocks: - id: color_blue type: Tag layout: flex: 0 0 auto properties: title: blue color: blue - id: color_green type: Tag layout: flex: 0 0 auto properties: title: green color: green - id: color_gold type: Tag layout: flex: 0 0 auto properties: title: gold color: gold - id: color_purple type: Tag layout: flex: 0 0 auto properties: title: purple color: purple - id: color_volcano type: Tag layout: flex: 0 0 auto properties: title: volcano color: volcano ``` ```yaml - id: variant_filled type: Tag layout: flex: 0 0 auto properties: title: Filled color: blue - id: variant_outlined type: Tag layout: flex: 0 0 auto properties: title: Outlined color: blue variant: outlined - id: variant_solid type: Tag layout: flex: 0 0 auto properties: title: Solid color: blue variant: solid - id: variant_solid_success type: Tag layout: flex: 0 0 auto properties: title: Paid color: success variant: solid ``` ```yaml - id: hex_coral type: Tag layout: flex: 0 0 auto properties: title: Coral color: "#ff6b6b" - id: hex_teal type: Tag layout: flex: 0 0 auto properties: title: Teal color: "#20c997" - id: hex_indigo type: Tag layout: flex: 0 0 auto properties: title: Indigo color: "#4c6ef5" - id: hex_navy type: Tag layout: flex: 0 0 auto properties: title: Navy color: "#1864ab" ``` ```yaml - id: icon_string type: Tag layout: flex: 0 0 auto properties: title: Approved icon: check color: success - id: icon_warning type: Tag layout: flex: 0 0 auto properties: title: Warning icon: warning color: warning - id: icon_star type: Tag layout: flex: 0 0 auto properties: title: Featured icon: star color: gold - id: icon_object_color type: Tag layout: flex: 0 0 auto properties: title: Custom Icon icon: name: Crown color: "#faad14" color: gold - id: icon_object_style type: Tag layout: flex: 0 0 auto properties: title: Large Icon icon: name: Rocket style: fontSize: 16 color: purple ``` ```yaml - id: closable_default type: Tag layout: flex: 0 0 auto properties: title: Removable closable: true - id: closable_colored type: Tag layout: flex: 0 0 auto properties: title: Done closable: true color: success - id: closable_with_icon type: Tag layout: flex: 0 0 auto properties: title: Starred closable: true icon: star - id: closable_custom_icon type: Tag layout: flex: 0 0 auto properties: title: Custom close icon closable: true closeIcon: close-circle color: gold - id: closable_with_event type: Tag layout: flex: 0 0 auto properties: title: Close me closable: true color: processing events: onClose: - id: closable_event_msg type: DisplayMessage params: content: Tag closed! status: info ``` ```yaml - id: link_tag type: Tag layout: flex: 0 0 auto properties: title: Open the docs icon: external-link href: https://docs.lowdefy.com target: _blank - id: disabled_tag type: Tag layout: flex: 0 0 auto properties: title: Archived disabled: true ``` ```yaml - id: click_basic type: Tag layout: flex: 0 0 auto properties: title: Click me color: blue events: onClick: - id: click_basic_msg type: DisplayMessage params: content: Tag clicked! status: info - id: click_with_icon type: Tag layout: flex: 0 0 auto properties: title: View Details icon: view color: geekblue events: onClick: - id: click_icon_msg type: DisplayMessage params: content: Opening details... status: info - id: click_set_state type: Tag layout: flex: 0 0 auto properties: title: Select Category color: purple events: onClick: - id: click_set_state_action type: SetState params: selectedCategory: design ``` ```yaml - id: shortcut_filter type: Tag layout: flex: 0 0 auto properties: title: Toggle Filter icon: filter color: blue events: onClick: shortcut: mod+f try: - id: filter_msg type: DisplayMessage params: content: Filter toggled status: info ``` ```yaml - id: css_shadow type: Tag layout: flex: 0 0 auto class: shadow-md properties: title: Shadow color: blue - id: css_rounded type: Tag layout: flex: 0 0 auto class: rounded-full properties: title: Pill Shape color: green - id: css_uppercase type: Tag layout: flex: 0 0 auto class: uppercase tracking-wider properties: title: Uppercase color: purple - id: style_inline_bold type: Tag layout: flex: 0 0 auto properties: title: Bold Text color: volcano style: .element: fontWeight: bold fontSize: 14px - id: style_inline_padding type: Tag layout: flex: 0 0 auto properties: title: Extra Padding color: cyan style: .element: padding: 4px 16px borderWidth: 2px ``` ```yaml - id: theme_light_blue type: Tag layout: flex: 0 0 auto properties: title: Light Blue theme: defaultColor: "#1677ff" - id: theme_dark type: Tag layout: flex: 0 0 auto properties: title: Dark Tag theme: defaultBg: "#141414" defaultColor: "#ffffff" - id: theme_warm type: Tag layout: flex: 0 0 auto properties: title: Warm Tone theme: defaultColor: "#d46b08" - id: theme_solid_text type: Tag layout: flex: 0 0 auto properties: title: Custom Solid color: "#7c3aed" theme: solidTextColor: "#fde68a" ``` When a user enters an email without a domain, the login page throws an unhandled exception and shows a blank screen. ```yaml - id: issue_card type: Card properties: title: "BUG-1042: Login page crashes on invalid email" size: small blocks: - id: issue_description type: Markdown properties: content: When a user enters an email without a domain, the login page throws an unhandled exception and shows a blank screen. - id: issue_tags_row type: Box layout: gap: 8 blocks: - id: issue_status type: Tag layout: flex: 0 0 auto properties: title: In Progress icon: RefreshCw color: processing - id: issue_priority type: Tag layout: flex: 0 0 auto properties: title: High Priority icon: arrow-up color: error style: .element: fontWeight: bold - id: issue_label_bug type: Tag layout: flex: 0 0 auto properties: title: bug color: red closable: true events: onClose: - id: issue_remove_label type: DisplayMessage params: content: Label removed status: info - id: issue_label_auth type: Tag layout: flex: 0 0 auto properties: title: authentication color: geekblue closable: true events: onClose: - id: issue_remove_auth_label type: DisplayMessage params: content: Label removed status: info - id: issue_actions_row type: Box layout: gap: 8 justify: flex-end blocks: - id: issue_assign_btn type: Button layout: flex: 0 0 auto properties: title: Assign icon: user color: default variant: outlined size: small events: onClick: - id: issue_assign_msg type: DisplayMessage params: content: Assignee updated status: success - id: issue_resolve_btn type: Button layout: flex: 0 0 auto properties: title: Resolve icon: check color: primary variant: solid size: small events: onClick: - id: issue_resolve_msg type: DisplayMessage params: content: Issue marked as resolved status: success ``` Premium over-ear headphones with active noise cancellation, 30-hour battery life, and Bluetooth 5.3 connectivity. **$249.99** ```yaml - id: catalog_card type: Card properties: title: Wireless Noise-Cancelling Headphones size: small blocks: - id: catalog_tags_row type: Box layout: gap: 8 blocks: - id: catalog_new type: Tag layout: flex: 0 0 auto properties: title: NEW color: "#52c41a" style: .element: fontWeight: bold letterSpacing: 1px - id: catalog_category type: Tag layout: flex: 0 0 auto properties: title: Electronics icon: Zap color: blue events: onClick: - id: catalog_cat_click type: Link params: pageId: introduction - id: catalog_stock type: Tag layout: flex: 0 0 auto properties: title: In Stock icon: check color: success - id: catalog_rating type: Tag layout: flex: 0 0 auto properties: title: 4.8 Stars icon: star color: gold - id: catalog_description type: Markdown properties: content: Premium over-ear headphones with active noise cancellation, 30-hour battery life, and Bluetooth 5.3 connectivity. - id: catalog_price_row type: Box layout: gap: 8 align: center blocks: - id: catalog_price type: Markdown layout: flex: 0 0 auto properties: content: "**$249.99**" - id: catalog_discount type: Tag layout: flex: 0 0 auto properties: title: 20% OFF color: red style: .element: fontWeight: bold - id: catalog_add_btn type: Button layout: flex: 0 0 auto properties: title: Add to Cart icon: ShoppingCart color: primary variant: solid size: small events: onClick: - id: catalog_add_msg type: DisplayMessage params: content: Added to cart! status: success ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `closable` | boolean | `false` | Allow tag to be closed. | | `closeIcon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to customize the close icon of a closable tag. | | `closeIcon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `closeIcon.color` | string | - | Icon color. | | `closeIcon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `closeIcon.rotate` | number | - | Number of degrees to rotate the icon. | | `closeIcon.spin` | boolean | - | Continuously spin the icon with animation. | | `closeIcon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `closeIcon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `closeIcon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `closeIcon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `color` | string | - | Color of the Tag. Preset options are success, processing, error, warning, default, blue, cyan, geekblue, gold, green, lime, magenta, orange, purple, red, volcano, or alternatively any hex color. | | `disabled` | boolean | `false` | Disable the tag. A disabled tag cannot be clicked or closed. | | `href` | string | - | Render the tag as a link to this URL. | | `target` | string | - | Where to open the href link, like `_blank` for a new tab. | | `title` | string | - | Content title of tag - supports html. | | `variant` | string | `"filled"` | Tag style variant. `filled` shows a tinted background, `outlined` adds a border and `solid` fills the tag with its color. Enum: `filled`, `solid`, `outlined`. | | `icon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to customize tag icon. | | `icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `icon.color` | string | - | Icon color. | | `icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `icon.rotate` | number | - | Number of degrees to rotate the icon. | | `icon.spin` | boolean | - | Continuously spin the icon with animation. | | `icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design tag tokens](https://ant.design/components/tag#design-token). | | `theme.defaultBg` | string | `"#fafafa"` | Default background color of the tag. | | `theme.defaultColor` | string | `"rgba(0, 0, 0, 0.88)"` | Default text color of the tag. | | `theme.solidTextColor` | string | `"#fff"` | Text color for solid (filled) tags, determined by background brightness. | | Event | Event Data | Description | | --- | --- | --- | | `onClick` | \- | Called when Tag is clicked. Renders a shortcut badge when a shortcut is configured. | | `onClose` | \- | Called when Tag close icon is clicked. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Tag element. | | `/icon` | The icon in the Tag. | | `/closeIcon` | The close icon of a closable Tag. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/title.md --- # Title Heading title with levels, copyable, ellipsis, and text styles. h1. Heading Level 1 h2. Heading Level 2 h3. Heading Level 3 h4. Heading Level 4 h5. Heading Level 5 ```yaml - id: level_h1 type: Title properties: content: h1. Heading Level 1 level: 1 - id: level_h2 type: Title properties: content: h2. Heading Level 2 level: 2 - id: level_h3 type: Title properties: content: h3. Heading Level 3 level: 3 - id: level_h4 type: Title properties: content: h4. Heading Level 4 level: 4 - id: level_h5 type: Title properties: content: h5. Heading Level 5 level: 5 ``` Default Title Secondary Title Success Title Warning Title Danger Title ```yaml - id: type_default type: Title properties: content: Default Title level: 4 - id: type_secondary type: Title properties: content: Secondary Title level: 4 type: secondary - id: type_success type: Title properties: content: Success Title level: 4 type: success - id: type_warning type: Title properties: content: Warning Title level: 4 type: warning - id: type_danger type: Title properties: content: Danger Title level: 4 type: danger ``` Blue Title Green Title Purple Title Coral Title ```yaml - id: color_blue type: Title properties: content: Blue Title level: 4 color: "#1677ff" - id: color_green type: Title properties: content: Green Title level: 4 color: "#52c41a" - id: color_purple type: Title properties: content: Purple Title level: 4 color: "#722ed1" - id: color_coral type: Title properties: content: Coral Title level: 3 color: "#ff6b6b" ``` Code Heading Level 3 const config = lowdefy.init() npm install lowdefy ```yaml - id: code_h3 type: Title properties: content: Code Heading Level 3 level: 3 code: true - id: code_h4 type: Title properties: content: const config = lowdefy.init() level: 4 code: true - id: code_h5 type: Title properties: content: npm install lowdefy level: 5 code: true ``` Italic Title Underlined Title Highlighted Title Deleted Title Italic & Underlined Esc ```yaml - id: deco_italic type: Title properties: content: Italic Title level: 4 italic: true - id: deco_underline type: Title properties: content: Underlined Title level: 4 underline: true - id: deco_mark type: Title properties: content: Highlighted Title level: 4 mark: true - id: deco_delete type: Title properties: content: Deleted Title level: 4 delete: true - id: deco_combined type: Title properties: content: Italic & Underlined level: 4 italic: true underline: true - id: deco_keyboard type: Title properties: content: Esc level: 4 keyboard: true ``` Disabled Section Heading Disabled Subsection Disabled Caption ```yaml - id: disabled_h3 type: Title properties: content: Disabled Section Heading level: 3 disabled: true - id: disabled_h4 type: Title properties: content: Disabled Subsection level: 4 disabled: true - id: disabled_h5 type: Title properties: content: Disabled Caption level: 5 disabled: true ``` Title with bold and italic words Visit the Lowdefy website Status: Online / Offline ```yaml - id: html_bold_italic type: Title properties: content: Title with bold and italic words level: 4 - id: html_link type: Title properties: content: Visit the Lowdefy website level: 4 - id: html_colored_spans type: Title properties: content: 'Status: Online / Offline' level: 4 ``` Copy this title Title with custom copy text Hover the copy icon Custom copy icon Two-state copy icons ```yaml - id: copyable_simple type: Title properties: content: Copy this title level: 4 copyable: true - id: copyable_custom_text type: Title properties: content: Title with custom copy text level: 4 copyable: text: This custom text is copied instead of the visible title. - id: copyable_tooltips type: Title properties: content: Hover the copy icon level: 4 copyable: tooltips: - Click to copy - Copied! - id: copyable_custom_icon type: Title properties: content: Custom copy icon level: 4 copyable: icon: Files - id: copyable_two_icons type: Title properties: content: Two-state copy icons level: 4 copyable: icon: - copy - check tooltips: - Copy text - Done! ``` This is a very long title that should be truncated with an ellipsis when it overflows the available space in a single line of the container. This is an extremely long title that demonstrates multi-row ellipsis truncation. When the content exceeds the specified number of rows, it will be truncated and show an ellipsis indicator at the end of the visible text area. This long title can be expanded by clicking the expand control. It demonstrates how ellipsis with expandable works to let users reveal the full heading when needed. This title has a custom suffix appended after the ellipsis truncation point so readers know more content exists. This long title can be expanded and collapsed again. The expand and collapse buttons use custom text set with the symbol option. Hover this truncated title to see the full heading in a tooltip, which helps when titles come from user data. ```yaml - id: ellipsis_single type: Title properties: content: This is a very long title that should be truncated with an ellipsis when it overflows the available space in a single line of the container. level: 4 ellipsis: true - id: ellipsis_rows_2 type: Title properties: content: This is an extremely long title that demonstrates multi-row ellipsis truncation. When the content exceeds the specified number of rows, it will be truncated and show an ellipsis indicator at the end of the visible text area. level: 5 ellipsis: rows: 2 - id: ellipsis_expandable type: Title properties: content: This long title can be expanded by clicking the expand control. It demonstrates how ellipsis with expandable works to let users reveal the full heading when needed. level: 5 ellipsis: rows: 1 expandable: true - id: ellipsis_suffix type: Title properties: content: This title has a custom suffix appended after the ellipsis truncation point so readers know more content exists. level: 4 ellipsis: rows: 1 suffix: " /Read More" - id: ellipsis_collapsible type: Title properties: content: This long title can be expanded and collapsed again. The expand and collapse buttons use custom text set with the symbol option. level: 5 ellipsis: rows: 1 expandable: collapsible symbol: - more - less - id: ellipsis_tooltip type: Title properties: content: Hover this truncated title to see the full heading in a tooltip, which helps when titles come from user data. level: 4 ellipsis: tooltip: true ``` Tailwind Background Tailwind Left Border Uppercase Tracking Inline Border Bottom Inline Text Shadow ```yaml - id: css_tailwind_bg type: Title class: bg-bg-layout px-4 py-2 rounded-lg properties: content: Tailwind Background level: 4 - id: css_tailwind_border type: Title class: border-l-4 border-blue-500 pl-3 properties: content: Tailwind Left Border level: 4 - id: css_tailwind_uppercase type: Title class: uppercase tracking-widest properties: content: Uppercase Tracking level: 5 - id: css_inline_border_bottom type: Title properties: content: Inline Border Bottom level: 4 style: .element: borderBottom: "2px solid #1677ff" paddingBottom: 8px - id: css_inline_shadow type: Title properties: content: Inline Text Shadow level: 3 color: "#1677ff" style: .element: textShadow: 2px 2px 4px rgba(22, 119, 255, 0.3) ``` Large H4 Title Light Weight Title Teal Success Title Extra Margin Title ```yaml - id: theme_large_h4 type: Title properties: content: Large H4 Title level: 4 theme: fontSizeHeading4: 28 - id: theme_light_weight type: Title properties: content: Light Weight Title level: 3 theme: fontWeightStrong: 300 - id: theme_custom_success type: Title properties: content: Teal Success Title level: 4 type: success theme: colorSuccess: "#13c2c2" - id: theme_spacing type: Title properties: content: Extra Margin Title level: 4 theme: titleMarginTop: 0 titleMarginBottom: 0 ``` Engineering Building Config-Driven Web Applications Learn how Lowdefy simplifies complex application development by replacing boilerplate code with declarative YAML configuration. ```yaml - id: article_card type: Card properties: size: small blocks: - id: article_category type: Title properties: content: Engineering level: 5 type: secondary - id: article_headline type: Title properties: content: Building Config-Driven Web Applications level: 2 - id: article_subtitle type: Paragraph properties: content: Learn how Lowdefy simplifies complex application development by replacing boilerplate code with declarative YAML configuration. type: secondary - id: article_meta type: Box layout: gap: 12 blocks: - id: article_author type: Button layout: flex: 0 0 auto properties: title: By Jane Smith icon: user color: default variant: text size: small - id: article_date type: Button layout: flex: 0 0 auto properties: title: March 12, 2026 icon: calendar color: default variant: text size: small ``` Dashboard Overview Monitor your application performance and key metrics. Active Users 12,847 Error Rate 0.12% Requests / min 3,204 Recent Activity ```yaml - id: dashboard_header type: Box layout: gap: 0 blocks: - id: dashboard_title type: Title properties: content: Dashboard Overview level: 2 - id: dashboard_desc type: Paragraph properties: content: Monitor your application performance and key metrics. type: secondary - id: dashboard_cards type: Box layout: gap: 12 blocks: - id: dashboard_stat_users type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: stat_users_label type: Title properties: content: Active Users level: 5 type: secondary - id: stat_users_value type: Title properties: content: 12,847 level: 2 type: success - id: dashboard_stat_errors type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: stat_errors_label type: Title properties: content: Error Rate level: 5 type: secondary - id: stat_errors_value type: Title properties: content: 0.12% level: 2 type: danger - id: dashboard_stat_requests type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: stat_requests_label type: Title properties: content: Requests / min level: 5 type: secondary - id: stat_requests_value type: Title properties: content: 3,204 level: 2 - id: dashboard_section_title type: Title properties: content: Recent Activity level: 4 copyable: true events: onCopy: - id: copy_msg type: DisplayMessage params: content: Section title copied. status: info ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `actions` | object | - | Configure the bar of action buttons, such as copy and expand. | | `actions.placement` | string | `"end"` | Place the action buttons before or after the text. Enum: `start`, `end`. | | `code` | boolean | `false` | Apply code style. | | `color` | string | - | Title color. | | `content` | string | - | Title text content - supports html. | | `copyable` | boolean \| object | `false` | Provide copy text button. | | `copyable.text` | string | - | Paragraph text to copy when clicked. | | `copyable.icon` | string \| object \| array | - | Copy icon, can be an array or two icons for before and after clicked. | | `copyable.tooltips` | string \| array | - | Tooltip text, can be an array or two strings for before and after clicked. | | `delete` | boolean | `false` | Apply deleted (strikethrough) style. | | `disabled` | boolean | `false` | Apply disabled style. | | `ellipsis` | boolean \| object | `false` | Display ellipsis when text overflows a single line. | | `ellipsis.rows` | number | - | Max rows of content. | | `ellipsis.expandable` | boolean \| string | - | Show a button that expands the hidden content. Set to 'collapsible' to also allow collapsing it again. Enum: `true`, `false`, `collapsible`. | | `ellipsis.defaultExpanded` | boolean | `false` | Start with the content expanded. | | `ellipsis.suffix` | string | - | Suffix of ellipses content. | | `ellipsis.symbol` | string \| array | - | Text of the expand button, or an array of two strings for the expand and collapse buttons. | | `ellipsis.tooltip` | boolean \| string | - | Show a tooltip when the text is truncated. true shows the full text, a string shows that text. | | `italic` | boolean | `false` | Apply italic style. | | `keyboard` | boolean | `false` | Apply keyboard key style. | | `level` | number | `1` | Set title type. Matches with h1, h2, h3 and h4. Enum: `1`, `2`, `3`, `4`, `5`. | | `mark` | boolean | `false` | Apply marked (highlighted) style. | | `type` | string | `"default"` | Additional types. Don't specify for default. Enum: `default`, `secondary`, `warning`, `danger`, `success`. | | `underline` | boolean | `false` | Apply underline style. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design typography tokens](https://ant.design/components/typography#design-token). | | `theme.titleMarginBottom` | string \| number | `"0.5em"` | Margin bottom of title, as a CSS length or a number of pixels. | | `theme.titleMarginTop` | string \| number | `"1.2em"` | Margin top of title, as a CSS length or a number of pixels. | | `theme.fontSizeHeading1` | number | `38` | Font size of h1 heading. | | `theme.fontSizeHeading2` | number | `30` | Font size of h2 heading. | | `theme.fontSizeHeading3` | number | `24` | Font size of h3 heading. | | `theme.fontSizeHeading4` | number | `20` | Font size of h4 heading. | | `theme.fontSizeHeading5` | number | `16` | Font size of h5 heading. | | `theme.lineHeightHeading1` | number | `1.2105` | Line height of h1 heading. | | `theme.lineHeightHeading2` | number | `1.2667` | Line height of h2 heading. | | `theme.lineHeightHeading3` | number | `1.3333` | Line height of h3 heading. | | `theme.lineHeightHeading4` | number | `1.4` | Line height of h4 heading. | | `theme.lineHeightHeading5` | number | `1.5` | Line height of h5 heading. | | `theme.fontWeightStrong` | number | `600` | Font weight for strong text. | | `theme.fontFamilyCode` | string | - | Font family for code style text. | | `theme.colorText` | string | - | Default text color. | | `theme.colorTextSecondary` | string | - | Secondary text color. | | `theme.colorSuccess` | string | - | Success text color. | | `theme.colorWarning` | string | - | Warning text color. | | `theme.colorError` | string | - | Danger/error text color. | | `theme.colorTextDisabled` | string | - | Disabled text color. | | `theme.colorLink` | string | - | Link color. | | Event | Event Data | Description | | --- | --- | --- | | `onExpand` | `{ expanded }` | Trigger action when the ellipsis expand or collapse button is clicked. | | `onCopy` | \- | Trigger action when copy text is clicked. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Title element. | | `/copyableIcon` | The copyable icon in the Title. | | `/actions` | The bar of action buttons, such as copy and expand, next to the text. | No slots defined. --- url: https://docs.lowdefy.com/md/display-blocks/tour.md --- # Tour Guided tour with steps, masks, and keyboard navigation. ```yaml - id: basic_btns_row type: Box layout: gap: 8 blocks: - id: basic_steps_start_btn type: Button layout: flex: 0 0 auto properties: title: 3 Steps color: primary variant: outlined size: small events: onClick: - id: basic_steps_start type: SetState params: basic_steps_tour_open: true - id: basic_single_step_start_btn type: Button layout: flex: 0 0 auto properties: title: Single Step color: primary variant: outlined size: small events: onClick: - id: basic_single_step_start type: SetState params: basic_single_step_tour_open: true - id: basic_detailed_start_btn type: Button layout: flex: 0 0 auto properties: title: 5 Steps color: primary variant: outlined size: small events: onClick: - id: basic_detailed_start type: SetState params: basic_detailed_tour_open: true - id: basic_steps_tour type: Tour properties: open: _state: basic_steps_tour_open steps: - title: Welcome description: This is the first step of the tour. - title: Explore Features description: Discover the key features available in the dashboard. - title: Get Started description: You are all set to begin using the application. events: onClose: - id: basic_steps_tour_close type: SetState params: basic_steps_tour_open: false onFinish: - id: basic_steps_tour_finish type: SetState params: basic_steps_tour_open: false - id: basic_single_step_tour type: Tour properties: open: _state: basic_single_step_tour_open steps: - title: Quick Tip description: A single-step tour works well for brief announcements. events: onClose: - id: basic_single_step_tour_close type: SetState params: basic_single_step_tour_open: false onFinish: - id: basic_single_step_tour_finish type: SetState params: basic_single_step_tour_open: false - id: basic_detailed_tour type: Tour properties: open: _state: basic_detailed_tour_open steps: - title: Step 1 of 5 description: Tours support many steps for longer walkthroughs. - title: Step 2 of 5 description: Each step can have its own title and description. - title: Step 3 of 5 description: Users navigate with Next and Previous buttons. - title: Step 4 of 5 description: Steps are zero-indexed internally. - title: Step 5 of 5 description: The last step shows a Finish button instead of Next. events: onClose: - id: basic_detailed_tour_close type: SetState params: basic_detailed_tour_open: false onFinish: - id: basic_detailed_tour_finish type: SetState params: basic_detailed_tour_open: false ``` ```yaml - id: type_btns_row type: Box layout: gap: 8 blocks: - id: type_default_start_btn type: Button layout: flex: 0 0 auto properties: title: Default color: primary variant: outlined size: small events: onClick: - id: type_default_start type: SetState params: type_default_tour_open: true - id: type_primary_start_btn type: Button layout: flex: 0 0 auto properties: title: Primary color: primary variant: outlined size: small events: onClick: - id: type_primary_start type: SetState params: type_primary_tour_open: true - id: type_per_step_start_btn type: Button layout: flex: 0 0 auto properties: title: Per Step color: primary variant: outlined size: small events: onClick: - id: type_per_step_start type: SetState params: type_per_step_tour_open: true - id: type_default_tour type: Tour properties: open: _state: type_default_tour_open type: default steps: - title: Default Type description: Light background with dark text. Blends naturally with light-themed interfaces. - title: Subtle Guidance description: The default type is understated, ideal for non-intrusive tours. events: onClose: - id: type_default_tour_close type: SetState params: type_default_tour_open: false onFinish: - id: type_default_tour_finish type: SetState params: type_default_tour_open: false - id: type_primary_tour type: Tour properties: open: _state: type_primary_tour_open type: primary steps: - title: Primary Type description: Bold colored background using the theme primary color. - title: High Visibility description: Primary tours draw attention and suit critical onboarding flows. events: onClose: - id: type_primary_tour_close type: SetState params: type_primary_tour_open: false onFinish: - id: type_primary_tour_finish type: SetState params: type_primary_tour_open: false - id: type_per_step_row type: Box layout: gap: 16 blocks: - id: type_per_step_btn_1 type: Button layout: flex: 0 0 auto properties: title: Introduction color: default variant: outlined - id: type_per_step_btn_2 type: Button layout: flex: 0 0 auto properties: title: Key Feature color: primary variant: solid - id: type_per_step_tour type: Tour properties: open: _state: type_per_step_tour_open type: default steps: - title: Introduction description: This step inherits the default type from the tour. target: type_per_step_btn_1 type: default - title: Key Feature description: This step overrides to primary to draw extra attention. target: type_per_step_btn_2 type: primary events: onClose: - id: type_per_step_tour_close type: SetState params: type_per_step_tour_open: false onFinish: - id: type_per_step_tour_finish type: SetState params: type_per_step_tour_open: false ``` ```yaml - id: target_btns_row type: Box layout: gap: 8 blocks: - id: target_tour_start_btn type: Button layout: flex: 0 0 auto properties: title: Button Targets color: primary variant: outlined size: small events: onClick: - id: target_tour_start type: SetState params: target_tour_open: true - id: target_mixed_tour_start_btn type: Button layout: flex: 0 0 auto properties: title: Mixed Targets color: primary variant: outlined size: small events: onClick: - id: target_mixed_tour_start type: SetState params: target_mixed_tour_open: true - id: target_row type: Box layout: gap: 16 blocks: - id: target_upload_btn type: Button layout: flex: 0 0 auto properties: title: Upload color: primary variant: solid icon: upload - id: target_save_btn type: Button layout: flex: 0 0 auto properties: title: Save Draft color: green variant: outlined icon: save - id: target_share_btn type: Button layout: flex: 0 0 auto properties: title: Share color: purple variant: solid icon: share - id: target_tour type: Tour properties: open: _state: target_tour_open steps: - title: Upload Files description: Click here to upload documents or images to the workspace. target: target_upload_btn - title: Save Progress description: Save your current work as a draft to continue later. target: target_save_btn - title: Share with Team description: Share your work with team members via link or invitation. target: target_share_btn events: onClose: - id: target_tour_close type: SetState params: target_tour_open: false onFinish: - id: target_tour_finish type: SetState params: target_tour_open: false - id: target_mixed_row type: Box layout: gap: 16 align: center blocks: - id: target_mixed_input type: TextInput layout: flex: 0 0 240px properties: label: disabled: true placeholder: Search... - id: target_mixed_switch type: Switch layout: flex: 0 0 auto properties: label: disabled: true - id: target_mixed_tour type: Tour properties: open: _state: target_mixed_tour_open steps: - title: Search Bar description: Use the search bar to quickly find items. target: target_mixed_input - title: Toggle Setting description: Toggle this switch to enable or disable notifications. target: target_mixed_switch events: onClose: - id: target_mixed_tour_close type: SetState params: target_mixed_tour_open: false onFinish: - id: target_mixed_tour_finish type: SetState params: target_mixed_tour_open: false ``` ```yaml - id: placement_btns_row type: Box layout: gap: 8 blocks: - id: placement_tour_start_btn type: Button layout: flex: 0 0 auto properties: title: Cardinal color: primary variant: outlined size: small events: onClick: - id: placement_tour_start type: SetState params: placement_tour_open: true - id: placement_fine_start_btn type: Button layout: flex: 0 0 auto properties: title: Fine Placement color: primary variant: outlined size: small events: onClick: - id: placement_fine_start type: SetState params: placement_fine_tour_open: true - id: placement_level_start_btn type: Button layout: flex: 0 0 auto properties: title: Tour-Level color: primary variant: outlined size: small events: onClick: - id: placement_level_start type: SetState params: placement_tour_level_open: true - id: placement_row type: Box layout: gap: 16 justify: center blocks: - id: placement_top_btn type: Button layout: flex: 0 0 auto properties: title: Top color: primary variant: outlined - id: placement_right_btn type: Button layout: flex: 0 0 auto properties: title: Right color: green variant: outlined - id: placement_bottom_btn type: Button layout: flex: 0 0 auto properties: title: Bottom color: purple variant: outlined - id: placement_left_btn type: Button layout: flex: 0 0 auto properties: title: Left color: orange variant: outlined - id: placement_tour type: Tour properties: open: _state: placement_tour_open steps: - title: Top Placement description: Guide card appears above the target element. target: placement_top_btn placement: top - title: Right Placement description: Guide card appears to the right of the target. target: placement_right_btn placement: right - title: Bottom Placement description: Guide card appears below the target element. target: placement_bottom_btn placement: bottom - title: Left Placement description: Guide card appears to the left of the target. target: placement_left_btn placement: left events: onClose: - id: placement_tour_close type: SetState params: placement_tour_open: false onFinish: - id: placement_tour_finish type: SetState params: placement_tour_open: false - id: placement_fine_tour type: Tour properties: open: _state: placement_fine_tour_open steps: - title: Top Left description: Aligned to the top-left corner of the target. placement: topLeft - title: Bottom Right description: Aligned to the bottom-right corner of the target. placement: bottomRight - title: Right Top description: On the right side, aligned to the top. placement: rightTop events: onClose: - id: placement_fine_tour_close type: SetState params: placement_fine_tour_open: false onFinish: - id: placement_fine_tour_finish type: SetState params: placement_fine_tour_open: false - id: placement_tour_level type: Tour properties: open: _state: placement_tour_level_open placement: bottom steps: - title: Tour-Level Placement description: Setting placement on the tour applies to all steps by default. - title: Consistent Position description: All steps appear below the target unless overridden at the step level. events: onClose: - id: placement_tour_level_close type: SetState params: placement_tour_level_open: false onFinish: - id: placement_tour_level_finish type: SetState params: placement_tour_level_open: false ``` ```yaml - id: mask_btns_row type: Box layout: gap: 8 blocks: - id: mask_enabled_start_btn type: Button layout: flex: 0 0 auto properties: title: Mask On color: primary variant: outlined size: small events: onClick: - id: mask_enabled_start type: SetState params: mask_enabled_tour_open: true - id: mask_disabled_start_btn type: Button layout: flex: 0 0 auto properties: title: Mask Off color: primary variant: outlined size: small events: onClick: - id: mask_disabled_start type: SetState params: mask_disabled_tour_open: true - id: mask_custom_start_btn type: Button layout: flex: 0 0 auto properties: title: Custom Mask color: primary variant: outlined size: small events: onClick: - id: mask_custom_start type: SetState params: mask_custom_tour_open: true - id: mask_per_step_start_btn type: Button layout: flex: 0 0 auto properties: title: Per Step color: primary variant: outlined size: small events: onClick: - id: mask_per_step_start type: SetState params: mask_per_step_tour_open: true - id: mask_row type: Box layout: gap: 16 blocks: - id: mask_target_btn type: Button layout: flex: 0 0 auto properties: title: Masked Target color: primary variant: solid - id: mask_enabled_tour type: Tour properties: open: _state: mask_enabled_tour_open mask: true steps: - title: Mask Enabled description: The background is dimmed, drawing focus to the tour card. target: mask_target_btn events: onClose: - id: mask_enabled_tour_close type: SetState params: mask_enabled_tour_open: false onFinish: - id: mask_enabled_tour_finish type: SetState params: mask_enabled_tour_open: false - id: mask_disabled_tour type: Tour properties: open: _state: mask_disabled_tour_open mask: false steps: - title: Mask Disabled description: No overlay is shown. The page remains fully visible and interactive. events: onClose: - id: mask_disabled_tour_close type: SetState params: mask_disabled_tour_open: false onFinish: - id: mask_disabled_tour_finish type: SetState params: mask_disabled_tour_open: false - id: mask_custom_tour type: Tour style: .mask: boxShadow: "inset 0 0 15px #333" properties: open: _state: mask_custom_tour_open mask: color: rgba(80, 0, 130, 0.5) steps: - title: Custom Mask description: A purple-tinted overlay with an inner shadow effect. target: mask_target_btn events: onClose: - id: mask_custom_tour_close type: SetState params: mask_custom_tour_open: false onFinish: - id: mask_custom_tour_finish type: SetState params: mask_custom_tour_open: false - id: mask_per_step_row type: Box layout: gap: 16 blocks: - id: mask_per_step_btn_1 type: Button layout: flex: 0 0 auto properties: title: Masked Step color: primary variant: solid - id: mask_per_step_btn_2 type: Button layout: flex: 0 0 auto properties: title: Unmasked Step color: default variant: outlined - id: mask_per_step_tour type: Tour properties: open: _state: mask_per_step_tour_open mask: true steps: - title: Masked description: This step shows the mask overlay. target: mask_per_step_btn_1 mask: true - title: Unmasked description: This step hides the mask for a clear view. target: mask_per_step_btn_2 mask: false events: onClose: - id: mask_per_step_tour_close type: SetState params: mask_per_step_tour_open: false onFinish: - id: mask_per_step_tour_finish type: SetState params: mask_per_step_tour_open: false ``` ```yaml - id: arrow_btns_row type: Box layout: gap: 8 blocks: - id: arrow_enabled_start_btn type: Button layout: flex: 0 0 auto properties: title: Arrow On color: primary variant: outlined size: small events: onClick: - id: arrow_enabled_start type: SetState params: arrow_enabled_tour_open: true - id: arrow_disabled_start_btn type: Button layout: flex: 0 0 auto properties: title: Arrow Off color: primary variant: outlined size: small events: onClick: - id: arrow_disabled_start type: SetState params: arrow_disabled_tour_open: true - id: arrow_centered_start_btn type: Button layout: flex: 0 0 auto properties: title: Centered color: primary variant: outlined size: small events: onClick: - id: arrow_centered_start type: SetState params: arrow_centered_tour_open: true - id: arrow_per_step_start_btn type: Button layout: flex: 0 0 auto properties: title: Per Step color: primary variant: outlined size: small events: onClick: - id: arrow_per_step_start type: SetState params: arrow_per_step_tour_open: true - id: arrow_enabled_tour type: Tour properties: open: _state: arrow_enabled_tour_open arrow: true steps: - title: Arrow Visible description: An arrow points from the guide card toward the target element. - title: Visual Connection description: Arrows help users understand which element the step references. events: onClose: - id: arrow_enabled_tour_close type: SetState params: arrow_enabled_tour_open: false onFinish: - id: arrow_enabled_tour_finish type: SetState params: arrow_enabled_tour_open: false - id: arrow_disabled_tour type: Tour properties: open: _state: arrow_disabled_tour_open arrow: false steps: - title: No Arrow description: Without arrows, the guide card appears as a clean floating panel. events: onClose: - id: arrow_disabled_tour_close type: SetState params: arrow_disabled_tour_open: false onFinish: - id: arrow_disabled_tour_finish type: SetState params: arrow_disabled_tour_open: false - id: arrow_centered_tour type: Tour properties: open: _state: arrow_centered_tour_open arrow: pointAtCenter: true steps: - title: Centered Arrow description: The arrow points precisely at the center of the target element. events: onClose: - id: arrow_centered_tour_close type: SetState params: arrow_centered_tour_open: false onFinish: - id: arrow_centered_tour_finish type: SetState params: arrow_centered_tour_open: false - id: arrow_per_step_row type: Box layout: gap: 16 blocks: - id: arrow_per_step_btn_1 type: Button layout: flex: 0 0 auto properties: title: With Arrow color: primary variant: solid - id: arrow_per_step_btn_2 type: Button layout: flex: 0 0 auto properties: title: Centered Arrow color: green variant: solid - id: arrow_per_step_tour type: Tour properties: open: _state: arrow_per_step_tour_open steps: - title: Default Arrow description: This step shows the default arrow. target: arrow_per_step_btn_1 arrow: true - title: Centered Arrow description: This step points the arrow at the center of the target. target: arrow_per_step_btn_2 arrow: pointAtCenter: true events: onClose: - id: arrow_per_step_tour_close type: SetState params: arrow_per_step_tour_open: false onFinish: - id: arrow_per_step_tour_finish type: SetState params: arrow_per_step_tour_open: false ``` ```yaml - id: cover_btns_row type: Box layout: gap: 8 blocks: - id: cover_tour_start_btn type: Button layout: flex: 0 0 auto properties: title: Cover Tour color: primary variant: outlined size: small events: onClick: - id: cover_tour_start type: SetState params: cover_tour_open: true - id: cover_target_tour_start_btn type: Button layout: flex: 0 0 auto properties: title: With Targets color: primary variant: outlined size: small events: onClick: - id: cover_target_tour_start type: SetState params: cover_target_tour_open: true - id: cover_tour type: Tour properties: open: _state: cover_tour_open steps: - title: Welcome to the Dashboard description: Get familiar with the main interface and its key areas. cover: https://gw.alipayobjects.com/zos/bmw-prod/e9a9b1e8-5e62-4e12-a3b7-e3695b0a5f3c.png - title: Navigation Menu description: Use the sidebar to navigate between different sections. cover: https://gw.alipayobjects.com/zos/bmw-prod/ed5cf3c4-5e34-43e5-9c39-fe131c7cf88e.png - title: All Done description: You are ready to start using the application. events: onClose: - id: cover_tour_close type: SetState params: cover_tour_open: false onFinish: - id: cover_tour_finish type: SetState params: cover_tour_open: false - id: cover_target_row type: Box layout: gap: 16 blocks: - id: cover_target_btn_1 type: Button layout: flex: 0 0 auto properties: title: Dashboard color: primary variant: solid icon: Gauge - id: cover_target_btn_2 type: Button layout: flex: 0 0 auto properties: title: Settings color: default variant: outlined icon: settings - id: cover_target_tour type: Tour properties: open: _state: cover_target_tour_open steps: - title: Dashboard Overview description: The dashboard shows a summary of your workspace activity. target: cover_target_btn_1 cover: https://gw.alipayobjects.com/zos/bmw-prod/e9a9b1e8-5e62-4e12-a3b7-e3695b0a5f3c.png - title: Configure Settings description: Adjust preferences and integrations from the settings page. target: cover_target_btn_2 cover: https://gw.alipayobjects.com/zos/bmw-prod/ed5cf3c4-5e34-43e5-9c39-fe131c7cf88e.png events: onClose: - id: cover_target_tour_close type: SetState params: cover_target_tour_open: false onFinish: - id: cover_target_tour_finish type: SetState params: cover_target_tour_open: false ``` ```yaml - id: closable_btns_row type: Box layout: gap: 8 blocks: - id: closable_tour_start_btn type: Button layout: flex: 0 0 auto properties: title: Closable color: primary variant: outlined size: small events: onClick: - id: closable_tour_start type: SetState params: closable_tour_open: true - id: not_closable_tour_start_btn type: Button layout: flex: 0 0 auto properties: title: Not Closable color: primary variant: outlined size: small events: onClick: - id: not_closable_tour_start type: SetState params: not_closable_tour_open: true - id: closable_per_step_start_btn type: Button layout: flex: 0 0 auto properties: title: Per Step color: primary variant: outlined size: small events: onClick: - id: closable_per_step_start type: SetState params: closable_per_step_tour_open: true - id: closable_tour type: Tour properties: open: _state: closable_tour_open closable: true keyboard: true steps: - title: Closable Tour description: The close button (X) is visible. Press Escape to close. - title: Keyboard Navigation description: Use arrow keys to navigate between steps. - title: Final Step description: Both close button and keyboard shortcuts are available throughout. events: onClose: - id: closable_tour_close type: SetState params: closable_tour_open: false onFinish: - id: closable_tour_finish type: SetState params: closable_tour_open: false - id: not_closable_tour type: Tour properties: open: _state: not_closable_tour_open closable: false keyboard: false steps: - title: Guided Tour description: Close button and Escape key are disabled. Use Next and Previous buttons. - title: Must Complete description: Users must step through all steps to finish the tour. events: onClose: - id: not_closable_tour_close type: SetState params: not_closable_tour_open: false onFinish: - id: not_closable_tour_finish type: SetState params: not_closable_tour_open: false - id: closable_per_step_row type: Box layout: gap: 16 blocks: - id: closable_per_step_btn_1 type: Button layout: flex: 0 0 auto properties: title: Closable color: green variant: outlined - id: closable_per_step_btn_2 type: Button layout: flex: 0 0 auto properties: title: Not Closable color: orange variant: outlined - id: closable_per_step_tour type: Tour properties: open: _state: closable_per_step_tour_open closable: true steps: - title: Closable Step description: This step has the close button visible. target: closable_per_step_btn_1 closable: true - title: Not Closable Step description: This step hides the close button. Navigate forward to continue. target: closable_per_step_btn_2 closable: false events: onClose: - id: closable_per_step_tour_close type: SetState params: closable_per_step_tour_open: false onFinish: - id: closable_per_step_tour_finish type: SetState params: closable_per_step_tour_open: false ``` ```yaml - id: anim_btns_row type: Box layout: gap: 8 blocks: - id: animated_tour_start_btn type: Button layout: flex: 0 0 auto properties: title: Animated color: primary variant: outlined size: small events: onClick: - id: animated_tour_start type: SetState params: animated_tour_open: true - id: no_animation_tour_start_btn type: Button layout: flex: 0 0 auto properties: title: No Animation color: primary variant: outlined size: small events: onClick: - id: no_animation_tour_start type: SetState params: no_animation_tour_open: true - id: disabled_interaction_tour_start_btn type: Button layout: flex: 0 0 auto properties: title: Disabled Interaction color: primary variant: outlined size: small events: onClick: - id: disabled_interaction_tour_start type: SetState params: disabled_interaction_tour_open: true - id: animated_tour type: Tour properties: open: _state: animated_tour_open animated: true scrollIntoViewOptions: true steps: - title: Animated Tour description: Smooth transitions between steps with animation enabled. - title: Auto Scroll description: Target elements automatically scroll into view when a step activates. events: onClose: - id: animated_tour_close type: SetState params: animated_tour_open: false onFinish: - id: animated_tour_finish type: SetState params: animated_tour_open: false - id: no_animation_tour type: Tour properties: open: _state: no_animation_tour_open animated: false scrollIntoViewOptions: false steps: - title: No Animation description: Instant transitions without animation. Scroll to target is also disabled. events: onClose: - id: no_animation_tour_close type: SetState params: no_animation_tour_open: false onFinish: - id: no_animation_tour_finish type: SetState params: no_animation_tour_open: false - id: disabled_interaction_row type: Box layout: gap: 16 blocks: - id: disabled_interaction_btn type: Button layout: flex: 0 0 auto properties: title: Cannot Click During Tour color: primary variant: solid icon: lock - id: disabled_interaction_tour type: Tour properties: open: _state: disabled_interaction_tour_open disabledInteraction: true steps: - title: Interaction Disabled description: The highlighted target element cannot be clicked during this tour. target: disabled_interaction_btn - title: Read-Only Tour description: Users can only navigate tour steps, not interact with highlighted elements. events: onClose: - id: disabled_interaction_tour_close type: SetState params: disabled_interaction_tour_open: false onFinish: - id: disabled_interaction_tour_finish type: SetState params: disabled_interaction_tour_open: false ``` ```yaml - id: gap_btns_row type: Box layout: gap: 8 blocks: - id: gap_default_tour_start_btn type: Button layout: flex: 0 0 auto properties: title: Default Gap color: primary variant: outlined size: small events: onClick: - id: gap_default_tour_start type: SetState params: gap_default_tour_open: true - id: gap_large_tour_start_btn type: Button layout: flex: 0 0 auto properties: title: Large Gap color: primary variant: outlined size: small events: onClick: - id: gap_large_tour_start type: SetState params: gap_large_tour_open: true - id: gap_tight_tour_start_btn type: Button layout: flex: 0 0 auto properties: title: No Gap color: primary variant: outlined size: small events: onClick: - id: gap_tight_tour_start type: SetState params: gap_tight_tour_open: true - id: current_step_tour_start_btn type: Button layout: flex: 0 0 auto properties: title: Start at Step 2 color: primary variant: outlined size: small events: onClick: - id: current_step_tour_start type: SetState params: current_step_tour_open: true - id: gap_row type: Box layout: gap: 16 blocks: - id: gap_default_btn type: Button layout: flex: 0 0 auto properties: title: Default Gap color: primary variant: solid - id: gap_large_btn type: Button layout: flex: 0 0 auto properties: title: Large Gap color: purple variant: solid - id: gap_tight_btn type: Button layout: flex: 0 0 auto properties: title: No Gap color: orange variant: solid - id: gap_default_tour type: Tour properties: open: _state: gap_default_tour_open steps: - title: Default Gap description: Standard spacing between the highlighted area and the target. target: gap_default_btn events: onClose: - id: gap_default_tour_close type: SetState params: gap_default_tour_open: false onFinish: - id: gap_default_tour_finish type: SetState params: gap_default_tour_open: false - id: gap_large_tour type: Tour properties: open: _state: gap_large_tour_open gap: offset: 20 radius: 8 steps: - title: Large Gap description: Increased gap (20px) around the target for more breathing room. target: gap_large_btn events: onClose: - id: gap_large_tour_close type: SetState params: gap_large_tour_open: false onFinish: - id: gap_large_tour_finish type: SetState params: gap_large_tour_open: false - id: gap_tight_tour type: Tour properties: open: _state: gap_tight_tour_open gap: offset: 0 radius: 0 steps: - title: No Gap description: The highlight fits tightly around the target with zero spacing. target: gap_tight_btn events: onClose: - id: gap_tight_tour_close type: SetState params: gap_tight_tour_open: false onFinish: - id: gap_tight_tour_finish type: SetState params: gap_tight_tour_open: false - id: current_step_tour type: Tour properties: open: _state: current_step_tour_open current: 1 steps: - title: Step One description: This is the first step, but the tour does not start here. - title: Step Two (Starting Point) description: The tour opens at this step because current is set to 1 (zero-indexed). - title: Step Three description: This is the third and final step. events: onClose: - id: current_step_tour_close type: SetState params: current_step_tour_open: false onFinish: - id: current_step_tour_finish type: SetState params: current_step_tour_open: false ``` ```yaml - id: zindex_btns_row type: Box layout: gap: 8 blocks: - id: zindex_default_start_btn type: Button layout: flex: 0 0 auto properties: title: Default (1070) color: primary variant: outlined size: small events: onClick: - id: zindex_default_start type: SetState params: zindex_default_tour_open: true - id: zindex_high_start_btn type: Button layout: flex: 0 0 auto properties: title: High (2000) color: primary variant: outlined size: small events: onClick: - id: zindex_high_start type: SetState params: zindex_high_tour_open: true - id: zindex_low_start_btn type: Button layout: flex: 0 0 auto properties: title: Low (900) color: primary variant: outlined size: small events: onClick: - id: zindex_low_start type: SetState params: zindex_low_tour_open: true - id: zindex_default_tour type: Tour properties: open: _state: zindex_default_tour_open steps: - title: Default Z-Index description: Tour uses the default stacking order (1070). Appears above most content. events: onClose: - id: zindex_default_tour_close type: SetState params: zindex_default_tour_open: false onFinish: - id: zindex_default_tour_finish type: SetState params: zindex_default_tour_open: false - id: zindex_high_tour type: Tour properties: open: _state: zindex_high_tour_open zIndex: 2000 steps: - title: High Z-Index (2000) description: Tour renders above other overlays such as modals and drawers. events: onClose: - id: zindex_high_tour_close type: SetState params: zindex_high_tour_open: false onFinish: - id: zindex_high_tour_finish type: SetState params: zindex_high_tour_open: false - id: zindex_low_tour type: Tour properties: open: _state: zindex_low_tour_open zIndex: 900 steps: - title: Low Z-Index (900) description: Tour renders below some overlays. Fixed headers may appear above it. events: onClose: - id: zindex_low_tour_close type: SetState params: zindex_low_tour_open: false onFinish: - id: zindex_low_tour_finish type: SetState params: zindex_low_tour_open: false ``` ```yaml - id: events_start_btn type: Button layout: flex: 0 0 auto properties: title: Start Events Tour color: primary variant: outlined size: small events: onClick: - id: events_tour_start type: SetState params: events_tour_open: true - id: events_row type: Box layout: gap: 16 blocks: - id: events_btn_1 type: Button layout: flex: 0 0 auto properties: title: Step 1 Target color: primary variant: solid - id: events_btn_2 type: Button layout: flex: 0 0 auto properties: title: Step 2 Target color: green variant: solid - id: events_tour type: Tour properties: open: _state: events_tour_open steps: - title: First Step description: Navigate forward to trigger the onChange event. target: events_btn_1 - title: Second Step description: Close the tour or finish to trigger onClose or onFinish. target: events_btn_2 events: onChange: - id: events_on_change type: DisplayMessage params: content: Step changed! status: info onClose: - id: events_tour_close type: SetState params: events_tour_open: false - id: events_on_close type: DisplayMessage params: content: Tour closed. status: warning onFinish: - id: events_tour_finish type: SetState params: events_tour_open: false - id: events_on_finish type: DisplayMessage params: content: Tour completed! status: success ``` ```yaml - id: css_btns_row type: Box layout: gap: 8 blocks: - id: css_tailwind_start_btn type: Button layout: flex: 0 0 auto properties: title: Shadow color: primary variant: outlined size: small events: onClick: - id: css_tailwind_start type: SetState params: css_tailwind_tour_open: true - id: css_rounded_start_btn type: Button layout: flex: 0 0 auto properties: title: Rounded color: primary variant: outlined size: small events: onClick: - id: css_rounded_start type: SetState params: css_rounded_tour_open: true - id: css_inline_start_btn type: Button layout: flex: 0 0 auto properties: title: Inline Style color: primary variant: outlined size: small events: onClick: - id: css_inline_start type: SetState params: css_inline_tour_open: true - id: css_dark_tour_start_btn type: Button layout: flex: 0 0 auto properties: title: Dark Background color: primary variant: outlined size: small events: onClick: - id: css_dark_tour_start type: SetState params: css_dark_tour_open: true - id: css_tailwind_tour type: Tour class: shadow-2xl properties: open: _state: css_tailwind_tour_open steps: - title: Tailwind Shadow description: Tailwind utility classes can be applied via the class property. events: onClose: - id: css_tailwind_tour_close type: SetState params: css_tailwind_tour_open: false onFinish: - id: css_tailwind_tour_finish type: SetState params: css_tailwind_tour_open: false - id: css_rounded_tour type: Tour class: rounded-2xl properties: open: _state: css_rounded_tour_open steps: - title: Tailwind Rounded description: Additional border radius applied via Tailwind class. events: onClose: - id: css_rounded_tour_close type: SetState params: css_rounded_tour_open: false onFinish: - id: css_rounded_tour_finish type: SetState params: css_rounded_tour_open: false - id: css_inline_tour type: Tour properties: open: _state: css_inline_tour_open steps: - title: Inline Style description: The element style key controls the tour card appearance. style: .element: maxWidth: 320px events: onClose: - id: css_inline_tour_close type: SetState params: css_inline_tour_open: false onFinish: - id: css_inline_tour_finish type: SetState params: css_inline_tour_open: false - id: css_dark_bg type: Box class: bg-gradient-to-r from-slate-900 to-slate-700 p-4 rounded-lg layout: gap: 8 blocks: - id: css_dark_btn type: Button layout: flex: 0 0 auto properties: title: Target on Dark Background ghost: true color: primary variant: outlined - id: css_dark_tour type: Tour properties: open: _state: css_dark_tour_open steps: - title: Dark Background Context description: Tour cards remain visible against dark page backgrounds. target: css_dark_btn events: onClose: - id: css_dark_tour_close type: SetState params: css_dark_tour_open: false onFinish: - id: css_dark_tour_finish type: SetState params: css_dark_tour_open: false ``` ```yaml - id: theme_btns_row type: Box layout: gap: 8 blocks: - id: theme_purple_start_btn type: Button layout: flex: 0 0 auto properties: title: Purple Primary color: primary variant: outlined size: small events: onClick: - id: theme_purple_start type: SetState params: theme_purple_tour_open: true - id: theme_rounded_start_btn type: Button layout: flex: 0 0 auto properties: title: Rounded Card color: primary variant: outlined size: small events: onClick: - id: theme_rounded_start type: SetState params: theme_rounded_tour_open: true - id: theme_large_font_start_btn type: Button layout: flex: 0 0 auto properties: title: Large Text color: primary variant: outlined size: small events: onClick: - id: theme_large_font_start type: SetState params: theme_large_font_tour_open: true - id: theme_combined_start_btn type: Button layout: flex: 0 0 auto properties: title: Combined color: primary variant: outlined size: small events: onClick: - id: theme_combined_start type: SetState params: theme_combined_tour_open: true - id: theme_purple_tour type: Tour properties: open: _state: theme_purple_tour_open type: primary theme: colorPrimary: "#722ed1" steps: - title: Purple Primary description: Custom purple colorPrimary changes background and button colors. - title: Brand Alignment description: Theme tokens align the tour with your brand palette. events: onClose: - id: theme_purple_tour_close type: SetState params: theme_purple_tour_open: false onFinish: - id: theme_purple_tour_finish type: SetState params: theme_purple_tour_open: false - id: theme_rounded_tour type: Tour properties: open: _state: theme_rounded_tour_open theme: borderRadiusLG: 20 padding: 24 steps: - title: Rounded Card description: Increased border radius (20px) and padding (24px) for a softer look. events: onClose: - id: theme_rounded_tour_close type: SetState params: theme_rounded_tour_open: false onFinish: - id: theme_rounded_tour_finish type: SetState params: theme_rounded_tour_open: false - id: theme_large_font_tour type: Tour properties: open: _state: theme_large_font_tour_open theme: fontSize: 16 fontSizeLG: 20 steps: - title: Large Text description: Increased font sizes improve readability and accessibility. events: onClose: - id: theme_large_font_tour_close type: SetState params: theme_large_font_tour_open: false onFinish: - id: theme_large_font_tour_finish type: SetState params: theme_large_font_tour_open: false - id: theme_combined_tour type: Tour properties: open: _state: theme_combined_tour_open type: primary theme: colorPrimary: "#fa541c" borderRadiusLG: 12 primaryPrevBtnBg: rgba(255, 255, 255, 0.35) arrowOffsetHorizontal: 16 steps: - title: Combined Tokens description: Multiple tokens create a cohesive warm-themed tour. - title: Previous Button description: The primaryPrevBtnBg token makes the Previous button more visible. events: onClose: - id: theme_combined_tour_close type: SetState params: theme_combined_tour_open: false onFinish: - id: theme_combined_tour_finish type: SetState params: theme_combined_tour_open: false ``` Welcome to the application. Click the button below to take a guided tour of the interface. ```yaml - id: onboard_card type: Card properties: title: My Application size: small blocks: - id: onboard_header_row type: Box layout: gap: 16 align: center blocks: - id: onboard_search type: TextInput layout: flex: 1 1 auto properties: label: disabled: true placeholder: Search anything... - id: onboard_create_btn type: Button layout: flex: 0 0 auto properties: title: Create New color: primary variant: solid icon: add - id: onboard_profile_btn type: Button layout: flex: 0 0 auto properties: title: Profile color: default variant: text icon: user - id: onboard_settings_btn type: Button layout: flex: 0 0 auto properties: title: Settings color: default variant: text icon: settings - id: onboard_content type: Markdown properties: content: > Welcome to the application. Click the button below to take a guided tour of the interface. - id: onboard_start_row type: Box layout: gap: 8 justify: flex-end blocks: - id: onboard_start_btn type: Button layout: flex: 0 0 auto properties: title: Take the Tour color: primary variant: solid icon: help events: onClick: - id: onboard_start_tour type: SetState params: show_onboard_tour: true - id: onboard_tour type: Tour properties: open: _state: show_onboard_tour type: default mask: true animated: true steps: - title: Welcome description: Let us take a quick tour to get you started. cover: https://gw.alipayobjects.com/zos/bmw-prod/e9a9b1e8-5e62-4e12-a3b7-e3695b0a5f3c.png - title: Search description: Use the search bar to quickly find documents, users, or settings. target: onboard_search placement: bottom - title: Create Content description: Click here to create a new document, project, or workspace item. target: onboard_create_btn placement: bottom - title: Your Profile description: Access your profile to update personal information and preferences. target: onboard_profile_btn placement: bottomRight - title: Settings description: Configure application settings, integrations, and notifications. target: onboard_settings_btn placement: bottomRight events: onClose: - id: onboard_close type: SetState params: show_onboard_tour: false onFinish: - id: onboard_finish_state type: SetState params: show_onboard_tour: false - id: onboard_finish_msg type: DisplayMessage params: content: Tour complete! You are ready to get started. status: success ``` ```yaml - id: feature_card type: Card properties: title: Document Editor size: small blocks: - id: feature_toolbar type: Box layout: gap: 8 align: center blocks: - id: feature_bold_btn type: Button layout: flex: 0 0 auto properties: title: B color: default variant: outlined size: small - id: feature_italic_btn type: Button layout: flex: 0 0 auto properties: title: I color: default variant: outlined size: small - id: feature_save_btn type: Button layout: flex: 0 0 auto properties: title: Save color: primary variant: solid size: small icon: save - id: feature_export_btn type: Button layout: flex: 0 0 auto properties: title: Export color: default variant: outlined size: small icon: SquareArrowOutUpRight - id: feature_editor type: TextArea properties: label: disabled: true placeholder: Start typing your document here... rows: 4 - id: feature_actions type: Box layout: gap: 8 justify: flex-end blocks: - id: feature_tour_btn type: Button layout: flex: 0 0 auto properties: title: Learn Editor Features color: primary variant: outlined icon: Lightbulb events: onClick: - id: feature_start_tour type: SetState params: show_feature_tour: true - id: feature_tour type: Tour properties: open: _state: show_feature_tour type: primary mask: true animated: true closable: true steps: - title: Formatting Tools description: Use Bold and Italic buttons to format your text. target: feature_bold_btn placement: bottom type: default - title: Save Your Work description: Click Save to store your document. Your work is automatically backed up every 5 minutes. target: feature_save_btn placement: bottom type: primary - title: Export Options description: Export your document as PDF, Word, or plain text for sharing. target: feature_export_btn placement: bottom type: default events: onClose: - id: feature_close type: SetState params: show_feature_tour: false onFinish: - id: feature_finish_state type: SetState params: show_feature_tour: false - id: feature_finish_msg type: DisplayMessage params: content: You are now familiar with the editor features! status: success ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `animated` | boolean | `true` | Whether to enable animation. | | `arrow` | boolean \| object | `true` | Whether to show the arrow. | | `closable` | boolean | `true` | Whether the close button is visible. | | `current` | integer | - | Current step index. | | `disabledInteraction` | boolean | `false` | Whether to disable interaction with the page while the tour is active. | | `gap` | object | - | Gap between the highlighted area and the target element, and its radius. | | `gap.offset` | number \| array | `6` | Gap in pixels around the target, or a [horizontal, vertical] pair of gaps. | | `gap.radius` | number | `2` | Border radius of the highlighted area in pixels. | | `gap.x` | number | - | Horizontal gap offset. Same as the first value of `offset`. | | `gap.y` | number | - | Vertical gap offset. Same as the second value of `offset`. | | `keyboard` | boolean | `true` | Whether to enable keyboard navigation. | | `mask` | boolean \| object | `true` | Whether to enable mask. | | `open` | boolean | `false` | Whether to show the tour. | | `placement` | string | `"bottom"` | Position of the guide card relative to the target element: center, left, leftTop, leftBottom, right, rightTop, rightBottom, top, topLeft, topRight, bottom, bottomLeft or bottomRight. | | `scrollIntoViewOptions` | boolean \| object | `true` | Whether to scroll the step target element into view, or scrollIntoView options such as `{ block: center }`. | | `steps` | array | - | Tour steps. Each step has title, description, target (blockId string), placement, etc. | | `steps.$.arrow` | boolean \| object | - | Whether to show the arrow for this step, or object with pointAtCenter. | | `steps.$.closable` | boolean | - | Whether the close button is visible for this step. | | `steps.$.cover` | string | - | Cover image URL for the step. | | `steps.$.description` | string | - | Description of the step. | | `steps.$.mask` | boolean \| object | - | Whether to enable mask for this step, or object with style and color properties. | | `steps.$.nextButtonProps` | object | - | Next button settings for this step. | | `steps.$.nextButtonProps.children` | string | - | Text of the next button, for example "Got it". | | `steps.$.prevButtonProps` | object | - | Previous button settings for this step. | | `steps.$.prevButtonProps.children` | string | - | Text of the previous button. | | `steps.$.placement` | string | - | Position of the guide card relative to the target element. | | `steps.$.target` | string | - | The blockId of the target element for this step. | | `steps.$.title` | string | - | Title of the step. | | `steps.$.type` | string | - | Type style for this step, overrides tour-level type. Enum: `default`, `primary`. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design tour tokens](https://ant.design/components/tour#design-token). | | `theme.zIndexPopup` | number | `1070` | Z-index of the tour popup. | | `theme.closeBtnSize` | number | - | Size of the close button. Calculated from fontSize and lineHeight. | | `theme.primaryPrevBtnBg` | string | `"rgba(255, 255, 255, 0.15)"` | Background color of the previous button in primary type tour. | | `theme.primaryNextBtnHoverBg` | string | - | Hover background color of the next button in primary type tour. Derived from colorBgTextHover. | | `theme.arrowOffsetHorizontal` | number | `12` | Horizontal offset of the arrow. | | `theme.arrowOffsetVertical` | number | `8` | Vertical offset of the arrow. | | `theme.colorPrimary` | string | - | Primary color override for the tour. | | `theme.colorBgContainer` | string | - | Background color of the tour card. | | `theme.colorText` | string | - | Text color in the tour card. | | `theme.colorTextDescription` | string | - | Description text color in the tour card. | | `theme.borderRadiusLG` | number | `8` | Border radius of the tour card. | | `theme.fontSize` | number | `14` | Font size for tour content. | | `theme.fontSizeLG` | number | `16` | Font size for tour title. | | `theme.padding` | number | `16` | Padding of the tour card content. | | `type` | string | `"default"` | Type of the tour. Enum: `default`, `primary`. | | `zIndex` | integer | - | Z-index of the tour. | | Event | Event Data | Description | | --- | --- | --- | | `onChange` | `{ current }` | Triggered when the current step changes. | | `onClose` | `{ current }` | Triggered when the tour is closed. | | `onFinish` | \- | Triggered when the tour is finished. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Tour element. | | `/mask` | The Tour mask. | | `/title` | The title of the step card. | | `/description` | The description of the step card. | | `/cover` | The cover image area of the step card. | | `/footer` | The footer of the step card with the indicators and buttons. | No slots defined. --- url: https://docs.lowdefy.com/md/container-blocks/alert.md --- # Alert Alert banner with type, description, icon, and closable options. > Operation completed successfully > This is an informational message > Please review before proceeding > An error occurred during processing ```yaml - id: type_success type: Alert properties: message: Operation completed successfully type: success - id: type_info type: Alert properties: message: This is an informational message type: info - id: type_warning type: Alert properties: message: Please review before proceeding type: warning - id: type_error type: Alert properties: message: An error occurred during processing type: error ``` > Backup completed > Two invoices are overdue ```yaml - id: filled_success type: Alert properties: message: Backup completed type: success variant: filled - id: filled_warning type: Alert properties: message: Two invoices are overdue description: Send a reminder to the customer or mark the invoices as paid. type: warning variant: filled ``` > Payment Received > Scheduled Maintenance > Storage Almost Full > Connection Failed ```yaml - id: desc_success type: Alert properties: message: Payment Received description: Your payment of $49.99 has been processed. A confirmation email has been sent to your inbox. type: success - id: desc_info type: Alert properties: message: Scheduled Maintenance description: The system will undergo maintenance on Saturday from 2:00 AM to 6:00 AM UTC. Please save your work beforehand. type: info - id: desc_warning type: Alert properties: message: Storage Almost Full description: You have used 90% of your available storage. Consider upgrading your plan or removing unused files. type: warning - id: desc_error type: Alert properties: message: Connection Failed description: Unable to connect to the database server. Please check your network settings and try again. type: error ``` > All systems operational > Your session will expire in 30 minutes > Unsaved changes will be lost > Invalid credentials provided ```yaml - id: msg_only_success type: Alert properties: message: All systems operational type: success - id: msg_only_info type: Alert properties: message: Your session will expire in 30 minutes type: info - id: msg_only_warning type: Alert properties: message: Unsaved changes will be lost type: warning - id: msg_only_error type: Alert properties: message: Invalid credentials provided type: error ``` > Changes saved > New version available > Session expires in 5 minutes > Request timed out > Notice ```yaml - id: noicon_success type: Alert properties: message: Changes saved type: success showIcon: false - id: noicon_info type: Alert properties: message: New version available type: info showIcon: false - id: noicon_warning type: Alert properties: message: Session expires in 5 minutes type: warning showIcon: false - id: noicon_error type: Alert properties: message: Request timed out type: error showIcon: false - id: noicon_desc type: Alert properties: message: Notice description: This alert has a description but no icon. The showIcon property is set to false, giving a cleaner text-only appearance. type: info showIcon: false ``` > You can dismiss this notification > Profile updated successfully > Your trial period ends in 3 days > Failed to sync data ```yaml - id: closable_info type: Alert properties: message: You can dismiss this notification type: info closable: true - id: closable_success type: Alert properties: message: Profile updated successfully type: success closable: true - id: closable_warning type: Alert properties: message: Your trial period ends in 3 days description: Upgrade to a paid plan to keep access to all features. type: warning closable: true - id: closable_error type: Alert properties: message: Failed to sync data description: The last synchronization attempt failed. Your local changes have been preserved. Please check your connection and try again. type: error closable: true ``` > Your session will expire in 5 minutes ```yaml - id: close_icon_circle type: Alert properties: message: Your session will expire in 5 minutes type: warning closable: true closeIcon: close-circle ``` > Cookie consent notice > New features available > Reminder to complete your profile > Browser update recommended ```yaml - id: close_text_dismiss type: Alert properties: message: Cookie consent notice description: We use cookies to improve your experience. By continuing to use this site, you agree to our cookie policy. type: info closable: true closeText: Dismiss - id: close_text_gotit type: Alert properties: message: New features available description: We have added dark mode support and keyboard shortcuts. Check the settings page for details. type: success closable: true closeText: Got it - id: close_text_close type: Alert properties: message: Reminder to complete your profile type: warning closable: true closeText: Close - id: close_text_later type: Alert properties: message: Browser update recommended description: You are using an older browser version. Some features may not work as expected. type: warning closable: true closeText: Later ``` > All tests passed > You have 3 new notifications > Your session has been locked for security > Bug detected in module > Deployment in progress > You earned a new achievement > Custom styled icon > Security scan complete ```yaml - id: custom_icon_check type: Alert properties: message: All tests passed type: success icon: check-circle - id: custom_icon_bell type: Alert properties: message: You have 3 new notifications type: info icon: bell - id: custom_icon_lock type: Alert properties: message: Your session has been locked for security type: warning icon: lock - id: custom_icon_bug type: Alert properties: message: Bug detected in module type: error icon: Bug - id: custom_icon_rocket type: Alert properties: message: Deployment in progress description: Your application is being deployed to the production environment. This usually takes 2-3 minutes. type: info icon: Rocket - id: custom_icon_star type: Alert properties: message: You earned a new achievement description: Congratulations on completing your first project! type: success icon: star - id: custom_icon_object type: Alert properties: message: Custom styled icon type: info icon: name: Zap color: "#faad14" - id: custom_icon_colored_obj type: Alert properties: message: Security scan complete description: No vulnerabilities were detected in your dependencies. type: success icon: name: Shield color: "#52c41a" ``` > Deployment completed successfully across all regions > Welcome to the new dashboard experience > Your account requires verification to continue > Service disruption detected in the EU region > This banner can be dismissed by the user > System Update Scheduled ```yaml - id: banner_success type: Alert properties: message: Deployment completed successfully across all regions type: success banner: true - id: banner_info type: Alert properties: message: Welcome to the new dashboard experience type: info banner: true - id: banner_warning type: Alert properties: message: Your account requires verification to continue type: warning banner: true - id: banner_error type: Alert properties: message: Service disruption detected in the EU region type: error banner: true - id: banner_closable type: Alert properties: message: This banner can be dismissed by the user type: info banner: true closable: true - id: banner_desc type: Alert properties: message: System Update Scheduled description: A major update is scheduled for this weekend. All services will be briefly unavailable during the migration window from 01:00 to 03:00 UTC on Sunday. type: warning banner: true ``` > Important: Your password expires in 7 days > Click here to learn more about Lowdefy > Release Notes v4.2.0 > Account Status: Active > Configuration Error > Data Migration Complete ```yaml - id: html_bold_message type: Alert properties: message: Important: Your password expires in 7 days type: warning - id: html_link_message type: Alert properties: message: Click here to learn more about Lowdefy type: info - id: html_rich_description type: Alert properties: message: Release Notes v4.2.0 description:
  • New: Dark mode support
  • Fixed: Login timeout issue
  • Improved: Dashboard loading speed by 40%
type: success - id: html_styled_message type: Alert properties: message: 'Account Status: Active' type: info showIcon: false - id: html_code_description type: Alert properties: message: Configuration Error description: The field database.host is required but was not provided. Please update your .env file and restart the server. type: error - id: html_emphasis_description type: Alert properties: message: Data Migration Complete description: All 15,432 records have been migrated to the new schema. No data loss detected. Please verify the results in the admin panel. type: success ``` > A new software update is available > Your subscription is about to expire > Payment processing failed > Merge conflict detected > Export completed ```yaml - id: action_button_info type: Alert properties: message: A new software update is available description: Version 2.5.0 includes bug fixes and performance improvements. type: info slots: action: blocks: - id: action_button_info_btn type: Button layout: flex: 0 0 auto properties: title: Update Now color: primary variant: solid size: small - id: action_button_warning type: Alert properties: message: Your subscription is about to expire description: Renew before March 15 to keep your current pricing. type: warning slots: action: blocks: - id: action_button_warning_btn type: Button layout: flex: 0 0 auto properties: title: Renew color: primary variant: outlined size: small - id: action_button_error type: Alert properties: message: Payment processing failed description: Your credit card was declined. Please update your payment method to avoid service interruption. type: error slots: action: blocks: - id: action_button_error_btn type: Button layout: flex: 0 0 auto properties: title: Update Payment color: danger variant: solid size: small - id: action_multiple_buttons type: Alert properties: message: Merge conflict detected description: The branch feature/auth has conflicts with main that need to be resolved manually. type: warning slots: action: blocks: - id: action_multi_row type: Box layout: gap: 8 blocks: - id: action_multi_resolve type: Button layout: flex: 0 0 auto properties: title: Resolve color: primary variant: solid size: small - id: action_multi_dismiss type: Button layout: flex: 0 0 auto properties: title: Dismiss color: default variant: outlined size: small - id: action_link_success type: Alert properties: message: Export completed description: Your data export is ready for download. type: success slots: action: blocks: - id: action_link_btn type: Button layout: flex: 0 0 auto properties: title: Download icon: download color: green variant: solid size: small ``` > Backup Completed > API Rate Limit Warning > Critical Security Alert > Welcome to the beta program! We appreciate your feedback. > Terms of Service Updated ```yaml - id: combo_full_success type: Alert properties: message: Backup Completed description: All 248 files have been backed up to cloud storage. Total size 1.2 GB. type: success closable: true closeText: OK icon: CloudUpload - id: combo_full_warning type: Alert properties: message: API Rate Limit Warning description: You have used 85% of your daily API quota (8,500 / 10,000 requests). Consider implementing request caching. type: warning closable: true icon: Plug - id: combo_full_error type: Alert properties: message: Critical Security Alert description: Unauthorized access attempt detected from IP 192.168.1.45 at 14:32 UTC. The account has been temporarily locked. type: error closable: true icon: warning - id: combo_banner_closable_icon type: Alert properties: message: Welcome to the beta program! We appreciate your feedback. type: info banner: true closable: true icon: FlaskConical - id: combo_noicon_closable_desc type: Alert properties: message: Terms of Service Updated description: Our terms of service have been updated effective March 1, 2026. By continuing to use the platform, you agree to the new terms. type: info showIcon: false closable: true closeText: I Agree ``` > Rounded with shadow > Left border accent > Elevated success > Alert on dark background > Warning on dark background > Compact alert with smaller text > Wider border radius ```yaml - id: css_rounded type: Alert class: rounded-xl shadow-md properties: message: Rounded with shadow description: This alert uses Tailwind classes for rounded corners and a shadow effect. type: info - id: css_border_accent type: Alert class: border-l-4 border-l-blue-500 properties: message: Left border accent description: A thicker left border creates a visual accent effect, drawing attention to the alert content. type: info showIcon: false - id: css_shadow_success type: Alert class: shadow-lg rounded-lg properties: message: Elevated success description: Shadow and border radius create a card-like elevated appearance for this success alert. type: success - id: css_dark_bg type: Box class: bg-bg-layout p-6 rounded-xl blocks: - id: css_on_dark_info type: Alert class: shadow-xl properties: message: Alert on dark background description: Alerts with shadows stand out well against darker container backgrounds. type: info - id: css_on_dark_warning type: Alert class: shadow-xl properties: message: Warning on dark background type: warning - id: css_compact type: Alert class: text-xs properties: message: Compact alert with smaller text type: info showIcon: false - id: css_wide_border type: Alert class: border-2 rounded-2xl properties: message: Wider border radius description: Extra border width and larger radius for a softer appearance. type: success ``` > Large border radius > Larger font size > Extra padding > Sharp corners (no radius) > Larger description icon > Fully customized alert ```yaml - id: theme_custom_radius type: Alert properties: message: Large border radius description: This alert uses a 16px border radius override via the theme property. type: info theme: borderRadius: 16 - id: theme_custom_font type: Alert properties: message: Larger font size description: The base font size is increased to 16px for improved readability. type: success theme: fontSize: 16 - id: theme_custom_padding type: Alert properties: message: Extra padding description: Custom padding makes the alert feel more spacious and prominent on the page. type: warning theme: defaultPadding: 16px 24px withDescriptionPadding: 28px 32px - id: theme_no_radius type: Alert properties: message: Sharp corners (no radius) description: Setting borderRadius to 0 creates a sharp-edged industrial look. type: info theme: borderRadius: 0 - id: theme_large_icon type: Alert properties: message: Larger description icon description: The icon size for alerts with descriptions is increased to 32px, making it more prominent. type: success theme: withDescriptionIconSize: 32 - id: theme_combined type: Alert properties: message: Fully customized alert description: This alert combines multiple theme token overrides including padding, radius, font size, and icon size for a completely custom appearance. type: info theme: borderRadius: 12 fontSize: 15 withDescriptionPadding: 24px 28px withDescriptionIconSize: 28 ``` > Please fix the following errors > Welcome to your workspace! > API v1 Deprecation Notice > Scheduled Downtime > Deployment Successful > Resource Quota Warning > Access Denied > Beta Feature Enabled ```yaml - id: real_form_validation type: Alert properties: message: Please fix the following errors description:
  • Email address is required
  • Password must be at least 8 characters
  • Please agree to the terms of service
type: error icon: CircleAlert - id: real_onboarding type: Alert properties: message: Welcome to your workspace! description: Get started by creating your first project. You can invite team members from the Settings page. type: info closable: true icon: FaceSlightlySmiling slots: action: blocks: - id: real_onboarding_btn type: Button layout: flex: 0 0 auto properties: title: Create Project color: primary variant: solid size: small icon: add - id: real_deprecation type: Alert properties: message: API v1 Deprecation Notice description: API v1 will be sunset on June 30, 2026. Please migrate to API v2 before this date. See the migration guide for instructions. type: warning closable: true closeText: Acknowledged icon: clock - id: real_maintenance type: Alert properties: message: Scheduled Downtime description: Services will be unavailable on Sunday, March 15 from 02:00 to 04:00 UTC for database migration. No action is required on your part. type: info banner: true closable: true - id: real_success_deploy type: Alert properties: message: Deployment Successful description: "Application my-app has been deployed to production environment. Build #1847 completed in 2m 34s." type: success icon: check-circle slots: action: blocks: - id: real_deploy_actions type: Box layout: gap: 8 blocks: - id: real_deploy_view type: Button layout: flex: 0 0 auto properties: title: View Logs color: default variant: outlined size: small - id: real_deploy_open type: Button layout: flex: 0 0 auto properties: title: Open App color: green variant: solid size: small icon: link - id: real_quota_warning type: Alert properties: message: Resource Quota Warning description: "Your project has used 92% of allocated compute resources. Current usage: 18.4 / 20 vCPUs. Consider scaling your plan or optimizing workloads." type: warning icon: Gauge slots: action: blocks: - id: real_quota_btn type: Button layout: flex: 0 0 auto properties: title: Upgrade Plan color: primary variant: solid size: small - id: real_permission_error type: Alert properties: message: Access Denied description: You do not have permission to view this resource. Contact your administrator to request access to the Analytics dashboard. type: error icon: Ban closable: true - id: real_feature_flag type: Alert properties: message: Beta Feature Enabled description: You are using an experimental feature. Some functionality may be unstable. Please report any issues you encounter. type: info closable: true closeText: Understood icon: FlaskConical ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `banner` | boolean | `false` | Style as banner at top of application window. | | `closable` | boolean | `false` | Allow alert to be closed. | | `closeIcon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to customize the close icon of a closable alert. | | `closeIcon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `closeIcon.color` | string | - | Icon color. | | `closeIcon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `closeIcon.rotate` | number | - | Number of degrees to rotate the icon. | | `closeIcon.spin` | boolean | - | Continuously spin the icon with animation. | | `closeIcon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `closeIcon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `closeIcon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `closeIcon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `closeText` | string | - | Close text to show instead of the close icon. Makes the alert closable. | | `description` | string | - | Content description of alert - supports html. | | `icon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to customize alert icon. | | `icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `icon.color` | string | - | Icon color. | | `icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `icon.rotate` | number | - | Number of degrees to rotate the icon. | | `icon.spin` | boolean | - | Continuously spin the icon with animation. | | `icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `message` | string | - | Content message of alert - supports html. | | `showIcon` | boolean | `true` | Show type default icon. | | `type` | string | `"info"` | Alert style type. Enum: `success`, `info`, `warning`, `error`. | | `variant` | string | `"outlined"` | Alert style variant. `filled` hides the border and keeps the background tint. Enum: `outlined`, `filled`. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design alert tokens](https://ant.design/components/alert#design-token). | | `theme.defaultPadding` | string | `"8px 12px"` | Default padding for the alert without description. | | `theme.withDescriptionPadding` | string | `"20px 24px"` | Padding for the alert when a description is present. | | `theme.withDescriptionIconSize` | number | `24` | Icon size when the alert has a description. | | `theme.colorText` | string | - | Text color of the alert message. | | `theme.colorTextHeading` | string | - | Heading text color when the alert has a description. | | `theme.colorIcon` | string | - | Icon color override. | | `theme.colorIconHover` | string | - | Icon hover color for the close button. | | `theme.fontSize` | number | `14` | Font size of the alert text. | | `theme.fontSizeLG` | number | `16` | Font size of the alert message when a description is present. | | `theme.lineHeight` | number | `1.5714` | Line height of the alert text. | | `theme.borderRadius` | number | `8` | Border radius of the alert container. | | `theme.borderRadiusLG` | number | `8` | Border radius for large alerts. | | `theme.colorSuccessBg` | string | - | Background color for success alerts. | | `theme.colorSuccessBorder` | string | - | Border color for success alerts. | | `theme.colorInfoBg` | string | - | Background color for info alerts. | | `theme.colorInfoBorder` | string | - | Border color for info alerts. | | `theme.colorWarningBg` | string | - | Background color for warning alerts. | | `theme.colorWarningBorder` | string | - | Border color for warning alerts. | | `theme.colorErrorBg` | string | - | Background color for error alerts. | | `theme.colorErrorBorder` | string | - | Border color for error alerts. | | Event | Event Data | Description | | --- | --- | --- | | `onClose` | \- | Called when Alert close button is clicked. | | `afterClose` | \- | Called after Alert has been closed. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Alert element. | | `/icon` | The icon in the Alert. | | `/message` | The Alert message. | | `/description` | The Alert description. | | `/action` | The Alert action. | | `/closeIcon` | The Alert close icon. | | Slot | Description | | --- | --- | | `action` | Action area content. | --- url: https://docs.lowdefy.com/md/container-blocks/affix.md --- # Affix An Affix block makes its content stick to the viewport when scrolling. ```yaml - id: affix_basic_top type: Affix properties: offsetTop: 10 blocks: - id: affix_basic_top_btn type: Button properties: title: Affix Top (10px) color: primary variant: solid ``` ```yaml - id: affix_offset_top_0 type: Affix layout: flex: 0 0 auto properties: offsetTop: 0 blocks: - id: affix_offset_top_0_btn type: Button layout: flex: 0 0 auto properties: title: Offset 0px color: primary variant: solid size: small - id: affix_offset_top_20 type: Affix layout: flex: 0 0 auto properties: offsetTop: 20 blocks: - id: affix_offset_top_20_btn type: Button layout: flex: 0 0 auto properties: title: Offset 20px color: primary variant: outlined size: small - id: affix_offset_top_80 type: Affix layout: flex: 0 0 auto properties: offsetTop: 80 blocks: - id: affix_offset_top_80_btn type: Button layout: flex: 0 0 auto properties: title: Offset 80px color: primary variant: dashed size: small ``` Scroll this panel. The toolbar sticks to the top of the panel instead of the page, because `target` names the panel's id. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ```yaml - id: affix_panel type: Box class: .element: h-48 overflow-y-auto rounded border border-gray-200 p-3 blocks: - id: affix_panel_toolbar type: Affix properties: offsetTop: 0 target: affix_panel blocks: - id: affix_panel_toolbar_btn type: Button layout: flex: 0 0 auto properties: title: Save changes icon: save size: small - id: affix_panel_text type: Paragraph properties: content: Scroll this panel. The toolbar sticks to the top of the panel instead of the page, because `target` names the panel's id. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ``` ```yaml - id: affix_offset_bottom_0 type: Affix layout: flex: 0 0 auto properties: offsetBottom: 0 blocks: - id: affix_offset_bottom_0_btn type: Button layout: flex: 0 0 auto properties: title: Bottom 0px color: primary variant: solid size: small - id: affix_offset_bottom_30 type: Affix layout: flex: 0 0 auto properties: offsetBottom: 30 blocks: - id: affix_offset_bottom_30_btn type: Button layout: flex: 0 0 auto properties: title: Bottom 30px color: primary variant: outlined size: small - id: affix_offset_bottom_60 type: Affix layout: flex: 0 0 auto properties: offsetBottom: 60 blocks: - id: affix_offset_bottom_60_btn type: Button layout: flex: 0 0 auto properties: title: Bottom 60px color: primary variant: dashed size: small ``` ```yaml - id: affix_on_change type: Affix properties: offsetTop: 10 events: onChange: - id: affix_on_change_message type: DisplayMessage params: content: _if: test: _eq: - _event: affixed - true then: Affix is now fixed to the viewport. else: Affix returned to its original position. duration: 3 blocks: - id: affix_on_change_btn type: Button properties: title: Scroll to trigger onChange color: primary variant: solid ``` ```yaml - id: affix_theme_default_z type: Affix layout: flex: 0 0 auto properties: offsetTop: 10 theme: zIndexPopup: 10 blocks: - id: affix_theme_default_z_btn type: Button layout: flex: 0 0 auto properties: title: Default z-index (10) color: default variant: outlined size: small - id: affix_theme_high_z type: Affix layout: flex: 0 0 auto properties: offsetTop: 10 theme: zIndexPopup: 100 blocks: - id: affix_theme_high_z_btn type: Button layout: flex: 0 0 auto properties: title: High z-index (100) color: primary variant: solid size: small - id: affix_theme_low_z type: Affix layout: flex: 0 0 auto properties: offsetTop: 10 theme: zIndexPopup: 1 blocks: - id: affix_theme_low_z_btn type: Button layout: flex: 0 0 auto properties: title: Low z-index (1) color: default variant: dashed size: small ``` ```yaml - id: affix_css_shadow type: Affix layout: flex: 0 0 auto class: .element: shadow-lg rounded-lg properties: offsetTop: 10 blocks: - id: affix_css_shadow_btn type: Button layout: flex: 0 0 auto properties: title: Shadow + Rounded color: primary variant: solid size: small - id: affix_css_ring type: Affix layout: flex: 0 0 auto class: .element: ring-2 ring-green-400 rounded-lg properties: offsetTop: 10 blocks: - id: affix_css_ring_btn type: Button layout: flex: 0 0 auto properties: title: Ring Border color: default variant: outlined size: small ``` ```yaml - id: affix_style_zindex type: Affix properties: offsetTop: 10 style: .element: zIndex: 200 boxShadow: 0 4px 12px rgba(0, 0, 0, 0.15) blocks: - id: affix_style_zindex_btn type: Button properties: title: Custom z-index (200) + Shadow color: primary variant: solid ``` Edit Employee Record ```yaml - id: affix_form_toolbar type: Affix properties: offsetTop: 0 blocks: - id: affix_form_toolbar_card type: Card properties: size: small bordered: true blocks: - id: affix_form_toolbar_row type: Box layout: gap: 8 justify: space-between align: center blocks: - id: affix_form_toolbar_title type: Title layout: flex: 0 0 auto properties: content: Edit Employee Record level: 5 - id: affix_form_toolbar_actions type: Box layout: flex: 0 0 auto gap: 8 blocks: - id: affix_form_toolbar_discard type: Button layout: flex: 0 0 auto properties: title: Discard color: default variant: outlined size: small events: onClick: - id: affix_form_toolbar_discard_msg type: DisplayMessage params: content: Changes discarded. status: warning - id: affix_form_toolbar_save type: Button layout: flex: 0 0 auto properties: title: Save Changes icon: save color: primary variant: solid size: small events: onClick: - id: affix_form_toolbar_save_msg type: DisplayMessage params: content: Employee record saved successfully. status: success ``` 3 items selected ```yaml - id: affix_bottom_bar type: Affix properties: offsetBottom: 0 blocks: - id: affix_bottom_bar_card type: Card class: shadow-lg properties: size: small blocks: - id: affix_bottom_bar_row type: Box layout: gap: 12 justify: flex-end align: center blocks: - id: affix_bottom_bar_status type: Paragraph layout: flex: 1 1 0 properties: content: 3 items selected - id: affix_bottom_bar_delete type: Button layout: flex: 0 0 auto properties: title: Delete icon: delete color: danger variant: outlined size: small events: onClick: - id: affix_bottom_bar_delete_msg type: DisplayMessage params: content: Items deleted. status: error - id: affix_bottom_bar_export type: Button layout: flex: 0 0 auto properties: title: Export icon: download color: primary variant: solid size: small events: onClick: - id: affix_bottom_bar_export_msg type: DisplayMessage params: content: Export started. status: success ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `offsetBottom` | number | - | Offset from the bottom of the viewport (in pixels). | | `offsetTop` | number | `0` | Offset from the top of the viewport (in pixels). | | `target` | string | - | Id of the scrollable element the content sticks to, for example a scrolling panel. Defaults to the page window. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design affix tokens](https://ant.design/components/affix#design-token). | | `theme.zIndexPopup` | number | `10` | Z-index of the affix element when fixed. | | Event | Event Data | Description | | --- | --- | --- | | `onChange` | `{ affixed }` | Triggered when container affix status changes. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Affix element. | | Slot | Description | | --- | --- | | `content` | Child blocks wrapped by the Affix. | --- url: https://docs.lowdefy.com/md/container-blocks/badge.md --- # Badge Badge with count, dot, status, color, and overflow. U U U ```yaml - id: basic_count_5 type: Badge layout: flex: 0 0 auto properties: count: 5 blocks: - id: basic_count_5_child type: Avatar properties: shape: square content: U - id: basic_count_25 type: Badge layout: flex: 0 0 auto properties: count: 25 blocks: - id: basic_count_25_child type: Avatar properties: shape: square content: U - id: basic_count_100 type: Badge layout: flex: 0 0 auto properties: count: 100 blocks: - id: basic_count_100_child type: Avatar properties: shape: square content: U ``` U U ```yaml - id: zero_hidden type: Badge layout: flex: 0 0 auto properties: count: 0 blocks: - id: zero_hidden_child type: Avatar properties: shape: square content: U - id: zero_shown type: Badge layout: flex: 0 0 auto properties: count: 0 showZero: true blocks: - id: zero_shown_child type: Avatar properties: shape: square content: U ``` U ```yaml - id: dot_on_avatar type: Badge layout: flex: 0 0 auto properties: dot: true blocks: - id: dot_on_avatar_child type: Avatar properties: shape: square content: U - id: dot_on_icon_avatar type: Badge layout: flex: 0 0 auto properties: dot: true blocks: - id: dot_on_icon_avatar_child type: Avatar properties: icon: bell shape: square - id: dot_on_button type: Badge layout: flex: 0 0 auto properties: dot: true blocks: - id: dot_on_button_child type: Button properties: title: Notifications color: primary variant: outlined ``` ```yaml - id: standalone_count type: Badge layout: flex: 0 0 auto properties: count: 25 - id: standalone_dot type: Badge layout: flex: 0 0 auto properties: dot: true - id: standalone_text type: Badge layout: flex: 0 0 auto properties: count: New ``` U U U U ```yaml - id: size_def type: Badge layout: flex: 0 0 auto properties: count: 11 size: default blocks: - id: size_def_child type: Avatar properties: shape: square content: U - id: size_sm type: Badge layout: flex: 0 0 auto properties: count: 11 size: small blocks: - id: size_sm_child type: Avatar properties: shape: square content: U - id: size_def_large_count type: Badge layout: flex: 0 0 auto properties: count: 999 overflowCount: 999 size: default blocks: - id: size_def_large_count_child type: Avatar properties: shape: square content: U - id: size_sm_large_count type: Badge layout: flex: 0 0 auto properties: count: 999 overflowCount: 999 size: small blocks: - id: size_sm_large_count_child type: Avatar properties: shape: square content: U ``` U U U ```yaml - id: overflow_default type: Badge layout: flex: 0 0 auto properties: count: 150 blocks: - id: overflow_default_child type: Avatar properties: shape: square content: U - id: overflow_10 type: Badge layout: flex: 0 0 auto properties: count: 150 overflowCount: 10 blocks: - id: overflow_10_child type: Avatar properties: shape: square content: U - id: overflow_999 type: Badge layout: flex: 0 0 auto properties: count: 2000 overflowCount: 999 blocks: - id: overflow_999_child type: Avatar properties: shape: square content: U ``` A B C D ```yaml - id: preset_blue type: Badge layout: flex: 0 0 auto properties: color: blue count: 5 blocks: - id: preset_blue_child type: Avatar properties: shape: square content: A - id: preset_green type: Badge layout: flex: 0 0 auto properties: color: green count: 5 blocks: - id: preset_green_child type: Avatar properties: shape: square content: B - id: preset_purple type: Badge layout: flex: 0 0 auto properties: color: purple count: 5 blocks: - id: preset_purple_child type: Avatar properties: shape: square content: C - id: preset_gold type: Badge layout: flex: 0 0 auto properties: color: gold count: 5 blocks: - id: preset_gold_child type: Avatar properties: shape: square content: D ``` A B C ```yaml - id: hex_coral type: Badge layout: flex: 0 0 auto properties: color: "#ff6b6b" count: 3 blocks: - id: hex_coral_child type: Avatar properties: shape: square content: A - id: hex_teal type: Badge layout: flex: 0 0 auto properties: color: "#20c997" count: 7 blocks: - id: hex_teal_child type: Avatar properties: shape: square content: B - id: hex_indigo type: Badge layout: flex: 0 0 auto properties: color: "#4c6ef5" count: 12 blocks: - id: hex_indigo_child type: Avatar properties: shape: square content: C ``` U U U ```yaml - id: text_new type: Badge layout: flex: 0 0 auto properties: count: New blocks: - id: text_new_child type: Avatar properties: shape: square content: U - id: text_hot type: Badge layout: flex: 0 0 auto properties: count: Hot color: red blocks: - id: text_hot_child type: Avatar properties: shape: square content: U - id: text_beta type: Badge layout: flex: 0 0 auto properties: count: Beta color: blue blocks: - id: text_beta_child type: Avatar properties: shape: square content: U ``` ```yaml - id: status_success type: Badge layout: flex: 0 0 auto properties: status: success text: Success - id: status_processing type: Badge layout: flex: 0 0 auto properties: status: processing text: Processing - id: status_default type: Badge layout: flex: 0 0 auto properties: status: default text: Default - id: status_error type: Badge layout: flex: 0 0 auto properties: status: error text: Error - id: status_warning type: Badge layout: flex: 0 0 auto properties: status: warning text: Warning ``` ```yaml - id: status_dot_success type: Badge layout: flex: 0 0 auto properties: status: success - id: status_dot_processing type: Badge layout: flex: 0 0 auto properties: status: processing - id: status_dot_error type: Badge layout: flex: 0 0 auto properties: status: error ``` U U U ```yaml - id: icon_bell type: Badge layout: flex: 0 0 auto properties: icon: bell blocks: - id: icon_bell_child type: Avatar properties: shape: square content: U - id: icon_clock type: Badge layout: flex: 0 0 auto properties: icon: name: clock color: "#1677ff" blocks: - id: icon_clock_child type: Avatar properties: shape: square content: U - id: icon_exclamation type: Badge layout: flex: 0 0 auto properties: icon: name: CircleAlert color: "#faad14" blocks: - id: icon_exclamation_child type: Avatar properties: shape: square content: U ``` U U U ```yaml - id: offset_none type: Badge layout: flex: 0 0 auto properties: count: 5 blocks: - id: offset_none_child type: Avatar properties: shape: square content: U - id: offset_right type: Badge layout: flex: 0 0 auto properties: count: 5 offset: - 10 - 0 blocks: - id: offset_right_child type: Avatar properties: shape: square content: U - id: offset_both type: Badge layout: flex: 0 0 auto properties: count: 5 offset: - -5 - 10 blocks: - id: offset_both_child type: Avatar properties: shape: square content: U ``` U N ```yaml - id: title_tooltip type: Badge layout: flex: 0 0 auto properties: count: 5 title: You have 5 unread messages blocks: - id: title_tooltip_child type: Avatar properties: shape: square content: U - id: title_tooltip_custom type: Badge layout: flex: 0 0 auto properties: count: 99 title: 99 pending tasks blocks: - id: title_tooltip_custom_child type: Avatar properties: shape: square icon: user - id: title_tooltip_off type: Badge layout: flex: 0 0 auto properties: count: 3 title: false blocks: - id: title_tooltip_off_child type: Avatar properties: shape: square content: N ``` JD ```yaml - id: wrap_avatar_circle type: Badge layout: flex: 0 0 auto properties: count: 3 blocks: - id: wrap_avatar_circle_child type: Avatar properties: content: JD color: "#1677ff" - id: wrap_button_solid type: Badge layout: flex: 0 0 auto properties: count: 5 blocks: - id: wrap_button_solid_child type: Button properties: title: Messages color: primary variant: solid - id: wrap_icon_bell type: Badge layout: flex: 0 0 auto properties: count: 12 blocks: - id: wrap_icon_bell_child type: Icon properties: name: bell size: 24 - id: wrap_icon_mail type: Badge layout: flex: 0 0 auto properties: dot: true blocks: - id: wrap_icon_mail_child type: Icon properties: name: mail size: 24 ``` Up to 20 members, shared workspaces and priority support. Pay yearly and save on every seat. ```yaml - id: ribbon_row type: Box layout: gap: 24 blocks: - id: ribbon_recommended type: Badge layout: span: 12 properties: ribbon: text: Recommended blocks: - id: ribbon_recommended_card type: Card properties: title: Team plan blocks: - id: ribbon_recommended_text type: Paragraph properties: content: Up to 20 members, shared workspaces and priority support. - id: ribbon_sale type: Badge layout: span: 12 properties: ribbon: text: 20% off color: volcano placement: start blocks: - id: ribbon_sale_card type: Card properties: title: Annual billing blocks: - id: ribbon_sale_text type: Paragraph properties: content: Pay yearly and save on every seat. ``` U U U ```yaml - id: css_indicator_shadow type: Badge layout: flex: 0 0 auto properties: count: 5 style: .indicator: boxShadow: "0 0 0 2px #1677ff" blocks: - id: css_indicator_shadow_child type: Avatar properties: shape: square content: U - id: css_indicator_green type: Badge layout: flex: 0 0 auto properties: count: 5 style: .indicator: backgroundColor: "#52c41a" blocks: - id: css_indicator_green_child type: Avatar properties: shape: square content: U - id: css_indicator_bold type: Badge layout: flex: 0 0 auto properties: count: 42 style: .indicator: fontWeight: bold fontSize: 14px blocks: - id: css_indicator_bold_child type: Avatar properties: shape: square content: U size: large ``` A B Inbox ```yaml - id: tw_element_margin type: Badge class: .element: mr-8 layout: flex: 0 0 auto properties: count: 3 blocks: - id: tw_element_margin_child type: Avatar properties: shape: square content: A - id: tw_indicator_animate type: Badge class: .indicator: animate-bounce layout: flex: 0 0 auto properties: count: 7 blocks: - id: tw_indicator_animate_child type: Avatar properties: shape: square content: B - id: tw_badge_in_box type: Badge class: .element: p-1 layout: flex: 0 0 auto properties: count: 3 blocks: - id: tw_badge_in_box_child type: Box class: bg-bg-layout rounded-lg p-4 blocks: - id: tw_badge_in_box_text type: Paragraph properties: content: Inbox ``` U U U U U ```yaml - id: theme_large_indicator type: Badge layout: flex: 0 0 auto properties: count: 8 theme: indicatorHeight: 28 textFontSize: 16 textFontWeight: bold blocks: - id: theme_large_indicator_child type: Avatar properties: shape: square content: U size: large - id: theme_small_indicator type: Badge layout: flex: 0 0 auto properties: count: 3 size: small theme: indicatorHeightSM: 18 textFontSizeSM: 10 blocks: - id: theme_small_indicator_child type: Avatar properties: shape: square content: U - id: theme_large_dot type: Badge layout: flex: 0 0 auto properties: dot: true theme: dotSize: 12 blocks: - id: theme_large_dot_child type: Avatar properties: shape: square content: U - id: theme_blue_badge type: Badge layout: flex: 0 0 auto properties: count: 15 theme: colorError: "#1677ff" colorErrorHover: "#4096ff" blocks: - id: theme_blue_badge_child type: Avatar properties: shape: square content: U - id: theme_large_status type: Badge layout: flex: 0 0 auto properties: status: processing text: Processing theme: statusSize: 10 - id: theme_combined type: Badge layout: flex: 0 0 auto properties: count: 42 theme: indicatorHeight: 26 textFontSize: 14 textFontWeight: bold textLineHeight: 26 colorError: "#722ed1" colorErrorHover: "#9254de" indicatorZIndex: 10 blocks: - id: theme_combined_child type: Avatar properties: shape: square content: U size: large ``` JD ```yaml - id: rw_notif_header type: Box layout: gap: 24 align: center justify: end class: bg-bg-container p-4 rounded-lg shadow-sm border border-border blocks: - id: rw_notif_search type: TextInput layout: flex: 1 1 0 properties: placeholder: Search... prefixIcon: search - id: rw_notif_badge_mail type: Badge layout: flex: 0 0 auto properties: count: 4 size: small blocks: - id: rw_notif_mail_btn type: Button properties: icon: mail color: default variant: text hideTitle: true events: onClick: - id: rw_notif_mail_action type: DisplayMessage params: content: You have 4 unread messages. status: info - id: rw_notif_badge_bell type: Badge layout: flex: 0 0 auto properties: dot: true blocks: - id: rw_notif_bell_btn type: Button properties: icon: bell color: default variant: text hideTitle: true events: onClick: - id: rw_notif_bell_action type: DisplayMessage params: content: You have new notifications. status: info - id: rw_notif_avatar type: Avatar layout: flex: 0 0 auto properties: content: JD color: "#1677ff" ``` #ORD-1042 Sarah Johnson #ORD-1043 Michael Chen #ORD-1044 Emily Davis #ORD-1045 James Wilson ```yaml - id: rw_orders_card type: Card properties: title: Recent Orders slots: extra: blocks: - id: rw_orders_extra_btn type: Button layout: flex: 0 0 auto properties: title: View All color: primary variant: link size: small events: onClick: - id: rw_orders_link_action type: DisplayMessage params: content: Navigating to full orders list. status: info blocks: - id: rw_order_row1 type: Box layout: gap: 16 align: center class: py-3 border-b border-border blocks: - id: rw_order1_id type: Paragraph layout: flex: 0 0 120px properties: content: "#ORD-1042" - id: rw_order1_customer type: Paragraph layout: flex: 1 1 0 properties: content: Sarah Johnson - id: rw_order1_status type: Badge layout: flex: 0 0 auto properties: status: success text: Delivered - id: rw_order_row2 type: Box layout: gap: 16 align: center class: py-3 border-b border-border blocks: - id: rw_order2_id type: Paragraph layout: flex: 0 0 120px properties: content: "#ORD-1043" - id: rw_order2_customer type: Paragraph layout: flex: 1 1 0 properties: content: Michael Chen - id: rw_order2_status type: Badge layout: flex: 0 0 auto properties: status: processing text: Shipping - id: rw_order_row3 type: Box layout: gap: 16 align: center class: py-3 border-b border-border blocks: - id: rw_order3_id type: Paragraph layout: flex: 0 0 120px properties: content: "#ORD-1044" - id: rw_order3_customer type: Paragraph layout: flex: 1 1 0 properties: content: Emily Davis - id: rw_order3_status type: Badge layout: flex: 0 0 auto properties: status: warning text: Pending Payment - id: rw_order_row4 type: Box layout: gap: 16 align: center class: py-3 blocks: - id: rw_order4_id type: Paragraph layout: flex: 0 0 120px properties: content: "#ORD-1045" - id: rw_order4_customer type: Paragraph layout: flex: 1 1 0 properties: content: James Wilson - id: rw_order4_status type: Badge layout: flex: 0 0 auto properties: status: error text: Cancelled ``` ```yaml - id: rw_sidebar type: Box layout: direction: column gap: 0 class: bg-bg-container rounded-lg shadow-sm border border-border p-2 max-w-xs blocks: - id: rw_nav_inbox_btn type: Button class: justify-start w-full properties: title: Inbox icon: Inbox color: primary variant: text events: onClick: - id: rw_nav_inbox_action type: SetState params: selectedNav: inbox blocks: - id: rw_nav_inbox_badge type: Badge layout: flex: 0 0 auto properties: count: 12 size: small theme: colorError: "#1677ff" - id: rw_nav_alerts_btn type: Button class: justify-start w-full properties: title: Alerts icon: Siren color: default variant: text events: onClick: - id: rw_nav_alerts_action type: SetState params: selectedNav: alerts blocks: - id: rw_nav_alerts_badge type: Badge layout: flex: 0 0 auto properties: count: 3 size: small color: orange - id: rw_nav_tasks_btn type: Button class: justify-start w-full properties: title: Tasks icon: SquareCheck color: default variant: text events: onClick: - id: rw_nav_tasks_action type: SetState params: selectedNav: tasks blocks: - id: rw_nav_tasks_badge type: Badge layout: flex: 0 0 auto properties: dot: true - id: rw_nav_settings_btn type: Button class: justify-start w-full properties: title: Settings icon: settings color: default variant: text events: onClick: - id: rw_nav_settings_action type: SetState params: selectedNav: settings ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `color` | string | - | Customize Badge dot color. | | `count` | number \| string | - | Text to show in badge. | | `dot` | boolean | `false` | Whether to display a red dot instead of count. | | `size` | string | `"default"` | Sets the size of badge if count is set. Enum: `default`, `small`. | | `icon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to use an icon in badge. | | `icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `icon.color` | string | - | Icon color. | | `icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `icon.rotate` | number | - | Number of degrees to rotate the icon. | | `icon.spin` | boolean | - | Continuously spin the icon with animation. | | `icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `offset` | array | - | Set offset of the badge dot, array of numbers for x and y offset ([x,y]). | | `overflowCount` | number | `99` | Max count to show | | `showZero` | boolean | `false` | Whether to show badge when count is zero. | | `status` | string | `null` | Set Badge as a status dot. Enum: `success`, `processing`, `default`, `error`, `warning`. | | `text` | string | - | If status is set, text sets the display text of the status dot. | | `ribbon` | object | - | Render a ribbon on the corner of the content area instead of a count badge. Count, dot and status properties are ignored when ribbon is set. | | `ribbon.text` | string | - | Text inside the ribbon. | | `ribbon.color` | string | - | Ribbon color. A preset color name or any css color. | | `ribbon.placement` | string | `"end"` | Corner of the content the ribbon is placed on. `start` and `end` follow the text direction. Enum: `start`, `end`. | | `title` | string \| boolean | - | Text to show when hovering over the badge. Set to false to remove the native tooltip. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design badge tokens](https://ant.design/components/badge#design-token). | | `theme.indicatorHeight` | number | `20` | Height of the badge indicator. | | `theme.indicatorHeightSM` | number | `14` | Height of the badge indicator in small size. | | `theme.dotSize` | number | `6` | Size of the dot badge. | | `theme.textFontSize` | number | `12` | Font size for badge count text. | | `theme.textFontSizeSM` | number | `12` | Font size for badge count text in small size. | | `theme.textFontWeight` | string | `"normal"` | Font weight for badge count text. | | `theme.paddingInline` | number \| string | `8` | Inline padding of a badge count with several characters. | | `theme.textLineHeight` | number | - | Line height for badge count text. | | `theme.statusSize` | number | `6` | Size of the status dot. | | `theme.indicatorZIndex` | number \| string | `"auto"` | Z-index of the badge indicator. | | `theme.colorBorderBg` | string | - | Background color of the badge border (gap between badge and wrapped element). | | `theme.colorError` | string | - | Color of the badge indicator (default red). | | `theme.colorErrorHover` | string | - | Hover color of the badge indicator. | No events defined. | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Badge element, or the ribbon wrapper when ribbon is set. | | `/icon` | The icon in the Badge. | | `/indicator` | The Badge count or dot indicator, or the ribbon when ribbon is set. | | Slot | Description | | --- | --- | | `content` | Child blocks wrapped by the Badge. | --- url: https://docs.lowdefy.com/md/container-blocks/box.md --- # Box Basic block-level container for layout. ```yaml - id: box_basic type: Box blocks: - id: box_basic_text type: Html properties: html: '

A basic Box renders a div and contains child blocks.

' ``` A Box can render a simple string using the content property. Bold and italic text via the content property. This content property overrides the content area below. ```yaml - id: box_content_string type: Box properties: content: A Box can render a simple string using the content property. - id: box_content_html type: Box properties: content: Bold and italic text via the content property. - id: box_content_override type: Box properties: content: This content property overrides the content area below. blocks: - id: box_content_override_child type: Html properties: html: '

This child block is hidden because the content property takes priority.

' ``` ```yaml - id: box_onclick type: Box style: .element: background: rgba(22, 119, 255, 0.08) border: 1px solid rgba(22, 119, 255, 0.3) padding: 16 borderRadius: 8 cursor: pointer events: onClick: - id: box_onclick_message type: DisplayMessage params: content: Box clicked! status: info blocks: - id: box_onclick_text type: Html properties: html: '

Click this box to trigger a DisplayMessage action.

' - id: box_onclick_setstate type: Box style: .element: background: rgba(82, 196, 26, 0.08) border: 1px solid rgba(82, 196, 26, 0.3) padding: 16 borderRadius: 8 cursor: pointer events: onClick: - id: box_onclick_set type: SetState params: box_click_count: _sum: - _state: box_click_count - 1 blocks: - id: box_onclick_counter_text type: Html properties: html: '

Click this box to increment a counter with SetState.

' - id: box_onclick_link type: Box style: .element: background: rgba(250, 140, 22, 0.08) border: 1px solid rgba(250, 140, 22, 0.3) padding: 16 borderRadius: 8 cursor: pointer events: onClick: - id: box_onclick_link_action type: Link params: url: / blocks: - id: box_onclick_link_text type: Html properties: html: '

Click this box to navigate using the Link action.

' ``` ```yaml - id: box_onpaste type: Box style: .element: background: rgba(47, 84, 235, 0.08) border: 1px dashed rgba(47, 84, 235, 0.3) padding: 16 borderRadius: 8 events: onPaste: - id: box_onpaste_message type: DisplayMessage params: content: Content pasted into the box! status: success blocks: - id: box_onpaste_text type: Html properties: html: '

Focus this box and paste something to trigger the onPaste event.

' ``` ```yaml - id: box_gap_8 type: Box layout: gap: 8 blocks: - id: box_gap_8_a type: Button layout: flex: 0 0 auto properties: title: Gap 8 color: primary variant: solid - id: box_gap_8_b type: Button layout: flex: 0 0 auto properties: title: Gap 8 color: primary variant: solid - id: box_gap_8_c type: Button layout: flex: 0 0 auto properties: title: Gap 8 color: primary variant: solid - id: box_gap_16 type: Box layout: gap: 16 blocks: - id: box_gap_16_a type: Button layout: flex: 0 0 auto properties: title: Gap 16 color: primary variant: outlined - id: box_gap_16_b type: Button layout: flex: 0 0 auto properties: title: Gap 16 color: primary variant: outlined - id: box_gap_16_c type: Button layout: flex: 0 0 auto properties: title: Gap 16 color: primary variant: outlined - id: box_gap_32 type: Box layout: gap: 32 blocks: - id: box_gap_32_a type: Button layout: flex: 0 0 auto properties: title: Gap 32 color: green variant: solid - id: box_gap_32_b type: Button layout: flex: 0 0 auto properties: title: Gap 32 color: green variant: solid - id: box_gap_32_c type: Button layout: flex: 0 0 auto properties: title: Gap 32 color: green variant: solid ``` **justify: flex-start** **justify: center** **justify: flex-end** **justify: space-between** **justify: space-around** ```yaml - id: box_justify_start_label type: Markdown properties: content: "**justify: flex-start**" - id: box_justify_start type: Box layout: gap: 8 justify: flex-start class: p-3 rounded-lg border border-gray-500/20 blocks: - id: box_js_a type: Button layout: flex: 0 0 auto properties: title: A color: primary variant: solid - id: box_js_b type: Button layout: flex: 0 0 auto properties: title: B color: primary variant: solid - id: box_js_c type: Button layout: flex: 0 0 auto properties: title: C color: primary variant: solid - id: box_justify_center_label type: Markdown properties: content: "**justify: center**" - id: box_justify_center type: Box layout: gap: 8 justify: center class: p-3 rounded-lg border border-gray-500/20 blocks: - id: box_jc_a type: Button layout: flex: 0 0 auto properties: title: A color: green variant: solid - id: box_jc_b type: Button layout: flex: 0 0 auto properties: title: B color: green variant: solid - id: box_jc_c type: Button layout: flex: 0 0 auto properties: title: C color: green variant: solid - id: box_justify_end_label type: Markdown properties: content: "**justify: flex-end**" - id: box_justify_end type: Box layout: gap: 8 justify: flex-end class: p-3 rounded-lg border border-gray-500/20 blocks: - id: box_je_a type: Button layout: flex: 0 0 auto properties: title: A color: danger variant: solid - id: box_je_b type: Button layout: flex: 0 0 auto properties: title: B color: danger variant: solid - id: box_je_c type: Button layout: flex: 0 0 auto properties: title: C color: danger variant: solid - id: box_justify_between_label type: Markdown properties: content: "**justify: space-between**" - id: box_justify_between type: Box layout: gap: 8 justify: space-between class: p-3 rounded-lg border border-gray-500/20 blocks: - id: box_jb_a type: Button layout: flex: 0 0 auto properties: title: A color: purple variant: solid - id: box_jb_b type: Button layout: flex: 0 0 auto properties: title: B color: purple variant: solid - id: box_jb_c type: Button layout: flex: 0 0 auto properties: title: C color: purple variant: solid - id: box_justify_around_label type: Markdown properties: content: "**justify: space-around**" - id: box_justify_around type: Box layout: gap: 8 justify: space-around class: p-3 rounded-lg border border-gray-500/20 blocks: - id: box_ja_a type: Button layout: flex: 0 0 auto properties: title: A color: cyan variant: solid - id: box_ja_b type: Button layout: flex: 0 0 auto properties: title: B color: cyan variant: solid - id: box_ja_c type: Button layout: flex: 0 0 auto properties: title: C color: cyan variant: solid ``` **align: flex-start** Tall Short **align: center** Tall Short **align: flex-end** Tall Short ```yaml - id: box_align_start_label type: Markdown properties: content: "**align: flex-start**" - id: box_align_start type: Box layout: gap: 8 align: flex-start class: p-3 rounded-lg border border-gray-500/20 style: .element: minHeight: 100 blocks: - id: box_as_tall type: Box layout: flex: 0 0 auto class: bg-blue-500 text-white px-4 py-2 rounded-md flex items-center style: .element: height: 80 properties: content: Tall - id: box_as_short type: Box layout: flex: 0 0 auto class: bg-blue-500 text-white px-4 py-2 rounded-md properties: content: Short - id: box_align_center_label type: Markdown properties: content: "**align: center**" - id: box_align_center type: Box layout: gap: 8 align: center class: p-3 rounded-lg border border-gray-500/20 style: .element: minHeight: 100 blocks: - id: box_ac_tall type: Box layout: flex: 0 0 auto class: bg-green-500 text-white px-4 py-2 rounded-md flex items-center style: .element: height: 80 properties: content: Tall - id: box_ac_short type: Box layout: flex: 0 0 auto class: bg-green-500 text-white px-4 py-2 rounded-md properties: content: Short - id: box_align_end_label type: Markdown properties: content: "**align: flex-end**" - id: box_align_end type: Box layout: gap: 8 align: flex-end class: p-3 rounded-lg border border-gray-500/20 style: .element: minHeight: 100 blocks: - id: box_ae_tall type: Box layout: flex: 0 0 auto class: bg-red-500 text-white px-4 py-2 rounded-md flex items-center style: .element: height: 80 properties: content: Tall - id: box_ae_short type: Box layout: flex: 0 0 auto class: bg-red-500 text-white px-4 py-2 rounded-md properties: content: Short ``` **Row direction (default):** Item 1 Item 2 Item 3 **Column direction (via style):** Item 1 Item 2 Item 3 ```yaml - id: box_dir_row_label type: Markdown properties: content: "**Row direction (default):**" - id: box_dir_row type: Box layout: gap: 8 class: p-3 rounded-lg border border-blue-500/20 bg-blue-500/5 blocks: - id: box_dr_a type: Box layout: flex: 0 0 auto class: bg-blue-500 text-white px-4 py-2 rounded-md properties: content: Item 1 - id: box_dr_b type: Box layout: flex: 0 0 auto class: bg-blue-500 text-white px-4 py-2 rounded-md properties: content: Item 2 - id: box_dr_c type: Box layout: flex: 0 0 auto class: bg-blue-500 text-white px-4 py-2 rounded-md properties: content: Item 3 - id: box_dir_column_label type: Markdown properties: content: "**Column direction (via style):**" - id: box_dir_column type: Box layout: gap: 8 class: p-3 rounded-lg border border-green-500/20 bg-green-500/5 style: .element: display: flex flexDirection: column blocks: - id: box_dc_a type: Box class: bg-green-500 text-white px-4 py-2 rounded-md properties: content: Item 1 - id: box_dc_b type: Box class: bg-green-500 text-white px-4 py-2 rounded-md properties: content: Item 2 - id: box_dc_c type: Box class: bg-green-500 text-white px-4 py-2 rounded-md properties: content: Item 3 ``` **Boxes wrap to the next line when they overflow:** Item 1 Item 2 Item 3 Item 4 Item 5 Item 6 ```yaml - id: box_wrap_label type: Markdown properties: content: "**Boxes wrap to the next line when they overflow:**" - id: box_wrap type: Box layout: gap: 8 wrap: wrap class: p-3 rounded-lg border border-gray-500/20 blocks: - id: box_w_a type: Box layout: flex: 0 0 150px class: bg-purple-600 text-white px-4 py-2 rounded-md text-center properties: content: Item 1 - id: box_w_b type: Box layout: flex: 0 0 150px class: bg-purple-600 text-white px-4 py-2 rounded-md text-center properties: content: Item 2 - id: box_w_c type: Box layout: flex: 0 0 150px class: bg-purple-600 text-white px-4 py-2 rounded-md text-center properties: content: Item 3 - id: box_w_d type: Box layout: flex: 0 0 150px class: bg-purple-600 text-white px-4 py-2 rounded-md text-center properties: content: Item 4 - id: box_w_e type: Box layout: flex: 0 0 150px class: bg-purple-600 text-white px-4 py-2 rounded-md text-center properties: content: Item 5 - id: box_w_f type: Box layout: flex: 0 0 150px class: bg-purple-600 text-white px-4 py-2 rounded-md text-center properties: content: Item 6 ``` **Children with flex: 1 grow to fill available space:** Fixed 100px Grows to fill Fixed 100px ```yaml - id: box_flex_label type: Markdown properties: content: "**Children with flex: 1 grow to fill available space:**" - id: box_flex_grow type: Box layout: gap: 8 class: p-3 rounded-lg border border-gray-500/20 blocks: - id: box_fg_fixed type: Box layout: flex: 0 0 100px class: bg-orange-500 text-white px-4 py-2 rounded-md text-center properties: content: Fixed 100px - id: box_fg_grow type: Box layout: flex: 1 class: bg-blue-500 text-white px-4 py-2 rounded-md text-center properties: content: Grows to fill - id: box_fg_fixed2 type: Box layout: flex: 0 0 100px class: bg-orange-500 text-white px-4 py-2 rounded-md text-center properties: content: Fixed 100px ``` ```yaml - id: box_nested_outer type: Box layout: gap: 12 class: p-4 rounded-lg border border-blue-500/20 bg-blue-500/5 blocks: - id: box_nested_left type: Box layout: flex: 1 class: p-4 rounded-md border border-gray-500/20 blocks: - id: box_nested_left_title type: Html properties: html: '

Left Panel

' - id: box_nested_left_text type: Html properties: html: '

This box is nested inside the outer box with flex: 1.

' - id: box_nested_right type: Box layout: flex: 1 class: p-4 rounded-md border border-gray-500/20 blocks: - id: box_nested_right_title type: Html properties: html: '

Right Panel

' - id: box_nested_right_text type: Html properties: html: '

Both panels share equal space via flex: 1.

' ``` ```yaml - id: box_tw_shadow type: Box layout: flex: 0 0 auto class: p-5 rounded-xl shadow-lg border border-gray-500/20 blocks: - id: box_tw_shadow_text type: Html properties: html: '

Elevated box using Tailwind shadow and rounded classes on the element cssKey.

' - id: box_tw_border type: Box layout: flex: 0 0 auto class: border-2 border-dashed border-blue-500 p-4 rounded-lg blocks: - id: box_tw_border_text type: Html properties: html: '

Dashed blue border via Tailwind utilities.

' - id: box_tw_accent type: Box layout: flex: 0 0 auto class: border-l-4 border-l-orange-500 pl-4 py-2 blocks: - id: box_tw_accent_text type: Html properties: html: '

Left accent border using Tailwind border utilities.

' - id: box_tw_centered type: Box class: max-w-sm mx-auto text-center p-4 bg-blue-500/10 rounded-lg blocks: - id: box_tw_centered_text type: Html properties: html: '

Centered and constrained width with Tailwind.

' ``` ```yaml - id: box_csskey_gradient type: Box layout: flex: 0 0 auto class: element: bg-gradient-to-br from-indigo-500 to-purple-600 text-white p-5 rounded-xl blocks: - id: box_csskey_gradient_text type: Html properties: html: '

Gradient Box

Using the element cssKey with a Tailwind gradient background.

' - id: box_csskey_hover type: Box layout: flex: 0 0 auto class: element: p-5 rounded-lg border border-gray-500/20 hover:shadow-lg hover:border-blue-400 transition-all duration-200 blocks: - id: box_csskey_hover_text type: Html properties: html: '

Hover over this box to see a shadow and border color transition using Tailwind hover utilities.

' ``` ```yaml - id: box_style_shadow type: Box layout: flex: 0 0 auto style: .element: padding: 20 borderRadius: 12 boxShadow: 0 4px 12px rgba(0, 0, 0, 0.1) background: rgba(255, 255, 255, 0.06) blocks: - id: box_style_shadow_text type: Html properties: html: '

Elevated box with box-shadow via inline style on the element cssKey.

' - id: box_style_dark type: Box layout: flex: 0 0 auto style: .element: background: "#1a1a2e" color: "#e0e0e0" padding: 20 borderRadius: 8 blocks: - id: box_style_dark_title type: Html properties: html: '

Dark Box

' - id: box_style_dark_text type: Html properties: html: '

Light text on dark background using inline style.

' - id: box_style_gradient type: Box layout: flex: 0 0 auto style: .element: background: linear-gradient(135deg, color: "#ffffff" padding: 20 borderRadius: 8 blocks: - id: box_style_gradient_text type: Html properties: html: '

Gradient Box

A gradient background using the inline style approach.

' ``` ```yaml - id: box_bg_blue type: Box layout: flex: 0 0 auto class: bg-blue-500/10 border border-blue-500/30 p-4 rounded-lg blocks: - id: box_bg_blue_text type: Html properties: html: '

Blue info box

' - id: box_bg_green type: Box layout: flex: 0 0 auto class: bg-green-500/10 border border-green-500/30 p-4 rounded-lg blocks: - id: box_bg_green_text type: Html properties: html: '

Green success box

' - id: box_bg_red type: Box layout: flex: 0 0 auto class: bg-red-500/10 border border-red-500/30 p-4 rounded-lg blocks: - id: box_bg_red_text type: Html properties: html: '

Red error box

' - id: box_bg_yellow type: Box layout: flex: 0 0 auto class: bg-yellow-500/10 border border-yellow-500/30 p-4 rounded-lg blocks: - id: box_bg_yellow_text type: Html properties: html: '

Yellow warning box

' ``` ```yaml - id: box_features_row type: Box layout: gap: 16 wrap: wrap blocks: - id: box_feature_a type: Box layout: flex: 1 1 200px class: p-5 rounded-xl border border-gray-500/20 blocks: - id: box_feature_a_icon type: Icon layout: flex: 0 0 auto properties: name: Rocket size: 32 color: "#1677ff" - id: box_feature_a_title type: Html properties: html: '

Fast

' - id: box_feature_a_desc type: Html properties: html: '

Optimized for performance with lazy loading and code splitting.

' - id: box_feature_b type: Box layout: flex: 1 1 200px class: p-5 rounded-xl border border-gray-500/20 blocks: - id: box_feature_b_icon type: Icon layout: flex: 0 0 auto properties: name: Shield size: 32 color: "#52c41a" - id: box_feature_b_title type: Html properties: html: '

Secure

' - id: box_feature_b_desc type: Html properties: html: '

Built-in security features with role-based access control.

' - id: box_feature_c type: Box layout: flex: 1 1 200px class: p-5 rounded-xl border border-gray-500/20 blocks: - id: box_feature_c_icon type: Icon layout: flex: 0 0 auto properties: name: Plug size: 32 color: "#722ed1" - id: box_feature_c_title type: Html properties: html: '

Extensible

' - id: box_feature_c_desc type: Html properties: html: '

Plug in your own components and connect to any API.

' ``` ```yaml - id: box_page_layout type: Box style: .element: display: flex flexDirection: column minHeight: 300 borderRadius: 8 overflow: hidden border: 1px solid rgba(128, 128, 128, 0.3) blocks: - id: box_page_header type: Box layout: justify: space-between align: center class: bg-slate-900 px-5 py-3 blocks: - id: box_page_logo type: Html layout: flex: 0 0 auto properties: html: 'My App' - id: box_page_nav type: Box layout: flex: 0 0 auto gap: 16 blocks: - id: box_page_nav_home type: Button layout: flex: 0 0 auto properties: title: Home color: default variant: link events: onClick: - id: box_page_nav_home_link type: Link params: url: / - id: box_page_nav_about type: Button layout: flex: 0 0 auto properties: title: About color: default variant: link events: onClick: - id: box_page_nav_about_link type: Link params: url: /about - id: box_page_nav_contact type: Button layout: flex: 0 0 auto properties: title: Contact color: default variant: link events: onClick: - id: box_page_nav_contact_link type: Link params: url: /contact - id: box_page_body type: Box layout: flex: 1 class: p-6 blocks: - id: box_page_body_text type: Html properties: html: '

Main content area grows to fill available space using flex: 1. Place page content blocks here.

' - id: box_page_footer type: Box layout: justify: center class: px-5 py-3 border-t border-gray-500/20 blocks: - id: box_page_footer_text type: Html layout: flex: 0 0 auto properties: html: '2026 My App. All rights reserved.' ``` ```yaml - id: box_inbox_wrapper type: Box class: rounded-lg border border-gray-500/20 overflow-hidden style: .element: display: flex flexDirection: column maxWidth: 480 blocks: - id: box_inbox_header type: Box layout: justify: space-between align: center class: px-4 py-3 border-b border-gray-500/20 blocks: - id: box_inbox_title type: Html layout: flex: 0 0 auto properties: html: 'Notifications' - id: box_inbox_mark_read type: Button layout: flex: 0 0 auto properties: title: Mark all read color: primary variant: link size: small events: onClick: - id: box_inbox_mark_read_action type: DisplayMessage params: content: All notifications marked as read. status: success - id: box_inbox_item_1 type: Box layout: gap: 12 align: flex-start class: px-4 py-3 border-b border-gray-500/10 bg-blue-500/10 cursor-pointer hover:bg-blue-500/15 events: onClick: - id: box_inbox_item_1_click type: DisplayMessage params: content: "Opened: Deployment completed" status: info blocks: - id: box_inbox_item_1_icon type: Icon layout: flex: 0 0 auto properties: name: check-circle size: 20 color: "#52c41a" - id: box_inbox_item_1_body type: Box layout: flex: 1 direction: column gap: 2 blocks: - id: box_inbox_item_1_text type: Html properties: html: 'Deployment completed' - id: box_inbox_item_1_time type: Html properties: html: 'Production v2.4.1 deployed successfully - 5 minutes ago' - id: box_inbox_item_2 type: Box layout: gap: 12 align: flex-start class: px-4 py-3 border-b border-gray-500/10 bg-blue-500/10 cursor-pointer hover:bg-blue-500/15 events: onClick: - id: box_inbox_item_2_click type: DisplayMessage params: content: "Opened: New team member" status: info blocks: - id: box_inbox_item_2_icon type: Icon layout: flex: 0 0 auto properties: name: UserPlus size: 20 color: "#1677ff" - id: box_inbox_item_2_body type: Box layout: flex: 1 direction: column gap: 2 blocks: - id: box_inbox_item_2_text type: Html properties: html: 'New team member added' - id: box_inbox_item_2_time type: Html properties: html: 'Sarah joined the Design team - 2 hours ago' - id: box_inbox_item_3 type: Box layout: gap: 12 align: flex-start class: px-4 py-3 cursor-pointer hover:bg-gray-500/10 events: onClick: - id: box_inbox_item_3_click type: DisplayMessage params: content: "Opened: Storage warning" status: info blocks: - id: box_inbox_item_3_icon type: Icon layout: flex: 0 0 auto properties: name: warning size: 20 color: "#faad14" - id: box_inbox_item_3_body type: Box layout: flex: 1 direction: column gap: 2 blocks: - id: box_inbox_item_3_text type: Html properties: html: 'Storage limit approaching' - id: box_inbox_item_3_time type: Html properties: html: 'You have used 85% of your storage quota - 1 day ago' ``` ```yaml - id: box_sidebar_layout type: Box layout: gap: 0 class: rounded-lg border border-gray-500/20 overflow-hidden style: .element: minHeight: 320 blocks: - id: box_sidebar_nav type: Box layout: flex: 0 0 200px class: border-r border-gray-500/20 style: .element: display: flex flexDirection: column blocks: - id: box_sidebar_brand type: Html properties: html: '
Workspace
' - id: box_sidebar_item_dashboard type: Box layout: gap: 8 align: center class: px-4 py-2 bg-blue-500/10 cursor-pointer events: onClick: - id: box_sidebar_dashboard_action type: SetState params: sidebar_active: dashboard blocks: - id: box_sidebar_icon_dashboard type: Icon layout: flex: 0 0 auto properties: name: Gauge size: 16 color: "#1677ff" - id: box_sidebar_text_dashboard type: Html layout: flex: 0 0 auto properties: html: 'Dashboard' - id: box_sidebar_item_projects type: Box layout: gap: 8 align: center class: px-4 py-2 cursor-pointer hover:bg-gray-500/10 events: onClick: - id: box_sidebar_projects_action type: SetState params: sidebar_active: projects blocks: - id: box_sidebar_icon_projects type: Icon layout: flex: 0 0 auto properties: name: SquareKanban size: 16 color: rgba(128, 128, 128, 0.7) - id: box_sidebar_text_projects type: Html layout: flex: 0 0 auto properties: html: 'Projects' - id: box_sidebar_item_settings type: Box layout: gap: 8 align: center class: px-4 py-2 cursor-pointer hover:bg-gray-500/10 events: onClick: - id: box_sidebar_settings_action type: SetState params: sidebar_active: settings blocks: - id: box_sidebar_icon_settings type: Icon layout: flex: 0 0 auto properties: name: settings size: 16 color: rgba(128, 128, 128, 0.7) - id: box_sidebar_text_settings type: Html layout: flex: 0 0 auto properties: html: 'Settings' - id: box_sidebar_content type: Box layout: flex: 1 class: p-6 blocks: - id: box_sidebar_content_title type: Html properties: html: '

Dashboard

' - id: box_sidebar_stats_row type: Box layout: gap: 12 blocks: - id: box_sidebar_stat_users type: Box layout: flex: 1 class: p-4 rounded-lg border border-blue-500/20 bg-blue-500/5 blocks: - id: box_sidebar_stat_users_label type: Html properties: html: 'Active Users' - id: box_sidebar_stat_users_value type: Html properties: html: '1,248' - id: box_sidebar_stat_revenue type: Box layout: flex: 1 class: p-4 rounded-lg border border-green-500/20 bg-green-500/5 blocks: - id: box_sidebar_stat_revenue_label type: Html properties: html: 'Revenue' - id: box_sidebar_stat_revenue_value type: Html properties: html: '$48,650' - id: box_sidebar_stat_tasks type: Box layout: flex: 1 class: p-4 rounded-lg border border-orange-500/20 bg-orange-500/5 blocks: - id: box_sidebar_stat_tasks_label type: Html properties: html: 'Open Tasks' - id: box_sidebar_stat_tasks_value type: Html properties: html: '37' ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `content` | string | - | Box content string. Overrides the "content" content area. | | Event | Event Data | Description | | --- | --- | --- | | `onClick` | \- | Trigger actions when the Box is clicked. | | `onPaste` | \- | Trigger actions when the element is focused and a paste event is triggered. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Box element. | | Slot | Description | | --- | --- | | `content` | Child blocks in the Box. | --- url: https://docs.lowdefy.com/md/container-blocks/card.md --- # Card Card container with title, extra, cover, and actions. Cards provide a flexible and extensible content container with multiple variants. This is the default card with a simple title and body content. You can place any blocks inside the card body using the standard blocks key. A card without a title. The header area is completely hidden, leaving only the body content. Useful as a simple elevated content container. ```yaml - id: basic_card type: Card properties: title: Card Title blocks: - id: basic_card_p1 type: Paragraph properties: content: Cards provide a flexible and extensible content container with multiple variants. This is the default card with a simple title and body content. - id: basic_card_p2 type: Paragraph properties: content: You can place any blocks inside the card body using the standard blocks key. - id: basic_no_title type: Card blocks: - id: basic_no_title_content type: Paragraph properties: content: A card without a title. The header area is completely hidden, leaving only the body content. Useful as a simple elevated content container. ``` Default size card with standard padding (24px) and normal header height (56px). This is the most common card size for general content. Small size card with compact padding (12px) and shorter header (38px). Ideal for dense layouts, dashboards, or sidebar widgets. ```yaml - id: size_default_card type: Card layout: flex: 1 1 0 properties: title: Default Size size: default blocks: - id: size_default_p type: Paragraph properties: content: Default size card with standard padding (24px) and normal header height (56px). This is the most common card size for general content. - id: size_default_row type: Box layout: gap: 8 blocks: - id: size_default_tag1 type: Tag properties: title: Standard color: blue - id: size_default_tag2 type: Tag properties: title: 24px padding color: default - id: size_small_card type: Card layout: flex: 1 1 0 properties: title: Small Size size: small blocks: - id: size_small_p type: Paragraph properties: content: Small size card with compact padding (12px) and shorter header (38px). Ideal for dense layouts, dashboards, or sidebar widgets. - id: size_small_row type: Box layout: gap: 8 blocks: - id: size_small_tag1 type: Tag properties: title: Compact color: green - id: size_small_tag2 type: Tag properties: title: 12px padding color: default ``` padding: compact tightens the body to 12px and lines the header up with it, while keeping the default header height. Use it for dense dashboards and forms. ```yaml - id: padding_compact_card type: Card layout: flex: 1 1 0 properties: title: Compact padding: compact blocks: - id: padding_compact_p type: Paragraph properties: content: "padding: compact tightens the body to 12px and lines the header up with it, while keeping the default header height. Use it for dense dashboards and forms." - id: padding_none_card type: Card layout: flex: 1 1 0 properties: title: Recent invoices padding: none blocks: - id: padding_none_card_rows type: Html properties: html: >
INV-1042$1,280.00
INV-1041$640.00
INV-1040$2,115.50
INV-1039$96.00
``` The default card has a visible border. This clearly separates the card from its surroundings. A borderless card blends into its container. Best used on colored backgrounds where the card's white fill provides enough contrast. ```yaml - id: border_wrapper type: Box class: bg-bg-layout p-6 rounded-lg layout: gap: 16 blocks: - id: bordered_card type: Card layout: flex: 1 1 0 properties: title: Outlined (default) variant: outlined blocks: - id: bordered_content type: Paragraph properties: content: The default card has a visible border. This clearly separates the card from its surroundings. - id: borderless_card type: Card layout: flex: 1 1 0 properties: title: Borderless variant: borderless blocks: - id: borderless_content type: Paragraph properties: content: A borderless card blends into its container. Best used on colored backgrounds where the card's white fill provides enough contrast. ``` User John updated the project settings 2 minutes ago. User Sarah added 3 new team members 15 minutes ago. Deployment completed successfully 1 hour ago. The extra slot renders content in the top-right corner of the card header, next to the title. It accepts any blocks, commonly used for action buttons or links. ```yaml - id: extra_text_card type: Card properties: title: Recent Activity slots: extra: blocks: - id: extra_text_link type: Button layout: flex: 0 0 auto properties: title: View All color: primary variant: link size: small blocks: - id: extra_text_p1 type: Paragraph properties: content: User John updated the project settings 2 minutes ago. - id: extra_text_p2 type: Paragraph properties: content: User Sarah added 3 new team members 15 minutes ago. - id: extra_text_p3 type: Paragraph properties: content: Deployment completed successfully 1 hour ago. - id: extra_buttons_card type: Card properties: title: Project Settings slots: extra: blocks: - id: extra_btn_row type: Box layout: gap: 8 blocks: - id: extra_btn_edit type: Button layout: flex: 0 0 auto properties: title: Edit icon: edit color: primary variant: outlined size: small - id: extra_btn_more type: Button layout: flex: 0 0 auto properties: icon: more color: default variant: text size: small hideTitle: true blocks: - id: extra_buttons_content type: Paragraph properties: content: The extra slot renders content in the top-right corner of the card header, next to the title. It accepts any blocks, commonly used for action buttons or links. ``` The title slot lets you replace the default text title with any combination of blocks. Here we use an Avatar and Title block side by side. This overrides the properties.title string. Custom Title Layout Combining the title slot with the extra slot gives you full control over the entire header area. Notifications ```yaml - id: title_slot_card type: Card slots: title: blocks: - id: title_slot_row type: Box layout: gap: 12 align: center blocks: - id: title_slot_avatar type: Avatar layout: flex: 0 0 auto properties: icon: user color: "#1677ff" - id: title_slot_text type: Title layout: flex: 0 0 auto properties: content: Custom Title Layout level: 5 blocks: - id: title_slot_content type: Paragraph properties: content: The title slot lets you replace the default text title with any combination of blocks. Here we use an Avatar and Title block side by side. This overrides the properties.title string. - id: title_slot_badge_card type: Card slots: title: blocks: - id: title_badge_row type: Box layout: gap: 8 align: center blocks: - id: title_badge_text type: Title layout: flex: 0 0 auto properties: content: Notifications level: 5 - id: title_badge_tag type: Tag layout: flex: 0 0 auto properties: title: 12 new color: red extra: blocks: - id: title_badge_extra_btn type: Button layout: flex: 0 0 auto properties: title: Mark all read color: primary variant: link size: small blocks: - id: title_badge_content type: Paragraph properties: content: Combining the title slot with the extra slot gives you full control over the entire header area. ``` The cover slot renders content above the card body, typically used for images. It sits outside the body padding for edge-to-edge display. Architecture Photography A cover card without a header title. The title is placed inside the body instead, giving a magazine-style layout. ```yaml - id: cover_card_1 type: Card layout: flex: 1 1 0 properties: title: Mountain Landscape hoverable: true slots: cover: blocks: - id: cover_img_1 type: Html properties: html: landscape blocks: - id: cover_card_1_desc type: Paragraph properties: content: The cover slot renders content above the card body, typically used for images. It sits outside the body padding for edge-to-edge display. - id: cover_card_2 type: Card layout: flex: 1 1 0 properties: hoverable: true slots: cover: blocks: - id: cover_img_2 type: Html properties: html: architecture blocks: - id: cover_card_2_title type: Title properties: content: Architecture Photography level: 5 - id: cover_card_2_desc type: Paragraph properties: content: A cover card without a header title. The title is placed inside the body instead, giving a magazine-style layout. - id: cover_card_2_tags type: Box layout: gap: 8 blocks: - id: cover_card_2_tag1 type: Tag properties: title: Photography color: blue - id: cover_card_2_tag2 type: Tag properties: title: Featured color: gold ``` Revenue grew 12% over the previous quarter. ```yaml - id: actions_card type: Card properties: title: Quarterly report slots: content: blocks: - id: actions_card_text type: Paragraph properties: content: Revenue grew 12% over the previous quarter. actions: blocks: - id: actions_card_buttons type: Flex properties: justify: space-around blocks: - id: actions_card_view type: Button properties: title: View type: link icon: view - id: actions_card_share type: Button properties: title: Share type: link icon: share ``` Shown once loading is false. ```yaml - id: loading_card type: Card properties: title: Customer details loading: true blocks: - id: loading_card_text type: Paragraph properties: content: Shown once loading is false. ``` Inner cards have a gray background and are designed for nesting inside a parent card. They provide visual grouping without extra border weight. Multiple inner cards can stack within a parent to organize related sections. Each has its own title and content area. Inner cards also support the small size variant for more compact layouts. ```yaml - id: inner_outer type: Card properties: title: Group Title slots: extra: blocks: - id: inner_outer_extra type: Button layout: flex: 0 0 auto properties: title: More color: primary variant: link size: small blocks: - id: inner_first type: Card properties: title: Inner Card One inner: true blocks: - id: inner_first_content type: Paragraph properties: content: Inner cards have a gray background and are designed for nesting inside a parent card. They provide visual grouping without extra border weight. - id: inner_second type: Card properties: title: Inner Card Two inner: true blocks: - id: inner_second_content type: Paragraph properties: content: Multiple inner cards can stack within a parent to organize related sections. Each has its own title and content area. - id: inner_third type: Card properties: title: Inner Card Three inner: true size: small blocks: - id: inner_third_content type: Paragraph properties: content: Inner cards also support the small size variant for more compact layouts. ``` Hover over any card in this section to see the elevation shadow effect. Commonly used for clickable card grids. ```yaml - id: hover_card_1 type: Card layout: flex: 1 1 0 properties: title: Dashboard hoverable: true slots: extra: blocks: - id: hover_card_1_icon type: Icon layout: flex: 0 0 auto properties: name: Gauge color: "#1677ff" size: 20 blocks: - id: hover_card_1_stat type: Statistic properties: title: Active Users value: 1248 - id: hover_card_1_p type: Paragraph properties: content: Hover over any card in this section to see the elevation shadow effect. Commonly used for clickable card grids. - id: hover_card_2 type: Card layout: flex: 1 1 0 properties: title: Revenue hoverable: true slots: extra: blocks: - id: hover_card_2_icon type: Icon layout: flex: 0 0 auto properties: name: CircleDollarSign color: "#52c41a" size: 20 blocks: - id: hover_card_2_stat type: Statistic properties: title: Monthly Revenue value: 48650 prefix: $ - id: hover_card_2_tag type: Tag properties: title: +12.5% color: success - id: hover_card_3 type: Card layout: flex: 1 1 0 properties: title: Tasks hoverable: true slots: extra: blocks: - id: hover_card_3_icon type: Icon layout: flex: 0 0 auto properties: name: check-circle color: "#faad14" size: 20 blocks: - id: hover_card_3_stat type: Statistic properties: title: Completed Today value: 37 - id: hover_card_3_progress type: Progress properties: percent: 74 ``` Jane Cooper Senior Product Designer A web application for managing team workflows, project timelines, and resource allocation across departments. Pro Plan $29 / month Unlimited projects 50 GB storage Priority support Advanced analytics ```yaml - id: rich_profile_card type: Card layout: flex: 1 1 0 blocks: - id: rich_profile_row type: Box layout: gap: 16 align: center blocks: - id: rich_profile_avatar type: Avatar layout: flex: 0 0 auto properties: icon: user size: 64 color: "#1677ff" - id: rich_profile_info type: Box layout: flex: 1 1 0 direction: column gap: 4 blocks: - id: rich_profile_name type: Title properties: content: Jane Cooper level: 4 - id: rich_profile_role type: Paragraph properties: content: Senior Product Designer - id: rich_profile_divider type: Divider - id: rich_profile_details type: Descriptions properties: size: small column: 1 items: - label: Email value: jane.cooper@example.com - label: Location value: San Francisco, CA - label: Team value: Design Systems - id: rich_project_card type: Card layout: flex: 1 1 0 properties: title: Project Alpha slots: extra: blocks: - id: rich_project_tag type: Tag layout: flex: 0 0 auto properties: title: Active color: success blocks: - id: rich_project_desc type: Paragraph properties: content: A web application for managing team workflows, project timelines, and resource allocation across departments. - id: rich_project_divider type: Divider - id: rich_project_meta_row type: Box layout: gap: 16 blocks: - id: rich_project_stat1 type: Statistic layout: flex: 1 1 0 properties: title: Tasks value: 42 - id: rich_project_stat2 type: Statistic layout: flex: 1 1 0 properties: title: Members value: 8 - id: rich_project_stat3 type: Statistic layout: flex: 1 1 0 properties: title: Days Left value: 14 - id: rich_project_progress type: Progress properties: percent: 68 - id: rich_pricing_card type: Card layout: flex: 1 1 0 properties: hoverable: true blocks: - id: rich_pricing_title type: Title properties: content: Pro Plan level: 4 - id: rich_pricing_price_row type: Box layout: align: baseline gap: 4 blocks: - id: rich_pricing_amount type: Title layout: flex: 0 0 auto properties: content: $29 level: 2 - id: rich_pricing_period type: Paragraph layout: flex: 0 0 auto properties: content: / month - id: rich_pricing_divider type: Divider - id: rich_pricing_f1 type: Paragraph properties: content: Unlimited projects - id: rich_pricing_f2 type: Paragraph properties: content: 50 GB storage - id: rich_pricing_f3 type: Paragraph properties: content: Priority support - id: rich_pricing_f4 type: Paragraph properties: content: Advanced analytics - id: rich_pricing_btn type: Button properties: title: Get Started color: primary variant: solid block: true ``` Track key metrics and performance indicators across your application in real time. Role-based access control, audit logging, and two-factor authentication for your team. Connect with over 50 third-party services including Slack, GitHub, Jira, and more. Shared workspaces, comments, mentions, and real-time co-editing for seamless teamwork. ```yaml - id: grid_card_1 type: Card layout: flex: 1 1 280px properties: title: Analytics size: small hoverable: true slots: extra: blocks: - id: grid_card_1_icon type: Icon layout: flex: 0 0 auto properties: name: chart color: "#1677ff" blocks: - id: grid_card_1_p type: Paragraph properties: content: Track key metrics and performance indicators across your application in real time. - id: grid_card_1_tags type: Box layout: gap: 8 blocks: - id: grid_card_1_t1 type: Tag properties: title: Metrics color: blue - id: grid_card_1_t2 type: Tag properties: title: Real-time color: cyan - id: grid_card_2 type: Card layout: flex: 1 1 280px properties: title: Security size: small hoverable: true slots: extra: blocks: - id: grid_card_2_icon type: Icon layout: flex: 0 0 auto properties: name: lock color: "#52c41a" blocks: - id: grid_card_2_p type: Paragraph properties: content: Role-based access control, audit logging, and two-factor authentication for your team. - id: grid_card_2_tags type: Box layout: gap: 8 blocks: - id: grid_card_2_t1 type: Tag properties: title: RBAC color: green - id: grid_card_2_t2 type: Tag properties: title: 2FA color: lime - id: grid_card_3 type: Card layout: flex: 1 1 280px properties: title: Integrations size: small hoverable: true slots: extra: blocks: - id: grid_card_3_icon type: Icon layout: flex: 0 0 auto properties: name: Plug color: "#722ed1" blocks: - id: grid_card_3_p type: Paragraph properties: content: Connect with over 50 third-party services including Slack, GitHub, Jira, and more. - id: grid_card_3_tags type: Box layout: gap: 8 blocks: - id: grid_card_3_t1 type: Tag properties: title: API color: purple - id: grid_card_3_t2 type: Tag properties: title: Webhooks color: geekblue - id: grid_card_4 type: Card layout: flex: 1 1 280px properties: title: Collaboration size: small hoverable: true slots: extra: blocks: - id: grid_card_4_icon type: Icon layout: flex: 0 0 auto properties: name: users color: "#fa8c16" blocks: - id: grid_card_4_p type: Paragraph properties: content: Shared workspaces, comments, mentions, and real-time co-editing for seamless teamwork. - id: grid_card_4_tags type: Box layout: gap: 8 blocks: - id: grid_card_4_t1 type: Tag properties: title: Teams color: orange - id: grid_card_4_t2 type: Tag properties: title: Comments color: gold ``` Gradient Card Using Tailwind CSS classes to create a gradient background card. The class property on the Card applies utility classes directly to the wrapper. A borderless card with extra-large shadow and rounded corners. The shadow-xl class creates a prominent floating effect. A left accent border highlights important cards. Combine Tailwind border utilities with the Card component for custom accents. ```yaml - id: styled_gradient type: Card layout: flex: 1 1 0 class: bg-gradient-to-br from-primary/100 to-purple-600 text-white border-0 rounded-xl blocks: - id: styled_gradient_title type: Title class: text-white properties: content: Gradient Card level: 4 - id: styled_gradient_p type: Paragraph class: text-blue-100 properties: content: Using Tailwind CSS classes to create a gradient background card. The class property on the Card applies utility classes directly to the wrapper. - id: styled_shadow type: Card layout: flex: 1 1 0 class: shadow-xl rounded-2xl border-0 properties: title: Elevated Card blocks: - id: styled_shadow_p type: Paragraph properties: content: A borderless card with extra-large shadow and rounded corners. The shadow-xl class creates a prominent floating effect. - id: styled_accent type: Card layout: flex: 1 1 0 class: border-l-4 border-l-green-500 rounded-lg properties: title: Accent Border blocks: - id: styled_accent_p type: Paragraph properties: content: A left accent border highlights important cards. Combine Tailwind border utilities with the Card component for custom accents. ``` Using style.header and style.body to apply custom CSS to the card header and body sections separately. The header has a blue tint with a matching border. A manually styled dark card using inline styles on the header and body. For app-wide dark themes, prefer antd theme tokens or ConfigProvider. Inline styles on the header and body can override the default padding for a more compact layout without using the small size variant. ```yaml - id: style_header_bg type: Card layout: flex: 1 1 0 properties: title: Colored Header style: .header: backgroundColor: var(--ant-color-primary-bg) borderBottom: 1px solid var(--ant-color-primary-border) .body: backgroundColor: var(--ant-color-fill-quaternary) blocks: - id: style_header_bg_p type: Paragraph properties: content: Using style.header and style.body to apply custom CSS to the card header and body sections separately. The header has a blue tint with a matching border. - id: style_dark_card type: Card layout: flex: 1 1 0 properties: title: Dark Theme Card style: .header: backgroundColor: "#1f1f1f" color: "#ffffff" borderBottom: "1px solid #333" .body: backgroundColor: "#2a2a2a" blocks: - id: style_dark_p type: Paragraph class: text-text-secondary properties: content: A manually styled dark card using inline styles on the header and body. For app-wide dark themes, prefer antd theme tokens or ConfigProvider. - id: style_compact_card type: Card layout: flex: 1 1 0 properties: title: Compact Custom Padding style: .header: padding: 8px 16px minHeight: 40px .body: padding: 12px 16px blocks: - id: style_compact_p type: Paragraph properties: content: Inline styles on the header and body can override the default padding for a more compact layout without using the small size variant. ``` Card supports these CSS keys: element, header, body, cover, actions, extra. Use class as an object to target each area with Tailwind classes. Combining the hoverable property with a Tailwind hover ring transition creates an interactive highlight on mouse over. The extra CSS key lets you style the container around the extra slot content. Here a green pill background wraps the status tag. ```yaml - id: css_element type: Card layout: flex: 1 1 0 class: .element: shadow-lg rounded-xl .header: bg-bg-layout .body: bg-bg-container properties: title: Element + Header + Body blocks: - id: css_element_p type: Paragraph properties: content: "Card supports these CSS keys: element, header, body, cover, actions, extra. Use class as an object to target each area with Tailwind classes." - id: css_hover_ring type: Card layout: flex: 1 1 0 class: .element: hover:ring-2 hover:ring-blue-400 transition-all duration-200 rounded-lg properties: title: Hover Ring Effect hoverable: true blocks: - id: css_hover_ring_p type: Paragraph properties: content: Combining the hoverable property with a Tailwind hover ring transition creates an interactive highlight on mouse over. - id: css_extra_styled type: Card layout: flex: 1 1 0 class: .extra: bg-bg-layout px-3 py-1 rounded-full properties: title: Styled Extra Area slots: extra: blocks: - id: css_extra_tag type: Tag layout: flex: 0 0 auto properties: title: Live color: success blocks: - id: css_extra_styled_p type: Paragraph properties: content: The extra CSS key lets you style the container around the extra slot content. Here a green pill background wraps the status tag. ``` Override bodyPadding and headerPadding tokens for extra spacious cards. These tokens control the internal spacing of the card. The headerBg token changes the card header background color, and colorTextHeading changes the title text color. These apply through the antd theme system. The borderRadiusLG token controls the card corner radius. Set a large value for a pill-like card appearance. Combine the small size variant with custom SM tokens for an ultra-compact card. Useful for data-dense dashboards. A warm-toned card using theme tokens to customize the background, header, text, and border colors. Theme tokens cascade correctly through the antd design system. ```yaml - id: theme_large_padding type: Card layout: flex: 1 1 0 properties: title: Large Padding theme: bodyPadding: 40 headerPadding: 40 blocks: - id: theme_large_padding_p type: Paragraph properties: content: Override bodyPadding and headerPadding tokens for extra spacious cards. These tokens control the internal spacing of the card. - id: theme_colored_header type: Card layout: flex: 1 1 0 properties: title: Custom Header Background theme: colorTextHeading: "#389e0d" blocks: - id: theme_colored_header_p type: Paragraph properties: content: The headerBg token changes the card header background color, and colorTextHeading changes the title text color. These apply through the antd theme system. - id: theme_rounded type: Card layout: flex: 1 1 0 properties: title: Extra Rounded theme: borderRadiusLG: 20 blocks: - id: theme_rounded_p type: Paragraph properties: content: The borderRadiusLG token controls the card corner radius. Set a large value for a pill-like card appearance. - id: theme_dense type: Card layout: flex: 1 1 0 properties: title: Dense Card size: small theme: bodyPaddingSM: 8 headerPaddingSM: 8 headerHeightSM: 32 headerFontSizeSM: 12 blocks: - id: theme_dense_p type: Paragraph properties: content: Combine the small size variant with custom SM tokens for an ultra-compact card. Useful for data-dense dashboards. - id: theme_custom_colors type: Card properties: title: Custom Color Scheme theme: colorTextHeading: "#d46b08" colorText: "#874d00" colorBorderSecondary: "#ffd591" blocks: - id: theme_custom_colors_p type: Paragraph properties: content: A warm-toned card using theme tokens to customize the background, header, text, and border colors. Theme tokens cascade correctly through the antd design system. ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `bordered` | boolean | `true` | Toggles rendering of the border around the card. `bordered: false` is the same as `variant: borderless`. | | `hoverable` | boolean | `false` | Lift up when hovering card. | | `loading` | boolean | `false` | Show a loading skeleton in place of the card content. | | `inner` | boolean | `false` | Change the card style to inner. | | `padding` | string | `"default"` | Space inside the card body. `compact` tightens the body to 12px and aligns the header with it. `none` removes the body padding so content such as a table, list or image runs edge to edge; the header keeps its padding. Enum: `default`, `compact`, `none`. | | `size` | string | `"default"` | Size of the card. Enum: `default`, `small`. | | `title` | string | - | Title to show in the title area - supports html. Overwritten by blocks in the title content area. | | `variant` | string | `"outlined"` | Card style variant. Enum: `outlined`, `borderless`. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design card tokens](https://ant.design/components/card#design-token). | | `theme.headerBg` | string | `"transparent"` | Background color of the card header. | | `theme.headerFontSize` | number | `16` | Font size of the card header title. | | `theme.headerFontSizeSM` | number | `14` | Font size of the card header title for small cards. | | `theme.headerHeight` | number | `56` | Height of the card header. | | `theme.headerHeightSM` | number | `38` | Height of the card header for small cards. | | `theme.actionsBg` | string | `"#ffffff"` | Background color of the card actions area. | | `theme.actionsLiMargin` | string | `"12px 0"` | Margin of each action item in the card actions area. | | `theme.tabsMarginBottom` | number | `-17` | Bottom margin adjustment for tabs in the card. | | `theme.extraColor` | string | `"rgba(0,0,0,0.88)"` | Text color for the extra content area in the header. | | `theme.bodyPadding` | number \| string | `24` | Padding of the card body: a number of pixels, or a CSS padding string such as `28px 32px`. | | `theme.bodyPaddingSM` | number \| string | `12` | Padding of the card body for small cards. | | `theme.headerPadding` | number | `24` | Horizontal padding of the card header. | | `theme.headerPaddingSM` | number | `12` | Horizontal padding of the card header for small cards. | | `theme.borderRadiusLG` | number | `8` | Border radius of the card. | | `theme.colorBorderSecondary` | string | - | Border color of the card. | | `theme.colorBgContainer` | string | - | Background color of the card body. | | `theme.colorText` | string | - | Text color inside the card. | | `theme.colorTextHeading` | string | - | Color of the card header title text. | | `theme.boxShadowTertiary` | string | - | Shadow applied to the card on hover when hoverable is true. | | Event | Event Data | Description | | --- | --- | --- | | `onClick` | \- | Trigger actions when the Card is clicked. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Card element. | | `/header` | The Card header. | | `/title` | The Card title. | | `/body` | The Card body. | | `/cover` | The Card cover. | | `/actions` | The Card actions. | | `/extra` | The Card extra content. | | Slot | Description | | --- | --- | | `actions` | Action bar at the bottom of the card. | | `content` | Main Card body. | | `cover` | Image or content above the body. | | `extra` | Extra content in the top-right header. | | `title` | Title area. Overrides the title property. | --- url: https://docs.lowdefy.com/md/container-blocks/carousel.md --- # Carousel Carousel slider with autoplay, dots, and transition effects. Slide 1 Slide 2 Slide 3 Slide 4 ```yaml - id: basic_carousel type: Carousel slots: basic_s1: blocks: - id: basic_s1 type: Box style: height: 160 background: "#364d79" display: flex alignItems: center justifyContent: center blocks: - id: basic_s1_text type: Title properties: content: Slide 1 level: 3 style: color: white basic_s2: blocks: - id: basic_s2 type: Box style: height: 160 background: "#263c5a" display: flex alignItems: center justifyContent: center blocks: - id: basic_s2_text type: Title properties: content: Slide 2 level: 3 style: color: white basic_s3: blocks: - id: basic_s3 type: Box style: height: 160 background: "#1a2d45" display: flex alignItems: center justifyContent: center blocks: - id: basic_s3_text type: Title properties: content: Slide 3 level: 3 style: color: white basic_s4: blocks: - id: basic_s4 type: Box style: height: 160 background: "#0d1e30" display: flex alignItems: center justifyContent: center blocks: - id: basic_s4_text type: Title properties: content: Slide 4 level: 3 style: color: white ``` Blue Green Purple Volcano Magenta ```yaml - id: color_carousel type: Carousel slots: color_s1: blocks: - id: color_s1 type: Box style: height: 180 background: "#1677ff" display: flex alignItems: center justifyContent: center blocks: - id: color_s1_text type: Title properties: content: Blue level: 3 style: color: white color_s2: blocks: - id: color_s2 type: Box style: height: 180 background: "#52c41a" display: flex alignItems: center justifyContent: center blocks: - id: color_s2_text type: Title properties: content: Green level: 3 style: color: white color_s3: blocks: - id: color_s3 type: Box style: height: 180 background: "#722ed1" display: flex alignItems: center justifyContent: center blocks: - id: color_s3_text type: Title properties: content: Purple level: 3 style: color: white color_s4: blocks: - id: color_s4 type: Box style: height: 180 background: "#fa541c" display: flex alignItems: center justifyContent: center blocks: - id: color_s4_text type: Title properties: content: Volcano level: 3 style: color: white color_s5: blocks: - id: color_s5 type: Box style: height: 180 background: "#eb2f96" display: flex alignItems: center justifyContent: center blocks: - id: color_s5_text type: Title properties: content: Magenta level: 3 style: color: white ``` Auto Slide 1 Auto Slide 2 Auto Slide 3 Auto Slide 4 ```yaml - id: auto_carousel type: Carousel properties: autoplay: true autoplaySpeed: 2000 slots: auto_s1: blocks: - id: auto_s1 type: Box style: height: 160 background: "#13c2c2" display: flex alignItems: center justifyContent: center blocks: - id: auto_s1_text type: Title properties: content: Auto Slide 1 level: 3 style: color: white auto_s2: blocks: - id: auto_s2 type: Box style: height: 160 background: "#1677ff" display: flex alignItems: center justifyContent: center blocks: - id: auto_s2_text type: Title properties: content: Auto Slide 2 level: 3 style: color: white auto_s3: blocks: - id: auto_s3 type: Box style: height: 160 background: "#722ed1" display: flex alignItems: center justifyContent: center blocks: - id: auto_s3_text type: Title properties: content: Auto Slide 3 level: 3 style: color: white auto_s4: blocks: - id: auto_s4 type: Box style: height: 160 background: "#eb2f96" display: flex alignItems: center justifyContent: center blocks: - id: auto_s4_text type: Title properties: content: Auto Slide 4 level: 3 style: color: white ``` The active dot fills until the next slide Second slide ```yaml - id: dot_duration_carousel type: Carousel properties: autoplay: dotDuration: true autoplaySpeed: 4000 slots: progress_s1: blocks: - id: progress_s1 type: Box style: height: 160 background: "#1677ff" display: flex alignItems: center justifyContent: center blocks: - id: progress_s1_text type: Title properties: content: The active dot fills until the next slide level: 4 style: color: white progress_s2: blocks: - id: progress_s2 type: Box style: height: 160 background: "#722ed1" display: flex alignItems: center justifyContent: center blocks: - id: progress_s2_text type: Title properties: content: Second slide level: 4 style: color: white ``` Hover to Pause Autoplay Resumes On Mouse Leave ```yaml - id: pause_carousel type: Carousel properties: autoplay: true autoplaySpeed: 1500 pauseOnHover: true pauseOnFocus: true slots: pause_s1: blocks: - id: pause_s1 type: Box style: height: 160 background: "#389e0d" display: flex alignItems: center justifyContent: center blocks: - id: pause_s1_text type: Title properties: content: Hover to Pause level: 3 style: color: white pause_s2: blocks: - id: pause_s2 type: Box style: height: 160 background: "#08979c" display: flex alignItems: center justifyContent: center blocks: - id: pause_s2_text type: Title properties: content: Autoplay Resumes level: 3 style: color: white pause_s3: blocks: - id: pause_s3 type: Box style: height: 160 background: "#531dab" display: flex alignItems: center justifyContent: center blocks: - id: pause_s3_text type: Title properties: content: On Mouse Leave level: 3 style: color: white ``` Dots on Top Navigation Above Slide Content ```yaml - id: dot_top_carousel type: Carousel properties: dotPosition: top slots: dot_top_s1: blocks: - id: dot_top_s1 type: Box style: height: 160 background: "#fa8c16" display: flex alignItems: center justifyContent: center blocks: - id: dot_top_s1_text type: Title properties: content: Dots on Top level: 3 style: color: white dot_top_s2: blocks: - id: dot_top_s2 type: Box style: height: 160 background: "#d46b08" display: flex alignItems: center justifyContent: center blocks: - id: dot_top_s2_text type: Title properties: content: Navigation Above level: 3 style: color: white dot_top_s3: blocks: - id: dot_top_s3 type: Box style: height: 160 background: "#ad4e00" display: flex alignItems: center justifyContent: center blocks: - id: dot_top_s3_text type: Title properties: content: Slide Content level: 3 style: color: white ``` Dots on Left Vertical Dots Side Navigation ```yaml - id: dot_left_carousel type: Carousel properties: dotPosition: left slots: dot_left_s1: blocks: - id: dot_left_s1 type: Box style: height: 200 background: "#13c2c2" display: flex alignItems: center justifyContent: center blocks: - id: dot_left_s1_text type: Title properties: content: Dots on Left level: 3 style: color: white dot_left_s2: blocks: - id: dot_left_s2 type: Box style: height: 200 background: "#006d75" display: flex alignItems: center justifyContent: center blocks: - id: dot_left_s2_text type: Title properties: content: Vertical Dots level: 3 style: color: white dot_left_s3: blocks: - id: dot_left_s3 type: Box style: height: 200 background: "#00474f" display: flex alignItems: center justifyContent: center blocks: - id: dot_left_s3_text type: Title properties: content: Side Navigation level: 3 style: color: white ``` Dots on Right Right Aligned Navigation ```yaml - id: dot_right_carousel type: Carousel properties: dotPosition: right slots: dot_right_s1: blocks: - id: dot_right_s1 type: Box style: height: 200 background: "#eb2f96" display: flex alignItems: center justifyContent: center blocks: - id: dot_right_s1_text type: Title properties: content: Dots on Right level: 3 style: color: white dot_right_s2: blocks: - id: dot_right_s2 type: Box style: height: 200 background: "#c41d7f" display: flex alignItems: center justifyContent: center blocks: - id: dot_right_s2_text type: Title properties: content: Right Aligned level: 3 style: color: white dot_right_s3: blocks: - id: dot_right_s3 type: Box style: height: 200 background: "#9e1068" display: flex alignItems: center justifyContent: center blocks: - id: dot_right_s3_text type: Title properties: content: Navigation level: 3 style: color: white ``` Fade Transition Smooth Crossfade Between Slides ```yaml - id: fade_carousel type: Carousel properties: effect: fade slots: fade_s1: blocks: - id: fade_s1 type: Box style: height: 180 background: "#531dab" display: flex alignItems: center justifyContent: center blocks: - id: fade_s1_text type: Title properties: content: Fade Transition level: 3 style: color: white fade_s2: blocks: - id: fade_s2 type: Box style: height: 180 background: "#08979c" display: flex alignItems: center justifyContent: center blocks: - id: fade_s2_text type: Title properties: content: Smooth Crossfade level: 3 style: color: white fade_s3: blocks: - id: fade_s3 type: Box style: height: 180 background: "#d4380d" display: flex alignItems: center justifyContent: center blocks: - id: fade_s3_text type: Title properties: content: Between Slides level: 3 style: color: white ``` Gradient Violet Gradient Pink Gradient Cyan Gradient Mint ```yaml - id: fade_auto_carousel type: Carousel properties: effect: fade autoplay: true autoplaySpeed: 2500 slots: fade_auto_s1: blocks: - id: fade_auto_s1 type: Box style: height: 180 background: linear-gradient(135deg, display: flex alignItems: center justifyContent: center blocks: - id: fade_auto_s1_text type: Title properties: content: Gradient Violet level: 3 style: color: white fade_auto_s2: blocks: - id: fade_auto_s2 type: Box style: height: 180 background: linear-gradient(135deg, display: flex alignItems: center justifyContent: center blocks: - id: fade_auto_s2_text type: Title properties: content: Gradient Pink level: 3 style: color: white fade_auto_s3: blocks: - id: fade_auto_s3 type: Box style: height: 180 background: linear-gradient(135deg, display: flex alignItems: center justifyContent: center blocks: - id: fade_auto_s3_text type: Title properties: content: Gradient Cyan level: 3 style: color: white fade_auto_s4: blocks: - id: fade_auto_s4 type: Box style: height: 180 background: linear-gradient(135deg, display: flex alignItems: center justifyContent: center blocks: - id: fade_auto_s4_text type: Title properties: content: Gradient Mint level: 3 style: color: white ``` Navigate with Arrows Click Left or Right To Change Slides Arrow Navigation ```yaml - id: arrows_carousel type: Carousel properties: arrows: true slots: arrows_s1: blocks: - id: arrows_s1 type: Box style: height: 180 background: "#389e0d" display: flex alignItems: center justifyContent: center blocks: - id: arrows_s1_text type: Title properties: content: Navigate with Arrows level: 3 style: color: white arrows_s2: blocks: - id: arrows_s2 type: Box style: height: 180 background: "#1d39c4" display: flex alignItems: center justifyContent: center blocks: - id: arrows_s2_text type: Title properties: content: Click Left or Right level: 3 style: color: white arrows_s3: blocks: - id: arrows_s3 type: Box style: height: 180 background: "#c41d7f" display: flex alignItems: center justifyContent: center blocks: - id: arrows_s3_text type: Title properties: content: To Change Slides level: 3 style: color: white arrows_s4: blocks: - id: arrows_s4 type: Box style: height: 180 background: "#d4380d" display: flex alignItems: center justifyContent: center blocks: - id: arrows_s4_text type: Title properties: content: Arrow Navigation level: 3 style: color: white ``` Arrows Only No Dot Indicators Clean Navigation ```yaml - id: arrows_nodots_carousel type: Carousel properties: arrows: true dots: false slots: arrows_nodots_s1: blocks: - id: arrows_nodots_s1 type: Box style: height: 160 background: "#faad14" display: flex alignItems: center justifyContent: center blocks: - id: arrows_nodots_s1_text type: Title properties: content: Arrows Only level: 3 style: color: white arrows_nodots_s2: blocks: - id: arrows_nodots_s2 type: Box style: height: 160 background: "#d48806" display: flex alignItems: center justifyContent: center blocks: - id: arrows_nodots_s2_text type: Title properties: content: No Dot Indicators level: 3 style: color: white arrows_nodots_s3: blocks: - id: arrows_nodots_s3 type: Box style: height: 160 background: "#ad6800" display: flex alignItems: center justifyContent: center blocks: - id: arrows_nodots_s3_text type: Title properties: content: Clean Navigation level: 3 style: color: white ``` 1 2 3 4 5 6 ```yaml - id: multi_carousel type: Carousel properties: slidesToShow: 3 slidesToScroll: 1 slots: multi_s1: blocks: - id: multi_s1 type: Box style: height: 140 background: "#1677ff" display: flex alignItems: center justifyContent: center margin: 0 8px blocks: - id: multi_s1_text type: Title properties: content: "1" level: 3 style: color: white multi_s2: blocks: - id: multi_s2 type: Box style: height: 140 background: "#4096ff" display: flex alignItems: center justifyContent: center margin: 0 8px blocks: - id: multi_s2_text type: Title properties: content: "2" level: 3 style: color: white multi_s3: blocks: - id: multi_s3 type: Box style: height: 140 background: "#69b1ff" display: flex alignItems: center justifyContent: center margin: 0 8px blocks: - id: multi_s3_text type: Title properties: content: "3" level: 3 style: color: white multi_s4: blocks: - id: multi_s4 type: Box style: height: 140 background: "#91caff" display: flex alignItems: center justifyContent: center margin: 0 8px blocks: - id: multi_s4_text type: Title properties: content: "4" level: 3 style: color: white multi_s5: blocks: - id: multi_s5 type: Box style: height: 140 display: flex alignItems: center justifyContent: center margin: 0 8px blocks: - id: multi_s5_text type: Title properties: content: "5" level: 3 style: color: "#1677ff" multi_s6: blocks: - id: multi_s6 type: Box style: height: 140 display: flex alignItems: center justifyContent: center margin: 0 8px blocks: - id: multi_s6_text type: Title properties: content: "6" level: 3 style: color: "#1677ff" ``` First Slide Middle Slide Last Slide (Stops Here) ```yaml - id: finite_carousel type: Carousel properties: infinite: false arrows: true slots: finite_s1: blocks: - id: finite_s1 type: Box style: height: 160 background: "#cf1322" display: flex alignItems: center justifyContent: center blocks: - id: finite_s1_text type: Title properties: content: First Slide level: 3 style: color: white finite_s2: blocks: - id: finite_s2 type: Box style: height: 160 background: "#a8071a" display: flex alignItems: center justifyContent: center blocks: - id: finite_s2_text type: Title properties: content: Middle Slide level: 3 style: color: white finite_s3: blocks: - id: finite_s3 type: Box style: height: 160 background: "#820014" display: flex alignItems: center justifyContent: center blocks: - id: finite_s3_text type: Title properties: content: Last Slide (Stops Here) level: 3 style: color: white ``` Launch Fast Build and deploy web apps in record time with config-driven development. Production Ready Enterprise-grade applications with built-in auth, roles, and security. Connect Anything Integrate with databases, REST APIs, and GraphQL endpoints effortlessly. ```yaml - id: rich_carousel type: Carousel properties: autoplay: true autoplaySpeed: 4000 slots: rich_s1: blocks: - id: rich_s1 type: Box style: height: 220 background: linear-gradient(135deg, display: flex flexDirection: column alignItems: center justifyContent: center padding: 24px blocks: - id: rich_s1_icon type: Icon properties: name: Rocket size: 40 color: white - id: rich_s1_title type: Title properties: content: Launch Fast level: 3 style: color: white marginTop: 12 marginBottom: 0 - id: rich_s1_desc type: Paragraph properties: content: Build and deploy web apps in record time with config-driven development. style: color: rgba(255, 255, 255, 0.85) textAlign: center maxWidth: 400 rich_s2: blocks: - id: rich_s2 type: Box style: height: 220 background: linear-gradient(135deg, display: flex flexDirection: column alignItems: center justifyContent: center padding: 24px blocks: - id: rich_s2_icon type: Icon properties: name: check-circle size: 40 color: white - id: rich_s2_title type: Title properties: content: Production Ready level: 3 style: color: white marginTop: 12 marginBottom: 0 - id: rich_s2_desc type: Paragraph properties: content: Enterprise-grade applications with built-in auth, roles, and security. style: color: rgba(255, 255, 255, 0.85) textAlign: center maxWidth: 400 rich_s3: blocks: - id: rich_s3 type: Box style: height: 220 background: linear-gradient(135deg, display: flex flexDirection: column alignItems: center justifyContent: center padding: 24px blocks: - id: rich_s3_icon type: Icon properties: name: Plug size: 40 color: white - id: rich_s3_title type: Title properties: content: Connect Anything level: 3 style: color: white marginTop: 12 marginBottom: 0 - id: rich_s3_desc type: Paragraph properties: content: Integrate with databases, REST APIs, and GraphQL endpoints effortlessly. style: color: rgba(255, 255, 255, 0.85) textAlign: center maxWidth: 400 ``` Monitor your app metrics and KPIs at a glance with customizable widgets and real-time data. Handle roles, permissions, and team invitations with a built-in authentication layer. Create complex multi-step forms with validation, conditional logic, and file uploads. Automate business processes with triggers, conditions, and multi-step action sequences. ```yaml - id: card_carousel type: Carousel properties: slidesToShow: 2 slidesToScroll: 1 arrows: true slots: card_s1: blocks: - id: card_s1 type: Box style: padding: 12px blocks: - id: card_s1_card type: Card properties: title: Dashboard bordered: true blocks: - id: card_s1_card_desc type: Paragraph properties: content: Monitor your app metrics and KPIs at a glance with customizable widgets and real-time data. - id: card_s1_card_tag type: Tag properties: title: Analytics color: blue card_s2: blocks: - id: card_s2 type: Box style: padding: 12px blocks: - id: card_s2_card type: Card properties: title: User Management bordered: true blocks: - id: card_s2_card_desc type: Paragraph properties: content: Handle roles, permissions, and team invitations with a built-in authentication layer. - id: card_s2_card_tag type: Tag properties: title: Security color: green card_s3: blocks: - id: card_s3 type: Box style: padding: 12px blocks: - id: card_s3_card type: Card properties: title: Form Builder bordered: true blocks: - id: card_s3_card_desc type: Paragraph properties: content: Create complex multi-step forms with validation, conditional logic, and file uploads. - id: card_s3_card_tag type: Tag properties: title: Input color: purple card_s4: blocks: - id: card_s4 type: Box style: padding: 12px blocks: - id: card_s4_card type: Card properties: title: Workflow Engine bordered: true blocks: - id: card_s4_card_desc type: Paragraph properties: content: Automate business processes with triggers, conditions, and multi-step action sequences. - id: card_s4_card_tag type: Tag properties: title: Automation color: orange ``` Soft Blue Fresh Lime Warm Sunset Lavender Mist Coral Sunrise ```yaml - id: gradient_carousel type: Carousel properties: effect: fade autoplay: true autoplaySpeed: 3000 slots: grad_s1: blocks: - id: grad_s1 type: Box style: height: 200 background: linear-gradient(120deg, display: flex alignItems: center justifyContent: center blocks: - id: grad_s1_text type: Title properties: content: Soft Blue level: 2 style: color: "#1d39c4" grad_s2: blocks: - id: grad_s2 type: Box style: height: 200 background: linear-gradient(120deg, display: flex alignItems: center justifyContent: center blocks: - id: grad_s2_text type: Title properties: content: Fresh Lime level: 2 style: color: "#135200" grad_s3: blocks: - id: grad_s3 type: Box style: height: 200 background: linear-gradient(120deg, display: flex alignItems: center justifyContent: center blocks: - id: grad_s3_text type: Title properties: content: Warm Sunset level: 2 style: color: "#610b00" grad_s4: blocks: - id: grad_s4 type: Box style: height: 200 background: linear-gradient(120deg, display: flex alignItems: center justifyContent: center blocks: - id: grad_s4_text type: Title properties: content: Lavender Mist level: 2 style: color: "#391085" grad_s5: blocks: - id: grad_s5 type: Box style: height: 200 background: linear-gradient(120deg, display: flex alignItems: center justifyContent: center blocks: - id: grad_s5_text type: Title properties: content: Coral Sunrise level: 2 style: color: white ``` Drag to Navigate Click and Drag On Desktop Swipe on Mobile ```yaml - id: drag_carousel type: Carousel properties: draggable: true swipeToSlide: true slots: drag_s1: blocks: - id: drag_s1 type: Box style: height: 160 background: "#2f54eb" display: flex alignItems: center justifyContent: center blocks: - id: drag_s1_text type: Title properties: content: Drag to Navigate level: 3 style: color: white drag_s2: blocks: - id: drag_s2 type: Box style: height: 160 background: "#597ef7" display: flex alignItems: center justifyContent: center blocks: - id: drag_s2_text type: Title properties: content: Click and Drag level: 3 style: color: white drag_s3: blocks: - id: drag_s3 type: Box style: height: 160 background: "#85a5ff" display: flex alignItems: center justifyContent: center blocks: - id: drag_s3_text type: Title properties: content: On Desktop level: 3 style: color: white drag_s4: blocks: - id: drag_s4 type: Box style: height: 160 display: flex alignItems: center justifyContent: center blocks: - id: drag_s4_text type: Title properties: content: Swipe on Mobile level: 3 style: color: "#2f54eb" ``` Center Mode Peek at Adjacent Slide Previews With Padding ```yaml - id: center_carousel type: Carousel properties: centerMode: true centerPadding: 60px slidesToShow: 1 slots: center_s1: blocks: - id: center_s1 type: Box style: height: 160 background: "#faad14" display: flex alignItems: center justifyContent: center margin: 0 8px borderRadius: 8 blocks: - id: center_s1_text type: Title properties: content: Center Mode level: 3 style: color: white center_s2: blocks: - id: center_s2 type: Box style: height: 160 background: "#d48806" display: flex alignItems: center justifyContent: center margin: 0 8px borderRadius: 8 blocks: - id: center_s2_text type: Title properties: content: Peek at Adjacent level: 3 style: color: white center_s3: blocks: - id: center_s3 type: Box style: height: 160 background: "#ad6800" display: flex alignItems: center justifyContent: center margin: 0 8px borderRadius: 8 blocks: - id: center_s3_text type: Title properties: content: Slide Previews level: 3 style: color: white center_s4: blocks: - id: center_s4 type: Box style: height: 160 background: "#874d00" display: flex alignItems: center justifyContent: center margin: 0 8px borderRadius: 8 blocks: - id: center_s4_text type: Title properties: content: With Padding level: 3 style: color: white ``` Tailwind Gradients Use Tailwind utility classes on slides Green to Cyan Combine class and style for full control Rose to Orange Beautiful color transitions Violet to Fuchsia Rich purple gradients ```yaml - id: css_carousel type: Carousel slots: css_s1: blocks: - id: css_s1 type: Box class: bg-gradient-to-br from-primary/100 to-indigo-600 style: height: 200 display: flex flexDirection: column alignItems: center justifyContent: center blocks: - id: css_s1_title type: Title properties: content: Tailwind Gradients level: 2 style: color: white marginBottom: 0 - id: css_s1_sub type: Paragraph properties: content: Use Tailwind utility classes on slides style: color: rgba(255, 255, 255, 0.8) css_s2: blocks: - id: css_s2 type: Box class: bg-gradient-to-br from-emerald-400 to-cyan-500 style: height: 200 display: flex flexDirection: column alignItems: center justifyContent: center blocks: - id: css_s2_title type: Title properties: content: Green to Cyan level: 2 style: color: white marginBottom: 0 - id: css_s2_sub type: Paragraph properties: content: Combine class and style for full control style: color: rgba(255, 255, 255, 0.8) css_s3: blocks: - id: css_s3 type: Box class: bg-gradient-to-br from-rose-400 to-orange-400 style: height: 200 display: flex flexDirection: column alignItems: center justifyContent: center blocks: - id: css_s3_title type: Title properties: content: Rose to Orange level: 2 style: color: white marginBottom: 0 - id: css_s3_sub type: Paragraph properties: content: Beautiful color transitions style: color: rgba(255, 255, 255, 0.8) css_s4: blocks: - id: css_s4 type: Box class: bg-gradient-to-br from-violet-500 to-fuchsia-500 style: height: 200 display: flex flexDirection: column alignItems: center justifyContent: center blocks: - id: css_s4_title type: Title properties: content: Violet to Fuchsia level: 2 style: color: white marginBottom: 0 - id: css_s4_sub type: Paragraph properties: content: Rich purple gradients style: color: rgba(255, 255, 255, 0.8) ``` **Slow transition (1500ms):** Slow and Smooth Ease In Out 1.5 Second Slide **Fast transition (200ms):** Quick Snap 200ms Transition Instant Feel ```yaml - id: speed_label type: Markdown properties: content: "**Slow transition (1500ms):**" - id: slow_carousel type: Carousel properties: speed: 1500 easing: ease-in-out slots: slow_s1: blocks: - id: slow_s1 type: Box style: height: 140 background: "#595959" display: flex alignItems: center justifyContent: center blocks: - id: slow_s1_text type: Title properties: content: Slow and Smooth level: 3 style: color: white slow_s2: blocks: - id: slow_s2 type: Box style: height: 140 background: "#434343" display: flex alignItems: center justifyContent: center blocks: - id: slow_s2_text type: Title properties: content: Ease In Out level: 3 style: color: white slow_s3: blocks: - id: slow_s3 type: Box style: height: 140 background: "#262626" display: flex alignItems: center justifyContent: center blocks: - id: slow_s3_text type: Title properties: content: 1.5 Second Slide level: 3 style: color: white - id: fast_label type: Markdown properties: content: "**Fast transition (200ms):**" - id: fast_carousel type: Carousel properties: speed: 200 slots: fast_s1: blocks: - id: fast_s1 type: Box style: height: 140 background: "#ff4d4f" display: flex alignItems: center justifyContent: center blocks: - id: fast_s1_text type: Title properties: content: Quick Snap level: 3 style: color: white fast_s2: blocks: - id: fast_s2 type: Box style: height: 140 background: "#ff7875" display: flex alignItems: center justifyContent: center blocks: - id: fast_s2_text type: Title properties: content: 200ms Transition level: 3 style: color: white fast_s3: blocks: - id: fast_s3 type: Box style: height: 140 background: "#ffa39e" display: flex alignItems: center justifyContent: center blocks: - id: fast_s3_text type: Title properties: content: Instant Feel level: 3 style: color: "#cf1322" ``` Large Dots Custom Indicators More Visible ```yaml - id: theme_large_dots_carousel type: Carousel properties: theme: dotWidth: 24 dotHeight: 6 dotActiveWidth: 40 slots: theme_ld_s1: blocks: - id: theme_ld_s1 type: Box style: height: 160 background: "#1677ff" display: flex alignItems: center justifyContent: center blocks: - id: theme_ld_s1_text type: Title properties: content: Large Dots level: 3 style: color: white theme_ld_s2: blocks: - id: theme_ld_s2 type: Box style: height: 160 background: "#4096ff" display: flex alignItems: center justifyContent: center blocks: - id: theme_ld_s2_text type: Title properties: content: Custom Indicators level: 3 style: color: white theme_ld_s3: blocks: - id: theme_ld_s3 type: Box style: height: 160 background: "#69b1ff" display: flex alignItems: center justifyContent: center blocks: - id: theme_ld_s3_text type: Title properties: content: More Visible level: 3 style: color: white ``` Round Dots Circular Indicators Same Width Active ```yaml - id: theme_round_dots_carousel type: Carousel properties: theme: dotWidth: 12 dotHeight: 12 dotActiveWidth: 12 slots: theme_rd_s1: blocks: - id: theme_rd_s1 type: Box style: height: 160 background: "#52c41a" display: flex alignItems: center justifyContent: center blocks: - id: theme_rd_s1_text type: Title properties: content: Round Dots level: 3 style: color: white theme_rd_s2: blocks: - id: theme_rd_s2 type: Box style: height: 160 background: "#73d13d" display: flex alignItems: center justifyContent: center blocks: - id: theme_rd_s2_text type: Title properties: content: Circular Indicators level: 3 style: color: white theme_rd_s3: blocks: - id: theme_rd_s3 type: Box style: height: 160 background: "#95de64" display: flex alignItems: center justifyContent: center blocks: - id: theme_rd_s3_text type: Title properties: content: Same Width Active level: 3 style: color: "#135200" ``` Larger Arrows More Prominent Navigation Controls ```yaml - id: theme_large_arrows_carousel type: Carousel properties: arrows: true theme: arrowSize: 24 arrowOffset: 16 slots: theme_la_s1: blocks: - id: theme_la_s1 type: Box style: height: 180 background: "#722ed1" display: flex alignItems: center justifyContent: center blocks: - id: theme_la_s1_text type: Title properties: content: Larger Arrows level: 3 style: color: white theme_la_s2: blocks: - id: theme_la_s2 type: Box style: height: 180 background: "#9254de" display: flex alignItems: center justifyContent: center blocks: - id: theme_la_s2_text type: Title properties: content: More Prominent level: 3 style: color: white theme_la_s3: blocks: - id: theme_la_s3 type: Box style: height: 180 background: "#b37feb" display: flex alignItems: center justifyContent: center blocks: - id: theme_la_s3_text type: Title properties: content: Navigation Controls level: 3 style: color: white ``` Spaced Dots Wide Gap More Offset Custom Spacing ```yaml - id: theme_spaced_dots_carousel type: Carousel properties: theme: dotGap: 12 dotOffset: 20 dotWidth: 20 dotHeight: 4 dotActiveWidth: 36 slots: theme_sd_s1: blocks: - id: theme_sd_s1 type: Box style: height: 160 background: "#eb2f96" display: flex alignItems: center justifyContent: center blocks: - id: theme_sd_s1_text type: Title properties: content: Spaced Dots level: 3 style: color: white theme_sd_s2: blocks: - id: theme_sd_s2 type: Box style: height: 160 background: "#f759ab" display: flex alignItems: center justifyContent: center blocks: - id: theme_sd_s2_text type: Title properties: content: Wide Gap level: 3 style: color: white theme_sd_s3: blocks: - id: theme_sd_s3 type: Box style: height: 160 background: "#ff85c0" display: flex alignItems: center justifyContent: center blocks: - id: theme_sd_s3_text type: Title properties: content: More Offset level: 3 style: color: white theme_sd_s4: blocks: - id: theme_sd_s4 type: Box style: height: 160 display: flex alignItems: center justifyContent: center blocks: - id: theme_sd_s4_text type: Title properties: content: Custom Spacing level: 3 style: color: "#c41d7f" ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `accessibility` | boolean | `true` | Enable tabbing and arrow key navigation. | | `adaptiveHeight` | boolean | `false` | Adjust the slide's height automatically. | | `arrows` | boolean | `false` | Whether or not to show arrows. | | `autoplaySpeed` | integer | `3000` | Delay between each auto scroll (in milliseconds). | | `autoplay` | boolean \| object | `false` | Toggles whether or not to scroll automatically. Set to `{ dotDuration: true }` to show the time until the next slide as a progress bar in the active dot. | | `autoplay.dotDuration` | boolean | `false` | Show the autoplay progress in the active dot. | | `centerMode` | boolean | `false` | Center current slide. | | `centerPadding` | string | `"50px"` | Padding applied to center slide. | | `dotPosition` | string | `"bottom"` | The position of the dots, which can be one of top, bottom, left or right. Left and right dots make the carousel vertical. Enum: `left`, `right`, `top`, `bottom`. | | `dots` | boolean | `true` | Whether or not to show the dots. | | `draggable` | boolean | `false` | Enable scrollable via dragging on desktop | | `easing` | string | `"linear"` | Transition interpolation function name. | | `effect` | string | `"scrollx"` | Transition effect, either scrollx or fade. | | `focusOnSelect` | boolean | `false` | Go to slide on click. | | `initialSlide` | integer | `0` | Index of the slide to show first. | | `infinite` | boolean | `true` | Infinitely wrap around contents. | | `pauseOnDotsHover` | boolean | `false` | Prevents autoplay while hovering on dot. | | `pauseOnFocus` | boolean | `false` | Prevents autoplay while focused on slides. | | `pauseOnHover` | boolean | `true` | Prevents autoplay while hovering on track. | | `responsive` | array | `[]` | Customize based on breakpoints. | | `responsive.$.breakpoint` | integer | - | Maximum screen size. | | `responsive.$.settings` | object | - | Carousel properties. | | `rows` | integer | `1` | Number of rows per slide in the slider, (enables grid mode). | | `rtl` | boolean | `false` | Reverses the slide order. | | `slides` | array | - | Optional list of slides for explicit ordering and metadata. Each entry must have a `key` matching a slot name. When omitted, every defined slot becomes a slide, ordered alphabetically by key. Each entry is passed to `afterChange` and `beforeChange` events as `event.current` and `event.next` — any extra fields on the entry (e.g. a title or analytics id) are available there. | | `slides.$.key` | string | - | Slot key for this slide. | | `slidesPerRow` | integer | `1` | Number of slides to display in grid mode, this is useful with rows option. | | `slidesToScroll` | integer | `1` | How many slides to scroll at once. | | `slidesToShow` | integer | `1` | How many slides to show in one frame. | | `speed` | integer | `500` | Slide transition animation speed in milliseconds. | | `swipeToSlide` | boolean | `false` | Enable drag/swipe irrespective of `slidesToScroll`. | | `swipe` | boolean | `true` | Enable/disable swiping to change slides. | | `vertical` | boolean | `false` | Whether or not the slides are shown in a column. | | `waitForAnimate` | boolean | `false` | Ignore requests to change slide while a slide transition is animating. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design carousel tokens](https://ant.design/components/carousel#design-token). | | `theme.dotWidth` | number | `16` | Width of the indicator dot. | | `theme.dotHeight` | number | `3` | Height of the indicator dot. | | `theme.dotGap` | number | `4` | Gap between indicator dots. | | `theme.dotOffset` | number | `12` | Offset distance of dots from the carousel edge. | | `theme.dotActiveWidth` | number | `24` | Width of the active indicator dot. | | `theme.arrowSize` | number | `16` | Size of the navigation arrows. | | `theme.arrowOffset` | number | `8` | Offset distance of arrows from the carousel edge. | | `theme.colorBgContainer` | string | - | Background color used for indicator dots. | | Event | Event Data | Description | | --- | --- | --- | | `afterChange` | \- | Trigger actions after the slide is changed. | | `beforeChange` | \- | Trigger actions before the slide is changed. | | `onInit` | \- | Trigger actions when the carousel is initialized. | | `onSwipe` | `{ direction }` | Trigger actions when the user starts to swipe or drag the carousel. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Carousel element. | Slot keys are user-defined in your config and resolved at build time — not generated at runtime. The block typically pairs slots with an array property (`tabs`, `panels`, `slides`) listed in the Properties table; see the examples above for the expected shape. --- url: https://docs.lowdefy.com/md/container-blocks/collapse.md --- # Collapse Collapsible panels with accordion mode and custom icons. A web application that enables team collaboration through shared workspaces, real-time messaging, and integrated task management across departments. The platform supports up to 500 concurrent users with role-based access control and end-to-end encryption for all communications. Node.js 18+, PostgreSQL 15, Redis 7 for caching, and a minimum of 4GB RAM for the development environment. Docker is recommended for local setup. Phase 1 launches in Q2 2026 with core messaging and workspace features. Phase 2 adds analytics, reporting dashboards, and third-party integrations in Q3 2026. ```yaml - id: basic_collapse type: Collapse properties: panels: - key: overview title: Project Overview - key: requirements title: System Requirements - key: timeline title: Project Timeline slots: overview: blocks: - id: basic_overview_text type: Paragraph properties: content: A web application that enables team collaboration through shared workspaces, real-time messaging, and integrated task management across departments. - id: basic_overview_detail type: Paragraph properties: content: The platform supports up to 500 concurrent users with role-based access control and end-to-end encryption for all communications. requirements: blocks: - id: basic_requirements_text type: Paragraph properties: content: Node.js 18+, PostgreSQL 15, Redis 7 for caching, and a minimum of 4GB RAM for the development environment. Docker is recommended for local setup. timeline: blocks: - id: basic_timeline_text type: Paragraph properties: content: Phase 1 launches in Q2 2026 with core messaging and workspace features. Phase 2 adds analytics, reporting dashboards, and third-party integrations in Q3 2026. ``` Configure your display name, preferred language, and timezone. These settings apply across all devices linked to your account. Choose which notifications you receive via email, push, or in-app messages. Set quiet hours to pause non-critical alerts. Manage profile visibility, activity status display, and data sharing preferences. Enable two-factor authentication for added security. Switch between light and dark themes, adjust font size, and customize the sidebar layout to match your workflow. ```yaml - id: accordion_collapse type: Collapse properties: accordion: true panels: - key: acc_general title: General Settings - key: acc_notifications title: Notification Preferences - key: acc_privacy title: Privacy & Security - key: acc_appearance title: Appearance slots: acc_general: blocks: - id: accordion_general_text type: Paragraph properties: content: Configure your display name, preferred language, and timezone. These settings apply across all devices linked to your account. acc_notifications: blocks: - id: accordion_notifications_text type: Paragraph properties: content: Choose which notifications you receive via email, push, or in-app messages. Set quiet hours to pause non-critical alerts. acc_privacy: blocks: - id: accordion_privacy_text type: Paragraph properties: content: Manage profile visibility, activity status display, and data sharing preferences. Enable two-factor authentication for added security. acc_appearance: blocks: - id: accordion_appearance_text type: Paragraph properties: content: Switch between light and dark themes, adjust font size, and customize the sidebar layout to match your workflow. ``` Sign up for a free account, complete the onboarding wizard, and deploy your first application in under five minutes using one of our starter templates. We offer Free, Pro ($29/month), and Enterprise tiers. The Free plan includes unlimited development environments and up to 100 monthly active users. Yes, the Pro plan includes a 14-day free trial with full access to all features. No credit card required to start. Reach our support team via the in-app chat widget, email us at support@example.com, or visit the community forums for peer assistance. ```yaml - id: default_active_collapse type: Collapse properties: defaultActiveKey: da_started panels: - key: da_started title: How do I get started? - key: da_pricing title: What are the pricing plans? - key: da_trial title: Is there a free trial? - key: da_support title: How do I contact support? slots: da_started: blocks: - id: default_active_started_text type: Paragraph properties: content: Sign up for a free account, complete the onboarding wizard, and deploy your first application in under five minutes using one of our starter templates. da_pricing: blocks: - id: default_active_pricing_text type: Paragraph properties: content: We offer Free, Pro ($29/month), and Enterprise tiers. The Free plan includes unlimited development environments and up to 100 monthly active users. da_trial: blocks: - id: default_active_trial_text type: Paragraph properties: content: Yes, the Pro plan includes a 14-day free trial with full access to all features. No credit card required to start. da_support: blocks: - id: default_active_support_text type: Paragraph properties: content: Reach our support team via the in-app chat widget, email us at support@example.com, or visit the community forums for peer assistance. ``` Founded in 2020, we build tools that make software development accessible to everyone regardless of technical background. Our mission is to democratize application development through low-code and open-source technologies. Founded in 2020, we build tools that make software development accessible to everyone regardless of technical background. Our mission is to democratize application development through low-code and open-source technologies. ```yaml - id: bordered_collapse type: Collapse properties: bordered: true panels: - key: brd_about title: About Us (Bordered) - key: brd_mission title: Our Mission slots: brd_about: blocks: - id: bordered_about_text type: Paragraph properties: content: Founded in 2020, we build tools that make software development accessible to everyone regardless of technical background. brd_mission: blocks: - id: bordered_mission_text type: Paragraph properties: content: Our mission is to democratize application development through low-code and open-source technologies. - id: borderless_collapse type: Collapse properties: bordered: false panels: - key: bls_about title: About Us (Borderless) - key: bls_mission title: Our Mission slots: bls_about: blocks: - id: borderless_about_text type: Paragraph properties: content: Founded in 2020, we build tools that make software development accessible to everyone regardless of technical background. bls_mission: blocks: - id: borderless_mission_text type: Paragraph properties: content: Our mission is to democratize application development through low-code and open-source technologies. ``` Orders ship within two business days. Unused items can be returned within 30 days. Compact header and content padding. Roomy header and content padding. ```yaml - id: ghost_collapse type: Collapse properties: ghost: true panels: - key: ghost_shipping title: Shipping (ghost) - key: ghost_returns title: Returns slots: ghost_shipping: blocks: - id: ghost_shipping_text type: Paragraph properties: content: Orders ship within two business days. ghost_returns: blocks: - id: ghost_returns_text type: Paragraph properties: content: Unused items can be returned within 30 days. - id: small_collapse type: Collapse properties: size: small panels: - key: small_panel title: Small size slots: small_panel: blocks: - id: small_panel_text type: Paragraph properties: content: Compact header and content padding. - id: large_collapse type: Collapse properties: size: large panels: - key: large_panel title: Large size slots: large_panel: blocks: - id: large_panel_text type: Paragraph properties: content: Roomy header and content padding. ``` With padding none, panel content sets its own spacing. Tighter panel content, still aligned with the header. ```yaml - id: padding_none_collapse type: Collapse properties: padding: none panels: - key: padding_none_invoices title: Invoices (padding none) - key: padding_none_notes title: Notes slots: padding_none_invoices: blocks: - id: padding_none_invoices_rows type: Html properties: html: >
INV-1042$1,280.00
INV-1041$640.00
INV-1040$2,115.50
INV-1039$96.00
padding_none_notes: blocks: - id: padding_none_notes_text type: Paragraph class: px-4 py-2 properties: content: With padding none, panel content sets its own spacing. - id: padding_compact_collapse type: Collapse properties: padding: compact panels: - key: padding_compact_panel title: Compact padding slots: padding_compact_panel: blocks: - id: padding_compact_panel_text type: Paragraph properties: content: Tighter panel content, still aligned with the header. ``` **Start (default):** Premium wireless headphones with active noise cancellation, 30-hour battery life, and multipoint connection. Bluetooth 5.3, 40mm custom drivers, frequency response 20Hz-40kHz, weight 250g, USB-C fast charging. **End:** Premium wireless headphones with active noise cancellation, 30-hour battery life, and multipoint connection. Bluetooth 5.3, 40mm custom drivers, frequency response 20Hz-40kHz, weight 250g, USB-C fast charging. ```yaml - id: icon_start_label type: Markdown properties: content: "**Start (default):**" - id: icon_start_collapse type: Collapse properties: expandIconPlacement: start panels: - key: is_details title: Product Details - key: is_specs title: Technical Specifications slots: is_details: blocks: - id: icon_start_details_text type: Paragraph properties: content: Premium wireless headphones with active noise cancellation, 30-hour battery life, and multipoint connection. is_specs: blocks: - id: icon_start_specs_text type: Paragraph properties: content: Bluetooth 5.3, 40mm custom drivers, frequency response 20Hz-40kHz, weight 250g, USB-C fast charging. - id: icon_end_label type: Markdown properties: content: "**End:**" - id: icon_end_collapse type: Collapse properties: expandIconPlacement: end panels: - key: ie_details title: Product Details - key: ie_specs title: Technical Specifications slots: ie_details: blocks: - id: icon_end_details_text type: Paragraph properties: content: Premium wireless headphones with active noise cancellation, 30-hour battery life, and multipoint connection. ie_specs: blocks: - id: icon_end_specs_text type: Paragraph properties: content: Bluetooth 5.3, 40mm custom drivers, frequency response 20Hz-40kHz, weight 250g, USB-C fast charging. ``` Real-time collaboration, version history, automated backups, and a powerful API for custom integrations. Connect with Slack, GitHub, Jira, Google Workspace, Figma, and over 200 other services via our marketplace. Flexible per-seat pricing starting at $10/month. Volume discounts available for teams larger than 50 members. The expand icon can be passed as an object with name and color properties for further customization. The icon rotates 90 degrees when the panel is expanded, providing a visual cue for the open/closed state. ```yaml - id: custom_icon_collapse type: Collapse properties: expandIcon: add-circle panels: - key: ci_features title: Key Features - key: ci_integrations title: Integrations - key: ci_pricing title: Pricing slots: ci_features: blocks: - id: custom_icon_features_text type: Paragraph properties: content: Real-time collaboration, version history, automated backups, and a powerful API for custom integrations. ci_integrations: blocks: - id: custom_icon_integrations_text type: Paragraph properties: content: Connect with Slack, GitHub, Jira, Google Workspace, Figma, and over 200 other services via our marketplace. ci_pricing: blocks: - id: custom_icon_pricing_text type: Paragraph properties: content: Flexible per-seat pricing starting at $10/month. Volume discounts available for teams larger than 50 members. - id: custom_icon_obj_collapse type: Collapse properties: expandIcon: name: arrow-right color: "#1677ff" panels: - key: cio_one title: Colored Custom Icon - key: cio_two title: Another Panel slots: cio_one: blocks: - id: custom_icon_obj_one_text type: Paragraph properties: content: The expand icon can be passed as an object with name and color properties for further customization. cio_two: blocks: - id: custom_icon_obj_two_text type: Paragraph properties: content: The icon rotates 90 degrees when the panel is expanded, providing a visual cue for the open/closed state. ``` This section is accessible to all users and contains general product documentation and getting-started guides. This content requires a Pro subscription to access. Enterprise-only analytics and reporting features. Community resources, FAQ, and links to the public knowledge base are available here for everyone. ```yaml - id: disabled_collapse type: Collapse properties: panels: - key: dis_available title: Available Section - key: dis_locked title: Locked Section (Upgrade Required) disabled: true - key: dis_premium title: Premium Content disabled: true - key: dis_open title: Another Available Section slots: dis_available: blocks: - id: disabled_available_text type: Paragraph properties: content: This section is accessible to all users and contains general product documentation and getting-started guides. dis_locked: blocks: - id: disabled_locked_text type: Paragraph properties: content: This content requires a Pro subscription to access. dis_premium: blocks: - id: disabled_premium_text type: Paragraph properties: content: Enterprise-only analytics and reporting features. dis_open: blocks: - id: disabled_open_text type: Paragraph properties: content: Community resources, FAQ, and links to the public knowledge base are available here for everyone. ``` Visit the registration page and fill in your name, email, and a secure password. You will receive a verification email within minutes. Choose a workspace name, select your industry, and configure default permissions for new members joining the organization. Send invitation links via email or share a join code. Assign roles (Admin, Editor, Viewer) to control access levels. ```yaml - id: no_arrow_collapse type: Collapse properties: showArrow: false panels: - key: na_step1 title: Step 1 - Create Account - key: na_step2 title: Step 2 - Configure Workspace - key: na_step3 title: Step 3 - Invite Team Members slots: na_step1: blocks: - id: no_arrow_step1_text type: Paragraph properties: content: Visit the registration page and fill in your name, email, and a secure password. You will receive a verification email within minutes. na_step2: blocks: - id: no_arrow_step2_text type: Paragraph properties: content: Choose a workspace name, select your industry, and configure default permissions for new members joining the organization. na_step3: blocks: - id: no_arrow_step3_text type: Paragraph properties: content: Send invitation links via email or share a join code. Assign roles (Admin, Editor, Viewer) to control access levels. ``` Our cross-functional engineering team brings together expertise in frontend, backend, and DevOps. Built with modern frameworks and battle-tested infrastructure components. > All dependencies are pinned and audited weekly for security vulnerabilities. Key delivery milestones for the current development cycle. Internal testing release with core features — March 2026 Public beta with limited access and feedback collection — June 2026 General availability with full feature set and SLA guarantees — September 2026 ```yaml - id: rich_collapse type: Collapse properties: panels: - key: rich_team title: Development Team - key: rich_stack title: Technology Stack - key: rich_milestones title: Project Milestones slots: rich_team: blocks: - id: rich_team_intro type: Paragraph properties: content: Our cross-functional engineering team brings together expertise in frontend, backend, and DevOps. - id: rich_team_tags_row type: Box layout: gap: 6 blocks: - id: rich_team_tag_fe type: Tag properties: title: Frontend color: blue - id: rich_team_tag_be type: Tag properties: title: Backend color: green - id: rich_team_tag_devops type: Tag properties: title: DevOps color: orange - id: rich_team_tag_qa type: Tag properties: title: QA color: purple - id: rich_team_stat_row type: Box layout: gap: 16 blocks: - id: rich_team_stat_members type: Statistic layout: flex: 0 0 auto properties: title: Team Members value: 12 - id: rich_team_stat_sprints type: Statistic layout: flex: 0 0 auto properties: title: Sprints Completed value: 24 rich_stack: blocks: - id: rich_stack_desc type: Paragraph properties: content: Built with modern frameworks and battle-tested infrastructure components. - id: rich_stack_tags_row type: Box layout: gap: 6 blocks: - id: rich_stack_tag_react type: Tag properties: title: React 18 color: cyan - id: rich_stack_tag_next type: Tag properties: title: Next.js 14 color: default - id: rich_stack_tag_pg type: Tag properties: title: PostgreSQL color: blue - id: rich_stack_tag_redis type: Tag properties: title: Redis color: red - id: rich_stack_tag_docker type: Tag properties: title: Docker color: geekblue - id: rich_stack_note type: Alert properties: message: All dependencies are pinned and audited weekly for security vulnerabilities. type: info showIcon: true rich_milestones: blocks: - id: rich_milestones_desc type: Paragraph properties: content: Key delivery milestones for the current development cycle. - id: rich_milestones_p1 type: Box layout: gap: 8 blocks: - id: rich_milestone_alpha_tag type: Tag layout: flex: 0 0 auto properties: title: Alpha color: orange - id: rich_milestone_alpha_text type: Paragraph properties: content: Internal testing release with core features — March 2026 - id: rich_milestones_p2 type: Box layout: gap: 8 blocks: - id: rich_milestone_beta_tag type: Tag layout: flex: 0 0 auto properties: title: Beta color: blue - id: rich_milestone_beta_text type: Paragraph properties: content: Public beta with limited access and feedback collection — June 2026 - id: rich_milestones_p3 type: Box layout: gap: 8 blocks: - id: rich_milestone_ga_tag type: Tag layout: flex: 0 0 auto properties: title: GA color: green - id: rich_milestone_ga_text type: Paragraph properties: content: General availability with full feature set and SLA guarantees — September 2026 ``` Manage user accounts, assign roles, reset passwords, and review login activity across the organization. View current subscription details, download invoices, update payment methods, and manage billing contacts. Audit trail of all administrative actions including user changes, permission updates, and configuration modifications. ```yaml - id: extra_collapse type: Collapse properties: panels: - key: ex_users title: User Management extraKey: ex_users_extra - key: ex_billing title: Billing & Invoices extraKey: ex_billing_extra - key: ex_logs title: Activity Logs extraKey: ex_logs_extra slots: ex_users: blocks: - id: extra_users_text type: Paragraph properties: content: Manage user accounts, assign roles, reset passwords, and review login activity across the organization. ex_users_extra: blocks: - id: extra_users_badge type: Tag properties: title: 48 users color: blue ex_billing: blocks: - id: extra_billing_text type: Paragraph properties: content: View current subscription details, download invoices, update payment methods, and manage billing contacts. ex_billing_extra: blocks: - id: extra_billing_badge type: Tag properties: title: Pro Plan color: gold ex_logs: blocks: - id: extra_logs_text type: Paragraph properties: content: Audit trail of all administrative actions including user changes, permission updates, and configuration modifications. ex_logs_extra: blocks: - id: extra_logs_badge type: Tag properties: title: 1,204 events color: default ``` The frontend is organized into feature modules, each with its own routing, state management, and component library. Shared UI components built with Ant Design, organized by atomic design principles (atoms, molecules, organisms). Global state managed via Redux Toolkit with RTK Query for server state synchronization and caching. File-based routing with Next.js App Router, protected routes via middleware, and dynamic route segments. The backend follows a microservices architecture with an API gateway handling authentication and rate limiting. RESTful API with OpenAPI 3.1 documentation. GraphQL available for complex queries. Rate limited to 1000 req/min per tenant. PostgreSQL with read replicas for horizontal scaling. Schema migrations managed with Prisma. Automated daily backups with point-in-time recovery. OAuth 2.0 with PKCE flow, JWT access tokens (15 min TTL), and secure httpOnly refresh tokens. SAML SSO available for Enterprise. ```yaml - id: nested_outer type: Collapse properties: panels: - key: no_frontend title: Frontend Architecture - key: no_backend title: Backend Architecture slots: no_frontend: blocks: - id: nested_fe_intro type: Paragraph properties: content: The frontend is organized into feature modules, each with its own routing, state management, and component library. - id: nested_fe_inner type: Collapse properties: bordered: false panels: - key: nfi_components title: Component Library - key: nfi_state title: State Management - key: nfi_routing title: Routing slots: nfi_components: blocks: - id: nested_fe_components_text type: Paragraph properties: content: Shared UI components built with Ant Design, organized by atomic design principles (atoms, molecules, organisms). nfi_state: blocks: - id: nested_fe_state_text type: Paragraph properties: content: Global state managed via Redux Toolkit with RTK Query for server state synchronization and caching. nfi_routing: blocks: - id: nested_fe_routing_text type: Paragraph properties: content: File-based routing with Next.js App Router, protected routes via middleware, and dynamic route segments. no_backend: blocks: - id: nested_be_intro type: Paragraph properties: content: The backend follows a microservices architecture with an API gateway handling authentication and rate limiting. - id: nested_be_inner type: Collapse properties: bordered: false panels: - key: nbi_api title: API Layer - key: nbi_database title: Database Design - key: nbi_auth title: Authentication slots: nbi_api: blocks: - id: nested_be_api_text type: Paragraph properties: content: RESTful API with OpenAPI 3.1 documentation. GraphQL available for complex queries. Rate limited to 1000 req/min per tenant. nbi_database: blocks: - id: nested_be_database_text type: Paragraph properties: content: PostgreSQL with read replicas for horizontal scaling. Schema migrations managed with Prisma. Automated daily backups with point-in-time recovery. nbi_auth: blocks: - id: nested_be_auth_text type: Paragraph properties: content: OAuth 2.0 with PKCE flow, JWT access tokens (15 min TTL), and secure httpOnly refresh tokens. SAML SSO available for Enterprise. ``` Deployed on 3 regions (us-east, eu-west, ap-southeast) with automatic failover and CDN edge caching. Mirror of production with synthetic test data. Auto-deploys from the staging branch on every merge. Used for QA validation before production releases. > Last deployment was 2 hours ago. All 847 integration tests passed. Local development environment running via Docker Compose. Hot-reload enabled for frontend and backend services with seeded test database. > 3 pending database migrations need to be applied before starting the dev server. ```yaml - id: actions_collapse type: Collapse properties: panels: - key: act_server title: Production Server extraKey: act_server_extra - key: act_staging title: Staging Environment extraKey: act_staging_extra - key: act_dev title: Development Environment extraKey: act_dev_extra slots: act_server: blocks: - id: actions_server_stats type: Box layout: gap: 16 blocks: - id: act_server_uptime type: Statistic layout: flex: 0 0 auto properties: title: Uptime value: 99.97 suffix: "%" - id: act_server_latency type: Statistic layout: flex: 0 0 auto properties: title: Avg Latency value: 42 suffix: ms - id: act_server_rpm type: Statistic layout: flex: 0 0 auto properties: title: Requests/min value: 8420 - id: actions_server_desc type: Paragraph properties: content: Deployed on 3 regions (us-east, eu-west, ap-southeast) with automatic failover and CDN edge caching. - id: actions_server_btns type: Box layout: gap: 8 blocks: - id: act_server_btn_logs type: Button layout: flex: 0 0 auto properties: title: View Logs icon: document color: primary variant: outlined size: small - id: act_server_btn_metrics type: Button layout: flex: 0 0 auto properties: title: Metrics icon: chart color: primary variant: outlined size: small act_server_extra: blocks: - id: act_server_status type: Tag properties: title: Healthy color: success act_staging: blocks: - id: actions_staging_desc type: Paragraph properties: content: Mirror of production with synthetic test data. Auto-deploys from the staging branch on every merge. Used for QA validation before production releases. - id: actions_staging_alert type: Alert properties: message: Last deployment was 2 hours ago. All 847 integration tests passed. type: success showIcon: true act_staging_extra: blocks: - id: act_staging_status type: Tag properties: title: Healthy color: success act_dev: blocks: - id: actions_dev_desc type: Paragraph properties: content: Local development environment running via Docker Compose. Hot-reload enabled for frontend and backend services with seeded test database. - id: actions_dev_alert type: Alert properties: message: 3 pending database migrations need to be applied before starting the dev server. type: warning showIcon: true act_dev_extra: blocks: - id: act_dev_status type: Tag properties: title: Needs Attention color: warning ``` Follow the 8px grid system for all spacing. Use consistent border radius (8px for containers, 4px for inputs). Maintain a minimum touch target of 44x44px for mobile. Primary: #1677ff, Success: #52c41a, Warning: #faad14, Error: #ff4d4f. Always use the design token system rather than hardcoding hex values. Body: 14px/1.5715, Headings: 24px/1.3, Small: 12px/1.6667. Use the Inter font family for UI text and JetBrains Mono for code snippets. ```yaml - id: css_styled_collapse type: Collapse style: .element: background: transparent borderColor: "#d9d9d9" .header: fontWeight: 600 .content: color: "#595959" properties: panels: - key: css_design title: Design Guidelines - key: css_brand title: Brand Colors - key: css_typography title: Typography slots: css_design: blocks: - id: css_design_text type: Paragraph properties: content: Follow the 8px grid system for all spacing. Use consistent border radius (8px for containers, 4px for inputs). Maintain a minimum touch target of 44x44px for mobile. css_brand: blocks: - id: css_brand_text type: Paragraph properties: content: "Primary: #1677ff, Success: #52c41a, Warning: #faad14, Error: #ff4d4f. Always use the design token system rather than hardcoding hex values." - id: css_brand_tags type: Box layout: gap: 6 blocks: - id: css_brand_tag_primary type: Tag properties: title: Primary color: "#1677ff" - id: css_brand_tag_success type: Tag properties: title: Success color: "#52c41a" - id: css_brand_tag_warning type: Tag properties: title: Warning color: "#faad14" - id: css_brand_tag_error type: Tag properties: title: Error color: "#ff4d4f" css_typography: blocks: - id: css_typography_text type: Paragraph properties: content: "Body: 14px/1.5715, Headings: 24px/1.3, Small: 12px/1.6667. Use the Inter font family for UI text and JetBrains Mono for code snippets." ``` This collapse uses a light blue header background and a slightly tinted content area, achieved by overriding the headerBg and contentBg design tokens. The padding has been increased to 16px vertically and 20px horizontally for both header and content areas, giving a more spacious feel. A dark-themed collapse achieved entirely through design token overrides. The header, content, and border colors are all customized. Token overrides cascade to all child elements, making it straightforward to create themed sections without global CSS changes. The borderRadiusLG token controls the rounding of the collapse container corners. Set to 16px here for a softer appearance. Combining borderRadiusLG with a green-tinted headerBg creates a nature-inspired visual style suitable for eco-friendly or wellness applications. ```yaml - id: theme_blue_header type: Collapse properties: theme: headerPadding: 16px 20px contentPadding: 20px 20px panels: - key: tb_overview title: Blue Header Theme - key: tb_details title: Expanded Padding slots: tb_overview: blocks: - id: theme_blue_overview_text type: Paragraph properties: content: This collapse uses a light blue header background and a slightly tinted content area, achieved by overriding the headerBg and contentBg design tokens. tb_details: blocks: - id: theme_blue_details_text type: Paragraph properties: content: The padding has been increased to 16px vertically and 20px horizontally for both header and content areas, giving a more spacious feel. - id: theme_dark_collapse type: Collapse properties: theme: headerBg: "#1f1f1f" contentBg: "#141414" colorText: "#ffffffd9" colorTextHeading: "#ffffffd9" colorBorder: "#424242" panels: - key: td_about title: Dark Theme - key: td_settings title: Configuration slots: td_about: blocks: - id: theme_dark_about_text type: Paragraph properties: content: A dark-themed collapse achieved entirely through design token overrides. The header, content, and border colors are all customized. td_settings: blocks: - id: theme_dark_settings_text type: Paragraph properties: content: Token overrides cascade to all child elements, making it straightforward to create themed sections without global CSS changes. - id: theme_rounded_collapse type: Collapse properties: theme: borderRadiusLG: 16 panels: - key: tr_one title: Extra Rounded Corners - key: tr_two title: Green Tinted Headers slots: tr_one: blocks: - id: theme_rounded_one_text type: Paragraph properties: content: The borderRadiusLG token controls the rounding of the collapse container corners. Set to 16px here for a softer appearance. tr_two: blocks: - id: theme_rounded_two_text type: Paragraph properties: content: Combining borderRadiusLG with a green-tinted headerBg creates a nature-inspired visual style suitable for eco-friendly or wellness applications. ``` Get started at no cost with essential features for individuals and small teams exploring the platform. > Community support via forums. No SLA guarantee. Everything in Free, plus advanced collaboration tools, priority support, and higher resource limits for growing teams. > Email and chat support with 24-hour response SLA. Full platform access with dedicated infrastructure, custom integrations, and white-glove onboarding for large organizations. > Dedicated account manager, 1-hour response SLA, and custom SLA agreements. ```yaml - id: rich_accordion type: Collapse properties: accordion: true expandIconPlacement: end panels: - key: ra_plan_free title: Free Plan extraKey: ra_plan_free_extra - key: ra_plan_pro title: Pro Plan extraKey: ra_plan_pro_extra - key: ra_plan_enterprise title: Enterprise Plan extraKey: ra_plan_enterprise_extra slots: ra_plan_free: blocks: - id: ra_free_desc type: Paragraph properties: content: Get started at no cost with essential features for individuals and small teams exploring the platform. - id: ra_free_features type: Box layout: gap: 6 blocks: - id: ra_free_tag_users type: Tag properties: title: Up to 5 users color: default - id: ra_free_tag_projects type: Tag properties: title: 3 projects color: default - id: ra_free_tag_storage type: Tag properties: title: 1 GB storage color: default - id: ra_free_alert type: Alert properties: message: Community support via forums. No SLA guarantee. type: info showIcon: true ra_plan_free_extra: blocks: - id: ra_free_price type: Tag properties: title: $0/month color: green ra_plan_pro: blocks: - id: ra_pro_desc type: Paragraph properties: content: Everything in Free, plus advanced collaboration tools, priority support, and higher resource limits for growing teams. - id: ra_pro_features type: Box layout: gap: 6 blocks: - id: ra_pro_tag_users type: Tag properties: title: Up to 50 users color: blue - id: ra_pro_tag_projects type: Tag properties: title: Unlimited projects color: blue - id: ra_pro_tag_storage type: Tag properties: title: 100 GB storage color: blue - id: ra_pro_tag_sso type: Tag properties: title: SSO included color: blue - id: ra_pro_alert type: Alert properties: message: Email and chat support with 24-hour response SLA. type: success showIcon: true ra_plan_pro_extra: blocks: - id: ra_pro_price type: Tag properties: title: $29/month color: blue ra_plan_enterprise: blocks: - id: ra_enterprise_desc type: Paragraph properties: content: Full platform access with dedicated infrastructure, custom integrations, and white-glove onboarding for large organizations. - id: ra_enterprise_features type: Box layout: gap: 6 blocks: - id: ra_ent_tag_users type: Tag properties: title: Unlimited users color: gold - id: ra_ent_tag_projects type: Tag properties: title: Unlimited projects color: gold - id: ra_ent_tag_storage type: Tag properties: title: Unlimited storage color: gold - id: ra_ent_tag_sso type: Tag properties: title: SAML SSO color: gold - id: ra_ent_tag_sla type: Tag properties: title: 99.99% SLA color: gold - id: ra_enterprise_alert type: Alert properties: message: Dedicated account manager, 1-hour response SLA, and custom SLA agreements. type: warning showIcon: true ra_plan_enterprise_extra: blocks: - id: ra_enterprise_price type: Tag properties: title: Custom color: gold ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `accordion` | boolean | `false` | If true, only one panel is open at a time. | | `activeKey` | string \| array | - | Current panel's key, or a list of keys of the open panels. | | `bordered` | boolean | `true` | Toggles rendering of the border around the collapse block. | | `collapsible` | string | - | How panels are toggled: by clicking anywhere in the `header` (default), only the expand `icon`, or `disabled` to stop all panels from toggling. Enum: `header`, `icon`, `disabled`. | | `defaultActiveKey` | string \| array | - | Initial active panel key, or a list of keys, if activeKey is not set. | | `destroyInactivePanel` | boolean | `false` | Destroy inactive panel. Maps to antd's `destroyOnHidden`. | | `expandIcon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block for expand icon on the right of selector. | | `expandIcon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `expandIcon.color` | string | - | Icon color. | | `expandIcon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `expandIcon.rotate` | number | - | Number of degrees to rotate the icon. | | `expandIcon.spin` | boolean | - | Continuously spin the icon with animation. | | `expandIcon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `expandIcon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `expandIcon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `expandIcon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `expandIconPlacement` | string | `"start"` | Set placement of the expand icon. Enum: `start`, `end`. | | `forceRender` | boolean | `false` | Force render for all panels. | | `ghost` | boolean | `false` | Make the collapse borderless with a transparent background. | | `padding` | string | `"default"` | Space inside each panel body. `compact` tightens it to 8px 16px. `none` removes it so content such as a table or list runs edge to edge; the panel headers keep their padding. Enum: `default`, `compact`, `none`. | | `panels` | array | - | | | `panels.$.title` | string | - | Title of the panel - supports html. | | `panels.$.key` | string | - | Key of the panel. | | `panels.$.extraKey` | string | - | Key for the extra area of the panel. | | `panels.$.disabled` | boolean | `false` | Disable the panel if true. | | `showArrow` | boolean | `true` | Show expand icon. | | `size` | string | `"medium"` | Size of the panel headers and content padding. Enum: `small`, `medium`, `large`. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design collapse tokens](https://ant.design/components/collapse#design-token). | | `theme.headerPadding` | string | `"12px 16px"` | Padding of the collapse header. | | `theme.headerPaddingSM` | string | `"8px 12px 8px 8px"` | Padding of the collapse header when size is small. | | `theme.headerPaddingLG` | string | `"16px 24px 16px 16px"` | Padding of the collapse header when size is large. | | `theme.headerBg` | string | `"rgba(0, 0, 0, 0.02)"` | Background color of the collapse header. | | `theme.contentPadding` | string | `"16px 16px"` | Padding of the collapse content area. | | `theme.contentPaddingSM` | number \| string | `12` | Padding of the collapse content area when size is small. | | `theme.contentPaddingLG` | number \| string | `24` | Padding of the collapse content area when size is large. | | `theme.contentBg` | string | `"#ffffff"` | Background color of the collapse content area. | | `theme.borderlessContentPadding` | string | `"4px 16px 16px"` | Padding of the content area in borderless (ghost) style. | | `theme.borderlessContentBg` | string | `"transparent"` | Background color of the content area in borderless (ghost) style. | | `theme.colorBorder` | string | - | Border color of the collapse component. | | `theme.borderRadiusLG` | number | `8` | Border radius of the collapse container. | | `theme.colorText` | string | - | Text color for the collapse content. | | `theme.colorTextHeading` | string | - | Text color for the collapse header. | | `theme.fontSize` | number | `14` | Font size of the collapse content. | | Event | Event Data | Description | | --- | --- | --- | | `onChange` | `{ activeKey }` | Trigger actions when collapse item is toggled. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Collapse element. | | `/expandIcon` | The expand icon in the Collapse. | | `/header` | The Collapse header. | | `/title` | The panel title text in the Collapse header. | | `/content` | The Collapse content. | Slot keys are user-defined in your config and resolved at build time — not generated at runtime. The block typically pairs slots with an array property (`tabs`, `panels`, `slides`) listed in the Properties table; see the examples above for the expected shape. --- url: https://docs.lowdefy.com/md/container-blocks/configprovider.md --- # ConfigProvider Global configuration provider for antd theme and locale settings. ```yaml - id: cp_primary_color type: ConfigProvider properties: token: colorPrimary: "#722ed1" blocks: - id: cp_primary_row type: Box layout: gap: 8 blocks: - id: cp_primary_btn_solid type: Button layout: flex: 0 0 auto properties: title: Solid color: primary variant: solid - id: cp_primary_btn_outlined type: Button layout: flex: 0 0 auto properties: title: Outlined color: primary variant: outlined - id: cp_primary_btn_dashed type: Button layout: flex: 0 0 auto properties: title: Dashed color: primary variant: dashed - id: cp_primary_btn_text type: Button layout: flex: 0 0 auto properties: title: Text color: primary variant: text - id: cp_primary_btn_link type: Button layout: flex: 0 0 auto properties: title: Link color: primary variant: link - id: cp_primary_switch type: Switch properties: label: title: Purple themed switch colon: false - id: cp_primary_progress type: Progress properties: percent: 65 ``` Dark Mode The dark algorithm inverts the color scheme for all child components. Backgrounds become dark and text becomes light automatically. ```yaml - id: cp_dark type: ConfigProvider properties: algorithm: dark blocks: - id: cp_dark_box type: Box style: padding: 24px background: "#141414" borderRadius: 8px layout: gap: 12 blocks: - id: cp_dark_title type: Title properties: content: Dark Mode level: 4 - id: cp_dark_text type: Paragraph properties: content: The dark algorithm inverts the color scheme for all child components. Backgrounds become dark and text becomes light automatically. - id: cp_dark_btn_row type: Box layout: gap: 8 blocks: - id: cp_dark_btn1 type: Button layout: flex: 0 0 auto properties: title: Primary color: primary variant: solid - id: cp_dark_btn2 type: Button layout: flex: 0 0 auto properties: title: Default color: default variant: outlined - id: cp_dark_btn3 type: Button layout: flex: 0 0 auto properties: title: Danger color: danger variant: solid - id: cp_dark_switch type: Switch properties: label: title: Dark switch colon: false ``` The compact algorithm reduces padding and margins for denser layouts. Useful for data-heavy dashboards and admin panels. ```yaml - id: cp_compact type: ConfigProvider properties: algorithm: compact blocks: - id: cp_compact_row type: Box layout: gap: 8 blocks: - id: cp_compact_btn1 type: Button layout: flex: 0 0 auto properties: title: Compact Solid color: primary variant: solid - id: cp_compact_btn2 type: Button layout: flex: 0 0 auto properties: title: Compact Outlined color: primary variant: outlined - id: cp_compact_input type: TextInput properties: label: title: Compact input colon: false placeholder: Compact sized input - id: cp_compact_text type: Paragraph properties: content: The compact algorithm reduces padding and margins for denser layouts. Useful for data-heavy dashboards and admin panels. ``` Dark + Compact Multiple algorithms can be combined by passing an array. Here dark and compact are used together for a dense dark-themed layout. ```yaml - id: cp_combined type: ConfigProvider properties: algorithm: - dark - compact blocks: - id: cp_combined_box type: Box style: padding: 24px background: "#141414" borderRadius: 8px layout: gap: 12 blocks: - id: cp_combined_title type: Title properties: content: Dark + Compact level: 4 - id: cp_combined_text type: Paragraph properties: content: Multiple algorithms can be combined by passing an array. Here dark and compact are used together for a dense dark-themed layout. - id: cp_combined_btn_row type: Box layout: gap: 8 blocks: - id: cp_combined_btn1 type: Button layout: flex: 0 0 auto properties: title: Action color: primary variant: solid - id: cp_combined_btn2 type: Button layout: flex: 0 0 auto properties: title: Cancel color: default variant: outlined ``` **Small:** **Middle (default):** **Large:** ```yaml - id: cp_size_small_label type: Markdown properties: content: "**Small:**" - id: cp_size_small type: ConfigProvider properties: componentSize: small blocks: - id: cp_small_row type: Box layout: gap: 8 blocks: - id: cp_small_btn type: Button layout: flex: 0 0 auto properties: title: Small Button color: primary variant: solid - id: cp_small_input type: TextInput layout: flex: 0 1 200px properties: placeholder: Small input label: disabled: true - id: cp_size_middle_label type: Markdown properties: content: "**Middle (default):**" - id: cp_size_middle type: ConfigProvider properties: componentSize: middle blocks: - id: cp_middle_row type: Box layout: gap: 8 blocks: - id: cp_middle_btn type: Button layout: flex: 0 0 auto properties: title: Middle Button color: primary variant: solid - id: cp_middle_input type: TextInput layout: flex: 0 1 200px properties: placeholder: Middle input label: disabled: true - id: cp_size_large_label type: Markdown properties: content: "**Large:**" - id: cp_size_large type: ConfigProvider properties: componentSize: large blocks: - id: cp_large_row type: Box layout: gap: 8 blocks: - id: cp_large_btn type: Button layout: flex: 0 0 auto properties: title: Large Button color: primary variant: solid - id: cp_large_input type: TextInput layout: flex: 0 1 200px properties: placeholder: Large input label: disabled: true ``` Setting componentDisabled to true disables all interactive child components at once, without needing to set disabled on each individual component. A block that sets disabled to false stays enabled. ```yaml - id: cp_disabled type: ConfigProvider properties: componentDisabled: true blocks: - id: cp_disabled_row type: Box layout: gap: 8 blocks: - id: cp_disabled_btn type: Button layout: flex: 0 0 auto properties: title: Disabled Button color: primary variant: solid - id: cp_disabled_btn2 type: Button layout: flex: 0 0 auto properties: title: Also Disabled color: default variant: outlined - id: cp_disabled_switch type: Switch properties: label: title: Disabled switch colon: false - id: cp_disabled_input type: TextInput properties: placeholder: Disabled input label: title: Disabled input colon: false - id: cp_disabled_selector type: Selector properties: placeholder: Disabled selector options: - Option 1 - Option 2 label: title: Disabled selector colon: false - id: cp_enabled_input type: TextInput properties: placeholder: Still enabled disabled: false label: title: "Re-enabled with disabled: false" colon: false - id: cp_disabled_text type: Paragraph properties: content: Setting componentDisabled to true disables all interactive child components at once, without needing to set disabled on each individual component. A block that sets disabled to false stays enabled. ``` Setting direction to rtl enables right-to-left layout for all child components. This is essential for languages like Arabic and Hebrew. ```yaml - id: cp_rtl type: ConfigProvider properties: direction: rtl blocks: - id: cp_rtl_row type: Box layout: gap: 8 blocks: - id: cp_rtl_btn type: Button layout: flex: 0 0 auto properties: title: Right-to-Left color: primary variant: solid icon: arrow-left - id: cp_rtl_input type: TextInput properties: placeholder: RTL text input label: title: RTL Input colon: false - id: cp_rtl_text type: Paragraph properties: content: Setting direction to rtl enables right-to-left layout for all child components. This is essential for languages like Arabic and Hebrew. ``` **Outlined (default):** **Filled:** **Borderless:** **Underlined:** ```yaml - id: cp_variant_outlined_label type: Markdown properties: content: "**Outlined (default):**" - id: cp_variant_outlined type: ConfigProvider properties: variant: outlined blocks: - id: cp_vo_input type: TextInput properties: placeholder: Outlined input label: title: Name colon: false - id: cp_variant_filled_label type: Markdown properties: content: "**Filled:**" - id: cp_variant_filled type: ConfigProvider properties: variant: filled blocks: - id: cp_vf_input type: TextInput properties: placeholder: Filled input label: title: Name colon: false - id: cp_variant_borderless_label type: Markdown properties: content: "**Borderless:**" - id: cp_variant_borderless type: ConfigProvider properties: variant: borderless blocks: - id: cp_vb_input type: TextInput properties: placeholder: Borderless input label: title: Name colon: false - id: cp_variant_underlined_label type: Markdown properties: content: "**Underlined:**" - id: cp_variant_underlined type: ConfigProvider properties: variant: underlined blocks: - id: cp_vu_input type: TextInput properties: placeholder: Underlined input label: title: Name colon: false ``` The components property allows overriding design tokens at the component level. Here, Button and Input components have custom borderRadius applied without affecting other components. ```yaml - id: cp_components type: ConfigProvider properties: components: Button: borderRadius: 20 controlHeight: 40 Input: borderRadius: 20 blocks: - id: cp_components_row type: Box layout: gap: 8 blocks: - id: cp_comp_btn type: Button layout: flex: 0 0 auto properties: title: Pill Button color: primary variant: solid - id: cp_comp_btn2 type: Button layout: flex: 0 0 auto properties: title: Also Pill color: default variant: outlined - id: cp_comp_input type: TextInput properties: placeholder: Rounded input label: title: Rounded input colon: false - id: cp_comp_text type: Paragraph properties: content: The components property allows overriding design tokens at the component level. Here, Button and Input components have custom borderRadius applied without affecting other components. ``` Global design tokens customize the look of all child components. This section uses a custom primary color (orange), larger font size, rounded borders, and a warm background color. ```yaml - id: cp_tokens type: ConfigProvider properties: token: colorPrimary: "#fa541c" fontSize: 16 borderRadius: 8 blocks: - id: cp_tokens_card type: Card properties: title: Custom Themed Card blocks: - id: cp_tokens_text type: Paragraph properties: content: Global design tokens customize the look of all child components. This section uses a custom primary color (orange), larger font size, rounded borders, and a warm background color. - id: cp_tokens_btn_row type: Box layout: gap: 8 blocks: - id: cp_tokens_btn1 type: Button layout: flex: 0 0 auto properties: title: Primary color: primary variant: solid - id: cp_tokens_btn2 type: Button layout: flex: 0 0 auto properties: title: Outlined color: primary variant: outlined - id: cp_tokens_input type: TextInput properties: placeholder: Themed input label: title: Themed Input colon: false ``` The theme property provides antd design token overrides for this block and all its descendants. It works similarly to token but is applied at the block level through the antd theme system. ```yaml - id: cp_theme type: ConfigProvider properties: theme: colorPrimary: "#13c2c2" colorInfo: "#13c2c2" borderRadius: 12 blocks: - id: cp_theme_card type: Card properties: title: Theme Overrides via theme Property blocks: - id: cp_theme_text type: Paragraph properties: content: The theme property provides antd design token overrides for this block and all its descendants. It works similarly to token but is applied at the block level through the antd theme system. - id: cp_theme_btn_row type: Box layout: gap: 8 blocks: - id: cp_theme_btn1 type: Button layout: flex: 0 0 auto properties: title: Teal Primary color: primary variant: solid - id: cp_theme_btn2 type: Button layout: flex: 0 0 auto properties: title: Teal Outlined color: primary variant: outlined - id: cp_theme_switch type: Switch properties: label: title: Teal themed switch colon: false ``` Styled Wrapper Tailwind CSS classes can be applied to the ConfigProvider wrapper div using the class property. Here a gradient background with rounded corners wraps all themed children. Elevated Section A ConfigProvider with shadow and padding classes creates a visually distinct themed section on the page. ```yaml - id: cp_styled_gradient type: ConfigProvider class: bg-gradient-to-r from-primary/10 to-primary/5 p-6 rounded-xl border border-border properties: token: colorPrimary: "#6366f1" blocks: - id: cp_styled_gradient_title type: Title properties: content: Styled Wrapper level: 4 - id: cp_styled_gradient_text type: Paragraph properties: content: Tailwind CSS classes can be applied to the ConfigProvider wrapper div using the class property. Here a gradient background with rounded corners wraps all themed children. - id: cp_styled_gradient_row type: Box layout: gap: 8 blocks: - id: cp_styled_gradient_btn1 type: Button layout: flex: 0 0 auto properties: title: Indigo Action color: primary variant: solid - id: cp_styled_gradient_btn2 type: Button layout: flex: 0 0 auto properties: title: Cancel color: default variant: outlined - id: cp_styled_shadow type: ConfigProvider class: shadow-lg p-8 rounded-2xl bg-bg-container properties: token: colorPrimary: "#059669" blocks: - id: cp_styled_shadow_title type: Title properties: content: Elevated Section level: 4 - id: cp_styled_shadow_text type: Paragraph properties: content: A ConfigProvider with shadow and padding classes creates a visually distinct themed section on the page. - id: cp_styled_shadow_btn type: Button layout: flex: 0 0 auto properties: title: Confirm color: primary variant: solid ``` Acme Commerce This demonstrates using ConfigProvider to apply a brand theme across an entire section. The rose primary color, pill-shaped buttons, and custom font size create a cohesive brand identity for all child components. ```yaml - id: cp_brand_wrapper type: ConfigProvider properties: token: colorPrimary: "#e11d48" colorInfo: "#0ea5e9" borderRadius: 6 fontSize: 14 components: Button: borderRadius: 20 blocks: - id: cp_brand_header type: Box class: bg-bg-layout p-6 rounded-lg border border-border layout: gap: 16 blocks: - id: cp_brand_title_row type: Box layout: gap: 12 align: center blocks: - id: cp_brand_avatar type: Avatar layout: flex: 0 0 auto properties: icon: Store size: 48 color: "#e11d48" - id: cp_brand_title type: Title layout: flex: 1 1 0 properties: content: Acme Commerce level: 3 - id: cp_brand_description type: Paragraph properties: content: This demonstrates using ConfigProvider to apply a brand theme across an entire section. The rose primary color, pill-shaped buttons, and custom font size create a cohesive brand identity for all child components. - id: cp_brand_form_row type: Box layout: gap: 12 blocks: - id: cp_brand_search_input type: TextInput layout: flex: 1 1 0 properties: placeholder: Search products... label: disabled: true suffixIcon: search - id: cp_brand_search_btn type: Button layout: flex: 0 0 auto properties: title: Search color: primary variant: solid - id: cp_brand_cart_btn type: Button layout: flex: 0 0 auto properties: title: Cart icon: ShoppingCart color: default variant: outlined - id: cp_brand_stats_row type: Box layout: gap: 16 blocks: - id: cp_brand_stat1 type: Statistic layout: flex: 1 1 0 properties: title: Products value: 1240 - id: cp_brand_stat2 type: Statistic layout: flex: 1 1 0 properties: title: Orders Today value: 87 - id: cp_brand_stat3 type: Statistic layout: flex: 1 1 0 properties: title: Revenue value: 12480 prefix: $ ``` System Metrics ```yaml - id: cp_dashboard_wrapper type: ConfigProvider properties: algorithm: - compact componentSize: small token: colorPrimary: "#1677ff" fontSize: 12 variant: filled blocks: - id: cp_dashboard_title type: Title properties: content: System Metrics level: 4 - id: cp_dashboard_cards_row type: Box layout: gap: 12 blocks: - id: cp_dashboard_card1 type: Card layout: flex: 1 1 0 properties: title: CPU Usage size: small blocks: - id: cp_dashboard_progress1 type: Progress properties: percent: 72 - id: cp_dashboard_tag1 type: Tag properties: title: Normal color: success - id: cp_dashboard_card2 type: Card layout: flex: 1 1 0 properties: title: Memory size: small blocks: - id: cp_dashboard_progress2 type: Progress properties: percent: 89 - id: cp_dashboard_tag2 type: Tag properties: title: High color: warning - id: cp_dashboard_card3 type: Card layout: flex: 1 1 0 properties: title: Disk I/O size: small blocks: - id: cp_dashboard_progress3 type: Progress properties: percent: 34 - id: cp_dashboard_tag3 type: Tag properties: title: Low color: blue - id: cp_dashboard_filter_row type: Box layout: gap: 8 align: center blocks: - id: cp_dashboard_filter_input type: TextInput layout: flex: 0 1 200px properties: placeholder: Filter services... label: disabled: true - id: cp_dashboard_selector type: Selector layout: flex: 0 1 150px properties: placeholder: Region label: disabled: true options: - label: US East value: us-east - label: US West value: us-west - label: EU West value: eu-west - id: cp_dashboard_refresh_btn type: Button layout: flex: 0 0 auto properties: title: Refresh icon: refresh color: primary variant: solid ``` Admin Panel A dark-themed admin panel using ConfigProvider to apply the dark algorithm with a custom purple accent color. All child components inherit the dark color scheme automatically. ```yaml - id: cp_admin_wrapper type: ConfigProvider properties: algorithm: dark token: colorPrimary: "#7c3aed" borderRadius: 8 blocks: - id: cp_admin_box type: Box style: padding: 24px background: "#141414" borderRadius: 12px layout: gap: 16 blocks: - id: cp_admin_header_row type: Box layout: gap: 12 align: center blocks: - id: cp_admin_title type: Title layout: flex: 1 1 0 properties: content: Admin Panel level: 3 - id: cp_admin_status_tag type: Tag layout: flex: 0 0 auto properties: title: Online color: success - id: cp_admin_desc type: Paragraph properties: content: A dark-themed admin panel using ConfigProvider to apply the dark algorithm with a custom purple accent color. All child components inherit the dark color scheme automatically. - id: cp_admin_form type: Box layout: gap: 12 blocks: - id: cp_admin_name_input type: TextInput properties: placeholder: Enter username label: title: Username colon: false - id: cp_admin_email_input type: TextInput properties: placeholder: Enter email address label: title: Email colon: false - id: cp_admin_role_selector type: Selector properties: placeholder: Select role label: title: Role colon: false options: - label: Administrator value: admin - label: Editor value: editor - label: Viewer value: viewer - id: cp_admin_btn_row type: Box layout: gap: 8 blocks: - id: cp_admin_save_btn type: Button layout: flex: 0 0 auto properties: title: Save User icon: save color: primary variant: solid - id: cp_admin_cancel_btn type: Button layout: flex: 0 0 auto properties: title: Cancel color: default variant: outlined ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `algorithm` | string \| array | - | Theme algorithm. Can be "default", "dark", "compact", or an array of these values. | | `componentDisabled` | boolean | `false` | Disable every input and button inside the ConfigProvider. A block that sets its own `disabled` property keeps that value, so `disabled: false` re-enables it. | | `componentSize` | string | - | Set size for all child components. `middle` is the older name for `medium`. Enum: `small`, `medium`, `middle`, `large`. | | `components` | object | - | Component-level token overrides. Keys are component names, values are token objects. | | `direction` | string | `"ltr"` | Direction of layout. Enum: `ltr`, `rtl`. | | `locale` | object | - | Antd locale object to localize built-in component strings (date pickers, pagination, modal, form validation). Pair with the _locale operator and config.i18n to keep the whole subtree in one language. | | `popupMatchSelectWidth` | boolean \| number | - | Whether dropdowns of select-like components match the width of their input. A number sets a minimum dropdown width in pixels. `false` also turns off virtual scrolling. | | `popupOverflow` | string | `"viewport"` | Keep dropdowns of select-like components inside the viewport, or let them follow the page scroll. Enum: `viewport`, `scroll`. | | `token` | object | - | Theme token configuration. Customize design tokens like colorPrimary, fontSize, etc. | | `virtual` | boolean | `true` | Set to false to turn off virtual scrolling in selectors, trees and tables, so every option renders. | | `wave` | object | - | Click wave effect of buttons and other clickable components. | | `wave.disabled` | boolean | `false` | Turn off the click wave effect. | | `variant` | string | - | Global input variant style for all child components. Enum: `outlined`, `filled`, `borderless`, `underlined`. | | `theme` | object | - | Antd design token overrides for this block and its descendants. Merged with token, which takes precedence. See [antd design tokens](https://ant.design/components/overview#design-token). | No events defined. No CSS keys defined. | Slot | Description | | --- | --- | | `content` | Child blocks wrapped by the ConfigProvider. | --- url: https://docs.lowdefy.com/md/container-blocks/confirmmodal.md --- # ConfirmModal Modal dialog requiring user confirmation before proceeding. This action cannot be undone. ```yaml - id: confirm_basic_trigger type: Button layout: flex: 0 0 auto properties: title: Open Confirm color: primary variant: solid events: onClick: - id: confirm_basic_open type: CallMethod params: blockId: confirm_basic method: open - id: confirm_basic type: ConfirmModal properties: title: Are you sure? content: This action cannot be undone. ``` Do you want to proceed with this action? Your account has been updated successfully. The operation completed successfully. This will overwrite existing data. Are you sure you want to continue? An error occurred while processing your request. Do you want to retry? ```yaml - id: confirm_status_confirm_btn type: Button layout: flex: 0 0 auto properties: title: Confirm color: primary variant: outlined events: onClick: - id: confirm_status_confirm_open type: CallMethod params: blockId: confirm_status_confirm method: open - id: confirm_status_info_btn type: Button layout: flex: 0 0 auto properties: title: Info color: blue variant: outlined events: onClick: - id: confirm_status_info_open type: CallMethod params: blockId: confirm_status_info method: open - id: confirm_status_success_btn type: Button layout: flex: 0 0 auto properties: title: Success color: green variant: outlined events: onClick: - id: confirm_status_success_open type: CallMethod params: blockId: confirm_status_success method: open - id: confirm_status_warning_btn type: Button layout: flex: 0 0 auto properties: title: Warning color: orange variant: outlined events: onClick: - id: confirm_status_warning_open type: CallMethod params: blockId: confirm_status_warning method: open - id: confirm_status_error_btn type: Button layout: flex: 0 0 auto properties: title: Error color: danger variant: outlined events: onClick: - id: confirm_status_error_open type: CallMethod params: blockId: confirm_status_error method: open - id: confirm_status_confirm type: ConfirmModal properties: title: Confirm Action content: Do you want to proceed with this action? status: confirm - id: confirm_status_info type: ConfirmModal properties: title: Information content: Your account has been updated successfully. status: info - id: confirm_status_success type: ConfirmModal properties: title: Success content: The operation completed successfully. status: success - id: confirm_status_warning type: ConfirmModal properties: title: Warning content: This will overwrite existing data. Are you sure you want to continue? status: warning - id: confirm_status_error type: ConfirmModal properties: title: Error content: An error occurred while processing your request. Do you want to retry? status: error ``` The status of this modal was set at open time via the method args, overriding the default. ```yaml - id: confirm_override_info_btn type: Button layout: flex: 0 0 auto properties: title: Open as Info color: blue variant: outlined events: onClick: - id: confirm_override_info_open type: CallMethod params: blockId: confirm_override method: open args: status: info - id: confirm_override_error_btn type: Button layout: flex: 0 0 auto properties: title: Open as Error color: danger variant: outlined events: onClick: - id: confirm_override_error_open type: CallMethod params: blockId: confirm_override method: open args: status: error - id: confirm_override_success_btn type: Button layout: flex: 0 0 auto properties: title: Open as Success color: green variant: outlined events: onClick: - id: confirm_override_success_open type: CallMethod params: blockId: confirm_override method: open args: status: success - id: confirm_override type: ConfirmModal properties: title: Dynamic Status content: The status of this modal was set at open time via the method args, overriding the default. status: confirm ``` You have unsaved changes. Do you want to save before leaving? ```yaml - id: confirm_btn_text_trigger type: Button layout: flex: 0 0 auto properties: title: Open Custom Buttons color: primary variant: outlined events: onClick: - id: confirm_btn_text_open type: CallMethod params: blockId: confirm_btn_text method: open - id: confirm_btn_text type: ConfirmModal properties: title: Save Changes? content: You have unsaved changes. Do you want to save before leaving? okText: Save cancelText: Discard ``` This item will be permanently removed from the system. Are you absolutely sure you want to delete this project and all its data? ```yaml - id: confirm_icon_trigger type: Button layout: flex: 0 0 auto properties: title: Open with Custom Icon color: primary variant: outlined events: onClick: - id: confirm_icon_open type: CallMethod params: blockId: confirm_icon method: open - id: confirm_icon_colored_trigger type: Button layout: flex: 0 0 auto properties: title: Open with Colored Icon color: danger variant: outlined events: onClick: - id: confirm_icon_colored_open type: CallMethod params: blockId: confirm_icon_colored method: open - id: confirm_icon type: ConfirmModal properties: title: Delete Item? content: This item will be permanently removed from the system. icon: delete - id: confirm_icon_colored type: ConfirmModal properties: title: Danger Zone content: Are you absolutely sure you want to delete this project and all its data? icon: name: warning color: "#ff4d4f" size: 22 ``` This confirm modal shows a close (x) button in the top right. Click the mask area outside the modal to dismiss it. This confirm modal has no mask overlay behind it. ```yaml - id: confirm_closable_trigger type: Button layout: flex: 0 0 auto properties: title: Closable color: primary variant: outlined events: onClick: - id: confirm_closable_open type: CallMethod params: blockId: confirm_closable method: open - id: confirm_mask_closable_trigger type: Button layout: flex: 0 0 auto properties: title: Mask Closable color: primary variant: outlined events: onClick: - id: confirm_mask_closable_open type: CallMethod params: blockId: confirm_mask_closable method: open - id: confirm_no_mask_trigger type: Button layout: flex: 0 0 auto properties: title: No Mask color: primary variant: outlined events: onClick: - id: confirm_no_mask_open type: CallMethod params: blockId: confirm_no_mask method: open - id: confirm_closable type: ConfirmModal properties: title: Closable Confirm content: This confirm modal shows a close (x) button in the top right. closable: true - id: confirm_mask_closable type: ConfirmModal properties: title: Mask Closable content: Click the mask area outside the modal to dismiss it. maskClosable: true - id: confirm_no_mask type: ConfirmModal properties: title: No Mask content: This confirm modal has no mask overlay behind it. mask: false ``` This confirm modal is vertically centered in the viewport. A compact 300px confirm modal. A wider 600px confirm modal with more room for content. Useful for complex messages or forms. ```yaml - id: confirm_centered_trigger type: Button layout: flex: 0 0 auto properties: title: Centered color: primary variant: outlined events: onClick: - id: confirm_centered_open type: CallMethod params: blockId: confirm_centered method: open - id: confirm_narrow_trigger type: Button layout: flex: 0 0 auto properties: title: Narrow (300px) color: primary variant: outlined events: onClick: - id: confirm_narrow_open type: CallMethod params: blockId: confirm_narrow method: open - id: confirm_wide_trigger type: Button layout: flex: 0 0 auto properties: title: Wide (600px) color: primary variant: outlined events: onClick: - id: confirm_wide_open type: CallMethod params: blockId: confirm_wide method: open - id: confirm_centered type: ConfirmModal properties: title: Centered Confirm content: This confirm modal is vertically centered in the viewport. centered: true - id: confirm_narrow type: ConfirmModal properties: title: Narrow Confirm content: A compact 300px confirm modal. width: 300 - id: confirm_wide type: ConfirmModal properties: title: Wide Confirm content: A wider 600px confirm modal with more room for content. Useful for complex messages or forms. width: 600 ``` This confirm modal uses a custom z-index of 2000, ensuring it appears above other overlays. ```yaml - id: confirm_zindex_trigger type: Button layout: flex: 0 0 auto properties: title: Open Confirm (z-index 2000) color: primary variant: outlined events: onClick: - id: confirm_zindex_open type: CallMethod params: blockId: confirm_zindex method: open - id: confirm_zindex type: ConfirmModal properties: title: High Z-Index Confirm content: This confirm modal uses a custom z-index of 2000, ensuring it appears above other overlays. zIndex: 2000 ``` This action will permanently delete the selected items and cannot be undone. Your data will be exported to a CSV file and downloaded. ```yaml - id: confirm_ok_btn_trigger type: Button layout: flex: 0 0 auto properties: title: Open with Danger Ok color: danger variant: outlined events: onClick: - id: confirm_ok_btn_open type: CallMethod params: blockId: confirm_ok_btn method: open - id: confirm_icon_btns_trigger type: Button layout: flex: 0 0 auto properties: title: Open with Icon Buttons color: primary variant: outlined events: onClick: - id: confirm_icon_btns_open type: CallMethod params: blockId: confirm_icon_btns method: open - id: confirm_ok_btn type: ConfirmModal properties: title: Delete Permanently? content: This action will permanently delete the selected items and cannot be undone. status: error okText: Delete okButton: danger: true cancelText: Keep Items - id: confirm_icon_btns type: ConfirmModal properties: title: Export Data? content: Your data will be exported to a CSV file and downloaded. status: confirm okText: Export okButton: icon: download iconPlacement: end cancelText: Cancel cancelButton: icon: close ``` This modal uses style to apply custom CSS directly to the modal element. ```yaml - id: confirm_modal_style_trigger type: Button layout: flex: 0 0 auto properties: title: Open Styled Modal color: primary variant: outlined events: onClick: - id: confirm_modal_style_open type: CallMethod params: blockId: confirm_modal_style method: open - id: confirm_modal_style type: ConfirmModal properties: title: Styled Modal content: This modal uses style to apply custom CSS directly to the modal element. style: .element: borderRadius: 16 boxShadow: 0 8px 32px rgba(0, 0, 0, 0.2) ``` ```yaml - id: confirm_content_slot_trigger type: Button layout: flex: 0 0 auto properties: title: Open with Rich Content color: primary variant: solid events: onClick: - id: confirm_content_slot_open type: CallMethod params: blockId: confirm_content_slot method: open - id: confirm_content_slot type: ConfirmModal properties: title: Review Changes status: confirm width: 500 blocks: - id: confirm_content_slot_desc type: Descriptions properties: size: small column: 1 items: - label: File value: report-2026-Q1.xlsx - label: Size value: 2.4 MB - label: Modified value: March 10, 2026 - label: Author value: Jane Cooper ``` Your subscription will expire on March 31, 2026. Please renew to avoid service interruption. ```yaml - id: confirm_html_trigger type: Button layout: flex: 0 0 auto properties: title: Open with HTML Content color: primary variant: outlined events: onClick: - id: confirm_html_open type: CallMethod params: blockId: confirm_html method: open - id: confirm_html type: ConfirmModal properties: title: Important Notice content: Your subscription will expire on March 31, 2026. Please renew to avoid service interruption. status: warning ``` Clicking Ok or Cancel will update the state and show a message. ```yaml - id: confirm_events_trigger type: Button layout: flex: 0 0 auto properties: title: Open with Event Actions color: primary variant: solid events: onClick: - id: confirm_events_open type: CallMethod params: blockId: confirm_events method: open - id: confirm_events_result type: Paragraph properties: content: _if: test: _not: _not: _state: confirm_events_action then: _string.concat: - "Last action: " - _state: confirm_events_action else: Click the button above and choose Ok or Cancel. - id: confirm_events type: ConfirmModal properties: title: Confirm with Events content: Clicking Ok or Cancel will update the state and show a message. okText: Confirm events: onOpen: - id: confirm_events_on_open_set type: SetState params: confirm_events_action: Modal opened... onOk: - id: confirm_events_on_ok_set type: SetState params: confirm_events_action: Confirmed - id: confirm_events_on_ok_msg type: DisplayMessage params: content: Action confirmed successfully. status: success onCancel: - id: confirm_events_on_cancel_set type: SetState params: confirm_events_action: Cancelled - id: confirm_events_on_cancel_msg type: DisplayMessage params: content: Action was cancelled. status: info ``` The element CSS key applies Tailwind classes to the modal wrapper. This modal uses rounded-2xl and shadow-2xl on the element key. The body CSS key targets the modal body area. This modal uses bg-bg-layout and p-6 on the body key for a tinted content area. ```yaml - id: confirm_css_element_trigger type: Button layout: flex: 0 0 auto properties: title: Styled Element color: primary variant: outlined events: onClick: - id: confirm_css_element_open type: CallMethod params: blockId: confirm_css_element method: open - id: confirm_css_body_trigger type: Button layout: flex: 0 0 auto properties: title: Styled Body color: primary variant: outlined events: onClick: - id: confirm_css_body_open type: CallMethod params: blockId: confirm_css_body method: open - id: confirm_css_element type: ConfirmModal class: .element: rounded-2xl shadow-2xl properties: title: Styled Element content: The element CSS key applies Tailwind classes to the modal wrapper. This modal uses rounded-2xl and shadow-2xl on the element key. - id: confirm_css_body type: ConfirmModal class: .body: bg-bg-layout p-6 properties: title: Styled Body content: The body CSS key targets the modal body area. This modal uses bg-bg-layout and p-6 on the body key for a tinted content area. ``` This modal uses theme tokens to set a custom header background, title color, content background, and footer background. This modal uses confirmBodyPadding and confirmBtnsMarginTop tokens for tighter spacing. The confirmIconMarginInlineEnd adjusts icon-to-text distance. This modal uses borderRadiusLG for extra-rounded corners and colorBgMask for a tinted overlay backdrop. ```yaml - id: confirm_theme_colors_trigger type: Button layout: flex: 0 0 auto properties: title: Custom Colors color: primary variant: outlined events: onClick: - id: confirm_theme_colors_open type: CallMethod params: blockId: confirm_theme_colors method: open - id: confirm_theme_padding_trigger type: Button layout: flex: 0 0 auto properties: title: Custom Padding color: primary variant: outlined events: onClick: - id: confirm_theme_padding_open type: CallMethod params: blockId: confirm_theme_padding method: open - id: confirm_theme_radius_trigger type: Button layout: flex: 0 0 auto properties: title: Custom Radius and Mask color: primary variant: outlined events: onClick: - id: confirm_theme_radius_open type: CallMethod params: blockId: confirm_theme_radius method: open - id: confirm_theme_colors type: ConfirmModal properties: title: Custom Color Theme content: This modal uses theme tokens to set a custom header background, title color, content background, and footer background. theme: titleColor: "#0958d9" titleFontSize: 18 - id: confirm_theme_padding type: ConfirmModal properties: title: Custom Padding content: This modal uses confirmBodyPadding and confirmBtnsMarginTop tokens for tighter spacing. The confirmIconMarginInlineEnd adjusts icon-to-text distance. theme: confirmBodyPadding: 16px 24px confirmBtnsMarginTop: 16 confirmIconMarginInlineEnd: 12 - id: confirm_theme_radius type: ConfirmModal properties: title: Rounded with Tinted Mask content: This modal uses borderRadiusLG for extra-rounded corners and colorBgMask for a tinted overlay backdrop. theme: borderRadiusLG: 20 colorBgMask: rgba(0, 0, 100, 0.3) titleLineHeight: 2 ``` Sarah Chen - Product Manager James Park - Engineering Lead ```yaml - id: delete_flow_card type: Card properties: title: Team Members size: small blocks: - id: delete_flow_list_item_1 type: Box layout: gap: 12 align: center blocks: - id: delete_flow_avatar_1 type: Avatar layout: flex: 0 0 auto properties: icon: user size: 32 color: "#1677ff" - id: delete_flow_name_1 type: Paragraph layout: flex: 1 1 0 properties: content: Sarah Chen - Product Manager - id: delete_flow_remove_btn_1 type: Button layout: flex: 0 0 auto properties: title: Remove icon: delete color: danger variant: text size: small events: onClick: - id: delete_flow_set_member type: SetState params: member_to_remove: Sarah Chen - id: delete_flow_open_confirm type: CallMethod params: blockId: delete_flow_confirm method: open - id: delete_flow_divider type: Divider - id: delete_flow_list_item_2 type: Box layout: gap: 12 align: center blocks: - id: delete_flow_avatar_2 type: Avatar layout: flex: 0 0 auto properties: icon: user size: 32 color: "#52c41a" - id: delete_flow_name_2 type: Paragraph layout: flex: 1 1 0 properties: content: James Park - Engineering Lead - id: delete_flow_remove_btn_2 type: Button layout: flex: 0 0 auto properties: title: Remove icon: delete color: danger variant: text size: small events: onClick: - id: delete_flow_set_member_2 type: SetState params: member_to_remove: James Park - id: delete_flow_open_confirm_2 type: CallMethod params: blockId: delete_flow_confirm method: open - id: delete_flow_confirm type: ConfirmModal properties: title: Remove Team Member status: error icon: name: UserMinus color: "#ff4d4f" okText: Remove okButton: danger: true cancelText: Keep Member content: _string.concat: - "Are you sure you want to remove " - _state: member_to_remove - " from the team? They will lose access to all project resources." events: onOk: - id: delete_flow_ok_msg type: DisplayMessage params: content: _string.concat: - _state: member_to_remove - " has been removed from the team." status: success ``` Please verify your ticket details before submitting. You will receive a confirmation email once the ticket is created. ```yaml - id: form_submit_card type: Card properties: title: Submit Support Ticket blocks: - id: form_submit_subject type: TextInput properties: title: Subject placeholder: Brief description of the issue - id: form_submit_priority type: Selector properties: title: Priority options: - label: Low value: low - label: Medium value: medium - label: High value: high - label: Critical value: critical - id: form_submit_description type: TextArea properties: title: Description placeholder: Describe your issue in detail... rows: 4 - id: form_submit_btn type: Button layout: flex: 0 0 auto properties: title: Submit Ticket icon: send color: primary variant: solid events: onClick: - id: form_submit_open_confirm type: CallMethod params: blockId: form_submit_confirm method: open - id: form_submit_confirm type: ConfirmModal properties: title: Submit Support Ticket? content: Please verify your ticket details before submitting. You will receive a confirmation email once the ticket is created. status: confirm okText: Submit cancelText: Review Again centered: true width: 480 icon: check-circle events: onOk: - id: form_submit_ok_msg type: DisplayMessage params: content: Support ticket submitted successfully. Check your email for confirmation. status: success - id: form_submit_ok_reset type: SetState params: form_submit_subject: null form_submit_priority: null form_submit_description: null onCancel: - id: form_submit_cancel_msg type: DisplayMessage params: content: Take your time to review the details. status: info ``` You have unsaved changes that will be lost if you leave this page. Do you want to save your changes before leaving? ```yaml - id: nav_guard_card type: Card properties: title: Edit Profile blocks: - id: nav_guard_name type: TextInput properties: title: Display Name placeholder: Enter your display name - id: nav_guard_email type: TextInput properties: title: Email Address placeholder: name@example.com - id: nav_guard_actions type: Box layout: gap: 8 justify: end blocks: - id: nav_guard_save_btn type: Button layout: flex: 0 0 auto properties: title: Save Changes color: primary variant: solid events: onClick: - id: nav_guard_save_msg type: DisplayMessage params: content: Profile saved successfully. status: success - id: nav_guard_leave_btn type: Button layout: flex: 0 0 auto properties: title: Leave Page color: default variant: outlined events: onClick: - id: nav_guard_open_confirm type: CallMethod params: blockId: nav_guard_confirm method: open - id: nav_guard_confirm type: ConfirmModal properties: title: Unsaved Changes content: You have unsaved changes that will be lost if you leave this page. Do you want to save your changes before leaving? status: warning icon: warning okText: Save & Leave cancelText: Discard Changes closable: true events: onOk: - id: nav_guard_ok_msg type: DisplayMessage params: content: Changes saved. Navigating away... status: success - id: nav_guard_ok_link type: Link params: home: true onCancel: - id: nav_guard_cancel_link type: Link params: home: true ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `title` | string | - | Modal title - supports html. | | `centered` | boolean | `false` | Centered Modal. | | `closable` | boolean \| object | `false` | Whether a close (x) button is visible on top right of the confirm dialog or not. Set `{ disabled: true }` to show the button disabled. | | `closable.disabled` | boolean | `false` | Show the close button, but disabled. | | `content` | string | - | Modal content. Overridden by the "content" content area - supports html. | | `icon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to customize modal icon. | | `icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `icon.color` | string | - | Icon color. | | `icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `icon.rotate` | number | - | Number of degrees to rotate the icon. | | `icon.spin` | boolean | - | Continuously spin the icon with animation. | | `icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `focusable` | object | - | Focus management of the dialog. | | `focusable.trap` | boolean | `true` | Keep keyboard focus inside the dialog while it is open. | | `focusable.focusTriggerAfterClose` | boolean | `true` | Return focus to the element that opened the dialog after it closes. | | `focusable.autoFocusButton` | string \| null | `"ok"` | The button that receives focus when the confirm modal opens. Set to null to focus neither. Enum: `ok`, `cancel`, `null`. | | `keyboard` | boolean | `true` | Whether pressing Esc closes the confirm modal. | | `mask` | boolean \| object | `true` | Whether to show the mask. Set an object to configure the mask with `enabled`, `blur` and `closable`. | | `mask.enabled` | boolean | `true` | Whether to show the mask. | | `mask.blur` | boolean | `false` | Blur the page behind the mask. | | `mask.closable` | boolean | - | Whether clicking the mask closes the dialog. Takes precedence over `maskClosable`. | | `maskClosable` | boolean | `false` | Whether to close the modal dialog when the mask (area outside the modal) is clicked. `mask.closable` takes precedence. | | `scrollLock` | boolean | `true` | Whether to lock page scrolling while the confirm modal is open. | | `okText` | string | - | Text of the Ok button. When unset, antd uses the localized default from ConfigProvider locale. | | `cancelText` | string | - | Text of the Cancel button. When unset, antd uses the localized default from ConfigProvider locale. | | `okButton` | object | - | Ok button properties. | | `okButton.type` | string | - | The button type. Enum: `default`, `primary`, `dashed`, `text`, `link`. | | `okButton.danger` | boolean | `false` | Set the danger status of the button. | | `okButton.disabled` | boolean | `false` | Disable the button. | | `okButton.icon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to show in the button. | | `okButton.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `okButton.icon.color` | string | - | Icon color. | | `okButton.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `okButton.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `okButton.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `okButton.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `okButton.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `okButton.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `okButton.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `okButton.iconPlacement` | string | `"start"` | Place the icon before (`start`) or after (`end`) the button text. Enum: `start`, `end`. | | `cancelButton` | object | - | Cancel button properties. | | `cancelButton.type` | string | - | The button type. Enum: `default`, `primary`, `dashed`, `text`, `link`. | | `cancelButton.danger` | boolean | `false` | Set the danger status of the button. | | `cancelButton.disabled` | boolean | `false` | Disable the button. | | `cancelButton.icon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to show in the button. | | `cancelButton.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `cancelButton.icon.color` | string | - | Icon color. | | `cancelButton.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `cancelButton.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `cancelButton.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `cancelButton.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `cancelButton.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `cancelButton.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `cancelButton.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `cancelButton.iconPlacement` | string | `"start"` | Place the icon before (`start`) or after (`end`) the button text. Enum: `start`, `end`. | | `width` | number \| string | `416` | Width of the modal dialog. | | `zIndex` | number | `1000` | The z-index of the Modal. | | `status` | string | `"confirm"` | Modal status type. Enum: `success`, `error`, `info`, `warning`, `confirm`. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design modal tokens](https://ant.design/components/modal#design-token). | | `theme.headerBg` | string | - | Background color of the modal header. | | `theme.titleLineHeight` | number | `1.5` | Line height of the modal title. | | `theme.titleFontSize` | number | `16` | Font size of the modal title. | | `theme.titleColor` | string | - | Color of the modal title text. | | `theme.contentBg` | string | - | Background color of the modal content. | | `theme.footerBg` | string | `"transparent"` | Background color of the modal footer. | | `theme.contentPadding` | number \| string | - | Padding of the content area. | | `theme.confirmBodyPadding` | number \| string | - | Padding of the confirm modal body. | | `theme.confirmIconMarginInlineEnd` | number \| string | - | Inline end margin of the confirm modal icon. | | `theme.confirmBtnsMarginTop` | number \| string | - | Margin top of the confirm modal buttons. | | `theme.borderRadiusLG` | number | `8` | Border radius of the modal. | | `theme.colorBgMask` | string | `"rgba(0, 0, 0, 0.45)"` | Background color of the modal mask. | | Event | Event Data | Description | | --- | --- | --- | | `onOk` | \- | Trigger actions when Ok button is clicked. | | `onOpen` | \- | Trigger actions when confirm modal is opened. | | `onCancel` | \- | Trigger actions when Cancel button is clicked. | | `onClose` | \- | Trigger actions after the confirm modal has closed, once the onOk or onCancel actions are completed. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The ConfirmModal element. | | `/body` | The ConfirmModal body. | | `/cancelIcon` | The cancel button icon in the ConfirmModal. | | `/icon` | The icon in the ConfirmModal. | | `/okIcon` | The ok button icon in the ConfirmModal. | | Slot | Description | | --- | --- | | `content` | Content blocks in the confirm modal body. | --- url: https://docs.lowdefy.com/md/container-blocks/content.md --- # Content Main content layout section. The Content block is the main content area within a Layout. It maps to Ant Design's Layout.Content component and holds the primary page content. Content without any special configuration. It fills the available space within its parent Layout. ```yaml - id: content_basic_layout type: Layout blocks: - id: content_basic type: Content style: padding: 24px background: var(--ant-color-bg-container) minHeight: 200px blocks: - id: content_basic_text type: Paragraph properties: content: > The Content block is the main content area within a Layout. It maps to Ant Design's Layout.Content component and holds the primary page content. - id: content_empty_layout type: Layout blocks: - id: content_empty type: Content style: padding: 24px background: var(--ant-color-bg-layout) minHeight: 120px display: flex alignItems: center justifyContent: center blocks: - id: content_empty_text type: Paragraph properties: content: > Content without any special configuration. It fills the available space within its parent Layout. ``` My Application Welcome Back Content works within a Layout alongside Header and Footer blocks to form a complete page structure. It automatically fills the available vertical space between other layout sections. © 2026 My Application ```yaml - id: content_full_page_layout type: Layout style: minHeight: 350px blocks: - id: content_full_page_header type: Header style: paddingInline: 24px borderBottom: 1px solid var(--ant-color-border) blocks: - id: content_full_page_header_title type: Title properties: content: My Application level: 4 style: .element: margin: 0 - id: content_full_page_main type: Content style: padding: 24px 48px background: var(--ant-color-bg-container) blocks: - id: content_full_page_title type: Title properties: content: Welcome Back level: 3 - id: content_full_page_desc type: Paragraph properties: content: > Content works within a Layout alongside Header and Footer blocks to form a complete page structure. It automatically fills the available vertical space between other layout sections. - id: content_full_page_footer type: Footer style: textAlign: center blocks: - id: content_full_page_footer_text type: Paragraph properties: content: © 2026 My Application ``` App with Sidebar Dashboard When used alongside a Sider block, Content fills the remaining horizontal space. This is the standard layout pattern for admin dashboards and applications with navigation sidebars. ```yaml - id: content_sider_outer type: Layout style: minHeight: 350px blocks: - id: content_sider_header type: Header style: paddingInline: 24px borderBottom: 1px solid var(--ant-color-border) blocks: - id: content_sider_header_title type: Title properties: content: App with Sidebar level: 4 style: .element: margin: 0 - id: content_sider_inner type: Layout properties: hasSider: true blocks: - id: content_sider_nav type: Sider layout: flex: 0 0 auto properties: width: 200 style: background: var(--ant-color-bg-container) paddingTop: 16px blocks: - id: content_sider_menu type: Menu properties: mode: inline links: - id: cs_dashboard type: MenuLink properties: title: Dashboard icon: Gauge - id: cs_users type: MenuLink properties: title: Users icon: user - id: cs_settings type: MenuLink properties: title: Settings icon: settings - id: content_sider_main type: Content layout: flex: 1 1 auto style: padding: 24px background: var(--ant-color-bg-container) margin: 0 16px blocks: - id: content_sider_main_title type: Title properties: content: Dashboard level: 3 - id: content_sider_main_text type: Paragraph properties: content: > When used alongside a Sider block, Content fills the remaining horizontal space. This is the standard layout pattern for admin dashboards and applications with navigation sidebars. ``` ```yaml - id: content_theme_layout type: Layout properties: theme: bodyBg: "#e6f4ff" blocks: - id: content_theme_custom type: Content style: padding: 24px minHeight: 150px blocks: - id: content_theme_text type: Html properties: html: The parent Layout theme sets the background behind the Content area with the bodyBg token. - id: content_theme_warm_layout type: Layout properties: theme: bodyBg: "#fff7e6" blocks: - id: content_theme_warm type: Content style: padding: 24px minHeight: 150px blocks: - id: content_theme_warm_text type: Html properties: html: A warm-toned content area from the same Layout token. ``` Rounded Content with Tailwind Use the element CSS key to apply Tailwind classes directly to the Content element. Content has one CSS key: element, which targets the Layout.Content wrapper. Gradient Background Tailwind gradient and shadow utilities applied via the element CSS key create a visually distinct content area without needing custom CSS. Accent Border Content A left accent border highlights the content area. Combine Tailwind border utilities with background colors for status-aware content regions. ```yaml - id: content_css_rounded_layout type: Layout blocks: - id: content_css_rounded type: Content class: .element: bg-bg-layout rounded-xl p-6 m-4 style: minHeight: 150px blocks: - id: content_css_rounded_title type: Title properties: content: Rounded Content with Tailwind level: 4 - id: content_css_rounded_text type: Paragraph properties: content: "Use the element CSS key to apply Tailwind classes directly to the Content element. Content has one CSS key: element, which targets the Layout.Content wrapper." - id: content_css_gradient_layout type: Layout blocks: - id: content_css_gradient type: Content class: .element: bg-gradient-to-br from-primary/10 to-primary/5 p-8 m-4 rounded-lg shadow-inner style: minHeight: 150px blocks: - id: content_css_gradient_title type: Title properties: content: Gradient Background level: 4 - id: content_css_gradient_text type: Paragraph properties: content: > Tailwind gradient and shadow utilities applied via the element CSS key create a visually distinct content area without needing custom CSS. - id: content_css_border_layout type: Layout blocks: - id: content_css_border type: Content class: .element: border-l-4 border-l-green-500 bg-bg-layout p-6 m-4 style: minHeight: 150px blocks: - id: content_css_border_title type: Title properties: content: Accent Border Content level: 4 - id: content_css_border_text type: Paragraph properties: content: > A left accent border highlights the content area. Combine Tailwind border utilities with background colors for status-aware content regions. ``` Analytics Dashboard User John updated the project settings — 2 minutes ago Deployment to production completed — 15 minutes ago New team member Sarah joined — 1 hour ago ```yaml - id: content_dashboard_outer type: Layout style: minHeight: 450px blocks: - id: content_dashboard_header type: Header style: justifyContent: space-between borderBottom: 1px solid var(--ant-color-border) paddingInline: 24px blocks: - id: content_dashboard_header_title type: Title layout: flex: 0 0 auto properties: content: Analytics Dashboard level: 4 style: .element: margin: 0 - id: content_dashboard_header_btn type: Button layout: flex: 0 0 auto properties: title: Export Report icon: download color: default variant: outlined size: small - id: content_dashboard_inner type: Layout properties: hasSider: true blocks: - id: content_dashboard_sider type: Sider layout: flex: 0 0 auto properties: width: 200 style: background: var(--ant-color-bg-container) paddingTop: 16px blocks: - id: content_dashboard_menu type: Menu properties: mode: inline links: - id: cd_overview type: MenuLink properties: title: Overview icon: home - id: cd_analytics type: MenuLink properties: title: Analytics icon: chart - id: cd_reports type: MenuLink properties: title: Reports icon: document - id: cd_team type: MenuLink properties: title: Team icon: users - id: content_dashboard_main type: Content layout: flex: 1 1 auto style: padding: 24px background: var(--ant-color-bg-layout) blocks: - id: content_dashboard_stats_row type: Box layout: gap: 16 blocks: - id: content_dashboard_stat1 type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: content_dashboard_stat1_val type: Statistic properties: title: Total Users value: 12480 - id: content_dashboard_stat2 type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: content_dashboard_stat2_val type: Statistic properties: title: Revenue value: 48650 prefix: $ - id: content_dashboard_stat3 type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: content_dashboard_stat3_val type: Statistic properties: title: Conversion value: 3.2 suffix: "%" - id: content_dashboard_main_card type: Card properties: title: Recent Activity blocks: - id: content_dashboard_activity_1 type: Paragraph properties: content: User John updated the project settings — 2 minutes ago - id: content_dashboard_activity_2 type: Paragraph properties: content: Deployment to production completed — 15 minutes ago - id: content_dashboard_activity_3 type: Paragraph properties: content: New team member Sarah joined — 1 hour ago ``` Account Settings Profile Information Update your personal details and preferences below. ```yaml - id: content_settings_outer type: Layout style: minHeight: 400px blocks: - id: content_settings_header type: Header style: paddingInline: 24px borderBottom: 1px solid var(--ant-color-border) blocks: - id: content_settings_header_title type: Title properties: content: Account Settings level: 4 style: .element: margin: 0 - id: content_settings_main type: Content style: padding: 32px 48px background: var(--ant-color-bg-container) blocks: - id: content_settings_title type: Title properties: content: Profile Information level: 3 - id: content_settings_desc type: Paragraph properties: content: Update your personal details and preferences below. - id: content_settings_form_name type: TextInput properties: title: Display Name placeholder: Enter your display name - id: content_settings_form_email type: TextInput properties: title: Email Address placeholder: you@example.com - id: content_settings_form_bio type: TextArea properties: title: Bio placeholder: Tell us about yourself rows: 3 - id: content_settings_divider type: Divider - id: content_settings_actions type: Box layout: gap: 12 justify: end blocks: - id: content_settings_cancel_btn type: Button layout: flex: 0 0 auto properties: title: Cancel color: default variant: outlined - id: content_settings_save_btn type: Button layout: flex: 0 0 auto properties: title: Save Changes color: primary variant: solid icon: save events: onClick: - id: content_settings_save_msg type: DisplayMessage params: content: Settings saved successfully! status: success ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `theme` | object | - | Not applied: antd scopes Layout design tokens to the Layout element, so set tokens such as headerHeight or footerBg on the parent Layout block theme. | No events defined. | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Content element. | | Slot | Description | | --- | --- | | `content` | Child blocks in the content area. | --- url: https://docs.lowdefy.com/md/container-blocks/descriptions.md --- # Descriptions Description list with configurable layout and columns. ```yaml - id: basic_array_items type: Descriptions properties: title: User Profile items: - label: Full Name value: Sarah Johnson - label: Email value: sarah.johnson@example.com - label: Phone value: +1 (555) 123-4567 - label: Location value: San Francisco, CA - label: Role value: Senior Developer - label: Department value: Engineering - id: basic_object_items type: Descriptions properties: title: Object Format items: Name: Alex Chen Email: alex.chen@example.com Phone: +1 (555) 987-6543 Location: New York, NY Role: Product Manager Team: Growth ``` ```yaml - id: bordered_default type: Descriptions properties: title: Borderless (Default) items: - label: Hostname value: prod-web-01 - label: IP Address value: 192.168.1.100 - label: OS value: Ubuntu 22.04 LTS - label: CPU value: 8 cores - label: Memory value: 32 GB - label: Storage value: 500 GB SSD - id: bordered_true type: Descriptions properties: title: Bordered bordered: true items: - label: Hostname value: prod-web-02 - label: IP Address value: 192.168.1.101 - label: OS value: Debian 12 - label: CPU value: 16 cores - label: Memory value: 64 GB - label: Storage value: 1 TB NVMe ``` ```yaml - id: size_default type: Descriptions properties: title: Default Size size: default bordered: true items: - label: Product value: Widget Pro - label: SKU value: WGT-PRO-001 - label: Price value: $49.99 - label: Category value: Electronics - label: Weight value: 0.5 kg - label: In Stock value: 1,250 units - id: size_medium type: Descriptions properties: title: Medium Size size: medium bordered: true items: - label: Product value: Widget Plus - label: SKU value: WGT-PLS-003 - label: Price value: $34.99 - label: Category value: Electronics - label: Weight value: 0.4 kg - label: In Stock value: 2,015 units - id: size_small type: Descriptions properties: title: Small Size size: small bordered: true items: - label: Product value: Widget Lite - label: SKU value: WGT-LIT-002 - label: Price value: $24.99 - label: Category value: Electronics - label: Weight value: 0.3 kg - label: In Stock value: 3,480 units ``` ```yaml - id: vertical_borderless type: Descriptions properties: title: Order Summary layout: vertical items: - label: Order Number value: ORD-2026-0042 - label: Date value: March 10, 2026 - label: Status value: Shipped - label: Total value: $129.97 - id: vertical_bordered type: Descriptions properties: title: Invoice Details bordered: true layout: vertical items: - label: Invoice Number value: INV-2026-003 - label: Date Issued value: March 1, 2026 - label: Due Date value: March 31, 2026 - label: Amount value: $2,450.00 - label: Status value: Pending - label: Payment Method value: Wire Transfer ``` ```yaml - id: columns_1 type: Descriptions properties: title: Single Column column: 1 bordered: true items: - label: Full Name value: Alex Chen - label: Email value: alex.chen@example.com - label: Phone value: +1 (555) 987-6543 - label: Address value: 123 Main Street, New York, NY 10001 - id: columns_2 type: Descriptions properties: title: Two Columns column: 2 bordered: true items: - label: First Name value: Maria - label: Last Name value: Garcia - label: Department value: Marketing - label: Title value: Director - label: Start Date value: January 15, 2024 - label: Office value: Building A, Floor 3 - id: columns_4 type: Descriptions properties: title: Four Columns column: 4 bordered: true items: - label: Q1 value: $125,000 - label: Q2 value: $142,500 - label: Q3 value: $138,900 - label: Q4 value: $167,200 - label: YoY Growth value: +12.4% - label: Market Share value: 23.1% - label: Customers value: 4,582 - label: NPS Score value: 72 ``` ```yaml - id: responsive_columns type: Descriptions properties: title: Employee Record bordered: true column: xs: 1 sm: 2 md: 3 lg: 3 xl: 4 items: - label: Employee ID value: EMP-1042 - label: Full Name value: Jordan Rivera - label: Department value: Engineering - label: Title value: Staff Engineer - label: Manager value: Pat Williams - label: Location value: Austin, TX - label: Start Date value: April 2022 - label: Level value: L5 ``` ```yaml - id: spanning_basic type: Descriptions properties: title: Support Ticket bordered: true items: - label: Ticket ID value: TICKET-7891 - label: Priority value: High - label: Created value: March 9, 2026 - label: Description value: Users are unable to log in after the latest deployment. The authentication service returns a 503 error when attempting to validate credentials against the LDAP directory. span: 3 - id: spanning_mixed type: Descriptions properties: title: Project Overview bordered: true column: 3 items: - label: Project Name value: Phoenix Migration - label: Status value: In Progress - label: Priority value: Critical - label: Lead value: Priya Patel - label: Team Size value: 12 - label: Sprint value: Sprint 14 - label: Objective value: Migrate all legacy microservices from the monolithic architecture to a cloud-native Kubernetes deployment with zero downtime and full backwards compatibility. span: 2 - label: Deadline value: June 30, 2026 - id: spanning_full_width type: Descriptions properties: title: API Error Log bordered: true column: 3 items: - label: Timestamp value: 2026-03-10T14:23:07Z - label: Endpoint value: /api/v2/users/auth - label: Status Code value: 503 - label: Stack Trace value: "Error: ECONNREFUSED 10.0.1.45:5432 - Connection refused at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1141:16)" span: 3 - label: Resolution value: Database connection pool exhausted. Increased max_connections from 100 to 250 and implemented connection retry logic with exponential backoff. span: 3 - id: spanning_filled type: Descriptions properties: title: Shipment bordered: true column: 3 items: - label: Carrier value: Road freight - label: Tracking value: Use span filled to take the rest of the row, whatever the column count. span: filled - label: Weight value: 12 kg - label: Pieces value: 3 - label: Insured value: Yes ``` ```yaml - id: colon_true type: Descriptions properties: title: With Colons (Default) colon: true items: - label: Name value: Sarah Johnson - label: Role value: Engineer - label: Team value: Platform - id: colon_false type: Descriptions properties: title: Without Colons colon: false items: - label: Name value: Sarah Johnson - label: Role value: Engineer - label: Team value: Platform ``` ```yaml - id: no_title_borderless type: Descriptions properties: items: - label: Browser value: Chrome 122 - label: Operating System value: macOS 15.3 - label: Screen Resolution value: 2560 x 1440 - label: Language value: English (US) - label: Timezone value: America/Los_Angeles - label: User Agent value: Mozilla/5.0 - id: no_title_bordered type: Descriptions properties: bordered: true items: - label: Protocol value: HTTPS - label: Method value: POST - label: Content-Type value: application/json - label: Response Time value: 142ms - label: Cache value: MISS - label: CDN Node value: us-west-2a ``` ```yaml - id: html_content type: Descriptions properties: title: Release Notes bordered: true items: - label: Version value: 'v3.2.0' - label: Release Date value: March 10, 2026 - label: Status value: 'Stable' - label: Changes value: Added OAuth2 support, fixed memory leak in worker pool, improved query performance by 40% span: 3 ``` ```yaml - id: desc_with_extra type: Descriptions properties: title: Account Details bordered: true items: - label: Account ID value: ACC-2026-0891 - label: Plan value: Enterprise - label: Status value: Active - label: Created value: January 12, 2024 - label: Billing Cycle value: Annual - label: Next Invoice value: January 12, 2027 slots: extra: blocks: - id: desc_extra_button type: Button layout: flex: 0 0 auto properties: title: Edit color: primary variant: link size: small ``` ```yaml - id: user_account type: Descriptions properties: title: Account Information bordered: true column: 2 items: - label: Username value: sjohnson - label: Display Name value: Sarah Johnson - label: Email value: sarah.johnson@acme.com - label: Phone value: +1 (555) 123-4567 - label: Role value: Administrator - label: Last Login value: March 10, 2026 at 9:14 AM - label: Two-Factor Auth value: Enabled - label: API Key value: sk-****-****-7f3a - label: Permissions value: Read, Write, Delete, Manage Users, Audit Logs span: 2 ``` ```yaml - id: order_details type: Descriptions properties: title: "Order #ORD-2026-11583" bordered: true items: - label: Customer value: Acme Corporation - label: Date Placed value: March 8, 2026 - label: Payment Method value: Visa ending in 4242 - label: Subtotal value: $1,249.00 - label: Tax (8.25%) value: $103.04 - label: Shipping value: $0.00 (Free) - label: Total value: $1,352.04 span: 3 - id: order_shipping type: Descriptions properties: title: Shipping Information bordered: true column: 2 items: - label: Carrier value: FedEx Ground - label: Tracking Number value: 7489 2305 1847 - label: Ship Date value: March 9, 2026 - label: Est. Delivery value: March 14, 2026 - label: Ship From value: Warehouse B, Dallas, TX - label: Ship To value: 456 Oak Avenue, Portland, OR 97201 ``` ```yaml - id: system_info type: Descriptions properties: title: System Health Dashboard bordered: true size: small items: - label: Service value: api-gateway - label: Status value: 'Healthy' - label: Uptime value: 99.97% - label: CPU Usage value: 34% - label: Memory value: 12.4 / 32 GB - label: Disk value: 187 / 500 GB - label: Active Connections value: 2,847 - label: Requests/sec value: 1,243 - label: Avg Latency value: 23ms - label: Error Rate value: '0.02%' - label: Last Deploy value: March 10, 2026 08:30 UTC - label: Version value: v2.14.3-rc1 ``` ```yaml - id: item_label_styles type: Descriptions properties: title: Styled Labels and Values bordered: true items: - label: Revenue value: $487,200 labelStyle: fontWeight: bold contentStyle: color: "#52c41a" fontWeight: bold - label: Expenses value: $312,100 labelStyle: fontWeight: bold contentStyle: color: "#ff4d4f" fontWeight: bold - label: Net Profit value: $175,100 labelStyle: fontWeight: bold contentStyle: color: "#52c41a" fontWeight: bold fontSize: 16 - id: item_inline_styles type: Descriptions properties: title: Custom Item Backgrounds bordered: true items: - label: Critical value: 2 issues - label: Warning value: 5 issues - label: Info value: 12 issues ``` ```yaml - id: css_element type: Descriptions properties: title: Element CSS Key bordered: true items: - label: Name value: Jordan Rivera - label: Email value: jordan@example.com - label: Phone value: +1 (555) 444-7890 style: .element: border: "2px solid #1677ff" borderRadius: 8px padding: 4px - id: css_label_content type: Descriptions properties: title: Label and Content CSS Keys bordered: true items: - label: Service value: Production API - label: Region value: us-east-1 - label: Tier value: Premium style: .label: fontWeight: bold color: "#1677ff" .content: fontStyle: italic ``` ```yaml - id: tailwind_shadow type: Descriptions class: shadow-md rounded-lg overflow-hidden properties: title: Card-Style Descriptions bordered: true items: - label: Company value: Acme Inc. - label: Industry value: Technology - label: Founded value: 2018 - label: Employees value: 250 - label: Headquarters value: San Francisco, CA - label: Revenue value: $48M ARR - id: tailwind_dark_bg type: Box class: bg-bg-layout p-4 rounded-lg blocks: - id: tailwind_on_dark type: Descriptions properties: title: 'Dark Background' colon: false items: - label: 'Status' value: 'Online' - label: 'Latency' value: '12ms' - label: 'Region' value: 'us-west-2' ``` ```yaml - id: theme_custom_colors type: Descriptions properties: title: Custom Label Colors bordered: true theme: labelColor: "#0958d9" contentColor: "#434343" items: - label: Environment value: Production - label: Region value: us-east-1 - label: Availability Zone value: us-east-1a - label: Instance Type value: m5.xlarge - label: Auto Scaling value: Enabled - label: Load Balancer value: ALB-prod-01 - id: theme_warm_palette type: Descriptions properties: title: Warm Color Palette bordered: true theme: labelColor: "#ad4e00" titleColor: "#ad4e00" contentColor: "#613400" items: - label: Cafe value: The Daily Grind - label: Location value: 42 Roast Avenue - label: Hours value: 7 AM - 9 PM - label: Specialty value: Single-origin pour over - label: Rating value: 4.8 / 5.0 - label: Price Range value: $$ - id: theme_compact_spacing type: Descriptions properties: title: Compact Spacing bordered: true size: small theme: itemPaddingBottom: 4 itemPaddingEnd: 8 titleMarginBottom: 8 items: - label: CPU value: 24% - label: RAM value: 8.2 GB - label: Disk value: 45% - label: Network In value: 12 Mbps - label: Network Out value: 8 Mbps - label: Processes value: 312 - id: theme_green_palette type: Descriptions properties: title: Success Theme bordered: true theme: labelColor: "#135200" titleColor: "#135200" contentColor: "#237804" items: - label: Test Suite value: Integration Tests - label: Passed value: 247 / 247 - label: Duration value: 4m 32s - label: Coverage value: 94.2% - label: Branch value: main - label: Commit value: a3f7b21 - id: theme_wide_colons type: Descriptions properties: title: Wide Colon Spacing theme: colonMarginLeft: 4 colonMarginRight: 16 items: - label: First Name value: Taylor - label: Last Name value: Brooks - label: Department value: Design ``` ```yaml - id: combined_full_profile type: Descriptions class: shadow-sm rounded-lg overflow-hidden properties: title: Employee Profile bordered: true column: 2 size: small theme: labelColor: "#1d39c4" items: - label: Employee ID value: EMP-4521 - label: Full Name value: Priya Patel - label: Department value: Engineering - label: Title value: Principal Engineer - label: Email value: priya.patel@acme.com - label: Phone value: +1 (555) 867-5309 - label: Office value: Building C, Floor 7 - label: Manager value: David Kim - label: Bio value: 15+ years of experience in distributed systems and cloud architecture. Led the migration of core services to Kubernetes, reducing infrastructure costs by 40%. span: 2 - id: combined_invoice type: Descriptions class: shadow-sm rounded-lg overflow-hidden properties: title: "Invoice #INV-2026-0847" bordered: true layout: vertical column: 4 size: small items: - label: Billed To value: Acme Corporation - label: Invoice Date value: March 1, 2026 - label: Due Date value: March 31, 2026 - label: Status value: 'Pending' - label: Professional Services (160 hrs @ $150/hr) value: $24,000.00 span: 2 - label: Cloud Infrastructure value: $3,200.00 - label: Support Plan value: $1,800.00 - label: Subtotal value: $29,000.00 - label: Tax (8%) value: $2,320.00 - label: Total Due value: '$31,320.00' span: 2 ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `bordered` | boolean | `false` | Render items in a table. | | `colon` | boolean | `true` | Include a colon in item labels. | | `column` | number \| object | `3` | The number of description items in a row. | | `column.xs` | integer | - | The number of description items in a row for 'xs' media size. | | `column.sm` | integer | - | The number of description items in a row for 'sm' media size. | | `column.md` | integer | - | The number of description items in a row for 'md' media size. | | `column.lg` | integer | - | The number of description items in a row for 'lg' media size. | | `column.xl` | integer | - | The number of description items in a row for 'xl' media size. | | `column.xxl` | integer | - | The number of description items in a row for 'xxl' media size. | | `column.xxxl` | integer | - | The number of description items in a row for 'xxxl' media size. | | `itemOptions` | array | - | | | `itemOptions.$.key` | string | - | Item key to which these settings should apply. | | `itemOptions.$.span` | number \| string \| object | - | Item span for this key, or 'filled' to fill the rest of the row. Can also be a function that receives item and index. | | `itemOptions.$.style` | object | - | Item css style for this key. Can also be a function that receives item and index. | | `itemOptions.$.transformLabel` | object | - | Function to transform item key or label. Function receives arguments label, item and index. | | `itemOptions.$.transformValue` | object | - | Function to transform item value. Function receives arguments value, item and index. | | `items` | array \| object | - | List of items to display | | `items.$.contentStyle` | object | - | Customize content style. | | `items.$.labelStyle` | object | - | Customize label style. | | `items.$.label` | string | - | Item label - supports html. | | `items.$.value` | string \| number | - | Value of item - supports html. | | `items.$.span` | integer \| string \| object | - | Number of columns for this item to span. Set to 'filled' to fill the rest of the row, or an object of breakpoint spans like `{ xs: 1, md: 2 }`. | | `items.$.style` | object | - | Css style object to applied to item. | | `layout` | string | `"horizontal"` | Put values next to or below their labels. Enum: `horizontal`, `vertical`. | | `size` | string | `"default"` | Size of the block. `default` is the largest size. Enum: `default`, `medium`, `small`. | | `title` | string | - | The title of the description block, placed at the top - supports html. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design descriptions tokens](https://ant.design/components/descriptions#design-token). | | `theme.labelBg` | string | `"rgba(0, 0, 0, 0.02)"` | Background color for labels in bordered mode. | | `theme.labelColor` | string | `"rgba(0, 0, 0, 0.45)"` | Text color for labels. | | `theme.titleColor` | string | `"rgba(0, 0, 0, 0.88)"` | Color for the component title. | | `theme.titleMarginBottom` | number | `20` | Bottom margin spacing for the title. | | `theme.itemPaddingBottom` | number | `16` | Bottom padding for description items. | | `theme.itemPaddingEnd` | number | `16` | End (right in LTR) padding for description items. | | `theme.colonMarginRight` | number | `8` | Right margin for colons after labels. | | `theme.colonMarginLeft` | number | `2` | Left margin for colons after labels. | | `theme.contentColor` | string | `"rgba(0, 0, 0, 0.88)"` | Text color for content values. | | `theme.extraColor` | string | `"rgba(0, 0, 0, 0.88)"` | Color for extra content in the header. | No events defined. | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Descriptions element. | | `/header` | The header row with the title and extra content. | | `/title` | The Descriptions title. | | `/content` | The Descriptions content. | | `/label` | The Descriptions label. | | Slot | Description | | --- | --- | | `extra` | Extra content in the header. | --- url: https://docs.lowdefy.com/md/container-blocks/drawer.md --- # Drawer Panel that slides in from the edge of the screen. This is a basic drawer with a title and default settings. It opens from the right side with the default width of 378px. Click the X button or the mask area to close it. ```yaml - id: drawer_basic_trigger type: Button layout: flex: 0 0 auto properties: title: Open Drawer color: primary variant: solid events: onClick: - id: drawer_basic_open type: CallMethod params: blockId: drawer_basic method: setOpen args: - open: true - id: drawer_basic type: Drawer properties: title: Basic Drawer blocks: - id: drawer_basic_p1 type: Paragraph properties: content: This is a basic drawer with a title and default settings. It opens from the right side with the default width of 378px. - id: drawer_basic_p2 type: Paragraph properties: content: Click the X button or the mask area to close it. ``` This drawer slides in from the right side, which is the default placement. This drawer slides in from the left side. Useful for navigation panels. This drawer slides in from the top. Use the height property instead of width for top and bottom placements. This drawer slides in from the bottom. Commonly used for mobile action sheets or bottom panels. ```yaml - id: drawer_place_right_btn type: Button layout: flex: 0 0 auto properties: title: Right (default) color: primary variant: outlined events: onClick: - id: drawer_place_right_open type: CallMethod params: blockId: drawer_place_right method: setOpen args: - open: true - id: drawer_place_left_btn type: Button layout: flex: 0 0 auto properties: title: Left color: primary variant: outlined events: onClick: - id: drawer_place_left_open type: CallMethod params: blockId: drawer_place_left method: setOpen args: - open: true - id: drawer_place_top_btn type: Button layout: flex: 0 0 auto properties: title: Top color: primary variant: outlined events: onClick: - id: drawer_place_top_open type: CallMethod params: blockId: drawer_place_top method: setOpen args: - open: true - id: drawer_place_bottom_btn type: Button layout: flex: 0 0 auto properties: title: Bottom color: primary variant: outlined events: onClick: - id: drawer_place_bottom_open type: CallMethod params: blockId: drawer_place_bottom method: setOpen args: - open: true - id: drawer_place_right type: Drawer properties: title: Right Placement placement: right blocks: - id: drawer_place_right_content type: Paragraph properties: content: This drawer slides in from the right side, which is the default placement. - id: drawer_place_left type: Drawer properties: title: Left Placement placement: left blocks: - id: drawer_place_left_content type: Paragraph properties: content: This drawer slides in from the left side. Useful for navigation panels. - id: drawer_place_top type: Drawer properties: title: Top Placement placement: top height: 300 blocks: - id: drawer_place_top_content type: Paragraph properties: content: This drawer slides in from the top. Use the height property instead of width for top and bottom placements. - id: drawer_place_bottom type: Drawer properties: title: Bottom Placement placement: bottom height: 300 blocks: - id: drawer_place_bottom_content type: Paragraph properties: content: This drawer slides in from the bottom. Commonly used for mobile action sheets or bottom panels. ``` A narrow 200px drawer, suitable for simple menus or quick settings. A 500px wide drawer with more room for complex content like forms, tables, or detail views. Using a percentage width makes the drawer responsive. This drawer takes up 60% of the viewport width. ```yaml - id: drawer_narrow_btn type: Button layout: flex: 0 0 auto properties: title: Narrow (200px) color: primary variant: outlined size: small events: onClick: - id: drawer_narrow_open type: CallMethod params: blockId: drawer_narrow method: setOpen args: - open: true - id: drawer_wide_btn type: Button layout: flex: 0 0 auto properties: title: Wide (500px) color: primary variant: outlined size: small events: onClick: - id: drawer_wide_open type: CallMethod params: blockId: drawer_wide method: setOpen args: - open: true - id: drawer_pct_btn type: Button layout: flex: 0 0 auto properties: title: Percentage (60%) color: primary variant: outlined size: small events: onClick: - id: drawer_pct_open type: CallMethod params: blockId: drawer_pct method: setOpen args: - open: true - id: drawer_narrow type: Drawer properties: title: Narrow Drawer width: 200 blocks: - id: drawer_narrow_content type: Paragraph properties: content: A narrow 200px drawer, suitable for simple menus or quick settings. - id: drawer_wide type: Drawer properties: title: Wide Drawer width: 500 blocks: - id: drawer_wide_content type: Paragraph properties: content: A 500px wide drawer with more room for complex content like forms, tables, or detail views. - id: drawer_pct type: Drawer properties: title: 60% Width Drawer width: 60% blocks: - id: drawer_pct_content type: Paragraph properties: content: Using a percentage width makes the drawer responsive. This drawer takes up 60% of the viewport width. ``` ```yaml - id: drawer_padding_btn type: Button layout: flex: 0 0 auto properties: title: Open Drawer with padding none events: onClick: - id: drawer_padding_open type: CallMethod params: blockId: drawer_padding method: setOpen args: - open: true - id: drawer_padding type: Drawer properties: title: Recent invoices padding: none blocks: - id: drawer_padding_rows type: Html properties: html: >
INV-1042$1,280.00
INV-1041$640.00
INV-1040$2,115.50
INV-1039$96.00
``` The close button is visible in the top right corner. This is the default behavior. The close (x) button is hidden. Users can still close by clicking the mask or pressing Escape. ```yaml - id: drawer_closable_btn type: Button layout: flex: 0 0 auto properties: title: Closable (default) color: primary variant: outlined events: onClick: - id: drawer_closable_open type: CallMethod params: blockId: drawer_closable method: setOpen args: - open: true - id: drawer_not_closable_btn type: Button layout: flex: 0 0 auto properties: title: No Close Button color: danger variant: outlined events: onClick: - id: drawer_not_closable_open type: CallMethod params: blockId: drawer_not_closable method: setOpen args: - open: true - id: drawer_closable type: Drawer properties: title: Closable Drawer closable: true blocks: - id: drawer_closable_content type: Paragraph properties: content: The close button is visible in the top right corner. This is the default behavior. - id: drawer_not_closable type: Drawer properties: title: No Close Button closable: false blocks: - id: drawer_not_closable_content type: Paragraph properties: content: The close (x) button is hidden. Users can still close by clicking the mask or pressing Escape. - id: drawer_not_closable_close_btn type: Button properties: title: Close Drawer color: primary variant: solid events: onClick: - id: drawer_not_closable_close type: CallMethod params: blockId: drawer_not_closable method: setOpen args: - open: false ``` The mask is shown and clicking it closes the drawer. This is the default behavior. No mask overlay is shown behind the drawer. The page content remains fully visible and interactive. The mask is visible but clicking it does not close the drawer. Users must use the close button or Escape key. ```yaml - id: drawer_mask_default_btn type: Button layout: flex: 0 0 auto properties: title: With Mask (default) color: primary variant: outlined events: onClick: - id: drawer_mask_default_open type: CallMethod params: blockId: drawer_mask_default method: setOpen args: - open: true - id: drawer_no_mask_btn type: Button layout: flex: 0 0 auto properties: title: No Mask color: primary variant: outlined events: onClick: - id: drawer_no_mask_open type: CallMethod params: blockId: drawer_no_mask method: setOpen args: - open: true - id: drawer_mask_no_close_btn type: Button layout: flex: 0 0 auto properties: title: Mask Not Closable color: primary variant: outlined events: onClick: - id: drawer_mask_no_close_open type: CallMethod params: blockId: drawer_mask_no_close method: setOpen args: - open: true - id: drawer_mask_default type: Drawer properties: title: Default Mask mask: true maskClosable: true blocks: - id: drawer_mask_default_content type: Paragraph properties: content: The mask is shown and clicking it closes the drawer. This is the default behavior. - id: drawer_no_mask type: Drawer properties: title: No Mask mask: false blocks: - id: drawer_no_mask_content type: Paragraph properties: content: No mask overlay is shown behind the drawer. The page content remains fully visible and interactive. - id: drawer_mask_no_close type: Drawer properties: title: Mask Not Closable mask: true maskClosable: false blocks: - id: drawer_mask_no_close_content type: Paragraph properties: content: The mask is visible but clicking it does not close the drawer. Users must use the close button or Escape key. ``` Drag the left edge to resize the drawer. The onResizeEnd event receives the new size, so it can be saved as a user preference. ```yaml - id: drawer_resizable_trigger type: Button layout: flex: 0 0 auto properties: title: Open Resizable Drawer color: primary variant: outlined events: onClick: - id: drawer_resizable_open type: CallMethod params: blockId: drawer_resizable method: setOpen args: - open: true - id: drawer_resizable type: Drawer properties: title: Resizable Drawer size: 480 resizable: true maxSize: 900 closable: placement: end mask: blur: true events: onResizeEnd: - id: drawer_resizable_save_size type: SetState params: drawer_size: _event: size blocks: - id: drawer_resizable_p1 type: Paragraph properties: content: Drag the left edge to resize the drawer. The onResizeEnd event receives the new size, so it can be saved as a user preference. ``` Pressing the Escape key closes this drawer. This is the default behavior. Pressing Escape does not close this drawer. The user must click the close button or mask to dismiss. ```yaml - id: drawer_keyboard_on_btn type: Button layout: flex: 0 0 auto properties: title: Keyboard Enabled (default) color: primary variant: outlined events: onClick: - id: drawer_keyboard_on_open type: CallMethod params: blockId: drawer_keyboard_on method: setOpen args: - open: true - id: drawer_keyboard_off_btn type: Button layout: flex: 0 0 auto properties: title: Keyboard Disabled color: danger variant: outlined events: onClick: - id: drawer_keyboard_off_open type: CallMethod params: blockId: drawer_keyboard_off method: setOpen args: - open: true - id: drawer_keyboard_on type: Drawer properties: title: Press Escape to Close keyboard: true blocks: - id: drawer_keyboard_on_content type: Paragraph properties: content: Pressing the Escape key closes this drawer. This is the default behavior. - id: drawer_keyboard_off type: Drawer properties: title: Escape Disabled keyboard: false blocks: - id: drawer_keyboard_off_content type: Paragraph properties: content: Pressing Escape does not close this drawer. The user must click the close button or mask to dismiss. ``` This drawer uses a custom z-index of 2000 to ensure it appears above other overlays. The default is 1000. ```yaml - id: drawer_zindex_btn type: Button layout: flex: 0 0 auto properties: title: Open Drawer (z-index 2000) color: primary variant: outlined events: onClick: - id: drawer_zindex_open type: CallMethod params: blockId: drawer_zindex method: setOpen args: - open: true - id: drawer_zindex type: Drawer properties: title: High Z-Index Drawer zIndex: 2000 blocks: - id: drawer_zindex_content type: Paragraph properties: content: This drawer uses a custom z-index of 2000 to ensure it appears above other overlays. The default is 1000. ``` The extra slot renders content in the header area opposite the title. Commonly used for action buttons like Save or Cancel. ```yaml - id: drawer_extra_btn type: Button layout: flex: 0 0 auto properties: title: Open Drawer with Extra color: primary variant: solid events: onClick: - id: drawer_extra_open type: CallMethod params: blockId: drawer_extra method: setOpen args: - open: true - id: drawer_extra type: Drawer properties: title: Drawer with Extra slots: extra: blocks: - id: drawer_extra_actions_row type: Box layout: gap: 8 blocks: - id: drawer_extra_cancel_btn type: Button layout: flex: 0 0 auto properties: title: Cancel color: default variant: outlined size: small events: onClick: - id: drawer_extra_cancel type: CallMethod params: blockId: drawer_extra method: setOpen args: - open: false - id: drawer_extra_submit_btn type: Button layout: flex: 0 0 auto properties: title: Submit color: primary variant: solid size: small blocks: - id: drawer_extra_content type: Paragraph properties: content: The extra slot renders content in the header area opposite the title. Commonly used for action buttons like Save or Cancel. ``` The footer slot renders content in the drawer's footer area, pinned at the bottom. It is commonly used for action buttons like Save and Cancel. ```yaml - id: drawer_footer_btn type: Button layout: flex: 0 0 auto properties: title: Open Drawer with Footer color: primary variant: solid events: onClick: - id: drawer_footer_open type: CallMethod params: blockId: drawer_footer method: setOpen args: - open: true - id: drawer_footer type: Drawer properties: title: Drawer with Footer width: 400 slots: footer: blocks: - id: drawer_footer_actions_row type: Box layout: gap: 8 justify: end blocks: - id: drawer_footer_cancel_btn type: Button layout: flex: 0 0 auto properties: title: Cancel color: default variant: outlined events: onClick: - id: drawer_footer_cancel type: CallMethod params: blockId: drawer_footer method: setOpen args: - open: false - id: drawer_footer_save_btn type: Button layout: flex: 0 0 auto properties: title: Save color: primary variant: solid events: onClick: - id: drawer_footer_save_msg type: DisplayMessage params: content: Saved successfully status: success duration: 2 blocks: - id: drawer_footer_content type: Paragraph properties: content: The footer slot renders content in the drawer's footer area, pinned at the bottom. It is commonly used for action buttons like Save and Cancel. ``` This drawer uses the toggleOpen method. Each click of the button alternates between open and closed states. ```yaml - id: drawer_toggle_btn type: Button layout: flex: 0 0 auto properties: title: Toggle Drawer color: primary variant: solid icon: menu events: onClick: - id: drawer_toggle_call type: CallMethod params: blockId: drawer_toggle method: toggleOpen - id: drawer_toggle type: Drawer properties: title: Toggle Drawer blocks: - id: drawer_toggle_content type: Paragraph properties: content: This drawer uses the toggleOpen method. Each click of the button alternates between open and closed states. ``` This drawer triggers DisplayMessage actions on onOpen, onClose, and afterClose events. Close the drawer to see all three messages fire in sequence. Available events: onOpen fires when opening, onClose fires when the close action starts, onToggle fires on toggle, afterClose fires after the close animation completes, and afterOpenChange fires after any open state transition. ```yaml - id: drawer_events_btn type: Button layout: flex: 0 0 auto properties: title: Open Drawer with Events color: primary variant: solid events: onClick: - id: drawer_events_open type: CallMethod params: blockId: drawer_events method: setOpen args: - open: true - id: drawer_events type: Drawer properties: title: Drawer with Events width: 400 events: onOpen: - id: drawer_events_on_open_msg type: DisplayMessage params: content: Drawer opened duration: 2 onClose: - id: drawer_events_on_close_msg type: DisplayMessage params: content: Drawer closing... duration: 2 afterClose: - id: drawer_events_after_close_msg type: DisplayMessage params: content: Drawer fully closed duration: 2 blocks: - id: drawer_events_p1 type: Paragraph properties: content: This drawer triggers DisplayMessage actions on onOpen, onClose, and afterClose events. Close the drawer to see all three messages fire in sequence. - id: drawer_events_p2 type: Paragraph properties: content: "Available events: onOpen fires when opening, onClose fires when the close action starts, onToggle fires on toggle, afterClose fires after the close animation completes, and afterOpenChange fires after any open state transition." ``` This drawer uses style.header and style.body to apply custom background colors to the header and body areas. ```yaml - id: drawer_inline_style_btn type: Button layout: flex: 0 0 auto properties: title: Open Styled Drawer color: primary variant: solid events: onClick: - id: drawer_inline_style_open type: CallMethod params: blockId: drawer_inline_style method: setOpen args: - open: true - id: drawer_inline_style type: Drawer properties: title: Styled Drawer style: .header: backgroundColor: var(--ant-color-primary-bg) borderBottom: 1px solid var(--ant-color-primary-border) .body: backgroundColor: var(--ant-color-fill-quaternary) blocks: - id: drawer_inline_style_content type: Paragraph properties: content: This drawer uses style.header and style.body to apply custom background colors to the header and body areas. ``` Drawer supports these CSS keys: element, header, body, footer, mask, wrapper, content. Use class as an object to target each area with Tailwind classes. A gradient header created using Tailwind classes on the header CSS key. The body uses a subtle gray background. The mask CSS key applies a backdrop blur effect, and the content CSS key adds rounded corners to the drawer panel. ```yaml - id: drawer_css_keys_btn type: Button layout: flex: 0 0 auto properties: title: Open CSS Styled Drawer color: primary variant: solid events: onClick: - id: drawer_css_keys_open type: CallMethod params: blockId: drawer_css_keys method: setOpen args: - open: true - id: drawer_css_keys type: Drawer properties: title: Tailwind CSS Keys class: .header: bg-bg-layout .body: bg-bg-layout blocks: - id: drawer_css_keys_content type: Paragraph properties: content: "Drawer supports these CSS keys: element, header, body, footer, mask, wrapper, content. Use class as an object to target each area with Tailwind classes." - id: drawer_css_accent_btn type: Button layout: flex: 0 0 auto properties: title: Open Accent Drawer color: primary variant: outlined events: onClick: - id: drawer_css_accent_open type: CallMethod params: blockId: drawer_css_accent method: setOpen args: - open: true - id: drawer_css_accent type: Drawer properties: title: Accent Header class: .header: bg-gradient-to-r from-primary/100 to-indigo-600 text-white .body: bg-bg-layout blocks: - id: drawer_css_accent_content type: Paragraph properties: content: A gradient header created using Tailwind classes on the header CSS key. The body uses a subtle gray background. - id: drawer_css_mask_btn type: Button layout: flex: 0 0 auto properties: title: Open Blurred Mask Drawer color: primary variant: outlined events: onClick: - id: drawer_css_mask_open type: CallMethod params: blockId: drawer_css_mask method: setOpen args: - open: true - id: drawer_css_mask type: Drawer properties: title: Custom Mask Style class: .mask: backdrop-blur-sm .content: rounded-l-xl blocks: - id: drawer_css_mask_content type: Paragraph properties: content: The mask CSS key applies a backdrop blur effect, and the content CSS key adds rounded corners to the drawer panel. ``` Overrides colorBgElevated for a green-tinted background and colorBgMask for a subtle green mask overlay. A dark-themed drawer using design token overrides for background, text, and icon colors. Overrides footerPaddingBlock, footerPaddingInline, and zIndexPopup design tokens. These control footer spacing and the popup layer z-index. ```yaml - id: drawer_theme_green_btn type: Button layout: flex: 0 0 auto properties: title: Green Theme color: primary variant: solid events: onClick: - id: drawer_theme_green_open type: CallMethod params: blockId: drawer_theme_green method: setOpen args: - open: true - id: drawer_theme_green type: Drawer properties: title: Custom Theme Drawer theme: colorBgMask: rgba(0, 100, 0, 0.15) blocks: - id: drawer_theme_green_content type: Paragraph properties: content: Overrides colorBgElevated for a green-tinted background and colorBgMask for a subtle green mask overlay. - id: drawer_theme_dark_btn type: Button layout: flex: 0 0 auto properties: title: Dark Theme color: default variant: solid events: onClick: - id: drawer_theme_dark_open type: CallMethod params: blockId: drawer_theme_dark method: setOpen args: - open: true - id: drawer_theme_dark type: Drawer properties: title: Dark Theme Drawer theme: colorBgElevated: "#1f1f1f" colorBgMask: rgba(0, 0, 0, 0.7) colorText: "#ffffff" colorTextHeading: "#ffffff" colorIcon: "#ffffff" colorIconHover: "#1677ff" blocks: - id: drawer_theme_dark_content type: Paragraph class: text-text-secondary properties: content: A dark-themed drawer using design token overrides for background, text, and icon colors. - id: drawer_theme_spacing_btn type: Button layout: flex: 0 0 auto properties: title: Custom Spacing color: primary variant: outlined events: onClick: - id: drawer_theme_spacing_open type: CallMethod params: blockId: drawer_theme_spacing method: setOpen args: - open: true - id: drawer_theme_spacing type: Drawer properties: title: Custom Footer Padding theme: footerPaddingBlock: 16 footerPaddingInline: 24 zIndexPopup: 1500 blocks: - id: drawer_theme_spacing_content type: Paragraph properties: content: Overrides footerPaddingBlock, footerPaddingInline, and zIndexPopup design tokens. These control footer spacing and the popup layer z-index. ``` ```yaml - id: drawer_edit_form_btn type: Button layout: flex: 0 0 auto properties: title: Edit Record color: primary variant: solid icon: edit events: onClick: - id: drawer_edit_form_open type: CallMethod params: blockId: drawer_edit_form method: setOpen args: - open: true - id: drawer_edit_form type: Drawer properties: title: Edit Customer width: 480 slots: extra: blocks: - id: drawer_edit_form_extra_row type: Box layout: gap: 8 blocks: - id: drawer_edit_form_cancel_btn type: Button layout: flex: 0 0 auto properties: title: Cancel color: default variant: outlined size: small events: onClick: - id: drawer_edit_form_cancel type: CallMethod params: blockId: drawer_edit_form method: setOpen args: - open: false - id: drawer_edit_form_save_btn type: Button layout: flex: 0 0 auto properties: title: Save Changes color: primary variant: solid size: small events: onClick: - id: drawer_edit_form_save_msg type: DisplayMessage params: content: Changes saved successfully status: success duration: 3 - id: drawer_edit_form_close type: CallMethod params: blockId: drawer_edit_form method: setOpen args: - open: false blocks: - id: drawer_edit_form_name type: TextInput properties: title: Full Name placeholder: Enter customer name - id: drawer_edit_form_email type: TextInput properties: title: Email Address placeholder: customer@example.com - id: drawer_edit_form_phone type: TextInput properties: title: Phone Number placeholder: +1 (555) 000-0000 - id: drawer_edit_form_status type: Selector properties: title: Status options: - label: Active value: active - label: Inactive value: inactive - label: Pending value: pending - id: drawer_edit_form_notes type: TextArea properties: title: Notes placeholder: Additional notes about this customer... autoSize: minRows: 3 ``` ```yaml - id: drawer_filter_panel_btn type: Button layout: flex: 0 0 auto properties: title: Filters color: default variant: outlined icon: filter events: onClick: - id: drawer_filter_panel_open type: CallMethod params: blockId: drawer_filter_panel method: setOpen args: - open: true - id: drawer_filter_panel type: Drawer properties: title: Filter Results placement: left width: 320 events: onClose: - id: drawer_filter_panel_close_msg type: DisplayMessage params: content: Filters applied status: info duration: 2 slots: extra: blocks: - id: drawer_filter_panel_clear_btn type: Button layout: flex: 0 0 auto properties: title: Clear All color: danger variant: link size: small events: onClick: - id: drawer_filter_panel_clear_msg type: DisplayMessage params: content: All filters cleared status: warning duration: 2 blocks: - id: drawer_filter_panel_category type: MultipleSelector properties: title: Category options: - label: Electronics value: electronics - label: Clothing value: clothing - label: Books value: books - label: Home & Garden value: home - id: drawer_filter_panel_price type: Slider properties: title: Price Range min: 0 max: 500 step: 10 - id: drawer_filter_panel_rating type: ButtonSelector properties: title: Minimum Rating options: - label: Any value: 0 - label: 3+ value: 3 - label: 4+ value: 4 - label: "5" value: 5 - id: drawer_filter_panel_in_stock type: Switch properties: title: In Stock Only - id: drawer_filter_panel_apply_btn type: Button properties: title: Apply Filters color: primary variant: solid block: true events: onClick: - id: drawer_filter_panel_apply_msg type: DisplayMessage params: content: Filters applied to results status: success duration: 2 - id: drawer_filter_panel_apply_close type: CallMethod params: blockId: drawer_filter_panel method: setOpen args: - open: false ``` Jane Cooper ```yaml - id: drawer_profile_btn type: Button layout: flex: 0 0 auto properties: title: View Profile color: primary variant: solid icon: user events: onClick: - id: drawer_profile_open type: CallMethod params: blockId: drawer_profile method: setOpen args: - open: true - id: drawer_profile type: Drawer properties: title: User Profile width: 400 blocks: - id: drawer_profile_avatar_row type: Box layout: gap: 16 align: center blocks: - id: drawer_profile_avatar type: Avatar layout: flex: 0 0 auto properties: icon: user size: 64 color: "#1677ff" - id: drawer_profile_info type: Box layout: flex: 1 1 0 direction: column gap: 4 blocks: - id: drawer_profile_name type: Title properties: content: Jane Cooper level: 4 - id: drawer_profile_role_tag type: Tag properties: title: Senior Product Designer color: blue - id: drawer_profile_divider type: Divider - id: drawer_profile_details type: Descriptions properties: size: small column: 1 items: - label: Email value: jane.cooper@example.com - label: Phone value: +1 (555) 123-4567 - label: Location value: San Francisco, CA - label: Department value: Design - label: Start Date value: January 15, 2023 - id: drawer_profile_divider2 type: Divider - id: drawer_profile_actions_row type: Box layout: gap: 8 blocks: - id: drawer_profile_edit_btn type: Button layout: flex: 1 1 0 properties: title: Edit Profile color: primary variant: outlined icon: edit - id: drawer_profile_message_btn type: Button layout: flex: 1 1 0 properties: title: Send Message color: primary variant: solid icon: mail ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `closable` | boolean \| object | `true` | Whether a close (x) button is visible in the Drawer header or not. Set an object to disable the button or to move it to the end of the header. | | `closable.disabled` | boolean | `false` | Show the close button, but disabled. | | `closable.placement` | string | `"start"` | Place the close button at the start or the end of the header. Enum: `start`, `end`. | | `destroyOnHidden` | boolean | `false` | Unmount the blocks inside the Drawer when it closes, so they mount again (and their onMount events run) each time it opens. Their state is kept. | | `focusable` | object | - | Focus management of the dialog. | | `focusable.trap` | boolean | `true` | Keep keyboard focus inside the dialog while it is open. | | `focusable.focusTriggerAfterClose` | boolean | `true` | Return focus to the element that opened the dialog after it closes. | | `forceRender` | boolean | `false` | Render the blocks inside the Drawer before it is first opened, so their methods can be called and their onMount events run while it is still closed. | | `getContainer` | string \| boolean | - | Where the Drawer is mounted. By default it is mounted on the page body. Set to `false` to render it in place, inside the nearest positioned parent (give that parent `position: relative`), or to a CSS selector to mount it in the first matching element. | | `loading` | boolean | `false` | Show a loading skeleton in place of the Drawer body. | | `mask` | boolean \| object | `true` | Whether to show the mask. Set an object to configure the mask with `enabled`, `blur` and `closable`. | | `mask.enabled` | boolean | `true` | Whether to show the mask. | | `mask.blur` | boolean | `false` | Blur the page behind the mask. | | `mask.closable` | boolean | - | Whether clicking the mask closes the dialog. Takes precedence over `maskClosable`. | | `maskClosable` | boolean | `true` | Clicking on the mask (area outside the Drawer) to close the Drawer or not. `mask.closable` takes precedence. | | `maxSize` | number | - | Maximum size in pixels a resizable Drawer can be dragged to. | | `padding` | string | `"default"` | Space inside the drawer. `compact` tightens the header and body. `none` removes the body padding so content such as a table or list runs edge to edge; the header and footer keep their padding. Enum: `default`, `compact`, `none`. | | `resizable` | boolean | `false` | Let the user resize the Drawer by dragging its edge. The configured size is the starting size. | | `size` | string \| number | - | Size of the Drawer: width for left and right placements, height for top and bottom. `default` (378px), `large` (736px), a number of pixels or a CSS length. Takes precedence over width and height. | | `title` | string | - | The title of the Drawer - supports html. | | `width` | string \| number | `378` | Width of the Drawer dialog. | | `height` | string \| number | `378` | When placement is top or bottom, height of the Drawer dialog. | | `zIndex` | integer | `1000` | The z-index of the Drawer. | | `placement` | string | `"right"` | The placement of the Drawer. Enum: `top`, `right`, `bottom`, `left`. | | `keyboard` | boolean | `true` | Whether pressing Esc closes the Drawer. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design drawer tokens](https://ant.design/components/drawer#design-token). | | `theme.footerPaddingBlock` | number | `8` | Vertical padding of the footer. | | `theme.footerPaddingInline` | number | `16` | Horizontal padding of the footer. | | `theme.zIndexPopup` | number | `1000` | Z-index of the drawer. | | `theme.draggerSize` | number | `4` | Size of the resize handle. | | `theme.colorBgElevated` | string | - | Background color of the drawer. | | `theme.colorBgMask` | string | `"rgba(0, 0, 0, 0.45)"` | Background color of the drawer mask. | | Event | Event Data | Description | | --- | --- | --- | | `onToggle` | \- | Trigger actions when drawer is toggled. | | `onClose` | \- | Trigger actions when drawer is closed. | | `onOpen` | \- | Trigger actions when drawer is opened. | | `afterClose` | \- | Trigger actions after drawer is closed. | | `afterOpenChange` | `{ open, drawerOpen }` | Trigger actions after the open or close animation of the drawer has finished. | | `onResizeEnd` | `{ size }` | Trigger actions when the user finishes resizing a resizable drawer. Not triggered when the resize handle is clicked without dragging. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Drawer element. | | `/header` | The Drawer header. | | `/title` | The Drawer title. | | `/body` | The Drawer body. | | `/footer` | The Drawer footer. | | `/mask` | The Drawer mask. | | `/wrapper` | The Drawer wrapper. | | `/content` | The Drawer content. | | Slot | Description | | --- | --- | | `content` | Main Drawer body. | | `extra` | Extra content in the header. | | `footer` | The Drawer footer. | --- url: https://docs.lowdefy.com/md/container-blocks/dropdownmenu.md --- # DropdownMenu Floating dropdown menu triggered by any child block. Menu items follow the Menu block links pattern — MenuLink for navigation, MenuGroup for grouping, MenuDivider for separators. Use trigger modes click, hover, or contextMenu. ```yaml - id: dropdown_menu_basic type: DropdownMenu layout: flex: 0 0 auto properties: trigger: click links: - id: dm_basic_home type: MenuLink pageId: home properties: title: Home icon: home - id: dm_basic_profile type: MenuLink pageId: home properties: title: Profile icon: user - id: dm_basic_div type: MenuDivider - id: dm_basic_logout type: MenuLink properties: title: Log Out icon: logout danger: true blocks: - id: dropdown_menu_basic_btn type: Button layout: flex: 0 0 auto properties: title: Account color: primary variant: solid ``` ```yaml - id: dropdown_menu_hover type: DropdownMenu layout: flex: 0 0 auto properties: links: - id: dm_hover_item1 type: MenuLink properties: title: Option A - id: dm_hover_item2 type: MenuLink properties: title: Option B - id: dm_hover_item3 type: MenuLink properties: title: Option C blocks: - id: dropdown_menu_hover_btn type: Button layout: flex: 0 0 auto properties: title: Hover me color: default variant: outlined ``` Right-click anywhere in this card to open the context menu. ```yaml - id: dropdown_menu_context type: DropdownMenu properties: trigger: contextMenu links: - id: dm_ctx_copy type: MenuLink properties: title: Copy icon: copy - id: dm_ctx_paste type: MenuLink properties: title: Paste icon: Files - id: dm_ctx_div type: MenuDivider - id: dm_ctx_delete type: MenuLink properties: title: Delete icon: delete danger: true blocks: - id: dropdown_menu_context_area type: Card properties: size: small bordered: true blocks: - id: dropdown_menu_context_text type: Paragraph properties: content: Right-click anywhere in this card to open the context menu. ``` ```yaml - id: dropdown_menu_pl_bl type: DropdownMenu layout: flex: 0 0 auto properties: trigger: click placement: bottomLeft links: - id: dm_pl_bl_1 type: MenuLink properties: title: Item 1 - id: dm_pl_bl_2 type: MenuLink properties: title: Item 2 blocks: - id: dropdown_menu_pl_bl_btn type: Button layout: flex: 0 0 auto properties: title: bottomLeft color: primary variant: solid size: small - id: dropdown_menu_pl_br type: DropdownMenu layout: flex: 0 0 auto properties: trigger: click placement: bottomRight links: - id: dm_pl_br_1 type: MenuLink properties: title: Item 1 - id: dm_pl_br_2 type: MenuLink properties: title: Item 2 blocks: - id: dropdown_menu_pl_br_btn type: Button layout: flex: 0 0 auto properties: title: bottomRight color: primary variant: outlined size: small - id: dropdown_menu_pl_tl type: DropdownMenu layout: flex: 0 0 auto properties: trigger: click placement: topLeft links: - id: dm_pl_tl_1 type: MenuLink properties: title: Item 1 - id: dm_pl_tl_2 type: MenuLink properties: title: Item 2 blocks: - id: dropdown_menu_pl_tl_btn type: Button layout: flex: 0 0 auto properties: title: topLeft color: default variant: outlined size: small - id: dropdown_menu_pl_right type: DropdownMenu layout: flex: 0 0 auto properties: trigger: click placement: right links: - id: dm_pl_right_1 type: MenuLink properties: title: Item 1 - id: dm_pl_right_2 type: MenuLink properties: title: Item 2 blocks: - id: dropdown_menu_pl_right_btn type: Button layout: flex: 0 0 auto properties: title: right color: default variant: outlined size: small ``` ```yaml - id: dropdown_menu_selectable type: DropdownMenu layout: flex: 0 0 auto properties: trigger: click selectable: true selectedKeys: _if_none: - _state: dm_sort_keys - - dm_sort_newest links: - id: dm_sort_newest type: MenuLink properties: title: Newest first - id: dm_sort_oldest type: MenuLink properties: title: Oldest first - id: dm_sort_name type: MenuLink properties: title: Name events: onSelect: - id: dm_sort_set type: SetState params: dm_sort_keys: _event: selectedKeys blocks: - id: dropdown_menu_selectable_btn type: Button layout: flex: 0 0 auto properties: title: Sort icon: sort color: default variant: outlined ``` ```yaml - id: dropdown_menu_arrow type: DropdownMenu layout: flex: 0 0 auto properties: trigger: click arrow: true links: - id: dm_arrow_1 type: MenuLink properties: title: Settings icon: settings - id: dm_arrow_2 type: MenuLink properties: title: Help icon: help blocks: - id: dropdown_menu_arrow_btn type: Button layout: flex: 0 0 auto properties: title: With Arrow color: primary variant: solid - id: dropdown_menu_arrow_center type: DropdownMenu layout: flex: 0 0 auto properties: trigger: click arrow: pointAtCenter: true links: - id: dm_arrowc_1 type: MenuLink properties: title: Settings icon: settings - id: dm_arrowc_2 type: MenuLink properties: title: Help icon: help blocks: - id: dropdown_menu_arrow_center_btn type: Button layout: flex: 0 0 auto properties: title: Arrow at Center color: primary variant: outlined ``` ```yaml - id: dropdown_menu_groups type: DropdownMenu layout: flex: 0 0 auto properties: trigger: click links: - id: dm_grp_nav type: MenuGroup properties: title: Navigation icon: grid links: - id: dm_grp_nav_home type: MenuLink pageId: home properties: title: Home - id: dm_grp_nav_dash type: MenuLink pageId: home properties: title: Dashboard - id: dm_grp_div type: MenuDivider - id: dm_grp_account type: MenuGroup properties: title: Account icon: user links: - id: dm_grp_acc_profile type: MenuLink pageId: home properties: title: Profile - id: dm_grp_acc_settings type: MenuLink pageId: home properties: title: Settings blocks: - id: dropdown_menu_groups_btn type: Button layout: flex: 0 0 auto properties: title: Grouped Menu color: primary variant: solid ``` ```yaml - id: dropdown_menu_disabled type: DropdownMenu layout: flex: 0 0 auto properties: trigger: click links: - id: dm_dis_active type: MenuLink properties: title: Available Action icon: check - id: dm_dis_disabled type: MenuLink properties: title: Unavailable (disabled) disabled: true - id: dm_dis_div type: MenuDivider - id: dm_dis_danger type: MenuLink properties: title: Danger Action danger: true blocks: - id: dropdown_menu_disabled_btn type: Button layout: flex: 0 0 auto properties: title: Mixed States color: default variant: outlined ``` ```yaml - id: dropdown_menu_shortcuts type: DropdownMenu layout: flex: 0 0 auto properties: trigger: click links: - id: dm_sc_new type: MenuLink properties: title: New File icon: FilePlus shortcut: mod+shift+N - id: dm_sc_save type: MenuLink properties: title: Save icon: save shortcut: mod+shift+S - id: dm_sc_div type: MenuDivider - id: dm_sc_close type: MenuLink properties: title: Close icon: close shortcut: mod+shift+W events: onClick: - id: dropdown_menu_shortcuts_msg type: DisplayMessage params: content: _string.concat: - "Shortcut action: " - _event: key duration: 2 blocks: - id: dropdown_menu_shortcuts_btn type: Button layout: flex: 0 0 auto properties: title: File Menu color: primary variant: solid ``` ```yaml - id: dropdown_menu_event type: DropdownMenu layout: flex: 0 0 auto properties: trigger: click links: - id: dm_ev_1 type: MenuLink properties: title: Option A - id: dm_ev_2 type: MenuLink properties: title: Option B events: onOpenChange: - id: dropdown_menu_event_msg type: DisplayMessage params: content: Dropdown toggled! duration: 2 blocks: - id: dropdown_menu_event_btn type: Button layout: flex: 0 0 auto properties: title: Click to Toggle color: primary variant: solid icon: bell ``` ```yaml - id: dropdown_menu_onclick type: DropdownMenu layout: flex: 0 0 auto properties: trigger: click links: - id: dm_oc_edit type: MenuLink properties: title: Edit icon: edit - id: dm_oc_copy type: MenuLink properties: title: Copy icon: copy - id: dm_oc_delete type: MenuLink properties: title: Delete icon: delete danger: true events: onClick: - id: dropdown_menu_onclick_msg type: DisplayMessage params: content: _string.concat: - "Clicked: " - _event: key duration: 2 blocks: - id: dropdown_menu_onclick_btn type: Button layout: flex: 0 0 auto properties: title: Actions color: default variant: outlined ``` ```yaml - id: dropdown_menu_theme type: DropdownMenu layout: flex: 0 0 auto properties: trigger: click theme: controlItemBgHover: "#f0f5ff" borderRadiusLG: 12 links: - id: dm_th_1 type: MenuLink properties: title: Styled Item A - id: dm_th_2 type: MenuLink properties: title: Styled Item B - id: dm_th_3 type: MenuLink properties: title: Styled Item C blocks: - id: dropdown_menu_theme_btn type: Button layout: flex: 0 0 auto properties: title: Custom Theme color: primary variant: solid ``` JD Jane Doe ```yaml - id: dropdown_menu_user type: DropdownMenu layout: flex: 0 0 auto properties: trigger: click placement: bottomRight links: - id: dm_user_profile type: MenuLink pageId: home properties: title: My Profile icon: user - id: dm_user_settings type: MenuLink pageId: home properties: title: Settings icon: settings - id: dm_user_div type: MenuDivider - id: dm_user_logout type: MenuLink properties: title: Log Out icon: logout danger: true blocks: - id: dropdown_menu_user_trigger type: Box layout: gap: 8 align: center blocks: - id: dropdown_menu_user_avatar type: Avatar layout: flex: 0 0 auto properties: content: JD color: "#1677ff" - id: dropdown_menu_user_name type: Paragraph layout: flex: 0 0 auto properties: content: Jane Doe ``` ```yaml - id: dropdown_menu_op type: DropdownMenu properties: trigger: click links: _menu: default blocks: - id: dropdown_menu_op_trigger type: Button properties: title: Open Menu icon: menu ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `links` | array | - | Menu items. Same structure as Menu block: MenuLink, MenuGroup, MenuDivider. | | `links.$.id` | string | - | Menu item id. | | `links.$.type` | string | `"MenuLink"` | Menu item type. Enum: `MenuDivider`, `MenuLink`, `MenuGroup`. | | `links.$.pageId` | string | - | Page to link to. | | `links.$.url` | string | - | External URL to link to. | | `links.$.newTab` | boolean | - | Open link in new tab. | | `links.$.style` | object \| string \| array | - | CSS styles for the menu item. Use a flat object for the item wrapper, or dot-prefixed slot keys (`.element`, `.icon`, `.label`). | | `links.$.class` | string \| array \| object | - | CSS classes for the menu item. Flat applies to the item wrapper; use dot-prefixed slot keys to target parts. | | `links.$.properties` | object | - | Properties for the menu item. | | `links.$.properties.title` | string | - | Menu item title. | | `links.$.properties.icon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to customize icon on menu item. | | `links.$.properties.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `links.$.properties.icon.color` | string | - | Icon color. | | `links.$.properties.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `links.$.properties.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `links.$.properties.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `links.$.properties.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `links.$.properties.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `links.$.properties.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `links.$.properties.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `links.$.properties.danger` | boolean | `false` | Apply danger style to menu item. | | `links.$.properties.disabled` | boolean | `false` | Disable the menu item. | | `links.$.properties.tooltip` | string | - | Tooltip text shown when the menu is collapsed. | | `links.$.properties.extra` | string | - | Free-form right-aligned label on a MenuLink. For real keybindings use `shortcut`; when both are set, `shortcut` sits to the right of `extra`. | | `links.$.properties.dashed` | boolean | `false` | Whether the divider line is dashed. | | `links.$.properties.shortcut` | string | - | Keyboard shortcut for this menu item. Renders a kbd badge floated to the far right of the item AND wires the key handler. Use "mod" for Cmd/Ctrl. | | `links.$.links` | array | - | Nested menu items for MenuGroup. | | `links.$.links.$.id` | string | - | Menu item id. | | `links.$.links.$.type` | string | `"MenuLink"` | Menu item type. Enum: `MenuDivider`, `MenuLink`. | | `links.$.links.$.pageId` | string | - | Page to link to. | | `links.$.links.$.url` | string | - | External URL to link to. | | `links.$.links.$.newTab` | boolean | - | Open link in new tab. | | `links.$.links.$.style` | object \| string \| array | - | CSS styles for the menu item. Flat or dot-prefixed slot keys (`.element`, `.icon`, `.label`). | | `links.$.links.$.class` | string \| array \| object | - | CSS classes for the menu item. | | `links.$.links.$.properties` | object | - | Properties for the menu item. | | `links.$.links.$.properties.title` | string | - | Menu item title. | | `links.$.links.$.properties.icon` | string \| object | - | Icon name or config. | | `links.$.links.$.properties.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `links.$.links.$.properties.icon.color` | string | - | Icon color. | | `links.$.links.$.properties.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `links.$.links.$.properties.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `links.$.links.$.properties.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `links.$.links.$.properties.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `links.$.links.$.properties.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `links.$.links.$.properties.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `links.$.links.$.properties.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `links.$.links.$.properties.danger` | boolean | `false` | Danger style. | | `links.$.links.$.properties.disabled` | boolean | `false` | Disable the item. | | `links.$.links.$.properties.tooltip` | string | - | Tooltip text shown when the menu is collapsed. | | `links.$.links.$.properties.extra` | string | - | Free-form right-aligned label on a MenuLink. For real keybindings use `shortcut`. | | `links.$.links.$.properties.dashed` | boolean | `false` | Dashed divider line. | | `links.$.links.$.properties.shortcut` | string | - | Keyboard shortcut. Renders a kbd badge floated to the far right and wires the key handler. Use "mod" for Cmd/Ctrl. | | `trigger` | string | `"hover"` | How the dropdown opens. Enum: `click`, `hover`, `contextMenu`. | | `placement` | string | `"bottomLeft"` | Position relative to trigger. Enum: `bottomLeft`, `bottom`, `bottomRight`, `topLeft`, `top`, `topRight`, `left`, `leftTop`, `leftBottom`, `right`, `rightTop`, `rightBottom`. | | `arrow` | boolean \| object | `false` | Show arrow pointing to trigger. | | `arrow.pointAtCenter` | boolean | - | | | `disabled` | boolean | `false` | Disable the dropdown. | | `destroyOnClose` | boolean | `false` | Unmount menu DOM when closed. | | `selectable` | boolean | `false` | Allow menu items to be selected. Selecting an item highlights it and fires onSelect. | | `selectedKeys` | array | - | Highlighted menu items. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design dropdown tokens](https://ant.design/components/dropdown#design-token). | | `theme.zIndexPopup` | number | `1050` | Z-index of the dropdown popup. | | `theme.controlItemBgHover` | string | - | Background color on item hover. | | `theme.colorPrimary` | string | - | Primary color override. | | `theme.borderRadiusLG` | number | - | Border radius for the dropdown. | | `theme.paddingBlock` | number | - | Vertical padding for menu items. | | Event | Event Data | Description | | --- | --- | --- | | `onClick` | `{ key, keyPath, pageId, url }` | Trigger action when a menu item is clicked. | | `onSelect` | `{ key, selectedKeys, pageId, url }` | Trigger action when a menu item is selected. Requires `selectable: true`. | | `onOpenChange` | `{ open }` | Trigger action when dropdown opens or closes. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The trigger wrapper element. | | `/menu` | The floating menu container. | | `/item` | Individual menu items. | | `/itemIcon` | Icon within menu items. | | `/subMenu` | Has no effect: antd does not expose submenu containers as a separately styled part. The item key also applies to submenu items. | | `/arrow` | Has no effect: antd does not expose the dropdown arrow as a separately styled part. | | Slot | Description | | --- | --- | | `content` | Blocks that trigger the dropdown. | --- url: https://docs.lowdefy.com/md/container-blocks/flex.md --- # Flex Flexible box layout container for arranging child blocks. ```yaml - id: flex_basic type: Flex properties: gap: 8 blocks: - id: flex_basic_btn1 type: Button layout: flex: 0 0 auto properties: title: One color: primary variant: solid - id: flex_basic_btn2 type: Button layout: flex: 0 0 auto properties: title: Two color: primary variant: outlined - id: flex_basic_btn3 type: Button layout: flex: 0 0 auto properties: title: Three color: primary variant: dashed ``` Stacked vertically. Below the first card. Below the second card. ```yaml - id: flex_vertical type: Flex properties: orientation: vertical gap: 12 blocks: - id: flex_vert_card1 type: Card properties: title: First size: small blocks: - id: flex_vert_card1_p type: Paragraph properties: content: Stacked vertically. - id: flex_vert_card2 type: Card properties: title: Second size: small blocks: - id: flex_vert_card2_p type: Paragraph properties: content: Below the first card. - id: flex_vert_card3 type: Card properties: title: Third size: small blocks: - id: flex_vert_card3_p type: Paragraph properties: content: Below the second card. ``` **gap: small (8px)** **gap: medium (16px)** **gap: large (24px)** **gap: 40 (custom number)** ```yaml - id: flex_gap_small_label type: Markdown properties: content: "**gap: small (8px)**" - id: flex_gap_small type: Flex properties: gap: small blocks: - id: flex_gs_1 type: Tag layout: flex: 0 0 auto properties: title: Tag A color: blue - id: flex_gs_2 type: Tag layout: flex: 0 0 auto properties: title: Tag B color: blue - id: flex_gs_3 type: Tag layout: flex: 0 0 auto properties: title: Tag C color: blue - id: flex_gap_middle_label type: Markdown properties: content: "**gap: medium (16px)**" - id: flex_gap_middle type: Flex properties: gap: medium blocks: - id: flex_gm_1 type: Tag layout: flex: 0 0 auto properties: title: Tag A color: green - id: flex_gm_2 type: Tag layout: flex: 0 0 auto properties: title: Tag B color: green - id: flex_gm_3 type: Tag layout: flex: 0 0 auto properties: title: Tag C color: green - id: flex_gap_large_label type: Markdown properties: content: "**gap: large (24px)**" - id: flex_gap_large type: Flex properties: gap: large blocks: - id: flex_gl_1 type: Tag layout: flex: 0 0 auto properties: title: Tag A color: orange - id: flex_gl_2 type: Tag layout: flex: 0 0 auto properties: title: Tag B color: orange - id: flex_gl_3 type: Tag layout: flex: 0 0 auto properties: title: Tag C color: orange - id: flex_gap_custom_label type: Markdown properties: content: "**gap: 40 (custom number)**" - id: flex_gap_custom type: Flex properties: gap: 40 blocks: - id: flex_gc_1 type: Tag layout: flex: 0 0 auto properties: title: Tag A color: purple - id: flex_gc_2 type: Tag layout: flex: 0 0 auto properties: title: Tag B color: purple - id: flex_gc_3 type: Tag layout: flex: 0 0 auto properties: title: Tag C color: purple ``` **justify: flex-start** **justify: center** **justify: flex-end** **justify: space-between** **justify: space-around** **justify: space-evenly** ```yaml - id: flex_j_start_label type: Markdown properties: content: "**justify: flex-start**" - id: flex_j_start type: Flex class: bg-bg-layout p-2 rounded properties: justify: flex-start gap: 8 blocks: - id: flex_js_1 type: Button layout: flex: 0 0 auto properties: title: A color: primary variant: solid size: small - id: flex_js_2 type: Button layout: flex: 0 0 auto properties: title: B color: primary variant: solid size: small - id: flex_js_3 type: Button layout: flex: 0 0 auto properties: title: C color: primary variant: solid size: small - id: flex_j_center_label type: Markdown properties: content: "**justify: center**" - id: flex_j_center type: Flex class: bg-bg-layout p-2 rounded properties: justify: center gap: 8 blocks: - id: flex_jc_1 type: Button layout: flex: 0 0 auto properties: title: A color: blue variant: solid size: small - id: flex_jc_2 type: Button layout: flex: 0 0 auto properties: title: B color: blue variant: solid size: small - id: flex_jc_3 type: Button layout: flex: 0 0 auto properties: title: C color: blue variant: solid size: small - id: flex_j_end_label type: Markdown properties: content: "**justify: flex-end**" - id: flex_j_end type: Flex class: bg-bg-layout p-2 rounded properties: justify: flex-end gap: 8 blocks: - id: flex_je_1 type: Button layout: flex: 0 0 auto properties: title: A color: green variant: solid size: small - id: flex_je_2 type: Button layout: flex: 0 0 auto properties: title: B color: green variant: solid size: small - id: flex_je_3 type: Button layout: flex: 0 0 auto properties: title: C color: green variant: solid size: small - id: flex_j_between_label type: Markdown properties: content: "**justify: space-between**" - id: flex_j_between type: Flex class: bg-bg-layout p-2 rounded properties: justify: space-between blocks: - id: flex_jb_1 type: Button layout: flex: 0 0 auto properties: title: A color: orange variant: solid size: small - id: flex_jb_2 type: Button layout: flex: 0 0 auto properties: title: B color: orange variant: solid size: small - id: flex_jb_3 type: Button layout: flex: 0 0 auto properties: title: C color: orange variant: solid size: small - id: flex_j_around_label type: Markdown properties: content: "**justify: space-around**" - id: flex_j_around type: Flex class: bg-bg-layout p-2 rounded properties: justify: space-around blocks: - id: flex_ja_1 type: Button layout: flex: 0 0 auto properties: title: A color: purple variant: solid size: small - id: flex_ja_2 type: Button layout: flex: 0 0 auto properties: title: B color: purple variant: solid size: small - id: flex_ja_3 type: Button layout: flex: 0 0 auto properties: title: C color: purple variant: solid size: small - id: flex_j_evenly_label type: Markdown properties: content: "**justify: space-evenly**" - id: flex_j_evenly type: Flex class: bg-bg-layout p-2 rounded properties: justify: space-evenly blocks: - id: flex_jev_1 type: Button layout: flex: 0 0 auto properties: title: A color: red variant: solid size: small - id: flex_jev_2 type: Button layout: flex: 0 0 auto properties: title: B color: red variant: solid size: small - id: flex_jev_3 type: Button layout: flex: 0 0 auto properties: title: C color: red variant: solid size: small ``` **align: flex-start** **align: center** **align: flex-end** **align: stretch** Stretches to fill. Same height as tallest sibling. **align: baseline** Large Title Body text aligned to the same baseline. Small ```yaml - id: flex_a_start_label type: Markdown properties: content: "**align: flex-start**" - id: flex_a_start type: Flex class: bg-bg-layout p-2 rounded style: .element: minHeight: 80px properties: align: flex-start gap: 8 blocks: - id: flex_as_1 type: Button layout: flex: 0 0 auto properties: title: Short color: primary variant: solid - id: flex_as_2 type: Button layout: flex: 0 0 auto properties: title: Tall color: primary variant: solid size: large - id: flex_as_3 type: Button layout: flex: 0 0 auto properties: title: Short color: primary variant: solid size: small - id: flex_a_center_label type: Markdown properties: content: "**align: center**" - id: flex_a_center type: Flex class: bg-bg-layout p-2 rounded style: .element: minHeight: 80px properties: align: center gap: 8 blocks: - id: flex_ac_1 type: Button layout: flex: 0 0 auto properties: title: Short color: blue variant: solid - id: flex_ac_2 type: Button layout: flex: 0 0 auto properties: title: Tall color: blue variant: solid size: large - id: flex_ac_3 type: Button layout: flex: 0 0 auto properties: title: Short color: blue variant: solid size: small - id: flex_a_end_label type: Markdown properties: content: "**align: flex-end**" - id: flex_a_end type: Flex class: bg-bg-layout p-2 rounded style: .element: minHeight: 80px properties: align: flex-end gap: 8 blocks: - id: flex_ae_1 type: Button layout: flex: 0 0 auto properties: title: Short color: green variant: solid - id: flex_ae_2 type: Button layout: flex: 0 0 auto properties: title: Tall color: green variant: solid size: large - id: flex_ae_3 type: Button layout: flex: 0 0 auto properties: title: Short color: green variant: solid size: small - id: flex_a_stretch_label type: Markdown properties: content: "**align: stretch**" - id: flex_a_stretch type: Flex class: bg-bg-layout p-2 rounded style: .element: minHeight: 80px properties: align: stretch gap: 8 blocks: - id: flex_ax_1 type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: flex_ax_1_p type: Paragraph properties: content: Stretches to fill. - id: flex_ax_2 type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: flex_ax_2_p type: Paragraph properties: content: Same height as tallest sibling. - id: flex_a_baseline_label type: Markdown properties: content: "**align: baseline**" - id: flex_a_baseline type: Flex class: bg-bg-layout p-2 rounded properties: align: baseline gap: 16 blocks: - id: flex_ab_1 type: Title layout: flex: 0 0 auto properties: content: Large Title level: 2 - id: flex_ab_2 type: Paragraph layout: flex: 0 0 auto properties: content: Body text aligned to the same baseline. - id: flex_ab_3 type: Title layout: flex: 0 0 auto properties: content: Small level: 5 ``` **wrap: true** - items wrap to next line when space runs out ```yaml - id: flex_wrap_label type: Markdown properties: content: "**wrap: true** - items wrap to next line when space runs out" - id: flex_wrap type: Flex class: bg-bg-layout p-2 rounded properties: wrap: true gap: 8 blocks: - id: flex_w_1 type: Button layout: flex: 0 0 auto properties: title: Alpha color: primary variant: solid - id: flex_w_2 type: Button layout: flex: 0 0 auto properties: title: Bravo color: blue variant: solid - id: flex_w_3 type: Button layout: flex: 0 0 auto properties: title: Charlie color: green variant: solid - id: flex_w_4 type: Button layout: flex: 0 0 auto properties: title: Delta color: orange variant: solid - id: flex_w_5 type: Button layout: flex: 0 0 auto properties: title: Echo color: purple variant: solid - id: flex_w_6 type: Button layout: flex: 0 0 auto properties: title: Foxtrot color: red variant: solid - id: flex_w_7 type: Button layout: flex: 0 0 auto properties: title: Golf color: cyan variant: solid - id: flex_w_8 type: Button layout: flex: 0 0 auto properties: title: Hotel color: magenta variant: solid - id: flex_w_9 type: Button layout: flex: 0 0 auto properties: title: India color: gold variant: solid - id: flex_w_10 type: Button layout: flex: 0 0 auto properties: title: Juliet color: lime variant: solid ``` **flex** property on the Flex container itself sets the CSS flex shorthand flex 1 (grows) flex none (fixed) flex 2 (grows 2x) ```yaml - id: flex_prop_label type: Markdown properties: content: "**flex** property on the Flex container itself sets the CSS flex shorthand" - id: flex_prop_outer type: Flex properties: gap: 16 blocks: - id: flex_prop_grow type: Flex layout: flex: 1 1 0 class: bg-bg-layout p-4 rounded properties: flex: 1 justify: center align: center blocks: - id: flex_prop_grow_txt type: Paragraph properties: content: flex 1 (grows) - id: flex_prop_fixed type: Flex layout: flex: 0 0 auto class: bg-bg-layout p-4 rounded properties: flex: none justify: center align: center blocks: - id: flex_prop_fixed_txt type: Paragraph properties: content: flex none (fixed) - id: flex_prop_grow2 type: Flex layout: flex: 2 1 0 class: bg-bg-layout p-4 rounded properties: flex: 2 justify: center align: center blocks: - id: flex_prop_grow2_txt type: Paragraph properties: content: flex 2 (grows 2x) ``` ```yaml - id: flex_component_nav type: Flex properties: component: nav gap: 16 justify: center blocks: - id: flex_component_nav_link1 type: Button layout: flex: 0 0 auto properties: title: Home color: primary variant: link - id: flex_component_nav_link2 type: Button layout: flex: 0 0 auto properties: title: About color: primary variant: link - id: flex_component_nav_link3 type: Button layout: flex: 0 0 auto properties: title: Contact color: primary variant: link ``` ```yaml - id: flex_theme_medium type: Flex properties: gap: medium theme: padding: 32 blocks: - id: flex_theme_medium_btn1 type: Button layout: flex: 0 0 auto properties: title: Wider medium gap color: primary variant: solid - id: flex_theme_medium_btn2 type: Button layout: flex: 0 0 auto properties: title: padding token 32 color: primary variant: outlined - id: flex_theme_small type: Flex properties: gap: small theme: paddingXS: 2 blocks: - id: flex_theme_small_btn1 type: Button layout: flex: 0 0 auto properties: title: Tighter small gap color: green variant: solid - id: flex_theme_small_btn2 type: Button layout: flex: 0 0 auto properties: title: paddingXS token 2 color: green variant: outlined ``` Dark Panel ```yaml - id: flex_style_inline type: Flex style: .element: padding: 16px borderRadius: 8px border: "1px dashed #91caff" properties: gap: 12 justify: center blocks: - id: flex_style_inline_btn1 type: Button layout: flex: 0 0 auto properties: title: Styled Container color: primary variant: solid - id: flex_style_inline_btn2 type: Button layout: flex: 0 0 auto properties: title: With Dashed Border color: primary variant: outlined - id: flex_style_dark type: Flex style: .element: backgroundColor: "#1f1f1f" padding: 20px borderRadius: 12px properties: gap: 12 justify: space-between align: center blocks: - id: flex_style_dark_title type: Title layout: flex: 0 0 auto class: text-white properties: content: Dark Panel level: 5 - id: flex_style_dark_btn type: Button layout: flex: 0 0 auto properties: title: Action color: primary variant: solid size: small ``` Gradient Flex Ring utility applied to the element CSS key. ```yaml - id: flex_tw_gradient type: Flex class: .element: bg-gradient-to-r from-primary/100 to-purple-600 p-6 rounded-xl shadow-lg properties: gap: 16 align: center justify: space-between blocks: - id: flex_tw_gradient_title type: Title layout: flex: 0 0 auto class: text-white properties: content: Gradient Flex level: 4 - id: flex_tw_gradient_btn type: Button layout: flex: 0 0 auto properties: title: Learn More color: default variant: solid size: small - id: flex_tw_bordered type: Flex class: .element: border-2 border-dashed border-border p-4 rounded-lg hover:border-blue-400 transition-colors duration-200 properties: gap: 12 justify: center wrap: true blocks: - id: flex_tw_bordered_tag1 type: Tag layout: flex: 0 0 auto properties: title: Hover me color: blue - id: flex_tw_bordered_tag2 type: Tag layout: flex: 0 0 auto properties: title: Dashed border color: cyan - id: flex_tw_bordered_tag3 type: Tag layout: flex: 0 0 auto properties: title: Transitions color: purple - id: flex_tw_ring type: Flex class: .element: ring-2 ring-green-400 ring-offset-2 p-4 rounded-lg bg-bg-layout properties: gap: 8 align: center blocks: - id: flex_tw_ring_icon type: Icon layout: flex: 0 0 auto properties: name: check-circle color: "#52c41a" size: 24 - id: flex_tw_ring_text type: Paragraph layout: flex: 0 0 auto properties: content: Ring utility applied to the element CSS key. ``` Dashboard ```yaml - id: flex_app_toolbar type: Flex class: bg-bg-container p-3 rounded-lg shadow-sm border properties: justify: space-between align: center gap: 8 blocks: - id: flex_app_toolbar_left type: Flex properties: gap: 8 align: center blocks: - id: flex_app_toolbar_title type: Title layout: flex: 0 0 auto properties: content: Dashboard level: 5 - id: flex_app_toolbar_tag type: Tag layout: flex: 0 0 auto properties: title: Live color: success - id: flex_app_toolbar_right type: Flex properties: gap: 8 blocks: - id: flex_app_toolbar_btn_filter type: Button layout: flex: 0 0 auto properties: title: Filter icon: filter color: default variant: outlined size: small events: onClick: - id: flex_app_toolbar_filter_msg type: DisplayMessage params: content: Filter panel toggled. status: info - id: flex_app_toolbar_btn_export type: Button layout: flex: 0 0 auto properties: title: Export icon: download color: primary variant: solid size: small events: onClick: - id: flex_app_toolbar_export_msg type: DisplayMessage params: content: Export started. status: success ``` ```yaml - id: flex_stats_row type: Flex properties: gap: 16 wrap: true blocks: - id: flex_stats_card1 type: Card layout: flex: 1 1 200px properties: title: Users size: small hoverable: true blocks: - id: flex_stats_stat1 type: Statistic properties: title: Total value: 1234 - id: flex_stats_tag1 type: Tag properties: title: +8.2% color: success - id: flex_stats_card2 type: Card layout: flex: 1 1 200px properties: title: Revenue size: small hoverable: true blocks: - id: flex_stats_stat2 type: Statistic properties: title: Monthly value: 8650 prefix: $ - id: flex_stats_tag2 type: Tag properties: title: +12.5% color: success - id: flex_stats_card3 type: Card layout: flex: 1 1 200px properties: title: Orders size: small hoverable: true blocks: - id: flex_stats_stat3 type: Statistic properties: title: This Week value: 56 - id: flex_stats_tag3 type: Tag properties: title: -3.1% color: error ``` Jane Cooper Senior Product Designer at Acme Corp ```yaml - id: flex_profile_header type: Flex class: bg-bg-container p-6 rounded-xl shadow-sm border properties: gap: 24 align: center wrap: true blocks: - id: flex_profile_avatar type: Avatar layout: flex: 0 0 auto properties: icon: user size: 72 color: "#1677ff" - id: flex_profile_info type: Flex layout: flex: 1 1 200px properties: vertical: true gap: 4 blocks: - id: flex_profile_name type: Title properties: content: Jane Cooper level: 4 - id: flex_profile_role type: Paragraph properties: content: Senior Product Designer at Acme Corp - id: flex_profile_tags type: Flex properties: gap: 8 wrap: true blocks: - id: flex_profile_tag1 type: Tag layout: flex: 0 0 auto properties: title: Design color: blue - id: flex_profile_tag2 type: Tag layout: flex: 0 0 auto properties: title: UX Research color: purple - id: flex_profile_tag3 type: Tag layout: flex: 0 0 auto properties: title: Figma color: cyan - id: flex_profile_actions type: Flex layout: flex: 0 0 auto properties: gap: 8 blocks: - id: flex_profile_btn_msg type: Button layout: flex: 0 0 auto properties: title: Message icon: mail color: primary variant: outlined events: onClick: - id: flex_profile_msg_action type: DisplayMessage params: content: Opening message composer... status: info - id: flex_profile_btn_follow type: Button layout: flex: 0 0 auto properties: title: Follow icon: UserPlus color: primary variant: solid events: onClick: - id: flex_profile_follow_action type: SetState params: following: true - id: flex_profile_follow_msg type: DisplayMessage params: content: You are now following Jane Cooper. status: success ``` Fast Builds Incremental compilation and smart caching reduce build times by up to 80% compared to traditional frameworks. Secure by Default Built-in authentication, role-based access control, and automatic input sanitization protect your application out of the box. Fully Extensible Custom plugins for blocks, connections, operators, and actions let you extend functionality to match any requirement. ```yaml - id: flex_features_row type: Flex properties: gap: 16 wrap: true align: stretch blocks: - id: flex_feature_card1 type: Card layout: flex: 1 1 240px properties: hoverable: true blocks: - id: flex_feature1_icon_row type: Flex properties: gap: 12 align: center blocks: - id: flex_feature1_icon type: Icon layout: flex: 0 0 auto properties: name: Zap color: "#1677ff" size: 28 - id: flex_feature1_title type: Title layout: flex: 0 0 auto properties: content: Fast Builds level: 5 - id: flex_feature1_desc type: Paragraph properties: content: Incremental compilation and smart caching reduce build times by up to 80% compared to traditional frameworks. - id: flex_feature1_btn type: Button properties: title: Learn More color: primary variant: link size: small events: onClick: - id: flex_feature1_link type: Link params: url: https://docs.lowdefy.com newTab: true - id: flex_feature_card2 type: Card layout: flex: 1 1 240px properties: hoverable: true blocks: - id: flex_feature2_icon_row type: Flex properties: gap: 12 align: center blocks: - id: flex_feature2_icon type: Icon layout: flex: 0 0 auto properties: name: Shield color: "#52c41a" size: 28 - id: flex_feature2_title type: Title layout: flex: 0 0 auto properties: content: Secure by Default level: 5 - id: flex_feature2_desc type: Paragraph properties: content: Built-in authentication, role-based access control, and automatic input sanitization protect your application out of the box. - id: flex_feature2_btn type: Button properties: title: Learn More color: primary variant: link size: small events: onClick: - id: flex_feature2_link type: Link params: url: https://docs.lowdefy.com newTab: true - id: flex_feature_card3 type: Card layout: flex: 1 1 240px properties: hoverable: true blocks: - id: flex_feature3_icon_row type: Flex properties: gap: 12 align: center blocks: - id: flex_feature3_icon type: Icon layout: flex: 0 0 auto properties: name: Maximize2 color: "#722ed1" size: 28 - id: flex_feature3_title type: Title layout: flex: 0 0 auto properties: content: Fully Extensible level: 5 - id: flex_feature3_desc type: Paragraph properties: content: Custom plugins for blocks, connections, operators, and actions let you extend functionality to match any requirement. - id: flex_feature3_btn type: Button properties: title: Learn More color: primary variant: link size: small events: onClick: - id: flex_feature3_link type: Link params: url: https://docs.lowdefy.com newTab: true ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `orientation` | string | `"horizontal"` | Direction of the main axis. Takes precedence over vertical. Enum: `horizontal`, `vertical`. | | `vertical` | boolean | `false` | Whether the main axis direction is vertical. | | `wrap` | boolean \| string | - | Set whether the element is displayed in a single line or in multiple lines. | | `justify` | string | - | Set the alignment of elements on the main axis. Enum: `flex-start`, `center`, `flex-end`, `space-between`, `space-around`, `space-evenly`, `start`, `end`, `left`, `right`, `stretch`, `normal`. | | `align` | string | - | Set the alignment of elements on the cross axis. Enum: `flex-start`, `center`, `flex-end`, `stretch`, `baseline`, `start`, `end`, `self-start`, `self-end`, `normal`. | | `gap` | string \| number | - | Set the gap between items. Can be "small", "medium" ("middle" is an alias), "large", a number of pixels or a CSS length. | | `flex` | string \| number | - | Flex CSS shorthand property. | | `component` | string | - | Custom element type. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design flex tokens](https://ant.design/components/flex#design-token). | | `theme.paddingXS` | number | `8` | Gap of the "small" gap preset. | | `theme.padding` | number | `16` | Gap of the "medium" gap preset. | | `theme.paddingLG` | number | `24` | Gap of the "large" gap preset. | No events defined. | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Flex element. | | Slot | Description | | --- | --- | | `content` | Child blocks laid out by the Flex container. | --- url: https://docs.lowdefy.com/md/container-blocks/footer.md --- # Footer Page footer layout section. Page content above the footer. My Application - Copyright 2026 ```yaml - id: footer_basic_layout type: Layout blocks: - id: footer_basic_content type: Content style: padding: 24px background: var(--ant-color-bg-container) minHeight: 120px blocks: - id: footer_basic_content_text type: Paragraph properties: content: Page content above the footer. - id: footer_basic_el type: Footer style: textAlign: center blocks: - id: footer_basic_text type: Paragraph properties: content: My Application - Copyright 2026 ``` Page content. Made with Lowdefy ```yaml - id: footer_links_layout type: Layout blocks: - id: footer_links_content type: Content style: padding: 24px background: var(--ant-color-bg-container) minHeight: 80px blocks: - id: footer_links_content_text type: Paragraph properties: content: Page content. - id: footer_links_el type: Footer style: textAlign: center blocks: - id: footer_links_row type: Box layout: gap: 16 justify: center blocks: - id: footer_links_privacy type: Button layout: flex: 0 0 auto properties: title: Privacy Policy color: default variant: link - id: footer_links_terms type: Button layout: flex: 0 0 auto properties: title: Terms of Service color: default variant: link - id: footer_links_contact type: Button layout: flex: 0 0 auto properties: title: Contact Us color: default variant: link - id: footer_links_copyright type: Paragraph properties: content: Made with Lowdefy style: .element: marginTop: 8px color: "#999" ``` Main website content goes here. Acme Corp Building better tools for modern teams since 2020. Product Company © 2026 Acme Corp. All rights reserved. ```yaml - id: footer_corp_layout type: Layout blocks: - id: footer_corp_content type: Content style: padding: 24px background: var(--ant-color-bg-container) minHeight: 100px blocks: - id: footer_corp_content_text type: Paragraph properties: content: Main website content goes here. - id: footer_corp_el type: Footer class: .element: bg-bg-layout style: padding: 40px 50px 24px blocks: - id: footer_corp_columns type: Box layout: gap: 32 justify: space-between blocks: - id: footer_corp_col1 type: Box layout: flex: 1 1 200px direction: column gap: 8 blocks: - id: footer_corp_col1_title type: Title properties: content: Acme Corp level: 5 - id: footer_corp_col1_desc type: Paragraph properties: content: Building better tools for modern teams since 2020. style: .element: color: "#666" - id: footer_corp_col2 type: Box layout: flex: 0 0 auto direction: column gap: 4 blocks: - id: footer_corp_col2_title type: Title properties: content: Product level: 5 - id: footer_corp_col2_link1 type: Button properties: title: Features color: default variant: link size: small - id: footer_corp_col2_link2 type: Button properties: title: Pricing color: default variant: link size: small - id: footer_corp_col2_link3 type: Button properties: title: Documentation color: default variant: link size: small - id: footer_corp_col3 type: Box layout: flex: 0 0 auto direction: column gap: 4 blocks: - id: footer_corp_col3_title type: Title properties: content: Company level: 5 - id: footer_corp_col3_link1 type: Button properties: title: About color: default variant: link size: small - id: footer_corp_col3_link2 type: Button properties: title: Careers color: default variant: link size: small - id: footer_corp_col3_link3 type: Button properties: title: Contact color: default variant: link size: small - id: footer_corp_divider type: Divider - id: footer_corp_bottom type: Box layout: justify: space-between align: center blocks: - id: footer_corp_copyright type: Paragraph layout: flex: 0 0 auto properties: content: © 2026 Acme Corp. All rights reserved. style: .element: color: "#999" margin: 0 - id: footer_corp_social type: Box layout: flex: 0 0 auto gap: 12 blocks: - id: footer_corp_social_github type: Button layout: flex: 0 0 auto properties: icon: GitBranch color: default variant: text size: small hideTitle: true - id: footer_corp_social_twitter type: Button layout: flex: 0 0 auto properties: icon: Bird color: default variant: text size: small hideTitle: true ``` Dashboard Admin Portal v2.4.1 - Powered by Lowdefy ```yaml - id: footer_admin_layout type: Layout properties: hasSider: true blocks: - id: footer_admin_sider type: Sider layout: flex: 0 0 auto properties: width: 200 theme: dark style: minHeight: 200px blocks: - id: footer_admin_sider_menu type: Menu properties: theme: dark mode: inline links: - id: footer_admin_menu_dashboard type: MenuLink properties: title: Dashboard icon: Gauge - id: footer_admin_menu_users type: MenuLink properties: title: Users icon: user - id: footer_admin_menu_settings type: MenuLink properties: title: Settings icon: settings - id: footer_admin_inner_layout type: Layout layout: flex: 1 1 auto blocks: - id: footer_admin_content type: Content style: padding: 24px background: var(--ant-color-bg-container) minHeight: 120px blocks: - id: footer_admin_content_title type: Title properties: content: Dashboard level: 3 - id: footer_admin_stats_row type: Box layout: gap: 16 blocks: - id: footer_admin_stat1 type: Statistic layout: flex: 1 1 0 properties: title: Total Users value: 1284 - id: footer_admin_stat2 type: Statistic layout: flex: 1 1 0 properties: title: Active Sessions value: 312 - id: footer_admin_stat3 type: Statistic layout: flex: 1 1 0 properties: title: Pending Tasks value: 47 - id: footer_admin_el type: Footer style: textAlign: center padding: 12px 50px blocks: - id: footer_admin_text type: Paragraph properties: content: Admin Portal v2.4.1 - Powered by Lowdefy style: .element: color: "#999" margin: 0 fontSize: 12px ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `theme` | object | - | Not applied: antd scopes Layout design tokens to the Layout element, so set tokens such as headerHeight or footerBg on the parent Layout block theme. | No events defined. | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Footer element. | | Slot | Description | | --- | --- | | `content` | Child blocks in the footer. | --- url: https://docs.lowdefy.com/md/container-blocks/googlemaps.md --- # GoogleMaps Interactive Google Map with markers, info windows, marker clusterers, and heatmap layer. Must be wrapped in a `GoogleMapsScript` block. Supports auto-bounding, custom map styles, and events for clicks, zoom changes, and marker interactions. > The GoogleMaps block must be wrapped in a GoogleMapsScript block with a valid Google Maps API key. The examples on this page require a configured API key to render maps. ```yaml - id: gm_default type: GoogleMaps ``` ```yaml - id: gm_center_paris type: GoogleMaps properties: map: center: lat: 48.8566 lng: 2.3522 zoom: 14 ``` ```yaml - id: gm_type_satellite type: GoogleMaps properties: map: center: lat: 37.7749 lng: -122.4194 zoom: 13 mapTypeId: satellite ``` ```yaml - id: gm_no_default_ui type: GoogleMaps properties: map: center: lat: 34.0522 lng: -118.2437 zoom: 10 disableDefaultUI: true - id: gm_selective_controls type: GoogleMaps properties: map: center: lat: 34.0522 lng: -118.2437 zoom: 10 zoomControl: false streetViewControl: false fullscreenControl: true mapTypeControl: false ``` ```yaml - id: gm_single_marker type: GoogleMaps properties: map: center: lat: 48.8566 lng: 2.3522 zoom: 14 markers: - position: lat: 48.8584 lng: 2.2945 label: E title: Eiffel Tower ``` ```yaml - id: gm_multi_markers type: GoogleMaps properties: map: center: lat: 40.7484 lng: -73.9857 zoom: 12 markers: - position: lat: 40.6892 lng: -74.0445 label: A title: Statue of Liberty - position: lat: 40.7484 lng: -73.9857 label: B title: Empire State Building - position: lat: 40.7589 lng: -73.9851 label: C title: Times Square - position: lat: 40.7527 lng: -73.9772 label: D title: Grand Central Terminal ``` ```yaml - id: gm_clusterer_basic type: GoogleMaps properties: map: center: lat: 51.51 lng: -0.13 zoom: 12 markerClusterers: - markers: - position: lat: 51.5074 lng: -0.1278 - position: lat: 51.5155 lng: -0.1419 - position: lat: 51.5014 lng: -0.1419 - position: lat: 51.5194 lng: -0.127 - position: lat: 51.5033 lng: -0.1195 - id: gm_clusterer_options type: GoogleMaps properties: map: center: lat: 51.51 lng: -0.13 zoom: 10 markerClusterers: - markers: - position: lat: 51.5074 lng: -0.1278 - position: lat: 51.5155 lng: -0.1419 - position: lat: 51.5014 lng: -0.1419 - position: lat: 51.5194 lng: -0.127 - position: lat: 51.5033 lng: -0.1195 - position: lat: 51.49 lng: -0.14 - position: lat: 51.525 lng: -0.11 options: averageCenter: true gridSize: 60 minimumClusterSize: 3 zoomOnClick: true ``` ```yaml - id: gm_info_window type: GoogleMaps properties: map: center: lat: -33.8568 lng: 151.2153 zoom: 15 markers: - position: lat: -33.8568 lng: 151.2153 title: Sydney Opera House infoWindow: position: lat: -33.8568 lng: 151.2153 ``` ```yaml - id: gm_auto_bounds_enabled type: GoogleMaps properties: markers: - position: lat: 48.8566 lng: 2.3522 label: Paris - position: lat: 35.6762 lng: 139.6503 label: Tokyo - position: lat: -33.8688 lng: 151.2093 label: Sydney - id: gm_auto_bounds_disabled type: GoogleMaps properties: autoBounds: false map: center: lat: 0 lng: 0 zoom: 2 markers: - position: lat: 48.8566 lng: 2.3522 - position: lat: 35.6762 lng: 139.6503 ``` ```yaml - id: gm_custom_style_tall type: GoogleMaps properties: style: height: 500px map: center: lat: 40.7128 lng: -74.006 zoom: 12 - id: gm_custom_style_compact type: GoogleMaps properties: style: height: 200px map: center: lat: 51.5074 lng: -0.1278 zoom: 10 ``` ```yaml - id: gm_heatmap type: GoogleMaps properties: map: center: lat: 37.775 lng: -122.434 zoom: 13 heatmap: data: - lat: 37.782 lng: -122.447 weight: 3 - lat: 37.782 lng: -122.445 weight: 2 - lat: 37.782 lng: -122.443 weight: 5 - lat: 37.78 lng: -122.44 weight: 1 - lat: 37.779 lng: -122.438 weight: 4 - lat: 37.774 lng: -122.43 weight: 2 - lat: 37.77 lng: -122.425 weight: 3 ``` ```yaml - id: gm_events_click type: GoogleMaps properties: map: center: lat: 51.5074 lng: -0.1278 zoom: 12 markers: - position: lat: 51.5074 lng: -0.1278 label: A title: Central London events: onClick: - id: gm_click_action type: DisplayMessage params: content: Map clicked! duration: 3 onMarkerClick: - id: gm_marker_click_action type: DisplayMessage params: content: Marker clicked! duration: 3 onZoomChanged: - id: gm_zoom_action type: SetState params: lastZoomEvent: zoom level changed ``` ```yaml - id: gm_tailwind_rounded type: GoogleMaps class: rounded-xl overflow-hidden shadow-lg properties: map: center: lat: 48.8566 lng: 2.3522 zoom: 13 - id: gm_tailwind_bordered type: GoogleMaps class: rounded-lg border-2 border-blue-400 shadow-md properties: map: center: lat: 35.6762 lng: 139.6503 zoom: 12 ``` ```yaml - id: gm_css_key_element type: GoogleMaps class: element: rounded-2xl overflow-hidden shadow-xl border border-gray-200 properties: map: center: lat: 40.4168 lng: -3.7038 zoom: 13 ``` Coffee Shop Locations Find your nearest branch. Click a marker for details. ```yaml - id: gm_store_locator_title type: Title properties: content: Coffee Shop Locations level: 4 - id: gm_store_locator_description type: Paragraph properties: content: Find your nearest branch. Click a marker for details. - id: gm_store_locator_map type: GoogleMaps class: rounded-lg overflow-hidden shadow-md properties: style: height: 400px map: center: lat: 40.758 lng: -73.9855 zoom: 14 markers: - position: lat: 40.7614 lng: -73.9776 label: "1" title: 5th Avenue Branch - position: lat: 40.7549 lng: -73.984 label: "2" title: Times Square Branch - position: lat: 40.7527 lng: -73.9772 label: "3" title: Grand Central Branch events: onMarkerClick: - id: gm_store_locator_marker_click type: DisplayMessage params: content: Loading branch details... duration: 2 ``` Active Deliveries ```yaml - id: gm_delivery_header type: Box layout: gap: 16 align: center blocks: - id: gm_delivery_title type: Title layout: flex: 1 1 0 properties: content: Active Deliveries level: 4 - id: gm_delivery_count type: Tag layout: flex: 0 0 auto properties: title: 4 in transit color: processing - id: gm_delivery_map type: GoogleMaps class: rounded-lg overflow-hidden border border-gray-200 properties: style: height: 450px map: center: lat: 51.51 lng: -0.12 zoom: 12 markers: - position: lat: 51.5225 lng: -0.0835 label: "1" title: "Order #1042 - En route" - position: lat: 51.5074 lng: -0.1278 label: "2" title: "Order #1043 - Arriving soon" - position: lat: 51.4975 lng: -0.1357 label: "3" title: "Order #1044 - Picked up" - position: lat: 51.5155 lng: -0.1419 label: "4" title: "Order #1045 - En route" events: onMarkerClick: - id: gm_delivery_marker_action type: DisplayMessage params: content: Opening delivery details... duration: 2 - id: gm_delivery_legend type: Box layout: gap: 16 blocks: - id: gm_delivery_tag1 type: Tag properties: title: En route color: blue - id: gm_delivery_tag2 type: Tag properties: title: Arriving soon color: green - id: gm_delivery_tag3 type: Tag properties: title: Picked up color: orange ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `autoBounds` | boolean | `true` | Automatically fit the map bounds to include all markers, clusterers, and info windows. | | `map` | object | - | Map settings object. | | `map.center` | object | - | A coordinate position object by which to center the map. | | `map.center.lat` | number | - | Lateral coordinate. | | `map.center.lng` | number | - | Longitudinal coordinate. | | `map.zoom` | number | - | Map zoom level. | | `map.options` | object | - | Map options. See [Google Maps MapOptions](https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions). | | `heatmap` | object | - | Add a heatmap layer. This will automatically load the visualization library. See [heatmap options](https://developers.google.com/maps/documentation/javascript/reference/visualization#HeatmapLayerOptions). | | `heatmap.data` | array | - | A list of heatmap data points. | | `heatmap.data.$.lat` | number | - | Lateral coordinate. | | `heatmap.data.$.lng` | number | - | Longitudinal coordinate. | | `heatmap.data.$.weight` | number | - | Item weight on heatmap. | | `markers` | array | - | A list of markers with marker options. See [Google Maps Markers](https://developers.google.com/maps/documentation/javascript/markers). | | `markers.$.position` | object | - | | | `markers.$.position.lat` | number | - | Lateral coordinate. | | `markers.$.position.lng` | number | - | Longitudinal coordinate. | | `markers.$.label` | string | - | Label displayed on marker. | | `markers.$.title` | string | - | Marker title shown on hover. | | `markerClusterers` | array | - | A list of marker clusterers with marker clusterer options. | | `markerClusterers.$.markers` | array | - | A list of markers with marker options. | | `markerClusterers.$.markers.$.position` | object | - | | | `markerClusterers.$.markers.$.position.lat` | number | - | Lateral coordinate. | | `markerClusterers.$.markers.$.position.lng` | number | - | Longitudinal coordinate. | | `markerClusterers.$.markers.$.label` | string | - | Label displayed on marker. | | `markerClusterers.$.options` | object | - | Marker clusterer options. See [MarkerClusterer](https://react-google-maps-api-docs.netlify.app/#markerclusterer). | | `markerClusterers.$.options.averageCenter` | boolean | - | Whether the position of a cluster marker should be the average position of all markers in the cluster. | | `markerClusterers.$.options.gridSize` | number | - | The grid size of a cluster in pixels. | | `markerClusterers.$.options.maxZoom` | number | - | The maximum zoom level at which clustering is enabled. | | `markerClusterers.$.options.minimumClusterSize` | number | - | The minimum number of markers needed to form a cluster. | | `markerClusterers.$.options.styles` | array | - | Styles of the cluster markers to be used. | | `markerClusterers.$.options.zoomOnClick` | boolean | - | Whether to zoom the map when a cluster marker is clicked. | | `infoWindow` | object | - | Info window options. See [InfoWindowOptions](https://developers.google.com/maps/documentation/javascript/reference/info-window#InfoWindowOptions). | | `infoWindow.visible` | boolean | - | When visible is true, blocks inside the infoWindow content area will be rendered. | | `infoWindow.position` | object | - | | | `infoWindow.position.lat` | number | - | Lateral coordinate. | | `infoWindow.position.lng` | number | - | Longitudinal coordinate. | | Event | Event Data | Description | | --- | --- | --- | | `onBoundsChanged` | \- | Trigger actions when the bounds of the map are changed. | | `onCenterChanged` | \- | Trigger actions when the center of the map is changed. | | `onClick` | \- | Trigger actions when the map is clicked. | | `onClusterClick` | \- | Trigger actions when a marker cluster is clicked. | | `onMarkerClick` | \- | Trigger actions when a marker is clicked. | | `onZoomChanged` | \- | Trigger actions when the zoom on the map is changed. | | `onInfoWindowCloseClick` | \- | Trigger actions when the info window close button is clicked. | | `onInfoWindowPositionChanged` | \- | Trigger actions when the info window position changes. | | `onLoad` | \- | Trigger actions when the map is loaded. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The map container element. | | Slot | Description | | --- | --- | | `content` | Marker blocks on the map. | | `infoWindow` | Info window popup content. | --- url: https://docs.lowdefy.com/md/container-blocks/googlemapsheatmap.md --- # GoogleMapsHeatmap Google Map with a heatmap visualization layer for displaying data density. Must be wrapped in a `GoogleMapsScript` block with `libraries: [visualization]`. Supports weighted data points, configurable radius and opacity, markers, and info windows. > The GoogleMapsHeatmap block must be wrapped in a GoogleMapsScript block with a valid Google Maps API key and the visualization library. The examples on this page require a configured API key to render maps. ```yaml - id: heatmap_basic type: GoogleMapsHeatmap properties: map: center: lat: 37.775 lng: -122.434 zoom: 13 heatmap: data: - lat: 37.782 lng: -122.447 - lat: 37.782 lng: -122.445 - lat: 37.782 lng: -122.443 - lat: 37.784 lng: -122.441 - lat: 37.786 lng: -122.439 - lat: 37.786 lng: -122.435 - lat: 37.77 lng: -122.432 - lat: 37.768 lng: -122.43 ``` ```yaml - id: heatmap_zoomed_out type: GoogleMapsHeatmap properties: map: center: lat: 51.509 lng: -0.118 zoom: 10 heatmap: data: - lat: 51.512 lng: -0.091 - lat: 51.507 lng: -0.076 - lat: 51.503 lng: -0.119 - lat: 51.515 lng: -0.142 - id: heatmap_zoomed_in type: GoogleMapsHeatmap properties: map: center: lat: 51.509 lng: -0.118 zoom: 15 heatmap: data: - lat: 51.512 lng: -0.091 - lat: 51.507 lng: -0.076 - lat: 51.503 lng: -0.119 - lat: 51.515 lng: -0.142 ``` ```yaml - id: heatmap_map_options type: GoogleMapsHeatmap properties: map: center: lat: 40.7128 lng: -74.006 zoom: 12 options: mapTypeId: satellite disableDefaultUI: true heatmap: data: - lat: 40.7128 lng: -74.006 - lat: 40.72 lng: -73.998 - lat: 40.706 lng: -74.01 - lat: 40.718 lng: -73.99 ``` ```yaml - id: heatmap_weighted type: GoogleMapsHeatmap properties: autoBounds: false map: center: lat: 51.509 lng: -0.118 zoom: 13 heatmap: data: - location: lat: 51.512 lng: -0.091 weight: 10 - location: lat: 51.507 lng: -0.076 weight: 5 - location: lat: 51.503 lng: -0.119 weight: 8 - location: lat: 51.515 lng: -0.142 weight: 3 - location: lat: 51.52 lng: -0.105 weight: 7 ``` ```yaml - id: heatmap_radius_small type: GoogleMapsHeatmap properties: map: center: lat: 40.7128 lng: -74.006 zoom: 12 heatmap: radius: 15 data: - lat: 40.7128 lng: -74.006 - lat: 40.72 lng: -73.998 - lat: 40.706 lng: -74.01 - lat: 40.718 lng: -73.99 - lat: 40.71 lng: -74.015 - id: heatmap_radius_large type: GoogleMapsHeatmap properties: map: center: lat: 40.7128 lng: -74.006 zoom: 12 heatmap: radius: 50 data: - lat: 40.7128 lng: -74.006 - lat: 40.72 lng: -73.998 - lat: 40.706 lng: -74.01 - lat: 40.718 lng: -73.99 - lat: 40.71 lng: -74.015 - id: heatmap_opacity_low type: GoogleMapsHeatmap properties: map: center: lat: 34.0522 lng: -118.2437 zoom: 12 heatmap: opacity: 0.3 data: - lat: 34.0522 lng: -118.2437 - lat: 34.06 lng: -118.25 - lat: 34.048 lng: -118.235 - lat: 34.055 lng: -118.26 - id: heatmap_opacity_full type: GoogleMapsHeatmap properties: map: center: lat: 34.0522 lng: -118.2437 zoom: 12 heatmap: opacity: 1 data: - lat: 34.0522 lng: -118.2437 - lat: 34.06 lng: -118.25 - lat: 34.048 lng: -118.235 - lat: 34.055 lng: -118.26 ``` ```yaml - id: heatmap_no_auto_bounds type: GoogleMapsHeatmap properties: autoBounds: false map: center: lat: 35.6762 lng: 139.6503 zoom: 11 heatmap: data: - lat: 35.6812 lng: 139.7671 - lat: 35.6595 lng: 139.7005 - lat: 35.6938 lng: 139.7034 - lat: 35.6586 lng: 139.7454 ``` ```yaml - id: heatmap_with_markers type: GoogleMapsHeatmap properties: map: center: lat: 48.8566 lng: 2.3522 zoom: 13 markers: - position: lat: 48.8584 lng: 2.2945 label: A - position: lat: 48.8606 lng: 2.3376 label: B - position: lat: 48.853 lng: 2.3499 label: C heatmap: radius: 25 data: - lat: 48.8584 lng: 2.2945 - lat: 48.8606 lng: 2.3376 - lat: 48.853 lng: 2.3499 - lat: 48.862 lng: 2.332 - lat: 48.8566 lng: 2.3522 ``` ```yaml - id: heatmap_custom_style type: GoogleMapsHeatmap properties: style: width: 100% height: 500px map: center: lat: -33.8688 lng: 151.2093 zoom: 12 heatmap: radius: 30 data: - lat: -33.8688 lng: 151.2093 - lat: -33.856 lng: 151.2153 - lat: -33.875 lng: 151.2 - lat: -33.861 lng: 151.21 ``` ```yaml - id: heatmap_events type: GoogleMapsHeatmap properties: map: center: lat: 48.8566 lng: 2.3522 zoom: 13 markers: - position: lat: 48.8584 lng: 2.2945 label: A - position: lat: 48.8606 lng: 2.3376 label: B heatmap: data: - lat: 48.8584 lng: 2.2945 - lat: 48.8606 lng: 2.3376 - lat: 48.8566 lng: 2.3522 events: onClick: - id: heatmap_click_display type: DisplayMessage params: content: Map clicked! status: info onMarkerClick: - id: heatmap_marker_click_msg type: DisplayMessage params: content: Marker clicked! status: success onZoomChanged: - id: heatmap_zoom_set_state type: SetState params: currentZoom: _event: zoom ``` ```yaml - id: heatmap_css_element type: GoogleMapsHeatmap class: element: rounded-xl shadow-lg border-2 border-blue-300 properties: map: center: lat: 37.775 lng: -122.434 zoom: 13 heatmap: data: - lat: 37.782 lng: -122.447 - lat: 37.782 lng: -122.445 - lat: 37.784 lng: -122.441 - lat: 37.786 lng: -122.435 - id: heatmap_css_rounded_shadow type: GoogleMapsHeatmap class: element: rounded-2xl shadow-2xl overflow-hidden properties: style: height: 350px map: center: lat: 51.509 lng: -0.118 zoom: 13 heatmap: data: - lat: 51.512 lng: -0.091 - lat: 51.507 lng: -0.076 - lat: 51.503 lng: -0.119 ``` Store Foot Traffic Analysis Visualizing customer activity density across downtown locations. Higher intensity areas indicate more foot traffic during peak hours. ```yaml - id: store_dashboard_title type: Title properties: content: Store Foot Traffic Analysis level: 3 - id: store_dashboard_desc type: Paragraph properties: content: Visualizing customer activity density across downtown locations. Higher intensity areas indicate more foot traffic during peak hours. - id: store_dashboard_map type: GoogleMapsHeatmap class: element: rounded-lg shadow-md properties: style: height: 450px map: center: lat: 40.758 lng: -73.9855 zoom: 14 markers: - position: lat: 40.7614 lng: -73.9776 label: S1 - position: lat: 40.7549 lng: -73.984 label: S2 - position: lat: 40.7587 lng: -73.99 label: S3 heatmap: radius: 35 opacity: 0.7 data: - lat: 40.7614 lng: -73.9776 - lat: 40.76 lng: -73.979 - lat: 40.759 lng: -73.98 - lat: 40.7549 lng: -73.984 - lat: 40.756 lng: -73.983 - lat: 40.7587 lng: -73.99 - lat: 40.7575 lng: -73.987 events: onMarkerClick: - id: store_marker_click_msg type: DisplayMessage params: content: Click a store marker to view detailed foot traffic analytics. status: info ``` Real-time air quality index readings from distributed sensor stations. Warmer areas on the heatmap indicate higher pollutant concentrations. Markers show individual sensor locations. ```yaml - id: sensor_network_card type: Card properties: title: Air Quality Monitoring - San Francisco Bay Area slots: extra: blocks: - id: sensor_network_refresh_btn type: Button layout: flex: 0 0 auto properties: title: Refresh Data icon: refresh color: primary variant: outlined size: small events: onClick: - id: sensor_refresh_msg type: DisplayMessage params: content: Sensor data refreshed. status: success blocks: - id: sensor_network_desc type: Paragraph properties: content: Real-time air quality index readings from distributed sensor stations. Warmer areas on the heatmap indicate higher pollutant concentrations. Markers show individual sensor locations. - id: sensor_network_map type: GoogleMapsHeatmap class: element: rounded-lg properties: autoBounds: false style: height: 500px map: center: lat: 37.775 lng: -122.418 zoom: 12 options: mapTypeControl: false streetViewControl: false markers: - position: lat: 37.7849 lng: -122.4094 label: "1" - position: lat: 37.7694 lng: -122.4862 label: "2" - position: lat: 37.7599 lng: -122.4148 label: "3" - position: lat: 37.7909 lng: -122.4 label: "4" - position: lat: 37.775 lng: -122.435 label: "5" heatmap: radius: 40 opacity: 0.6 data: - lat: 37.7849 lng: -122.4094 - lat: 37.7694 lng: -122.4862 - lat: 37.7599 lng: -122.4148 - lat: 37.7909 lng: -122.4 - lat: 37.775 lng: -122.435 - lat: 37.78 lng: -122.42 - lat: 37.772 lng: -122.45 events: onMarkerClick: - id: sensor_marker_click_msg type: DisplayMessage params: content: Select a sensor station to view detailed air quality readings. status: info onClick: - id: sensor_map_click_set type: SetState params: selectedLocation: _event: latLng ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `autoBounds` | boolean | `true` | Automatically fit the map bounds to include all markers and heatmap data points. | | `map` | object | - | Map settings object. | | `map.center` | object | - | A coordinate position object by which to center the map. | | `map.center.lat` | number | - | Lateral coordinate. | | `map.center.lng` | number | - | Longitudinal coordinate. | | `map.zoom` | number | - | Map zoom level. | | `map.options` | object | - | Map options. See [Google Maps MapOptions](https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions). | | `heatmap` | object | - | Heatmap layer options. See [heatmap options](https://developers.google.com/maps/documentation/javascript/reference/visualization#HeatmapLayerOptions). | | `heatmap.data` | array | - | A list of heatmap data points. | | `heatmap.data.$.lat` | number | - | Lateral coordinate. | | `heatmap.data.$.lng` | number | - | Longitudinal coordinate. | | `heatmap.data.$.weight` | number | - | Item weight on heatmap. | | `heatmap.radius` | number | - | The radius of influence for each data point, in pixels. | | `heatmap.opacity` | number | - | The opacity of the heatmap, from 0 to 1. | | `markers` | array | - | A list of markers with marker options. See [Google Maps Markers](https://developers.google.com/maps/documentation/javascript/markers). | | `markers.$.position` | object | - | | | `markers.$.position.lat` | number | - | Lateral coordinate. | | `markers.$.position.lng` | number | - | Longitudinal coordinate. | | `markers.$.label` | string | - | Label displayed on marker. | | `infoWindow` | object | - | Info window options. See [InfoWindowOptions](https://developers.google.com/maps/documentation/javascript/reference/info-window#InfoWindowOptions). | | `infoWindow.visible` | boolean | - | When visible is true, blocks inside the infoWindow content area will be rendered. | | `infoWindow.position` | object | - | | | `infoWindow.position.lat` | number | - | Lateral coordinate. | | `infoWindow.position.lng` | number | - | Longitudinal coordinate. | | Event | Event Data | Description | | --- | --- | --- | | `onClick` | \- | Trigger actions when the map is clicked. | | `onMarkerClick` | \- | Trigger actions when a marker is clicked. | | `onZoomChanged` | \- | Trigger actions when the zoom on the map is changed. | | `onBoundsChanged` | \- | Trigger actions when the bounds of the map are changed. | | `onCenterChanged` | \- | Trigger actions when the center of the map is changed. | | `onInfoWindowCloseClick` | \- | Trigger actions when the info window close button is clicked. | | `onInfoWindowPositionChanged` | \- | Trigger actions when the info window position changes. | | `onLoad` | \- | Trigger actions when the map is loaded. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The map container element. | | Slot | Description | | --- | --- | | `content` | Marker blocks on the map. | | `infoWindow` | Info window popup content. | --- url: https://docs.lowdefy.com/md/container-blocks/googlemapsscript.md --- # GoogleMapsScript Loads the Google Maps JavaScript API and wraps `GoogleMaps`, `GoogleMapsHeatmap` and `PlacesAutocomplete` blocks as a parent container. Configure the API key, language, region, libraries, and API version. Blocks that use an optional library should be wrapped in a GoogleMapsScript that loads it — list `libraries: [places]` for `PlacesAutocomplete` so the library is ready on first render. When it is not listed, `PlacesAutocomplete` imports it on demand through `google.maps.importLibrary`. > GoogleMapsScript loads the Google Maps JavaScript API. Only one GoogleMapsScript should be used per page. It must wrap GoogleMaps, GoogleMapsHeatmap or PlacesAutocomplete blocks as a parent container. The examples below show configuration patterns — maps require a valid API key to render. ```yaml - id: gms_full_config type: GoogleMapsScript properties: apiKey: null language: en region: US version: weekly libraries: - visualization - places channel: production blocks: - id: gms_full_config_map type: GoogleMaps properties: map: center: lat: 40.7128 lng: -74.006 zoom: 12 markers: - position: lat: 40.7484 lng: -73.9857 label: A title: Empire State Building - position: lat: 40.6892 lng: -74.0445 label: B title: Statue of Liberty - position: lat: 40.7589 lng: -73.9851 label: C title: Times Square ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `apiKey` | string | - | Your Google Maps API key. | | `libraries` | array | - | A list of Google libraries to load. See [Google Maps Libraries](https://developers.google.com/maps/documentation/javascript/libraries). | | `language` | string | - | The language code for the Google Maps API (e.g. "en", "fr", "ja"). | | `region` | string | - | The region code to bias geocoding results (e.g. "US", "GB"). See [localization](https://developers.google.com/maps/documentation/javascript/localization). | | `version` | string | - | The version of the Google Maps JavaScript API to load (e.g. "weekly", "3.50"). | | `channel` | string | - | A channel parameter for tracking API usage. | No events defined. No CSS keys defined. | Slot | Description | | --- | --- | | `content` | Child blocks wrapped by the script provider. | --- url: https://docs.lowdefy.com/md/container-blocks/header.md --- # Header Page header layout section. Supports built-in notifications, profile avatar, and dark mode toggle. ```yaml - id: header_basic type: Header style: borderBottom: 1px solid var(--ant-color-border) blocks: - id: header_basic_logo type: Html layout: flex: 0 0 auto properties: html: MyApp ``` ```yaml - id: header_nav type: Header style: borderBottom: 1px solid var(--ant-color-border) blocks: - id: header_nav_logo type: Html layout: flex: 0 0 auto properties: html: MyApp - id: header_nav_home type: Button layout: flex: 0 0 auto properties: title: Home icon: home color: default variant: link - id: header_nav_about type: Button layout: flex: 0 0 auto properties: title: About color: default variant: link - id: header_nav_contact type: Button layout: flex: 0 0 auto properties: title: Contact color: default variant: link ``` ```yaml - id: header_profile type: Header properties: darkModeToggle: true notifications: count: 3 profile: avatar: content: JD color: "#1677ff" links: - id: header_prof_profile type: MenuLink properties: title: My Profile icon: user - id: header_prof_settings type: MenuLink properties: title: Settings icon: settings - id: header_prof_divider type: MenuDivider - id: header_prof_logout type: MenuLink properties: title: Logout icon: logout danger: true style: borderBottom: 1px solid var(--ant-color-border) blocks: - id: header_profile_logo type: Html layout: flex: 0 0 auto properties: html: Dashboard - id: header_profile_overview type: Button layout: flex: 0 0 auto properties: title: Overview icon: Gauge color: default variant: link - id: header_profile_projects type: Button layout: flex: 0 0 auto properties: title: Projects icon: SquareKanban color: default variant: link ``` Build Better Products, Faster A Header for marketing pages with button navigation and call-to-action buttons. ```yaml - id: header_marketing type: Layout style: minHeight: 300px blocks: - id: header_marketing_el type: Header style: borderBottom: 1px solid var(--ant-color-border) blocks: - id: header_marketing_brand type: Html layout: flex: 0 0 auto properties: html: Acme Inc - id: header_marketing_nav_features type: Button layout: flex: 0 0 auto properties: title: Features color: default variant: link - id: header_marketing_nav_pricing type: Button layout: flex: 0 0 auto properties: title: Pricing color: default variant: link - id: header_marketing_nav_docs type: Button layout: flex: 0 0 auto properties: title: Docs color: default variant: link - id: header_marketing_spacer type: Box layout: flex: 1 1 0 - id: header_marketing_btn_login type: Button layout: flex: 0 0 auto properties: title: Log In color: default variant: outlined - id: header_marketing_btn_signup type: Button layout: flex: 0 0 auto properties: title: Get Started color: primary variant: solid - id: header_marketing_content type: Content style: padding: 64px 48px textAlign: center blocks: - id: header_marketing_hero_title type: Title properties: content: Build Better Products, Faster level: 1 - id: header_marketing_hero_subtitle type: Paragraph properties: content: A Header for marketing pages with button navigation and call-to-action buttons. ``` ```yaml - id: header_token_branded_layout type: Layout properties: theme: headerHeight: 56 headerPadding: 0 24px blocks: - id: header_token_branded type: Header properties: color: "#0958d9" blocks: - id: header_token_branded_title type: Html layout: flex: 0 0 auto properties: html: Branded Header - id: header_token_branded_spacer type: Box layout: flex: 1 1 0 - id: header_token_branded_subtitle type: Html layout: flex: 0 0 auto properties: html: color property with Layout headerHeight and headerPadding tokens - id: header_token_compact_layout type: Layout properties: theme: headerHeight: 40 headerPadding: 0 16px blocks: - id: header_token_compact type: Header style: borderBottom: 1px solid var(--ant-color-border) blocks: - id: header_token_compact_title type: Html properties: html: Compact Header (40px height) ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `color` | string | - | Header background color. Accepts any CSS color value. Defaults to the antd container background color (light in light mode, dark in dark mode). | | `iconsColor` | string | - | Color for the notification, profile, and dark mode toggle icons. Use when the header has a dark background color. | | `notifications` | object | - | Notification bell icon with badge in the header. Renders when configured. Use the link property to navigate when clicked. | | `notifications.link` | object | - | Link to navigate to when the notification bell is clicked. | | `notifications.link.pageId` | string | - | Page to link to. | | `notifications.link.url` | string | - | External URL to link to. | | `notifications.link.newTab` | boolean | - | Open link in new tab. | | `notifications.count` | number | - | Number to display on the badge. Set to 0 to hide the badge (unless showZero is true). | | `notifications.dot` | boolean | `false` | Show a dot instead of a count number. | | `notifications.showZero` | boolean | `false` | Show badge when count is zero. | | `notifications.overflowCount` | number | `99` | Max count to show. Values above this display as "N+". | | `notifications.color` | string | - | Badge color. | | `notifications.icon` | string \| object | - | Icon for the notification button. Defaults to `bell`. | | `notifications.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `notifications.icon.color` | string | - | Icon color. | | `notifications.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `notifications.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `notifications.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `notifications.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `notifications.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `notifications.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `notifications.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `notifications.size` | string | `"small"` | Size of the notification button. Enum: `small`, `default`, `large`. | | `profile` | object | - | Profile avatar with optional dropdown menu in the header. Renders when configured. Use with the _user operator to populate from the authenticated user. | | `profile.avatar` | object | - | Avatar display properties. | | `profile.avatar.src` | string | - | Image URL for the avatar. Typically bound to _user: image. | | `profile.avatar.content` | string | - | Text content inside the avatar (e.g. user initials). Shown when no src is provided. | | `profile.avatar.icon` | string \| object | - | Icon to display in avatar when no src or content is set. Defaults to `user`. | | `profile.avatar.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `profile.avatar.icon.color` | string | - | Icon color. | | `profile.avatar.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `profile.avatar.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `profile.avatar.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `profile.avatar.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `profile.avatar.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `profile.avatar.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `profile.avatar.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `profile.avatar.color` | string | - | Background color of the avatar when not using src. | | `profile.avatar.size` | string \| number | `"small"` | Size of the avatar: default, small, large or a pixel number. | | `profile.avatar.shape` | string | `"circle"` | Shape of the avatar. Enum: `circle`, `square`. | | `profile.links` | array | - | Dropdown menu items. Uses the same MenuLink/MenuGroup/MenuDivider pattern as DropdownMenu. When links are provided, clicking the avatar opens a dropdown menu. | | `profile.links.$.id` | string | - | Menu item id. | | `profile.links.$.type` | string | `"MenuLink"` | Menu item type. Enum: `MenuDivider`, `MenuLink`, `MenuGroup`. | | `profile.links.$.pageId` | string | - | Page to link to. | | `profile.links.$.url` | string | - | External URL to link to. | | `profile.links.$.newTab` | boolean | - | Open link in new tab. | | `profile.links.$.properties` | object | - | Properties for the menu item. | | `profile.links.$.properties.title` | string | - | Menu item title. | | `profile.links.$.properties.icon` | string \| object | - | Icon for the menu item. | | `profile.links.$.properties.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `profile.links.$.properties.icon.color` | string | - | Icon color. | | `profile.links.$.properties.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `profile.links.$.properties.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `profile.links.$.properties.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `profile.links.$.properties.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `profile.links.$.properties.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `profile.links.$.properties.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `profile.links.$.properties.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `profile.links.$.properties.danger` | boolean | `false` | Apply danger style to menu item. | | `profile.links.$.properties.disabled` | boolean | `false` | Disable the menu item. | | `profile.trigger` | string | `"hover"` | How the profile dropdown opens. Enum: `click`, `hover`. | | `profile.placement` | string | `"bottomRight"` | Dropdown placement relative to the avatar. Enum: `bottomLeft`, `bottom`, `bottomRight`, `topLeft`, `top`, `topRight`. | | `profile.arrow` | boolean \| object | `false` | Show arrow on the dropdown. | | `profile.arrow.pointAtCenter` | boolean | - | | | `darkModeToggle` | boolean | `false` | Show a dark mode toggle icon in the header. Toggles the Ant Design dark theme for the entire page. Preference is persisted to localStorage. | | `theme` | object | - | Not applied: antd scopes Layout design tokens to the Layout element, so set tokens such as headerHeight or footerBg on the parent Layout block theme. | | Event | Event Data | Description | | --- | --- | --- | | `onProfileMenuClick` | `{ key, keyPath, pageId, url }` | Trigger action when a profile dropdown menu item is clicked. | | `onProfileMenuOpen` | `{ open }` | Trigger action when the profile dropdown opens or closes. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Header element. | | `/headerActions` | The header actions container (notifications, profile, dark mode toggle). | | `/notifications` | The notification bell wrapper. | | `/notificationsBadge` | The notification badge. | | `/notificationsIcon` | The notification bell icon. | | `/profile` | The profile avatar and dropdown wrapper. | | `/profileAvatar` | The profile avatar element. | | `/profileMenu` | The profile dropdown menu popup. | | `/darkModeToggle` | The dark mode toggle wrapper. | | Slot | Description | | --- | --- | | `content` | Child blocks in the header. | --- url: https://docs.lowdefy.com/md/container-blocks/label.md --- # Label Form label wrapper with title, description, and validation feedback. ```yaml - id: label_basic type: Label properties: title: Field Label blocks: - id: label_basic_input type: TextInput - id: label_no_title type: Label blocks: - id: label_no_title_input type: TextInput ``` ```yaml - id: label_html_title type: Label properties: title: 'Blue Label' blocks: - id: label_html_title_input type: TextInput - id: label_html_bold type: Label properties: title: Bold with italic blocks: - id: label_html_bold_input type: TextInput ``` ```yaml - id: label_colon_true type: Label properties: title: With Colon (default) colon: true blocks: - id: label_colon_true_input type: TextInput - id: label_colon_false type: Label properties: title: Without Colon colon: false blocks: - id: label_colon_false_input type: TextInput ``` ```yaml - id: label_disabled type: Label properties: title: This title is hidden disabled: true blocks: - id: label_disabled_input type: TextInput - id: label_disabled_extra type: Label properties: title: Also hidden disabled: true extra: Extra text still appears when label is disabled. blocks: - id: label_disabled_extra_input type: TextInput ``` ```yaml - id: label_extra_short type: Label properties: title: Username extra: Must be between 3 and 20 characters. blocks: - id: label_extra_short_input type: TextInput - id: label_extra_html type: Label properties: title: Website extra: Enter a full URL, e.g. https://example.com blocks: - id: label_extra_html_input type: TextInput ``` ```yaml - id: label_required type: Label required: true properties: title: Email Address blocks: - id: label_required_input type: TextInput - id: label_required_extra type: Label required: true properties: title: Password extra: Minimum 8 characters. blocks: - id: label_required_extra_input type: TextInput ``` ```yaml - id: label_size_small type: Label properties: title: Small size: small blocks: - id: label_size_small_input type: TextInput properties: size: small - id: label_size_default type: Label properties: title: Default size: default blocks: - id: label_size_default_input type: TextInput - id: label_size_large type: Label properties: title: Large size: large blocks: - id: label_size_large_input type: TextInput properties: size: large ``` ```yaml - id: label_align_left type: Label properties: title: Left Aligned (default) align: left inline: true span: 6 blocks: - id: label_align_left_input type: TextInput - id: label_align_right type: Label properties: title: Right Aligned align: right inline: true span: 6 blocks: - id: label_align_right_input type: TextInput ``` ```yaml - id: label_inline type: Label properties: title: Inline Label inline: true blocks: - id: label_inline_input type: TextInput - id: label_inline_span4 type: Label properties: title: Span 4 inline: true span: 4 blocks: - id: label_inline_span4_input type: TextInput - id: label_inline_span8 type: Label properties: title: Span 8 inline: true span: 8 blocks: - id: label_inline_span8_input type: TextInput - id: label_inline_extra type: Label properties: title: Inline with Extra inline: true span: 6 extra: Helper text shown below the input. blocks: - id: label_inline_extra_input type: TextInput ``` ```yaml - id: label_wrap_inline type: Label layout: span: 12 properties: title: A long inline label that wraps onto a second line when space runs out inline: true wrap: true blocks: - id: label_wrap_inline_input type: TextInput ``` ```yaml - id: label_inline_small type: Label properties: title: Small Inline size: small inline: true span: 6 blocks: - id: label_inline_small_input type: TextInput properties: size: small - id: label_inline_default type: Label properties: title: Default Inline size: default inline: true span: 6 blocks: - id: label_inline_default_input type: TextInput - id: label_inline_large type: Label properties: title: Large Inline size: large inline: true span: 6 blocks: - id: label_inline_large_input type: TextInput properties: size: large ``` ```yaml - id: label_wrap_number type: Label properties: title: Quantity blocks: - id: label_wrap_number_input type: NumberInput properties: min: 0 max: 100 - id: label_wrap_switch type: Label properties: title: Enable Notifications blocks: - id: label_wrap_switch_input type: Switch - id: label_wrap_select type: Label properties: title: Country blocks: - id: label_wrap_select_input type: Selector properties: options: - label: United States value: us - label: United Kingdom value: uk - label: Germany value: de ``` ```yaml - id: label_theme_purple type: Label required: true properties: title: Purple Label Theme extra: Extra text inherits theme theme: labelColor: "#722ed1" labelFontSize: 16 labelRequiredMarkColor: "#722ed1" blocks: - id: label_theme_purple_input type: TextInput - id: label_theme_green_mark type: Label required: true properties: title: Green Required Mark theme: labelRequiredMarkColor: "#52c41a" blocks: - id: label_theme_green_mark_input type: TextInput - id: label_theme_colon_spacing type: Label properties: title: Wide Colon Spacing theme: labelColonMarginInlineStart: 8 labelColonMarginInlineEnd: 16 blocks: - id: label_theme_colon_input type: TextInput - id: label_theme_error_colors type: Label properties: title: Custom Feedback Colors theme: colorError: "#cf1322" colorWarning: "#d48806" colorSuccess: "#389e0d" blocks: - id: label_theme_error_colors_input type: TextInput - id: label_theme_description type: Label properties: title: Custom Description Color extra: This extra text uses a custom description color. theme: colorTextDescription: "#722ed1" fontSize: 13 blocks: - id: label_theme_description_input type: TextInput ``` ```yaml - id: label_css_label type: Label properties: title: Styled Label Text extra: Styled extra text class: .label: text-purple-600 font-bold .extra: text-purple-400 italic blocks: - id: label_css_label_input type: TextInput - id: label_css_element type: Label properties: title: Bordered Container class: .element: border border-border p-3 rounded-lg blocks: - id: label_css_element_input type: TextInput - id: label_css_feedback type: Label properties: title: Feedback Styling class: .feedback: font-semibold text-xs blocks: - id: label_css_feedback_input type: TextInput - id: label_style_inline type: Label properties: title: Inline Style Overrides extra: Styled with inline CSS style: .label: color: "#d46b08" fontWeight: bold fontSize: 16px .extra: color: "#d48806" fontStyle: italic .element: border: "1px solid #ffd591" padding: 12px borderRadius: 8px blocks: - id: label_style_inline_input type: TextInput ``` ```yaml - id: label_reg_card type: Card properties: title: Create Account blocks: - id: label_reg_name type: Label required: true properties: title: Full Name blocks: - id: label_reg_name_input type: TextInput - id: label_reg_email type: Label required: true properties: title: Email Address extra: We will never share your email with anyone. blocks: - id: label_reg_email_input type: TextInput - id: label_reg_password type: Label required: true properties: title: Password extra: Minimum 8 characters with at least one number. blocks: - id: label_reg_password_input type: PasswordInput - id: label_reg_age type: Label properties: title: Age blocks: - id: label_reg_age_input type: NumberInput properties: min: 18 max: 120 - id: label_reg_bio type: Label properties: title: Bio extra: Optional short description about yourself. colon: false blocks: - id: label_reg_bio_input type: TextArea properties: rows: 3 - id: label_reg_actions_row type: Box layout: gap: 8 justify: flex-end blocks: - id: label_reg_cancel type: Button layout: flex: 0 0 auto properties: title: Cancel color: default variant: outlined events: onClick: - id: label_reg_cancel_link type: Link params: home: true - id: label_reg_submit type: Button layout: flex: 0 0 auto properties: title: Register color: primary variant: solid events: onClick: - id: label_reg_submit_validate type: Validate params: - label_reg_name - label_reg_email - label_reg_password - id: label_reg_submit_msg type: DisplayMessage params: content: Account created successfully! status: success ``` ```yaml - id: label_settings_card type: Card properties: title: Notification Settings size: small blocks: - id: label_settings_email_notif type: Label properties: title: Email Notifications inline: true span: 8 align: right extra: Receive updates about your account activity. blocks: - id: label_settings_email_notif_input type: Switch - id: label_settings_frequency type: Label properties: title: Frequency inline: true span: 8 align: right blocks: - id: label_settings_frequency_input type: Selector properties: options: - label: Immediately value: immediate - label: Daily Digest value: daily - label: Weekly Summary value: weekly - id: label_settings_sms type: Label properties: title: SMS Alerts inline: true span: 8 align: right extra: Standard messaging rates may apply. blocks: - id: label_settings_sms_input type: Switch - id: label_settings_phone type: Label properties: title: Phone Number inline: true span: 8 align: right blocks: - id: label_settings_phone_input type: PhoneNumberInput - id: label_settings_save_row type: Box layout: justify: flex-end gap: 8 blocks: - id: label_settings_save type: Button layout: flex: 0 0 auto properties: title: Save Preferences color: primary variant: solid events: onClick: - id: label_settings_save_state type: SetState params: settingsSaved: true - id: label_settings_save_msg type: DisplayMessage params: content: Preferences saved. status: success ``` ```yaml - id: label_data_card type: Card properties: title: Add Product blocks: - id: label_data_name type: Label required: true properties: title: Product Name size: large blocks: - id: label_data_name_input type: TextInput properties: size: large - id: label_data_row type: Box layout: gap: 16 blocks: - id: label_data_price type: Label layout: flex: 1 1 0 required: true properties: title: Price blocks: - id: label_data_price_input type: NumberInput properties: min: 0 - id: label_data_category type: Label layout: flex: 1 1 0 required: true properties: title: Category blocks: - id: label_data_category_input type: Selector properties: options: - label: Electronics value: electronics - label: Clothing value: clothing - label: Home & Garden value: home - id: label_data_stock type: Label layout: flex: 1 1 0 properties: title: Stock Quantity blocks: - id: label_data_stock_input type: NumberInput properties: min: 0 - id: label_data_desc type: Label properties: title: Description extra: Markdown formatting is supported. colon: false blocks: - id: label_data_desc_input type: TextArea properties: rows: 4 - id: label_data_active type: Label properties: title: Active Listing inline: true span: 6 extra: Toggle off to save as draft. blocks: - id: label_data_active_input type: Switch - id: label_data_actions_row type: Box layout: gap: 8 justify: flex-end blocks: - id: label_data_save_draft type: Button layout: flex: 0 0 auto properties: title: Save Draft color: default variant: outlined events: onClick: - id: label_data_draft_msg type: DisplayMessage params: content: Draft saved. status: info - id: label_data_publish type: Button layout: flex: 0 0 auto properties: title: Publish Product color: primary variant: solid events: onClick: - id: label_data_publish_validate type: Validate params: - label_data_name - label_data_price - label_data_category - id: label_data_publish_msg type: DisplayMessage params: content: Product published successfully! status: success ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `xs` | object | - | Label width on extra small screens (below 576px) when the label is not inline. | | `xs.span` | number | - | Label width in columns, out of 24. The content takes the remaining columns. | | `sm` | object | - | Label width on small screens (576px and up) when the label is not inline. Also applies below 576px unless `xs` is set. | | `sm.span` | number | - | Label width in columns, out of 24. The content takes the remaining columns. | | `md` | object | - | Label width on medium screens (768px and up) when the label is not inline. Overrides `span`. | | `md.span` | number | - | Label width in columns, out of 24. The content takes the remaining columns. | | `lg` | object | - | Label width on large screens (992px and up) when the label is not inline. | | `lg.span` | number | - | Label width in columns, out of 24. The content takes the remaining columns. | | `xl` | object | - | Label width on extra large screens (1200px and up) when the label is not inline. | | `xl.span` | number | - | Label width in columns, out of 24. The content takes the remaining columns. | | `xxl` | object | - | Label width on extra extra large screens (1600px and up) when the label is not inline. | | `xxl.span` | number | - | Label width in columns, out of 24. The content takes the remaining columns. | | `align` | string | `"left"` | Align label left or right when inline. Enum: `left`, `right`. | | `colon` | boolean | `true` | Append label with colon. | | `disabled` | boolean | `false` | Disable to not render a label title. | | `extra` | string | - | Extra text to display beneath the content - supports html. | | `size` | string | `"default"` | Size of the block. Enum: `small`, `default`, `large`. | | `title` | string | - | Label title - supports html. | | `tooltip` | string \| object | - | Help tooltip shown via an icon beside the label. A string sets the tooltip text (supports html), or an object to also customize the icon and color. Use the block's onTooltipClick event to respond to clicks on the icon. | | `tooltip.title` | string | - | Tooltip text shown on hover - supports html. | | `tooltip.icon` | string | `"help"` | Icon name to show beside the label: a semantic name like `help`, a Lucide icon name like `CircleQuestionMark`, or a set-qualified name like `tabler:HelpCircle`. | | `tooltip.color` | string | - | Color of the tooltip icon. | | `span` | number | - | Label width in columns, out of 24, on medium screens (768px) and up when the label is not inline. The content takes the remaining columns. | | `inline` | boolean | `false` | Render input and label inline. | | `wrap` | boolean | `false` | Wrap long label text onto multiple lines when the label is inline. Labels above their content always wrap. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design form tokens](https://ant.design/components/form#design-token). | | `theme.labelFontSize` | number | `14` | Font size of the label text. | | `theme.labelColor` | string | `"rgba(0,0,0,0.88)"` | Text color of the label. | | `theme.labelRequiredMarkColor` | string | `"#ff4d4f"` | Color of the required asterisk mark. | | `theme.labelColonMarginInlineStart` | number | `2` | Inline start margin of the colon after the label. | | `theme.labelColonMarginInlineEnd` | number | `8` | Inline end margin of the colon after the label. | | `theme.colorError` | string | `"#ff4d4f"` | Color used for error validation feedback. | | `theme.colorWarning` | string | `"#faad14"` | Color used for warning validation feedback. | | `theme.colorSuccess` | string | `"#52c41a"` | Color used for success validation feedback. | | `theme.colorText` | string | - | Text color for the extra and feedback text. | | `theme.colorTextDescription` | string | - | Color for the extra description text. | | `theme.fontSize` | number | `14` | Base font size. | | Event | Event Data | Description | | --- | --- | --- | | `onTooltipClick` | \- | Trigger actions when the tooltip icon is clicked. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Label element. | | `/label` | The Label label. | | `/extra` | The Label extra content. | | `/feedback` | The Label validation feedback. | | Slot | Description | | --- | --- | | `content` | The labeled input or content blocks. | --- url: https://docs.lowdefy.com/md/container-blocks/layout.md --- # Layout Ant Design layout container for structuring page sections. This is the main content area. The Layout block wraps Header, Content, and Footer to create a standard page structure. Footer - Copyright 2026 ```yaml - id: layout_basic type: Layout style: minHeight: 300px blocks: - id: layout_basic_header type: Header style: paddingInline: 24px borderBottom: 1px solid var(--ant-color-border) blocks: - id: layout_basic_header_text type: Html properties: html: Header - id: layout_basic_content type: Content style: padding: 24px background: var(--ant-color-bg-container) blocks: - id: layout_basic_content_text type: Paragraph properties: content: This is the main content area. The Layout block wraps Header, Content, and Footer to create a standard page structure. - id: layout_basic_footer type: Footer style: textAlign: center blocks: - id: layout_basic_footer_text type: Paragraph properties: content: Footer - Copyright 2026 ``` Layout blocks compose together to create complex page structures. Here, an outer Layout wraps a Header, and an inner Layout contains a Sider and Content side by side. Footer ```yaml - id: layout_sider_outer type: Layout style: minHeight: 350px blocks: - id: layout_sider_header type: Header style: paddingInline: 24px borderBottom: 1px solid var(--ant-color-border) blocks: - id: layout_sider_header_text type: Html properties: html: Header - id: layout_sider_inner type: Layout properties: hasSider: true blocks: - id: layout_sider_side type: Sider layout: flex: 0 0 auto properties: theme: light width: 200 blocks: - id: layout_sider_side_menu type: Menu properties: mode: inline theme: light links: - id: layout_sider_nav_1 type: MenuLink properties: title: Dashboard icon: Gauge - id: layout_sider_nav_2 type: MenuLink properties: title: Settings icon: settings - id: layout_sider_content type: Content layout: flex: 1 1 auto style: padding: 24px background: var(--ant-color-bg-container) blocks: - id: layout_sider_content_text type: Paragraph properties: content: Layout blocks compose together to create complex page structures. Here, an outer Layout wraps a Header, and an inner Layout contains a Sider and Content side by side. - id: layout_sider_footer type: Footer style: textAlign: center blocks: - id: layout_sider_footer_text type: Paragraph properties: content: Footer ``` Main content area with a right-side panel. The Sider is placed after Content to render on the right. Right side panel for supplementary content, filters, or navigation. ```yaml - id: layout_right_sider_outer type: Layout style: minHeight: 300px blocks: - id: layout_right_sider_inner type: Layout properties: hasSider: true blocks: - id: layout_right_sider_content type: Content layout: flex: 1 1 auto style: padding: 24px background: var(--ant-color-bg-container) blocks: - id: layout_right_sider_content_text type: Paragraph properties: content: Main content area with a right-side panel. The Sider is placed after Content to render on the right. - id: layout_right_sider_side type: Sider layout: flex: 0 0 auto properties: theme: light width: 250 reverseArrow: true style: borderLeft: "1px solid #f0f0f0" blocks: - id: layout_right_sider_side_text type: Paragraph style: padding: 16px properties: content: Right side panel for supplementary content, filters, or navigation. ``` The collapse trigger uses the lightTriggerBg and lightTriggerColor tokens. The Layout theme property accepts antd Layout design tokens like bodyBg, headerHeight, footerBg and the Sider trigger tokens. They apply to the Header, Footer and Sider blocks inside the layout. Footer styled with the footerBg and footerPadding tokens ```yaml - id: layout_theme_outer type: Layout style: minHeight: 280px properties: theme: bodyBg: "#f0f5ff" headerHeight: 48 headerColor: "#1d39c4" footerBg: "#d6e4ff" footerPadding: 12px 24px lightTriggerBg: "#adc6ff" lightTriggerColor: "#1d39c4" blocks: - id: layout_theme_header type: Header style: paddingInline: 24px blocks: - id: layout_theme_header_text type: Html properties: html: 48px header from the headerHeight token - id: layout_theme_inner type: Layout properties: hasSider: true blocks: - id: layout_theme_sider type: Sider layout: flex: 0 0 auto properties: theme: light width: 180 collapsible: true initialCollapsed: false blocks: - id: layout_theme_sider_text type: Paragraph style: padding: 16px properties: content: The collapse trigger uses the lightTriggerBg and lightTriggerColor tokens. - id: layout_theme_content type: Content layout: flex: 1 1 auto style: padding: 24px blocks: - id: layout_theme_content_text type: Paragraph properties: content: The Layout theme property accepts antd Layout design tokens like bodyBg, headerHeight, footerBg and the Sider trigger tokens. They apply to the Header, Footer and Sider blocks inside the layout. - id: layout_theme_footer type: Footer style: textAlign: center blocks: - id: layout_theme_footer_text type: Paragraph properties: content: Footer styled with the footerBg and footerPadding tokens ``` The Layout block has a single cssKey called "element". Use the class property to apply Tailwind utility classes directly to the Layout wrapper. Here the outer Layout uses rounded corners, shadow, and a border. Child blocks like Header, Content, and Footer can each have their own class applied. Footer with Tailwind background ```yaml - id: layout_tailwind_outer type: Layout class: rounded-lg overflow-hidden shadow-lg border border-border style: minHeight: 280px blocks: - id: layout_tailwind_header type: Header properties: color: "#4338ca" style: paddingInline: 24px color: white blocks: - id: layout_tailwind_header_text type: Html properties: html: Tailwind-styled Layout - id: layout_tailwind_content type: Content class: .element: bg-bg-layout style: padding: 24px blocks: - id: layout_tailwind_content_text type: Paragraph properties: content: The Layout block has a single cssKey called "element". Use the class property to apply Tailwind utility classes directly to the Layout wrapper. Here the outer Layout uses rounded corners, shadow, and a border. Child blocks like Header, Content, and Footer can each have their own class applied. - id: layout_tailwind_footer type: Footer class: .element: bg-bg-layout text-center blocks: - id: layout_tailwind_footer_text type: Paragraph properties: content: Footer with Tailwind background ``` This is where a data table or chart would display recent order information. The Layout block provides the overall page structure with a dark sidebar, top header, and scrollable content area. Acme Corp Admin Panel - Version 3.2.1 ```yaml - id: layout_admin_outer type: Layout style: minHeight: 450px blocks: - id: layout_admin_header type: Header properties: notifications: count: 5 profile: avatar: icon: user style: paddingInline: 24px borderBottom: 1px solid var(--ant-color-border) blocks: - id: layout_admin_header_title type: Html layout: flex: 0 0 auto properties: html: Acme Admin - id: layout_admin_inner type: Layout properties: hasSider: true blocks: - id: layout_admin_sider type: Sider layout: flex: 0 0 auto properties: theme: dark width: 220 breakpoint: lg blocks: - id: layout_admin_menu type: Menu properties: mode: inline theme: dark links: - id: layout_admin_menu_dashboard type: MenuLink properties: title: Dashboard icon: Gauge - id: layout_admin_menu_users type: MenuLink properties: title: Users icon: users - id: layout_admin_menu_orders type: MenuLink properties: title: Orders icon: ShoppingCart - id: layout_admin_menu_reports type: MenuLink properties: title: Reports icon: chart - id: layout_admin_menu_settings type: MenuLink properties: title: Settings icon: settings - id: layout_admin_content_area type: Content layout: flex: 1 1 auto style: padding: 24px background: var(--ant-color-bg-layout) blocks: - id: layout_admin_breadcrumb type: Breadcrumb properties: list: - Home - Dashboard - id: layout_admin_stats_row type: Box layout: gap: 16 blocks: - id: layout_admin_stat_users type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: layout_admin_stat_users_val type: Statistic properties: title: Total Users value: 12480 - id: layout_admin_stat_orders type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: layout_admin_stat_orders_val type: Statistic properties: title: Orders Today value: 342 - id: layout_admin_stat_revenue type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: layout_admin_stat_revenue_val type: Statistic properties: title: Revenue value: 58200 prefix: $ - id: layout_admin_main_card type: Card properties: title: Recent Orders blocks: - id: layout_admin_table_placeholder type: Paragraph properties: content: This is where a data table or chart would display recent order information. The Layout block provides the overall page structure with a dark sidebar, top header, and scrollable content area. - id: layout_admin_footer type: Footer style: textAlign: center blocks: - id: layout_admin_footer_text type: Paragraph properties: content: Acme Corp Admin Panel - Version 3.2.1 ``` DevDocs Getting Started Introduction Welcome to DevDocs. This guide walks you through setting up your first project. The page uses a Layout with a light-themed Sider for documentation navigation, a white Header with logo and search, and a Content area with comfortable reading width. > Tip ```yaml - id: layout_docs_outer type: Layout style: minHeight: 450px blocks: - id: layout_docs_header type: Header style: paddingInline: 24px borderBottom: 1px solid var(--ant-color-border) background: var(--ant-color-bg-container) blocks: - id: layout_docs_logo type: Title layout: flex: 0 0 auto properties: content: DevDocs level: 4 style: .element: margin: 0 - id: layout_docs_version_tag type: Tag layout: flex: 0 0 auto properties: title: v4.2 color: blue - id: layout_docs_spacer type: Box layout: flex: 1 1 0 - id: layout_docs_search_btn type: Button layout: flex: 0 0 auto properties: icon: search title: Search docs... color: default variant: outlined - id: layout_docs_github_btn type: Button layout: flex: 0 0 auto properties: icon: GitBranch color: default variant: text hideTitle: true - id: layout_docs_inner type: Layout properties: hasSider: true blocks: - id: layout_docs_sider type: Sider layout: flex: 0 0 auto properties: theme: light width: 260 breakpoint: md style: borderRight: "1px solid #f0f0f0" blocks: - id: layout_docs_nav_title type: Paragraph style: padding: 16px 24px 8px fontWeight: 600 properties: content: Getting Started - id: layout_docs_nav_menu type: Menu properties: mode: inline theme: light links: - id: layout_docs_nav_intro type: MenuLink properties: title: Introduction icon: BookOpen - id: layout_docs_nav_install type: MenuLink properties: title: Installation icon: download - id: layout_docs_nav_config type: MenuLink properties: title: Configuration icon: Wrench - id: layout_docs_nav_deploy type: MenuLink properties: title: Deployment icon: CloudUpload - id: layout_docs_content_area type: Content layout: flex: 1 1 auto style: padding: 32px 48px background: var(--ant-color-bg-container) blocks: - id: layout_docs_content_breadcrumb type: Breadcrumb properties: list: - Docs - Getting Started - Introduction - id: layout_docs_content_title type: Title properties: content: Introduction level: 2 - id: layout_docs_content_paragraph type: Paragraph properties: content: Welcome to DevDocs. This guide walks you through setting up your first project. The page uses a Layout with a light-themed Sider for documentation navigation, a white Header with logo and search, and a Content area with comfortable reading width. - id: layout_docs_content_alert type: Alert properties: type: info message: Tip description: The Layout block is the top-level structural container. Nest Header, Content, Footer, and Sider blocks inside to build any page structure. showIcon: true ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `hasSider` | boolean | `false` | Lay the child blocks out in a row when the layout contains a Sider. | | `theme` | object | - | Antd Layout design token overrides for this block. Header, Footer and Sider blocks inside the layout use them too. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design layout tokens](https://ant.design/components/layout#design-token). | | `theme.bodyBg` | string | `"#f5f5f5"` | Background color of the layout. | | `theme.headerHeight` | number | `64` | Height of Header blocks. | | `theme.headerPadding` | string | `"0 50px"` | Padding of Header blocks. | | `theme.headerColor` | string | `"rgba(0, 0, 0, 0.88)"` | Text color of Header blocks. | | `theme.footerBg` | string | `"#f5f5f5"` | Background color of Footer blocks. | | `theme.footerPadding` | string | `"24px 50px"` | Padding of Footer blocks. | | `theme.triggerHeight` | number | `48` | Height of the collapse trigger of a collapsible Sider. | | `theme.triggerBg` | string | `"#002140"` | Background color of the collapse trigger of a dark Sider. | | `theme.triggerColor` | string | `"#fff"` | Color of the collapse trigger of a dark Sider. | | `theme.lightTriggerBg` | string | `"#ffffff"` | Background color of the collapse trigger of a light Sider. | | `theme.lightTriggerColor` | string | `"rgba(0, 0, 0, 0.88)"` | Color of the collapse trigger of a light Sider. | No events defined. | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Layout element. | | Slot | Description | | --- | --- | | `content` | Layout child blocks (Header, Sider, Content, Footer). | --- url: https://docs.lowdefy.com/md/container-blocks/masonry.md --- # Masonry Masonry-style grid layout container. +12.5% vs last month Target is 5%. Campaigns are underperforming in APAC. ```yaml - id: mas_3col type: Masonry properties: columns: 3 gutter: 12 blocks: - id: mas_3col_c1 type: Card properties: size: small title: Revenue blocks: - id: mas_3col_c1_stat type: Statistic properties: value: 128450 prefix: $ - id: mas_3col_c2 type: Card properties: size: small title: Active Users blocks: - id: mas_3col_c2_stat type: Statistic properties: value: 3842 - id: mas_3col_c2_p type: Paragraph properties: content: +12.5% vs last month - id: mas_3col_c3 type: Card properties: size: small title: Uptime blocks: - id: mas_3col_c3_stat type: Statistic properties: value: 99.97 suffix: "%" - id: mas_3col_c4 type: Card properties: size: small title: Top Region blocks: - id: mas_3col_c4_tags type: Box layout: gap: 8 wrap: wrap blocks: - id: mas_3col_c4_t1 type: Tag properties: title: North America color: blue - id: mas_3col_c4_t2 type: Tag properties: title: Europe color: green - id: mas_3col_c4_t3 type: Tag properties: title: Asia Pacific color: orange - id: mas_3col_c5 type: Card properties: size: small title: Conversion Rate blocks: - id: mas_3col_c5_stat type: Statistic properties: value: 4.2 suffix: "%" - id: mas_3col_c5_p1 type: Paragraph properties: content: Target is 5%. Campaigns are underperforming in APAC. - id: mas_3col_c5_p2 type: Progress properties: percent: 84 ``` > Deployment completed > Disk usage at 85% ```yaml - id: mas_2col type: Masonry properties: columns: 2 gutter: 12 blocks: - id: mas_2col_c1 type: Card properties: size: small title: Notifications blocks: - id: mas_2col_c1_alert type: Alert properties: type: success showIcon: true message: Deployment completed - id: mas_2col_c1_alert2 type: Alert properties: type: warning showIcon: true message: Disk usage at 85% - id: mas_2col_c2 type: Card properties: size: small title: Quick Stats blocks: - id: mas_2col_c2_stat type: Statistic properties: value: 1024 suffix: requests/s ``` Resize browser to see columns change at each breakpoint. Stacks to a single column on small screens. ```yaml - id: mas_resp type: Masonry properties: columns: xs: 1 sm: 2 md: 3 gutter: 12 blocks: - id: mas_resp_c1 type: Card properties: size: small title: xs:1 sm:2 md:3 blocks: - id: mas_resp_c1_p type: Paragraph properties: content: Resize browser to see columns change at each breakpoint. - id: mas_resp_c2 type: Card properties: size: small title: Responsive blocks: - id: mas_resp_c2_stat type: Statistic properties: value: 3 suffix: breakpoints - id: mas_resp_c3 type: Card properties: size: small title: Mobile blocks: - id: mas_resp_c3_p type: Paragraph properties: content: Stacks to a single column on small screens. ``` 32px gap between items in both directions. ```yaml - id: mas_gutter type: Masonry properties: columns: 2 gutter: 32 blocks: - id: mas_gutter_c1 type: Card properties: size: small title: Spacious blocks: - id: mas_gutter_c1_p type: Paragraph properties: content: 32px gap between items in both directions. - id: mas_gutter_c2 type: Card properties: size: small title: Airy Layout blocks: - id: mas_gutter_c2_stat type: Statistic properties: value: 32 suffix: px ``` 32px horizontal, 8px vertical. Compact rows, wide columns. Notice the asymmetric spacing. ```yaml - id: mas_hv type: Masonry properties: columns: 2 gutter: - 32 - 8 blocks: - id: mas_hv_c1 type: Card properties: size: small title: Wide Horizontal blocks: - id: mas_hv_c1_p type: Paragraph properties: content: 32px horizontal, 8px vertical. Compact rows, wide columns. - id: mas_hv_c2 type: Card properties: size: small title: Tight Vertical blocks: - id: mas_hv_c2_stat type: Statistic properties: value: 8 suffix: px vertical - id: mas_hv_c3 type: Card properties: size: small title: Third blocks: - id: mas_hv_c3_p type: Paragraph properties: content: Notice the asymmetric spacing. ``` Theme tokens cascade to all child blocks within this Masonry. ```yaml - id: mas_blue type: Masonry properties: columns: 2 gutter: 12 theme: colorBorderSecondary: "#adc6ff" borderRadiusLG: 16 blocks: - id: mas_blue_c1 type: Card properties: size: small title: Themed Card blocks: - id: mas_blue_c1_p type: Paragraph properties: content: Theme tokens cascade to all child blocks within this Masonry. - id: mas_blue_c2 type: Card properties: size: small title: Rounded Corners blocks: - id: mas_blue_c2_stat type: Statistic properties: value: 16 suffix: px radius ``` Warm-toned cards using gold design tokens. Text, borders, and backgrounds all harmonized. ```yaml - id: mas_warm type: Masonry properties: columns: 2 gutter: 12 theme: colorBorderSecondary: "#ffd591" colorTextHeading: "#d46b08" colorText: "#874d00" blocks: - id: mas_warm_c1 type: Card properties: size: small title: Orange Palette blocks: - id: mas_warm_c1_p type: Paragraph properties: content: Warm-toned cards using gold design tokens. - id: mas_warm_c2 type: Card properties: size: small title: Consistent blocks: - id: mas_warm_c2_p type: Paragraph properties: content: Text, borders, and backgrounds all harmonized. ``` Tailwind classes on the element CSS key style the Masonry wrapper. White cards against gray create visual separation. ```yaml - id: mas_tw type: Masonry class: .element: bg-bg-layout p-6 rounded-xl properties: columns: 2 gutter: 12 blocks: - id: mas_tw_c1 type: Card properties: size: small title: Gray Background blocks: - id: mas_tw_c1_p type: Paragraph properties: content: Tailwind classes on the element CSS key style the Masonry wrapper. - id: mas_tw_c2 type: Card properties: size: small title: Contrast blocks: - id: mas_tw_c2_p type: Paragraph properties: content: White cards against gray create visual separation. ``` Resize browser to see slow item repositioning. ease-in-out instead of the default cubic-bezier. ```yaml - id: mas_motion type: Masonry properties: columns: 2 gutter: 12 theme: motionDurationSlow: 2s motionDurationFast: 1s motionEaseOut: ease-in-out blocks: - id: mas_motion_c1 type: Card properties: size: small title: 2s Transitions blocks: - id: mas_motion_c1_p type: Paragraph properties: content: Resize browser to see slow item repositioning. - id: mas_motion_c2 type: Card properties: size: small title: Custom Easing blocks: - id: mas_motion_c2_p type: Paragraph properties: content: ease-in-out instead of the default cubic-bezier. ``` Mountain Sunrise A stunning view of the morning light breaking over the peaks. City at Night Urban skyline illuminated by thousands of city lights reflecting off the river below. Autumn Forest Ocean Waves Crashing waves along the rocky coastline during golden hour. Desert Dunes Rolling sand dunes stretching to the horizon under a clear blue sky. The patterns in the sand are shaped by the wind. ```yaml - id: mas_gallery type: Masonry properties: columns: 3 gutter: 12 blocks: - id: mas_gallery_c1 type: Card properties: hoverable: true size: small slots: cover: blocks: - id: mas_gallery_img1 type: Html properties: html: landscape blocks: - id: mas_gallery_c1_title type: Title properties: content: Mountain Sunrise level: 5 - id: mas_gallery_c1_p type: Paragraph properties: content: A stunning view of the morning light breaking over the peaks. - id: mas_gallery_c1_tags type: Box layout: gap: 8 blocks: - id: mas_gallery_c1_t1 type: Tag properties: title: Nature color: green - id: mas_gallery_c1_t2 type: Tag properties: title: Featured color: gold - id: mas_gallery_c2 type: Card properties: hoverable: true size: small slots: cover: blocks: - id: mas_gallery_img2 type: Html properties: html: cityscape blocks: - id: mas_gallery_c2_title type: Title properties: content: City at Night level: 5 - id: mas_gallery_c2_p type: Paragraph properties: content: Urban skyline illuminated by thousands of city lights reflecting off the river below. - id: mas_gallery_c3 type: Card properties: hoverable: true size: small slots: cover: blocks: - id: mas_gallery_img3 type: Html properties: html: forest blocks: - id: mas_gallery_c3_title type: Title properties: content: Autumn Forest level: 5 - id: mas_gallery_c4 type: Card properties: hoverable: true size: small slots: cover: blocks: - id: mas_gallery_img4 type: Html properties: html: ocean blocks: - id: mas_gallery_c4_title type: Title properties: content: Ocean Waves level: 5 - id: mas_gallery_c4_p type: Paragraph properties: content: Crashing waves along the rocky coastline during golden hour. - id: mas_gallery_c4_tags type: Box layout: gap: 8 blocks: - id: mas_gallery_c4_t1 type: Tag properties: title: Seascape color: blue - id: mas_gallery_c5 type: Card properties: hoverable: true size: small slots: cover: blocks: - id: mas_gallery_img5 type: Html properties: html: desert blocks: - id: mas_gallery_c5_title type: Title properties: content: Desert Dunes level: 5 - id: mas_gallery_c5_p type: Paragraph properties: content: Rolling sand dunes stretching to the horizon under a clear blue sky. The patterns in the sand are shaped by the wind. ``` Sarah Chen Lead Designer Marcus Johnson Senior Backend Engineer Priya Patel Product Manager ```yaml - id: mas_team type: Masonry properties: columns: xs: 1 sm: 2 lg: 3 gutter: 16 blocks: - id: mas_team_c1 type: Card properties: size: small hoverable: true blocks: - id: mas_team_c1_row type: Box layout: gap: 12 align: center blocks: - id: mas_team_c1_avatar type: Avatar layout: flex: 0 0 auto properties: icon: user size: 48 color: "#1677ff" - id: mas_team_c1_info type: Box layout: flex: 1 1 0 direction: column gap: 2 blocks: - id: mas_team_c1_name type: Title properties: content: Sarah Chen level: 5 - id: mas_team_c1_role type: Paragraph properties: content: Lead Designer - id: mas_team_c1_divider type: Divider - id: mas_team_c1_desc type: Descriptions properties: size: small column: 1 items: - label: Email value: sarah.chen@example.com - label: Location value: San Francisco, CA - label: Team value: Design Systems - id: mas_team_c1_btn type: Button properties: title: View Profile color: primary variant: outlined block: true size: small events: onClick: - id: mas_team_c1_msg type: DisplayMessage params: content: Viewing Sarah Chen's profile status: info - id: mas_team_c2 type: Card properties: size: small hoverable: true blocks: - id: mas_team_c2_row type: Box layout: gap: 12 align: center blocks: - id: mas_team_c2_avatar type: Avatar layout: flex: 0 0 auto properties: icon: user size: 48 color: "#52c41a" - id: mas_team_c2_info type: Box layout: flex: 1 1 0 direction: column gap: 2 blocks: - id: mas_team_c2_name type: Title properties: content: Marcus Johnson level: 5 - id: mas_team_c2_role type: Paragraph properties: content: Senior Backend Engineer - id: mas_team_c2_divider type: Divider - id: mas_team_c2_desc type: Descriptions properties: size: small column: 1 items: - label: Email value: marcus.j@example.com - label: Location value: Austin, TX - id: mas_team_c2_btn type: Button properties: title: View Profile color: primary variant: outlined block: true size: small events: onClick: - id: mas_team_c2_msg type: DisplayMessage params: content: Viewing Marcus Johnson's profile status: info - id: mas_team_c3 type: Card properties: size: small hoverable: true blocks: - id: mas_team_c3_row type: Box layout: gap: 12 align: center blocks: - id: mas_team_c3_avatar type: Avatar layout: flex: 0 0 auto properties: icon: user size: 48 color: "#722ed1" - id: mas_team_c3_info type: Box layout: flex: 1 1 0 direction: column gap: 2 blocks: - id: mas_team_c3_name type: Title properties: content: Priya Patel level: 5 - id: mas_team_c3_role type: Paragraph properties: content: Product Manager - id: mas_team_c3_divider type: Divider - id: mas_team_c3_desc type: Descriptions properties: size: small column: 1 items: - label: Email value: priya.p@example.com - label: Location value: London, UK - label: Team value: Product - label: Projects value: 12 active - id: mas_team_c3_btn type: Button properties: title: View Profile color: primary variant: outlined block: true size: small events: onClick: - id: mas_team_c3_msg type: DisplayMessage params: content: Viewing Priya Patel's profile status: info ``` Wireless Headphones Leather Notebook Ceramic Mug Set Handcrafted set of 4 ceramic mugs in earth-tone glazes. Dishwasher and microwave safe. Desk Lamp ```yaml - id: mas_products type: Masonry class: .element: bg-bg-layout p-6 rounded-xl properties: columns: xs: 1 sm: 2 md: 3 lg: 4 gutter: 16 blocks: - id: mas_prod_c1 type: Card properties: hoverable: true size: small slots: cover: blocks: - id: mas_prod_img1 type: Html properties: html: product blocks: - id: mas_prod_c1_name type: Title properties: content: Wireless Headphones level: 5 - id: mas_prod_c1_price type: Statistic properties: value: 79.99 prefix: $ - id: mas_prod_c1_tags type: Box layout: gap: 8 blocks: - id: mas_prod_c1_t1 type: Tag properties: title: Electronics color: blue - id: mas_prod_c1_t2 type: Tag properties: title: Best Seller color: red - id: mas_prod_c1_btn type: Button properties: title: Add to Cart color: primary variant: solid block: true size: small icon: ShoppingCart events: onClick: - id: mas_prod_c1_add type: DisplayMessage params: content: Wireless Headphones added to cart status: success - id: mas_prod_c2 type: Card properties: hoverable: true size: small slots: cover: blocks: - id: mas_prod_img2 type: Html properties: html: product blocks: - id: mas_prod_c2_name type: Title properties: content: Leather Notebook level: 5 - id: mas_prod_c2_price type: Statistic properties: value: 24.5 prefix: $ - id: mas_prod_c2_btn type: Button properties: title: Add to Cart color: primary variant: solid block: true size: small icon: ShoppingCart events: onClick: - id: mas_prod_c2_add type: DisplayMessage params: content: Leather Notebook added to cart status: success - id: mas_prod_c3 type: Card properties: hoverable: true size: small slots: cover: blocks: - id: mas_prod_img3 type: Html properties: html: product blocks: - id: mas_prod_c3_name type: Title properties: content: Ceramic Mug Set level: 5 - id: mas_prod_c3_desc type: Paragraph properties: content: Handcrafted set of 4 ceramic mugs in earth-tone glazes. Dishwasher and microwave safe. - id: mas_prod_c3_price type: Statistic properties: value: 45 prefix: $ - id: mas_prod_c3_tags type: Box layout: gap: 8 blocks: - id: mas_prod_c3_t1 type: Tag properties: title: Home color: orange - id: mas_prod_c3_t2 type: Tag properties: title: New color: green - id: mas_prod_c3_btn type: Button properties: title: Add to Cart color: primary variant: solid block: true size: small icon: ShoppingCart events: onClick: - id: mas_prod_c3_add type: DisplayMessage params: content: Ceramic Mug Set added to cart status: success - id: mas_prod_c4 type: Card properties: hoverable: true size: small slots: cover: blocks: - id: mas_prod_img4 type: Html properties: html: product blocks: - id: mas_prod_c4_name type: Title properties: content: Desk Lamp level: 5 - id: mas_prod_c4_price type: Statistic properties: value: 59.99 prefix: $ - id: mas_prod_c4_btn type: Button properties: title: Add to Cart color: primary variant: solid block: true size: small icon: ShoppingCart events: onClick: - id: mas_prod_c4_add type: DisplayMessage params: content: Desk Lamp added to cart status: success ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `columns` | integer \| object | `3` | Number of columns, or responsive breakpoint object (e.g. { xs: 1, sm: 2, md: 3 }). | | `fresh` | boolean | `false` | Keep watching the size of each item and re-layout when it changes, for items whose height changes after they render (images loading, expanding content). | | `gutter` | number \| array \| object | `0` | Gap between items in pixels. A number, a responsive breakpoint object (e.g. { xs: 8, md: 16 }), or a [horizontal, vertical] array of either. | | `sequential` | boolean | `false` | Has no effect: antd Masonry always places each item in the shortest column. Kept so existing configs stay valid. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design masonry tokens](https://ant.design/components/masonry#design-token). | | `theme.motionDurationSlow` | string | `"0.3s"` | Duration for item position transitions and appear animations. | | `theme.motionDurationFast` | string | `"0.1s"` | Duration for item leave animations. | | `theme.motionEaseOut` | string | `"cubic-bezier(0.215, 0.61, 0.355, 1)"` | Easing function for item transitions. | No events defined. | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Masonry element. | | `/item` | Each item in the masonry grid. | | Slot | Description | | --- | --- | | `content` | Child blocks arranged in a masonry grid. | --- url: https://docs.lowdefy.com/md/container-blocks/mobilemenu.md --- # MobileMenu Responsive mobile navigation menu with drawer. ```yaml - id: basic_simple type: MobileMenu layout: flex: 0 0 auto properties: links: - id: bs_home type: MenuLink properties: title: Home - id: bs_about type: MenuLink properties: title: About - id: bs_contact type: MenuLink properties: title: Contact - id: basic_icons type: MobileMenu layout: flex: 0 0 auto properties: links: - id: bi_dashboard type: MenuLink properties: title: Dashboard icon: Gauge - id: bi_users type: MenuLink properties: title: Users icon: user - id: bi_settings type: MenuLink properties: title: Settings icon: settings - id: basic_page_links type: MobileMenu layout: flex: 0 0 auto properties: links: - id: bpl_docs type: MenuLink pageId: docs properties: title: Documentation icon: document - id: bpl_api type: MenuLink pageId: api-reference properties: title: API Reference icon: Plug - id: bpl_changelog type: MenuLink pageId: changelog properties: title: Changelog icon: history ``` ```yaml - id: groups_single type: MobileMenu layout: flex: 0 0 auto properties: links: - id: gs_overview type: MenuLink properties: title: Overview icon: home - id: gs_management type: MenuGroup properties: title: Management icon: grid links: - id: gs_projects type: MenuLink properties: title: Projects - id: gs_tasks type: MenuLink properties: title: Tasks - id: groups_nested type: MobileMenu layout: flex: 0 0 auto properties: links: - id: gn_content type: MenuGroup properties: title: Content icon: document links: - id: gn_posts type: MenuLink properties: title: Posts - id: gn_pages type: MenuLink properties: title: Pages - id: gn_users type: MenuGroup properties: title: Users icon: users links: - id: gn_all type: MenuLink properties: title: All Users - id: gn_roles type: MenuLink properties: title: Roles - id: groups_deep type: MobileMenu layout: flex: 0 0 auto properties: links: - id: gd_products type: MenuGroup properties: title: Products icon: Store links: - id: gd_electronics type: MenuGroup properties: title: Electronics links: - id: gd_phones type: MenuLink properties: title: Phones - id: gd_laptops type: MenuLink properties: title: Laptops ``` ```yaml - id: divider_basic type: MobileMenu layout: flex: 0 0 auto properties: links: - id: db_home type: MenuLink properties: title: Home icon: home - id: db_divider type: MenuDivider - id: db_settings type: MenuLink properties: title: Settings icon: settings - id: divider_dashed type: MobileMenu layout: flex: 0 0 auto properties: links: - id: dd_profile type: MenuLink properties: title: Profile icon: user - id: dd_divider type: MenuDivider properties: dashed: true - id: dd_help type: MenuLink properties: title: Help Center icon: help - id: divider_danger type: MobileMenu layout: flex: 0 0 auto properties: links: - id: dg_account type: MenuLink properties: title: Account icon: user - id: dg_divider type: MenuDivider - id: dg_delete type: MenuLink properties: title: Delete Account icon: delete danger: true - id: dg_logout type: MenuLink properties: title: Sign Out icon: logout danger: true ``` ```yaml - id: theme_light type: MobileMenu layout: flex: 0 0 auto properties: theme: light links: - id: tl_home type: MenuLink properties: title: Home icon: home - id: tl_explore type: MenuLink properties: title: Explore icon: Compass - id: tl_settings type: MenuLink properties: title: Settings icon: settings - id: theme_dark type: MobileMenu layout: flex: 0 0 auto properties: theme: dark links: - id: td_home type: MenuLink properties: title: Home icon: home - id: td_explore type: MenuLink properties: title: Explore icon: Compass - id: td_settings type: MenuLink properties: title: Settings icon: settings ``` ```yaml - id: selected_single type: MobileMenu layout: flex: 0 0 auto properties: selectedKeys: - sk_users links: - id: sk_dashboard type: MenuLink properties: title: Dashboard icon: Gauge - id: sk_users type: MenuLink properties: title: Users icon: user - id: sk_reports type: MenuLink properties: title: Reports icon: chart - id: selected_multiple type: MobileMenu layout: flex: 0 0 auto properties: selectedKeys: - sm_inbox - sm_sent links: - id: sm_inbox type: MenuLink properties: title: Inbox icon: Inbox - id: sm_sent type: MenuLink properties: title: Sent icon: send - id: sm_drafts type: MenuLink properties: title: Drafts icon: edit - id: selected_in_group type: MobileMenu layout: flex: 0 0 auto properties: selectedKeys: - sig_tasks links: - id: sig_overview type: MenuLink properties: title: Overview icon: home - id: sig_project type: MenuGroup properties: title: Project icon: SquareKanban links: - id: sig_tasks type: MenuLink properties: title: Tasks - id: sig_timeline type: MenuLink properties: title: Timeline ``` ```yaml - id: toggle_default_type type: MobileMenu layout: flex: 0 0 auto properties: toggleMenuButton: type: default icon: menu links: - id: tdt_home type: MenuLink properties: title: Home - id: tdt_about type: MenuLink properties: title: About - id: toggle_text_variant type: MobileMenu layout: flex: 0 0 auto properties: toggleMenuButton: type: text icon: list links: - id: ttv_home type: MenuLink properties: title: Home - id: ttv_about type: MenuLink properties: title: About - id: toggle_danger_style type: MobileMenu layout: flex: 0 0 auto properties: toggleMenuButton: type: primary icon: menu danger: true links: - id: tds_home type: MenuLink properties: title: Home - id: tds_about type: MenuLink properties: title: About ``` ```yaml - id: drawer_titled type: MobileMenu layout: flex: 0 0 auto properties: drawer: title: Navigation width: 280 links: - id: dt_home type: MenuLink properties: title: Home icon: home - id: dt_profile type: MenuLink properties: title: Profile icon: user - id: drawer_left type: MobileMenu layout: flex: 0 0 auto properties: drawer: title: Menu placement: left width: 240 links: - id: dl_home type: MenuLink properties: title: Home icon: home - id: dl_search type: MenuLink properties: title: Search icon: search - id: drawer_wide type: MobileMenu layout: flex: 0 0 auto properties: drawer: title: Browse width: 360 closable: true mask: true maskClosable: true links: - id: dw_categories type: MenuGroup properties: title: Categories icon: grid links: - id: dw_electronics type: MenuLink properties: title: Electronics - id: dw_clothing type: MenuLink properties: title: Clothing ``` ```yaml - id: delay_fast type: MobileMenu layout: flex: 0 0 auto properties: subMenuOpenDelay: 0 subMenuCloseDelay: 0 links: - id: df_tools type: MenuGroup properties: title: Tools icon: Wrench links: - id: df_editor type: MenuLink properties: title: Editor - id: df_terminal type: MenuLink properties: title: Terminal - id: delay_slow type: MobileMenu layout: flex: 0 0 auto properties: subMenuOpenDelay: 0.5 subMenuCloseDelay: 1 links: - id: ds_tools type: MenuGroup properties: title: Tools icon: Wrench links: - id: ds_editor type: MenuLink properties: title: Editor - id: ds_terminal type: MenuLink properties: title: Terminal ``` ```yaml - id: events_toggle type: MobileMenu layout: flex: 0 0 auto properties: links: - id: et_home type: MenuLink properties: title: Home icon: home - id: et_about type: MenuLink properties: title: About icon: info events: onToggleDrawer: - id: toggle_msg type: DisplayMessage params: content: Drawer toggled status: info - id: events_open_close type: MobileMenu layout: flex: 0 0 auto properties: links: - id: eoc_home type: MenuLink properties: title: Home icon: home - id: eoc_profile type: MenuLink properties: title: Profile icon: user events: onOpen: - id: open_msg type: DisplayMessage params: content: Menu opened status: success onClose: - id: close_msg type: DisplayMessage params: content: Menu closed status: warning - id: events_item_click type: MobileMenu layout: flex: 0 0 auto properties: links: - id: eic_dashboard type: MenuLink properties: title: Dashboard icon: Gauge - id: eic_reports type: MenuLink properties: title: Reports icon: chart events: onMenuItemClick: - id: click_msg type: DisplayMessage params: content: Menu item clicked status: info onMenuItemSelect: - id: select_msg type: DisplayMessage params: content: Menu item selected status: success - id: events_group_toggle type: MobileMenu layout: flex: 0 0 auto properties: links: - id: egt_tools type: MenuGroup properties: title: Tools icon: Wrench links: - id: egt_editor type: MenuLink properties: title: Editor - id: egt_terminal type: MenuLink properties: title: Terminal events: onToggleMenuGroup: - id: group_toggle_msg type: DisplayMessage params: content: Menu group toggled status: info ``` ```yaml - id: link_style_custom type: MobileMenu layout: flex: 0 0 auto properties: links: - id: lsc_featured type: MenuLink style: fontWeight: bold color: "#1677ff" properties: title: Featured icon: star - id: lsc_regular type: MenuLink properties: title: Regular Item icon: file - id: lsc_muted type: MenuLink style: opacity: 0.6 fontStyle: italic properties: title: Archived icon: folder - id: link_style_icon_object type: MobileMenu layout: flex: 0 0 auto properties: links: - id: lsi_alert type: MenuLink properties: title: Alerts icon: name: bell color: "#faad14" - id: lsi_success type: MenuLink properties: title: Completed icon: name: check-circle color: "#52c41a" - id: lsi_error type: MenuLink properties: title: Errors icon: name: close-circle color: "#ff4d4f" ``` ```yaml - id: css_tailwind_shadow type: MobileMenu layout: flex: 0 0 auto class: shadow-md rounded-lg properties: links: - id: cts_home type: MenuLink properties: title: Home icon: home - id: cts_settings type: MenuLink properties: title: Settings icon: settings - id: css_tailwind_bg type: MobileMenu layout: flex: 0 0 auto class: bg-bg-layout p-1 rounded-md properties: links: - id: ctb_home type: MenuLink properties: title: Home icon: home - id: ctb_explore type: MenuLink properties: title: Explore icon: Compass - id: css_inline_style type: MobileMenu layout: flex: 0 0 auto style: .element: padding: 4px borderRadius: 8 border: "1px solid #d6e4ff" properties: links: - id: cis_home type: MenuLink properties: title: Home icon: home - id: cis_help type: MenuLink properties: title: Help icon: help - id: css_dark_context type: Box class: bg-gradient-to-r from-slate-900 to-slate-700 p-4 rounded-lg layout: gap: 8 blocks: - id: css_on_dark type: MobileMenu layout: flex: 0 0 auto properties: theme: dark toggleMenuButton: type: primary ghost: true icon: menu links: - id: cod_home type: MenuLink properties: title: Home icon: home - id: cod_settings type: MenuLink properties: title: Settings icon: settings ``` ```yaml - id: mobile_shortcut type: MobileMenu layout: flex: 0 0 auto properties: drawer: title: Navigation links: - id: ms_home type: MenuLink properties: title: Home icon: home shortcut: mod+1 - id: ms_search type: MenuLink properties: title: Search icon: search shortcut: mod+k - id: ms_settings type: MenuLink properties: title: Settings icon: settings shortcut: mod+, ``` ```yaml - id: mm_logo_example type: MobileMenu layout: flex: 0 0 auto properties: logo: src: logo-square-dark-theme.png alt: Lowdefy style: width: 60 links: - id: ml_home type: MenuLink properties: title: Home icon: home - id: ml_settings type: MenuLink properties: title: Settings icon: settings ``` Content above the menu items. ```yaml - id: mm_drawer_header_example type: MobileMenu layout: flex: 0 0 auto properties: links: - id: mdh_home type: MenuLink properties: title: Home - id: mdh_about type: MenuLink properties: title: About slots: drawerHeader: blocks: - id: mdh_workspace type: Paragraph properties: content: Content above the menu items. style: .element: padding: 0 24px margin: 0 fontWeight: 600 ``` Additional content below the menu items. ```yaml - id: mm_drawer_content_example type: MobileMenu layout: flex: 0 0 auto properties: links: - id: mdc_home type: MenuLink properties: title: Home - id: mdc_about type: MenuLink properties: title: About slots: drawerContent: blocks: - id: mdc_extra type: Paragraph properties: content: Additional content below the menu items. style: .element: padding: 16px color: "#999" ``` App v2.1.0 ```yaml - id: mm_drawer_footer_example type: MobileMenu layout: flex: 0 0 auto properties: links: - id: mdf_home type: MenuLink properties: title: Home slots: drawerFooter: blocks: - id: mdf_footer type: Paragraph properties: content: App v2.1.0 style: .element: textAlign: center margin: 0 color: "#aaa" fontSize: 12 ``` **Admin Panel** You have **12** new orders, **3** pending reviews, and **5** unread messages. Use the navigation menu to manage your application. ```yaml - id: admin_header type: Box class: bg-bg-container border-b border-border px-4 py-3 layout: direction: row align: center justify: space-between blocks: - id: admin_left type: Box layout: direction: row align: center gap: 12 flex: 0 0 auto blocks: - id: admin_nav type: MobileMenu layout: flex: 0 0 auto properties: toggleMenuButton: type: text icon: menu drawer: title: Admin Panel width: 280 links: - id: an_dashboard type: MenuLink properties: title: Dashboard icon: Gauge - id: an_content type: MenuGroup properties: title: Content icon: document links: - id: an_posts type: MenuLink properties: title: Posts - id: an_media type: MenuLink properties: title: Media Library - id: an_users type: MenuGroup properties: title: Users icon: users links: - id: an_all_users type: MenuLink properties: title: All Users - id: an_roles type: MenuLink properties: title: Roles - id: an_divider type: MenuDivider - id: an_settings type: MenuLink properties: title: Settings icon: settings - id: an_divider2 type: MenuDivider properties: dashed: true - id: an_logout type: MenuLink properties: title: Sign Out icon: logout danger: true events: onMenuItemClick: - id: admin_click_msg type: DisplayMessage params: content: Navigating... status: info - id: admin_title type: Markdown layout: flex: 0 0 auto properties: content: "**Admin Panel**" - id: admin_actions type: Box layout: direction: row align: center gap: 8 flex: 0 0 auto blocks: - id: admin_bell type: Button layout: flex: 0 0 auto properties: icon: bell hideTitle: true variant: text color: default - id: admin_avatar type: Button layout: flex: 0 0 auto properties: icon: user hideTitle: true shape: circle variant: filled color: primary - id: admin_body type: Card properties: title: Welcome Back size: small blocks: - id: admin_stats type: Markdown properties: content: > You have **12** new orders, **3** pending reviews, and **5** unread messages. Use the navigation menu to manage your application. ``` **ShopNow** Browse our latest collection across all categories. **Free shipping** on orders over $50. Use the menu to explore departments. ```yaml - id: shop_header type: Box class: bg-gradient-to-r from-indigo-600 to-purple-600 px-4 py-3 layout: direction: row align: center justify: space-between blocks: - id: shop_left type: Box layout: direction: row align: center gap: 12 flex: 0 0 auto blocks: - id: shop_nav type: MobileMenu layout: flex: 0 0 auto properties: toggleMenuButton: type: primary ghost: true icon: menu drawer: title: Shop Categories width: 300 theme: light selectedKeys: - sn_clothing links: - id: sn_home type: MenuLink url: / properties: title: Home icon: home - id: sn_divider_top type: MenuDivider - id: sn_clothing type: MenuGroup properties: title: Clothing icon: Shirt links: - id: sn_mens type: MenuLink properties: title: Men's Wear - id: sn_womens type: MenuLink properties: title: Women's Wear - id: sn_kids type: MenuLink properties: title: Kids - id: sn_electronics type: MenuGroup properties: title: Electronics icon: Laptop links: - id: sn_phones type: MenuLink properties: title: Phones & Tablets - id: sn_computers type: MenuLink properties: title: Computers - id: sn_divider_bottom type: MenuDivider properties: dashed: true - id: sn_deals type: MenuLink properties: title: Today's Deals icon: Zap - id: sn_orders type: MenuLink pageId: orders properties: title: My Orders icon: ShoppingCart events: onMenuItemSelect: - id: shop_select_msg type: DisplayMessage params: content: Loading category... status: info onToggleDrawer: - id: shop_toggle_state type: SetState params: menuOpen: _not: _state: menuOpen - id: shop_brand type: Markdown layout: flex: 0 0 auto style: .element: color: white properties: content: "**ShopNow**" - id: shop_right type: Box layout: direction: row align: center gap: 8 flex: 0 0 auto blocks: - id: shop_search type: Button layout: flex: 0 0 auto properties: icon: search hideTitle: true variant: text ghost: true color: default - id: shop_cart type: Button layout: flex: 0 0 auto properties: icon: ShoppingCart hideTitle: true variant: text ghost: true color: default - id: shop_content type: Card properties: title: Featured Products size: small blocks: - id: shop_desc type: Markdown properties: content: > Browse our latest collection across all categories. **Free shipping** on orders over $50. Use the menu to explore departments. ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `toggleMenuButton` | object | - | Toggle menu button properties. | | `drawer` | object | - | Menu drawer properties. | | `logo` | object | - | Logo settings for the mobile menu drawer header. | | `logo.src` | string | - | Logo source url. | | `logo.srcMobile` | string | - | Mobile logo source url. | | `logo.alt` | string | `"Lowdefy"` | Logo alternative text. | | `logo.style` | object | - | Css style object to apply to logo. | | `menuId` | string | - | App menu id used to get menu links. | | `selectedKeys` | array | - | Array with the keys of currently selected menu items. | | `subMenuCloseDelay` | number | - | Delay time to hide submenu when mouse leaves (in seconds). | | `subMenuOpenDelay` | number | - | Delay time to show submenu when mouse enters (in seconds). | | `theme` | string | `"light"` | Color theme of menu. Enum: `dark`, `light`. | | `links` | array | - | | | `links.$.id` | string | - | Menu item id. | | `links.$.type` | string | `"MenuLink"` | Menu item type. Enum: `MenuDivider`, `MenuLink`, `MenuGroup`. | | `links.$.pageId` | string | - | Page to link to. | | `links.$.style` | object | - | Css style to applied to link. | | `links.$.properties` | object | - | properties from menu item. | | `links.$.properties.title` | string | - | Menu item title. | | `links.$.properties.icon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to customize icon on menu item. | | `links.$.properties.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `links.$.properties.icon.color` | string | - | Icon color. | | `links.$.properties.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `links.$.properties.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `links.$.properties.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `links.$.properties.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `links.$.properties.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `links.$.properties.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `links.$.properties.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `links.$.properties.danger` | boolean | `false` | Apply danger style to menu item. | | `links.$.properties.dashed` | boolean | `false` | Whether the divider line is dashed. | | `links.$.links` | array | - | | | `links.$.links.$.id` | string | - | Menu item id. | | `links.$.links.$.type` | string | `"MenuLink"` | Menu item type. Enum: `MenuDivider`, `MenuLink`, `MenuGroup`. | | `links.$.links.$.style` | object | - | Css style to applied to sub-link. | | `links.$.links.$.pageId` | string | - | Page to link to. | | `links.$.links.$.properties` | object | - | properties from menu item. | | `links.$.links.$.properties.title` | string | - | Menu item title. | | `links.$.links.$.properties.danger` | boolean | `false` | Apply danger style to menu item. | | `links.$.links.$.properties.dashed` | boolean | `false` | Whether the divider line is dashed. | | `links.$.links.$.links` | array | - | | | `links.$.links.$.links.$.id` | string | - | Menu item id. | | `links.$.links.$.links.$.type` | string | `"MenuLink"` | Menu item type. Enum: `MenuDivider`, `MenuLink`. | | `links.$.links.$.links.$.style` | object | - | Css style to applied to sub-link. | | `links.$.links.$.links.$.pageId` | string | - | Page to link to. | | `links.$.links.$.links.$.properties` | object | - | properties from menu item. | | `links.$.links.$.links.$.properties.title` | string | - | Menu item title. | | `links.$.links.$.links.$.properties.danger` | boolean | `false` | Apply danger style to menu item. | | `links.$.links.$.links.$.properties.dashed` | boolean | `false` | Whether the divider line is dashed. | | Event | Event Data | Description | | --- | --- | --- | | `onToggleDrawer` | \- | Trigger action when mobile menu drawer is toggled. | | `onClose` | \- | Trigger action when mobile menu is closed. | | `onOpen` | \- | Trigger action when mobile menu is opened. | | `onMenuItemSelect` | \- | Trigger action when menu item is selected. | | `onMenuItemClick` | \- | Trigger action when menu item is clicked. | | `onToggleMenuGroup` | \- | Trigger action when mobile menu group is opened. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The MobileMenu element. | | Slot | Description | | --- | --- | | `drawerHeader` | Content above the menu in the drawer. | | `drawerContent` | Additional content below the menu in the drawer. | | `drawerFooter` | Footer content in the drawer. | --- url: https://docs.lowdefy.com/md/container-blocks/modal.md --- # Modal Modal dialog overlay for focused content. Add `shortcut: Escape` to the `onClose` event to run custom actions when Escape is pressed. Ant Design's Modal already closes on Escape by default — the shortcut triggers your custom actions in addition to the built-in close. This is a basic modal dialog with a title, content area, and default Ok/Cancel buttons. Click Ok to confirm or Cancel to dismiss. ```yaml - id: modal_basic_trigger type: Button layout: flex: 0 0 auto properties: title: Open Modal color: primary variant: solid events: onClick: - id: modal_basic_open type: CallMethod params: blockId: modal_basic method: setOpen args: - open: true - id: modal_basic type: Modal properties: title: Basic Modal blocks: - id: modal_basic_p1 type: Paragraph properties: content: This is a basic modal dialog with a title, content area, and default Ok/Cancel buttons. - id: modal_basic_p2 type: Paragraph properties: content: Click Ok to confirm or Cancel to dismiss. ``` The modal title supports HTML markup for rich text formatting. ```yaml - id: modal_html_title_trigger type: Button layout: flex: 0 0 auto properties: title: Open Modal with HTML Title color: primary variant: outlined events: onClick: - id: modal_html_title_open type: CallMethod params: blockId: modal_html_title method: setOpen args: - open: true - id: modal_html_title type: Modal properties: title: Bold and italic title blocks: - id: modal_html_title_content type: Paragraph properties: content: The modal title supports HTML markup for rich text formatting. ``` ```yaml - id: modal_padding_trigger type: Button layout: flex: 0 0 auto properties: title: Open Modal with padding none events: onClick: - id: modal_padding_open type: CallMethod params: blockId: modal_padding method: setOpen args: - open: true - id: modal_padding type: Modal properties: title: Recent invoices padding: none blocks: - id: modal_padding_rows type: Html properties: html: >
INV-1042$1,280.00
INV-1041$640.00
INV-1040$2,115.50
INV-1039$96.00
``` The okText and cancelText properties customize the footer button labels. This modal uses "Save Changes" and "Discard" instead of "Ok" and "Cancel". ```yaml - id: modal_btn_text_trigger type: Button layout: flex: 0 0 auto properties: title: Open Modal color: primary variant: outlined events: onClick: - id: modal_btn_text_open type: CallMethod params: blockId: modal_btn_text method: setOpen args: - open: true - id: modal_btn_text type: Modal properties: title: Custom Button Text okText: Save Changes cancelText: Discard blocks: - id: modal_btn_text_content type: Paragraph properties: content: The okText and cancelText properties customize the footer button labels. This modal uses "Save Changes" and "Discard" instead of "Ok" and "Cancel". ``` Custom button props allow fine-grained control over the footer buttons. Here the Ok button is styled as a danger button, and the Cancel button uses primary styling. ```yaml - id: modal_btn_props_trigger type: Button layout: flex: 0 0 auto properties: title: Open Modal with Custom Buttons color: primary variant: outlined events: onClick: - id: modal_btn_props_open type: CallMethod params: blockId: modal_btn_props method: setOpen args: - open: true - id: modal_btn_props type: Modal properties: title: Danger Confirmation okText: Delete cancelText: Keep okButtonProps: danger: true cancelButtonProps: type: primary blocks: - id: modal_btn_props_content type: Paragraph properties: content: Custom button props allow fine-grained control over the footer buttons. Here the Ok button is styled as a danger button, and the Cancel button uses primary styling. ``` This modal is vertically centered in the viewport. By default, modals appear near the top of the page. ```yaml - id: modal_centered_trigger type: Button layout: flex: 0 0 auto properties: title: Open Centered Modal color: primary variant: outlined events: onClick: - id: modal_centered_open type: CallMethod params: blockId: modal_centered method: setOpen args: - open: true - id: modal_centered type: Modal properties: title: Centered Modal centered: true blocks: - id: modal_centered_content type: Paragraph properties: content: This modal is vertically centered in the viewport. By default, modals appear near the top of the page. ``` A compact 300px modal, suitable for simple confirmations. An 800px wide modal with more room for complex content like forms, tables, or detail views. Using a percentage width makes the modal responsive, taking up 80% of the viewport width. ```yaml - id: modal_narrow_trigger type: Button layout: flex: 0 0 auto properties: title: Narrow (300px) color: primary variant: outlined size: small events: onClick: - id: modal_narrow_open type: CallMethod params: blockId: modal_narrow method: setOpen args: - open: true - id: modal_wide_trigger type: Button layout: flex: 0 0 auto properties: title: Wide (800px) color: primary variant: outlined size: small events: onClick: - id: modal_wide_open type: CallMethod params: blockId: modal_wide method: setOpen args: - open: true - id: modal_pct_trigger type: Button layout: flex: 0 0 auto properties: title: Percentage (80%) color: primary variant: outlined size: small events: onClick: - id: modal_pct_open type: CallMethod params: blockId: modal_pct method: setOpen args: - open: true - id: modal_narrow type: Modal properties: title: Narrow Modal width: 300 blocks: - id: modal_narrow_content type: Paragraph properties: content: A compact 300px modal, suitable for simple confirmations. - id: modal_wide type: Modal properties: title: Wide Modal width: 800 blocks: - id: modal_wide_content type: Paragraph properties: content: An 800px wide modal with more room for complex content like forms, tables, or detail views. - id: modal_pct type: Modal properties: title: 80% Width Modal width: 80% blocks: - id: modal_pct_content type: Paragraph properties: content: Using a percentage width makes the modal responsive, taking up 80% of the viewport width. ``` The close button is visible in the top right corner. This is the default behavior. The close (x) button is hidden. Users must use the Ok or Cancel buttons to close this modal. ```yaml - id: modal_closable_trigger type: Button layout: flex: 0 0 auto properties: title: Closable (default) color: primary variant: outlined events: onClick: - id: modal_closable_open type: CallMethod params: blockId: modal_closable method: setOpen args: - open: true - id: modal_not_closable_trigger type: Button layout: flex: 0 0 auto properties: title: No Close Button color: danger variant: outlined events: onClick: - id: modal_not_closable_open type: CallMethod params: blockId: modal_not_closable method: setOpen args: - open: true - id: modal_closable type: Modal properties: title: Closable Modal closable: true blocks: - id: modal_closable_content type: Paragraph properties: content: The close button is visible in the top right corner. This is the default behavior. - id: modal_not_closable type: Modal properties: title: No Close Button closable: false blocks: - id: modal_not_closable_content type: Paragraph properties: content: The close (x) button is hidden. Users must use the Ok or Cancel buttons to close this modal. ``` The mask is visible but clicking it does not close the modal. Users must use the buttons or close icon. No mask overlay is shown behind the modal. The page content remains fully visible. The page behind the mask is blurred, which keeps attention on the dialog. ```yaml - id: modal_mask_not_closable_trigger type: Button layout: flex: 0 0 auto properties: title: Mask Not Closable color: primary variant: outlined events: onClick: - id: modal_mask_not_closable_open type: CallMethod params: blockId: modal_mask_not_closable method: setOpen args: - open: true - id: modal_no_mask_trigger type: Button layout: flex: 0 0 auto properties: title: No Mask color: primary variant: outlined events: onClick: - id: modal_no_mask_open type: CallMethod params: blockId: modal_no_mask method: setOpen args: - open: true - id: modal_mask_not_closable type: Modal properties: title: Mask Not Closable maskClosable: false blocks: - id: modal_mask_not_closable_content type: Paragraph properties: content: The mask is visible but clicking it does not close the modal. Users must use the buttons or close icon. - id: modal_no_mask type: Modal properties: title: No Mask mask: false blocks: - id: modal_no_mask_content type: Paragraph properties: content: No mask overlay is shown behind the modal. The page content remains fully visible. - id: modal_blur_mask_trigger type: Button layout: flex: 0 0 auto properties: title: Blurred Mask color: primary variant: outlined events: onClick: - id: modal_blur_mask_open type: CallMethod params: blockId: modal_blur_mask method: setOpen args: - open: true - id: modal_blur_mask type: Modal properties: title: Blurred Mask mask: blur: true blocks: - id: modal_blur_mask_content type: Paragraph properties: content: The page behind the mask is blurred, which keeps attention on the dialog. ``` The body shows a skeleton while loading is true, for example while a request that fills the modal is running. ```yaml - id: modal_loading_trigger type: Button layout: flex: 0 0 auto properties: title: Open Loading Modal color: primary variant: outlined events: onClick: - id: modal_loading_open type: CallMethod params: blockId: modal_loading method: setOpen args: - open: true - id: modal_loading type: Modal properties: title: Customer Details loading: true width: xs: 90% md: 600 blocks: - id: modal_loading_content type: Paragraph properties: content: The body shows a skeleton while loading is true, for example while a request that fills the modal is running. ``` Setting footer to false removes the Ok and Cancel buttons entirely. This is useful for informational dialogs or when you provide your own action buttons in the content area. ```yaml - id: modal_no_footer_trigger type: Button layout: flex: 0 0 auto properties: title: Open Modal (no footer) color: primary variant: outlined events: onClick: - id: modal_no_footer_open type: CallMethod params: blockId: modal_no_footer method: setOpen args: - open: true - id: modal_no_footer type: Modal properties: title: No Footer Modal footer: false blocks: - id: modal_no_footer_content type: Paragraph properties: content: Setting footer to false removes the Ok and Cancel buttons entirely. This is useful for informational dialogs or when you provide your own action buttons in the content area. - id: modal_no_footer_close_btn type: Button properties: title: Close color: primary variant: solid block: true events: onClick: - id: modal_no_footer_close type: CallMethod params: blockId: modal_no_footer method: setOpen args: - open: false ``` The footer slot replaces the default Ok/Cancel buttons with custom content. This example shows a Delete button on the left and Save/Cancel on the right. ```yaml - id: modal_footer_slot_trigger type: Button layout: flex: 0 0 auto properties: title: Open Modal with Custom Footer color: primary variant: solid events: onClick: - id: modal_footer_slot_open type: CallMethod params: blockId: modal_footer_slot method: setOpen args: - open: true - id: modal_footer_slot type: Modal properties: title: Custom Footer slots: footer: blocks: - id: modal_footer_slot_row type: Box layout: gap: 8 justify: space-between blocks: - id: modal_footer_delete_btn type: Button layout: flex: 0 0 auto properties: title: Delete color: danger variant: text icon: delete - id: modal_footer_actions type: Box layout: flex: 0 0 auto gap: 8 blocks: - id: modal_footer_cancel_btn type: Button layout: flex: 0 0 auto properties: title: Cancel color: default variant: outlined events: onClick: - id: modal_footer_cancel_close type: CallMethod params: blockId: modal_footer_slot method: setOpen args: - open: false - id: modal_footer_save_btn type: Button layout: flex: 0 0 auto properties: title: Save color: primary variant: solid icon: save events: onClick: - id: modal_footer_save_close type: CallMethod params: blockId: modal_footer_slot method: setOpen args: - open: false blocks: - id: modal_footer_slot_content type: Paragraph properties: content: The footer slot replaces the default Ok/Cancel buttons with custom content. This example shows a Delete button on the left and Save/Cancel on the right. ``` The style .wrapper key applies CSS to the modal wrapper element. This can control overflow, positioning, and other layout aspects of the wrapper container. ```yaml - id: modal_wrapper_style_trigger type: Button layout: flex: 0 0 auto properties: title: Open Modal with Wrapper Style color: primary variant: outlined events: onClick: - id: modal_wrapper_style_open type: CallMethod params: blockId: modal_wrapper_style method: setOpen args: - open: true - id: modal_wrapper_style type: Modal properties: title: Wrapper Style Modal style: .wrapper: overflow: hidden blocks: - id: modal_wrapper_style_content type: Paragraph properties: content: The style .wrapper key applies CSS to the modal wrapper element. This can control overflow, positioning, and other layout aspects of the wrapper container. ``` This modal uses a custom z-index of 2000 to ensure it appears above other overlays. The default is 1000. ```yaml - id: modal_zindex_trigger type: Button layout: flex: 0 0 auto properties: title: Open Modal (z-index 2000) color: primary variant: outlined events: onClick: - id: modal_zindex_open type: CallMethod params: blockId: modal_zindex method: setOpen args: - open: true - id: modal_zindex type: Modal properties: title: High Z-Index Modal zIndex: 2000 blocks: - id: modal_zindex_content type: Paragraph properties: content: This modal uses a custom z-index of 2000 to ensure it appears above other overlays. The default is 1000. ``` This modal uses the toggleOpen method. Each click of the button alternates between open and closed states. ```yaml - id: modal_toggle_trigger type: Button layout: flex: 0 0 auto properties: title: Toggle Modal color: primary variant: solid icon: ArrowRightLeft events: onClick: - id: modal_toggle_call type: CallMethod params: blockId: modal_toggle method: toggleOpen - id: modal_toggle type: Modal properties: title: Toggle Modal blocks: - id: modal_toggle_content type: Paragraph properties: content: This modal uses the toggleOpen method. Each click of the button alternates between open and closed states. ``` This modal demonstrates all four events. onOpen fires when the modal opens, onOk when the Ok button is clicked, onCancel when Cancel is clicked, and onClose fires after either onOk or onCancel completes. ```yaml - id: modal_events_trigger type: Button layout: flex: 0 0 auto properties: title: Open Modal with Events color: primary variant: solid events: onClick: - id: modal_events_open type: CallMethod params: blockId: modal_events method: setOpen args: - open: true - id: modal_events type: Modal properties: title: Modal with Events events: onOpen: - id: modal_events_on_open type: DisplayMessage params: content: Modal opened. status: info onOk: - id: modal_events_on_ok type: DisplayMessage params: content: Ok button clicked. status: success onCancel: - id: modal_events_on_cancel type: DisplayMessage params: content: Cancel button clicked. status: warning onClose: - id: modal_events_on_close type: DisplayMessage params: content: Modal closed. status: info blocks: - id: modal_events_content type: Paragraph properties: content: This modal demonstrates all four events. onOpen fires when the modal opens, onOk when the Ok button is clicked, onCancel when Cancel is clicked, and onClose fires after either onOk or onCancel completes. ``` This modal overrides design tokens via the theme property. The header has a blue background, custom title color and size, and larger border radius. A warm-toned modal using an extensive set of theme tokens including headerBg, contentBg, footerBg, titleColor, colorBgMask, headerBorderBottom, footerBorderTop, headerPadding, bodyPadding, footerPadding, and margin tokens. ```yaml - id: modal_theme_blue_trigger type: Button layout: flex: 0 0 auto properties: title: Open Blue Themed Modal color: primary variant: solid events: onClick: - id: modal_theme_blue_open type: CallMethod params: blockId: modal_theme_blue method: setOpen args: - open: true - id: modal_theme_blue type: Modal properties: title: Blue Theme Modal theme: titleColor: "#0958d9" titleFontSize: 20 titleLineHeight: 1.8 borderRadiusLG: 16 blocks: - id: modal_theme_blue_content type: Paragraph properties: content: This modal overrides design tokens via the theme property. The header has a blue background, custom title color and size, and larger border radius. - id: modal_theme_warm_trigger type: Button layout: flex: 0 0 auto properties: title: Open Warm Themed Modal color: default variant: solid events: onClick: - id: modal_theme_warm_open type: CallMethod params: blockId: modal_theme_warm method: setOpen args: - open: true - id: modal_theme_warm type: Modal properties: title: Warm Theme Modal theme: titleColor: "#d46b08" colorBgMask: rgba(250, 173, 20, 0.1) borderRadiusLG: 20 headerBorderBottom: "1px solid #ffd591" footerBorderTop: "1px solid #ffd591" headerMarginBottom: 0 footerMarginTop: 0 headerPadding: 16px 24px bodyPadding: 24 footerPadding: 16px 24px blocks: - id: modal_theme_warm_content type: Paragraph properties: content: A warm-toned modal using an extensive set of theme tokens including headerBg, contentBg, footerBg, titleColor, colorBgMask, headerBorderBottom, footerBorderTop, headerPadding, bodyPadding, footerPadding, and margin tokens. ``` This modal uses style.body and style.mask to apply custom background colors. The body has a green tint and the mask has a subtle green overlay. The element, wrapper, header, body, footer, mask, and content CSS keys allow targeting different parts of the modal with inline styles or Tailwind classes. ```yaml - id: modal_styled_body_trigger type: Button layout: flex: 0 0 auto properties: title: Open Styled Modal color: primary variant: solid events: onClick: - id: modal_styled_body_open type: CallMethod params: blockId: modal_styled_body method: setOpen args: - open: true - id: modal_styled_body type: Modal properties: title: Styled Modal style: .body: padding: 24px .mask: backgroundColor: rgba(0, 100, 0, 0.15) blocks: - id: modal_styled_body_content type: Paragraph properties: content: This modal uses style.body and style.mask to apply custom background colors. The body has a green tint and the mask has a subtle green overlay. - id: modal_styled_wrapper_trigger type: Button layout: flex: 0 0 auto properties: title: Open Modal with Wrapper Style color: primary variant: outlined events: onClick: - id: modal_styled_wrapper_open type: CallMethod params: blockId: modal_styled_wrapper method: setOpen args: - open: true - id: modal_styled_wrapper type: Modal properties: title: Custom Wrapper Style style: .element: borderRadius: 20px .wrapper: backdropFilter: blur(2px) blocks: - id: modal_styled_wrapper_content type: Paragraph properties: content: The element, wrapper, header, body, footer, mask, and content CSS keys allow targeting different parts of the modal with inline styles or Tailwind classes. ``` Modal supports these CSS keys for Tailwind class targeting: element, header, body, footer, mask, wrapper, content. Use class as an object to apply Tailwind utilities to each area independently. ```yaml - id: modal_css_keys_trigger type: Button layout: flex: 0 0 auto properties: title: Open Modal with Tailwind Classes color: primary variant: solid events: onClick: - id: modal_css_keys_open type: CallMethod params: blockId: modal_css_keys method: setOpen args: - open: true - id: modal_css_keys type: Modal properties: title: Tailwind Styled Modal class: .header: bg-bg-layout .body: bg-bg-layout p-8 .footer: bg-bg-layout .content: rounded-2xl overflow-hidden blocks: - id: modal_css_keys_content type: Paragraph properties: content: "Modal supports these CSS keys for Tailwind class targeting: element, header, body, footer, mask, wrapper, content. Use class as an object to apply Tailwind utilities to each area independently." ``` Jane Cooper Senior Product Designer ```yaml - id: modal_profile_trigger type: Button layout: flex: 0 0 auto properties: title: Edit Profile color: primary variant: solid icon: edit events: onClick: - id: modal_profile_open type: CallMethod params: blockId: modal_profile method: setOpen args: - open: true - id: modal_profile type: Modal properties: title: Edit Your Profile width: 640 okText: Save Profile cancelText: Cancel centered: true theme: headerBorderBottom: "1px solid #f0f0f0" headerMarginBottom: 0 events: onOk: - id: modal_profile_save type: DisplayMessage params: content: Profile saved successfully. status: success blocks: - id: modal_profile_avatar_row type: Box layout: gap: 16 align: center blocks: - id: modal_profile_avatar type: Avatar layout: flex: 0 0 auto properties: icon: user size: 72 color: "#1677ff" - id: modal_profile_info type: Box layout: flex: 1 1 0 direction: column gap: 4 blocks: - id: modal_profile_name_title type: Title properties: content: Jane Cooper level: 4 - id: modal_profile_role_text type: Paragraph properties: content: Senior Product Designer - id: modal_profile_divider type: Divider - id: modal_profile_name_input type: TextInput properties: title: Full Name placeholder: Enter your full name - id: modal_profile_email_input type: TextInput properties: title: Email Address placeholder: Enter your email - id: modal_profile_bio_input type: TextArea properties: title: Bio placeholder: Tell us about yourself rows: 3 ``` This action cannot be undone. All project data, files, and team assignments will be permanently removed. ```yaml - id: modal_confirm_trigger type: Button layout: flex: 0 0 auto properties: title: Delete Project color: danger variant: solid icon: delete events: onClick: - id: modal_confirm_open type: CallMethod params: blockId: modal_confirm method: setOpen args: - open: true - id: modal_confirm type: Modal properties: title: Delete Project? width: 420 centered: true okText: Delete Permanently cancelText: Keep Project okButtonProps: danger: true closable: false maskClosable: false theme: titleColor: "#cf1322" events: onOk: - id: modal_confirm_delete type: DisplayMessage params: content: Project deleted. status: success onCancel: - id: modal_confirm_keep type: DisplayMessage params: content: Project kept. status: info blocks: - id: modal_confirm_warning type: Paragraph properties: content: This action cannot be undone. All project data, files, and team assignments will be permanently removed. - id: modal_confirm_details type: Descriptions properties: column: 1 size: small items: - label: Project value: Project Alpha - label: Owner value: Jane Cooper - label: Files value: 248 files (1.2 GB) ``` A serene mountain landscape captured at golden hour. Photography by John Doe. ```yaml - id: modal_image_trigger type: Button layout: flex: 0 0 auto properties: title: Preview Image color: primary variant: outlined icon: image events: onClick: - id: modal_image_open type: CallMethod params: blockId: modal_image method: setOpen args: - open: true - id: modal_image type: Modal properties: title: Mountain Landscape width: 720 footer: false centered: true theme: bodyPadding: 0 headerMarginBottom: 0 blocks: - id: modal_image_html type: Html properties: html: Mountain
          landscape - id: modal_image_caption type: Box layout: gap: 8 align: center justify: space-between class: p-4 blocks: - id: modal_image_desc type: Paragraph layout: flex: 1 1 0 properties: content: A serene mountain landscape captured at golden hour. Photography by John Doe. - id: modal_image_tags type: Box layout: flex: 0 0 auto gap: 8 blocks: - id: modal_image_tag1 type: Tag properties: title: Photography color: blue - id: modal_image_tag2 type: Tag properties: title: Featured color: gold ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `centered` | boolean | `false` | Center the modal vertically. | | `closable` | boolean \| object | `true` | Whether a close (x) button is visible on top right of the modal dialog or not. Set `{ disabled: true }` to show the button disabled. | | `closable.disabled` | boolean | `false` | Show the close button, but disabled. | | `destroyOnHidden` | boolean | `false` | Unmount the blocks inside the modal when it closes, so they mount again (and their onMount events run) each time it opens. Their state is kept. | | `focusable` | object | - | Focus management of the dialog. | | `focusable.trap` | boolean | `true` | Keep keyboard focus inside the dialog while it is open. | | `focusable.focusTriggerAfterClose` | boolean | `true` | Return focus to the element that opened the dialog after it closes. | | `forceRender` | boolean | `false` | Render the blocks inside the modal before it is first opened, so their methods can be called and their onMount events run while it is still closed. | | `keyboard` | boolean | `true` | Whether pressing Esc closes the modal. | | `loading` | boolean | `false` | Show a loading skeleton in place of the modal body. | | `padding` | string | `"default"` | Space inside the modal. `compact` tightens it to 16px. `none` removes the body padding so content such as a table, list or image runs edge to edge; the header and footer keep their padding. Enum: `default`, `compact`, `none`. | | `scrollLock` | boolean | `true` | Whether to lock page scrolling while the modal is open. | | `title` | string | - | The modal dialog's title - supports html. | | `footer` | boolean | `true` | Show footer area. | | `mask` | boolean \| object | `true` | Whether to show the mask. Set an object to configure the mask with `enabled`, `blur` and `closable`. | | `mask.enabled` | boolean | `true` | Whether to show the mask. | | `mask.blur` | boolean | `false` | Blur the page behind the mask. | | `mask.closable` | boolean | - | Whether clicking the mask closes the dialog. Takes precedence over `maskClosable`. | | `maskClosable` | boolean | `true` | Whether to close the modal dialog when the mask (area outside the modal) is clicked. `mask.closable` takes precedence. | | `okText` | string | - | Text of the Ok button. When unset, antd uses the localized default from ConfigProvider locale. | | `okButtonProps` | object | - | Set additional properties for the ok button. | | `cancelText` | string | - | Text of the Cancel button. When unset, antd uses the localized default from ConfigProvider locale. | | `cancelButtonProps` | object | - | Set additional properties for the cancel button. | | `width` | string \| number \| object | `520` | Width of the modal dialog. Set an object of breakpoints (`xs`, `sm`, `md`, `lg`, `xl`, `xxl`) for a responsive width. | | `width.xs` | string \| number | - | Width on extra small screens. | | `width.sm` | string \| number | - | Width on small screens. | | `width.md` | string \| number | - | Width on medium screens. | | `width.lg` | string \| number | - | Width on large screens. | | `width.xl` | string \| number | - | Width on extra large screens. | | `width.xxl` | string \| number | - | Width on extra extra large screens. | | `zIndex` | integer | `1000` | The z-index of the modal. Useful when displaying two modals simultaneously. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design modal tokens](https://ant.design/components/modal#design-token). | | `theme.headerBg` | string | - | Background color of the modal header. | | `theme.titleLineHeight` | number | `1.5` | Line height of the modal title. | | `theme.titleFontSize` | number | `16` | Font size of the modal title. | | `theme.titleColor` | string | - | Color of the modal title text. | | `theme.contentBg` | string | - | Background color of the modal content. | | `theme.footerBg` | string | `"transparent"` | Background color of the modal footer. | | `theme.contentPadding` | number \| string | - | Padding of the content area. | | `theme.headerPadding` | number \| string | - | Padding of the header area. | | `theme.headerBorderBottom` | string | - | Border bottom of the header. | | `theme.headerMarginBottom` | number | `8` | Margin bottom of the header. | | `theme.bodyPadding` | number | - | Padding of the body area. | | `theme.footerPadding` | number \| string | - | Padding of the footer area. | | `theme.footerBorderTop` | string | - | Border top of the footer. | | `theme.footerBorderRadius` | number \| string | - | Border radius of the footer. | | `theme.footerMarginTop` | number \| string | `8` | Margin top of the footer. | | `theme.confirmBodyPadding` | number \| string | - | Padding of the confirm modal body. | | `theme.confirmIconMarginInlineEnd` | number \| string | - | Inline end margin of the confirm modal icon. | | `theme.confirmBtnsMarginTop` | number \| string | - | Margin top of the confirm modal buttons. | | `theme.borderRadiusLG` | number | `8` | Border radius of the modal. | | `theme.colorBgMask` | string | `"rgba(0, 0, 0, 0.45)"` | Background color of the modal mask. | | Event | Event Data | Description | | --- | --- | --- | | `onOk` | \- | Trigger actions when Ok button is clicked. | | `onOpen` | \- | Trigger actions when modal is opened. | | `onCancel` | \- | Trigger actions when Cancel button is clicked. | | `onClose` | \- | Trigger actions after onOk or onCancel is completed. | | `afterClose` | \- | Trigger actions after the modal has closed and its close animation has finished. | | `afterOpenChange` | `{ open }` | Trigger actions after the open or close animation of the modal has finished. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Modal element. | | `/header` | The Modal header. | | `/title` | The Modal title. | | `/body` | The Modal body. | | `/footer` | The Modal footer. | | `/mask` | The Modal mask. | | `/wrapper` | The Modal wrapper. | | `/content` | The Modal content. | | Slot | Description | | --- | --- | | `content` | Main Modal body. | | `footer` | Custom footer. Overrides default Ok/Cancel buttons. | --- url: https://docs.lowdefy.com/md/container-blocks/pageheadermenu.md --- # PageHeaderMenu Page layout with a top header navigation menu. Welcome Follow the quick start guide to set up your first project in minutes. Browse the full API reference and configuration guides. ```yaml - id: phm_basic type: PageHeaderMenu properties: menu: links: - id: phm_basic_home type: MenuLink properties: title: Home icon: home - id: phm_basic_products type: MenuLink properties: title: Products icon: grid - id: phm_basic_pricing type: MenuLink properties: title: Pricing - id: phm_basic_contact type: MenuLink properties: title: Contact blocks: - id: phm_basic_title type: Title properties: content: Welcome level: 2 - id: phm_basic_cards type: Box layout: gap: 16 blocks: - id: phm_basic_card_1 type: Card layout: flex: 1 1 0 properties: title: Getting Started blocks: - id: phm_basic_card_1_text type: Paragraph properties: content: Follow the quick start guide to set up your first project in minutes. - id: phm_basic_card_2 type: Card layout: flex: 1 1 0 properties: title: Documentation blocks: - id: phm_basic_card_2_text type: Paragraph properties: content: Browse the full API reference and configuration guides. ``` padding: compact trims the content padding from 40px to 16px, so data-heavy pages show more. ```yaml - id: phm_compact type: PageHeaderMenu properties: padding: compact menu: links: - id: phm_compact_home type: MenuLink properties: title: Home - id: phm_compact_invoices type: MenuLink properties: title: Invoices blocks: - id: phm_compact_text type: Paragraph properties: content: "padding: compact trims the content padding from 40px to 16px, so data-heavy pages show more." - id: phm_compact_card type: Card properties: title: Recent invoices padding: none blocks: - id: phm_compact_card_rows type: Html properties: html: >
INV-1042$1,280.00
INV-1041$640.00
INV-1040$2,115.50
INV-1039$96.00
``` Analytics Overview ```yaml - id: phm_grouped type: PageHeaderMenu properties: darkModeToggle: true breadcrumb: separator: / list: - label: Home icon: home - label: Analytics - label: Overview menu: links: - id: phm_g_dash type: MenuLink properties: title: Dashboard icon: Gauge - id: phm_g_analytics type: MenuGroup properties: title: Analytics icon: chart links: - id: phm_g_overview type: MenuLink properties: title: Overview - id: phm_g_realtime type: MenuLink properties: title: Real-time - id: phm_g_funnel type: MenuLink properties: title: Funnels - id: phm_g_settings type: MenuLink properties: title: Settings icon: settings blocks: - id: phm_g_title type: Title properties: content: Analytics Overview level: 3 - id: phm_g_cards type: Box layout: gap: 16 blocks: - id: phm_g_card_visits type: Card layout: flex: 1 1 200px properties: size: small blocks: - id: phm_g_stat_visits type: Statistic properties: title: Page Views value: 12847 - id: phm_g_card_users type: Card layout: flex: 1 1 200px properties: size: small blocks: - id: phm_g_stat_users type: Statistic properties: title: Active Users value: 1024 - id: phm_g_card_rate type: Card layout: flex: 1 1 200px properties: size: small blocks: - id: phm_g_stat_rate type: Statistic properties: title: Bounce Rate value: 23.4 suffix: "%" ``` Dashboard ```yaml - id: phm_profile type: PageHeaderMenu properties: darkModeToggle: true notifications: count: 3 profile: avatar: content: JD color: "#1677ff" links: - id: phm_prof_my_profile type: MenuLink properties: title: My Profile icon: user - id: phm_prof_settings type: MenuLink properties: title: Settings icon: settings - id: phm_prof_divider type: MenuDivider - id: phm_prof_logout type: MenuLink properties: title: Logout icon: logout danger: true menu: links: - id: phm_prof_home type: MenuLink properties: title: Home icon: home - id: phm_prof_projects type: MenuLink properties: title: Projects icon: SquareKanban - id: phm_prof_reports type: MenuLink properties: title: Reports icon: chart blocks: - id: phm_prof_title type: Title properties: content: Dashboard level: 3 - id: phm_prof_cards type: Box layout: gap: 16 blocks: - id: phm_prof_card_1 type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: phm_prof_stat_1 type: Statistic properties: title: Active Projects value: 12 - id: phm_prof_card_2 type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: phm_prof_stat_2 type: Statistic properties: title: Pending Tasks value: 8 ``` Welcome back, Jane Office hours updated: Building opens at 7:00 AM starting next Monday. Q1 All-Hands meeting scheduled for March 28 at 2:00 PM. Submit a Time-Off Request View Expense Reports ```yaml - id: phm_full type: PageHeaderMenu properties: darkModeToggle: true logo: alt: Acme Corp notifications: dot: true profile: avatar: icon: user breadcrumb: list: - Intranet - Team Directory menu: links: - id: phm_full_home type: MenuLink properties: title: Home icon: home - id: phm_full_directory type: MenuLink properties: title: Directory icon: users - id: phm_full_resources type: MenuGroup properties: title: Resources icon: Book links: - id: phm_full_policies type: MenuLink properties: title: Company Policies - id: phm_full_benefits type: MenuLink properties: title: Benefits Guide - id: phm_full_help type: MenuLink properties: title: IT Support icon: help slots: footer: blocks: - id: phm_full_footer_text type: Paragraph properties: content: Acme Corp Intranet - Internal Use Only style: .element: margin: 0 color: "#888" textAlign: center blocks: - id: phm_full_welcome type: Title properties: content: Welcome back, Jane level: 3 - id: phm_full_cards type: Box layout: gap: 16 blocks: - id: phm_full_card_announce type: Card layout: flex: 1 1 300px properties: title: Announcements blocks: - id: phm_full_announce_1 type: Paragraph properties: content: "Office hours updated: Building opens at 7:00 AM starting next Monday." - id: phm_full_announce_2 type: Paragraph properties: content: Q1 All-Hands meeting scheduled for March 28 at 2:00 PM. - id: phm_full_card_links type: Card layout: flex: 1 1 300px properties: title: Quick Links blocks: - id: phm_full_link_1 type: Paragraph properties: content: Submit a Time-Off Request - id: phm_full_link_2 type: Paragraph properties: content: View Expense Reports ``` Installation Get started by installing the framework via npm or yarn. Follow the steps below to set up your development environment. > Prerequisites 1. Initialize a new project directory and run the setup command. 2. Configure your lowdefy.yaml file with pages and connections. 3. Start the development server to preview your application. ```yaml - id: phm_light type: PageHeaderMenu properties: darkModeToggle: true menu: links: - id: phm_light_guide type: MenuLink properties: title: Guide icon: Book - id: phm_light_api type: MenuLink properties: title: API Reference icon: Plug - id: phm_light_examples type: MenuLink properties: title: Examples icon: CodeXml - id: phm_light_community type: MenuLink properties: title: Community icon: globe breadcrumb: separator: / list: - Docs - Getting Started - Installation slots: header: blocks: - id: phm_light_search_btn type: Button layout: flex: 0 0 auto properties: title: Search icon: search color: default variant: outlined size: small blocks: - id: phm_light_title type: Title properties: content: Installation level: 2 - id: phm_light_intro type: Paragraph properties: content: Get started by installing the framework via npm or yarn. Follow the steps below to set up your development environment. - id: phm_light_alert type: Alert properties: message: Prerequisites description: Make sure you have Node.js version 18 or higher installed before proceeding. type: info showIcon: true - id: phm_light_steps type: Card properties: title: Setup Steps size: small blocks: - id: phm_light_step_1 type: Paragraph properties: content: 1. Initialize a new project directory and run the setup command. - id: phm_light_step_2 type: Paragraph properties: content: 2. Configure your lowdefy.yaml file with pages and connections. - id: phm_light_step_3 type: Paragraph properties: content: 3. Start the development server to preview your application. ``` Profile from _menu > The profile dropdown links are populated using _menu operator, pulling from the menus defined in lowdefy.yaml. Click the avatar to see the dropdown. ```yaml - id: phm_menu_op type: PageHeaderMenu properties: darkModeToggle: true notifications: count: 2 link: pageId: home profile: avatar: content: JD color: "#6366f1" links: _menu: default menu: links: - id: phm_mo_home type: MenuLink properties: title: Home icon: home - id: phm_mo_settings type: MenuLink properties: title: Settings icon: settings blocks: - id: phm_mo_title type: Title properties: content: Profile from _menu level: 3 - id: phm_mo_info type: Alert properties: message: The profile dropdown links are populated using _menu operator, pulling from the menus defined in lowdefy.yaml. Click the avatar to see the dropdown. type: info showIcon: true ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `logo` | object | - | Header logo settings. By default, images are served from the app public folder and auto-swap between light and dark variants based on dark mode. See [Hosting Files](/hosting-files) for details. | | `logo.src` | string | - | Logo image URL for desktop. Defaults to logo-light-theme.png or logo-dark-theme.png from the public folder (~250x72px), auto-selected based on dark mode. | | `logo.srcMobile` | string | - | Logo image URL for mobile. Defaults to logo-square-light-theme.png or logo-square-dark-theme.png from the public folder (~125x125px), auto-selected based on dark mode. | | `logo.breakpoint` | number | - | Viewport width breakpoint (in px) for switching between mobile and desktop logo. Default is 577. | | `logo.alt` | string | `"Lowdefy"` | Logo image alt text. | | `header` | object | - | Header properties. | | `footer` | object | - | Footer properties. | | `padding` | string | `"default"` | Space around the page content. `compact` tightens it to 16px for dense, data-heavy pages. `none` removes it so content such as a table or grid runs edge to edge; the breadcrumb keeps its own inset. Enum: `default`, `compact`, `none`. | | `content` | object | - | Content properties. | | `breadcrumb` | object | - | Breadcrumb properties. | | `breadcrumb.separator` | string | `"/"` | Use a custom separator string. | | `breadcrumb.list` | array | - | List of breadcrumb links. | | `breadcrumb.list.$.label` | string | - | Label of the breadcrumb link. | | `breadcrumb.list.$.pageId` | string | - | Page id to link to when clicked. | | `breadcrumb.list.$.url` | string | - | External url link. | | `breadcrumb.list.$.style` | object | - | Css style to apply to link. | | `breadcrumb.list.$.icon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to use an icon in breadcrumb link. | | `breadcrumb.list.$.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `breadcrumb.list.$.icon.color` | string | - | Icon color. | | `breadcrumb.list.$.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `breadcrumb.list.$.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `breadcrumb.list.$.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `breadcrumb.list.$.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `breadcrumb.list.$.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `breadcrumb.list.$.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `breadcrumb.list.$.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `breadcrumb.list.$.links` | array | - | Links shown in a dropdown menu on the breadcrumb item. | | `breadcrumb.list.$.links.$.label` | string | - | Label of the dropdown link. | | `breadcrumb.list.$.links.$.pageId` | string | - | Page id to link to when clicked. | | `breadcrumb.list.$.links.$.url` | string | - | External url link. | | `menu` | object | - | Menu properties. | | `menu.links` | array | - | | | `menu.links.$.id` | string | - | Menu item id. | | `menu.links.$.pageId` | string | - | Page to link to. | | `menu.links.$.properties` | object | - | properties from menu item. | | `menu.links.$.properties.title` | string | - | Menu item title. | | `menu.links.$.properties.icon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to customize icon on menu item. | | `menu.links.$.properties.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `menu.links.$.properties.icon.color` | string | - | Icon color. | | `menu.links.$.properties.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `menu.links.$.properties.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `menu.links.$.properties.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `menu.links.$.properties.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `menu.links.$.properties.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `menu.links.$.properties.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `menu.links.$.properties.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `menuLg` | object | - | Menu large screen properties. Overwrites menu properties on desktop screen sizes. | | `menuMd` | object | - | Mobile menu properties. Overwrites menu properties on mobile screen sizes. | | `notifications` | object | - | Notification bell icon with badge in the header. Renders when configured. Use the link property to navigate when clicked. | | `notifications.link` | object | - | Link to navigate to when the notification bell is clicked. | | `notifications.link.pageId` | string | - | Page to link to. | | `notifications.link.url` | string | - | External URL to link to. | | `notifications.link.newTab` | boolean | - | Open link in new tab. | | `notifications.count` | number | - | Number to display on the badge. Set to 0 to hide the badge (unless showZero is true). | | `notifications.dot` | boolean | `false` | Show a dot instead of a count number. | | `notifications.showZero` | boolean | `false` | Show badge when count is zero. | | `notifications.overflowCount` | number | `99` | Max count to show. Values above this display as "N+". | | `notifications.color` | string | - | Badge color. | | `notifications.icon` | string \| object | - | Icon for the notification button. Defaults to `bell`. | | `notifications.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `notifications.icon.color` | string | - | Icon color. | | `notifications.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `notifications.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `notifications.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `notifications.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `notifications.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `notifications.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `notifications.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `notifications.size` | string | `"small"` | Size of the notification button. Enum: `small`, `default`, `large`. | | `profile` | object | - | Profile avatar with optional dropdown menu in the header. Renders when configured. Use with the _user operator to populate from the authenticated user. | | `profile.avatar` | object | - | Avatar display properties. | | `profile.avatar.src` | string | - | Image URL for the avatar. Typically bound to _user: image. | | `profile.avatar.content` | string | - | Text content inside the avatar (e.g. user initials). Shown when no src is provided. | | `profile.avatar.icon` | string \| object | - | Icon to display in avatar when no src or content is set. Defaults to `user`. | | `profile.avatar.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `profile.avatar.icon.color` | string | - | Icon color. | | `profile.avatar.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `profile.avatar.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `profile.avatar.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `profile.avatar.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `profile.avatar.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `profile.avatar.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `profile.avatar.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `profile.avatar.color` | string | - | Background color of the avatar when not using src. | | `profile.avatar.size` | string \| number | `"small"` | Size of the avatar: default, small, large or a pixel number. | | `profile.avatar.shape` | string | `"circle"` | Shape of the avatar. Enum: `circle`, `square`. | | `profile.links` | array | - | Dropdown menu items. Uses the same MenuLink/MenuGroup/MenuDivider schema as Menu. Compatible with _menu operator output for access-filtered menus. | | `profile.links.$.id` | string | - | Menu item id. | | `profile.links.$.type` | string | `"MenuLink"` | Menu item type. Enum: `MenuDivider`, `MenuLink`, `MenuGroup`. | | `profile.links.$.pageId` | string | - | Page to link to. | | `profile.links.$.url` | string | - | External URL to link to. | | `profile.links.$.newTab` | boolean | - | Open link in new tab. | | `profile.links.$.style` | object | - | CSS style applied to the link. | | `profile.links.$.properties` | object | - | Properties for the menu item. | | `profile.links.$.properties.title` | string | - | Menu item title. | | `profile.links.$.properties.icon` | string \| object | - | Icon for the menu item. | | `profile.links.$.properties.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `profile.links.$.properties.icon.color` | string | - | Icon color. | | `profile.links.$.properties.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `profile.links.$.properties.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `profile.links.$.properties.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `profile.links.$.properties.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `profile.links.$.properties.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `profile.links.$.properties.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `profile.links.$.properties.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `profile.links.$.properties.danger` | boolean | `false` | Apply danger style to menu item. | | `profile.links.$.properties.disabled` | boolean | `false` | Disable the menu item. | | `profile.links.$.properties.dashed` | boolean | `false` | Whether the divider line is dashed. | | `profile.links.$.properties.shortcut` | string | - | Keyboard shortcut. Renders a kbd badge floated to the far right and wires the key handler. Use "mod" for Cmd/Ctrl. | | `profile.links.$.links` | array | - | Nested menu items for MenuGroup. | | `profile.trigger` | string | `"hover"` | How the profile dropdown opens. Enum: `click`, `hover`. | | `profile.placement` | string | `"bottomRight"` | Dropdown placement relative to the avatar. Enum: `bottomLeft`, `bottom`, `bottomRight`, `topLeft`, `top`, `topRight`. | | `profile.arrow` | boolean \| object | `false` | Show arrow on the dropdown. | | `profile.arrow.pointAtCenter` | boolean | - | | | `darkModeToggle` | boolean | `false` | Show a dark mode toggle button in the header. Toggles the Ant Design dark theme for the entire page. Preference is persisted to localStorage. | | `localeSelector` | boolean | `false` | Show a locale picker dropdown in the header. Lists locales declared in `config.i18n.locales` and dispatches `SetLocale` on selection. Renders nothing when `config.i18n` is not configured. | | `theme` | object | - | Antd global design token overrides, such as colorPrimary or borderRadius, applied to everything in the page layout. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design component tokens](https://ant.design/docs/react/customize-theme#seedtoken). | | Event | Event Data | Description | | --- | --- | --- | | `onBreadcrumbClick` | \- | Trigger action when a breadcrumb item is clicked. | | `onClose` | \- | Trigger action when mobile menu is closed. | | `onMenuItemClick` | \- | Trigger action when menu item is clicked. | | `onMenuItemSelect` | \- | Trigger action when menu item is selected. | | `onOpen` | \- | Trigger action when mobile menu is open. | | `onProfileMenuClick` | `{ key, keyPath, pageId, url }` | Trigger action when a profile dropdown menu item is clicked. | | `onProfileMenuOpen` | `{ open }` | Trigger action when the profile dropdown opens or closes. | | `onToggleDrawer` | \- | Trigger action when mobile menu drawer is toggled. | | `onToggleMenuGroup` | \- | Trigger action when mobile menu group is opened. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The PageHeaderMenu element. | | `/header` | The PageHeaderMenu header. | | `/headerActions` | The header actions container (notifications, profile, dark mode toggle). | | `/headerContent` | The PageHeaderMenu header content area. | | `/logo` | The PageHeaderMenu logo. | | `/notifications` | The notification bell button. | | `/notificationsBadge` | The notification badge wrapper. | | `/notificationsIcon` | The notification bell icon. | | `/profile` | The profile avatar and dropdown wrapper. | | `/profileAvatar` | The profile avatar element. | | `/profileMenu` | The profile dropdown menu popup. | | `/darkModeToggle` | The PageHeaderMenu dark mode toggle button. | | `/localeSelector` | The PageHeaderMenu locale selector trigger. | | `/localeSelectorMenu` | The PageHeaderMenu locale selector dropdown popup. | | `/mobileMenu` | The PageHeaderMenu mobile menu. | | `/menu` | The PageHeaderMenu menu. | | `/content` | The PageHeaderMenu content. | | `/breadcrumb` | The PageHeaderMenu breadcrumb. | | `/footer` | The PageHeaderMenu footer. | | Slot | Description | | --- | --- | | `content` | Main page content. | | `footer` | Page footer. | | `header` | Additional header content. | --- url: https://docs.lowdefy.com/md/container-blocks/pagesidebarlayout.md --- # PageSidebarLayout Full-page layout with a full-height sidebar and no top-level header. The sider spans the entire viewport height with the logo affixed at the bottom. A mobile drawer menu appears on small screens. Sider collapse state is persisted in localStorage. PageSidebarLayout provides a full-page layout with a full-height sidebar. The sider spans the entire viewport height with the logo at the bottom. On mobile screens, a hamburger menu with a full-width drawer replaces the sider. ```yaml - id: psl_basic type: PageSidebarLayout properties: siderStorageKey: psl_basic sider: initialCollapsed: false width: 220 menu: links: - id: psl_basic_dash type: MenuLink properties: title: Dashboard icon: Gauge - id: psl_basic_users type: MenuLink properties: title: Users icon: user - id: psl_basic_settings type: MenuLink properties: title: Settings icon: settings blocks: - id: psl_basic_content type: Paragraph properties: content: PageSidebarLayout provides a full-page layout with a full-height sidebar. The sider spans the entire viewport height with the logo at the bottom. On mobile screens, a hamburger menu with a full-width drawer replaces the sider. ``` padding: none removes the content padding, so tables and grids can run edge to edge. Blocks set their own spacing. ```yaml - id: psl_flush type: PageSidebarLayout properties: siderStorageKey: psl_flush padding: none menu: links: - id: psl_flush_dashboard type: MenuLink properties: title: Dashboard - id: psl_flush_invoices type: MenuLink properties: title: Invoices blocks: - id: psl_flush_text type: Paragraph class: px-4 properties: content: "padding: none removes the content padding, so tables and grids can run edge to edge. Blocks set their own spacing." - id: psl_flush_card type: Card properties: title: Recent invoices padding: none variant: borderless blocks: - id: psl_flush_card_rows type: Html properties: html: >
INV-1042$1,280.00
INV-1041$640.00
INV-1040$2,115.50
INV-1039$96.00
``` Sider starts collapsed. Click the toggle button to expand. The collapsed state is persisted in localStorage. ```yaml - id: psl_collapsed type: PageSidebarLayout properties: siderStorageKey: psl_collapsed sider: initialCollapsed: true menu: links: - id: psl_collapsed_dash type: MenuLink properties: title: Dashboard icon: Gauge - id: psl_collapsed_users type: MenuLink properties: title: Users icon: user blocks: - id: psl_collapsed_content type: Paragraph properties: content: Sider starts collapsed. Click the toggle button to expand. The collapsed state is persisted in localStorage. ``` Optional desktop header with custom content. The header sits inside the content area, not above the sider. ```yaml - id: psl_header type: PageSidebarLayout properties: siderStorageKey: psl_header sider: width: 220 header: contentStyle: justifyContent: flex-end menu: links: - id: psl_header_dash type: MenuLink properties: title: Dashboard icon: Gauge - id: psl_header_settings type: MenuLink properties: title: Settings icon: settings slots: header: blocks: - id: psl_header_search_btn type: Button layout: flex: 0 0 auto properties: title: Search icon: search color: default variant: outlined size: small blocks: - id: psl_header_content type: Paragraph properties: content: Optional desktop header with custom content. The header sits inside the content area, not above the sider. ``` The siderHeader slot sits above the menu and stays in place while a long menu scrolls. siderHeaderClosed takes its place while the sider is collapsed, and mobileDrawerHeader puts content above the menu in the mobile drawer. Acme Inc ```yaml - id: psl_sider_header type: PageSidebarLayout properties: siderStorageKey: psl_sider_header sider: width: 240 menu: links: - id: psl_sider_header_general type: MenuLink properties: title: General icon: settings - id: psl_sider_header_members type: MenuLink properties: title: Members icon: users - id: psl_sider_header_billing type: MenuLink properties: title: Billing icon: CreditCard slots: siderHeader: blocks: - id: psl_sider_header_open type: Box style: padding: 8px 16px blocks: - id: psl_sider_header_back type: Button properties: title: Back to app icon: arrow-left type: link size: small - id: psl_sider_header_title type: Title properties: content: Acme Inc level: 5 siderHeaderClosed: blocks: - id: psl_sider_header_closed type: Box style: padding: 8px textAlign: center blocks: - id: psl_sider_header_back_icon type: Button properties: hideTitle: true title: Back to app icon: arrow-left type: link mobileDrawerHeader: blocks: - id: psl_sider_header_drawer type: Box style: padding: 0 24px blocks: - id: psl_sider_header_drawer_back type: Button properties: title: Back to app icon: arrow-left type: link size: small blocks: - id: psl_sider_header_content type: Paragraph properties: content: The siderHeader slot sits above the menu and stays in place while a long menu scrolls. siderHeaderClosed takes its place while the sider is collapsed, and mobileDrawerHeader puts content above the menu in the mobile drawer. ``` Custom logo with style overrides. The full logo (src) shows when the sider is expanded, and the square logo (srcMobile) shows when collapsed. ```yaml - id: psl_logo type: PageSidebarLayout properties: siderStorageKey: psl_logo logo: src: /logo_example.png srcMobile: /logo_example.png alt: Custom Logo style: width: 80 menu: links: - id: psl_logo_dash type: MenuLink properties: title: Dashboard icon: Gauge blocks: - id: psl_logo_content type: Paragraph properties: content: Custom logo with style overrides. The full logo (src) shows when the sider is expanded, and the square logo (srcMobile) shows when collapsed. ``` Dashboard Overview ```yaml - id: psl_admin type: PageSidebarLayout properties: siderStorageKey: psl_admin darkModeToggle: true notifications: count: 3 profile: avatar: content: AU color: "#1677ff" links: - id: psl_admin_prof_profile type: MenuLink properties: title: My Profile icon: user - id: psl_admin_prof_settings type: MenuLink properties: title: Settings icon: settings - id: psl_admin_prof_divider type: MenuDivider - id: psl_admin_prof_logout type: MenuLink properties: title: Sign Out icon: logout danger: true sider: width: 240 breadcrumb: list: - Admin - Dashboard menu: defaultOpenKeys: - psl_admin_users_group links: - id: psl_admin_dash type: MenuLink properties: title: Dashboard icon: Gauge - id: psl_admin_users_group type: MenuGroup properties: title: User Management icon: users links: - id: psl_admin_all_users type: MenuLink properties: title: All Users - id: psl_admin_user_roles type: MenuLink properties: title: Roles & Permissions - id: psl_admin_analytics type: MenuLink properties: title: Analytics icon: chart - id: psl_admin_settings type: MenuLink properties: title: Settings icon: settings slots: siderOpen: blocks: - id: psl_admin_sider_open type: Box style: padding: 8px 12px blocks: - id: psl_admin_env_tag type: Tag properties: title: Production color: green icon: Server siderClosed: blocks: - id: psl_admin_sider_closed type: Box style: padding: 8px textAlign: center blocks: - id: psl_admin_env_tag_sm type: Tag properties: title: Prod color: green footer: blocks: - id: psl_admin_footer_text type: Paragraph properties: content: Admin Panel v2.4.1 style: .element: textAlign: center margin: 0 color: "#999" fontSize: 12 blocks: - id: psl_admin_title type: Title properties: content: Dashboard Overview level: 3 - id: psl_admin_stats_row type: Box layout: gap: 16 blocks: - id: psl_admin_stat_users type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: psl_admin_stat_users_val type: Statistic properties: title: Total Users value: 2847 prefixIcon: user - id: psl_admin_stat_revenue type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: psl_admin_stat_revenue_val type: Statistic properties: title: Revenue value: 58420 prefix: $ - id: psl_admin_stat_orders type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: psl_admin_stat_orders_val type: Statistic properties: title: Orders value: 384 ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `title` | string | - | Page title. Accepted for compatibility. | | `theme` | object | - | Antd global design token overrides, such as colorPrimary or borderRadius, applied to everything in the page layout. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design component tokens](https://ant.design/docs/react/customize-theme#seedtoken). | | `logo` | object | - | Header logo settings. By default, images are served from the app public folder and auto-swap between light and dark variants based on dark mode. See [Hosting Files](/hosting-files) for details. | | `logo.src` | string | - | Logo image URL for desktop. Defaults to logo-light-theme.png or logo-dark-theme.png from the public folder (~250x72px), auto-selected based on dark mode. | | `logo.srcMobile` | string | - | Logo image URL for mobile. Defaults to logo-square-light-theme.png or logo-square-dark-theme.png from the public folder (~125x125px), auto-selected based on dark mode. | | `logo.alt` | string | `"Lowdefy"` | Logo alternative text. | | `logo.style` | object | - | Css style object to apply to logo. | | `sider` | object | - | Sider properties. | | `sider.collapsedWidth` | integer | - | Width of the collapsed sidebar, by setting to 0 a special trigger will appear. | | `sider.collapsible` | boolean | `false` | Show a trigger at the bottom of the sider that collapses and expands it, like the toggle button. | | `sider.initialCollapsed` | boolean | `false` | Set the initial collapsed state. | | `sider.width` | string \| number | - | Width of the sidebar. | | `sider.hideToggleButton` | boolean | `false` | Hide toggle button in sider. | | `siderStorageKey` | string | `"sider"` | localStorage key suffix for sider state persistence. Produces key 'lf-{siderStorageKey}-open', kept per app. | | `header` | object | - | Header properties. | | `header.contentStyle` | object | - | Header content css style object. | | `toggleSiderButton` | object | - | Toggle sider button properties. | | `footer` | object | - | Footer properties. | | `footer.style` | object | - | Footer css style object. | | `padding` | string | `"default"` | Space around the page content. `compact` tightens it to 16px for dense, data-heavy pages. `none` removes it so content such as a table or grid runs edge to edge; the breadcrumb keeps its own inset. Enum: `default`, `compact`, `none`. | | `content` | object | - | Content properties. | | `content.style` | object | - | Content css style object. | | `breadcrumb` | object | - | Breadcrumb properties. | | `breadcrumb.separator` | string | `"/"` | Use a custom separator string. | | `breadcrumb.list` | array | - | List of breadcrumb links. | | `breadcrumb.list.$.label` | string | - | Label of the breadcrumb link. | | `breadcrumb.list.$.pageId` | string | - | Page id to link to when clicked. | | `breadcrumb.list.$.url` | string | - | External url link. | | `breadcrumb.list.$.style` | object | - | Css style to apply to link. | | `breadcrumb.list.$.icon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to use an icon in breadcrumb link. | | `breadcrumb.list.$.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `breadcrumb.list.$.icon.color` | string | - | Icon color. | | `breadcrumb.list.$.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `breadcrumb.list.$.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `breadcrumb.list.$.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `breadcrumb.list.$.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `breadcrumb.list.$.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `breadcrumb.list.$.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `breadcrumb.list.$.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `breadcrumb.list.$.links` | array | - | Links shown in a dropdown menu on the breadcrumb item. | | `breadcrumb.list.$.links.$.label` | string | - | Label of the dropdown link. | | `breadcrumb.list.$.links.$.pageId` | string | - | Page id to link to when clicked. | | `breadcrumb.list.$.links.$.url` | string | - | External url link. | | `menu` | object | - | Menu properties. | | `menu.links` | array | - | | | `menu.links.$.id` | string | - | Menu item id. | | `menu.links.$.pageId` | string | - | Page to link to. | | `menu.links.$.properties` | object | - | properties from menu item. | | `menu.links.$.properties.title` | string | - | Menu item title. | | `menu.links.$.properties.icon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to customize icon on menu item. | | `menu.links.$.properties.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `menu.links.$.properties.icon.color` | string | - | Icon color. | | `menu.links.$.properties.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `menu.links.$.properties.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `menu.links.$.properties.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `menu.links.$.properties.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `menu.links.$.properties.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `menu.links.$.properties.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `menu.links.$.properties.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `menuLg` | object | - | Menu large screen properties. Overwrites menu properties on desktop screen sizes. | | `menuMd` | object | - | Mobile menu properties. Overwrites menu properties on mobile screen sizes. | | `notifications` | object | - | Notification bell icon with badge. Shown in the sider on desktop and the mobile header on small screens. Renders when configured. Use the link property to navigate when clicked. | | `notifications.title` | string | `"Notifications"` | Label shown next to the bell icon when the sider is expanded. Hidden on mobile header and collapsed sider. | | `notifications.link` | object | - | Link to navigate to when the notification bell is clicked. | | `notifications.link.pageId` | string | - | Page to link to. | | `notifications.link.url` | string | - | External URL to link to. | | `notifications.link.newTab` | boolean | - | Open link in new tab. | | `notifications.count` | number | - | Number to display on the badge. Set to 0 to hide the badge (unless showZero is true). | | `notifications.dot` | boolean | `false` | Show a dot instead of a count number. | | `notifications.showZero` | boolean | `false` | Show badge when count is zero. | | `notifications.overflowCount` | number | `99` | Max count to show. Values above this display as "N+". | | `notifications.color` | string | - | Badge color. | | `notifications.icon` | string \| object | - | Icon for the notification button. Defaults to `bell`. | | `notifications.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `notifications.icon.color` | string | - | Icon color. | | `notifications.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `notifications.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `notifications.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `notifications.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `notifications.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `notifications.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `notifications.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `notifications.size` | string | `"small"` | Size of the notification button. Enum: `small`, `default`, `large`. | | `profile` | object | - | Profile avatar with optional dropdown menu. Shown in the sider on desktop and the mobile header on small screens. Renders when configured. Use with the _user operator to populate from the authenticated user. | | `profile.title` | string | `"Profile"` | Label shown next to the avatar when the sider is expanded. Hidden on mobile header and collapsed sider. | | `profile.avatar` | object | - | Avatar display properties. | | `profile.avatar.src` | string | - | Image URL for the avatar. Typically bound to _user: image. | | `profile.avatar.content` | string | - | Text content inside the avatar (e.g. user initials). Shown when no src is provided. | | `profile.avatar.icon` | string \| object | - | Icon to display in avatar when no src or content is set. Defaults to `user`. | | `profile.avatar.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `profile.avatar.icon.color` | string | - | Icon color. | | `profile.avatar.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `profile.avatar.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `profile.avatar.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `profile.avatar.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `profile.avatar.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `profile.avatar.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `profile.avatar.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `profile.avatar.color` | string | - | Background color of the avatar when not using src. | | `profile.avatar.size` | string \| number | `"small"` | Size of the avatar: default, small, large or a pixel number. | | `profile.avatar.shape` | string | `"circle"` | Shape of the avatar. Enum: `circle`, `square`. | | `profile.links` | array | - | Dropdown menu items. Uses the same MenuLink/MenuGroup/MenuDivider schema as Menu. Compatible with _menu operator output for access-filtered menus. | | `profile.links.$.id` | string | - | Menu item id. | | `profile.links.$.type` | string | `"MenuLink"` | Menu item type. Enum: `MenuDivider`, `MenuLink`, `MenuGroup`. | | `profile.links.$.pageId` | string | - | Page to link to. | | `profile.links.$.url` | string | - | External URL to link to. | | `profile.links.$.newTab` | boolean | - | Open link in new tab. | | `profile.links.$.style` | object | - | CSS style applied to the link. | | `profile.links.$.properties` | object | - | Properties for the menu item. | | `profile.links.$.properties.title` | string | - | Menu item title. | | `profile.links.$.properties.icon` | string \| object | - | Icon for the menu item. | | `profile.links.$.properties.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `profile.links.$.properties.icon.color` | string | - | Icon color. | | `profile.links.$.properties.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `profile.links.$.properties.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `profile.links.$.properties.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `profile.links.$.properties.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `profile.links.$.properties.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `profile.links.$.properties.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `profile.links.$.properties.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `profile.links.$.properties.danger` | boolean | `false` | Apply danger style to menu item. | | `profile.links.$.properties.disabled` | boolean | `false` | Disable the menu item. | | `profile.links.$.properties.dashed` | boolean | `false` | Whether the divider line is dashed. | | `profile.links.$.properties.shortcut` | string | - | Keyboard shortcut. Renders a kbd badge floated to the far right and wires the key handler. Use "mod" for Cmd/Ctrl. | | `profile.links.$.links` | array | - | Nested menu items for MenuGroup. | | `profile.trigger` | string | `"hover"` | How the profile dropdown opens. Enum: `click`, `hover`. | | `profile.placement` | string | `"bottomRight"` | Dropdown placement relative to the avatar. Enum: `bottomLeft`, `bottom`, `bottomRight`, `topLeft`, `top`, `topRight`. | | `profile.arrow` | boolean \| object | `false` | Show arrow on the dropdown. | | `profile.arrow.pointAtCenter` | boolean | - | | | `darkModeToggle` | boolean | `false` | Show a dark mode toggle button in the sider and mobile header. Toggles the Ant Design dark theme for the entire page. Preference is persisted to localStorage. | | `localeSelector` | boolean | `false` | Show a locale picker dropdown in the sider and mobile header. Lists locales declared in `config.i18n.locales` and dispatches `SetLocale` on selection. Renders nothing when `config.i18n` is not configured. | | `iconsColor` | string | - | Color for notification and dark mode toggle icons. | | Event | Event Data | Description | | --- | --- | --- | | `onToggleSider` | \- | Trigger action when sider toggle button is clicked. | | `onMenuItemClick` | \- | Trigger action when menu item is clicked. | | `onMenuItemSelect` | \- | Trigger action when menu item is selected. | | `onToggleMenuGroup` | \- | Trigger action when menu group is opened. | | `onBreadcrumbClick` | \- | Trigger action when a breadcrumb item is clicked. | | `onMobileMenuOpen` | \- | Trigger action when mobile menu is opened. | | `onMobileMenuClose` | \- | Trigger action when mobile menu is closed. | | `onToggleDrawer` | \- | Trigger action when mobile menu drawer is toggled. | | `onProfileMenuClick` | `{ key, keyPath, pageId, url }` | Trigger action when a profile dropdown menu item is clicked. | | `onProfileMenuOpen` | `{ open }` | Trigger action when the profile dropdown opens or closes. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The PageSidebarLayout element. | | `/sider` | The PageSidebarLayout sider. | | `/siderBody` | The box inside the sider that holds the toggle button, menu and sider slots. | | `/siderHeader` | The box above the sider menu that holds the siderHeader or siderHeaderClosed slot. | | `/menu` | The PageSidebarLayout menu. | | `/mobileHeader` | The PageSidebarLayout mobile header. | | `/mobileMenu` | The PageSidebarLayout mobile menu. | | `/header` | The PageSidebarLayout header. | | `/headerActions` | The header actions container (notifications, profile, dark mode toggle). | | `/headerContent` | The PageSidebarLayout header content area (the header slot row). | | `/logo` | The PageSidebarLayout logo. | | `/notifications` | The notification bell button. | | `/notificationsBadge` | The notification badge wrapper. | | `/notificationsIcon` | The notification bell icon. | | `/profile` | The profile avatar and dropdown wrapper. | | `/profileAvatar` | The profile avatar element. | | `/profileMenu` | The profile dropdown menu popup. | | `/darkModeToggle` | The dark mode toggle button. | | `/localeSelector` | The locale selector trigger. | | `/localeSelectorMenu` | The locale selector dropdown popup. | | `/content` | The PageSidebarLayout content. | | `/breadcrumb` | The PageSidebarLayout breadcrumb. | | `/footer` | The PageSidebarLayout footer. | | `/toggleButton` | The PageSidebarLayout sider toggle button. | | Slot | Description | | --- | --- | | `content` | Main page content. | | `footer` | Page footer. | | `header` | Additional header content. | | `mobileDrawerContent` | Content in the mobile menu drawer. | | `mobileDrawerFooter` | Footer in the mobile menu drawer. | | `mobileDrawerHeader` | Content above the menu in the mobile menu drawer. | | `mobileExtra` | Extra content in the mobile header bar. | | `siderClosed` | Content shown in the sider when collapsed. | | `siderHeader` | Content above the sider menu when expanded. It stays in place while the menu scrolls. | | `siderHeaderClosed` | Content above the sider menu when collapsed. | | `siderOpen` | Content shown in the sider when expanded. | --- url: https://docs.lowdefy.com/md/container-blocks/pagesidermenu.md --- # PageSiderMenu Page layout with a sidebar navigation menu. Dashboard ```yaml - id: psm_basic type: PageSiderMenu properties: sider: width: 220 hideToggleButton: true menu: links: - id: psm_basic_dash type: MenuLink properties: title: Dashboard icon: Gauge - id: psm_basic_orders type: MenuLink properties: title: Orders icon: ShoppingCart - id: psm_basic_products type: MenuLink properties: title: Products icon: grid - id: psm_basic_settings type: MenuLink properties: title: Settings icon: settings blocks: - id: psm_basic_title type: Title properties: content: Dashboard level: 3 - id: psm_basic_stats type: Box layout: gap: 16 blocks: - id: psm_basic_stat_1 type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: psm_basic_stat_1_val type: Statistic properties: title: Orders Today value: 128 - id: psm_basic_stat_2 type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: psm_basic_stat_2_val type: Statistic properties: title: Revenue value: 4820 prefix: $ ``` padding: compact trims the content padding from 40px to 16px, so data-heavy pages show more. ```yaml - id: psm_compact type: PageSiderMenu properties: padding: compact menu: links: - id: psm_compact_dashboard type: MenuLink properties: title: Dashboard - id: psm_compact_invoices type: MenuLink properties: title: Invoices blocks: - id: psm_compact_text type: Paragraph properties: content: "padding: compact trims the content padding from 40px to 16px, so data-heavy pages show more." - id: psm_compact_card type: Card properties: title: Recent invoices padding: none blocks: - id: psm_compact_card_rows type: Html properties: html: >
INV-1042$1,280.00
INV-1041$640.00
INV-1040$2,115.50
INV-1039$96.00
``` Website Redesign Breadcrumb items support both simple strings and objects with label, icon, pageId, and url properties. Toggle dark mode to see the layout adapt automatically. > 3 tasks due this week ```yaml - id: psm_light type: PageSiderMenu properties: darkModeToggle: true sider: width: 230 hideToggleButton: true breadcrumb: list: - label: Home icon: home - label: Projects - label: Website Redesign menu: links: - id: psm_light_overview type: MenuLink properties: title: Overview icon: SquareKanban - id: psm_light_tasks type: MenuLink properties: title: Tasks icon: SquareCheck - id: psm_light_files type: MenuLink properties: title: Files icon: file - id: psm_light_team type: MenuLink properties: title: Team icon: users blocks: - id: psm_light_title type: Title properties: content: Website Redesign level: 3 - id: psm_light_desc type: Paragraph properties: content: Breadcrumb items support both simple strings and objects with label, icon, pageId, and url properties. Toggle dark mode to see the layout adapt automatically. - id: psm_light_alert type: Alert properties: message: 3 tasks due this week type: warning showIcon: true ``` All Users Jane Cooper Tom Wilson ```yaml - id: psm_groups type: PageSiderMenu properties: sider: width: 240 hideToggleButton: false menu: links: - id: psm_grp_dash type: MenuLink properties: title: Dashboard icon: Gauge - id: psm_grp_users_group type: MenuGroup properties: title: User Management icon: users links: - id: psm_grp_all_users type: MenuLink properties: title: All Users - id: psm_grp_roles type: MenuLink properties: title: Roles & Permissions - id: psm_grp_content_group type: MenuGroup properties: title: Content icon: edit links: - id: psm_grp_articles type: MenuLink properties: title: Articles - id: psm_grp_categories type: MenuLink properties: title: Categories - id: psm_grp_settings type: MenuLink properties: title: Settings icon: settings blocks: - id: psm_grp_title type: Title properties: content: All Users level: 3 - id: psm_grp_card type: Card properties: title: User List blocks: - id: psm_grp_user_1 type: Box layout: gap: 8 align: center style: padding: 8px 0 borderBottom: 1px solid var(--ant-color-border) blocks: - id: psm_grp_avatar_1 type: Avatar layout: flex: 0 0 auto properties: size: small color: "#1677ff" icon: user - id: psm_grp_name_1 type: Paragraph layout: flex: 1 1 0 properties: content: Jane Cooper style: .element: margin: 0 - id: psm_grp_tag_1 type: Tag layout: flex: 0 0 auto properties: title: Admin color: blue - id: psm_grp_user_2 type: Box layout: gap: 8 align: center style: padding: 8px 0 blocks: - id: psm_grp_avatar_2 type: Avatar layout: flex: 0 0 auto properties: size: small color: "#52c41a" icon: user - id: psm_grp_name_2 type: Paragraph layout: flex: 1 1 0 properties: content: Tom Wilson style: .element: margin: 0 - id: psm_grp_tag_2 type: Tag layout: flex: 0 0 auto properties: title: Editor color: green ``` Admin Dashboard ```yaml - id: psm_profile type: PageSiderMenu properties: darkModeToggle: true sider: width: 220 hideToggleButton: true notifications: count: 5 profile: avatar: content: JC color: "#1677ff" links: - id: psm_prof_my_profile type: MenuLink properties: title: My Profile icon: user - id: psm_prof_settings type: MenuLink properties: title: Account Settings icon: settings - id: psm_prof_divider type: MenuDivider - id: psm_prof_logout type: MenuLink properties: title: Sign Out icon: logout danger: true menu: links: - id: psm_prof_dash type: MenuLink properties: title: Dashboard icon: Gauge - id: psm_prof_users type: MenuLink properties: title: Users icon: users - id: psm_prof_reports type: MenuLink properties: title: Reports icon: chart blocks: - id: psm_prof_title type: Title properties: content: Admin Dashboard level: 3 - id: psm_prof_stats type: Box layout: gap: 16 blocks: - id: psm_prof_stat_1 type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: psm_prof_stat_1_val type: Statistic properties: title: Active Users value: 1284 - id: psm_prof_stat_2 type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: psm_prof_stat_2_val type: Statistic properties: title: Pending Requests value: 23 ``` Dashboard Overview Sprint Progress ```yaml - id: psm_full type: PageSiderMenu properties: darkModeToggle: true sider: width: 240 hideToggleButton: true notifications: dot: true profile: avatar: icon: user color: "#1677ff" breadcrumb: separator: / list: - Admin - Dashboard menu: links: - id: psm_full_dash type: MenuLink properties: title: Dashboard icon: Gauge - id: psm_full_analytics type: MenuLink properties: title: Analytics icon: chart - id: psm_full_settings type: MenuLink properties: title: Settings icon: settings slots: sider: blocks: - id: psm_full_sider_box type: Box style: padding: 12px blocks: - id: psm_full_progress_label type: Paragraph properties: content: Sprint Progress style: .element: margin: 0 0 8px 0 fontSize: 12 color: "#999" - id: psm_full_progress type: Progress properties: percent: 68 size: small footer: blocks: - id: psm_full_footer_text type: Paragraph properties: content: Admin Panel v2.4.1 style: .element: textAlign: center margin: 0 color: "#999" fontSize: 12 blocks: - id: psm_full_title type: Title properties: content: Dashboard Overview level: 3 - id: psm_full_stats_row type: Box layout: gap: 16 blocks: - id: psm_full_stat_users type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: psm_full_stat_users_val type: Statistic properties: title: Total Users value: 2847 prefixIcon: user - id: psm_full_stat_revenue type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: psm_full_stat_revenue_val type: Statistic properties: title: Revenue value: 58420 prefix: $ - id: psm_full_stat_orders type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: psm_full_stat_orders_val type: Statistic properties: title: Orders value: 384 ``` Profile from _menu > The profile dropdown links are populated using _menu operator, pulling from the menus defined in lowdefy.yaml. Click the avatar to see the dropdown. ```yaml - id: psm_menu_op type: PageSiderMenu properties: darkModeToggle: true sider: width: 220 hideToggleButton: true notifications: count: 4 link: pageId: home profile: avatar: content: JC color: "#6366f1" links: _menu: default menu: links: - id: psm_mo_dash type: MenuLink properties: title: Dashboard icon: Gauge - id: psm_mo_users type: MenuLink properties: title: Users icon: users blocks: - id: psm_mo_title type: Title properties: content: Profile from _menu level: 3 - id: psm_mo_info type: Alert properties: message: The profile dropdown links are populated using _menu operator, pulling from the menus defined in lowdefy.yaml. Click the avatar to see the dropdown. type: info showIcon: true ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `logo` | object | - | Header logo settings. By default, images are served from the app public folder and auto-swap between light and dark variants based on dark mode. See [Hosting Files](/hosting-files) for details. | | `logo.src` | string | - | Logo image URL for desktop. Defaults to logo-light-theme.png or logo-dark-theme.png from the public folder (~250x72px), auto-selected based on dark mode. | | `logo.srcMobile` | string | - | Logo image URL for mobile. Defaults to logo-square-light-theme.png or logo-square-dark-theme.png from the public folder (~125x125px), auto-selected based on dark mode. | | `logo.breakpoint` | number | - | Viewport width breakpoint (in px) for switching between mobile and desktop logo. Default is 577. | | `logo.alt` | string | `"Lowdefy"` | Logo image alt text. | | `header` | object | - | Header properties. | | `sider` | object | - | Sider properties. | | `sider.breakpoint` | string | `"sm"` | Breakpoint of the responsive layout. Enum: `xs`, `sm`, `md`, `lg`, `xl`. | | `sider.collapsedWidth` | integer | - | Width of the collapsed sidebar, by setting to 0 a special trigger will appear. | | `sider.collapsible` | boolean | `false` | Show a trigger at the bottom of the sider that collapses and expands it, like the toggle button. | | `sider.initialCollapsed` | boolean | `false` | Set the initial collapsed state. Used as the fallback when no persisted preference exists in localStorage. | | `sider.reverseArrow` | boolean | `false` | Direction of arrow, for a sider that expands from the right. | | `sider.width` | string \| number | - | Width of the sidebar. | | `sider.hideToggleButton` | boolean | `false` | Hide toggle button in sider. | | `siderStorageKey` | string | `"sider"` | localStorage key suffix for sider state persistence. Produces key 'lf-{siderStorageKey}-open', kept per app. | | `toggleSiderButton` | object | - | Toggle sider button properties. | | `footer` | object | - | Footer properties. | | `padding` | string | `"default"` | Space around the page content. `compact` tightens it to 16px for dense, data-heavy pages. `none` removes it so content such as a table or grid runs edge to edge; the breadcrumb keeps its own inset. Enum: `default`, `compact`, `none`. | | `content` | object | - | Content properties. | | `breadcrumb` | object | - | Breadcrumb properties. | | `breadcrumb.separator` | string | `"/"` | Use a custom separator string. | | `breadcrumb.list` | array | - | List of breadcrumb links. | | `breadcrumb.list.$.label` | string | - | Label of the breadcrumb link. | | `breadcrumb.list.$.pageId` | string | - | Page id to link to when clicked. | | `breadcrumb.list.$.url` | string | - | External url link. | | `breadcrumb.list.$.style` | object | - | Css style to apply to link. | | `breadcrumb.list.$.icon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to use an icon in breadcrumb link. | | `breadcrumb.list.$.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `breadcrumb.list.$.icon.color` | string | - | Icon color. | | `breadcrumb.list.$.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `breadcrumb.list.$.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `breadcrumb.list.$.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `breadcrumb.list.$.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `breadcrumb.list.$.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `breadcrumb.list.$.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `breadcrumb.list.$.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `breadcrumb.list.$.links` | array | - | Links shown in a dropdown menu on the breadcrumb item. | | `breadcrumb.list.$.links.$.label` | string | - | Label of the dropdown link. | | `breadcrumb.list.$.links.$.pageId` | string | - | Page id to link to when clicked. | | `breadcrumb.list.$.links.$.url` | string | - | External url link. | | `menu` | object | - | Menu properties. | | `menu.links` | array | - | | | `menu.links.$.id` | string | - | Menu item id. | | `menu.links.$.pageId` | string | - | Page to link to. | | `menu.links.$.properties` | object | - | properties from menu item. | | `menu.links.$.properties.title` | string | - | Menu item title. | | `menu.links.$.properties.icon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to customize icon on menu item. | | `menu.links.$.properties.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `menu.links.$.properties.icon.color` | string | - | Icon color. | | `menu.links.$.properties.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `menu.links.$.properties.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `menu.links.$.properties.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `menu.links.$.properties.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `menu.links.$.properties.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `menu.links.$.properties.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `menu.links.$.properties.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `menuLg` | object | - | Menu large screen properties. Overwrites menu properties on desktop screen sizes. | | `menuMd` | object | - | Mobile menu properties. Overwrites menu properties on mobile screen sizes. | | `notifications` | object | - | Notification bell icon with badge in the header. Renders when configured. Use the link property to navigate when clicked. | | `notifications.link` | object | - | Link to navigate to when the notification bell is clicked. | | `notifications.link.pageId` | string | - | Page to link to. | | `notifications.link.url` | string | - | External URL to link to. | | `notifications.link.newTab` | boolean | - | Open link in new tab. | | `notifications.count` | number | - | Number to display on the badge. Set to 0 to hide the badge (unless showZero is true). | | `notifications.dot` | boolean | `false` | Show a dot instead of a count number. | | `notifications.showZero` | boolean | `false` | Show badge when count is zero. | | `notifications.overflowCount` | number | `99` | Max count to show. Values above this display as "N+". | | `notifications.color` | string | - | Badge color. | | `notifications.icon` | string \| object | - | Icon for the notification button. Defaults to `bell`. | | `notifications.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `notifications.icon.color` | string | - | Icon color. | | `notifications.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `notifications.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `notifications.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `notifications.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `notifications.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `notifications.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `notifications.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `notifications.size` | string | `"small"` | Size of the notification button. Enum: `small`, `default`, `large`. | | `profile` | object | - | Profile avatar with optional dropdown menu in the header. Renders when configured. Use with the _user operator to populate from the authenticated user. | | `profile.avatar` | object | - | Avatar display properties. | | `profile.avatar.src` | string | - | Image URL for the avatar. Typically bound to _user: image. | | `profile.avatar.content` | string | - | Text content inside the avatar (e.g. user initials). Shown when no src is provided. | | `profile.avatar.icon` | string \| object | - | Icon to display in avatar when no src or content is set. Defaults to `user`. | | `profile.avatar.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `profile.avatar.icon.color` | string | - | Icon color. | | `profile.avatar.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `profile.avatar.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `profile.avatar.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `profile.avatar.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `profile.avatar.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `profile.avatar.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `profile.avatar.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `profile.avatar.color` | string | - | Background color of the avatar when not using src. | | `profile.avatar.size` | string \| number | `"small"` | Size of the avatar: default, small, large or a pixel number. | | `profile.avatar.shape` | string | `"circle"` | Shape of the avatar. Enum: `circle`, `square`. | | `profile.links` | array | - | Dropdown menu items. Uses the same MenuLink/MenuGroup/MenuDivider schema as Menu. Compatible with _menu operator output for access-filtered menus. | | `profile.links.$.id` | string | - | Menu item id. | | `profile.links.$.type` | string | `"MenuLink"` | Menu item type. Enum: `MenuDivider`, `MenuLink`, `MenuGroup`. | | `profile.links.$.pageId` | string | - | Page to link to. | | `profile.links.$.url` | string | - | External URL to link to. | | `profile.links.$.newTab` | boolean | - | Open link in new tab. | | `profile.links.$.style` | object | - | CSS style applied to the link. | | `profile.links.$.properties` | object | - | Properties for the menu item. | | `profile.links.$.properties.title` | string | - | Menu item title. | | `profile.links.$.properties.icon` | string \| object | - | Icon for the menu item. | | `profile.links.$.properties.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `profile.links.$.properties.icon.color` | string | - | Icon color. | | `profile.links.$.properties.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `profile.links.$.properties.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `profile.links.$.properties.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `profile.links.$.properties.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `profile.links.$.properties.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `profile.links.$.properties.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `profile.links.$.properties.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `profile.links.$.properties.danger` | boolean | `false` | Apply danger style to menu item. | | `profile.links.$.properties.disabled` | boolean | `false` | Disable the menu item. | | `profile.links.$.properties.dashed` | boolean | `false` | Whether the divider line is dashed. | | `profile.links.$.properties.shortcut` | string | - | Keyboard shortcut. Renders a kbd badge floated to the far right and wires the key handler. Use "mod" for Cmd/Ctrl. | | `profile.links.$.links` | array | - | Nested menu items for MenuGroup. | | `profile.trigger` | string | `"hover"` | How the profile dropdown opens. Enum: `click`, `hover`. | | `profile.placement` | string | `"bottomRight"` | Dropdown placement relative to the avatar. Enum: `bottomLeft`, `bottom`, `bottomRight`, `topLeft`, `top`, `topRight`. | | `profile.arrow` | boolean \| object | `false` | Show arrow on the dropdown. | | `profile.arrow.pointAtCenter` | boolean | - | | | `darkModeToggle` | boolean | `false` | Show a dark mode toggle button in the header. Toggles the Ant Design dark theme for the entire page. Preference is persisted to localStorage. | | `localeSelector` | boolean | `false` | Show a locale picker dropdown in the header. Lists locales declared in `config.i18n.locales` and dispatches `SetLocale` on selection. Renders nothing when `config.i18n` is not configured. | | `theme` | object | - | Antd global design token overrides, such as colorPrimary or borderRadius, applied to everything in the page layout. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design component tokens](https://ant.design/docs/react/customize-theme#seedtoken). | | Event | Event Data | Description | | --- | --- | --- | | `onBreadcrumbClick` | \- | Trigger action when a breadcrumb item is clicked. | | `onChangeToggleSiderAffix` | \- | Trigger action when sider collapse button affix triggers a onChange event. | | `onClose` | \- | Trigger action when menu is closed. | | `onMenuItemSelect` | \- | Trigger action when menu item is selected. | | `onMenuItemClick` | \- | Trigger action when menu item is clicked. | | `onOpen` | \- | Trigger action when menu is open. | | `onProfileMenuClick` | `{ key, keyPath, pageId, url }` | Trigger action when a profile dropdown menu item is clicked. | | `onProfileMenuOpen` | `{ open }` | Trigger action when the profile dropdown opens or closes. | | `onToggleDrawer` | \- | Trigger action when mobile menu drawer is toggled. | | `onToggleMenuGroup` | \- | Trigger action when mobile menu group is opened. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The PageSiderMenu element. | | `/header` | The PageSiderMenu header. | | `/headerActions` | The header actions container (notifications, profile, dark mode toggle). | | `/headerContent` | The PageSiderMenu header content area. | | `/logo` | The PageSiderMenu logo. | | `/notifications` | The notification bell button. | | `/notificationsBadge` | The notification badge wrapper. | | `/notificationsIcon` | The notification bell icon. | | `/profile` | The profile avatar and dropdown wrapper. | | `/profileAvatar` | The profile avatar element. | | `/profileMenu` | The profile dropdown menu popup. | | `/darkModeToggle` | The PageSiderMenu dark mode toggle button. | | `/localeSelector` | The PageSiderMenu locale selector trigger. | | `/localeSelectorMenu` | The PageSiderMenu locale selector dropdown popup. | | `/mobileMenu` | The PageSiderMenu mobile menu. | | `/layout` | The PageSiderMenu inner layout. | | `/sider` | The PageSiderMenu sider. | | `/siderBody` | The box inside the sider that holds the menu, sider slot and toggle button. | | `/menu` | The PageSiderMenu menu. | | `/content` | The PageSiderMenu content. | | `/breadcrumb` | The PageSiderMenu breadcrumb. | | `/footer` | The PageSiderMenu footer. | | Slot | Description | | --- | --- | | `content` | Main page content. | | `footer` | Page footer. | | `header` | Additional header content. | | `sider` | Sider content below the menu. | --- url: https://docs.lowdefy.com/md/container-blocks/popover.md --- # Popover Popup card appearing when hovering or clicking a trigger element. This is the popover body content. You can place any blocks here. A popover without a title - just body content. ```yaml - id: popover_basic type: Popover layout: flex: 0 0 auto properties: title: Popover Title slots: popover: blocks: - id: popover_basic_content type: Paragraph properties: content: This is the popover body content. You can place any blocks here. blocks: - id: popover_basic_btn type: Button layout: flex: 0 0 auto properties: title: Hover me color: primary variant: solid - id: popover_no_title type: Popover layout: flex: 0 0 auto slots: popover: blocks: - id: popover_no_title_content type: Paragraph properties: content: A popover without a title - just body content. blocks: - id: popover_no_title_btn type: Button layout: flex: 0 0 auto properties: title: No title color: default variant: outlined ``` Popover placed at top. Popover placed at topLeft. Popover placed at topRight. Popover placed at left. Popover placed at right. Popover placed at bottom (default). ```yaml - id: popover_place_row_top type: Box layout: gap: 8 justify: center blocks: - id: popover_pl_top type: Popover layout: flex: 0 0 auto properties: title: Top placement: top slots: popover: blocks: - id: popover_pl_top_msg type: Paragraph properties: content: Popover placed at top. blocks: - id: popover_pl_top_btn type: Button layout: flex: 0 0 auto properties: title: top color: primary variant: solid size: small - id: popover_pl_topLeft type: Popover layout: flex: 0 0 auto properties: title: Top Left placement: topLeft slots: popover: blocks: - id: popover_pl_topLeft_msg type: Paragraph properties: content: Popover placed at topLeft. blocks: - id: popover_pl_topLeft_btn type: Button layout: flex: 0 0 auto properties: title: topLeft color: default variant: outlined size: small - id: popover_pl_topRight type: Popover layout: flex: 0 0 auto properties: title: Top Right placement: topRight slots: popover: blocks: - id: popover_pl_topRight_msg type: Paragraph properties: content: Popover placed at topRight. blocks: - id: popover_pl_topRight_btn type: Button layout: flex: 0 0 auto properties: title: topRight color: default variant: outlined size: small - id: popover_place_row_sides type: Box layout: gap: 8 blocks: - id: popover_pl_left type: Popover layout: flex: 0 0 auto properties: title: Left placement: left slots: popover: blocks: - id: popover_pl_left_msg type: Paragraph properties: content: Popover placed at left. blocks: - id: popover_pl_left_btn type: Button layout: flex: 0 0 auto properties: title: left color: primary variant: solid size: small - id: popover_pl_right type: Popover layout: flex: 0 0 auto properties: title: Right placement: right slots: popover: blocks: - id: popover_pl_right_msg type: Paragraph properties: content: Popover placed at right. blocks: - id: popover_pl_right_btn type: Button layout: flex: 0 0 auto properties: title: right color: primary variant: outlined size: small - id: popover_pl_bottom type: Popover layout: flex: 0 0 auto properties: title: Bottom placement: bottom slots: popover: blocks: - id: popover_pl_bottom_msg type: Paragraph properties: content: Popover placed at bottom (default). blocks: - id: popover_pl_bottom_btn type: Button layout: flex: 0 0 auto properties: title: bottom (default) color: default variant: outlined size: small ``` This popover appears on hover (default). This popover appears on click. Click outside to close. This popover appears on focus. Tab to the button or click it. ```yaml - id: popover_trig_hover type: Popover layout: flex: 0 0 auto properties: title: Hover Popover trigger: hover slots: popover: blocks: - id: popover_trig_hover_msg type: Paragraph properties: content: This popover appears on hover (default). blocks: - id: popover_trig_hover_btn type: Button layout: flex: 0 0 auto properties: title: Hover (default) color: primary variant: solid - id: popover_trig_click type: Popover layout: flex: 0 0 auto properties: title: Click Popover trigger: click slots: popover: blocks: - id: popover_trig_click_msg type: Paragraph properties: content: This popover appears on click. Click outside to close. blocks: - id: popover_trig_click_btn type: Button layout: flex: 0 0 auto properties: title: Click me color: primary variant: outlined - id: popover_trig_focus type: Popover layout: flex: 0 0 auto properties: title: Focus Popover trigger: focus slots: popover: blocks: - id: popover_trig_focus_msg type: Paragraph properties: content: This popover appears on focus. Tab to the button or click it. blocks: - id: popover_trig_focus_btn type: Button layout: flex: 0 0 auto properties: title: Focus (tab to me) color: primary variant: dashed ``` Default background color. A warm light yellow background. A cool light blue background. ```yaml - id: popover_col_row type: Box layout: gap: 8 blocks: - id: popover_col_default type: Popover layout: flex: 0 0 auto properties: title: Default Color slots: popover: blocks: - id: popover_col_default_msg type: Paragraph properties: content: Default background color. blocks: - id: popover_col_default_btn type: Button layout: flex: 0 0 auto properties: title: Default color: default variant: outlined size: small - id: popover_col_yellow type: Popover layout: flex: 0 0 auto properties: title: Light Yellow color: "#fffbe6" slots: popover: blocks: - id: popover_col_yellow_msg type: Paragraph properties: content: A warm light yellow background. blocks: - id: popover_col_yellow_btn type: Button layout: flex: 0 0 auto properties: title: Light Yellow color: gold variant: outlined size: small - id: popover_col_blue type: Popover layout: flex: 0 0 auto properties: title: Light Blue color: "#e6f4ff" slots: popover: blocks: - id: popover_col_blue_msg type: Paragraph properties: content: A cool light blue background. blocks: - id: popover_col_blue_btn type: Button layout: flex: 0 0 auto properties: title: Light Blue color: blue variant: outlined size: small ``` This popover is open by default when the page loads. ```yaml - id: popover_defopen type: Popover layout: flex: 0 0 auto properties: title: Open by Default defaultOpen: true placement: right slots: popover: blocks: - id: popover_defopen_msg type: Paragraph properties: content: This popover is open by default when the page loads. blocks: - id: popover_defopen_btn type: Button layout: flex: 0 0 auto properties: title: Open by default color: green variant: solid ``` Repositions automatically near viewport edges (default behavior). Stays in the specified position even near edges. ```yaml - id: popover_adj_on type: Popover layout: flex: 0 0 auto properties: title: Auto Adjust autoAdjustOverflow: true placement: top slots: popover: blocks: - id: popover_adj_on_msg type: Paragraph properties: content: Repositions automatically near viewport edges (default behavior). blocks: - id: popover_adj_on_btn type: Button layout: flex: 0 0 auto properties: title: Auto adjust (default) color: primary variant: solid - id: popover_adj_off type: Popover layout: flex: 0 0 auto properties: title: Fixed Position autoAdjustOverflow: false placement: top slots: popover: blocks: - id: popover_adj_off_msg type: Paragraph properties: content: Stays in the specified position even near edges. blocks: - id: popover_adj_off_btn type: Button layout: flex: 0 0 auto properties: title: No auto adjust color: default variant: outlined ``` No delay on enter or leave. 1 second delay before showing. Stays visible for 2 seconds after mouse leaves. ```yaml - id: popover_del_fast type: Popover layout: flex: 0 0 auto properties: title: Instant mouseEnterDelay: 0 mouseLeaveDelay: 0 slots: popover: blocks: - id: popover_del_fast_msg type: Paragraph properties: content: No delay on enter or leave. blocks: - id: popover_del_fast_btn type: Button layout: flex: 0 0 auto properties: title: No delay color: primary variant: solid - id: popover_del_slow type: Popover layout: flex: 0 0 auto properties: title: Slow Enter mouseEnterDelay: 1 slots: popover: blocks: - id: popover_del_slow_msg type: Paragraph properties: content: 1 second delay before showing. blocks: - id: popover_del_slow_btn type: Button layout: flex: 0 0 auto properties: title: 1s enter delay color: primary variant: outlined - id: popover_del_sticky type: Popover layout: flex: 0 0 auto properties: title: Sticky mouseLeaveDelay: 2 slots: popover: blocks: - id: popover_del_sticky_msg type: Paragraph properties: content: Stays visible for 2 seconds after mouse leaves. blocks: - id: popover_del_sticky_btn type: Button layout: flex: 0 0 auto properties: title: 2s leave delay color: primary variant: dashed ``` This popover has a custom z-index of 2000, useful when layering over modals or drawers. ```yaml - id: popover_zindex type: Popover layout: flex: 0 0 auto properties: title: Custom Z-Index zIndex: 2000 slots: popover: blocks: - id: popover_zindex_msg type: Paragraph properties: content: This popover has a custom z-index of 2000, useful when layering over modals or drawers. blocks: - id: popover_zindex_btn type: Button layout: flex: 0 0 auto properties: title: Z-index 2000 color: primary variant: solid ``` Custom inner style with larger border radius and a stronger shadow. This popover has a fixed width of 400px and extra padding, useful for displaying larger content like forms or details. ```yaml - id: popover_overlay_style type: Popover layout: flex: 0 0 auto properties: title: Styled Overlay overlayInnerStyle: borderRadius: 16 boxShadow: 0 6px 16px rgba(0, 0, 0, 0.2) slots: popover: blocks: - id: popover_overlay_style_msg type: Paragraph properties: content: Custom inner style with larger border radius and a stronger shadow. blocks: - id: popover_overlay_style_btn type: Button layout: flex: 0 0 auto properties: title: Custom overlay style color: primary variant: solid - id: popover_overlay_wide type: Popover layout: flex: 0 0 auto properties: title: Wide Popover overlayInnerStyle: width: 400 padding: 20 slots: popover: blocks: - id: popover_overlay_wide_msg type: Paragraph properties: content: This popover has a fixed width of 400px and extra padding, useful for displaying larger content like forms or details. blocks: - id: popover_overlay_wide_btn type: Button layout: flex: 0 0 auto properties: title: Wide overlay color: primary variant: outlined ``` Opening or closing this popover triggers a DisplayMessage action. This popover uses SetState to track that it was opened. ```yaml - id: popover_event_open type: Popover layout: flex: 0 0 auto properties: title: Event Popover trigger: click events: onOpenChange: - id: popover_event_open_msg type: DisplayMessage params: content: Popover visibility changed! duration: 2 slots: popover: blocks: - id: popover_event_open_content type: Paragraph properties: content: Opening or closing this popover triggers a DisplayMessage action. blocks: - id: popover_event_open_btn type: Button layout: flex: 0 0 auto properties: title: Click to toggle color: primary variant: solid icon: bell - id: popover_event_state type: Popover layout: flex: 0 0 auto properties: title: Track State trigger: click events: onOpenChange: - id: popover_event_state_set type: SetState params: popover_was_opened: true slots: popover: blocks: - id: popover_event_state_content type: Paragraph properties: content: This popover uses SetState to track that it was opened. blocks: - id: popover_event_state_btn type: Button layout: flex: 0 0 auto properties: title: Track open state color: primary variant: outlined ``` JD Jane Doe jane.doe@example.com ```yaml - id: popover_rich_profile type: Popover layout: flex: 0 0 auto properties: title: User Profile trigger: click slots: popover: blocks: - id: popover_rich_profile_row type: Box layout: gap: 8 blocks: - id: popover_rich_profile_avatar type: Avatar layout: flex: 0 0 auto properties: content: JD color: blue - id: popover_rich_profile_info type: Box blocks: - id: popover_rich_profile_name type: Title properties: content: Jane Doe level: 5 - id: popover_rich_profile_email type: Paragraph properties: content: jane.doe@example.com blocks: - id: popover_rich_profile_btn type: Button layout: flex: 0 0 auto properties: title: View Profile color: primary variant: solid icon: user - id: popover_rich_actions type: Popover layout: flex: 0 0 auto properties: title: Actions trigger: click slots: popover: blocks: - id: popover_rich_actions_col type: Box layout: gap: 4 direction: column blocks: - id: popover_rich_act_edit type: Button properties: title: Edit icon: edit color: primary variant: text block: true - id: popover_rich_act_copy type: Button properties: title: Duplicate icon: copy color: default variant: text block: true - id: popover_rich_act_del type: Button properties: title: Delete icon: delete color: danger variant: text block: true blocks: - id: popover_rich_actions_btn type: Button layout: flex: 0 0 auto properties: title: More icon: more color: default variant: outlined ``` This popover has a wider minimum title width of 300px using the titleMinWidth token. Custom border radius of 20px for a softer appearance. Custom elevated background color using the colorBgElevated theme token. Increased inner padding (24px), custom title padding, and larger title margin bottom. A visible border under the title using titleBorderBottom, plus innerContentPadding for spacing. The zIndexPopup theme token sets the popover z-index at the theme level (default 1030). ```yaml - id: popover_theme_wide type: Popover layout: flex: 0 0 auto properties: title: Wide Title theme: titleMinWidth: 300 slots: popover: blocks: - id: popover_theme_wide_msg type: Paragraph properties: content: This popover has a wider minimum title width of 300px using the titleMinWidth token. blocks: - id: popover_theme_wide_btn type: Button layout: flex: 0 0 auto properties: title: Wide title (300px) color: primary variant: solid - id: popover_theme_radius type: Popover layout: flex: 0 0 auto properties: title: Large Radius theme: borderRadiusLG: 20 slots: popover: blocks: - id: popover_theme_radius_msg type: Paragraph properties: content: Custom border radius of 20px for a softer appearance. blocks: - id: popover_theme_radius_btn type: Button layout: flex: 0 0 auto properties: title: Large radius color: primary variant: outlined - id: popover_theme_bg type: Popover layout: flex: 0 0 auto properties: title: Custom Background slots: popover: blocks: - id: popover_theme_bg_msg type: Paragraph properties: content: Custom elevated background color using the colorBgElevated theme token. blocks: - id: popover_theme_bg_btn type: Button layout: flex: 0 0 auto properties: title: Custom background color: primary variant: dashed - id: popover_theme_padding type: Popover layout: flex: 0 0 auto properties: title: Extra Padding theme: innerPadding: 24 titlePadding: 16px 24px titleMarginBottom: 16 slots: popover: blocks: - id: popover_theme_padding_msg type: Paragraph properties: content: Increased inner padding (24px), custom title padding, and larger title margin bottom. blocks: - id: popover_theme_padding_btn type: Button layout: flex: 0 0 auto properties: title: Extra padding color: default variant: outlined - id: popover_theme_border type: Popover layout: flex: 0 0 auto properties: title: Title Border theme: titleBorderBottom: "1px solid #d9d9d9" titleMarginBottom: 12 innerContentPadding: 8px 0 slots: popover: blocks: - id: popover_theme_border_msg type: Paragraph properties: content: A visible border under the title using titleBorderBottom, plus innerContentPadding for spacing. blocks: - id: popover_theme_border_btn type: Button layout: flex: 0 0 auto properties: title: Title border color: default variant: outlined - id: popover_theme_zindex type: Popover layout: flex: 0 0 auto properties: title: Custom Z-Index Token theme: zIndexPopup: 2050 slots: popover: blocks: - id: popover_theme_zindex_msg type: Paragraph properties: content: The zIndexPopup theme token sets the popover z-index at the theme level (default 1030). blocks: - id: popover_theme_zindex_btn type: Button layout: flex: 0 0 auto properties: title: Z-index token color: default variant: dashed ``` The element CSS key targets the outer Popover wrapper. Here it adds a pointer cursor on the trigger area. The inner CSS key targets the popover inner container. Here it applies extra shadow and rounded corners. The title CSS key styles the popover title area with blue text, bold weight, and a bottom border. The content CSS key targets the popover content area. Here it adds a gray background, padding, and rounded corners. ```yaml - id: popover_css_element type: Popover layout: flex: 0 0 auto class: .element: cursor-pointer properties: title: Element CSS Key trigger: click slots: popover: blocks: - id: popover_css_element_msg type: Paragraph properties: content: The element CSS key targets the outer Popover wrapper. Here it adds a pointer cursor on the trigger area. blocks: - id: popover_css_element_btn type: Button layout: flex: 0 0 auto properties: title: Element key color: primary variant: solid - id: popover_css_inner type: Popover layout: flex: 0 0 auto class: .inner: shadow-xl rounded-xl properties: title: Inner CSS Key slots: popover: blocks: - id: popover_css_inner_msg type: Paragraph properties: content: The inner CSS key targets the popover inner container. Here it applies extra shadow and rounded corners. blocks: - id: popover_css_inner_btn type: Button layout: flex: 0 0 auto properties: title: Inner key color: primary variant: outlined - id: popover_css_title type: Popover layout: flex: 0 0 auto class: .title: font-bold text-blue-600 border-b border-border pb-2 properties: title: Title CSS Key slots: popover: blocks: - id: popover_css_title_msg type: Paragraph properties: content: The title CSS key styles the popover title area with blue text, bold weight, and a bottom border. blocks: - id: popover_css_title_btn type: Button layout: flex: 0 0 auto properties: title: Title key color: blue variant: outlined - id: popover_css_content type: Popover layout: flex: 0 0 auto class: .content: bg-bg-layout p-4 rounded-md properties: title: Content CSS Key slots: popover: blocks: - id: popover_css_content_msg type: Paragraph properties: content: The content CSS key targets the popover content area. Here it adds a gray background, padding, and rounded corners. blocks: - id: popover_css_content_btn type: Button layout: flex: 0 0 auto properties: title: Content key color: default variant: outlined ``` SC Sarah Chen Sarah Chen Product Manager ```yaml - id: popover_usercard type: Popover layout: flex: 0 0 auto properties: trigger: click overlayInnerStyle: width: 320 padding: 0 slots: popover: blocks: - id: popover_usercard_header type: Box class: bg-bg-layout p-4 rounded-t-lg layout: gap: 12 align: center blocks: - id: popover_usercard_avatar type: Avatar layout: flex: 0 0 auto properties: icon: user size: 48 color: "#1677ff" - id: popover_usercard_info type: Box layout: direction: column gap: 2 blocks: - id: popover_usercard_name type: Title properties: content: Sarah Chen level: 5 - id: popover_usercard_role type: Paragraph properties: content: Product Manager - id: popover_usercard_details type: Descriptions class: p-4 properties: size: small column: 1 items: - label: Email value: sarah.chen@company.com - label: Team value: Product - label: Location value: San Francisco, CA - id: popover_usercard_actions_row type: Box class: px-4 pb-4 layout: gap: 8 blocks: - id: popover_usercard_msg_btn type: Button layout: flex: 1 1 0 properties: title: Message icon: mail color: primary variant: solid size: small events: onClick: - id: popover_usercard_msg_action type: DisplayMessage params: content: Opening chat with Sarah... duration: 2 - id: popover_usercard_profile_btn type: Button layout: flex: 1 1 0 properties: title: View Profile icon: user color: default variant: outlined size: small events: onClick: - id: popover_usercard_profile_action type: Link params: url: https://example.com/profile newTab: true blocks: - id: popover_usercard_trigger type: Box layout: gap: 8 align: center blocks: - id: popover_usercard_trigger_avatar type: Avatar layout: flex: 0 0 auto properties: content: SC color: "#1677ff" - id: popover_usercard_trigger_name type: Paragraph layout: flex: 0 0 auto properties: content: Sarah Chen ``` ```yaml - id: popover_quickmenu type: Popover layout: flex: 0 0 auto properties: trigger: click placement: bottomRight overlayInnerStyle: width: 200 padding: 8 events: onOpenChange: - id: popover_quickmenu_state type: SetState params: menu_toggled: true slots: popover: blocks: - id: popover_quickmenu_col type: Box layout: direction: column gap: 2 blocks: - id: popover_quickmenu_new type: Button properties: title: New Document icon: FilePlus color: primary variant: text block: true events: onClick: - id: popover_quickmenu_new_action type: DisplayMessage params: content: Creating new document... duration: 2 - id: popover_quickmenu_upload type: Button properties: title: Upload File icon: upload color: default variant: text block: true events: onClick: - id: popover_quickmenu_upload_action type: DisplayMessage params: content: Opening file picker... duration: 2 - id: popover_quickmenu_divider type: Divider properties: dashed: true - id: popover_quickmenu_import type: Button properties: title: Import from URL icon: link color: default variant: text block: true events: onClick: - id: popover_quickmenu_import_action type: DisplayMessage params: content: Enter a URL to import... duration: 2 - id: popover_quickmenu_template type: Button properties: title: Use Template icon: grid color: default variant: text block: true blocks: - id: popover_quickmenu_trigger type: Button layout: flex: 0 0 auto properties: title: Create New icon: add color: primary variant: solid ``` Monthly Budget The monthly budget is the maximum amount your team can spend on resources each billing cycle. It resets on the 1st of every month. You will receive a warning notification when 80% of the budget is used. ```yaml - id: popover_help_row type: Box layout: gap: 16 align: center blocks: - id: popover_help_label type: Title layout: flex: 0 0 auto properties: content: Monthly Budget level: 5 - id: popover_help_icon type: Popover layout: flex: 0 0 auto properties: title: About Monthly Budget placement: right overlayInnerStyle: width: 300 slots: popover: blocks: - id: popover_help_desc type: Paragraph properties: content: The monthly budget is the maximum amount your team can spend on resources each billing cycle. It resets on the 1st of every month. - id: popover_help_note type: Paragraph class: text-text-secondary text-sm properties: content: You will receive a warning notification when 80% of the budget is used. - id: popover_help_link type: Button properties: title: Learn more color: primary variant: link size: small events: onClick: - id: popover_help_link_action type: Link params: url: https://example.com/docs/budget newTab: true blocks: - id: popover_help_trigger type: Icon layout: flex: 0 0 auto properties: name: help color: "#8c8c8c" size: 18 - id: popover_help_input type: NumberInput layout: flex: 1 1 0 properties: label: disabled: true prefix: $ ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `arrow` | boolean \| object | `true` | Whether to show the arrow. Set `{ pointAtCenter: true }` to point the arrow at the center of the target. | | `arrow.pointAtCenter` | boolean | `false` | Whether the arrow is pointed at the center of target. | | `destroyOnHidden` | boolean | `false` | Unmount the blocks inside the popover when it closes, so they mount again each time it opens. | | `title` | string | - | Title of the card - supports html. | | `color` | string | - | Popover background color. | | `defaultOpen` | boolean | `false` | Whether the popover is open by default. | | `autoAdjustOverflow` | boolean | `true` | Whether to adjust popup placement automatically when popup is off screen | | `placement` | string | `"top"` | Placement of the popover. Enum: `top`, `topLeft`, `topRight`, `left`, `leftTop`, `leftBottom`, `right`, `rightTop`, `rightBottom`, `bottom`, `bottomLeft`, `bottomRight`. | | `trigger` | string | `"hover"` | Trigger mode which executes the popover. `contextMenu` opens the popover on right click. Enum: `hover`, `click`, `focus`, `contextMenu`. | | `zIndex` | number | - | Z-index of the popover. | | `overlayInnerStyle` | object | - | Style of overlay inner div. Prefer `style.inner`. | | `mouseEnterDelay` | number | `0.1` | Delay in seconds, before the popover is shown on mouse enter. | | `mouseLeaveDelay` | number | `0.1` | Delay in seconds, before the popover is hidden on mouse leave. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design popover tokens](https://ant.design/components/popover#design-token). | | `theme.width` | number \| string | - | Width of the popover. | | `theme.minWidth` | number \| string | - | Minimum width of the popover. | | `theme.titleMinWidth` | number \| string | `177` | Minimum width of the popover when it has a title. | | `theme.zIndexPopup` | number | `1030` | Z-index of the popover. | | `theme.innerPadding` | number \| string | `12` | Padding inside the popover content area. | | `theme.titlePadding` | number \| string | - | Padding of the title area. | | `theme.titleMarginBottom` | number | `8` | Margin bottom of the title. | | `theme.titleBorderBottom` | string | `"none"` | Border bottom of the title area. | | `theme.innerContentPadding` | number \| string | `0` | Padding of the inner content area. | | `theme.colorBgElevated` | string | - | Background color of the popover. | | `theme.borderRadiusLG` | number | `8` | Border radius of the popover container. | | Event | Event Data | Description | | --- | --- | --- | | `onOpenChange` | `{ open }` | Trigger actions when visibility of the popover card is changed. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Popover element. | | `/inner` | The Popover inner. | | `/title` | The Popover title. | | `/content` | The Popover content. | | Slot | Description | | --- | --- | | `content` | Blocks that trigger the Popover. | | `popover` | Blocks inside the popup. | --- url: https://docs.lowdefy.com/md/container-blocks/result.md --- # Result Result page with status icon, title, and extra actions. ```yaml - id: status_success type: Result properties: status: success title: Successfully Purchased Cloud Server subTitle: "Order number: 2026-0342-8756-0028. Cloud server configuration takes 1-5 minutes, please wait." - id: status_error type: Result properties: status: error title: Submission Failed subTitle: Please check and modify the following information before resubmitting. - id: status_info type: Result properties: status: info title: Your Operation Has Been Executed subTitle: We will review your request and respond within 24 hours. - id: status_warning type: Result properties: status: warning title: There Are Some Problems With Your Operation subTitle: Please review and correct the issues listed below before proceeding. ``` ```yaml - id: http_404 type: Result properties: status: "404" title: "404" subTitle: Sorry, the page you visited does not exist. - id: http_403 type: Result properties: status: "403" title: "403" subTitle: Sorry, you are not authorized to access this page. - id: http_500 type: Result properties: status: "500" title: "500" subTitle: Sorry, something went wrong on our server. ``` ```yaml - id: extra_success type: Result properties: status: success title: Payment Successful subTitle: "Order number: 2026-0815-9932. Your receipt has been emailed to you." slots: extra: blocks: - id: extra_success_row type: Box layout: gap: 8 justify: center blocks: - id: extra_success_home type: Button layout: flex: 0 0 auto properties: title: Back to Home color: primary variant: solid - id: extra_success_orders type: Button layout: flex: 0 0 auto properties: title: View Orders color: default variant: outlined - id: extra_error type: Result properties: status: error title: Submission Failed subTitle: Please check and modify the following information before resubmitting. slots: extra: blocks: - id: extra_error_row type: Box layout: gap: 8 justify: center blocks: - id: extra_error_retry type: Button layout: flex: 0 0 auto properties: title: Try Again color: primary variant: solid icon: refresh - id: extra_error_support type: Button layout: flex: 0 0 auto properties: title: Contact Support color: default variant: outlined icon: Headset - id: extra_404 type: Result properties: status: "404" title: "404" subTitle: Sorry, the page you visited does not exist. slots: extra: blocks: - id: extra_404_btn type: Button layout: flex: 0 0 auto properties: title: Back Home color: primary variant: solid - id: extra_403 type: Result properties: status: "403" title: "403" subTitle: Sorry, you are not authorized to access this page. slots: extra: blocks: - id: extra_403_btn type: Button layout: flex: 0 0 auto properties: title: Back Home color: primary variant: solid - id: extra_500 type: Result properties: status: "500" title: "500" subTitle: Sorry, something went wrong on our server. slots: extra: blocks: - id: extra_500_btn type: Button layout: flex: 0 0 auto properties: title: Back Home color: primary variant: solid ``` ```yaml - id: child_error type: Result properties: status: error title: Submission Failed subTitle: Please check and modify the following information before resubmitting. slots: extra: blocks: - id: child_error_extra_row type: Box layout: gap: 8 justify: center blocks: - id: child_error_retry_btn type: Button layout: flex: 0 0 auto properties: title: Try Again color: primary variant: solid - id: child_error_modify_btn type: Button layout: flex: 0 0 auto properties: title: Modify Details color: default variant: outlined blocks: - id: child_error_details type: Descriptions properties: title: Error Details bordered: true size: small column: 1 items: - label: Field value: Email Address - label: Issue value: Invalid email format — must include @ symbol - label: Field value: Phone Number - label: Issue value: Phone number must be 10 digits - id: child_success type: Result properties: status: success title: Application Submitted subTitle: "Reference: APP-2026-1234. We will review and respond within 3 business days." slots: extra: blocks: - id: child_success_extra_row type: Box layout: gap: 8 justify: center blocks: - id: child_success_dashboard_btn type: Button layout: flex: 0 0 auto properties: title: Go to Dashboard color: primary variant: solid - id: child_success_track_btn type: Button layout: flex: 0 0 auto properties: title: Track Application color: default variant: outlined blocks: - id: child_success_summary type: Descriptions properties: title: Submission Summary bordered: true size: small column: 1 items: - label: Applicant value: Jane Smith - label: Submitted value: March 10, 2026 at 14:32 - label: Type value: Business License Renewal - label: Status value: Under Review ``` ```yaml - id: icon_rocket type: Result properties: status: info title: Deployment in Progress subTitle: Your application is being deployed to production. This usually takes 2-5 minutes. icon: name: Rocket color: "#1677ff" size: 72 - id: icon_lock type: Result properties: status: warning title: Account Locked subTitle: Your account has been temporarily locked due to multiple failed login attempts. Try again in 30 minutes. icon: name: lock color: "#faad14" size: 72 - id: icon_mail type: Result properties: status: success title: Email Verified subTitle: Your email address has been successfully verified. You can now access all features. icon: name: mail color: "#52c41a" size: 72 - id: icon_cloud type: Result properties: status: success title: Upload Complete subTitle: All 24 files have been uploaded successfully to your cloud storage. icon: name: CloudUpload color: "#1677ff" size: 72 ``` ```yaml - id: scenario_payment_success type: Result properties: status: success title: Payment Successful subTitle: "Transaction ID: TXN-2026-0310-5847. Amount: $299.00 charged to Visa ending 4242." slots: extra: blocks: - id: scenario_payment_extra_row type: Box layout: gap: 8 justify: center blocks: - id: scenario_payment_receipt_btn type: Button layout: flex: 0 0 auto properties: title: Download Receipt color: primary variant: solid icon: download - id: scenario_payment_orders_btn type: Button layout: flex: 0 0 auto properties: title: View All Orders color: default variant: outlined blocks: - id: scenario_payment_details type: Descriptions properties: title: Order Summary bordered: true size: small column: 1 items: - label: Product value: Annual Pro Plan - label: Amount value: $299.00 - label: Payment Method value: Visa ending 4242 - label: Billing Email value: jane.smith@example.com - label: Next Renewal value: March 10, 2027 - id: scenario_payment_failed type: Result properties: status: error title: Payment Failed subTitle: Your card was declined. Please try a different payment method or contact your bank. slots: extra: blocks: - id: scenario_payment_failed_row type: Box layout: gap: 8 justify: center blocks: - id: scenario_payment_failed_retry_btn type: Button layout: flex: 0 0 auto properties: title: Try Different Card color: primary variant: solid icon: CreditCard - id: scenario_payment_failed_cancel_btn type: Button layout: flex: 0 0 auto properties: title: Cancel Order color: default variant: outlined ``` ```yaml - id: scenario_permission_denied type: Result properties: status: "403" title: Permission Denied subTitle: You need admin privileges to access the User Management panel. Contact your organization administrator. slots: extra: blocks: - id: scenario_perm_extra_row type: Box layout: gap: 8 justify: center blocks: - id: scenario_perm_request_btn type: Button layout: flex: 0 0 auto properties: title: Request Access color: primary variant: solid icon: Key - id: scenario_perm_back_btn type: Button layout: flex: 0 0 auto properties: title: Go Back color: default variant: outlined - id: scenario_page_not_found type: Result properties: status: "404" title: Page Not Found subTitle: The page /dashboard/reports/q4-2025 does not exist or has been moved. slots: extra: blocks: - id: scenario_notfound_extra_row type: Box layout: gap: 8 justify: center blocks: - id: scenario_notfound_home_btn type: Button layout: flex: 0 0 auto properties: title: Back to Dashboard color: primary variant: solid - id: scenario_notfound_search_btn type: Button layout: flex: 0 0 auto properties: title: Search color: default variant: outlined icon: search - id: scenario_server_error type: Result properties: status: "500" title: Service Unavailable subTitle: "We are experiencing technical difficulties. Error reference: ERR-2026-0310-7721." slots: extra: blocks: - id: scenario_server_extra_row type: Box layout: gap: 8 justify: center blocks: - id: scenario_server_refresh_btn type: Button layout: flex: 0 0 auto properties: title: Refresh Page color: primary variant: solid icon: refresh - id: scenario_server_status_btn type: Button layout: flex: 0 0 auto properties: title: System Status color: default variant: outlined ``` ```yaml - id: scenario_form_submitted type: Result properties: status: success title: Form Submitted Successfully subTitle: Thank you for your feedback. Our team will review it and get back to you within 48 hours. slots: extra: blocks: - id: scenario_form_extra_row type: Box layout: gap: 8 justify: center blocks: - id: scenario_form_new_btn type: Button layout: flex: 0 0 auto properties: title: Submit Another color: primary variant: solid - id: scenario_form_home_btn type: Button layout: flex: 0 0 auto properties: title: Return Home color: default variant: outlined - id: scenario_account_created type: Result properties: status: success title: Account Created subTitle: Welcome aboard! A verification email has been sent to your inbox. Please verify within 24 hours. icon: name: UserPlus color: "#52c41a" size: 72 slots: extra: blocks: - id: scenario_acct_extra_row type: Box layout: gap: 8 justify: center blocks: - id: scenario_acct_login_btn type: Button layout: flex: 0 0 auto properties: title: Go to Login color: primary variant: solid - id: scenario_acct_resend_btn type: Button layout: flex: 0 0 auto properties: title: Resend Verification color: default variant: outlined icon: mail - id: scenario_subscription_expired type: Result properties: status: warning title: Subscription Expired subTitle: Your Pro plan expired on March 1, 2026. Renew now to continue accessing premium features. slots: extra: blocks: - id: scenario_sub_extra_row type: Box layout: gap: 8 justify: center blocks: - id: scenario_sub_renew_btn type: Button layout: flex: 0 0 auto properties: title: Renew Subscription color: primary variant: solid icon: CreditCard - id: scenario_sub_plans_btn type: Button layout: flex: 0 0 auto properties: title: View Plans color: default variant: outlined ``` ```yaml - id: title_only type: Result properties: status: success title: Operation Completed - id: subtitle_only type: Result properties: status: info subTitle: Your changes have been saved. No further action is required. - id: html_title type: Result properties: status: success title: "Order #ORD-2026-4455 Confirmed" subTitle: "Estimated delivery: March 15, 2026. Track your package in the orders section." - id: long_content type: Result properties: status: info title: System Maintenance Scheduled subTitle: Our platform will undergo scheduled maintenance on Saturday, March 15, 2026 from 02:00 to 06:00 UTC. During this window, the application may be intermittently unavailable. All data will be preserved and no action is required on your part. We apologize for any inconvenience. ``` ```yaml - id: compact_success type: Result properties: status: success title: Saved style: .element: padding: 16px 24px - id: compact_error type: Result properties: status: error title: Failed subTitle: Please try again. style: .element: padding: 16px 24px - id: compact_warning type: Result properties: status: warning title: Attention Required style: .element: padding: 16px 24px - id: compact_info type: Result properties: status: info title: Processing subTitle: Please wait a moment. style: .element: padding: 16px 24px ``` ```yaml - id: styled_success_card type: Result class: bg-bg-layout rounded-lg border border-border properties: status: success title: Environment Ready subTitle: Your staging environment has been provisioned and is ready for deployment. - id: styled_error_card type: Result class: bg-bg-layout rounded-lg border border-border properties: status: error title: Build Failed subTitle: The CI pipeline encountered 3 errors during the build step. Check the logs for details. - id: styled_warning_card type: Result class: bg-bg-layout rounded-lg border border-border properties: status: warning title: Rate Limit Approaching subTitle: You have used 85% of your monthly API quota. Consider upgrading your plan. - id: styled_info_card type: Result class: bg-bg-layout rounded-lg border border-border properties: status: info title: New Features Available subTitle: Version 4.2 includes real-time collaboration and improved dashboard analytics. - id: styled_gradient type: Result class: bg-gradient-to-br from-primary/10 to-primary/5 rounded-lg border border-border properties: status: success title: Welcome to Your Workspace subTitle: Your team workspace has been created. Invite members to get started. icon: name: users color: "#722ed1" size: 72 - id: styled_dark type: Result class: bg-bg-layout rounded-lg style: .element: color: white properties: status: success title: Deployment Complete subTitle: All services are running on the latest version. ``` ```yaml - id: inline_bordered type: Result properties: status: info title: Verification Required subTitle: Please verify your identity to continue. Check your email for the verification code. style: .element: border: "2px solid #1677ff" borderRadius: 12px padding: 32px - id: inline_shadow type: Result properties: status: success title: Connected subTitle: Your database connection has been established successfully. style: .element: boxShadow: 0 4px 16px rgba(0, 0, 0, 0.08) borderRadius: 12px padding: 32px - id: inline_max_width type: Result properties: status: warning title: Storage Almost Full subTitle: You have used 9.2 GB of your 10 GB storage limit. Delete files or upgrade your plan. style: .element: maxWidth: 480px margin: 0 auto border: "1px solid #faad14" borderRadius: 8px padding: 24px ``` ```yaml - id: theme_large_title type: Result properties: status: success title: All Systems Operational subTitle: All 12 services are running normally. theme: titleFontSize: 32 subtitleFontSize: 18 - id: theme_small_icon type: Result properties: status: info title: Update Available subTitle: A new version is ready to install. theme: iconFontSize: 48 titleFontSize: 20 - id: theme_custom_colors type: Result properties: status: success title: Invitation Accepted subTitle: You have joined the Design Team workspace. theme: colorSuccess: "#389e0d" colorTextHeading: "#135200" colorTextDescription: "#595959" - id: theme_branded type: Result properties: status: info title: Welcome to Acme Corp subTitle: Your enterprise account is ready. Start by configuring your first project. theme: colorInfo: "#6366f1" titleFontSize: 28 subtitleFontSize: 16 iconFontSize: 80 ``` ```yaml - id: fullpage_404 type: Result style: .element: minHeight: 320px display: flex flexDirection: column justifyContent: center properties: status: "404" title: Page Not Found subTitle: The page you are looking for might have been removed, had its name changed, or is temporarily unavailable. slots: extra: blocks: - id: fullpage_404_extra_row type: Box layout: gap: 8 justify: center blocks: - id: fullpage_404_home_btn type: Button layout: flex: 0 0 auto properties: title: Take Me Home color: primary variant: solid size: large icon: home - id: fullpage_404_report_btn type: Button layout: flex: 0 0 auto properties: title: Report Issue color: default variant: outlined size: large - id: fullpage_500 type: Result style: .element: minHeight: 320px display: flex flexDirection: column justifyContent: center properties: status: "500" title: Something Went Wrong subTitle: Our engineering team has been automatically notified. Please try again in a few minutes. slots: extra: blocks: - id: fullpage_500_extra_row type: Box layout: gap: 8 justify: center blocks: - id: fullpage_500_retry_btn type: Button layout: flex: 0 0 auto properties: title: Retry color: primary variant: solid size: large icon: refresh - id: fullpage_500_status_btn type: Button layout: flex: 0 0 auto properties: title: Check Status Page color: default variant: outlined size: large - id: fullpage_403 type: Result style: .element: minHeight: 320px display: flex flexDirection: column justifyContent: center properties: status: "403" title: Access Restricted subTitle: This resource requires elevated permissions. If you believe this is an error, contact your system administrator. slots: extra: blocks: - id: fullpage_403_extra_row type: Box layout: gap: 8 justify: center blocks: - id: fullpage_403_login_btn type: Button layout: flex: 0 0 auto properties: title: Sign In as Different User color: primary variant: solid size: large icon: user - id: fullpage_403_back_btn type: Button layout: flex: 0 0 auto properties: title: Go Back color: default variant: outlined size: large ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `icon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to customize icon to use as result image. | | `icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `icon.color` | string | - | Icon color. | | `icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `icon.rotate` | number | - | Number of degrees to rotate the icon. | | `icon.spin` | boolean | - | Continuously spin the icon with animation. | | `icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `status` | string | `"info"` | Status of the result. Determines image and color. Enum: `success`, `error`, `info`, `warning`, `404`, `403`, `500`. | | `subTitle` | string | - | Result subtitle or secondary text - supports html. | | `title` | string | - | Result title or primary text - supports html. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design result tokens](https://ant.design/components/result#design-token). | | `theme.titleFontSize` | number | `24` | Font size of the result title. | | `theme.subtitleFontSize` | number | `14` | Font size of the result subtitle. | | `theme.iconFontSize` | number | `72` | Font size of the result status icon. | | `theme.extraMargin` | string | `"24px 0 0 0"` | Margin applied to the extra content area. | | `theme.colorSuccess` | string | - | Color used for the success status icon. | | `theme.colorError` | string | - | Color used for the error status icon. | | `theme.colorInfo` | string | - | Color used for the info status icon. | | `theme.colorWarning` | string | - | Color used for the warning status icon. | | `theme.colorTextHeading` | string | - | Color of the result title text. | | `theme.colorTextDescription` | string | - | Color of the result subtitle text. | | `theme.paddingLG` | number | `24` | Large padding value applied to the result container. | | `theme.lineHeight` | number | `1.5714285714285714` | Line height used for result text content. | No events defined. | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Result element. | | `/icon` | The icon in the Result. | | `/title` | The Result title. | | `/subTitle` | The Result subtitle. | | Slot | Description | | --- | --- | | `content` | Additional content below the Result. | | `extra` | Extra content, typically action buttons. | --- url: https://docs.lowdefy.com/md/container-blocks/sider.md --- # Sider Sidebar layout container for page navigation. The Sider block provides a sidebar within a Layout. It supports collapsing, themes, breakpoints, and custom widths. Place navigation menus or supplementary content inside the sider. ```yaml - id: basic_layout type: Layout style: minHeight: 300px blocks: - id: basic_sider type: Sider properties: theme: light width: 200 blocks: - id: basic_menu type: Menu properties: mode: inline theme: light links: - id: basic_home type: MenuLink properties: title: Home icon: home - id: basic_users type: MenuLink properties: title: Users icon: user - id: basic_settings type: MenuLink properties: title: Settings icon: settings - id: basic_content type: Content style: padding: 24px background: var(--ant-color-bg-container) blocks: - id: basic_content_text type: Paragraph properties: content: The Sider block provides a sidebar within a Layout. It supports collapsing, themes, breakpoints, and custom widths. Place navigation menus or supplementary content inside the sider. ``` Setting theme to "dark" gives the sider a dark background with light text. Match the Menu theme to the sider theme for a consistent appearance. ```yaml - id: dark_layout type: Layout style: minHeight: 300px blocks: - id: dark_sider type: Sider properties: theme: dark width: 200 blocks: - id: dark_menu type: Menu properties: mode: inline theme: dark links: - id: dark_dashboard type: MenuLink properties: title: Dashboard icon: Gauge - id: dark_analytics type: MenuLink properties: title: Analytics icon: chart - id: dark_files type: MenuLink properties: title: Files icon: file - id: dark_content type: Content style: padding: 24px background: var(--ant-color-bg-container) blocks: - id: dark_content_text type: Paragraph properties: content: Setting theme to "dark" gives the sider a dark background with light text. Match the Menu theme to the sider theme for a consistent appearance. ``` With collapsible set to true a collapse trigger appears at the bottom. Toggle between expanded and collapsed states. Menu icons remain visible when collapsed. ```yaml - id: collapsible_layout type: Layout style: minHeight: 300px blocks: - id: collapsible_sider type: Sider properties: theme: light width: 220 collapsible: true initialCollapsed: false blocks: - id: collapsible_menu type: Menu properties: mode: inline theme: light links: - id: col_inbox type: MenuLink properties: title: Inbox icon: Inbox - id: col_starred type: MenuLink properties: title: Starred icon: star - id: col_sent type: MenuLink properties: title: Sent icon: send - id: collapsible_content type: Content style: padding: 24px background: var(--ant-color-bg-container) blocks: - id: collapsible_content_text type: Paragraph properties: content: With collapsible set to true a collapse trigger appears at the bottom. Toggle between expanded and collapsed states. Menu icons remain visible when collapsed. ``` This sider starts collapsed with initialCollapsed set to true. Click the collapse trigger to expand it. ```yaml - id: init_collapsed_layout type: Layout style: minHeight: 300px blocks: - id: init_collapsed_sider type: Sider properties: theme: light width: 200 collapsible: true initialCollapsed: true blocks: - id: init_collapsed_menu type: Menu properties: mode: inline theme: light links: - id: ic_overview type: MenuLink properties: title: Overview icon: grid - id: ic_reports type: MenuLink properties: title: Reports icon: document - id: ic_config type: MenuLink properties: title: Configuration icon: Wrench - id: init_collapsed_content type: Content style: padding: 24px background: var(--ant-color-bg-container) blocks: - id: init_collapsed_content_text type: Paragraph properties: content: This sider starts collapsed with initialCollapsed set to true. Click the collapse trigger to expand it. ``` Setting collapsedWidth to 0 completely hides the sider when collapsed and shows a special floating trigger. Useful for mobile-friendly layouts where sidebar space needs to be fully reclaimed. ```yaml - id: cw_layout type: Layout style: minHeight: 300px blocks: - id: cw_sider type: Sider properties: theme: dark width: 240 collapsedWidth: 0 collapsible: true initialCollapsed: true blocks: - id: cw_menu type: Menu properties: mode: inline theme: dark links: - id: cw_projects type: MenuLink properties: title: Projects icon: SquareKanban - id: cw_tasks type: MenuLink properties: title: Tasks icon: SquareCheck - id: cw_content type: Content style: padding: 24px background: var(--ant-color-bg-container) blocks: - id: cw_content_text type: Paragraph properties: content: Setting collapsedWidth to 0 completely hides the sider when collapsed and shows a special floating trigger. Useful for mobile-friendly layouts where sidebar space needs to be fully reclaimed. ``` The Sider is placed on the right side of the layout with reverseArrow set to true. This flips the collapse arrow direction to match the right-side placement. ```yaml - id: reverse_layout type: Layout style: minHeight: 300px blocks: - id: reverse_content type: Content style: padding: 24px background: var(--ant-color-bg-container) blocks: - id: reverse_content_text type: Paragraph properties: content: The Sider is placed on the right side of the layout with reverseArrow set to true. This flips the collapse arrow direction to match the right-side placement. - id: reverse_sider type: Sider properties: theme: light width: 200 collapsible: true initialCollapsed: false reverseArrow: true style: borderLeft: "1px solid #f0f0f0" blocks: - id: reverse_menu type: Menu properties: mode: inline theme: light links: - id: rev_details type: MenuLink properties: title: Details icon: info - id: rev_history type: MenuLink properties: title: History icon: history ``` The onBreakpoint event fires when the viewport crosses the breakpoint, with `broken` true below it. Here it calls the sider's setOpen method, so the sider collapses below the large (lg) breakpoint. Supported values are xs, sm, md, lg, xl, xxl and xxxl. ```yaml - id: bp_layout type: Layout style: minHeight: 300px blocks: - id: bp_sider type: Sider properties: theme: light width: 200 collapsible: true initialCollapsed: false breakpoint: lg events: onBreakpoint: - id: bp_sider_set_open type: CallMethod params: blockId: bp_sider method: setOpen args: - open: _not: _event: broken blocks: - id: bp_menu type: Menu properties: mode: inline theme: light links: - id: bp_home type: MenuLink properties: title: Home icon: home - id: bp_search type: MenuLink properties: title: Search icon: search - id: bp_content type: Content style: padding: 24px background: var(--ant-color-bg-container) blocks: - id: bp_content_text type: Paragraph properties: content: The onBreakpoint event fires when the viewport crosses the breakpoint, with `broken` true below it. Here it calls the sider's setOpen method, so the sider collapses below the large (lg) breakpoint. Supported values are xs, sm, md, lg, xl, xxl and xxxl. ``` The width property accepts a number (pixels) or string value. Here the sider is set to 300px wide, providing more room for longer menu labels or nested navigation. ```yaml - id: width_layout type: Layout style: minHeight: 300px blocks: - id: width_sider type: Sider properties: theme: light width: 300 collapsible: false initialCollapsed: false blocks: - id: width_menu type: Menu properties: mode: inline theme: light links: - id: width_nav1 type: MenuLink properties: title: Getting Started icon: Rocket - id: width_nav2 type: MenuLink properties: title: Documentation icon: Book - id: width_nav3 type: MenuLink properties: title: API Reference icon: CodeXml - id: width_content type: Content style: padding: 24px background: var(--ant-color-bg-container) blocks: - id: width_content_text type: Paragraph properties: content: The width property accepts a number (pixels) or string value. Here the sider is set to 300px wide, providing more room for longer menu labels or nested navigation. ``` The Sider fires onOpen and onClose events when it expands or collapses. Use these events with SetState or other actions to react to sider state changes. Toggle the sider to see the state update above. ```yaml - id: events_layout type: Layout style: minHeight: 300px blocks: - id: events_sider type: Sider properties: theme: light width: 200 collapsible: true initialCollapsed: false events: onOpen: - id: events_on_open_action type: SetState params: siderState: open onClose: - id: events_on_close_action type: SetState params: siderState: closed blocks: - id: events_menu type: Menu properties: mode: inline theme: light links: - id: events_nav_home type: MenuLink properties: title: Home icon: home - id: events_nav_alerts type: MenuLink properties: title: Alerts icon: bell - id: events_content type: Content style: padding: 24px background: var(--ant-color-bg-container) blocks: - id: events_state_label type: Paragraph properties: content: _nunjucks: template: "Sider state: {{ siderState }}" on: _state: true - id: events_content_text type: Paragraph properties: content: The Sider fires onOpen and onClose events when it expands or collapses. Use these events with SetState or other actions to react to sider state changes. Toggle the sider to see the state update above. ``` The Sider theme takes antd Layout design tokens. Here lightTriggerBg, lightTriggerColor and triggerHeight style the collapse trigger, and the Menu theme styles the selected item. ```yaml - id: token_layout type: Layout style: minHeight: 300px blocks: - id: token_sider type: Sider properties: width: 220 collapsible: true initialCollapsed: false theme: lightTriggerBg: "#f9f0ff" lightTriggerColor: "#722ed1" triggerHeight: 40 blocks: - id: token_menu type: Menu properties: mode: inline theme: itemSelectedBg: "#f9f0ff" itemSelectedColor: "#722ed1" links: - id: token_nav_explore type: MenuLink properties: title: Explore icon: Compass - id: token_nav_favorites type: MenuLink properties: title: Favorites icon: heart - id: token_nav_settings type: MenuLink properties: title: Settings icon: settings - id: token_content type: Content style: padding: 24px background: var(--ant-color-bg-container) blocks: - id: token_content_text type: Paragraph properties: content: The Sider theme takes antd Layout design tokens. Here lightTriggerBg, lightTriggerColor and triggerHeight style the collapse trigger, and the Menu theme styles the selected item. ``` The Sider has a single CSS key called "element" which targets the entire sider container. Use it with Tailwind classes for gradients, borders, shadows, and other visual effects. A shadow-lg class on the element CSS key gives the sider an elevated appearance that visually separates it from the main content area. ```yaml - id: css_layout type: Layout style: minHeight: 300px blocks: - id: css_sider type: Sider properties: theme: light width: 220 collapsible: false initialCollapsed: false class: .element: bg-gradient-to-b from-primary/10 to-bg-container border-r border-border blocks: - id: css_menu type: Menu properties: mode: inline theme: light links: - id: css_nav_workspace type: MenuLink properties: title: Workspace icon: grid - id: css_nav_members type: MenuLink properties: title: Members icon: users - id: css_nav_billing type: MenuLink properties: title: Billing icon: CreditCard - id: css_content type: Content style: padding: 24px background: var(--ant-color-bg-container) blocks: - id: css_content_text type: Paragraph properties: content: The Sider has a single CSS key called "element" which targets the entire sider container. Use it with Tailwind classes for gradients, borders, shadows, and other visual effects. - id: css_shadow_layout type: Layout style: minHeight: 300px blocks: - id: css_shadow_sider type: Sider properties: theme: light width: 200 collapsible: false initialCollapsed: false class: .element: shadow-lg bg-bg-container blocks: - id: css_shadow_menu type: Menu properties: mode: inline theme: light links: - id: css_sh_dashboard type: MenuLink properties: title: Dashboard icon: Gauge - id: css_sh_analytics type: MenuLink properties: title: Analytics icon: ChartLine - id: css_shadow_content type: Content style: padding: 24px background: var(--ant-color-bg-container) blocks: - id: css_shadow_content_text type: Paragraph properties: content: A shadow-lg class on the element CSS key gives the sider an elevated appearance that visually separates it from the main content area. ``` Admin Dashboard ```yaml - id: admin_layout type: Layout style: minHeight: 400px blocks: - id: admin_sider type: Sider properties: theme: dark width: 240 collapsible: true initialCollapsed: false events: onClose: - id: admin_on_close type: SetState params: adminSiderOpen: false onOpen: - id: admin_on_open type: SetState params: adminSiderOpen: true blocks: - id: admin_logo_box type: Box style: padding: 16px textAlign: center blocks: - id: admin_logo_title type: Title class: text-white properties: content: Admin level: 4 - id: admin_menu type: Menu properties: mode: inline theme: dark links: - id: admin_nav_dashboard type: MenuLink properties: title: Dashboard icon: Gauge - id: admin_nav_users type: MenuGroup properties: title: User Management icon: users links: - id: admin_nav_all_users type: MenuLink properties: title: All Users - id: admin_nav_roles type: MenuLink properties: title: Roles - id: admin_nav_content type: MenuGroup properties: title: Content icon: document links: - id: admin_nav_pages type: MenuLink properties: title: Pages - id: admin_nav_media type: MenuLink properties: title: Media - id: admin_nav_settings type: MenuLink properties: title: Settings icon: settings - id: admin_content type: Content style: padding: 24px background: var(--ant-color-bg-layout) blocks: - id: admin_header_row type: Box layout: gap: 16 justify: space-between align: center blocks: - id: admin_page_title type: Title layout: flex: 0 0 auto properties: content: Dashboard level: 3 - id: admin_header_actions type: Box layout: flex: 0 0 auto gap: 8 blocks: - id: admin_notif_btn type: Button layout: flex: 0 0 auto properties: icon: bell color: default variant: text hideTitle: true - id: admin_profile_btn type: Button layout: flex: 0 0 auto properties: icon: user color: default variant: text hideTitle: true - id: admin_stats_row type: Box layout: gap: 16 blocks: - id: admin_stat_users type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: admin_stat_users_val type: Statistic properties: title: Total Users value: 2847 - id: admin_stat_revenue type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: admin_stat_revenue_val type: Statistic properties: title: Revenue value: 48200 prefix: $ - id: admin_stat_orders type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: admin_stat_orders_val type: Statistic properties: title: Orders value: 384 ``` Settings Profile Settings Manage your personal information and preferences. ```yaml - id: settings_layout type: Layout style: minHeight: 400px blocks: - id: settings_sider type: Sider properties: theme: light width: 220 collapsible: false initialCollapsed: false style: borderRight: "1px solid #f0f0f0" blocks: - id: settings_heading type: Title style: padding: 16px 16px 8px properties: content: Settings level: 5 - id: settings_menu type: Menu properties: mode: inline theme: light links: - id: settings_nav_profile type: MenuLink properties: title: Profile icon: user - id: settings_nav_account type: MenuLink properties: title: Account icon: Shield - id: settings_nav_notifications type: MenuLink properties: title: Notifications icon: bell - id: settings_nav_appearance type: MenuLink properties: title: Appearance icon: PaintBucket - id: settings_nav_integrations type: MenuLink properties: title: Integrations icon: Plug - id: settings_content type: Content style: padding: 24px background: var(--ant-color-bg-container) blocks: - id: settings_content_title type: Title properties: content: Profile Settings level: 4 - id: settings_content_desc type: Paragraph properties: content: Manage your personal information and preferences. - id: settings_divider type: Divider - id: settings_form_name type: TextInput properties: title: Display Name placeholder: Enter your name - id: settings_form_email type: TextInput properties: title: Email Address placeholder: you@example.com - id: settings_form_bio type: TextArea properties: title: Bio placeholder: Tell us about yourself - id: settings_form_save type: Button properties: title: Save Changes color: primary variant: solid icon: save ``` Introduction Welcome to the documentation. This page gives an overview of the framework and how to get started building your first application. Use the sidebar on the left to browse through different sections. On smaller screens the sidebar collapses automatically thanks to the breakpoint property set to "md". ```yaml - id: docs_layout type: Layout style: minHeight: 400px blocks: - id: docs_sider type: Sider properties: theme: light width: 260 collapsible: true initialCollapsed: false breakpoint: md class: .element: border-r border-border blocks: - id: docs_search type: TextInput style: padding: 12px 16px properties: placeholder: Search docs... prefixIcon: search - id: docs_menu type: Menu properties: mode: inline theme: light links: - id: docs_nav_getting_started type: MenuGroup properties: title: Getting Started links: - id: docs_nav_intro type: MenuLink properties: title: Introduction - id: docs_nav_install type: MenuLink properties: title: Installation - id: docs_nav_quickstart type: MenuLink properties: title: Quick Start - id: docs_nav_guides type: MenuGroup properties: title: Guides links: - id: docs_nav_blocks type: MenuLink properties: title: Blocks - id: docs_nav_operators type: MenuLink properties: title: Operators - id: docs_nav_actions type: MenuLink properties: title: Actions - id: docs_content type: Content style: padding: 32px background: var(--ant-color-bg-container) blocks: - id: docs_content_title type: Title properties: content: Introduction level: 3 - id: docs_content_p1 type: Paragraph properties: content: Welcome to the documentation. This page gives an overview of the framework and how to get started building your first application. - id: docs_content_p2 type: Paragraph properties: content: Use the sidebar on the left to browse through different sections. On smaller screens the sidebar collapses automatically thanks to the breakpoint property set to "md". ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `breakpoint` | string | - | Breakpoint of the responsive layout. Crossing it fires onBreakpoint. Enum: `xs`, `sm`, `md`, `lg`, `xl`, `xxl`, `xxxl`. | | `collapsedWidth` | integer | - | Width of the collapsed sidebar, by setting to 0 a special trigger will appear | | `collapsible` | boolean | `false` | Show a trigger at the bottom of the sider that collapses and expands it. | | `initialCollapsed` | boolean | `true` | Set the initial collapsed state | | `reverseArrow` | boolean | `false` | Direction of arrow, for a sider that expands from the right | | `width` | string \| number | - | width of the sidebar | | `theme` | string \| object | - | The Sider color theme, light or dark, or antd Layout design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design layout tokens](https://ant.design/components/layout#design-token). | | `theme.triggerHeight` | number | `48` | Height of the collapse trigger of a collapsible Sider. | | `theme.triggerBg` | string | `"#002140"` | Background color of the collapse trigger of a dark Sider. | | `theme.triggerColor` | string | `"#fff"` | Color of the collapse trigger of a dark Sider. | | `theme.lightTriggerBg` | string | `"#ffffff"` | Background color of the collapse trigger of a light Sider. | | `theme.lightTriggerColor` | string | `"rgba(0, 0, 0, 0.88)"` | Color of the collapse trigger of a light Sider. | | `theme.zeroTriggerWidth` | number | `40` | Width of the trigger shown when collapsedWidth is 0. | | `theme.zeroTriggerHeight` | number | `40` | Height of the trigger shown when collapsedWidth is 0. | | Event | Event Data | Description | | --- | --- | --- | | `onClose` | \- | Trigger actions when sider is closed. | | `onOpen` | \- | Trigger actions when sider is opened. | | `onBreakpoint` | `{ broken }` | Trigger actions when the screen width crosses the breakpoint. Use it with the setOpen method to collapse the sider on small screens. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Sider element. | | `/body` | The box inside the Sider that holds its child blocks. | | Slot | Description | | --- | --- | | `content` | Child blocks in the sider panel. | --- url: https://docs.lowdefy.com/md/container-blocks/span.md --- # Span Inline container for wrapping content. A Span with no child blocks, using the content property instead. ```yaml - id: span_basic type: Span blocks: - id: span_basic_text type: Html properties: html: This is text inside a Span block using the content area. - id: span_basic_empty type: Span properties: content: A Span with no child blocks, using the content property instead. ``` Plain text rendered via the content property. Bold, italic, and underlined via the content property. This content property overrides the content area below. ```yaml - id: span_content_plain type: Span properties: content: Plain text rendered via the content property. - id: span_content_html type: Span properties: content: Bold, italic, and underlined via the content property. - id: span_content_overrides type: Span properties: content: This content property overrides the content area below. blocks: - id: span_content_overrides_child type: Html properties: html: This child block is hidden because the content property takes priority. ``` ```yaml - id: span_content_area type: Span blocks: - id: span_content_area_icon type: Icon layout: flex: 0 0 auto properties: name: info size: 14 color: "#1677ff" - id: span_content_area_text type: Html layout: flex: 0 0 auto properties: html: Child blocks render inside the content area when no content property is set. ``` Click me to show a message Click to update state Click to navigate ```yaml - id: span_click_basic type: Span style: .element: color: "#1677ff" textDecoration: underline cursor: pointer properties: content: Click me to show a message events: onClick: - id: span_click_basic_msg type: DisplayMessage params: content: You clicked the Span! status: success - id: span_click_set_state type: Span style: .element: background: "#e6f4ff" padding: 4px 12px borderRadius: 4 color: "#1677ff" fontWeight: bold properties: content: Click to update state events: onClick: - id: span_click_set_state_action type: SetState params: spanClicked: true clickedAt: _date: now - id: span_click_link type: Span style: .element: color: "#722ed1" textDecoration: underline properties: content: Click to navigate events: onClick: - id: span_click_link_action type: Link params: url: https://docs.lowdefy.com newTab: true ``` Underlined span uppercase span monospace code style Gradient span ```yaml - id: span_style_underline type: Span layout: flex: 0 0 auto style: .element: textDecoration: underline textDecorationColor: "#1677ff" textUnderlineOffset: 4px properties: content: Underlined span - id: span_style_uppercase type: Span layout: flex: 0 0 auto style: .element: textTransform: uppercase letterSpacing: 2 fontSize: 12 fontWeight: bold color: "#8c8c8c" properties: content: uppercase span - id: span_style_mono type: Span layout: flex: 0 0 auto style: .element: fontFamily: monospace background: "#f5f5f5" padding: 2px 6px borderRadius: 4 fontSize: 13 color: "#cf1322" properties: content: monospace code style - id: span_style_gradient type: Span layout: flex: 0 0 auto style: .element: background: linear-gradient(90deg, color: "#ffffff" padding: 4px 12px borderRadius: 6 fontWeight: bold properties: content: Gradient span ``` Bold blue text via Tailwind Pill badge Hover for effect Ring outline ```yaml - id: span_tw_bold_blue type: Span layout: flex: 0 0 auto class: element: font-bold text-blue-600 properties: content: Bold blue text via Tailwind - id: span_tw_bg_pill type: Span layout: flex: 0 0 auto class: element: bg-green-100 text-green-800 px-3 py-1 rounded-full text-sm font-medium properties: content: Pill badge - id: span_tw_hover type: Span layout: flex: 0 0 auto class: element: text-gray-600 hover:text-blue-600 hover:underline transition-colors duration-200 cursor-pointer properties: content: Hover for effect - id: span_tw_ring type: Span layout: flex: 0 0 auto class: element: ring-2 ring-purple-300 bg-purple-50 text-purple-700 px-3 py-1 rounded-md text-sm properties: content: Ring outline ``` This paragraph has an inline highlighted Span mixed with regular text. ```yaml - id: span_inline_wrapper type: Box blocks: - id: span_inline_before type: Span layout: flex: 0 0 auto properties: content: "This paragraph has an " - id: span_inline_highlight type: Span layout: flex: 0 0 auto style: .element: color: "#1677ff" fontWeight: bold properties: content: inline highlighted Span - id: span_inline_after type: Span layout: flex: 0 0 auto properties: content: " mixed with regular text." ``` Hello, world ! ```yaml - id: span_multi_wrapper type: Box blocks: - id: span_multi_hello type: Span layout: flex: 0 0 auto style: .element: fontWeight: bold properties: content: "Hello, " - id: span_multi_world type: Span layout: flex: 0 0 auto style: .element: color: "#1677ff" properties: content: world - id: span_multi_excl type: Span layout: flex: 0 0 auto properties: content: "!" ``` Status: ```yaml - id: span_nested_outer type: Span style: .element: padding: 8px 12px background: "#f5f5f5" borderRadius: 6 display: inline-flex alignItems: center gap: 8 blocks: - id: span_nested_label type: Span layout: flex: 0 0 auto style: .element: fontWeight: bold color: "#262626" properties: content: "Status:" - id: span_nested_value type: Span layout: flex: 0 0 auto style: .element: color: "#52c41a" blocks: - id: span_nested_value_icon type: Icon layout: flex: 0 0 auto properties: name: check-circle size: 14 color: "#52c41a" - id: span_nested_value_text type: Html layout: flex: 0 0 auto properties: html: Active ``` Info Success Error ```yaml - id: span_tag_blue type: Span layout: flex: 0 0 auto style: .element: background: "#e6f4ff" color: "#1677ff" padding: 2px 8px borderRadius: 4 fontSize: 12 border: 1px solid properties: content: Info - id: span_tag_green type: Span layout: flex: 0 0 auto style: .element: background: "#f6ffed" color: "#52c41a" padding: 2px 8px borderRadius: 4 fontSize: 12 border: 1px solid properties: content: Success - id: span_tag_red type: Span layout: flex: 0 0 auto style: .element: background: "#fff2f0" color: "#ff4d4f" padding: 2px 8px borderRadius: 4 fontSize: 12 border: 1px solid properties: content: Error ``` NEW 3 LIVE ```yaml - id: span_badge_primary type: Span layout: flex: 0 0 auto style: .element: background: "#1677ff" color: "#ffffff" padding: 2px 10px borderRadius: 12 fontSize: 12 fontWeight: bold properties: content: NEW - id: span_badge_danger type: Span layout: flex: 0 0 auto style: .element: background: "#ff4d4f" color: "#ffffff" padding: 2px 10px borderRadius: 12 fontSize: 12 fontWeight: bold properties: content: "3" - id: span_badge_success type: Span layout: flex: 0 0 auto style: .element: background: "#52c41a" color: "#ffffff" padding: 2px 10px borderRadius: 12 fontSize: 12 fontWeight: bold properties: content: LIVE ``` ```yaml - id: span_icon_smile type: Span style: .element: display: inline-flex alignItems: center gap: 6 blocks: - id: span_icon_smile_icon type: Icon layout: flex: 0 0 auto properties: name: FaceSlightlySmiling size: 16 color: "#faad14" - id: span_icon_smile_label type: Html layout: flex: 0 0 auto properties: html: Happy face - id: span_icon_check type: Span style: .element: display: inline-flex alignItems: center gap: 6 blocks: - id: span_icon_check_icon type: Icon layout: flex: 0 0 auto properties: name: check-circle size: 16 color: "#52c41a" - id: span_icon_check_label type: Html layout: flex: 0 0 auto properties: html: Approved - id: span_icon_warning type: Span style: .element: display: inline-flex alignItems: center gap: 6 blocks: - id: span_icon_warning_icon type: Icon layout: flex: 0 0 auto properties: name: warning size: 16 color: "#ff4d4f" - id: span_icon_warning_label type: Html layout: flex: 0 0 auto properties: html: Warning ``` Order #10482: Estimated delivery Mar 18, 2026 ```yaml - id: span_order_wrapper type: Box layout: gap: 16 align: center blocks: - id: span_order_label type: Span layout: flex: 0 0 auto style: .element: fontWeight: bold color: "#262626" properties: content: "Order #10482:" - id: span_order_shipped type: Span layout: flex: 0 0 auto style: .element: display: inline-flex alignItems: center gap: 6 background: "#f6ffed" color: "#52c41a" padding: 4px 12px borderRadius: 12 fontSize: 13 fontWeight: 500 border: 1px solid blocks: - id: span_order_shipped_icon type: Icon layout: flex: 0 0 auto properties: name: check-circle size: 14 color: "#52c41a" - id: span_order_shipped_text type: Html layout: flex: 0 0 auto properties: html: Shipped - id: span_order_date type: Span layout: flex: 0 0 auto style: .element: color: "#8c8c8c" fontSize: 13 properties: content: Estimated delivery Mar 18, 2026 ``` Home / Products / Widget Pro X ```yaml - id: span_breadcrumb_wrapper type: Box layout: gap: 8 align: center blocks: - id: span_breadcrumb_home type: Span layout: flex: 0 0 auto style: .element: color: "#1677ff" cursor: pointer properties: content: Home events: onClick: - id: span_breadcrumb_home_click type: Link params: url: / - id: span_breadcrumb_sep1 type: Span layout: flex: 0 0 auto style: .element: color: "#d9d9d9" properties: content: / - id: span_breadcrumb_products type: Span layout: flex: 0 0 auto style: .element: color: "#1677ff" cursor: pointer properties: content: Products events: onClick: - id: span_breadcrumb_products_click type: Link params: url: /products - id: span_breadcrumb_sep2 type: Span layout: flex: 0 0 auto style: .element: color: "#d9d9d9" properties: content: / - id: span_breadcrumb_current type: Span layout: flex: 0 0 auto style: .element: color: "#595959" fontWeight: 500 properties: content: Widget Pro X ``` Sarah Mitchell Admin ```yaml - id: span_profile_wrapper type: Box layout: gap: 12 align: center blocks: - id: span_profile_avatar type: Icon layout: flex: 0 0 auto properties: name: user size: 20 color: "#1677ff" - id: span_profile_name type: Span layout: flex: 0 0 auto style: .element: fontWeight: bold fontSize: 14 color: "#262626" properties: content: Sarah Mitchell - id: span_profile_role type: Span layout: flex: 0 0 auto class: element: bg-purple-100 text-purple-700 px-2 py-0.5 rounded-full text-xs font-medium properties: content: Admin - id: span_profile_status type: Span layout: flex: 0 0 auto style: .element: display: inline-flex alignItems: center gap: 4 fontSize: 12 color: "#52c41a" blocks: - id: span_profile_status_dot type: Html layout: flex: 0 0 auto properties: html: - id: span_profile_status_text type: Html layout: flex: 0 0 auto properties: html: Online ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `content` | string | - | Span content string. Overrides the "content" content area. | | Event | Event Data | Description | | --- | --- | --- | | `onClick` | \- | Trigger actions when the Span is clicked. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Span element. | | Slot | Description | | --- | --- | | `content` | Child blocks in the Span. | --- url: https://docs.lowdefy.com/md/container-blocks/splitter.md --- # Splitter Resizable split panels with configurable sizes and collapsible. Left Panel Right Panel ```yaml - id: basic_h type: Splitter style: height: 200px border: "1px solid #e8e8e8" borderRadius: 8px properties: orientation: horizontal panels: - key: left defaultSize: 50 - key: right defaultSize: 50 slots: left: blocks: - id: basic_h_left type: Box class: bg-bg-layout h-full flex items-center justify-center blocks: - id: basic_h_left_text type: Paragraph properties: content: Left Panel right: blocks: - id: basic_h_right type: Box class: bg-bg-layout h-full flex items-center justify-center blocks: - id: basic_h_right_text type: Paragraph properties: content: Right Panel ``` Top Panel Bottom Panel ```yaml - id: basic_v type: Splitter style: height: 300px border: "1px solid #e8e8e8" borderRadius: 8px properties: orientation: vertical panels: - key: top defaultSize: 50 - key: bottom defaultSize: 50 slots: top: blocks: - id: basic_v_top type: Box class: bg-bg-layout h-full flex items-center justify-center blocks: - id: basic_v_top_text type: Paragraph properties: content: Top Panel bottom: blocks: - id: basic_v_bottom type: Box class: bg-bg-layout h-full flex items-center justify-center blocks: - id: basic_v_bottom_text type: Paragraph properties: content: Bottom Panel ``` 25% 50% 25% Fixed 200px Flexible remainder ```yaml - id: three_h type: Splitter style: height: 200px border: "1px solid #e8e8e8" borderRadius: 8px properties: orientation: horizontal panels: - key: first defaultSize: 25 - key: second defaultSize: 50 - key: third defaultSize: 25 slots: first: blocks: - id: three_h_first type: Box class: bg-bg-layout h-full flex items-center justify-center blocks: - id: three_h_first_text type: Paragraph properties: content: 25% second: blocks: - id: three_h_second type: Box class: bg-bg-layout h-full flex items-center justify-center blocks: - id: three_h_second_text type: Paragraph properties: content: 50% third: blocks: - id: three_h_third type: Box class: bg-bg-layout h-full flex items-center justify-center blocks: - id: three_h_third_text type: Paragraph properties: content: 25% - id: pixel_sizes type: Splitter style: height: 180px border: "1px solid #e8e8e8" borderRadius: 8px properties: orientation: horizontal panels: - key: px_fixed defaultSize: 200px - key: px_flex slots: px_fixed: blocks: - id: pixel_sizes_fixed type: Box class: bg-bg-layout h-full flex items-center justify-center blocks: - id: pixel_sizes_fixed_text type: Paragraph properties: content: Fixed 200px px_flex: blocks: - id: pixel_sizes_flex type: Box class: bg-bg-layout h-full flex items-center justify-center blocks: - id: pixel_sizes_flex_text type: Paragraph properties: content: Flexible remainder ``` Navigation Min: 20% / Max: 40% Main Content The left panel cannot be resized smaller than 20% or larger than 40%. Min: 100px / Max: 400px Flexible Center Min: 100px / Max: 300px ```yaml - id: constrained type: Splitter style: height: 200px border: "1px solid #e8e8e8" borderRadius: 8px properties: orientation: horizontal panels: - key: nav defaultSize: 30 min: 20 max: 40 - key: main defaultSize: 70 slots: nav: blocks: - id: constrained_nav type: Box class: bg-bg-layout h-full p-4 blocks: - id: constrained_nav_title type: Paragraph properties: content: Navigation - id: constrained_nav_desc type: Paragraph properties: content: "Min: 20% / Max: 40%" main: blocks: - id: constrained_main type: Box class: bg-bg-layout h-full p-4 blocks: - id: constrained_main_title type: Paragraph properties: content: Main Content - id: constrained_main_desc type: Paragraph properties: content: The left panel cannot be resized smaller than 20% or larger than 40%. - id: constrained_px type: Splitter style: height: 200px border: "1px solid #e8e8e8" borderRadius: 8px properties: orientation: horizontal panels: - key: cpx_left defaultSize: 30 min: 100px max: 400px - key: cpx_center defaultSize: 40 - key: cpx_right defaultSize: 30 min: 100px max: 300px slots: cpx_left: blocks: - id: constrained_px_left type: Box class: bg-bg-layout h-full p-4 flex items-center justify-center blocks: - id: constrained_px_left_text type: Paragraph properties: content: "Min: 100px / Max: 400px" cpx_center: blocks: - id: constrained_px_center type: Box class: bg-bg-layout h-full p-4 flex items-center justify-center blocks: - id: constrained_px_center_text type: Paragraph properties: content: Flexible Center cpx_right: blocks: - id: constrained_px_right type: Box class: bg-bg-layout h-full p-4 flex items-center justify-center blocks: - id: constrained_px_right_text type: Paragraph properties: content: "Min: 100px / Max: 300px" ``` Collapsible Sidebar Click the arrow on the divider to collapse this panel. Main body content remains visible when the sidebar is collapsed. Left (collapsible) Center (fixed) Right (collapsible) ```yaml - id: collapsible_simple type: Splitter style: height: 200px border: "1px solid #e8e8e8" borderRadius: 8px properties: orientation: horizontal panels: - key: col_sidebar defaultSize: 30 collapsible: true - key: col_body defaultSize: 70 slots: col_sidebar: blocks: - id: collapsible_simple_sidebar type: Box class: bg-bg-layout h-full p-4 blocks: - id: collapsible_simple_sidebar_text type: Paragraph properties: content: Collapsible Sidebar - id: collapsible_simple_sidebar_desc type: Paragraph properties: content: Click the arrow on the divider to collapse this panel. col_body: blocks: - id: collapsible_simple_body type: Box class: bg-bg-container h-full p-4 blocks: - id: collapsible_simple_body_text type: Paragraph properties: content: Main body content remains visible when the sidebar is collapsed. - id: collapsible_both type: Splitter style: height: 200px border: "1px solid #e8e8e8" borderRadius: 8px properties: orientation: horizontal panels: - key: cb_left defaultSize: 25 collapsible: true - key: cb_center defaultSize: 50 - key: cb_right defaultSize: 25 collapsible: true slots: cb_left: blocks: - id: collapsible_both_left type: Box class: bg-bg-layout h-full p-3 flex items-center justify-center blocks: - id: collapsible_both_left_text type: Paragraph properties: content: Left (collapsible) cb_center: blocks: - id: collapsible_both_center type: Box class: bg-bg-layout h-full p-3 flex items-center justify-center blocks: - id: collapsible_both_center_text type: Paragraph properties: content: Center (fixed) cb_right: blocks: - id: collapsible_both_right type: Box class: bg-bg-layout h-full p-3 flex items-center justify-center blocks: - id: collapsible_both_right_text type: Paragraph properties: content: Right (collapsible) ``` Collapse left only Collapse both directions Collapse right only ```yaml - id: col_start_end type: Splitter style: height: 200px border: "1px solid #e8e8e8" borderRadius: 8px properties: orientation: horizontal panels: - key: cse_left defaultSize: 30 collapsible: end: true - key: cse_center defaultSize: 40 collapsible: start: true end: true - key: cse_right defaultSize: 30 collapsible: start: true slots: cse_left: blocks: - id: col_se_left type: Box class: bg-bg-layout h-full p-3 flex items-center justify-center blocks: - id: col_se_left_text type: Paragraph properties: content: Collapse left only cse_center: blocks: - id: col_se_center type: Box class: bg-bg-layout h-full p-3 flex items-center justify-center blocks: - id: col_se_center_text type: Paragraph properties: content: Collapse both directions cse_right: blocks: - id: col_se_right type: Box class: bg-bg-layout h-full p-3 flex items-center justify-center blocks: - id: col_se_right_text type: Paragraph properties: content: Collapse right only ``` The panel animates as it collapses, and its blocks unmount while it is collapsed. Double-click the drag handle to trigger onDraggerDoubleClick. ```yaml - id: col_motion type: Splitter style: height: 200px border: "1px solid #e8e8e8" borderRadius: 8px properties: destroyOnHidden: true draggerIcon: GripVertical collapsible: motion: true icon: start: ChevronsLeft end: ChevronsRight panels: - key: col_motion_nav defaultSize: 30 collapsible: end: true showCollapsibleIcon: true - key: col_motion_main slots: col_motion_nav: blocks: - id: col_motion_nav_text type: Paragraph properties: content: The panel animates as it collapses, and its blocks unmount while it is collapsed. col_motion_main: blocks: - id: col_motion_main_text type: Paragraph properties: content: Double-click the drag handle to trigger onDraggerDoubleClick. ``` Fixed (resizable: false) Resizable Resizable ```yaml - id: nonresizable type: Splitter style: height: 200px border: "1px solid #e8e8e8" borderRadius: 8px properties: orientation: horizontal panels: - key: nr_fixed defaultSize: 30 resizable: false - key: nr_flex_a defaultSize: 40 - key: nr_flex_b defaultSize: 30 slots: nr_fixed: blocks: - id: nonresizable_fixed type: Box class: bg-bg-layout h-full p-3 flex items-center justify-center blocks: - id: nonresizable_fixed_text type: Paragraph properties: content: "Fixed (resizable: false)" nr_flex_a: blocks: - id: nonresizable_flex_a type: Box class: bg-bg-layout h-full p-3 flex items-center justify-center blocks: - id: nonresizable_flex_a_text type: Paragraph properties: content: Resizable nr_flex_b: blocks: - id: nonresizable_flex_b type: Box class: bg-bg-layout h-full p-3 flex items-center justify-center blocks: - id: nonresizable_flex_b_text type: Paragraph properties: content: Resizable ``` Lazy rendering defers content until the panel is visible. Collapse and expand to see the effect. This panel content is also lazily rendered. ```yaml - id: lazy_splitter type: Splitter style: height: 200px border: "1px solid #e8e8e8" borderRadius: 8px properties: orientation: horizontal lazy: true panels: - key: lazy_left defaultSize: 50 collapsible: true - key: lazy_right defaultSize: 50 slots: lazy_left: blocks: - id: lazy_left_box type: Box class: bg-bg-layout h-full p-4 flex items-center justify-center blocks: - id: lazy_left_text type: Paragraph properties: content: Lazy rendering defers content until the panel is visible. Collapse and expand to see the effect. lazy_right: blocks: - id: lazy_right_box type: Box class: bg-bg-layout h-full p-4 flex items-center justify-center blocks: - id: lazy_right_text type: Paragraph properties: content: This panel content is also lazily rendered. ``` Collapsible Panel Collapse this panel or resize the divider to see event messages. The onResizeEnd and onCollapse events fire DisplayMessage actions to show the current panel state. State Tracking Resize the panels. The onResizeStart fires a message and onResizeEnd saves panel sizes to state. Current sizes: ```yaml - id: event_splitter type: Splitter style: height: 200px border: "1px solid #e8e8e8" borderRadius: 8px properties: orientation: horizontal panels: - key: evt_left defaultSize: 50 collapsible: true - key: evt_right defaultSize: 50 events: onResizeEnd: - id: event_resize_msg type: DisplayMessage params: content: _string.concat: - "Resize complete. Panel sizes: " - _json.stringify: on: _event: sizes duration: 2 onCollapse: - id: event_collapse_msg type: DisplayMessage params: content: _if: test: _event: collapsed then: Panel collapsed. else: Panel expanded. duration: 2 slots: evt_left: blocks: - id: event_left_box type: Box class: bg-bg-layout h-full p-4 blocks: - id: event_left_title type: Paragraph properties: content: Collapsible Panel - id: event_left_desc type: Paragraph properties: content: Collapse this panel or resize the divider to see event messages. evt_right: blocks: - id: event_right_box type: Box class: bg-bg-container h-full p-4 blocks: - id: event_right_desc type: Paragraph properties: content: The onResizeEnd and onCollapse events fire DisplayMessage actions to show the current panel state. - id: event_state_splitter type: Splitter style: height: 220px border: "1px solid #e8e8e8" borderRadius: 8px properties: orientation: horizontal panels: - key: es_left defaultSize: 40 - key: es_right defaultSize: 60 events: onResizeEnd: - id: event_set_state type: SetState params: splitter_sizes: _event: sizes onResizeStart: - id: event_resize_start_msg type: DisplayMessage params: content: Resizing started... duration: 1 slots: es_left: blocks: - id: es_left_box type: Box class: bg-bg-layout h-full p-4 blocks: - id: es_left_title type: Paragraph properties: content: State Tracking - id: es_left_desc type: Paragraph properties: content: Resize the panels. The onResizeStart fires a message and onResizeEnd saves panel sizes to state. es_right: blocks: - id: es_right_box type: Box class: bg-bg-container h-full p-4 blocks: - id: es_right_title type: Paragraph properties: content: Current sizes: - id: es_right_sizes type: Paragraph properties: content: _if: test: _not: _not: _state: splitter_sizes then: _json.stringify: on: _state: splitter_sizes else: Resize panels to see sizes here. ``` Sidebar Dashboard Analytics Settings Main Content Area A horizontal splitter with a nested vertical splitter. The sidebar splits horizontally, while the main area is split vertically into content and a bottom panel. Bottom Panel Collapsible bottom panel inside the nested vertical splitter. ```yaml - id: nested_outer type: Splitter style: height: 350px border: "1px solid #e8e8e8" borderRadius: 8px properties: orientation: horizontal panels: - key: n_sidebar defaultSize: 25 min: 15 collapsible: true - key: n_main defaultSize: 75 slots: n_sidebar: blocks: - id: nested_sidebar type: Box class: bg-bg-layout h-full p-4 blocks: - id: nested_sidebar_title type: Paragraph properties: content: Sidebar - id: nested_sidebar_item_1 type: Paragraph properties: content: Dashboard - id: nested_sidebar_item_2 type: Paragraph properties: content: Analytics - id: nested_sidebar_item_3 type: Paragraph properties: content: Settings n_main: blocks: - id: nested_inner type: Splitter style: height: 100% properties: orientation: vertical panels: - key: n_content defaultSize: 70 - key: n_footer defaultSize: 30 min: 15 collapsible: true slots: n_content: blocks: - id: nested_content type: Box class: bg-bg-container h-full p-4 blocks: - id: nested_content_title type: Paragraph properties: content: Main Content Area - id: nested_content_desc type: Paragraph properties: content: A horizontal splitter with a nested vertical splitter. The sidebar splits horizontally, while the main area is split vertically into content and a bottom panel. n_footer: blocks: - id: nested_footer type: Box class: bg-bg-layout h-full p-4 blocks: - id: nested_footer_title type: Paragraph properties: content: Bottom Panel - id: nested_footer_desc type: Paragraph properties: content: Collapsible bottom panel inside the nested vertical splitter. ``` splitBarSize: 4, splitTriggerSize: 24 Thicker blue splitter bar with larger trigger area Green themed divider, arrows, and resize dots colorFill, colorPrimary, colorText overridden Minimal divider with no visible trigger handle resizeSpinnerSize: 0, splitTriggerSize: 0 Custom drag handle with blue tones splitBarDraggableSize: 40, borderRadius: 12 ```yaml - id: theme_thick_bar type: Splitter style: height: 200px border: "1px solid #e8e8e8" borderRadius: 8px properties: orientation: horizontal panels: - key: ttb_left defaultSize: 50 - key: ttb_right defaultSize: 50 theme: splitBarSize: 4 splitTriggerSize: 24 colorFill: "#1677ff" slots: ttb_left: blocks: - id: theme_thick_left type: Box class: bg-bg-layout h-full p-4 flex items-center justify-center blocks: - id: theme_thick_left_text type: Paragraph properties: content: "splitBarSize: 4, splitTriggerSize: 24" ttb_right: blocks: - id: theme_thick_right type: Box class: bg-bg-layout h-full p-4 flex items-center justify-center blocks: - id: theme_thick_right_text type: Paragraph properties: content: Thicker blue splitter bar with larger trigger area - id: theme_green type: Splitter style: height: 200px border: "1px solid #e8e8e8" borderRadius: 8px properties: orientation: horizontal panels: - key: tg_left defaultSize: 50 collapsible: true - key: tg_right defaultSize: 50 theme: splitBarSize: 3 colorFill: "#52c41a" colorPrimary: "#52c41a" colorText: "#52c41a" splitTriggerSize: 20 slots: tg_left: blocks: - id: theme_green_left type: Box class: bg-bg-layout h-full p-4 flex items-center justify-center blocks: - id: theme_green_left_text type: Paragraph properties: content: Green themed divider, arrows, and resize dots tg_right: blocks: - id: theme_green_right type: Box class: bg-bg-layout h-full p-4 flex items-center justify-center blocks: - id: theme_green_right_text type: Paragraph properties: content: colorFill, colorPrimary, colorText overridden - id: theme_minimal type: Splitter style: height: 200px border: "1px solid #e8e8e8" borderRadius: 8px properties: orientation: horizontal panels: - key: tm_left defaultSize: 50 - key: tm_right defaultSize: 50 theme: splitBarSize: 1 splitTriggerSize: 0 resizeSpinnerSize: 0 slots: tm_left: blocks: - id: theme_minimal_left type: Box class: bg-bg-layout h-full p-4 flex items-center justify-center blocks: - id: theme_minimal_left_text type: Paragraph properties: content: Minimal divider with no visible trigger handle tm_right: blocks: - id: theme_minimal_right type: Box class: bg-bg-layout h-full p-4 flex items-center justify-center blocks: - id: theme_minimal_right_text type: Paragraph properties: content: "resizeSpinnerSize: 0, splitTriggerSize: 0" - id: theme_custom_handle type: Splitter style: height: 200px border: "1px solid #e8e8e8" borderRadius: 8px properties: orientation: horizontal panels: - key: tch_left defaultSize: 50 - key: tch_right defaultSize: 50 theme: splitBarDraggableSize: 40 colorFillTertiary: "#e6f4ff" colorFillSecondary: "#91caff" borderRadius: 12 slots: tch_left: blocks: - id: theme_handle_left type: Box class: bg-bg-layout h-full p-4 flex items-center justify-center blocks: - id: theme_handle_left_text type: Paragraph properties: content: Custom drag handle with blue tones tch_right: blocks: - id: theme_handle_right type: Box class: bg-bg-layout h-full p-4 flex items-center justify-center blocks: - id: theme_handle_right_text type: Paragraph properties: content: "splitBarDraggableSize: 40, borderRadius: 12" ``` The element CSS key applies Tailwind classes to the outer Splitter wrapper. class.element: rounded-2xl shadow-lg overflow-hidden Dashed blue border via Tailwind class.element: border-2 border-dashed border-blue-300 rounded-xl ```yaml - id: css_rounded type: Splitter class: .element: rounded-2xl shadow-lg overflow-hidden style: height: 200px properties: orientation: horizontal panels: - key: css_r_left defaultSize: 50 - key: css_r_right defaultSize: 50 slots: css_r_left: blocks: - id: css_rounded_left type: Box class: bg-gradient-to-br from-primary/10 to-primary/10 h-full p-4 flex items-center justify-center blocks: - id: css_rounded_left_text type: Paragraph properties: content: The element CSS key applies Tailwind classes to the outer Splitter wrapper. css_r_right: blocks: - id: css_rounded_right type: Box class: bg-gradient-to-br from-primary/15 to-primary/10 h-full p-4 flex items-center justify-center blocks: - id: css_rounded_right_text type: Paragraph properties: content: "class.element: rounded-2xl shadow-lg overflow-hidden" - id: css_border type: Splitter class: .element: border-2 border-dashed border-blue-300 rounded-xl style: height: 180px properties: orientation: horizontal panels: - key: css_b_left defaultSize: 40 - key: css_b_right defaultSize: 60 slots: css_b_left: blocks: - id: css_border_left type: Box class: bg-bg-layout h-full p-4 flex items-center justify-center blocks: - id: css_border_left_text type: Paragraph properties: content: Dashed blue border via Tailwind css_b_right: blocks: - id: css_border_right type: Box class: bg-bg-container h-full p-4 flex items-center justify-center blocks: - id: css_border_right_text type: Paragraph properties: content: "class.element: border-2 border-dashed border-blue-300 rounded-xl" ``` Explorer src/ index.js App.js styles.css package.json App.js import React from "react";

function App() {
  return (
    <div className="app">
      <h1>Hello World</h1>
    </div>
  );
} Terminal $ npm start Compiled successfully! ```yaml - id: editor_outer type: Splitter style: height: 420px border: "1px solid #d9d9d9" borderRadius: 8px properties: orientation: horizontal panels: - key: editor_files defaultSize: 18 min: 12 collapsible: true - key: editor_main defaultSize: 82 events: onCollapse: - id: editor_collapse_msg type: DisplayMessage params: content: _if: test: _event: collapsed then: File explorer collapsed. else: File explorer expanded. duration: 2 slots: editor_files: blocks: - id: editor_files_box type: Box class: bg-bg-layout h-full p-3 blocks: - id: editor_files_title type: Paragraph style: color: "#9ca3af" fontSize: 11px textTransform: uppercase letterSpacing: 1px properties: content: Explorer - id: editor_files_f1 type: Paragraph style: color: "#e2e8f0" fontSize: 13px properties: content: src/ - id: editor_files_f2 type: Paragraph style: color: "#94a3b8" fontSize: 13px paddingLeft: 12px properties: content: index.js - id: editor_files_f3 type: Paragraph style: color: "#94a3b8" fontSize: 13px paddingLeft: 12px properties: content: App.js - id: editor_files_f4 type: Paragraph style: color: "#94a3b8" fontSize: 13px paddingLeft: 12px properties: content: styles.css - id: editor_files_f5 type: Paragraph style: color: "#e2e8f0" fontSize: 13px properties: content: package.json editor_main: blocks: - id: editor_inner type: Splitter style: height: 100% properties: orientation: vertical panels: - key: editor_code defaultSize: 65 - key: editor_terminal defaultSize: 35 min: 15 collapsible: true slots: editor_code: blocks: - id: editor_code_box type: Box class: bg-bg-layout h-full p-4 blocks: - id: editor_code_tab type: Paragraph style: color: "#64748b" fontSize: 12px borderBottom: "1px solid #334155" paddingBottom: 8px properties: content: App.js - id: editor_code_content type: Paragraph style: color: "#e2e8f0" fontFamily: monospace fontSize: 13px lineHeight: 1.6 properties: content: import React from "react";

function App() {
  return (
    <div className="app">
      <h1>Hello World</h1>
    </div>
  );
} editor_terminal: blocks: - id: editor_terminal_box type: Box class: bg-bg-layout h-full p-3 blocks: - id: editor_terminal_title type: Paragraph style: color: "#64748b" fontSize: 11px textTransform: uppercase letterSpacing: 1px borderBottom: "1px solid #1e293b" paddingBottom: 6px properties: content: Terminal - id: editor_terminal_line type: Paragraph style: color: "#22c55e" fontFamily: monospace fontSize: 13px properties: content: $ npm start - id: editor_terminal_output type: Paragraph style: color: "#94a3b8" fontFamily: monospace fontSize: 13px properties: content: Compiled successfully! ``` Overview Sarah updated the project plan 2 min ago James added 3 team members 15 min ago System completed deployment 1 hour ago Alex resolved 5 support tickets 3 hours ago ```yaml - id: dashboard_outer type: Splitter style: height: 400px border: "1px solid #e8e8e8" borderRadius: 8px properties: orientation: horizontal panels: - key: dash_nav defaultSize: 22 min: 15 max: 35 collapsible: true - key: dash_content defaultSize: 50 - key: dash_detail defaultSize: 28 min: 20 collapsible: true events: onResizeEnd: - id: dash_resize_state type: SetState params: dashboard_layout: _event: sizes slots: dash_nav: blocks: - id: dash_nav_box type: Box class: bg-bg-layout h-full p-0 blocks: - id: dash_nav_card type: Card properties: title: Navigation size: small bordered: false blocks: - id: dash_nav_item1 type: Button properties: title: Overview icon: Gauge variant: text block: true - id: dash_nav_item2 type: Button properties: title: Analytics icon: chart variant: text block: true - id: dash_nav_item3 type: Button properties: title: Users icon: users variant: text block: true - id: dash_nav_item4 type: Button properties: title: Settings icon: settings variant: text block: true dash_content: blocks: - id: dash_content_box type: Box class: h-full p-4 blocks: - id: dash_content_title type: Title properties: content: Overview level: 4 - id: dash_stats_row type: Box layout: gap: 16 blocks: - id: dash_stat_users type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: dash_stat_users_val type: Statistic properties: title: Total Users value: 2846 - id: dash_stat_revenue type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: dash_stat_revenue_val type: Statistic properties: title: Revenue value: 48200 prefix: $ - id: dash_progress_card type: Card properties: title: Monthly Target size: small blocks: - id: dash_progress_bar type: Progress properties: percent: 72 dash_detail: blocks: - id: dash_detail_box type: Box class: bg-bg-layout h-full p-0 blocks: - id: dash_detail_card type: Card properties: title: Recent Activity size: small bordered: false blocks: - id: dash_detail_item1 type: Paragraph properties: content: Sarah updated the project plan 2 min ago - id: dash_detail_divider1 type: Divider - id: dash_detail_item2 type: Paragraph properties: content: James added 3 team members 15 min ago - id: dash_detail_divider2 type: Divider - id: dash_detail_item3 type: Paragraph properties: content: System completed deployment 1 hour ago - id: dash_detail_divider3 type: Divider - id: dash_detail_item4 type: Paragraph properties: content: Alex resolved 5 support tickets 3 hours ago ``` Folders Inbox - 12 messages Alice Johnson Q4 Planning Meeting Notes 10:30 AM Bob Williams Design review feedback 9:15 AM Carol Davis Updated project timeline Yesterday Q4 Planning Meeting Notes Alice Johnson March 13, 2026 at 10:30 AM Hi team, here are the key takeaways from our Q4 planning meeting yesterday. We agreed on the following priorities: launching the new dashboard feature, improving API performance by 30%, and completing the migration to the new design system. Please review the attached document and share any feedback by end of week. Our next sync is scheduled for Monday at 2 PM. ```yaml - id: email_outer type: Splitter class: .element: rounded-lg shadow-md overflow-hidden style: height: 420px properties: orientation: horizontal panels: - key: email_folders defaultSize: 20 min: 15 collapsible: true - key: email_list defaultSize: 35 min: 25 - key: email_preview defaultSize: 45 events: onCollapse: - id: email_collapse_action type: DisplayMessage params: content: _if: test: _event: collapsed then: Folder panel collapsed. Click the arrow to expand. else: Folder panel expanded. duration: 2 slots: email_folders: blocks: - id: email_folders_box type: Box class: bg-bg-layout h-full p-3 blocks: - id: email_folders_title type: Paragraph style: fontWeight: 600 fontSize: 13px color: "#64748b" textTransform: uppercase letterSpacing: 0.5px properties: content: Folders - id: email_folder_inbox type: Button properties: title: Inbox (12) icon: Inbox variant: text block: true size: small - id: email_folder_sent type: Button properties: title: Sent icon: send variant: text block: true size: small - id: email_folder_drafts type: Button properties: title: Drafts (3) icon: edit variant: text block: true size: small - id: email_folder_trash type: Button properties: title: Trash icon: delete variant: text block: true size: small email_list: blocks: - id: email_list_box type: Box class: h-full p-0 blocks: - id: email_list_header type: Box class: px-3 py-2 border-b border-border blocks: - id: email_list_search type: Paragraph style: color: "#94a3b8" fontSize: 13px properties: content: Inbox - 12 messages - id: email_msg1 type: Box class: px-3 py-2 border-b border-border bg-bg-layout blocks: - id: email_msg1_from type: Paragraph style: fontWeight: 600 fontSize: 13px properties: content: Alice Johnson - id: email_msg1_subject type: Paragraph style: fontSize: 13px properties: content: Q4 Planning Meeting Notes - id: email_msg1_time type: Paragraph style: fontSize: 11px color: "#94a3b8" properties: content: 10:30 AM - id: email_msg2 type: Box class: px-3 py-2 border-b border-border blocks: - id: email_msg2_from type: Paragraph style: fontWeight: 600 fontSize: 13px properties: content: Bob Williams - id: email_msg2_subject type: Paragraph style: fontSize: 13px properties: content: Design review feedback - id: email_msg2_time type: Paragraph style: fontSize: 11px color: "#94a3b8" properties: content: 9:15 AM - id: email_msg3 type: Box class: px-3 py-2 border-b border-border blocks: - id: email_msg3_from type: Paragraph style: fontSize: 13px properties: content: Carol Davis - id: email_msg3_subject type: Paragraph style: fontSize: 13px properties: content: Updated project timeline - id: email_msg3_time type: Paragraph style: fontSize: 11px color: "#94a3b8" properties: content: Yesterday email_preview: blocks: - id: email_preview_box type: Box class: h-full p-4 blocks: - id: email_preview_subject type: Title properties: content: Q4 Planning Meeting Notes level: 4 - id: email_preview_meta type: Box layout: gap: 8 align: center blocks: - id: email_preview_avatar type: Avatar layout: flex: 0 0 auto properties: icon: user size: 32 color: "#1677ff" - id: email_preview_sender type: Box layout: flex: 1 1 0 blocks: - id: email_preview_from type: Paragraph style: fontWeight: 600 fontSize: 14px properties: content: Alice Johnson - id: email_preview_date type: Paragraph style: fontSize: 12px color: "#94a3b8" properties: content: March 13, 2026 at 10:30 AM - id: email_preview_divider type: Divider - id: email_preview_body type: Paragraph properties: content: "Hi team, here are the key takeaways from our Q4 planning meeting yesterday. We agreed on the following priorities: launching the new dashboard feature, improving API performance by 30%, and completing the migration to the new design system." - id: email_preview_body2 type: Paragraph properties: content: Please review the attached document and share any feedback by end of week. Our next sync is scheduled for Monday at 2 PM. - id: email_preview_tags type: Box layout: gap: 8 blocks: - id: email_preview_tag1 type: Tag properties: title: Planning color: blue - id: email_preview_tag2 type: Tag properties: title: Q4 color: purple ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `collapsible` | object | - | Collapse behaviour shared by all panels. Which panels collapse is set per panel with panels[].collapsible. | | `collapsible.motion` | boolean | `false` | Animate panels as they collapse and expand. | | `collapsible.icon` | object | - | Custom collapse icons. By default the collapse buttons show `chevron-left` and `chevron-right` arrows, or `chevron-up` and `chevron-down` arrows on a vertical Splitter. | | `collapsible.icon.start` | string \| object | - | Icon name or properties of an Icon block for the collapse button that moves the bar towards the start. | | `collapsible.icon.start.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `collapsible.icon.start.color` | string | - | Icon color. | | `collapsible.icon.start.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `collapsible.icon.start.rotate` | number | - | Number of degrees to rotate the icon. | | `collapsible.icon.start.spin` | boolean | - | Continuously spin the icon with animation. | | `collapsible.icon.start.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `collapsible.icon.start.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `collapsible.icon.start.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `collapsible.icon.start.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `collapsible.icon.end` | string \| object | - | Icon name or properties of an Icon block for the collapse button that moves the bar towards the end. | | `collapsible.icon.end.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `collapsible.icon.end.color` | string | - | Icon color. | | `collapsible.icon.end.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `collapsible.icon.end.rotate` | number | - | Number of degrees to rotate the icon. | | `collapsible.icon.end.spin` | boolean | - | Continuously spin the icon with animation. | | `collapsible.icon.end.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `collapsible.icon.end.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `collapsible.icon.end.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `collapsible.icon.end.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `destroyOnHidden` | boolean | `false` | Unmount the blocks in a panel while it is collapsed. Can be overridden per panel. | | `draggerIcon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to show in the drag handle. | | `draggerIcon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `draggerIcon.color` | string | - | Icon color. | | `draggerIcon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `draggerIcon.rotate` | number | - | Number of degrees to rotate the icon. | | `draggerIcon.spin` | boolean | - | Continuously spin the icon with animation. | | `draggerIcon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `draggerIcon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `draggerIcon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `draggerIcon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `lazy` | boolean | `false` | Lazy render panel content. | | `layout` | string | `"horizontal"` | Layout direction of the splitter. Prefer orientation. Enum: `horizontal`, `vertical`. | | `orientation` | string | - | Layout direction of the splitter. Alias for layout, takes precedence if both are set. Enum: `horizontal`, `vertical`. | | `panels` | array | - | Panel configuration array. Each panel has key, size, min, max, defaultSize, collapsible, resizable and destroyOnHidden. | | `panels.$.key` | string | - | Unique panel key, used to match content slots. | | `panels.$.size` | number \| string | - | Controlled panel size. | | `panels.$.min` | number \| string | - | Minimum size threshold. | | `panels.$.max` | number \| string | - | Maximum size threshold. | | `panels.$.defaultSize` | number \| string | - | Default panel size. | | `panels.$.collapsible` | boolean \| object | `false` | Whether the panel is collapsible. Set an object to choose the collapse directions and when the collapse buttons show. | | `panels.$.collapsible.start` | boolean | - | Show a collapse button on the bar at the start edge of the panel. | | `panels.$.collapsible.end` | boolean | - | Show a collapse button on the bar at the end edge of the panel. | | `panels.$.collapsible.showCollapsibleIcon` | boolean \| string | `"auto"` | When to show the collapse buttons: always (true), never (false) or on hover (auto). Enum: `true`, `false`, `auto`. | | `panels.$.destroyOnHidden` | boolean | - | Unmount the blocks in this panel while it is collapsed. Overrides the Splitter destroyOnHidden. | | `panels.$.resizable` | boolean | `true` | Whether the panel is resizable. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design splitter tokens](https://ant.design/components/splitter#design-token). | | `theme.splitBarSize` | number | `1` | Thickness of the divider bar between panels in pixels. | | `theme.splitTriggerSize` | number | `6` | Size of the interactive trigger area for resizing in pixels. | | `theme.splitBarDraggableSize` | number | `20` | Size of the draggable handle area in pixels. | | `theme.resizeSpinnerSize` | number | `20` | Size of the resize indicator dots in pixels. | | `theme.colorFill` | string | - | Color of the splitter bar. | | `theme.colorFillTertiary` | string | - | Background color of the drag trigger area. | | `theme.colorFillSecondary` | string | - | Background color of the drag trigger area on hover. | | `theme.colorPrimary` | string | - | Color used for the collapse arrows. | | `theme.colorText` | string | - | Color of the resize dots indicator. | | `theme.colorBgElevated` | string | - | Background color of the drag trigger handle. | | `theme.borderRadius` | number | `6` | Border radius of the drag trigger handle. | | Event | Event Data | Description | | --- | --- | --- | | `onCollapse` | `{ collapsed, sizes }` | Trigger action when a panel is collapsed or expanded. | | `onResize` | `{ sizes }` | Trigger action when panel sizes change during resize. | | `onResizeEnd` | `{ sizes }` | Trigger action when resize ends. | | `onResizeStart` | `{ sizes }` | Trigger action when resize starts. | | `onDraggerDoubleClick` | `{ index }` | Trigger action when a drag handle is double-clicked. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Splitter element. | | `/panel` | Each Splitter panel. | | `/dragger` | The drag handle between panels. | | `/draggerIcon` | The custom icon in the drag handle, set with draggerIcon. | | `/collapseIcon` | The collapse icons on the drag handles of collapsible panels. | Slot keys are user-defined in your config and resolved at build time — not generated at runtime. The block typically pairs slots with an array property (`tabs`, `panels`, `slides`) listed in the Properties table; see the examples above for the expected shape. --- url: https://docs.lowdefy.com/md/container-blocks/spinner.md --- # Spinner Loading spinner overlay for content areas. ```yaml - id: spinner_default type: Spinner ``` ```yaml - id: spinner_named_small type: Spinner layout: flex: 0 0 auto properties: size: small - id: spinner_named_medium type: Spinner layout: flex: 0 0 auto properties: size: medium - id: spinner_named_large type: Spinner layout: flex: 0 0 auto properties: size: large ``` ```yaml - id: spinner_numeric_16 type: Spinner layout: flex: 0 0 auto properties: size: 16 - id: spinner_numeric_32 type: Spinner layout: flex: 0 0 auto properties: size: 32 - id: spinner_numeric_64 type: Spinner layout: flex: 0 0 auto properties: size: 64 ``` ```yaml - id: spinner_styled_blue type: Spinner layout: flex: 0 0 auto class: element: text-blue-500 properties: size: large - id: spinner_styled_green type: Spinner layout: flex: 0 0 auto class: element: text-green-500 properties: size: large - id: spinner_styled_red type: Spinner layout: flex: 0 0 auto class: element: text-red-500 properties: size: large - id: spinner_styled_purple type: Spinner layout: flex: 0 0 auto class: element: text-purple-600 properties: size: large ``` ```yaml - id: spinner_css_bg_pill type: Spinner layout: flex: 0 0 auto class: element: bg-blue-50 p-4 rounded-full properties: size: large - id: spinner_css_bordered type: Spinner layout: flex: 0 0 auto class: element: border border-gray-200 p-3 rounded-lg bg-white shadow-sm properties: size: medium - id: spinner_css_dark_bg type: Spinner layout: flex: 0 0 auto class: element: bg-gray-800 text-white p-4 rounded-xl properties: size: large ``` ```yaml - id: spinner_style_custom_color type: Spinner layout: flex: 0 0 auto style: .element: color: "#fa8c16" properties: size: large - id: spinner_style_padded type: Spinner layout: flex: 0 0 auto style: .element: padding: 16px backgroundColor: "#f6ffed" borderRadius: 12px properties: size: medium ``` Loading orders... ```yaml - id: spinner_loading_overlay_card type: Card properties: title: Recent Orders slots: extra: blocks: - id: spinner_loading_overlay_btn type: Button layout: flex: 0 0 auto properties: title: Refresh icon: refresh color: primary variant: outlined size: small events: onClick: - id: spinner_loading_overlay_msg type: DisplayMessage params: content: Fetching latest orders... duration: 2 blocks: - id: spinner_loading_overlay_box type: Box layout: direction: column align: center justify: center gap: 12 style: .element: minHeight: 200px blocks: - id: spinner_loading_overlay_spinner type: Spinner layout: flex: 0 0 auto class: element: text-blue-500 properties: size: large - id: spinner_loading_overlay_text type: Paragraph layout: flex: 0 0 auto class: text-gray-400 properties: content: Loading orders... ``` ```yaml - id: spinner_btn_loading_card type: Card properties: title: Submit Application blocks: - id: spinner_btn_loading_form_name type: TextInput properties: label: title: Full Name placeholder: Enter your name - id: spinner_btn_loading_form_email type: TextInput properties: label: title: Email Address placeholder: you@example.com - id: spinner_btn_loading_row type: Box layout: gap: 12 align: center justify: end blocks: - id: spinner_btn_loading_indicator type: Spinner layout: flex: 0 0 auto class: element: text-blue-500 visible: _if: test: _eq: - _state: submitting - true then: true else: false properties: size: small - id: spinner_btn_loading_submit type: Button layout: flex: 0 0 auto properties: title: Submit color: primary variant: solid icon: send events: onClick: - id: spinner_btn_loading_set_state type: SetState params: submitting: true - id: spinner_btn_loading_msg type: DisplayMessage params: content: Application submitted successfully! duration: 3 ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `size` | string \| number | - | Size of the icon spinner: small, medium, large, or a pixel number. | No events defined. | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Spinner element. | No slots defined. --- url: https://docs.lowdefy.com/md/container-blocks/tabs.md --- # Tabs Tab panels with card, editable, and positioned variants. Personal Information Manage your personal information, display name, and avatar. Changes are saved automatically across all your connected devices. Preferences Configure application preferences, notification settings, and keyboard shortcuts. These settings apply to your current workspace. Billing Details View your current plan, payment history, and update billing information. Your next invoice is scheduled for the first of next month. ```yaml - id: basic_tabs type: Tabs properties: tabs: - key: profile title: Profile - key: settings title: Settings - key: billing title: Billing slots: profile: blocks: - id: basic_profile_title type: Title properties: content: Personal Information level: 4 - id: basic_profile_content type: Paragraph properties: content: Manage your personal information, display name, and avatar. Changes are saved automatically across all your connected devices. settings: blocks: - id: basic_settings_title type: Title properties: content: Preferences level: 4 - id: basic_settings_content type: Paragraph properties: content: Configure application preferences, notification settings, and keyboard shortcuts. These settings apply to your current workspace. billing: blocks: - id: basic_billing_title type: Title properties: content: Billing Details level: 4 - id: basic_billing_content type: Paragraph properties: content: View your current plan, payment history, and update billing information. Your next invoice is scheduled for the first of next month. ``` Dashboard overview showing key metrics and recent activity for your project. Conversion funnel progress for the current quarter. Generate and download reports for weekly, monthly, and quarterly performance summaries. Select a date range to get started. Export your data in CSV, JSON, or PDF format. Scheduled exports run daily at midnight UTC. ```yaml - id: card_tabs type: Tabs properties: tabType: card tabs: - key: overview title: Overview - key: analytics title: Analytics - key: reports title: Reports - key: exports title: Exports slots: overview: blocks: - id: card_overview_row type: Box layout: gap: 16 blocks: - id: card_stat_users type: Statistic layout: flex: 1 1 0 properties: title: Active Users value: 1284 - id: card_stat_sessions type: Statistic layout: flex: 1 1 0 properties: title: Sessions Today value: 3567 - id: card_stat_revenue type: Statistic layout: flex: 1 1 0 properties: title: Revenue value: 48250 prefix: $ - id: card_overview_content type: Paragraph properties: content: Dashboard overview showing key metrics and recent activity for your project. analytics: blocks: - id: card_analytics_progress_label type: Paragraph properties: content: Conversion funnel progress for the current quarter. - id: card_analytics_progress type: Progress properties: percent: 72 status: active reports: blocks: - id: card_reports_content type: Paragraph properties: content: Generate and download reports for weekly, monthly, and quarterly performance summaries. Select a date range to get started. exports: blocks: - id: card_exports_content type: Paragraph properties: content: Export your data in CSV, JSON, or PDF format. Scheduled exports run daily at midnight UTC. ``` **Top (default):** Tabs positioned at the top are the most common layout, suitable for primary navigation within a section. This is the second tab in the top-positioned layout. This is the third tab in the top-positioned layout. **Bottom:** You have 12 unread messages. The most recent is from the engineering team regarding deployment schedules. Your last sent message was a project status update to the stakeholders group. You have 3 saved drafts including the quarterly review document and team feedback form. **Start:** General application settings including workspace name, default language, and timezone configuration. Two-factor authentication, session management, and login history for your account. Connect with third-party services like Slack, GitHub, Jira, and Google Workspace. Manage API keys for programmatic access. Create, revoke, and monitor usage of your tokens. **End:** Premium wireless headphones with active noise cancellation, 30-hour battery life, and comfortable over-ear design. 4.7 out of 5 stars based on 2,340 reviews. Customers praise the sound quality and battery life. Free standard shipping (3-5 business days). Express shipping available for $9.99 (1-2 business days). ```yaml - id: pos_top_label type: Markdown properties: content: "**Top (default):**" - id: pos_top_tabs type: Tabs properties: tabPlacement: top tabs: - key: pos_top_first title: First - key: pos_top_second title: Second - key: pos_top_third title: Third slots: pos_top_first: blocks: - id: pos_top_first_content type: Paragraph properties: content: Tabs positioned at the top are the most common layout, suitable for primary navigation within a section. pos_top_second: blocks: - id: pos_top_second_content type: Paragraph properties: content: This is the second tab in the top-positioned layout. pos_top_third: blocks: - id: pos_top_third_content type: Paragraph properties: content: This is the third tab in the top-positioned layout. - id: pos_bottom_label type: Markdown properties: content: "**Bottom:**" - id: pos_bottom_tabs type: Tabs properties: tabPlacement: bottom tabs: - key: pos_bot_inbox title: Inbox - key: pos_bot_sent title: Sent - key: pos_bot_drafts title: Drafts slots: pos_bot_inbox: blocks: - id: pos_bot_inbox_content type: Paragraph properties: content: You have 12 unread messages. The most recent is from the engineering team regarding deployment schedules. pos_bot_sent: blocks: - id: pos_bot_sent_content type: Paragraph properties: content: Your last sent message was a project status update to the stakeholders group. pos_bot_drafts: blocks: - id: pos_bot_drafts_content type: Paragraph properties: content: You have 3 saved drafts including the quarterly review document and team feedback form. - id: pos_start_label type: Markdown properties: content: "**Start:**" - id: pos_left_tabs type: Tabs properties: tabPlacement: start tabs: - key: pos_left_general title: General - key: pos_left_security title: Security - key: pos_left_integrations title: Integrations - key: pos_left_api title: API Keys slots: pos_left_general: blocks: - id: pos_left_general_content type: Paragraph properties: content: General application settings including workspace name, default language, and timezone configuration. pos_left_security: blocks: - id: pos_left_security_content type: Paragraph properties: content: Two-factor authentication, session management, and login history for your account. pos_left_integrations: blocks: - id: pos_left_integrations_content type: Paragraph properties: content: Connect with third-party services like Slack, GitHub, Jira, and Google Workspace. pos_left_api: blocks: - id: pos_left_api_content type: Paragraph properties: content: Manage API keys for programmatic access. Create, revoke, and monitor usage of your tokens. - id: pos_end_label type: Markdown properties: content: "**End:**" - id: pos_right_tabs type: Tabs properties: tabPlacement: end tabs: - key: pos_right_desc title: Description - key: pos_right_reviews title: Reviews - key: pos_right_shipping title: Shipping slots: pos_right_desc: blocks: - id: pos_right_desc_content type: Paragraph properties: content: Premium wireless headphones with active noise cancellation, 30-hour battery life, and comfortable over-ear design. pos_right_reviews: blocks: - id: pos_right_reviews_content type: Paragraph properties: content: 4.7 out of 5 stars based on 2,340 reviews. Customers praise the sound quality and battery life. pos_right_shipping: blocks: - id: pos_right_shipping_content type: Paragraph properties: content: Free standard shipping (3-5 business days). Express shipping available for $9.99 (1-2 business days). ``` Billed every month. Cancel any time. Billed once a year with two months free. ```yaml - id: centered_tabs type: Tabs properties: centered: true tabBarGutter: 40 indicator: size: 24 align: center tabs: - key: centered_monthly title: Monthly - key: centered_yearly title: Yearly slots: centered_monthly: blocks: - id: centered_monthly_content type: Paragraph properties: content: Billed every month. Cancel any time. centered_yearly: blocks: - id: centered_yearly_content type: Paragraph properties: content: Billed once a year with two months free. ``` **Small:** Compact tabs for dense interfaces where screen space is at a premium. Ideal for side panels and secondary navigation. Technical specifications displayed in a compact view. Internal notes and comments in small tab format. **Default:** Standard-sized tabs for most use cases. Provides a balanced reading experience and comfortable click targets. Technical specifications displayed in standard tab format. Internal notes and comments in default tab format. **Large:** Large tabs for prominent navigation areas and primary page sections. Gives a spacious, easy-to-read layout. Technical specifications displayed with large, prominent tab headers. Internal notes and comments with large tab format for emphasis. ```yaml - id: size_small_label type: Markdown properties: content: "**Small:**" - id: size_small_tabs type: Tabs properties: size: small tabs: - key: sm_details title: Details - key: sm_specs title: Specifications - key: sm_notes title: Notes slots: sm_details: blocks: - id: sm_details_content type: Paragraph properties: content: Compact tabs for dense interfaces where screen space is at a premium. Ideal for side panels and secondary navigation. sm_specs: blocks: - id: sm_specs_content type: Paragraph properties: content: Technical specifications displayed in a compact view. sm_notes: blocks: - id: sm_notes_content type: Paragraph properties: content: Internal notes and comments in small tab format. - id: size_default_label type: Markdown properties: content: "**Default:**" - id: size_default_tabs type: Tabs properties: size: default tabs: - key: def_details title: Details - key: def_specs title: Specifications - key: def_notes title: Notes slots: def_details: blocks: - id: def_details_content type: Paragraph properties: content: Standard-sized tabs for most use cases. Provides a balanced reading experience and comfortable click targets. def_specs: blocks: - id: def_specs_content type: Paragraph properties: content: Technical specifications displayed in standard tab format. def_notes: blocks: - id: def_notes_content type: Paragraph properties: content: Internal notes and comments in default tab format. - id: size_large_label type: Markdown properties: content: "**Large:**" - id: size_large_tabs type: Tabs properties: size: large tabs: - key: lg_details title: Details - key: lg_specs title: Specifications - key: lg_notes title: Notes slots: lg_details: blocks: - id: lg_details_content type: Paragraph properties: content: Large tabs for prominent navigation areas and primary page sections. Gives a spacious, easy-to-read layout. lg_specs: blocks: - id: lg_specs_content type: Paragraph properties: content: Technical specifications displayed with large, prominent tab headers. lg_notes: blocks: - id: lg_notes_content type: Paragraph properties: content: Internal notes and comments with large tab format for emphasis. ``` **Small card:** Small card tabs are compact and fit well in tight layouts or secondary navigation areas. Second tab of the small card variant. Third tab of the small card variant. **Default card:** Default card tabs provide a standard raised panel appearance, commonly used for content organization. Second tab of the default card variant. Third tab of the default card variant. **Large card:** Large card tabs have more padding and larger font, making them ideal for primary dashboard navigation. Second tab of the large card variant. Third tab of the large card variant. ```yaml - id: card_sm_label type: Markdown properties: content: "**Small card:**" - id: card_small_tabs type: Tabs properties: tabType: card size: small tabs: - key: csm_tab1 title: Tab 1 - key: csm_tab2 title: Tab 2 - key: csm_tab3 title: Tab 3 slots: csm_tab1: blocks: - id: csm_tab1_content type: Paragraph properties: content: Small card tabs are compact and fit well in tight layouts or secondary navigation areas. csm_tab2: blocks: - id: csm_tab2_content type: Paragraph properties: content: Second tab of the small card variant. csm_tab3: blocks: - id: csm_tab3_content type: Paragraph properties: content: Third tab of the small card variant. - id: card_def_label type: Markdown properties: content: "**Default card:**" - id: card_default_tabs type: Tabs properties: tabType: card size: default tabs: - key: cdf_tab1 title: Tab 1 - key: cdf_tab2 title: Tab 2 - key: cdf_tab3 title: Tab 3 slots: cdf_tab1: blocks: - id: cdf_tab1_content type: Paragraph properties: content: Default card tabs provide a standard raised panel appearance, commonly used for content organization. cdf_tab2: blocks: - id: cdf_tab2_content type: Paragraph properties: content: Second tab of the default card variant. cdf_tab3: blocks: - id: cdf_tab3_content type: Paragraph properties: content: Third tab of the default card variant. - id: card_lg_label type: Markdown properties: content: "**Large card:**" - id: card_large_tabs type: Tabs properties: tabType: card size: large tabs: - key: clg_tab1 title: Tab 1 - key: clg_tab2 title: Tab 2 - key: clg_tab3 title: Tab 3 slots: clg_tab1: blocks: - id: clg_tab1_content type: Paragraph properties: content: Large card tabs have more padding and larger font, making them ideal for primary dashboard navigation. clg_tab2: blocks: - id: clg_tab2_content type: Paragraph properties: content: Second tab of the large card variant. clg_tab3: blocks: - id: clg_tab3_content type: Paragraph properties: content: Third tab of the large card variant. ``` Welcome to the dashboard. Here you can view recent activity, quick statistics, and announcements from your team. Application configuration, theme settings, and advanced options. Changes take effect immediately. Monthly target progress at 85%. On track for completion by end of quarter. ```yaml - id: icon_tabs type: Tabs properties: tabs: - key: icon_home title: Home icon: home - key: icon_users title: Users icon: users - key: icon_settings title: Settings icon: settings - key: icon_chart title: Analytics icon: chart slots: icon_home: blocks: - id: icon_home_content type: Paragraph properties: content: Welcome to the dashboard. Here you can view recent activity, quick statistics, and announcements from your team. icon_users: blocks: - id: icon_users_desc type: Descriptions properties: bordered: true size: small items: - label: Total Members value: "24" - label: Active Now value: "8" - label: Pending Invites value: "3" icon_settings: blocks: - id: icon_settings_content type: Paragraph properties: content: Application configuration, theme settings, and advanced options. Changes take effect immediately. icon_chart: blocks: - id: icon_chart_progress type: Progress properties: percent: 85 status: active - id: icon_chart_note type: Paragraph properties: content: Monthly target progress at 85%. On track for completion by end of quarter. ``` > 3 Deprecation Warnings ```yaml - id: custom_icon_tabs type: Tabs properties: tabs: - key: ci_success title: Passed icon: name: check-circle color: "#52c41a" - key: ci_warning title: Warnings icon: name: warning color: "#faad14" - key: ci_error title: Failed icon: name: close-circle color: "#ff4d4f" slots: ci_success: blocks: - id: ci_success_result type: Result properties: status: success title: All Tests Passed subTitle: 142 tests completed successfully in 4.2 seconds. ci_warning: blocks: - id: ci_warning_alert type: Alert properties: message: 3 Deprecation Warnings description: Some test suites use deprecated assertion methods. Please update to the latest API. type: warning showIcon: true ci_error: blocks: - id: ci_error_result type: Result properties: status: error title: 2 Tests Failed subTitle: Check the error log below for details on the failing assertions. ``` This tab is accessible and contains normal content. Users can freely navigate to this section. This content is not yet available. The tab is disabled to indicate an upcoming feature. > Beta Feature This section requires an upgraded subscription plan to access. ```yaml - id: disabled_tabs type: Tabs properties: tabs: - key: dis_active title: Active - key: dis_coming_soon title: Coming Soon disabled: true - key: dis_beta title: Beta - key: dis_locked title: Locked disabled: true slots: dis_active: blocks: - id: dis_active_content type: Paragraph properties: content: This tab is accessible and contains normal content. Users can freely navigate to this section. dis_coming_soon: blocks: - id: dis_coming_soon_content type: Paragraph properties: content: This content is not yet available. The tab is disabled to indicate an upcoming feature. dis_beta: blocks: - id: dis_beta_alert type: Alert properties: message: Beta Feature description: This feature is currently in beta. Some functionality may be incomplete or change without notice. type: info showIcon: true dis_locked: blocks: - id: dis_locked_content type: Paragraph properties: content: This section requires an upgraded subscription plan to access. ``` This is the first tab. It is not active by default because defaultActiveKey is set to the second tab. > This tab is active by default This is the third tab with regular content. ```yaml - id: default_key_tabs type: Tabs properties: defaultActiveKey: dk_second tabs: - key: dk_first title: First - key: dk_second title: Second (Default) - key: dk_third title: Third slots: dk_first: blocks: - id: dk_first_content type: Paragraph properties: content: This is the first tab. It is not active by default because defaultActiveKey is set to the second tab. dk_second: blocks: - id: dk_second_content type: Alert properties: message: This tab is active by default description: The defaultActiveKey property is set to "dk_second", so this tab is shown when the component first renders. type: success showIcon: true dk_third: blocks: - id: dk_third_content type: Paragraph properties: content: This is the third tab with regular content. ``` Browse your uploaded documents. Supported formats include PDF, DOCX, TXT, and Markdown files. Use the Upload button in the tab bar to add new files. View your image gallery. Supports JPEG, PNG, SVG, and WebP formats up to 10MB each. Your video library. Supports MP4, WebM, and MOV formats. Maximum file size is 500MB. ```yaml - id: extra_tabs type: Tabs properties: extraAreaKey: extra_area tabs: - key: extra_documents title: Documents - key: extra_images title: Images - key: extra_videos title: Videos slots: extra_area: blocks: - id: extra_area_button type: Button layout: flex: 0 0 auto properties: title: Upload icon: upload color: primary variant: solid size: small extra_documents: blocks: - id: extra_documents_content type: Paragraph properties: content: Browse your uploaded documents. Supported formats include PDF, DOCX, TXT, and Markdown files. Use the Upload button in the tab bar to add new files. extra_images: blocks: - id: extra_images_content type: Paragraph properties: content: View your image gallery. Supports JPEG, PNG, SVG, and WebP formats up to 10MB each. extra_videos: blocks: - id: extra_videos_content type: Paragraph properties: content: Your video library. Supports MP4, WebM, and MOV formats. Maximum file size is 500MB. ``` Quarterly target progress > 2 open positions Recent Activity 10:42 AM - Sarah merged pull request #247 into main branch. 09:15 AM - James deployed v2.4.1 to production environment. Yesterday - Alex created 3 new issues in the backlog tracker. ```yaml - id: rich_tabs type: Tabs properties: tabs: - key: rich_dashboard title: Dashboard icon: Gauge - key: rich_team title: Team icon: users - key: rich_activity title: Activity icon: history slots: rich_dashboard: blocks: - id: rich_dash_stats type: Box layout: gap: 16 blocks: - id: rich_stat_1 type: Statistic layout: flex: 1 1 0 properties: title: Total Revenue value: 126560 prefix: $ precision: 2 - id: rich_stat_2 type: Statistic layout: flex: 1 1 0 properties: title: Active Users value: 8846 - id: rich_stat_3 type: Statistic layout: flex: 1 1 0 properties: title: Completion Rate value: 93.2 suffix: "%" - id: rich_dash_divider type: Divider - id: rich_dash_progress_label type: Paragraph properties: content: Quarterly target progress - id: rich_dash_progress type: Progress properties: percent: 78 status: active rich_team: blocks: - id: rich_team_desc type: Descriptions properties: bordered: true column: 2 items: - label: Engineering value: 12 members - label: Design value: 5 members - label: Product value: 4 members - label: QA value: 3 members - id: rich_team_divider type: Divider - id: rich_team_note type: Alert properties: message: 2 open positions description: Senior Backend Engineer and Product Designer roles are currently hiring. type: info showIcon: true rich_activity: blocks: - id: rich_activity_title type: Title properties: content: Recent Activity level: 5 - id: rich_activity_item_1 type: Paragraph properties: content: "10:42 AM - Sarah merged pull request #247 into main branch." - id: rich_activity_divider_1 type: Divider properties: dashed: true - id: rich_activity_item_2 type: Paragraph properties: content: 09:15 AM - James deployed v2.4.1 to production environment. - id: rich_activity_divider_2 type: Divider properties: dashed: true - id: rich_activity_item_3 type: Paragraph properties: content: Yesterday - Alex created 3 new issues in the backlog tracker. ``` January report - Q1 kickoff with team planning sessions and annual goal setting. Revenue target set at $1.2M. February report - Sprint planning completed. 3 new team members onboarded. Infrastructure migration started. March report - Q1 close. Revenue hit 108% of target. Product launch scheduled for April. April report - Product v2.0 launched successfully. 500 new signups in the first week. May report - Customer feedback collected and prioritized. Feature roadmap updated for Q3. June report - Q2 close. Revenue exceeded targets by 15%. Team offsite and retrospective completed. July report - H2 planning underway. Engineering team focused on performance optimizations. August report - Beta testing for v3.0 started with 50 partner organizations. September report - Q3 close. Beta feedback incorporated. Security audit completed with no critical findings. October report - v3.0 released to general availability. Conference presentation at TechSummit. November report - Black Friday promotion drove 200% increase in signups. Support team scaled up. December report - Year-end review. Annual revenue target exceeded by 22%. Holiday code freeze in effect. ```yaml - id: scroll_tabs type: Tabs properties: tabs: - key: scroll_jan title: January - key: scroll_feb title: February - key: scroll_mar title: March - key: scroll_apr title: April - key: scroll_may title: May - key: scroll_jun title: June - key: scroll_jul title: July - key: scroll_aug title: August - key: scroll_sep title: September - key: scroll_oct title: October - key: scroll_nov title: November - key: scroll_dec title: December slots: scroll_jan: blocks: - id: scroll_jan_content type: Paragraph properties: content: January report - Q1 kickoff with team planning sessions and annual goal setting. Revenue target set at $1.2M. scroll_feb: blocks: - id: scroll_feb_content type: Paragraph properties: content: February report - Sprint planning completed. 3 new team members onboarded. Infrastructure migration started. scroll_mar: blocks: - id: scroll_mar_content type: Paragraph properties: content: March report - Q1 close. Revenue hit 108% of target. Product launch scheduled for April. scroll_apr: blocks: - id: scroll_apr_content type: Paragraph properties: content: April report - Product v2.0 launched successfully. 500 new signups in the first week. scroll_may: blocks: - id: scroll_may_content type: Paragraph properties: content: May report - Customer feedback collected and prioritized. Feature roadmap updated for Q3. scroll_jun: blocks: - id: scroll_jun_content type: Paragraph properties: content: June report - Q2 close. Revenue exceeded targets by 15%. Team offsite and retrospective completed. scroll_jul: blocks: - id: scroll_jul_content type: Paragraph properties: content: July report - H2 planning underway. Engineering team focused on performance optimizations. scroll_aug: blocks: - id: scroll_aug_content type: Paragraph properties: content: August report - Beta testing for v3.0 started with 50 partner organizations. scroll_sep: blocks: - id: scroll_sep_content type: Paragraph properties: content: September report - Q3 close. Beta feedback incorporated. Security audit completed with no critical findings. scroll_oct: blocks: - id: scroll_oct_content type: Paragraph properties: content: October report - v3.0 released to general availability. Conference presentation at TechSummit. scroll_nov: blocks: - id: scroll_nov_content type: Paragraph properties: content: November report - Black Friday promotion drove 200% increase in signups. Support team scaled up. scroll_dec: blocks: - id: scroll_dec_content type: Paragraph properties: content: December report - Year-end review. Annual revenue target exceeded by 22%. Holiday code freeze in effect. ``` Configure which events trigger notifications. Choose between email, push, or in-app delivery for each category. Customize your visual preferences including theme mode, accent color, font size, and compact layout options. > Privacy Settings ```yaml - id: left_card_tabs type: Tabs properties: tabType: card tabPlacement: start tabs: - key: lc_account title: Account - key: lc_notifications title: Notifications - key: lc_appearance title: Appearance - key: lc_privacy title: Privacy slots: lc_account: blocks: - id: lc_account_desc type: Descriptions properties: bordered: true column: 1 items: - label: Username value: johndoe - label: Email value: john@example.com - label: Role value: Administrator - label: Member Since value: March 2024 lc_notifications: blocks: - id: lc_notif_content type: Paragraph properties: content: Configure which events trigger notifications. Choose between email, push, or in-app delivery for each category. lc_appearance: blocks: - id: lc_appearance_content type: Paragraph properties: content: Customize your visual preferences including theme mode, accent color, font size, and compact layout options. lc_privacy: blocks: - id: lc_privacy_alert type: Alert properties: message: Privacy Settings description: Your profile is currently set to public. Other workspace members can see your name, avatar, and activity status. type: warning showIcon: true ``` Tab content switches instantly without any transition animation. This can be useful for performance-sensitive interfaces or when animation feels distracting. The animated property is set to false, disabling both the ink bar animation and the content transition. Compare this with the other tab examples on this page to see the difference in transition behavior. ```yaml - id: no_anim_tabs type: Tabs properties: animated: false tabs: - key: na_tab1 title: No Animation 1 - key: na_tab2 title: No Animation 2 - key: na_tab3 title: No Animation 3 slots: na_tab1: blocks: - id: na_tab1_content type: Paragraph properties: content: Tab content switches instantly without any transition animation. This can be useful for performance-sensitive interfaces or when animation feels distracting. na_tab2: blocks: - id: na_tab2_content type: Paragraph properties: content: The animated property is set to false, disabling both the ink bar animation and the content transition. na_tab3: blocks: - id: na_tab3_content type: Paragraph properties: content: Compare this with the other tab examples on this page to see the difference in transition behavior. ``` This example demonstrates CSS customization using the style property. The element has a light gray background with rounded corners, and the tab bar has extra bottom margin. The tabBar, tabPane, and element CSS keys can be individually targeted. Each accepts standard CSS properties as an object. Use style.element to control the outer wrapper, style.tabBar for the navigation strip, and style.tabPane for each content panel. Dark-themed tabs using CSS overrides. Suitable for code editors, terminal views, or developer tool interfaces. Output panel with matching dark theme. The element and tabBar CSS keys are styled together for a cohesive look. Log viewer panel. Monospaced fonts and dark backgrounds reduce eye strain during extended debugging sessions. ```yaml - id: css_tabs type: Tabs properties: tabs: - key: css_overview title: Overview - key: css_details title: Details - key: css_contact title: Contact style: .element: padding: 16px borderRadius: 8px .tabBar: marginBottom: 24px .tabPane: padding: 12px slots: css_overview: blocks: - id: css_overview_content type: Paragraph properties: content: This example demonstrates CSS customization using the style property. The element has a light gray background with rounded corners, and the tab bar has extra bottom margin. css_details: blocks: - id: css_details_content type: Paragraph properties: content: The tabBar, tabPane, and element CSS keys can be individually targeted. Each accepts standard CSS properties as an object. css_contact: blocks: - id: css_contact_content type: Paragraph properties: content: Use style.element to control the outer wrapper, style.tabBar for the navigation strip, and style.tabPane for each content panel. - id: css_dark_tabs type: Tabs properties: tabs: - key: cssd_code title: Code - key: cssd_output title: Output - key: cssd_logs title: Logs style: .element: background: "#1e1e2e" padding: 16px borderRadius: 8px color: "#cdd6f4" .tabBar: color: "#cdd6f4" slots: cssd_code: blocks: - id: cssd_code_content type: Paragraph properties: content: Dark-themed tabs using CSS overrides. Suitable for code editors, terminal views, or developer tool interfaces. style: .element: color: "#cdd6f4" cssd_output: blocks: - id: cssd_output_content type: Paragraph properties: content: Output panel with matching dark theme. The element and tabBar CSS keys are styled together for a cohesive look. style: .element: color: "#cdd6f4" cssd_logs: blocks: - id: cssd_logs_content type: Paragraph properties: content: Log viewer panel. Monospaced fonts and dark backgrounds reduce eye strain during extended debugging sessions. style: .element: color: "#cdd6f4" fontFamily: monospace ``` **Custom ink bar and text colors:** The ink bar and selected tab text are green, overriding the default blue. Use inkBarColor, itemSelectedColor, itemHoverColor, and itemActiveColor tokens. The garden planner helps you organize plant spacing, sunlight needs, and watering schedules throughout the growing season. Track your harvest yields by week and compare against previous seasons to optimize your gardening strategy. **Custom card tab appearance:** Card tabs with a warm amber background and custom height. The cardBg, cardHeight, cardPadding, and cardGutter tokens control the card-type appearance. Your saved favorite recipes. Browse your curated collection of breakfast, lunch, and dinner ideas. Recently viewed recipes from the past 30 days. Quick access to dishes you have been exploring. **Custom spacing and font size:** Wider spacing between tabs using horizontalItemGutter token set to 48px (default is 32px). The title font size is increased to 16px with a purple accent color. Chapter listing with spaced-out tab headers. The increased gutter gives each tab more visual breathing room. Supplementary materials and reference tables. The larger font size makes the tab labels easier to read at a glance. **Custom vertical tab tokens:** Vertical tabs with custom padding and pink accent. The verticalItemPadding and verticalItemMargin tokens control spacing in left/right tab layouts. Development phase with code reviews, testing, and continuous integration. The pink accent differentiates this tab set from others on the page. Deployment pipeline configuration. Preview environments, staging, and production release management. ```yaml - id: theme_custom_colors type: Markdown properties: content: "**Custom ink bar and text colors:**" - id: theme_color_tabs type: Tabs properties: theme: inkBarColor: "#52c41a" itemSelectedColor: "#52c41a" itemHoverColor: "#73d13d" itemActiveColor: "#389e0d" tabs: - key: tc_plants title: Plants - key: tc_garden title: Garden - key: tc_harvest title: Harvest slots: tc_plants: blocks: - id: tc_plants_content type: Paragraph properties: content: The ink bar and selected tab text are green, overriding the default blue. Use inkBarColor, itemSelectedColor, itemHoverColor, and itemActiveColor tokens. tc_garden: blocks: - id: tc_garden_content type: Paragraph properties: content: The garden planner helps you organize plant spacing, sunlight needs, and watering schedules throughout the growing season. tc_harvest: blocks: - id: tc_harvest_content type: Paragraph properties: content: Track your harvest yields by week and compare against previous seasons to optimize your gardening strategy. - id: theme_card_label type: Markdown properties: content: "**Custom card tab appearance:**" - id: theme_card_tabs type: Tabs properties: tabType: card theme: cardHeight: 48 cardPadding: 12px 24px cardGutter: 4 itemSelectedColor: "#d46b08" itemColor: "#874d00" tabs: - key: tcd_recipes title: Recipes - key: tcd_favorites title: Favorites - key: tcd_recent title: Recent slots: tcd_recipes: blocks: - id: tcd_recipes_content type: Paragraph properties: content: Card tabs with a warm amber background and custom height. The cardBg, cardHeight, cardPadding, and cardGutter tokens control the card-type appearance. tcd_favorites: blocks: - id: tcd_favorites_content type: Paragraph properties: content: Your saved favorite recipes. Browse your curated collection of breakfast, lunch, and dinner ideas. tcd_recent: blocks: - id: tcd_recent_content type: Paragraph properties: content: Recently viewed recipes from the past 30 days. Quick access to dishes you have been exploring. - id: theme_spacing_label type: Markdown properties: content: "**Custom spacing and font size:**" - id: theme_spacing_tabs type: Tabs properties: theme: horizontalItemGutter: 48 horizontalItemPadding: 16px 0 titleFontSize: 16 inkBarColor: "#722ed1" itemSelectedColor: "#722ed1" itemHoverColor: "#9254de" tabs: - key: ts_intro title: Introduction - key: ts_chapters title: Chapters - key: ts_appendix title: Appendix slots: ts_intro: blocks: - id: ts_intro_content type: Paragraph properties: content: Wider spacing between tabs using horizontalItemGutter token set to 48px (default is 32px). The title font size is increased to 16px with a purple accent color. ts_chapters: blocks: - id: ts_chapters_content type: Paragraph properties: content: Chapter listing with spaced-out tab headers. The increased gutter gives each tab more visual breathing room. ts_appendix: blocks: - id: ts_appendix_content type: Paragraph properties: content: Supplementary materials and reference tables. The larger font size makes the tab labels easier to read at a glance. - id: theme_vertical_label type: Markdown properties: content: "**Custom vertical tab tokens:**" - id: theme_vertical_tabs type: Tabs properties: tabPlacement: start theme: verticalItemPadding: 12px 32px verticalItemMargin: 8px 0 0 0 inkBarColor: "#eb2f96" itemSelectedColor: "#eb2f96" itemHoverColor: "#f759ab" titleFontSize: 15 tabs: - key: tv_design title: Design - key: tv_develop title: Develop - key: tv_deploy title: Deploy slots: tv_design: blocks: - id: tv_design_content type: Paragraph properties: content: Vertical tabs with custom padding and pink accent. The verticalItemPadding and verticalItemMargin tokens control spacing in left/right tab layouts. tv_develop: blocks: - id: tv_develop_content type: Paragraph properties: content: Development phase with code reviews, testing, and continuous integration. The pink accent differentiates this tab set from others on the page. tv_deploy: blocks: - id: tv_deploy_content type: Paragraph properties: content: Deployment pipeline configuration. Preview environments, staging, and production release management. ``` Tab titles support HTML content. The first tab title uses bold text via the b tag. This is useful for emphasizing key tabs. The second tab simulates a notification badge using a superscript element with red color. A lightweight alternative to separate badge components. The third tab uses an inline span with green color to indicate active status directly in the tab title. ```yaml - id: html_tabs type: Tabs properties: tabs: - key: html_bold title: Important - key: html_badge title: 'Messages 3' - key: html_colored title: 'Active' slots: html_bold: blocks: - id: html_bold_content type: Paragraph properties: content: Tab titles support HTML content. The first tab title uses bold text via the b tag. This is useful for emphasizing key tabs. html_badge: blocks: - id: html_badge_content type: Paragraph properties: content: The second tab simulates a notification badge using a superscript element with red color. A lightweight alternative to separate badge components. html_colored: blocks: - id: html_colored_content type: Paragraph properties: content: The third tab uses an inline span with green color to indicate active status directly in the tab title. ``` Press Cmd+1 (Mac) or Ctrl+1 (Windows) to jump to this tab. Press Cmd+2 (Mac) or Ctrl+2 (Windows) to jump to this tab. Press Cmd+3 (Mac) or Ctrl+3 (Windows) to jump to this tab. ```yaml - id: shortcut_tabs type: Tabs properties: tabs: - key: overview title: Overview shortcut: mod+1 - key: details title: Details shortcut: mod+2 - key: history title: History shortcut: mod+3 events: onChange: - id: shortcut_tab_msg type: DisplayMessage params: content: _string.concat: - "Switched to: " - _event: activeKey status: info slots: overview: blocks: - id: shortcut_overview_content type: Paragraph properties: content: Press Cmd+1 (Mac) or Ctrl+1 (Windows) to jump to this tab. details: blocks: - id: shortcut_details_content type: Paragraph properties: content: Press Cmd+2 (Mac) or Ctrl+2 (Windows) to jump to this tab. history: blocks: - id: shortcut_history_content type: Paragraph properties: content: Press Cmd+3 (Mac) or Ctrl+3 (Windows) to jump to this tab. ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `animated` | boolean | `true` | Whether to change tabs with animation. Only works while tabPlacement is top or bottom. | | `centered` | boolean | `false` | Center the tabs in the tab bar. | | `defaultActiveKey` | string | - | Initial active TabPane's key, if activeKey is not set. | | `destroyOnHidden` | boolean | `false` | Unmount the content of inactive tabs. Blocks in a hidden tab keep their state values. | | `indicator` | object | - | Size and alignment of the active tab indicator (ink bar). | | `indicator.size` | number | - | Length of the indicator in pixels. Defaults to the width of the tab. | | `indicator.align` | string | `"center"` | Alignment of the indicator along the tab when size is set. Enum: `start`, `center`, `end`. | | `tabBarGutter` | number | - | Gap between tabs in pixels. | | `size` | string | `"default"` | Size of the tabs. Enum: `default`, `small`, `large`. | | `tabPlacement` | string | `"top"` | Position of the tabs. Enum: `top`, `end`, `bottom`, `start`. | | `tabType` | string | `"line"` | Type of tabs. Enum: `line`, `card`. | | `tabs` | array | - | | | `tabs.$.title` | string | - | Title of the tab - supports html. | | `tabs.$.key` | string | - | Area key of the tab. | | `tabs.$.eventName` | string | - | Block-level event name to trigger when this tab becomes active, in addition to onChange. Declare an event with this name under `events:`. The event receives `{ key }` of the now-active tab. | | `tabs.$.disabled` | boolean | `false` | Disable the tab if true. | | `tabs.$.icon` | string \| object | - | Icon name (a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`) or properties of an Icon block to customize icon to show in tab title. | | `tabs.$.icon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `tabs.$.icon.color` | string | - | Icon color. | | `tabs.$.icon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `tabs.$.icon.rotate` | number | - | Number of degrees to rotate the icon. | | `tabs.$.icon.spin` | boolean | - | Continuously spin the icon with animation. | | `tabs.$.icon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `tabs.$.icon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `tabs.$.icon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `tabs.$.icon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `tabs.$.shortcut` | string | - | Keyboard shortcut to switch to this tab. Use "mod" for Cmd/Ctrl. | | `extraAreaKey` | string | - | Area key for the extra area blocks. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design tabs tokens](https://ant.design/components/tabs#design-token). | | `theme.cardBg` | string | `"rgba(0, 0, 0, 0.02)"` | Background color of card-type tab. | | `theme.cardGutter` | number | `2` | Gap between card-type tabs. | | `theme.cardHeight` | number | `40` | Height of card-type tab. | | `theme.cardHeightSM` | number | `32` | Height of small card-type tab. | | `theme.cardHeightLG` | number | `48` | Height of large card-type tab. | | `theme.cardPadding` | string | `"8px 16px"` | Padding of card-type tab. | | `theme.cardPaddingLG` | string | `"11px 16px"` | Padding of large card-type tab. | | `theme.cardPaddingSM` | string | `"4px 8px"` | Padding of small card-type tab. | | `theme.horizontalItemGutter` | number | `32` | Gap between horizontal tabs. | | `theme.horizontalItemMargin` | string | - | Horizontal margin of tab item. | | `theme.horizontalItemPadding` | string | `"12px 0"` | Padding of horizontal tab item. | | `theme.horizontalItemPaddingLG` | string | `"16px 0"` | Padding of large horizontal tab item. | | `theme.horizontalItemPaddingSM` | string | `"8px 0"` | Padding of small horizontal tab item. | | `theme.horizontalMargin` | string | `"0 0 16px 0"` | Margin of horizontal tab bar. | | `theme.inkBarColor` | string | `"#1677ff"` | Color of the active tab indicator bar. | | `theme.itemActiveColor` | string | `"#0958d9"` | Text color of active (mousedown) tab. | | `theme.itemColor` | string | `"rgba(0, 0, 0, 0.88)"` | Text color of tab. | | `theme.itemHoverColor` | string | `"#4096ff"` | Text color of hovered tab. | | `theme.itemSelectedColor` | string | `"#1677ff"` | Text color of selected tab. | | `theme.titleFontSize` | number | `14` | Font size of tab title. | | `theme.titleFontSizeLG` | number | `16` | Font size of large tab title. | | `theme.titleFontSizeSM` | number | `14` | Font size of small tab title. | | `theme.verticalItemMargin` | string | `"16px 0 0 0"` | Margin of vertical tab item. | | `theme.verticalItemPadding` | string | `"8px 24px"` | Padding of vertical tab item. | | `theme.zIndexPopup` | number | `1050` | Z-index of tab dropdown popup. | | Event | Event Data | Description | | --- | --- | --- | | `onChange` | `{ activeKey }` | Trigger action on any tab change. | | `onTabSelect` | `{ key }` | Documentation reference — not a fixed event name. When a tab becomes active, the `eventName` string declared on that `tabs[]` entry is triggered (in addition to onChange). Declare your named events under `events:` (e.g. `onProfileTab`, `onBillingTab`). | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Tabs element. | | `/icon` | The icon in the Tabs. | | `/tabBar` | The Tabs tab bar. | | `/tabPane` | The Tabs tab pane. | | `/inkBar` | The Tabs ink bar. | | `/item` | Each tab in the tab bar. | | `/popup` | The dropdown that lists tabs that overflow the tab bar. | Slot keys are user-defined in your config and resolved at build time — not generated at runtime. The block typically pairs slots with an array property (`tabs`, `panels`, `slides`) listed in the Properties table; see the examples above for the expected shape. --- url: https://docs.lowdefy.com/md/container-blocks/tooltip.md --- # Tooltip Tooltip popup appearing on hover or focus. ```yaml - id: tooltip_basic type: Tooltip layout: flex: 0 0 auto properties: title: This is a tooltip blocks: - id: tooltip_basic_btn type: Button layout: flex: 0 0 auto properties: title: Hover me color: primary variant: solid - id: tooltip_html type: Tooltip layout: flex: 0 0 auto properties: title: Bold and italic text blocks: - id: tooltip_html_btn type: Button layout: flex: 0 0 auto properties: title: HTML content color: primary variant: outlined - id: tooltip_long type: Tooltip layout: flex: 0 0 auto properties: title: This is a longer tooltip message that demonstrates how the tooltip wraps text when the content exceeds the default maximum width. blocks: - id: tooltip_long_btn type: Button layout: flex: 0 0 auto properties: title: Long text color: default variant: outlined ``` **Top placements:** **Bottom placements:** **Left placements:** **Right placements:** ```yaml - id: tooltip_placement_label_top type: Markdown properties: content: "**Top placements:**" - id: tooltip_placement_top_row type: Box layout: gap: 8 justify: center blocks: - id: tooltip_topLeft type: Tooltip layout: flex: 0 0 auto properties: title: topLeft placement: topLeft blocks: - id: tooltip_topLeft_btn type: Button layout: flex: 0 0 auto properties: title: topLeft color: default variant: outlined size: small - id: tooltip_top type: Tooltip layout: flex: 0 0 auto properties: title: top placement: top blocks: - id: tooltip_top_btn type: Button layout: flex: 0 0 auto properties: title: top color: primary variant: solid size: small - id: tooltip_topRight type: Tooltip layout: flex: 0 0 auto properties: title: topRight placement: topRight blocks: - id: tooltip_topRight_btn type: Button layout: flex: 0 0 auto properties: title: topRight color: default variant: outlined size: small - id: tooltip_placement_label_bottom type: Markdown properties: content: "**Bottom placements:**" - id: tooltip_placement_bottom_row type: Box layout: gap: 8 justify: center blocks: - id: tooltip_bottomLeft type: Tooltip layout: flex: 0 0 auto properties: title: bottomLeft placement: bottomLeft blocks: - id: tooltip_bottomLeft_btn type: Button layout: flex: 0 0 auto properties: title: bottomLeft color: default variant: outlined size: small - id: tooltip_bottom type: Tooltip layout: flex: 0 0 auto properties: title: bottom placement: bottom blocks: - id: tooltip_bottom_btn type: Button layout: flex: 0 0 auto properties: title: bottom color: primary variant: solid size: small - id: tooltip_bottomRight type: Tooltip layout: flex: 0 0 auto properties: title: bottomRight placement: bottomRight blocks: - id: tooltip_bottomRight_btn type: Button layout: flex: 0 0 auto properties: title: bottomRight color: default variant: outlined size: small - id: tooltip_placement_label_left type: Markdown properties: content: "**Left placements:**" - id: tooltip_placement_left_row type: Box layout: gap: 8 blocks: - id: tooltip_left type: Tooltip layout: flex: 0 0 auto properties: title: left placement: left blocks: - id: tooltip_left_btn type: Button layout: flex: 0 0 auto properties: title: left color: primary variant: solid size: small - id: tooltip_leftTop type: Tooltip layout: flex: 0 0 auto properties: title: leftTop placement: leftTop blocks: - id: tooltip_leftTop_btn type: Button layout: flex: 0 0 auto properties: title: leftTop color: default variant: outlined size: small - id: tooltip_leftBottom type: Tooltip layout: flex: 0 0 auto properties: title: leftBottom placement: leftBottom blocks: - id: tooltip_leftBottom_btn type: Button layout: flex: 0 0 auto properties: title: leftBottom color: default variant: outlined size: small - id: tooltip_placement_label_right type: Markdown properties: content: "**Right placements:**" - id: tooltip_placement_right_row type: Box layout: gap: 8 blocks: - id: tooltip_right type: Tooltip layout: flex: 0 0 auto properties: title: right placement: right blocks: - id: tooltip_right_btn type: Button layout: flex: 0 0 auto properties: title: right color: primary variant: solid size: small - id: tooltip_rightTop type: Tooltip layout: flex: 0 0 auto properties: title: rightTop placement: rightTop blocks: - id: tooltip_rightTop_btn type: Button layout: flex: 0 0 auto properties: title: rightTop color: default variant: outlined size: small - id: tooltip_rightBottom type: Tooltip layout: flex: 0 0 auto properties: title: rightBottom placement: rightBottom blocks: - id: tooltip_rightBottom_btn type: Button layout: flex: 0 0 auto properties: title: rightBottom color: default variant: outlined size: small ``` ```yaml - id: tooltip_trigger_hover type: Tooltip layout: flex: 0 0 auto properties: title: Triggered by hover trigger: hover blocks: - id: tooltip_trigger_hover_btn type: Button layout: flex: 0 0 auto properties: title: Hover (default) color: primary variant: solid - id: tooltip_trigger_click type: Tooltip layout: flex: 0 0 auto properties: title: Triggered by click trigger: click blocks: - id: tooltip_trigger_click_btn type: Button layout: flex: 0 0 auto properties: title: Click color: primary variant: outlined - id: tooltip_trigger_focus type: Tooltip layout: flex: 0 0 auto properties: title: Triggered by focus trigger: focus blocks: - id: tooltip_trigger_focus_btn type: Button layout: flex: 0 0 auto properties: title: Focus (tab to me) color: primary variant: dashed ``` ```yaml - id: tooltip_color_row type: Box layout: gap: 8 blocks: - id: tooltip_color_blue type: Tooltip layout: flex: 0 0 auto properties: title: blue color: blue blocks: - id: tooltip_color_blue_tag type: Tag properties: title: blue color: blue - id: tooltip_color_purple type: Tooltip layout: flex: 0 0 auto properties: title: purple color: purple blocks: - id: tooltip_color_purple_tag type: Tag properties: title: purple color: purple - id: tooltip_color_cyan type: Tooltip layout: flex: 0 0 auto properties: title: cyan color: cyan blocks: - id: tooltip_color_cyan_tag type: Tag properties: title: cyan color: cyan - id: tooltip_color_green type: Tooltip layout: flex: 0 0 auto properties: title: green color: green blocks: - id: tooltip_color_green_tag type: Tag properties: title: green color: green - id: tooltip_color_red type: Tooltip layout: flex: 0 0 auto properties: title: red color: red blocks: - id: tooltip_color_red_tag type: Tag properties: title: red color: red - id: tooltip_color_custom_row type: Box layout: gap: 8 blocks: - id: tooltip_color_hex1 type: Tooltip layout: flex: 0 0 auto properties: title: "Custom color: #ff6b6b" color: "#ff6b6b" blocks: - id: tooltip_color_hex1_tag type: Tag properties: title: "#ff6b6b" - id: tooltip_color_hex2 type: Tooltip layout: flex: 0 0 auto properties: title: "Custom color: #20c997" color: "#20c997" blocks: - id: tooltip_color_hex2_tag type: Tag properties: title: "#20c997" - id: tooltip_color_hex3 type: Tooltip layout: flex: 0 0 auto properties: title: "Custom color: #4c6ef5" color: "#4c6ef5" blocks: - id: tooltip_color_hex3_tag type: Tag properties: title: "#4c6ef5" ``` ```yaml - id: tooltip_arrow_default type: Tooltip layout: flex: 0 0 auto properties: title: Arrow at edge (default) placement: topLeft arrowPointAtCenter: false blocks: - id: tooltip_arrow_default_btn type: Button layout: flex: 0 0 auto properties: title: Default arrow color: default variant: outlined - id: tooltip_arrow_center type: Tooltip layout: flex: 0 0 auto properties: title: Arrow at center placement: topLeft arrowPointAtCenter: true blocks: - id: tooltip_arrow_center_btn type: Button layout: flex: 0 0 auto properties: title: Arrow at center color: primary variant: solid ``` ```yaml - id: tooltip_overflow_on type: Tooltip layout: flex: 0 0 auto properties: title: This tooltip auto-adjusts when near the viewport edge autoAdjustOverflow: true placement: top blocks: - id: tooltip_overflow_on_btn type: Button layout: flex: 0 0 auto properties: title: Auto adjust (default) color: primary variant: solid - id: tooltip_overflow_off type: Tooltip layout: flex: 0 0 auto properties: title: This tooltip stays in position even near viewport edges autoAdjustOverflow: false placement: top blocks: - id: tooltip_overflow_off_btn type: Button layout: flex: 0 0 auto properties: title: No auto adjust color: default variant: outlined ``` ```yaml - id: tooltip_delay_fast type: Tooltip layout: flex: 0 0 auto properties: title: Instant tooltip mouseEnterDelay: 0 mouseLeaveDelay: 0 blocks: - id: tooltip_delay_fast_btn type: Button layout: flex: 0 0 auto properties: title: No delay color: primary variant: solid - id: tooltip_delay_slow_enter type: Tooltip layout: flex: 0 0 auto properties: title: This tooltip has a 1 second enter delay mouseEnterDelay: 1 blocks: - id: tooltip_delay_slow_enter_btn type: Button layout: flex: 0 0 auto properties: title: 1s enter delay color: primary variant: outlined - id: tooltip_delay_slow_leave type: Tooltip layout: flex: 0 0 auto properties: title: This tooltip stays 2 seconds after mouse leaves mouseLeaveDelay: 2 blocks: - id: tooltip_delay_slow_leave_btn type: Button layout: flex: 0 0 auto properties: title: 2s leave delay color: primary variant: dashed ``` ```yaml - id: tooltip_default_open type: Tooltip layout: flex: 0 0 auto properties: title: I am open by default defaultOpen: true placement: right blocks: - id: tooltip_default_open_btn type: Button layout: flex: 0 0 auto properties: title: Visible by default color: green variant: solid - id: tooltip_destroy_on_hide type: Tooltip layout: flex: 0 0 auto properties: title: This tooltip DOM is destroyed when hidden destroyTooltipOnHide: true blocks: - id: tooltip_destroy_on_hide_btn type: Button layout: flex: 0 0 auto properties: title: Destroy on hide color: default variant: outlined ``` ```yaml - id: tooltip_zindex_high type: Tooltip layout: flex: 0 0 auto properties: title: High z-index tooltip (2000) zIndex: 2000 blocks: - id: tooltip_zindex_high_btn type: Button layout: flex: 0 0 auto properties: title: zIndex 2000 color: primary variant: solid ``` Hover over this text to see the tooltip. ```yaml - id: tooltip_on_text type: Tooltip layout: flex: 0 0 auto properties: title: Tooltip on a paragraph blocks: - id: tooltip_on_text_p type: Paragraph properties: content: Hover over this text to see the tooltip. - id: tooltip_on_tag type: Tooltip layout: flex: 0 0 auto properties: title: Tooltip on a tag blocks: - id: tooltip_on_tag_el type: Tag properties: title: Hover tag color: blue - id: tooltip_on_icon_btn type: Tooltip layout: flex: 0 0 auto properties: title: Delete this item placement: bottom blocks: - id: tooltip_on_icon_btn_el type: Button layout: flex: 0 0 auto properties: icon: delete hideTitle: true color: danger variant: text ``` ```yaml - id: tooltip_event_open_change type: Tooltip layout: flex: 0 0 auto properties: title: This tooltip fires an event on open/close events: onOpenChange: - id: tooltip_event_display_message type: DisplayMessage params: content: Tooltip visibility changed! duration: 2 blocks: - id: tooltip_event_open_change_btn type: Button layout: flex: 0 0 auto properties: title: Hover to trigger event color: primary variant: solid - id: tooltip_event_set_state type: Tooltip layout: flex: 0 0 auto properties: title: Tracks tooltip state trigger: click events: onOpenChange: - id: tooltip_event_set_state_action type: SetState params: tooltip_was_toggled: true blocks: - id: tooltip_event_set_state_btn type: Button layout: flex: 0 0 auto properties: title: Click to toggle and set state color: primary variant: outlined ``` ```yaml - id: tooltip_overlay_style type: Tooltip layout: flex: 0 0 auto properties: title: Tooltip with custom overlay style style: .inner: boxShadow: 0 4px 12px rgba(0, 0, 0, 0.3) blocks: - id: tooltip_overlay_style_btn type: Button layout: flex: 0 0 auto properties: title: Custom overlay shadow color: primary variant: solid ``` ```yaml - id: tooltip_theme_large_radius type: Tooltip layout: flex: 0 0 auto properties: title: Large radius tooltip theme: borderRadius: 16 blocks: - id: tooltip_theme_large_radius_btn type: Button layout: flex: 0 0 auto properties: title: Large radius color: primary variant: solid - id: tooltip_theme_bg type: Tooltip layout: flex: 0 0 auto properties: title: Custom background and text color theme: colorBgSpotlight: "#2d3436" colorTextLightSolid: "#74b9ff" blocks: - id: tooltip_theme_bg_btn type: Button layout: flex: 0 0 auto properties: title: Custom colors color: primary variant: outlined - id: tooltip_theme_font type: Tooltip layout: flex: 0 0 auto properties: title: Larger font size and line height theme: fontSize: 18 lineHeight: 2 blocks: - id: tooltip_theme_font_btn type: Button layout: flex: 0 0 auto properties: title: Large font color: primary variant: dashed - id: tooltip_theme_wide type: Tooltip layout: flex: 0 0 auto properties: title: This tooltip has a wider max width so longer text can display on a single line without wrapping. theme: maxWidth: 500 blocks: - id: tooltip_theme_wide_btn type: Button layout: flex: 0 0 auto properties: title: Wide tooltip (500px) color: default variant: outlined - id: tooltip_theme_compact type: Tooltip layout: flex: 0 0 auto properties: title: Compact tooltip theme: paddingXS: 4 controlHeight: 24 fontSize: 12 blocks: - id: tooltip_theme_compact_btn type: Button layout: flex: 0 0 auto properties: title: Compact theme color: default variant: dashed - id: tooltip_theme_zindex type: Tooltip layout: flex: 0 0 auto properties: title: Custom z-index via theme theme: zIndexPopup: 2000 blocks: - id: tooltip_theme_zindex_btn type: Button layout: flex: 0 0 auto properties: title: Theme z-index color: default variant: outlined ``` Hover me (help cursor via element style) Hover me (Tailwind element class) ```yaml - id: tooltip_style_element type: Tooltip layout: flex: 0 0 auto properties: title: Styled with element CSS key style: .element: cursor: help blocks: - id: tooltip_style_element_p type: Paragraph properties: content: Hover me (help cursor via element style) - id: tooltip_style_inner type: Tooltip layout: flex: 0 0 auto properties: title: Custom inner styling style: .inner: backgroundColor: "#1a1a2e" color: "#e94560" borderRadius: 12 padding: 12px 20px fontSize: 16 blocks: - id: tooltip_style_inner_btn type: Button layout: flex: 0 0 auto properties: title: Custom inner style color: primary variant: solid - id: tooltip_class_element type: Tooltip layout: flex: 0 0 auto properties: title: Tailwind on element wrapper class: .element: cursor-help blocks: - id: tooltip_class_element_text type: Paragraph properties: content: Hover me (Tailwind element class) - id: tooltip_class_inner type: Tooltip layout: flex: 0 0 auto properties: title: Tailwind on inner popup class: .inner: text-lg font-bold blocks: - id: tooltip_class_inner_btn type: Button layout: flex: 0 0 auto properties: title: Tailwind inner class color: primary variant: outlined ``` Product Revenue Units Sold Widget Pro $12,450 342 Gadget X $8,230 189 ```yaml - id: tooltip_table_header_card type: Card properties: title: Monthly Sales Report size: small blocks: - id: tooltip_table_header_row type: Box layout: gap: 0 direction: column blocks: - id: tooltip_table_header_labels type: Box class: bg-bg-layout px-4 py-2 border-b border-border layout: gap: 0 blocks: - id: tooltip_table_hdr_product type: Tooltip layout: flex: 2 1 0 properties: title: The product name as listed in the inventory system placement: top blocks: - id: tooltip_table_hdr_product_text type: Paragraph class: font-semibold text-text-secondary text-sm m-0 properties: content: Product - id: tooltip_table_hdr_revenue type: Tooltip layout: flex: 1 1 0 properties: title: Total revenue after discounts and returns placement: top blocks: - id: tooltip_table_hdr_revenue_text type: Paragraph class: font-semibold text-text-secondary text-sm m-0 properties: content: Revenue - id: tooltip_table_hdr_units type: Tooltip layout: flex: 1 1 0 properties: title: Number of individual units sold this month placement: top blocks: - id: tooltip_table_hdr_units_text type: Paragraph class: font-semibold text-text-secondary text-sm m-0 properties: content: Units Sold - id: tooltip_table_row_1 type: Box class: px-4 py-2 border-b border-border layout: gap: 0 blocks: - id: tooltip_table_row_1_product type: Paragraph layout: flex: 2 1 0 class: text-sm m-0 properties: content: Widget Pro - id: tooltip_table_row_1_revenue type: Paragraph layout: flex: 1 1 0 class: text-sm m-0 properties: content: $12,450 - id: tooltip_table_row_1_units type: Paragraph layout: flex: 1 1 0 class: text-sm m-0 properties: content: "342" - id: tooltip_table_row_2 type: Box class: px-4 py-2 layout: gap: 0 blocks: - id: tooltip_table_row_2_product type: Paragraph layout: flex: 2 1 0 class: text-sm m-0 properties: content: Gadget X - id: tooltip_table_row_2_revenue type: Paragraph layout: flex: 1 1 0 class: text-sm m-0 properties: content: $8,230 - id: tooltip_table_row_2_units type: Paragraph layout: flex: 1 1 0 class: text-sm m-0 properties: content: "189" ``` ```yaml - id: tooltip_form_card type: Card properties: title: User Profile Settings blocks: - id: tooltip_form_username type: TextInput properties: label: title: Username tooltip: title: Your unique username. Must be 3-20 characters, letters and numbers only. color: "#8c8c8c" placeholder: Enter your username - id: tooltip_form_email type: TextInput properties: label: title: Email Address tooltip: title: We will send account notifications to this address. You can change it at any time. color: "#8c8c8c" placeholder: Enter your email ``` Active Users Monthly Revenue ```yaml - id: tooltip_dashboard_row type: Box layout: gap: 16 blocks: - id: tooltip_dashboard_users_card type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: tooltip_dashboard_users_header type: Box layout: gap: 8 align: center blocks: - id: tooltip_dashboard_users_title type: Paragraph layout: flex: 1 1 0 class: text-text-secondary text-sm m-0 properties: content: Active Users - id: tooltip_dashboard_users_info type: Tooltip layout: flex: 0 0 auto properties: title: Users who have logged in at least once in the last 30 days placement: top blocks: - id: tooltip_dashboard_users_info_icon type: Icon layout: flex: 0 0 auto properties: name: info size: 14 color: "#8c8c8c" - id: tooltip_dashboard_users_stat type: Statistic properties: value: 2847 - id: tooltip_dashboard_users_tag type: Tag properties: title: +12.3% color: success - id: tooltip_dashboard_revenue_card type: Card layout: flex: 1 1 0 properties: size: small blocks: - id: tooltip_dashboard_revenue_header type: Box layout: gap: 8 align: center blocks: - id: tooltip_dashboard_revenue_title type: Paragraph layout: flex: 1 1 0 class: text-text-secondary text-sm m-0 properties: content: Monthly Revenue - id: tooltip_dashboard_revenue_info type: Tooltip layout: flex: 0 0 auto properties: title: Gross revenue before taxes and refunds for the current billing period placement: top blocks: - id: tooltip_dashboard_revenue_info_icon type: Icon layout: flex: 0 0 auto properties: name: info size: 14 color: "#8c8c8c" - id: tooltip_dashboard_revenue_stat type: Statistic properties: value: 64250 prefix: $ - id: tooltip_dashboard_revenue_tag type: Tag properties: title: +8.7% color: success ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `arrow` | boolean \| object | `true` | Whether to show the arrow. Set `{ pointAtCenter: true }` to point the arrow at the center of the target. | | `arrow.pointAtCenter` | boolean | `false` | Whether the arrow is pointed at the center of target. | | `arrowPointAtCenter` | boolean | `false` | Whether the arrow is pointed at the center of target. Same as `arrow: { pointAtCenter: true }`. | | `autoAdjustOverflow` | boolean | `true` | Whether to adjust popup placement automatically when popup is off screen. | | `color` | string | - | The background color. | | `defaultOpen` | boolean | `false` | Whether the floating tooltip card is visible by default. | | `destroyOnHidden` | boolean | `false` | Whether to destroy the tooltip DOM when hidden. | | `destroyTooltipOnHide` | boolean | `false` | Same as destroyOnHidden, which takes precedence. | | `mouseEnterDelay` | number | `0.1` | Delay in seconds, before tooltip is shown on mouse enter. | | `mouseLeaveDelay` | number | `0.1` | Delay in seconds, before tooltip is hidden on mouse leave. | | `placement` | string | `"top"` | The position of the tooltip relative to the target. Enum: `top`, `left`, `right`, `bottom`, `topLeft`, `topRight`, `bottomLeft`, `bottomRight`, `leftTop`, `leftBottom`, `rightTop`, `rightBottom`. | | `trigger` | string | `"hover"` | Tooltip trigger mode. `contextMenu` opens the tooltip on right click. Enum: `hover`, `focus`, `click`, `contextMenu`. | | `title` | string | - | Title to show in the title area - supports html. Overwritten by blocks in the title content area. | | `zIndex` | integer | - | The z-index of the Tooltip. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design tooltip tokens](https://ant.design/components/tooltip#design-token). | | `theme.zIndexPopup` | number | `1070` | Z-index of the tooltip. | | `theme.maxWidth` | number | `250` | Maximum width of the tooltip. | | `theme.colorBgSpotlight` | string | `"rgba(0, 0, 0, 0.85)"` | Background color of the tooltip. | | `theme.colorTextLightSolid` | string | `"#fff"` | Text color inside the tooltip. | | `theme.borderRadius` | number | `6` | Border radius of the tooltip. | | `theme.controlHeight` | number | `32` | Minimum height of the tooltip. | | `theme.fontSize` | number | `14` | Font size of tooltip text. | | `theme.lineHeight` | number | `1.5714` | Line height of tooltip text. | | `theme.paddingXS` | number | `8` | Horizontal padding inside the tooltip. | | Event | Event Data | Description | | --- | --- | --- | | `onOpenChange` | `{ open }` | Trigger action when visibility of the tooltip card is changed. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Tooltip element. | | `/inner` | The Tooltip inner. | | Slot | Description | | --- | --- | | `content` | Child blocks that trigger the tooltip. | --- url: https://docs.lowdefy.com/md/container-blocks/watermark.md --- # Watermark Watermark overlay with text, image, and font customization. ```yaml - id: basic_single type: Watermark properties: text: Lowdefy blocks: - id: basic_single_child type: Box style: height: 200px - id: basic_multiline type: Watermark properties: text: - Lowdefy - Confidential blocks: - id: basic_multiline_child type: Box style: height: 220px ``` ```yaml - id: img_logo type: Watermark properties: image: https://lowdefy.com/favicon-32x32.png width: 80 height: 80 blocks: - id: img_logo_child type: Box style: height: 240px - id: img_small type: Watermark properties: image: https://lowdefy.com/favicon-32x32.png width: 40 height: 40 blocks: - id: img_small_child type: Box style: height: 200px ``` ```yaml - id: font_red type: Watermark properties: text: Rejected font: color: rgba(255, 0, 0, 0.15) blocks: - id: font_red_child type: Box style: height: 180px - id: font_large_bold type: Watermark properties: text: DRAFT font: fontSize: 28 fontWeight: bold color: rgba(255, 0, 0, 0.08) blocks: - id: font_large_bold_child type: Box style: height: 220px - id: font_serif_italic type: Watermark properties: text: Elegant font: fontFamily: Georgia, serif fontStyle: italic fontSize: 20 color: rgba(139, 92, 246, 0.1) blocks: - id: font_serif_italic_child type: Box style: height: 200px ``` ```yaml - id: per_line_font type: Watermark properties: text: - text: CONFIDENTIAL font: fontSize: 22 fontWeight: bold color: rgba(255, 0, 0, 0.12) - Prepared for the finance team blocks: - id: per_line_font_child type: Box style: height: 200px ``` ```yaml - id: align_left type: Watermark properties: text: - Left Aligned - Second Line font: textAlign: left color: rgba(0, 0, 0, 0.1) blocks: - id: align_left_child type: Box style: height: 200px - id: align_center type: Watermark properties: text: - Center Aligned - Second Line font: textAlign: center color: rgba(0, 0, 0, 0.1) blocks: - id: align_center_child type: Box style: height: 200px - id: align_right type: Watermark properties: text: - Right Aligned - Second Line font: textAlign: right color: rgba(0, 0, 0, 0.1) blocks: - id: align_right_child type: Box style: height: 200px ``` ```yaml - id: rotate_default type: Watermark properties: text: Default (-22 degrees) blocks: - id: rotate_default_child type: Box style: height: 180px - id: rotate_horizontal type: Watermark properties: text: Horizontal (0 degrees) rotate: 0 blocks: - id: rotate_horizontal_child type: Box style: height: 180px - id: rotate_steep type: Watermark properties: text: Steep (-45 degrees) rotate: -45 blocks: - id: rotate_steep_child type: Box style: height: 200px ``` ```yaml - id: gap_tight type: Watermark properties: text: Tight Gap gap: - 50 - 50 blocks: - id: gap_tight_child type: Box style: height: 200px - id: gap_wide type: Watermark properties: text: Wide Gap gap: - 200 - 200 blocks: - id: gap_wide_child type: Box style: height: 200px - id: offset_shifted type: Watermark properties: text: Offset (50, 40) offset: - 50 - 40 blocks: - id: offset_shifted_child type: Box style: height: 200px ``` ```yaml - id: zindex_high type: Watermark properties: text: High zIndex (100) zIndex: 100 blocks: - id: zindex_high_child type: Box style: height: 180px - id: inherit_false type: Watermark properties: text: Inherit disabled inherit: false blocks: - id: inherit_false_child type: Box style: height: 180px ``` ```yaml - id: theme_color_fill type: Watermark properties: text: Custom Fill Color theme: colorFill: rgba(22, 119, 255, 0.12) blocks: - id: theme_color_fill_child type: Box style: height: 200px - id: theme_font_size type: Watermark properties: text: Large Default Font theme: fontSizeLG: 24 blocks: - id: theme_font_size_child type: Box style: height: 200px - id: theme_font_family type: Watermark properties: text: Serif Theme Font theme: fontFamily: Georgia, serif blocks: - id: theme_font_family_child type: Box style: height: 200px ``` The element CSS key applies Tailwind classes to the Watermark wrapper. Here a gradient background and rounded corners create a styled preview area. Dark Background White watermark text with low opacity stays subtle on a dark surface. Useful for dark-mode interfaces and embedded viewers. Drag and drop files here or click to browse ```yaml - id: css_gradient_bg type: Watermark class: .element: bg-gradient-to-br from-primary/10 to-primary/5 rounded-lg p-4 properties: text: Preview font: color: rgba(79, 70, 229, 0.1) fontSize: 18 blocks: - id: css_gradient_bg_content type: Paragraph properties: content: > The element CSS key applies Tailwind classes to the Watermark wrapper. Here a gradient background and rounded corners create a styled preview area. - id: css_dark_surface type: Watermark class: .element: bg-bg-layout rounded-lg p-4 properties: text: Dark Theme font: color: rgba(255, 255, 255, 0.08) fontSize: 16 blocks: - id: css_dark_surface_title type: Title style: color: white properties: content: Dark Background level: 5 - id: css_dark_surface_content type: Paragraph style: color: rgba(255, 255, 255, 0.7) properties: content: > White watermark text with low opacity stays subtle on a dark surface. Useful for dark-mode interfaces and embedded viewers. - id: css_bordered_area type: Watermark class: .element: border-2 border-dashed border-border rounded-xl p-6 properties: text: Upload Area font: color: rgba(0, 0, 0, 0.06) fontSize: 20 rotate: 0 gap: - 120 - 80 blocks: - id: css_bordered_area_text type: Paragraph style: textAlign: center color: "#999" properties: content: Drag and drop files here or click to browse ``` Quarterly Financial Report Prepared for the Board of Directors — Q1 2026 Revenue for the first quarter reached $12.4M, representing a 15% increase year-over-year. Operating margins improved to 23% driven by efficiency gains across all business units. Customer acquisition costs decreased by 8% while customer lifetime value increased by 12%, indicating strong unit economics. The sales pipeline remains robust with $45M in qualified opportunities. ```yaml - id: report_wrapper type: Box class: bg-bg-container rounded-lg shadow-sm blocks: - id: report_watermark type: Watermark properties: text: DRAFT font: fontSize: 28 fontWeight: bold color: rgba(255, 0, 0, 0.06) gap: - 150 - 150 rotate: -30 blocks: - id: report_heading type: Title properties: content: Quarterly Financial Report level: 4 - id: report_subtitle type: Paragraph properties: content: Prepared for the Board of Directors — Q1 2026 - id: report_divider type: Divider - id: report_body_1 type: Paragraph properties: content: > Revenue for the first quarter reached $12.4M, representing a 15% increase year-over-year. Operating margins improved to 23% driven by efficiency gains across all business units. - id: report_body_2 type: Paragraph properties: content: > Customer acquisition costs decreased by 8% while customer lifetime value increased by 12%, indicating strong unit economics. The sales pipeline remains robust with $45M in qualified opportunities. ``` Project Overview A web application for managing team workflows and project timelines. Currently in active development with scheduled release next quarter. Mobile app for real-time inventory tracking across multiple warehouses. Integrates with existing ERP systems for seamless data sync. ```yaml - id: dashboard_watermark type: Watermark properties: text: - CONFIDENTIAL - Do Not Copy font: fontSize: 18 color: rgba(22, 119, 255, 0.06) fontWeight: bold gap: - 120 - 120 blocks: - id: dashboard_title type: Title properties: content: Project Overview level: 4 - id: dashboard_cards type: Box layout: gap: 16 blocks: - id: dashboard_card_alpha type: Card layout: flex: 1 1 0 properties: title: Project Alpha slots: extra: blocks: - id: dashboard_alpha_tag type: Tag layout: flex: 0 0 auto properties: title: Active color: success blocks: - id: dashboard_alpha_desc type: Paragraph properties: content: A web application for managing team workflows and project timelines. Currently in active development with scheduled release next quarter. - id: dashboard_alpha_progress type: Progress properties: percent: 68 - id: dashboard_card_beta type: Card layout: flex: 1 1 0 properties: title: Project Beta slots: extra: blocks: - id: dashboard_beta_tag type: Tag layout: flex: 0 0 auto properties: title: Planning color: warning blocks: - id: dashboard_beta_desc type: Paragraph properties: content: Mobile app for real-time inventory tracking across multiple warehouses. Integrates with existing ERP systems for seamless data sync. - id: dashboard_beta_progress type: Progress properties: percent: 25 ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `font` | object | - | Font style configuration with color, fontSize, fontWeight, fontFamily, fontStyle, textAlign. | | `font.color` | string | - | Font color of the watermark. | | `font.fontFamily` | string | - | Font family of the watermark. | | `font.fontSize` | number | - | Font size of the watermark. | | `font.fontStyle` | string | - | Font style of the watermark. | | `font.fontWeight` | string \| number | - | Font weight of the watermark. | | `font.textAlign` | string | - | Text alignment of the watermark. Enum: `start`, `end`, `left`, `right`, `center`. | | `gap` | array | - | Gap between watermarks as [horizontal, vertical]. | | `height` | number | - | Height of the watermark. | | `image` | string | - | Image URL to use as watermark. If set, text content is ignored. | | `inherit` | boolean | `true` | Inherit watermark config from parent Watermark block. | | `offset` | array | - | Offset of the watermark from the top-left as [x, y]. | | `rotate` | number | `-22` | Rotation angle of watermark in degrees. | | `text` | string \| array | - | Watermark text content. An array renders one line per item. A line can be a string, or `{ text, font }` to give that line its own font style. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design watermark tokens](https://ant.design/components/watermark#design-token). | | `theme.colorFill` | string | - | Default watermark text color when font.color is not set. Maps to the global colorFill token. | | `theme.fontSizeLG` | number | `16` | Default watermark font size when font.fontSize is not set. Maps to the global fontSizeLG token. | | `theme.fontFamily` | string | `"sans-serif"` | Default font family used when font.fontFamily is not set. | | `width` | number | - | Width of the watermark. | | `zIndex` | integer | - | Z-index of the watermark. | No events defined. | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The Watermark element. | | Slot | Description | | --- | --- | | `content` | Child blocks with a watermark overlay. | --- url: https://docs.lowdefy.com/md/list-blocks/controlledlist.md --- # ControlledList Dynamic list with built-in add and remove controls. Supports custom add/remove buttons, conditional field visibility, nested lists, theme tokens, and scoped validation. Use `addItemButton` and `removeItemIcon` to customize controls, and `visible` with operators for conditional fields. ```yaml - id: team type: ControlledList properties: title: Team Members blocks: - id: team.$.name type: TextInput required: true layout: flex: 1 1 0 properties: label: disabled: true placeholder: Full name - id: team.$.role type: Selector layout: flex: 1 1 0 properties: label: disabled: true placeholder: Role options: - Developer - Designer - PM - QA - id: team.$.email type: TextInput layout: flex: 1 1 0 properties: label: disabled: true placeholder: Email ``` ```yaml - id: bookmarks type: ControlledList properties: title: Bookmarks addToFront: true size: small addItemButton: title: Bookmark icon: add color: primary variant: dashed removeItemIcon: name: delete blocks: - id: bookmarks.$.url type: TextInput layout: flex: 1 1 0 properties: label: disabled: true placeholder: https://... prefixIcon: link - id: bookmarks.$.label type: TextInput layout: flex: 1 1 0 properties: label: disabled: true placeholder: Label ``` ```yaml - id: attendees type: ControlledList properties: title: Attendees addItemButton: title: Add Attendee icon: UserPlus color: primary variant: outlined blocks: - id: attendees.$.name type: TextInput required: true layout: flex: 2 1 0 properties: label: disabled: true placeholder: Full name - id: attendees.$.type type: Selector required: true layout: flex: 1 1 0 properties: label: disabled: true placeholder: Type options: - Adult - Child - Senior - id: attendees.$.email type: TextInput required: true visible: _eq: - _state: attendees.$.type - Adult layout: flex: 1 1 0 properties: label: disabled: true placeholder: Email validate: - message: Enter a valid email. status: error pass: _regex: pattern: ^[^@]+@[^@]+\.[^@]+$ on: _if_none: - _state: attendees.$.email - "" - id: attendees.$.meal type: Selector visible: _eq: - _state: attendees.$.type - Adult layout: flex: 1 1 0 properties: label: disabled: true placeholder: Meal options: - Standard - Vegetarian - Vegan - id: attendees.$.age type: NumberInput visible: _eq: - _state: attendees.$.type - Child layout: flex: 1 1 0 properties: label: disabled: true placeholder: Age min: 1 max: 17 - id: attendees.$.supervision type: Switch visible: _eq: - _state: attendees.$.type - Child layout: flex: 0 0 auto properties: title: Needs supervision size: small - id: attendees.$.wheelchair type: Switch visible: _eq: - _state: attendees.$.type - Senior layout: flex: 0 0 auto properties: title: Wheelchair access size: small - id: attendees.$.dietary type: TextInput visible: _eq: - _state: attendees.$.type - Senior layout: flex: 1 1 0 properties: label: disabled: true placeholder: Dietary notes - id: attendees_footer type: Box layout: justify: flex-end blocks: - id: attendees_register type: Button layout: flex: 0 0 auto properties: title: Register All icon: send color: primary variant: solid events: onClick: - id: attendees_validate type: Validate params: regex: ^attendees - id: attendees_msg type: DisplayMessage params: content: All attendees registered! status: success ``` ```yaml - id: expenses type: ControlledList properties: title: Expenses addItemButton: title: Add Expense icon: add color: primary variant: dashed theme: itemPadding: 16px 12px blocks: - id: expenses.$.date type: DateSelector required: true layout: flex: 1 1 0 properties: label: disabled: true placeholder: Date - id: expenses.$.category type: Selector required: true layout: flex: 1 1 0 properties: label: disabled: true placeholder: Category options: - Travel - Meals - Software - Equipment - Other - id: expenses.$.desc type: TextInput required: true layout: flex: 2 1 0 properties: label: disabled: true placeholder: Description - id: expenses.$.amount type: NumberInput required: true layout: flex: 1 1 0 properties: label: disabled: true placeholder: Amount min: 0 step: 0.01 prefix: $ - id: expenses_footer type: Box layout: gap: 16 justify: flex-end align: center blocks: - id: expenses_total type: Statistic layout: flex: 0 0 auto properties: title: Total Expenses prefix: $ precision: 2 value: _js: | const expenses = state('expenses') || []; return expenses.reduce((sum, item) => sum + (item.amount || 0), 0); - id: expenses_submit type: Button layout: flex: 0 0 auto properties: title: Submit Report icon: send color: primary variant: solid events: onClick: - id: expenses_validate type: Validate params: regex: ^expenses - id: expenses_msg type: DisplayMessage params: content: Expense report submitted! status: success ``` ```yaml - id: projects type: ControlledList properties: title: Projects addItemButton: title: Add Project icon: add color: primary variant: outlined blocks: - id: projects.$.name type: TextInput required: true layout: flex: 1 1 0 properties: label: disabled: true placeholder: Project name - id: projects.$.status type: Selector layout: flex: 0 0 140px properties: label: disabled: true placeholder: Status options: - Planning - Active - Review - Done - id: projects.$.tasks type: ControlledList properties: title: Tasks size: small addItemButton: title: Add Task icon: add size: small variant: dashed blocks: - id: projects.$.tasks.$.task type: TextInput required: true layout: flex: 1 1 0 properties: label: disabled: true placeholder: Task description size: small - id: projects.$.tasks.$.done type: Switch layout: flex: 0 0 auto properties: title: Done size: small - id: projects_footer type: Box layout: justify: flex-end blocks: - id: projects_save type: Button layout: flex: 0 0 auto properties: title: Save All icon: save color: primary variant: solid events: onClick: - id: projects_validate type: Validate params: regex: ^projects - id: projects_msg type: DisplayMessage params: content: Projects saved! status: success ``` ```yaml - id: tags type: ControlledList properties: title: Tags size: small addItemButton: title: Add Tag icon: add size: small variant: dashed events: onAdd: - id: tags_added_msg type: DisplayMessage params: content: _string.concat: - "Added row at index " - _event: index duration: 1 onRemove: - id: tags_removed_msg type: DisplayMessage params: content: _string.concat: - Removed " - _if_none: - _event: item.label - (empty) - '" at index ' - _event: index status: warning duration: 2 blocks: - id: tags.$.label type: TextInput layout: flex: 1 1 0 properties: label: disabled: true placeholder: Tag name size: small ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `title` | string | - | Controlled list title. | | `addToFront` | boolean | `false` | When true, add new items to the front of the list. | | `hideAddButton` | boolean | `false` | When true, hide the add new item button. | | `size` | string | `"default"` | Size of the list. Enum: `small`, `default`, `large`. | | `addItemButton` | object | - | Custom add item button properties. | | `removeItemIcon` | string \| object | - | Custom remove item icon properties. Defaults to `remove` at a standard size with the antd error color inherited from the icon wrapper — override via `class.removeIcon` / `style.removeIcon` for visual tweaks, or via this property to change the icon name itself. | | `removeItemIcon.name` | string | - | Icon name: a semantic name like `edit`, a Lucide icon name like `Pencil`, or a set-qualified name like `tabler:Pencil`. | | `removeItemIcon.color` | string | - | Icon color. | | `removeItemIcon.size` | string \| number | - | Size of the icon. Defaults to `theme.icons.size`. | | `removeItemIcon.rotate` | number | - | Number of degrees to rotate the icon. | | `removeItemIcon.spin` | boolean | - | Continuously spin the icon with animation. | | `removeItemIcon.strokeWidth` | number | - | Stroke width of the icon lines, in pixels of the 24px icon grid. Defaults to `theme.icons.strokeWidth` (2). | | `removeItemIcon.nonScalingStroke` | boolean | - | Keep the stroke width constant at any icon size. Defaults to `theme.icons.nonScalingStroke`. | | `removeItemIcon.title` | string | - | Icon hover title for accessibility. An empty string marks the icon as decorative. | | `removeItemIcon.disableLoadingIcon` | boolean | - | While loading after the icon has been clicked, don't render the loading icon. | | `hideRemoveButton` | boolean | `false` | When true, hide the remove item button on each list item. | | `noDataTitle` | string | - | Title to show when list is empty. | | `minItems` | number | `0` | Minimum number of items in the controlled list. | | `theme` | object | - | Antd design token overrides for this block. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design list tokens](https://ant.design/components/list#design-token). | | `theme.titleMarginBottom` | number | `12` | Margin bottom of list item title. | | `theme.contentWidth` | number | `220` | Width of the content area. | | `theme.itemPadding` | string | `"12px 0"` | Padding of list items. | | `theme.itemPaddingLG` | string | `"16px 24px"` | Padding of list items (large size). | | `theme.itemPaddingSM` | string | `"8px 16px"` | Padding of list items (small size). | | `theme.headerBg` | string | `"transparent"` | Background color of the list header. | | `theme.footerBg` | string | `"transparent"` | Background color of the list footer. | | `theme.emptyTextPadding` | number | `32` | Padding for the empty text area. | | `theme.metaMarginBottom` | number | `16` | Margin bottom of list item meta. | | `theme.avatarMarginRight` | number | `16` | Margin right of the avatar in list items. | | `theme.descriptionFontSize` | number | `14` | Font size of the description text. | | Event | Event Data | Description | | --- | --- | --- | | `onAdd` | \- | Triggered after a new item is added via the add button. The event payload is `{ index, item }`, where `item` is the newly added value (`undefined` for an empty row). | | `onRemove` | \- | Triggered after an item is removed via the remove icon. The event payload is `{ index, item }`, where `item` is the removed value captured before removal. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The ControlledList element. | | `/header` | The ControlledList header. | | `/footer` | The ControlledList footer. | | `/item` | The ControlledList item. | | `/removeIcon` | The remove-item icon wrapper. Defaults to the antd error color at `fontSizeLG`; override `color`, `font-size`, or spacing here. | | Slot | Description | | --- | --- | | `content` | Blocks rendered for each list item. | --- url: https://docs.lowdefy.com/md/list-blocks/list.md --- # List Flex-based list container that renders a template block for each item in an array. Supports column/row direction, wrapping, and scrolling. Use `CallMethod` with `pushItem`, `removeItem`, `moveItemUp`, and `moveItemDown` to manage items. Pair with `Validate` to validate inputs across all list rows. Notes ```yaml - id: notes_header type: Box layout: justify: space-between align: center blocks: - id: notes_title type: Title layout: flex: 0 0 auto properties: content: Notes level: 4 - id: notes_add type: Button layout: flex: 0 0 auto properties: title: Add Note icon: add color: primary variant: solid size: small events: onClick: - id: notes_push type: CallMethod params: blockId: notes method: pushItem - id: notes type: List properties: direction: row blocks: - id: notes.$.text type: TextInput layout: flex: 1 1 0 properties: label: disabled: true placeholder: Type a note... - id: notes.$.rm type: Button layout: flex: 0 0 auto properties: icon: close color: default variant: text shape: circle size: small events: onClick: - id: rm_note type: CallMethod params: blockId: notes method: removeItem args: - _index: 0 ``` ```yaml - id: skills type: List properties: direction: row wrap: wrap blocks: - id: skills.$.label type: Tag layout: flex: 0 0 auto properties: title: _state: skills.$.label color: _state: skills.$.color closable: true events: onClose: - id: rm_skill type: CallMethod params: blockId: skills method: removeItem args: - _index: 0 ``` Tasks ```yaml - id: tasks_header type: Box layout: justify: space-between align: center blocks: - id: tasks_title type: Title layout: flex: 0 0 auto properties: content: Tasks level: 4 - id: tasks_add type: Button layout: flex: 0 0 auto properties: title: Add Task icon: add color: primary variant: solid size: small events: onClick: - id: tasks_push type: CallMethod params: blockId: tasks method: pushItem - id: tasks type: List blocks: - id: tasks.$.done type: Switch layout: flex: 0 0 auto properties: label: disabled: true size: small - id: tasks.$.name type: TextInput required: true layout: flex: 1 1 0 properties: label: disabled: true placeholder: Task name size: small - id: tasks.$.priority type: Selector layout: flex: 0 0 140px properties: label: disabled: true placeholder: Priority size: small options: - High - Medium - Low - id: tasks.$.pri_tag type: Tag layout: flex: 0 0 auto properties: title: _if_none: - _state: tasks.$.priority - "" color: _if: test: _eq: - _state: tasks.$.priority - High then: red else: _if: test: _eq: - _state: tasks.$.priority - Medium then: orange else: green - id: tasks.$.up type: Button layout: flex: 0 0 auto properties: icon: arrow-up color: default variant: text size: small shape: circle events: onClick: - id: tasks_up type: CallMethod params: blockId: tasks method: moveItemUp args: - _index: 0 - id: tasks.$.down type: Button layout: flex: 0 0 auto properties: icon: arrow-down color: default variant: text size: small shape: circle events: onClick: - id: tasks_down type: CallMethod params: blockId: tasks method: moveItemDown args: - _index: 0 - id: tasks.$.rm type: Button layout: flex: 0 0 auto properties: icon: delete color: danger variant: text size: small shape: circle events: onClick: - id: tasks_rm type: CallMethod params: blockId: tasks method: removeItem args: - _index: 0 - id: tasks_footer type: Box layout: justify: flex-end blocks: - id: tasks_save type: Button layout: flex: 0 0 auto properties: title: Save Tasks icon: save color: primary variant: solid events: onClick: - id: tasks_validate type: Validate params: regex: ^tasks - id: tasks_msg type: DisplayMessage params: content: Tasks saved! status: success ``` User Directory ```yaml - id: users_header type: Box layout: justify: space-between align: center blocks: - id: users_title type: Title layout: flex: 0 0 auto properties: content: User Directory level: 4 - id: users_btns type: Box layout: flex: 0 0 auto gap: 8 blocks: - id: users_load type: Button layout: flex: 0 0 auto properties: title: Load Users icon: CloudDownload color: primary variant: solid size: small events: onClick: - id: fetch_users type: Fetch params: url: https://jsonplaceholder.typicode.com/users responseFunction: json - id: set_users type: SetState params: users: _actions: fetch_users.response - id: users_clear type: Button layout: flex: 0 0 auto properties: title: Clear color: default variant: outlined size: small events: onClick: - id: clear_users type: SetState params: users: [] - id: users type: List blocks: - id: users.$.avatar type: Avatar layout: flex: 0 0 auto properties: content: _string.charAt: on: _if_none: - _state: users.$.name - "?" index: 0 color: "#1677ff" - id: users.$.disp_name type: Paragraph layout: flex: 1 1 0 properties: content: _state: users.$.name - id: users.$.disp_email type: Paragraph layout: flex: 1 1 0 properties: content: _state: users.$.email type: secondary - id: users.$.disp_company type: Tag layout: flex: 0 0 auto properties: title: _state: users.$.company.name color: blue ``` Invoice #1042 ```yaml - id: inv_header type: Box layout: justify: space-between align: center blocks: - id: inv_title type: Title layout: flex: 0 0 auto properties: content: "Invoice #1042" level: 4 - id: inv_add type: Button layout: flex: 0 0 auto properties: title: Add Line icon: add color: primary variant: dashed size: small events: onClick: - id: inv_push type: CallMethod params: blockId: inv method: pushItem - id: inv type: List blocks: - id: inv.$.desc type: TextInput required: true layout: flex: 3 1 0 properties: label: disabled: true placeholder: Description - id: inv.$.qty type: NumberInput required: true layout: flex: 1 1 0 properties: label: disabled: true placeholder: Qty min: 1 - id: inv.$.price type: NumberInput required: true layout: flex: 1 1 0 properties: label: disabled: true placeholder: Unit price min: 0 step: 0.01 - id: inv.$.total type: Paragraph layout: flex: 0 0 100px properties: content: _js: | const qty = state('inv.$.qty') || 0; const price = state('inv.$.price') || 0; return '$' + (qty * price).toFixed(2); - id: inv.$.rm type: Button layout: flex: 0 0 auto properties: icon: remove color: danger variant: text size: small shape: circle events: onClick: - id: inv_rm type: CallMethod params: blockId: inv method: removeItem args: - _index: 0 - id: inv_footer type: Box layout: gap: 16 justify: flex-end align: center blocks: - id: inv_total type: Statistic layout: flex: 0 0 auto properties: title: Total prefix: $ precision: 2 value: _js: | const items = state('inv') || []; return items.reduce((sum, item) => sum + ((item.qty || 0) * (item.price || 0)), 0); - id: inv_submit type: Button layout: flex: 0 0 auto properties: title: Submit Invoice icon: send color: primary variant: solid events: onClick: - id: inv_validate type: Validate params: regex: ^inv - id: inv_msg type: DisplayMessage params: content: Invoice submitted! status: success ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `direction` | string | - | List content along a 'row' or down a 'column'. Applies the 'flex-direction' css property to the List block. Enum: `row`, `column`, `row-reverse`, `column-reverse`. | | `wrap` | string | - | Specifies wrapping style to be applied to List block as 'wrap', 'nowrap' or 'wrap-reverse'. Applies the 'flex-wrap' css property to the List block - defaults to 'wrap', requires List direction to be set. Enum: `wrap`, `nowrap`, `wrap-reverse`. | | `scroll` | boolean | - | Specifies whether scrolling should be applied to the List, can be true or false. Applies the 'overflow' css property to the List block - defaults to 'visible', requires List direction to be set. | | Event | Event Data | Description | | --- | --- | --- | | `onClick` | \- | Trigger actions when the List is clicked. | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The List element. | | Slot | Description | | --- | --- | | `content` | Blocks rendered for each list item. | --- url: https://docs.lowdefy.com/md/list-blocks/listselector.md --- # ListSelector Data-driven vertical list of headerless cards that doubles as a single-select input. Each card body is rendered from a Nunjucks template against its row item. Clicking a card sets the block value to that data item and highlights it; set `selectable: false` to turn selection off and render a read-only card list. Virtualized via `react-virtuoso` so thousands of variable-height cards render smoothly, with a memoized row component to skip unnecessary re-renders. Optional client-side search bar filters rows by text across any field paths, a built-in loading skeleton stands in while data loads, and `onChange` / `onClick` events report `{ value, index, item }` for the row that was clicked. ```yaml - id: basic_card_list type: ListSelector properties: data: - Alpha - Beta - Gamma html: |

{{ index + 1 }}. {{ item }}

``` ```yaml - id: select_list type: ListSelector properties: hoverable: true data: - id: 1 name: Starter price: $0 - id: 2 name: Pro price: $20 - id: 3 name: Enterprise price: $99 html: | {{ item.name }}
{{ item.price }} / month
events: onChange: - id: capture_selected type: SetState params: selected_plan: _event: value - id: selected_plan_display type: Paragraph properties: content: _nunjucks: template: "Selected: {{ selected_plan.name }}" on: _state: true ``` ```yaml - id: readonly_list type: ListSelector properties: selectable: false data: - One - Two - Three html: | {{ item }} ``` ```yaml - id: empty_list type: ListSelector properties: data: [] noData: No people to show yet. html: | {{ item.name }} ``` ```yaml - id: people_card_list type: ListSelector properties: hoverable: true bordered: false data: - name: Ada Lovelace email: ada@example.com role: Mathematician - name: Linus Torvalds email: linus@example.com role: Engineer - name: Grace Hopper email: grace@example.com role: Computer Scientist html: |
{{ item.name }} {{ item.role }} {{ item.email }}
``` ```yaml - id: click_card_list type: ListSelector properties: hoverable: true data: - id: 1 label: First - id: 2 label: Second - id: 3 label: Third html: |

{{ item.label }}

Click to see which row was selected.

events: onClick: - id: capture_clicked type: SetState params: clicked_card: _event: true ``` ```yaml - id: compact_card_list type: ListSelector properties: size: small gap: 4 data: - One - Two - Three - Four html: | {{ item }} ``` ```yaml - id: large_card_list type: ListSelector properties: hoverable: true height: 480 data: _string.split: on: _string.repeat: on: x, count: 999 separator: "," html: >

Row {{ index + 1 }}

This list renders one thousand cards. Only rows in (and near) the viewport are mounted in the DOM.

``` ```yaml - id: search_all_card_list type: ListSelector properties: hoverable: true height: 360 search: {} data: - name: Ada Lovelace email: ada@example.com role: Mathematician - name: Linus Torvalds email: linus@example.com role: Engineer - name: Grace Hopper email: grace@example.com role: Computer Scientist - name: Alan Turing email: alan@example.com role: Mathematician - name: Margaret Hamilton email: margaret@example.com role: Software Engineer html: > {{ item.name }}
{{ item.role }} · {{ item.email }}
``` ```yaml - id: search_fields_card_list type: ListSelector properties: hoverable: true height: 360 search: placeholder: Filter by name… fields: - name minLength: 1 data: - name: Ada Lovelace email: ada@example.com role: Mathematician - name: Linus Torvalds email: linus@example.com role: Engineer - name: Grace Hopper email: grace@example.com role: Computer Scientist html: | {{ item.name }}
{{ item.role }}
``` ```yaml - id: keyed_list type: ListSelector properties: valueKey: id primaryKey: id data: - id: 1 name: Starter price: $0 - id: 2 name: Pro price: $20 - id: 3 name: Enterprise price: $99 html: | {{ item.name }}
{{ item.price }} / month
events: onChange: - id: capture_keyed type: SetState params: keyed_plan_id: _event: value - id: keyed_plan_display type: Paragraph properties: content: _nunjucks: template: 'Selected plan id: {{ keyed_plan_id if keyed_plan_id != null else "—" }}' on: _state: true ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `data` | array | `[]` | Array of items. Each item is rendered as one card by passing it to the html Nunjucks template as `item`. | | `html` | string | - | Nunjucks template used to render the body of each card. The context exposes `item` (the current data row) and `index` (the zero-based row index). | | `selectable` | boolean | `true` | Enable selecting a card. When true, clicking a card sets the block value to that data item and highlights it. When false, the block is a read-only card list that stores no value. | | `allowDeselect` | boolean | `true` | Allow clicking the selected card again to clear the selection (sets the value to null). Ignored when `selectable` is false. | | `valueKey` | string | - | Field of each data item stored as the block value when a card is selected (e.g. "id"). Omit to store the whole item. Lets the selection be driven with SetState using a simple key. Supports dotted paths. | | `primaryKey` | string | - | Field used to match the current value (e.g. set with SetState) back to a card for highlighting. Defaults to `valueKey`. Set this when the stored value is the whole item but a single field (e.g. "id") uniquely identifies it. Supports dotted paths. | | `bordered` | boolean | `true` | Toggles the border around each card. | | `hoverable` | boolean | `false` | Lift each card up when hovered. | | `size` | string | `"default"` | Card size. Enum: `default`, `small`. | | `gap` | number | `8` | Pixel gap between cards. | | `height` | number \| string | - | Optional pixel height (number) or CSS height string of the scroll container. When omitted, the list scrolls with the page. | | `overscan` | number | `400` | Pixels of off-screen rows to render above and below the viewport. Increase for smoother fast-scroll, decrease to reduce DOM cost. | | `noData` | string | - | Text shown in place of the list when the `data` array is empty. Defaults to the `blocks.listSelector.noData` message ("No data"). | | `search` | object | - | Optional client-side search bar above the list. When this property is set, a debounced search input filters rows by text. Omit to hide the bar. | | `search.placeholder` | string | `"Search…"` | Placeholder text in the input. | | `search.fields` | array | - | Dotted field paths to match against (e.g. "user.name"). When omitted, every field path is searched (whole-item stringify). | | `search.caseSensitive` | boolean | `false` | Match case exactly. | | `search.debounce` | number | `150` | Milliseconds to wait after the last keystroke before filtering. | | `search.sticky` | boolean | `true` | Stick the search bar to the top while scrolling. | | `search.allowClear` | boolean | `true` | Show a clear (×) icon in the input. | | `search.minLength` | number | `0` | Skip filtering until the query is at least this many characters. | | `search.noResultsText` | string | `"No results"` | Text shown when the filter matches zero items. | | `theme` | object | - | Antd design token overrides for the cards. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design card tokens](https://ant.design/components/card#design-token). | | `theme.bodyPadding` | number | `24` | Padding of each card body. | | `theme.bodyPaddingSM` | number | `12` | Padding of each card body for small cards. | | `theme.borderRadiusLG` | number | `8` | Border radius of each card. | | `theme.colorBorderSecondary` | string | - | Border color of each card. | | `theme.colorBgContainer` | string | - | Background color of each card body. | | `theme.colorText` | string | - | Text color inside each card. | | `theme.boxShadowTertiary` | string | - | Shadow applied to each card on hover when hoverable is true. | | Event | Event Data | Description | | --- | --- | --- | | `onChange` | `{ value, index, item }` | Triggered when the selection changes (only fires when `selectable` is true). | | `onClick` | `{ index, item }` | Triggered when a card is clicked. | | `onSearch` | `{ value, resultCount }` | Triggered when the debounced search query changes (only fires when the `search` property is set). | | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The list container. | | `/card` | Each Card element. | | `/body` | Each Card body. | | `/selected` | The selected card. | | `/search` | The search bar wrapper above the list. | | `/noResults` | The "no results" placeholder shown when the search filter matches zero items. | | `/noData` | The "no data" placeholder shown when the data array is empty. | No slots defined. --- url: https://docs.lowdefy.com/md/list-blocks/masonrylist.md --- # MasonryList Masonry-style grid layout for list data. Items flow into columns and stack vertically, filling gaps left by shorter items. Provide an array in state and use _state operators in the template to access each item's data. ```yaml - id: ml_basic type: MasonryList properties: columns: 3 gutter: 16 blocks: - id: ml_basic.$.card type: Card class: _state: ml_basic.$.bg properties: size: small blocks: - id: ml_basic.$.title_block type: Title properties: content: _state: ml_basic.$.title level: 5 - id: ml_basic.$.desc_block type: Paragraph properties: content: _state: ml_basic.$.desc ``` ```yaml - id: ml_two type: MasonryList properties: columns: 2 gutter: 16 blocks: - id: ml_two.$.card type: Card class: _state: ml_two.$.bg properties: size: small blocks: - id: ml_two.$.title_block type: Title properties: content: _state: ml_two.$.title level: 5 - id: ml_two.$.body_block type: Paragraph properties: content: _state: ml_two.$.body ``` ```yaml - id: ml_gutter_tight type: MasonryList properties: columns: 3 gutter: 6 blocks: - id: ml_gutter_tight.$.card type: Card class: bg-bg-layout properties: size: small blocks: - id: ml_gutter_tight.$.label_block type: Paragraph properties: content: _state: ml_gutter_tight.$.label - id: ml_gutter_wide type: MasonryList properties: columns: 3 gutter: 24 blocks: - id: ml_gutter_wide.$.card type: Card class: bg-bg-layout properties: size: small blocks: - id: ml_gutter_wide.$.label_block type: Paragraph properties: content: _state: ml_gutter_wide.$.label ``` ```yaml - id: ml_notes type: MasonryList properties: columns: 3 gutter: 12 blocks: - id: ml_notes.$.card type: Card class: _state: ml_notes.$.bg properties: size: small blocks: - id: ml_notes.$.row type: Box layout: justify: space-between align: center blocks: - id: ml_notes.$.title_block type: Title layout: flex: 1 1 0 properties: content: _state: ml_notes.$.title level: 5 - id: ml_notes.$.tag_block type: Tag layout: flex: 0 0 auto properties: title: _state: ml_notes.$.tag color: _state: ml_notes.$.color - id: ml_notes.$.body_block type: Paragraph properties: content: _state: ml_notes.$.body ``` ```yaml - id: ml_team type: MasonryList properties: columns: 4 gutter: 12 blocks: - id: ml_team.$.card type: Card properties: size: small blocks: - id: ml_team.$.header type: Box layout: gap: 8 align: center blocks: - id: ml_team.$.avatar type: Avatar layout: flex: 0 0 auto properties: content: _state: ml_team.$.initials color: _state: ml_team.$.bg - id: ml_team.$.name_block type: Title layout: flex: 1 1 0 properties: content: _state: ml_team.$.name level: 5 - id: ml_team.$.role_block type: Paragraph properties: content: _state: ml_team.$.role ``` | Property | Type | Default | Description | | --- | --- | --- | --- | | `columns` | integer \| object | `3` | Number of columns, or responsive breakpoint object (e.g. { xs: 1, sm: 2, md: 3 }). | | `fresh` | boolean | `false` | Keep watching the size of each item and re-layout when it changes, for items whose height changes after they render (images loading, expanding content). | | `gutter` | number \| array \| object | `0` | Gap between items in pixels. A number, a responsive breakpoint object (e.g. { xs: 8, md: 16 }), or a [horizontal, vertical] array of either. | | `sequential` | boolean | `false` | Has no effect: antd Masonry always places each item in the shortest column. Kept so existing configs stay valid. | | `theme` | object | - | Antd design token overrides for this block. Masonry uses global motion tokens for item animations. See [antd design tokens](https://ant.design/components/overview#design-token). See [Ant Design masonry tokens](https://ant.design/components/masonry). | | `theme.motionDurationSlow` | string | `"0.3s"` | Duration of item position and fade-in animations. | | `theme.motionDurationFast` | string | `"0.1s"` | Duration of item fade-out animations. | | `theme.motionEaseOut` | string | `"cubic-bezier(0.215, 0.61, 0.355, 1)"` | Easing function for item animations. | No events defined. | Key | Target | | --- | --- | | `/block` | Outer block wrapper (always available). | | `/element` | The MasonryList element. | | `/item` | Each item in the masonry grid. | | Slot | Description | | --- | --- | | `content` | Blocks rendered for each masonry item. | --- url: https://docs.lowdefy.com/md/connections/aigateway.md --- # AI Gateway The AIGateway connection connects to the [Vercel AI Gateway](https://vercel.com/docs/ai-gateway), one API for models from many providers. It provides the `AIGatewayAgent` agent type for use with [Lowdefy agents](/agents-introduction), the one-shot `GenerateText` and `GenerateObject` requests, and `Decide` on evaluation models such as TypeSafe's Jev. > The AIGateway connection is provided by the `@lowdefy/connection-ai-gateway` package, which is included by default. ## Connections ### AIGateway #### Properties - `apiKey: string`: AI Gateway API key. Use [`_secret`](/_secret) to reference this securely. Optional — falls back to the `AI_GATEWAY_API_KEY` environment variable, or Vercel OIDC when deployed on Vercel. - `baseURL: string`: Optional base URL for the AI Gateway API. - `headers: object`: Optional custom headers to include in gateway requests. - `maxOutputTokens: number`: Default maximum number of tokens a model call generates, for the requests and agents on this connection that do not set their own. - `timeout: number`: Default milliseconds a model call may take, retries included, before it is cancelled, for the requests and agents on this connection that do not set their own. See _Limits and cancellation_ below. Model ids name the creator and the model, e.g. `anthropic/claude-sonnet-5`, `google/gemini-3.8-flash` or `typesafe-ai/jev`. ###### Connection example: ```yaml connections: - id: gateway type: AIGateway properties: apiKey: _secret: AI_GATEWAY_API_KEY ``` ## Agent Types ### AIGatewayAgent The `AIGatewayAgent` supports all [common agent properties](/agents-introduction) plus the gateway's routing options: | Property | Type | Description | | --- | --- | --- | | `order` | string[] | Provider slugs to try in order (e.g. `["vertex", "anthropic"]`). The gateway tries each until one succeeds. | | `only` | string[] | Restrict routing to these provider slugs. | | `fallbackModels` | string[] | Model ids tried in order when the primary model fails. | | `user` | string | End-user identifier for spend attribution in gateway analytics. | | `tags` | string[] | Tags to categorize and filter usage in gateway reports. | ###### Agent with a fallback model: ```yaml agents: - id: support_agent type: AIGatewayAgent connectionId: gateway properties: model: anthropic/claude-sonnet-5 fallbackModels: - google/gemini-3.8-flash instructions: | You are a customer support agent. Search the knowledge base before answering questions. maxSteps: 5 tools: - search-knowledge-base ``` ## Evaluation models Evaluation models return typed decisions — choices, yes/no probabilities and scores — instead of text, answer every question in one pass, and bill input tokens only. On the AI Gateway, `Decide` asks an evaluation model by default (`backend: evaluation`); set `backend: structured-output` to ask a language model on the gateway instead. See [`Decide`](#decide) below. ## Requests Request types: - GenerateText - GenerateObject - Decide All AI provider connections (`Anthropic`, `OpenAI`, `Google`, `AIGateway`) provide the same `GenerateText`, `GenerateObject` and `Decide` request types — the `connectionId` selects the provider. These make a single, one-shot model call and return the result. They can be used as page requests, or as steps in [API endpoint routines](/api) — useful for classify, extract, summarize, or decide steps where the surrounding logic stays in your routine. For a closed-vocabulary decision — pick one of these options, yes or no, rate on this scale — use `Decide`. For multi-step tool use, see [agents](/agents-introduction) instead. ### Limits and cancellation Two limits bound every AI request. Set them on the request, or on the connection as the default for every request and agent that uses it: - `maxOutputTokens` caps the tokens one call generates. Without it, the provider's default applies. - `timeout` cancels a call, retries included, that runs longer than this many milliseconds. The request fails with a `ServiceError`. A call is also cancelled when the request that started it closes, so a provider call that nobody is waiting for stops instead of running, and billing, to its end: - A page request, or an API endpoint called over HTTP, is cancelled when the server's request timeout (`config.requestTimeout`, default 30 seconds) answers first, which fails it with a `ServiceError`. On the Node server it is also cancelled when the client disconnects; that is logged as a warning, not an error. - On [Vercel](/vercel), only the request timeout cancels a call: a client disconnect does not, and the call runs until it finishes, times out, or the function reaches `config.vercel.maxDuration`. Keep `config.requestTimeout` below `maxDuration`, or Vercel stops the function before the timeout can cancel anything (the build warns when it is not). - Endpoints with `async: true`, `detached: true` calls, and scheduled runs answer at once and then run to their end: the closing request does not cancel them. - An agent chat turn runs to its end after the client disconnects, so its `onFinish` hooks still save the conversation. Bound it with the agent's `timeout` and `maxSteps`. A `CallAgent` step is cancelled with the routine that runs it. ### Files in messages A file part in `messages` (`{ type: 'file', data: , mediaType }`) goes to the model as a link when the provider takes that media type as a link (the main providers do for images and PDFs). Any other file, such as a CSV or text file, is downloaded by the server and sent as content, within the request's `fileDownload` limits. The server downloads only an `https:` link to a public address, checking the address on every redirect, and a failed download names the file's host, never its link, so a signed link stays out of the server log. `Decide` takes no files. When a provider rate-limits a call (HTTP 429), the model client retries it (`maxRetries`), waiting as long as the provider's `Retry-After` asks, up to a minute. If the retries run out, the request fails with a `ServiceError`, as a 429 does on every connection, and the server log records the `Retry-After` value. ### GenerateText Generates text from a prompt. #### Properties - `model: string`: **Required** - Model id to generate with. - `prompt: string`: Text prompt. Use either `prompt` or `messages`, not both. - `messages: object[]`: Model messages (`{ role, content }`). Use either `prompt` or `messages`, not both. - `system: string`: System prompt. - `allowSystemInMessages: boolean`: Default: `false` - Allow `system` role messages in `messages`. A system message instructs the model as the app itself, so without this a request whose `messages` include one fails. Only set it when the messages come from the app, never from a user (for example a message list built from `_payload`). Use `system` for the system prompt. - `maxOutputTokens: number`: Maximum number of tokens to generate. Defaults to the connection's `maxOutputTokens`. - `timeout: number`: Milliseconds the model call may take, retries included, before it is cancelled. Defaults to the connection's `timeout`. - `fileDownload: object`: Limits on a file in `messages` the server downloads (see [Files in messages](#files-in-messages)). - `maxBytes: integer`: Default: `20971520` (20 MB) - Largest file the server downloads. A larger file fails the request. - `timeout: integer`: Default: `30000` - Milliseconds a file download may take. - `temperature: number`: Sampling temperature (0 to 2). - `topP: number`: Nucleus sampling. - `topK: number`: Only sample from the top K options for each subsequent token. - `frequencyPenalty: number`: Penalize repeated tokens by frequency. - `presencePenalty: number`: Penalize tokens that have already appeared. - `seed: number`: Seed for deterministic sampling, if supported by the model. - `stopSequences: string[]`: Sequences that stop generation. - `maxRetries: number`: Maximum number of retries. Defaults to 2. - `providerOptions: object`: Provider-specific options, keyed by provider (e.g. `anthropic`, `openai`, `google`). #### Response ```yaml text: string # The generated text. reasoningText: string # Reasoning text, when the model produced any. finishReason: string # 'stop', 'length', 'content-filter', ... usage: object # Token usage ({ inputTokens, outputTokens, totalTokens, ... }). providerMetadata: object # Provider-specific metadata, when returned. warnings: array # Call warnings from the provider, when returned. ``` #### Examples ###### Summarize text in an API endpoint routine: ```yaml api: - id: summarize-text type: Api routine: - id: summarize type: GenerateText connectionId: claude properties: model: claude-haiku-4-5 system: You are a concise summarizer. prompt: _payload: text - ':return': summary: _step: summarize.text ``` ###### Text generation as a page request: ```yaml requests: - id: generate_reply type: GenerateText connectionId: claude payload: message: _state: customer_message properties: model: claude-haiku-4-5 system: Draft a friendly reply to the customer message. prompt: _payload: message ``` ### GenerateObject Generates a structured object matching a JSON Schema. Ideal for classification, extraction, and routing decisions — the result lands in `_step` (or `_request`) as plain data your config can act on. #### Properties Takes the same properties as `GenerateText`, except `stopSequences`, plus: - `schema: object`: **Required** - JSON Schema describing the object the model must generate. - `schemaName: string`: Optional name for the output schema, passed to the model. - `schemaDescription: string`: Optional description of the output schema, passed to the model. #### Response ```yaml object: object # The generated object, matching the schema. reasoningText: string # Reasoning text, when the model produced any. finishReason: string # 'stop', 'length', 'content-filter', ... usage: object # Token usage ({ inputTokens, outputTokens, totalTokens, ... }). providerMetadata: object # Provider-specific metadata, when returned. warnings: array # Call warnings from the provider, when returned. ``` #### Examples ###### Classify then route in an API endpoint routine: ```yaml api: - id: triage-ticket type: Api routine: - id: classify type: GenerateObject connectionId: claude properties: model: claude-haiku-4-5 prompt: _payload: ticket_text schema: type: object properties: category: type: string enum: - billing - technical - other urgent: type: boolean - ':if': _eq: - _step: classify.object.urgent - true ':then': - id: escalate type: CallApi properties: endpointId: escalate-ticket payload: category: _step: classify.object.category ``` ### Decide Makes typed decisions about a piece of state: pick one of a set of options, judge a statement true or false, or place something on an ordered scale. Every answer comes back in the shape you declared, with a confidence, so a routine can branch on it — and send a low-confidence answer to a person instead. The build knows every option, so a routine branch that compares an answer with a name that is not one of its options — `_eq: [{ _step: triage.team.choice }, bil-ling]` — fails the build rather than silently never matching. A `_step` reference to a question the step does not ask, or to a field its answer does not have, fails the build the same way. `Decide` has two backends behind the same interface: - `evaluation` — an evaluation model, such as TypeSafe's Jev (`typesafe-ai/jev`) on the [AI Gateway](/AIGateway). It returns a probability for every option in one pass, in under a second, and bills input tokens only. The default, and only available, on the `AIGateway` connection. - `structured-output` — any language model, asked for the answers as structured output that follows a schema of the questions. Providers do not all enforce the schema, so Lowdefy checks each answer against its question (see `null` answers below). Available on every AI connection, and the default on `Anthropic`, `OpenAI` and `Google`. Its confidence is the model's own estimate, not a calibrated probability. Keep the state to what the questions need: an evaluation model reads the state literally, and accuracy drops as it grows. A `Decide` is never a security boundary — text in the state can argue for its own answer. #### Properties - `model: string`: **Required** - Model id to decide with, e.g. `typesafe-ai/jev`. Pin a version wherever a confidence threshold is tuned — a `latest` alias changes answers on release. - `state: string | object | array`: **Required** - What the questions are about. - `questions: object`: **Required** - Named questions, answered together against the state. Each question id becomes a key of the response (`usage` is reserved). A question is one of: - `{ choice: string, options: object }` - Pick exactly one option. `options` maps option names (2 to 255) to what each one means (`null` for no description), or to an object with a `description` — its other keys are ignored, so one file can describe the options for `Decide` and for an [`IntentInput`](/IntentInput) block. - `{ yesno: string, criteria?: { yes: string, no: string } }` - Judge a statement true or false. `criteria` optionally says what counts as yes and as no. - `{ score: string, levels: string[] }` - Place the state on 2 to 10 ordered levels, lowest first. - `backend: enum`: `evaluation` or `structured-output`. Defaults to `evaluation` on `AIGateway` and `structured-output` elsewhere. - `maxOutputTokens: number`: Maximum number of tokens to generate, on the `structured-output` backend (an evaluation model generates no text). Defaults to the connection's `maxOutputTokens`. - `timeout: number`: Milliseconds the model call may take, retries included, before it is cancelled. Defaults to the connection's `timeout`. - `maxRetries: number`: Maximum number of retries. Defaults to 2. - `providerOptions: object`: Provider-specific options, keyed by provider (e.g. `gateway: { zeroDataRetention: true }`). #### Response One key per question, plus `usage`: ```yaml : choice: string # The chosen option. confidence: number # 0 to 1. probabilities: object # Probability per option (evaluation backend), else null. : answer: boolean # true when the statement holds. probability: number # Probability that it holds. confidence: number # Probability of the given answer. : level: string # The most likely level. index: number # Its position, from 0. score: number # Fractional position on the scale (evaluation backend). confidence: number # 0 to 1. probabilities: object # Probability per level (evaluation backend), else null. usage: object # Token usage ({ inputTokens, outputTokens, totalTokens }). ``` An answer outside its question's terms (an option or level the question does not have, a yes/no that is not true or false, or no answer at all) comes back with every field `null`, its confidence included. `_lt` compares `null` below any number, so a confidence gate such as `_lt: [{ _step: triage.team.confidence }, 0.7]` holds it back like a low-confidence answer, and a branch on the choice matches none of the options. #### Examples ###### Route a support ticket, and queue uncertain ones for a person: ```yaml api: - id: triage-ticket type: Api routine: - id: triage type: Decide connectionId: gateway properties: model: typesafe-ai/jev state: subject: _payload: subject body: _payload: body questions: team: choice: Which team handles this ticket options: billing: Invoices, charges and refunds tech: Outages and bugs sales: Plans and quotes down: yesno: The customer says the service is down providerOptions: gateway: zeroDataRetention: true - ':if': _lt: - _step: triage.team.confidence - 0.7 ':then': - id: queue_review type: MongoDBInsertOne connectionId: review_queue properties: doc: subject: _payload: subject suggested_team: _step: triage.team.choice - ':return': status: review - ':if': _eq: - _step: triage.team.choice - billing ':then': - id: open_billing_case type: CallApi properties: endpointId: open-billing-case - ':return': team: _step: triage.team.choice urgent: _step: triage.down.answer ``` ###### The same decision with any language model: ```yaml requests: - id: rate_review type: Decide connectionId: claude payload: review: _state: review_text properties: model: claude-haiku-4-5 state: _payload: review questions: sentiment: score: How positive is this review levels: - Very negative - Negative - Neutral - Positive - Very positive ``` --- url: https://docs.lowdefy.com/md/connections/anthropic.md --- # Anthropic The Anthropic connection connects to the [Anthropic API](https://docs.anthropic.com/) and provides the `ClaudeAgent` agent type for use with [Lowdefy agents](/agents-introduction). > The Anthropic connection is provided by the `@lowdefy/connection-anthropic` package, which is included by default. ## Connections ### Anthropic #### Properties - `apiKey: string`: __Required__ - Anthropic API key. Use [`_secret`](/_secret) to reference this securely. - `baseURL: string`: Optional base URL for the Anthropic API. Use this to connect to a proxy or compatible endpoint. - `maxOutputTokens: number`: Default maximum number of tokens a model call generates, for the requests and agents on this connection that do not set their own. - `timeout: number`: Default milliseconds a model call may take, retries included, before it is cancelled, for the requests and agents on this connection that do not set their own. See _Limits and cancellation_ below. ###### Connection example: ```yaml connections: - id: anthropic type: Anthropic properties: apiKey: _secret: ANTHROPIC_API_KEY ``` ## Agent Types ### ClaudeAgent The `ClaudeAgent` supports all [common agent properties](/agents-introduction) plus the following: | Property | Type | Description | | --- | --- | --- | | `thinking` | object | Extended thinking configuration. The model shows its reasoning before responding. | | `thinking.type` | string | Set to `'enabled'` to turn on extended thinking. | | `thinking.budgetTokens` | integer | Maximum tokens the model can use for thinking. | | `effort` | string | Thinking effort level: `'low'`, `'medium'`, or `'high'`. Simpler alternative to `thinking`. | > Use `effort` for a simple control or `thinking` for fine-grained token budget control — not both at the same time. ###### Agent with extended thinking: ```yaml connections: - id: anthropic type: Anthropic properties: apiKey: _secret: ANTHROPIC_API_KEY agents: - id: analyst type: ClaudeAgent connectionId: anthropic properties: model: claude-sonnet-4-20250514 thinking: type: enabled budgetTokens: 10000 instructions: | You are a data analyst. Think carefully through each question before answering. Show your reasoning. maxSteps: 8 tools: - query-analytics - get-dashboard-data ``` ###### Agent with effort level: ```yaml agents: - id: support_agent type: ClaudeAgent connectionId: anthropic properties: model: claude-sonnet-4-20250514 effort: high instructions: | You are a customer support agent. Search the knowledge base before answering questions. Be concise and friendly. maxSteps: 5 temperature: 0.3 tools: - search-knowledge-base - get-account-info - endpointId: create-ticket confirm: true ``` ## Requests Request types: - GenerateText - GenerateObject - Decide All AI provider connections (`Anthropic`, `OpenAI`, `Google`, `AIGateway`) provide the same `GenerateText`, `GenerateObject` and `Decide` request types — the `connectionId` selects the provider. These make a single, one-shot model call and return the result. They can be used as page requests, or as steps in [API endpoint routines](/api) — useful for classify, extract, summarize, or decide steps where the surrounding logic stays in your routine. For a closed-vocabulary decision — pick one of these options, yes or no, rate on this scale — use `Decide`. For multi-step tool use, see [agents](/agents-introduction) instead. ### Limits and cancellation Two limits bound every AI request. Set them on the request, or on the connection as the default for every request and agent that uses it: - `maxOutputTokens` caps the tokens one call generates. Without it, the provider's default applies. - `timeout` cancels a call, retries included, that runs longer than this many milliseconds. The request fails with a `ServiceError`. A call is also cancelled when the request that started it closes, so a provider call that nobody is waiting for stops instead of running, and billing, to its end: - A page request, or an API endpoint called over HTTP, is cancelled when the server's request timeout (`config.requestTimeout`, default 30 seconds) answers first, which fails it with a `ServiceError`. On the Node server it is also cancelled when the client disconnects; that is logged as a warning, not an error. - On [Vercel](/vercel), only the request timeout cancels a call: a client disconnect does not, and the call runs until it finishes, times out, or the function reaches `config.vercel.maxDuration`. Keep `config.requestTimeout` below `maxDuration`, or Vercel stops the function before the timeout can cancel anything (the build warns when it is not). - Endpoints with `async: true`, `detached: true` calls, and scheduled runs answer at once and then run to their end: the closing request does not cancel them. - An agent chat turn runs to its end after the client disconnects, so its `onFinish` hooks still save the conversation. Bound it with the agent's `timeout` and `maxSteps`. A `CallAgent` step is cancelled with the routine that runs it. ### Files in messages A file part in `messages` (`{ type: 'file', data: , mediaType }`) goes to the model as a link when the provider takes that media type as a link (the main providers do for images and PDFs). Any other file, such as a CSV or text file, is downloaded by the server and sent as content, within the request's `fileDownload` limits. The server downloads only an `https:` link to a public address, checking the address on every redirect, and a failed download names the file's host, never its link, so a signed link stays out of the server log. `Decide` takes no files. When a provider rate-limits a call (HTTP 429), the model client retries it (`maxRetries`), waiting as long as the provider's `Retry-After` asks, up to a minute. If the retries run out, the request fails with a `ServiceError`, as a 429 does on every connection, and the server log records the `Retry-After` value. ### GenerateText Generates text from a prompt. #### Properties - `model: string`: **Required** - Model id to generate with. - `prompt: string`: Text prompt. Use either `prompt` or `messages`, not both. - `messages: object[]`: Model messages (`{ role, content }`). Use either `prompt` or `messages`, not both. - `system: string`: System prompt. - `allowSystemInMessages: boolean`: Default: `false` - Allow `system` role messages in `messages`. A system message instructs the model as the app itself, so without this a request whose `messages` include one fails. Only set it when the messages come from the app, never from a user (for example a message list built from `_payload`). Use `system` for the system prompt. - `maxOutputTokens: number`: Maximum number of tokens to generate. Defaults to the connection's `maxOutputTokens`. - `timeout: number`: Milliseconds the model call may take, retries included, before it is cancelled. Defaults to the connection's `timeout`. - `fileDownload: object`: Limits on a file in `messages` the server downloads (see [Files in messages](#files-in-messages)). - `maxBytes: integer`: Default: `20971520` (20 MB) - Largest file the server downloads. A larger file fails the request. - `timeout: integer`: Default: `30000` - Milliseconds a file download may take. - `temperature: number`: Sampling temperature (0 to 2). - `topP: number`: Nucleus sampling. - `topK: number`: Only sample from the top K options for each subsequent token. - `frequencyPenalty: number`: Penalize repeated tokens by frequency. - `presencePenalty: number`: Penalize tokens that have already appeared. - `seed: number`: Seed for deterministic sampling, if supported by the model. - `stopSequences: string[]`: Sequences that stop generation. - `maxRetries: number`: Maximum number of retries. Defaults to 2. - `providerOptions: object`: Provider-specific options, keyed by provider (e.g. `anthropic`, `openai`, `google`). #### Response ```yaml text: string # The generated text. reasoningText: string # Reasoning text, when the model produced any. finishReason: string # 'stop', 'length', 'content-filter', ... usage: object # Token usage ({ inputTokens, outputTokens, totalTokens, ... }). providerMetadata: object # Provider-specific metadata, when returned. warnings: array # Call warnings from the provider, when returned. ``` #### Examples ###### Summarize text in an API endpoint routine: ```yaml api: - id: summarize-text type: Api routine: - id: summarize type: GenerateText connectionId: claude properties: model: claude-haiku-4-5 system: You are a concise summarizer. prompt: _payload: text - ':return': summary: _step: summarize.text ``` ###### Text generation as a page request: ```yaml requests: - id: generate_reply type: GenerateText connectionId: claude payload: message: _state: customer_message properties: model: claude-haiku-4-5 system: Draft a friendly reply to the customer message. prompt: _payload: message ``` ### GenerateObject Generates a structured object matching a JSON Schema. Ideal for classification, extraction, and routing decisions — the result lands in `_step` (or `_request`) as plain data your config can act on. #### Properties Takes the same properties as `GenerateText`, except `stopSequences`, plus: - `schema: object`: **Required** - JSON Schema describing the object the model must generate. - `schemaName: string`: Optional name for the output schema, passed to the model. - `schemaDescription: string`: Optional description of the output schema, passed to the model. #### Response ```yaml object: object # The generated object, matching the schema. reasoningText: string # Reasoning text, when the model produced any. finishReason: string # 'stop', 'length', 'content-filter', ... usage: object # Token usage ({ inputTokens, outputTokens, totalTokens, ... }). providerMetadata: object # Provider-specific metadata, when returned. warnings: array # Call warnings from the provider, when returned. ``` #### Examples ###### Classify then route in an API endpoint routine: ```yaml api: - id: triage-ticket type: Api routine: - id: classify type: GenerateObject connectionId: claude properties: model: claude-haiku-4-5 prompt: _payload: ticket_text schema: type: object properties: category: type: string enum: - billing - technical - other urgent: type: boolean - ':if': _eq: - _step: classify.object.urgent - true ':then': - id: escalate type: CallApi properties: endpointId: escalate-ticket payload: category: _step: classify.object.category ``` ### Decide Makes typed decisions about a piece of state: pick one of a set of options, judge a statement true or false, or place something on an ordered scale. Every answer comes back in the shape you declared, with a confidence, so a routine can branch on it — and send a low-confidence answer to a person instead. The build knows every option, so a routine branch that compares an answer with a name that is not one of its options — `_eq: [{ _step: triage.team.choice }, bil-ling]` — fails the build rather than silently never matching. A `_step` reference to a question the step does not ask, or to a field its answer does not have, fails the build the same way. `Decide` has two backends behind the same interface: - `evaluation` — an evaluation model, such as TypeSafe's Jev (`typesafe-ai/jev`) on the [AI Gateway](/AIGateway). It returns a probability for every option in one pass, in under a second, and bills input tokens only. The default, and only available, on the `AIGateway` connection. - `structured-output` — any language model, asked for the answers as structured output that follows a schema of the questions. Providers do not all enforce the schema, so Lowdefy checks each answer against its question (see `null` answers below). Available on every AI connection, and the default on `Anthropic`, `OpenAI` and `Google`. Its confidence is the model's own estimate, not a calibrated probability. Keep the state to what the questions need: an evaluation model reads the state literally, and accuracy drops as it grows. A `Decide` is never a security boundary — text in the state can argue for its own answer. #### Properties - `model: string`: **Required** - Model id to decide with, e.g. `typesafe-ai/jev`. Pin a version wherever a confidence threshold is tuned — a `latest` alias changes answers on release. - `state: string | object | array`: **Required** - What the questions are about. - `questions: object`: **Required** - Named questions, answered together against the state. Each question id becomes a key of the response (`usage` is reserved). A question is one of: - `{ choice: string, options: object }` - Pick exactly one option. `options` maps option names (2 to 255) to what each one means (`null` for no description), or to an object with a `description` — its other keys are ignored, so one file can describe the options for `Decide` and for an [`IntentInput`](/IntentInput) block. - `{ yesno: string, criteria?: { yes: string, no: string } }` - Judge a statement true or false. `criteria` optionally says what counts as yes and as no. - `{ score: string, levels: string[] }` - Place the state on 2 to 10 ordered levels, lowest first. - `backend: enum`: `evaluation` or `structured-output`. Defaults to `evaluation` on `AIGateway` and `structured-output` elsewhere. - `maxOutputTokens: number`: Maximum number of tokens to generate, on the `structured-output` backend (an evaluation model generates no text). Defaults to the connection's `maxOutputTokens`. - `timeout: number`: Milliseconds the model call may take, retries included, before it is cancelled. Defaults to the connection's `timeout`. - `maxRetries: number`: Maximum number of retries. Defaults to 2. - `providerOptions: object`: Provider-specific options, keyed by provider (e.g. `gateway: { zeroDataRetention: true }`). #### Response One key per question, plus `usage`: ```yaml : choice: string # The chosen option. confidence: number # 0 to 1. probabilities: object # Probability per option (evaluation backend), else null. : answer: boolean # true when the statement holds. probability: number # Probability that it holds. confidence: number # Probability of the given answer. : level: string # The most likely level. index: number # Its position, from 0. score: number # Fractional position on the scale (evaluation backend). confidence: number # 0 to 1. probabilities: object # Probability per level (evaluation backend), else null. usage: object # Token usage ({ inputTokens, outputTokens, totalTokens }). ``` An answer outside its question's terms (an option or level the question does not have, a yes/no that is not true or false, or no answer at all) comes back with every field `null`, its confidence included. `_lt` compares `null` below any number, so a confidence gate such as `_lt: [{ _step: triage.team.confidence }, 0.7]` holds it back like a low-confidence answer, and a branch on the choice matches none of the options. #### Examples ###### Route a support ticket, and queue uncertain ones for a person: ```yaml api: - id: triage-ticket type: Api routine: - id: triage type: Decide connectionId: gateway properties: model: typesafe-ai/jev state: subject: _payload: subject body: _payload: body questions: team: choice: Which team handles this ticket options: billing: Invoices, charges and refunds tech: Outages and bugs sales: Plans and quotes down: yesno: The customer says the service is down providerOptions: gateway: zeroDataRetention: true - ':if': _lt: - _step: triage.team.confidence - 0.7 ':then': - id: queue_review type: MongoDBInsertOne connectionId: review_queue properties: doc: subject: _payload: subject suggested_team: _step: triage.team.choice - ':return': status: review - ':if': _eq: - _step: triage.team.choice - billing ':then': - id: open_billing_case type: CallApi properties: endpointId: open-billing-case - ':return': team: _step: triage.team.choice urgent: _step: triage.down.answer ``` ###### The same decision with any language model: ```yaml requests: - id: rate_review type: Decide connectionId: claude payload: review: _state: review_text properties: model: claude-haiku-4-5 state: _payload: review questions: sentiment: score: How positive is this review levels: - Very negative - Negative - Neutral - Positive - Very positive ``` --- url: https://docs.lowdefy.com/md/connections/google.md --- # Google The Google connection connects to the [Google Gemini API](https://ai.google.dev/docs) and provides the `GeminiAgent` agent type for use with [Lowdefy agents](/agents-introduction). > The Google connection is provided by the `@lowdefy/connection-google` package, which is included by default. ## Connections ### Google #### Properties - `apiKey: string`: __Required__ - Google API key. Use [`_secret`](/_secret) to reference this securely. - `baseURL: string`: Optional base URL for the Google API. - `maxOutputTokens: number`: Default maximum number of tokens a model call generates, for the requests and agents on this connection that do not set their own. - `timeout: number`: Default milliseconds a model call may take, retries included, before it is cancelled, for the requests and agents on this connection that do not set their own. See _Limits and cancellation_ below. ###### Connection example: ```yaml connections: - id: google type: Google properties: apiKey: _secret: GOOGLE_API_KEY ``` ## Agent Types ### GeminiAgent The `GeminiAgent` supports all [common agent properties](/agents-introduction) plus the following: | Property | Type | Description | | --- | --- | --- | | `thinkingConfig` | object | Configuration for the model's thinking process. | | `thinkingConfig.thinkingBudget` | integer | Maximum thinking tokens (Gemini 2.5 models). Set to `0` to disable. | | `thinkingConfig.thinkingLevel` | string | Thinking depth (Gemini 3 models): `'minimal'`, `'low'`, `'medium'`, or `'high'`. | | `thinkingConfig.includeThoughts` | boolean | Return thought summaries in the response. | | `safetySettings` | object[] | Safety filter settings. | | `safetySettings[].category` | string | Safety category (e.g. `'HARM_CATEGORY_DANGEROUS_CONTENT'`). | | `safetySettings[].threshold` | string | Block threshold (e.g. `'BLOCK_ONLY_HIGH'`, `'BLOCK_NONE'`). | ###### Agent with thinking enabled: ```yaml connections: - id: google type: Google properties: apiKey: _secret: GOOGLE_API_KEY agents: - id: research_agent type: GeminiAgent connectionId: google properties: model: gemini-2.5-pro thinkingConfig: thinkingBudget: 8000 includeThoughts: true instructions: | You are a research assistant. Search for information and provide well-sourced answers. Think through complex questions step by step. maxSteps: 8 tools: - search-knowledge-base - get-document ``` ###### Agent with safety settings: ```yaml agents: - id: creative_writer type: GeminiAgent connectionId: google properties: model: gemini-2.5-pro temperature: 0.9 safetySettings: - category: HARM_CATEGORY_DANGEROUS_CONTENT threshold: BLOCK_ONLY_HIGH instructions: You are a creative writing assistant. ``` ## Requests Request types: - GenerateText - GenerateObject - Decide All AI provider connections (`Anthropic`, `OpenAI`, `Google`, `AIGateway`) provide the same `GenerateText`, `GenerateObject` and `Decide` request types — the `connectionId` selects the provider. These make a single, one-shot model call and return the result. They can be used as page requests, or as steps in [API endpoint routines](/api) — useful for classify, extract, summarize, or decide steps where the surrounding logic stays in your routine. For a closed-vocabulary decision — pick one of these options, yes or no, rate on this scale — use `Decide`. For multi-step tool use, see [agents](/agents-introduction) instead. ### Limits and cancellation Two limits bound every AI request. Set them on the request, or on the connection as the default for every request and agent that uses it: - `maxOutputTokens` caps the tokens one call generates. Without it, the provider's default applies. - `timeout` cancels a call, retries included, that runs longer than this many milliseconds. The request fails with a `ServiceError`. A call is also cancelled when the request that started it closes, so a provider call that nobody is waiting for stops instead of running, and billing, to its end: - A page request, or an API endpoint called over HTTP, is cancelled when the server's request timeout (`config.requestTimeout`, default 30 seconds) answers first, which fails it with a `ServiceError`. On the Node server it is also cancelled when the client disconnects; that is logged as a warning, not an error. - On [Vercel](/vercel), only the request timeout cancels a call: a client disconnect does not, and the call runs until it finishes, times out, or the function reaches `config.vercel.maxDuration`. Keep `config.requestTimeout` below `maxDuration`, or Vercel stops the function before the timeout can cancel anything (the build warns when it is not). - Endpoints with `async: true`, `detached: true` calls, and scheduled runs answer at once and then run to their end: the closing request does not cancel them. - An agent chat turn runs to its end after the client disconnects, so its `onFinish` hooks still save the conversation. Bound it with the agent's `timeout` and `maxSteps`. A `CallAgent` step is cancelled with the routine that runs it. ### Files in messages A file part in `messages` (`{ type: 'file', data: , mediaType }`) goes to the model as a link when the provider takes that media type as a link (the main providers do for images and PDFs). Any other file, such as a CSV or text file, is downloaded by the server and sent as content, within the request's `fileDownload` limits. The server downloads only an `https:` link to a public address, checking the address on every redirect, and a failed download names the file's host, never its link, so a signed link stays out of the server log. `Decide` takes no files. When a provider rate-limits a call (HTTP 429), the model client retries it (`maxRetries`), waiting as long as the provider's `Retry-After` asks, up to a minute. If the retries run out, the request fails with a `ServiceError`, as a 429 does on every connection, and the server log records the `Retry-After` value. ### GenerateText Generates text from a prompt. #### Properties - `model: string`: **Required** - Model id to generate with. - `prompt: string`: Text prompt. Use either `prompt` or `messages`, not both. - `messages: object[]`: Model messages (`{ role, content }`). Use either `prompt` or `messages`, not both. - `system: string`: System prompt. - `allowSystemInMessages: boolean`: Default: `false` - Allow `system` role messages in `messages`. A system message instructs the model as the app itself, so without this a request whose `messages` include one fails. Only set it when the messages come from the app, never from a user (for example a message list built from `_payload`). Use `system` for the system prompt. - `maxOutputTokens: number`: Maximum number of tokens to generate. Defaults to the connection's `maxOutputTokens`. - `timeout: number`: Milliseconds the model call may take, retries included, before it is cancelled. Defaults to the connection's `timeout`. - `fileDownload: object`: Limits on a file in `messages` the server downloads (see [Files in messages](#files-in-messages)). - `maxBytes: integer`: Default: `20971520` (20 MB) - Largest file the server downloads. A larger file fails the request. - `timeout: integer`: Default: `30000` - Milliseconds a file download may take. - `temperature: number`: Sampling temperature (0 to 2). - `topP: number`: Nucleus sampling. - `topK: number`: Only sample from the top K options for each subsequent token. - `frequencyPenalty: number`: Penalize repeated tokens by frequency. - `presencePenalty: number`: Penalize tokens that have already appeared. - `seed: number`: Seed for deterministic sampling, if supported by the model. - `stopSequences: string[]`: Sequences that stop generation. - `maxRetries: number`: Maximum number of retries. Defaults to 2. - `providerOptions: object`: Provider-specific options, keyed by provider (e.g. `anthropic`, `openai`, `google`). #### Response ```yaml text: string # The generated text. reasoningText: string # Reasoning text, when the model produced any. finishReason: string # 'stop', 'length', 'content-filter', ... usage: object # Token usage ({ inputTokens, outputTokens, totalTokens, ... }). providerMetadata: object # Provider-specific metadata, when returned. warnings: array # Call warnings from the provider, when returned. ``` #### Examples ###### Summarize text in an API endpoint routine: ```yaml api: - id: summarize-text type: Api routine: - id: summarize type: GenerateText connectionId: claude properties: model: claude-haiku-4-5 system: You are a concise summarizer. prompt: _payload: text - ':return': summary: _step: summarize.text ``` ###### Text generation as a page request: ```yaml requests: - id: generate_reply type: GenerateText connectionId: claude payload: message: _state: customer_message properties: model: claude-haiku-4-5 system: Draft a friendly reply to the customer message. prompt: _payload: message ``` ### GenerateObject Generates a structured object matching a JSON Schema. Ideal for classification, extraction, and routing decisions — the result lands in `_step` (or `_request`) as plain data your config can act on. #### Properties Takes the same properties as `GenerateText`, except `stopSequences`, plus: - `schema: object`: **Required** - JSON Schema describing the object the model must generate. - `schemaName: string`: Optional name for the output schema, passed to the model. - `schemaDescription: string`: Optional description of the output schema, passed to the model. #### Response ```yaml object: object # The generated object, matching the schema. reasoningText: string # Reasoning text, when the model produced any. finishReason: string # 'stop', 'length', 'content-filter', ... usage: object # Token usage ({ inputTokens, outputTokens, totalTokens, ... }). providerMetadata: object # Provider-specific metadata, when returned. warnings: array # Call warnings from the provider, when returned. ``` #### Examples ###### Classify then route in an API endpoint routine: ```yaml api: - id: triage-ticket type: Api routine: - id: classify type: GenerateObject connectionId: claude properties: model: claude-haiku-4-5 prompt: _payload: ticket_text schema: type: object properties: category: type: string enum: - billing - technical - other urgent: type: boolean - ':if': _eq: - _step: classify.object.urgent - true ':then': - id: escalate type: CallApi properties: endpointId: escalate-ticket payload: category: _step: classify.object.category ``` ### Decide Makes typed decisions about a piece of state: pick one of a set of options, judge a statement true or false, or place something on an ordered scale. Every answer comes back in the shape you declared, with a confidence, so a routine can branch on it — and send a low-confidence answer to a person instead. The build knows every option, so a routine branch that compares an answer with a name that is not one of its options — `_eq: [{ _step: triage.team.choice }, bil-ling]` — fails the build rather than silently never matching. A `_step` reference to a question the step does not ask, or to a field its answer does not have, fails the build the same way. `Decide` has two backends behind the same interface: - `evaluation` — an evaluation model, such as TypeSafe's Jev (`typesafe-ai/jev`) on the [AI Gateway](/AIGateway). It returns a probability for every option in one pass, in under a second, and bills input tokens only. The default, and only available, on the `AIGateway` connection. - `structured-output` — any language model, asked for the answers as structured output that follows a schema of the questions. Providers do not all enforce the schema, so Lowdefy checks each answer against its question (see `null` answers below). Available on every AI connection, and the default on `Anthropic`, `OpenAI` and `Google`. Its confidence is the model's own estimate, not a calibrated probability. Keep the state to what the questions need: an evaluation model reads the state literally, and accuracy drops as it grows. A `Decide` is never a security boundary — text in the state can argue for its own answer. #### Properties - `model: string`: **Required** - Model id to decide with, e.g. `typesafe-ai/jev`. Pin a version wherever a confidence threshold is tuned — a `latest` alias changes answers on release. - `state: string | object | array`: **Required** - What the questions are about. - `questions: object`: **Required** - Named questions, answered together against the state. Each question id becomes a key of the response (`usage` is reserved). A question is one of: - `{ choice: string, options: object }` - Pick exactly one option. `options` maps option names (2 to 255) to what each one means (`null` for no description), or to an object with a `description` — its other keys are ignored, so one file can describe the options for `Decide` and for an [`IntentInput`](/IntentInput) block. - `{ yesno: string, criteria?: { yes: string, no: string } }` - Judge a statement true or false. `criteria` optionally says what counts as yes and as no. - `{ score: string, levels: string[] }` - Place the state on 2 to 10 ordered levels, lowest first. - `backend: enum`: `evaluation` or `structured-output`. Defaults to `evaluation` on `AIGateway` and `structured-output` elsewhere. - `maxOutputTokens: number`: Maximum number of tokens to generate, on the `structured-output` backend (an evaluation model generates no text). Defaults to the connection's `maxOutputTokens`. - `timeout: number`: Milliseconds the model call may take, retries included, before it is cancelled. Defaults to the connection's `timeout`. - `maxRetries: number`: Maximum number of retries. Defaults to 2. - `providerOptions: object`: Provider-specific options, keyed by provider (e.g. `gateway: { zeroDataRetention: true }`). #### Response One key per question, plus `usage`: ```yaml : choice: string # The chosen option. confidence: number # 0 to 1. probabilities: object # Probability per option (evaluation backend), else null. : answer: boolean # true when the statement holds. probability: number # Probability that it holds. confidence: number # Probability of the given answer. : level: string # The most likely level. index: number # Its position, from 0. score: number # Fractional position on the scale (evaluation backend). confidence: number # 0 to 1. probabilities: object # Probability per level (evaluation backend), else null. usage: object # Token usage ({ inputTokens, outputTokens, totalTokens }). ``` An answer outside its question's terms (an option or level the question does not have, a yes/no that is not true or false, or no answer at all) comes back with every field `null`, its confidence included. `_lt` compares `null` below any number, so a confidence gate such as `_lt: [{ _step: triage.team.confidence }, 0.7]` holds it back like a low-confidence answer, and a branch on the choice matches none of the options. #### Examples ###### Route a support ticket, and queue uncertain ones for a person: ```yaml api: - id: triage-ticket type: Api routine: - id: triage type: Decide connectionId: gateway properties: model: typesafe-ai/jev state: subject: _payload: subject body: _payload: body questions: team: choice: Which team handles this ticket options: billing: Invoices, charges and refunds tech: Outages and bugs sales: Plans and quotes down: yesno: The customer says the service is down providerOptions: gateway: zeroDataRetention: true - ':if': _lt: - _step: triage.team.confidence - 0.7 ':then': - id: queue_review type: MongoDBInsertOne connectionId: review_queue properties: doc: subject: _payload: subject suggested_team: _step: triage.team.choice - ':return': status: review - ':if': _eq: - _step: triage.team.choice - billing ':then': - id: open_billing_case type: CallApi properties: endpointId: open-billing-case - ':return': team: _step: triage.team.choice urgent: _step: triage.down.answer ``` ###### The same decision with any language model: ```yaml requests: - id: rate_review type: Decide connectionId: claude payload: review: _state: review_text properties: model: claude-haiku-4-5 state: _payload: review questions: sentiment: score: How positive is this review levels: - Very negative - Negative - Neutral - Positive - Very positive ``` --- url: https://docs.lowdefy.com/md/connections/mcp.md --- # MCP The MCP connection defines a [Model Context Protocol](https://modelcontextprotocol.io/) server that provides tools to [Lowdefy agents](/agents-introduction). MCP is an open standard for connecting AI models to external tools and data sources. > The MCP connection is provided by the `@lowdefy/connection-mcp` package, which is included by default. MCP connections are referenced from the `mcp` array on an agent. The agent automatically discovers and uses the tools the MCP server provides. See [MCP Tools](/agent-mcp-tools) for details on using MCP with agents and for inline configuration without a connection. ## Connections ### Mcp #### Properties | Property | Type | Default | Description | | --- | --- | --- | --- | | `transport` | string | `'http'` | Transport protocol: `'http'`, `'sse'`, or `'stdio'`. | | `url` | string | | URL for the MCP server. Required for `http` and `sse` transports. | | `headers` | object | | HTTP headers. Only for `http` and `sse`. __Operators are evaluated__. | | `command` | string | | Command to run. Required for `stdio` transport. | | `args` | string[] | | Arguments for the stdio command. | | `env` | object | | Environment variables for the stdio command. __Operators are evaluated__. | ### HTTP Transport Connect to a remote MCP server over HTTP: ```yaml connections: - id: mcp_tools type: Mcp properties: url: https://mcp.example.com/tools transport: http ``` ###### With authentication: ```yaml connections: - id: mcp_tools type: Mcp properties: url: https://mcp.example.com/tools transport: http headers: Authorization: _string.concat: - 'Bearer ' - _secret: MCP_API_KEY ``` ### SSE Transport Connect using Server-Sent Events: ```yaml connections: - id: mcp_sse type: Mcp properties: url: https://mcp.example.com/sse transport: sse ``` ### Stdio Transport Launch a local MCP server process. Useful for npm-published MCP servers: ###### File system access: ```yaml connections: - id: mcp_filesystem type: Mcp properties: transport: stdio command: npx args: - '-y' - '@modelcontextprotocol/server-filesystem' - '/data/documents' ``` ###### GitHub integration: ```yaml connections: - id: mcp_github type: Mcp properties: transport: stdio command: npx args: - '-y' - '@modelcontextprotocol/server-github' env: GITHUB_TOKEN: _secret: GITHUB_TOKEN ``` ###### Custom server with environment variables: ```yaml connections: - id: mcp_database type: Mcp properties: transport: stdio command: node args: - ./mcp-servers/database.js env: DATABASE_URL: _secret: DATABASE_URL ``` ## Usage Reference MCP connections in the `mcp` array on an agent: ```yaml agents: - id: dev_assistant type: ClaudeAgent connectionId: anthropic properties: model: claude-sonnet-4-20250514 instructions: | You help developers with code and GitHub issues. Use GitHub tools to search issues, read files, and create pull requests. mcp: - mcp_github - mcp_filesystem ``` MCP servers can also be defined inline on the agent without a connection. See [MCP Tools](/agent-mcp-tools) for inline configuration. --- url: https://docs.lowdefy.com/md/connections/openai-connection.md --- # OpenAI The OpenAI connection connects to the [OpenAI API](https://platform.openai.com/docs/) and provides the `OpenAIAgent` agent type for use with [Lowdefy agents](/agents-introduction). > The OpenAI connection is provided by the `@lowdefy/connection-openai` package, which is included by default. ## Connections ### OpenAI #### Properties - `apiKey: string`: __Required__ - OpenAI API key. Use [`_secret`](/_secret) to reference this securely. - `baseURL: string`: Optional base URL. Use this to connect to Azure OpenAI or any OpenAI-compatible endpoint. - `maxOutputTokens: number`: Default maximum number of tokens a model call generates, for the requests and agents on this connection that do not set their own. - `timeout: number`: Default milliseconds a model call may take, retries included, before it is cancelled, for the requests and agents on this connection that do not set their own. See _Limits and cancellation_ below. ###### Connection example: ```yaml connections: - id: openai type: OpenAI properties: apiKey: _secret: OPENAI_API_KEY ``` ###### Azure OpenAI: ```yaml connections: - id: azure_openai type: OpenAI properties: apiKey: _secret: AZURE_OPENAI_API_KEY baseURL: https://my-resource.openai.azure.com/openai/deployments/my-deployment ``` ## Agent Types ### OpenAIAgent The `OpenAIAgent` supports all [common agent properties](/agents-introduction) plus the following: | Property | Type | Description | | --- | --- | --- | | `reasoningEffort` | string | Reasoning effort for reasoning models (o3, o4-mini): `'none'`, `'minimal'`, `'low'`, `'medium'`, `'high'`, or `'xhigh'`. | | `reasoningSummary` | string | Reasoning summary mode: `'auto'` or `'detailed'`. | > `reasoningEffort` and `reasoningSummary` only apply to OpenAI reasoning models (o3, o4-mini). They are ignored for standard models like gpt-4o. ###### Standard model: ```yaml agents: - id: assistant type: OpenAIAgent connectionId: openai properties: model: gpt-4o instructions: | You are a helpful assistant for our internal tools. Search the documentation when users ask questions. maxSteps: 5 tools: - search-docs - get-user-permissions ``` ###### Reasoning model for complex analysis: ```yaml agents: - id: code_reviewer type: OpenAIAgent connectionId: openai properties: model: o4-mini reasoningEffort: high reasoningSummary: detailed instructions: | You review code changes for bugs, security issues, and performance problems. Be thorough and specific. tools: - get-pull-request - get-file-contents ``` ## Requests Request types: - GenerateText - GenerateObject - Decide All AI provider connections (`Anthropic`, `OpenAI`, `Google`, `AIGateway`) provide the same `GenerateText`, `GenerateObject` and `Decide` request types — the `connectionId` selects the provider. These make a single, one-shot model call and return the result. They can be used as page requests, or as steps in [API endpoint routines](/api) — useful for classify, extract, summarize, or decide steps where the surrounding logic stays in your routine. For a closed-vocabulary decision — pick one of these options, yes or no, rate on this scale — use `Decide`. For multi-step tool use, see [agents](/agents-introduction) instead. ### Limits and cancellation Two limits bound every AI request. Set them on the request, or on the connection as the default for every request and agent that uses it: - `maxOutputTokens` caps the tokens one call generates. Without it, the provider's default applies. - `timeout` cancels a call, retries included, that runs longer than this many milliseconds. The request fails with a `ServiceError`. A call is also cancelled when the request that started it closes, so a provider call that nobody is waiting for stops instead of running, and billing, to its end: - A page request, or an API endpoint called over HTTP, is cancelled when the server's request timeout (`config.requestTimeout`, default 30 seconds) answers first, which fails it with a `ServiceError`. On the Node server it is also cancelled when the client disconnects; that is logged as a warning, not an error. - On [Vercel](/vercel), only the request timeout cancels a call: a client disconnect does not, and the call runs until it finishes, times out, or the function reaches `config.vercel.maxDuration`. Keep `config.requestTimeout` below `maxDuration`, or Vercel stops the function before the timeout can cancel anything (the build warns when it is not). - Endpoints with `async: true`, `detached: true` calls, and scheduled runs answer at once and then run to their end: the closing request does not cancel them. - An agent chat turn runs to its end after the client disconnects, so its `onFinish` hooks still save the conversation. Bound it with the agent's `timeout` and `maxSteps`. A `CallAgent` step is cancelled with the routine that runs it. ### Files in messages A file part in `messages` (`{ type: 'file', data: , mediaType }`) goes to the model as a link when the provider takes that media type as a link (the main providers do for images and PDFs). Any other file, such as a CSV or text file, is downloaded by the server and sent as content, within the request's `fileDownload` limits. The server downloads only an `https:` link to a public address, checking the address on every redirect, and a failed download names the file's host, never its link, so a signed link stays out of the server log. `Decide` takes no files. When a provider rate-limits a call (HTTP 429), the model client retries it (`maxRetries`), waiting as long as the provider's `Retry-After` asks, up to a minute. If the retries run out, the request fails with a `ServiceError`, as a 429 does on every connection, and the server log records the `Retry-After` value. ### GenerateText Generates text from a prompt. #### Properties - `model: string`: **Required** - Model id to generate with. - `prompt: string`: Text prompt. Use either `prompt` or `messages`, not both. - `messages: object[]`: Model messages (`{ role, content }`). Use either `prompt` or `messages`, not both. - `system: string`: System prompt. - `allowSystemInMessages: boolean`: Default: `false` - Allow `system` role messages in `messages`. A system message instructs the model as the app itself, so without this a request whose `messages` include one fails. Only set it when the messages come from the app, never from a user (for example a message list built from `_payload`). Use `system` for the system prompt. - `maxOutputTokens: number`: Maximum number of tokens to generate. Defaults to the connection's `maxOutputTokens`. - `timeout: number`: Milliseconds the model call may take, retries included, before it is cancelled. Defaults to the connection's `timeout`. - `fileDownload: object`: Limits on a file in `messages` the server downloads (see [Files in messages](#files-in-messages)). - `maxBytes: integer`: Default: `20971520` (20 MB) - Largest file the server downloads. A larger file fails the request. - `timeout: integer`: Default: `30000` - Milliseconds a file download may take. - `temperature: number`: Sampling temperature (0 to 2). - `topP: number`: Nucleus sampling. - `topK: number`: Only sample from the top K options for each subsequent token. - `frequencyPenalty: number`: Penalize repeated tokens by frequency. - `presencePenalty: number`: Penalize tokens that have already appeared. - `seed: number`: Seed for deterministic sampling, if supported by the model. - `stopSequences: string[]`: Sequences that stop generation. - `maxRetries: number`: Maximum number of retries. Defaults to 2. - `providerOptions: object`: Provider-specific options, keyed by provider (e.g. `anthropic`, `openai`, `google`). #### Response ```yaml text: string # The generated text. reasoningText: string # Reasoning text, when the model produced any. finishReason: string # 'stop', 'length', 'content-filter', ... usage: object # Token usage ({ inputTokens, outputTokens, totalTokens, ... }). providerMetadata: object # Provider-specific metadata, when returned. warnings: array # Call warnings from the provider, when returned. ``` #### Examples ###### Summarize text in an API endpoint routine: ```yaml api: - id: summarize-text type: Api routine: - id: summarize type: GenerateText connectionId: claude properties: model: claude-haiku-4-5 system: You are a concise summarizer. prompt: _payload: text - ':return': summary: _step: summarize.text ``` ###### Text generation as a page request: ```yaml requests: - id: generate_reply type: GenerateText connectionId: claude payload: message: _state: customer_message properties: model: claude-haiku-4-5 system: Draft a friendly reply to the customer message. prompt: _payload: message ``` ### GenerateObject Generates a structured object matching a JSON Schema. Ideal for classification, extraction, and routing decisions — the result lands in `_step` (or `_request`) as plain data your config can act on. #### Properties Takes the same properties as `GenerateText`, except `stopSequences`, plus: - `schema: object`: **Required** - JSON Schema describing the object the model must generate. - `schemaName: string`: Optional name for the output schema, passed to the model. - `schemaDescription: string`: Optional description of the output schema, passed to the model. #### Response ```yaml object: object # The generated object, matching the schema. reasoningText: string # Reasoning text, when the model produced any. finishReason: string # 'stop', 'length', 'content-filter', ... usage: object # Token usage ({ inputTokens, outputTokens, totalTokens, ... }). providerMetadata: object # Provider-specific metadata, when returned. warnings: array # Call warnings from the provider, when returned. ``` #### Examples ###### Classify then route in an API endpoint routine: ```yaml api: - id: triage-ticket type: Api routine: - id: classify type: GenerateObject connectionId: claude properties: model: claude-haiku-4-5 prompt: _payload: ticket_text schema: type: object properties: category: type: string enum: - billing - technical - other urgent: type: boolean - ':if': _eq: - _step: classify.object.urgent - true ':then': - id: escalate type: CallApi properties: endpointId: escalate-ticket payload: category: _step: classify.object.category ``` ### Decide Makes typed decisions about a piece of state: pick one of a set of options, judge a statement true or false, or place something on an ordered scale. Every answer comes back in the shape you declared, with a confidence, so a routine can branch on it — and send a low-confidence answer to a person instead. The build knows every option, so a routine branch that compares an answer with a name that is not one of its options — `_eq: [{ _step: triage.team.choice }, bil-ling]` — fails the build rather than silently never matching. A `_step` reference to a question the step does not ask, or to a field its answer does not have, fails the build the same way. `Decide` has two backends behind the same interface: - `evaluation` — an evaluation model, such as TypeSafe's Jev (`typesafe-ai/jev`) on the [AI Gateway](/AIGateway). It returns a probability for every option in one pass, in under a second, and bills input tokens only. The default, and only available, on the `AIGateway` connection. - `structured-output` — any language model, asked for the answers as structured output that follows a schema of the questions. Providers do not all enforce the schema, so Lowdefy checks each answer against its question (see `null` answers below). Available on every AI connection, and the default on `Anthropic`, `OpenAI` and `Google`. Its confidence is the model's own estimate, not a calibrated probability. Keep the state to what the questions need: an evaluation model reads the state literally, and accuracy drops as it grows. A `Decide` is never a security boundary — text in the state can argue for its own answer. #### Properties - `model: string`: **Required** - Model id to decide with, e.g. `typesafe-ai/jev`. Pin a version wherever a confidence threshold is tuned — a `latest` alias changes answers on release. - `state: string | object | array`: **Required** - What the questions are about. - `questions: object`: **Required** - Named questions, answered together against the state. Each question id becomes a key of the response (`usage` is reserved). A question is one of: - `{ choice: string, options: object }` - Pick exactly one option. `options` maps option names (2 to 255) to what each one means (`null` for no description), or to an object with a `description` — its other keys are ignored, so one file can describe the options for `Decide` and for an [`IntentInput`](/IntentInput) block. - `{ yesno: string, criteria?: { yes: string, no: string } }` - Judge a statement true or false. `criteria` optionally says what counts as yes and as no. - `{ score: string, levels: string[] }` - Place the state on 2 to 10 ordered levels, lowest first. - `backend: enum`: `evaluation` or `structured-output`. Defaults to `evaluation` on `AIGateway` and `structured-output` elsewhere. - `maxOutputTokens: number`: Maximum number of tokens to generate, on the `structured-output` backend (an evaluation model generates no text). Defaults to the connection's `maxOutputTokens`. - `timeout: number`: Milliseconds the model call may take, retries included, before it is cancelled. Defaults to the connection's `timeout`. - `maxRetries: number`: Maximum number of retries. Defaults to 2. - `providerOptions: object`: Provider-specific options, keyed by provider (e.g. `gateway: { zeroDataRetention: true }`). #### Response One key per question, plus `usage`: ```yaml : choice: string # The chosen option. confidence: number # 0 to 1. probabilities: object # Probability per option (evaluation backend), else null. : answer: boolean # true when the statement holds. probability: number # Probability that it holds. confidence: number # Probability of the given answer. : level: string # The most likely level. index: number # Its position, from 0. score: number # Fractional position on the scale (evaluation backend). confidence: number # 0 to 1. probabilities: object # Probability per level (evaluation backend), else null. usage: object # Token usage ({ inputTokens, outputTokens, totalTokens }). ``` An answer outside its question's terms (an option or level the question does not have, a yes/no that is not true or false, or no answer at all) comes back with every field `null`, its confidence included. `_lt` compares `null` below any number, so a confidence gate such as `_lt: [{ _step: triage.team.confidence }, 0.7]` holds it back like a low-confidence answer, and a branch on the choice matches none of the options. #### Examples ###### Route a support ticket, and queue uncertain ones for a person: ```yaml api: - id: triage-ticket type: Api routine: - id: triage type: Decide connectionId: gateway properties: model: typesafe-ai/jev state: subject: _payload: subject body: _payload: body questions: team: choice: Which team handles this ticket options: billing: Invoices, charges and refunds tech: Outages and bugs sales: Plans and quotes down: yesno: The customer says the service is down providerOptions: gateway: zeroDataRetention: true - ':if': _lt: - _step: triage.team.confidence - 0.7 ':then': - id: queue_review type: MongoDBInsertOne connectionId: review_queue properties: doc: subject: _payload: subject suggested_team: _step: triage.team.choice - ':return': status: review - ':if': _eq: - _step: triage.team.choice - billing ':then': - id: open_billing_case type: CallApi properties: endpointId: open-billing-case - ':return': team: _step: triage.team.choice urgent: _step: triage.down.answer ``` ###### The same decision with any language model: ```yaml requests: - id: rate_review type: Decide connectionId: claude payload: review: _state: review_text properties: model: claude-haiku-4-5 state: _payload: review questions: sentiment: score: How positive is this review levels: - Very negative - Negative - Neutral - Positive - Very positive ``` --- url: https://docs.lowdefy.com/md/connections/amazonredshift.md --- # Amazon Redshift The [Knex](/Knex) connection can be used to connect to a [Amazon Redshift](https://aws.amazon.com/redshift) database. ## Connections Connection types: - Knex ### Knex #### Properties - `client: enum`: __Required__ - Should be `redshift` to connect to Amazon Redshift. - `connection: object | string`: __Required__ - Connection string or object to pass to the [`pg`](https://www.npmjs.com/package/pg) database client. - `useNullAsDefault: boolean`: If true, undefined keys are replaced with NULL instead of DEFAULT. #### Examples ###### Connection object: ```yaml connections: - id: redshift type: Knex properties: client: redshift connection: host: _secret: REDSHIFT_HOST database: _secret: REDSHIFT_DB user: _secret: REDSHIFT_USER password: _secret: REDSHIFT_PASSWORD ``` Environment variables: ``` LOWDEFY_SECRET_REDSHIFT_HOST = examplecluster.XXXXXXXXXXXX.us-west-2.redshift.amazonaws.com LOWDEFY_SECRET_REDSHIFT_REDSHIFT_DB = db LOWDEFY_SECRET_REDSHIFT_USER = user LOWDEFY_SECRET_REDSHIFT_PASSWORD = password ``` ## Requests Request types: - KnexBuilder - KnexRaw ### KnexBuilder #### Properties - `query: object[]`: **Required** - SQL query builder array. An array of objects, with a single key which is the name of the knex builder function. The value should be an array of arguments to pass to the builder function. - `tableName: string | object`: The name of the table to query from. #### Examples ###### Build a query: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex payload: name: _state: name properties: query: - select: - '*' - from: - users - where: - name - _payload: name ``` ###### Using `tableName`: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex payload: name: _state: name properties: tableName: users query: - select: - '*' - where: - name - _payload: name ``` ###### Aliases: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex properties: tableName: a: tableA b: tableB query: - select: - aField: 'a.field' - bField: 'b.field' - limit: - 1 ``` ### KnexRaw #### Properties - `query: string`: **Required** - SQL query string. - `parameters: string | number | array | object`: SQL query parameters. #### Examples ###### Simple raw query: ```yaml id: knexRaw type: KnexRaw connectionId: knex properties: query: SELECT * FROM "my_table"; ``` ###### Query with named parameters: ```yaml id: knexRaw type: KnexRaw connectionId: knex payload: selected_name: _state: selected_name properties: query: select * from users where name = :name parameters: name: _payload: selected_name ``` ###### Query with positional parameters: ```yaml id: knexRaw type: KnexRaw connectionId: knex payload: selected_name: _state: selected_name properties: query: select * from users where name = ? parameters: - _payload: selected_name ``` ###### Reference a `.sql` file: ```yaml id: knexRaw type: KnexRaw connectionId: knex properties: query: _ref: my_query.sql ``` --- url: https://docs.lowdefy.com/md/connections/awss3.md --- # Amazon S3 AWS S3 is a file or data storage solution, provided by Amazon Web Services. S3 does not work like a traditional file system, data is stored as objects in a collection of objects called a bucket. Objects can be read from S3 and stored in S3 using web requests. These objects can be public or private. You can read more [here](https://docs.aws.amazon.com/AmazonS3/latest/dev/Welcome.html). Lowdefy integrates with AWS S3 using presigned links. These are links that are authorized to give access to private objects, or that allow you to create new objects, and that expire after a certain amount of time. This allows you to upload or download directly from S3 from the browser. A guide to creating and connecting to an S3 bucket is given at the bottom of this page. ## Connections Connection types: - AwsS3Bucket ### AwsS3Bucket The `AwsS3Bucket` connection is used to connect to a AWS S3 bucket. AWS S3 is the file storage solution provided by Amazon Web services. #### Properties - `accessKeyId: string`: __Required__ - AWS IAM access key id with s3 access. - `secretAccessKey: string`: __Required__ - AWS IAM secret access key with s3 access. - `region: string`: __Required__ - AWS region bucket is located in. - `bucket: string`: __Required__ - S3 bucket name. - `endpoint: string`: Custom S3 API endpoint for S3-compatible providers like Cloudflare R2, MinIO, DigitalOcean Spaces, Backblaze B2, and Wasabi. - `forcePathStyle: boolean`: Force path-style bucket addressing (`https://endpoint/bucket/key`). Required by MinIO and some self-hosted S3-compatible setups. - `publicUrlBase: string`: Base URL used to construct stable public object URLs (e.g. a CloudFront or CDN domain in front of the bucket). Used by download requests when the request sets `public: true`. - `read: boolean`: Default: `true` - Allow reads from the bucket. - `write: boolean`: Default: `false` - Allow writes to the bucket. #### Examples ###### Read and writes on a bucket: ```yaml connections: - id: my_bucket type: AwsS3Bucket properties: accessKeyId: _secret: S3_ACCESS_KEY_ID secretAccessKey: _secret: S3_SECRET_ACCESS_KEY region: eu-west-1 bucket: my-bucket-name write: true ``` Environment variables: ``` LOWDEFY_SECRET_S3_ACCESS_KEY_ID = AKIAIOSFODNN7EXAMPLE LOWDEFY_SECRET_S3_SECRET_ACCESS_KEY = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY ``` ###### S3-compatible provider (Cloudflare R2): ```yaml connections: - id: r2 type: AwsS3Bucket properties: endpoint: https://.r2.cloudflarestorage.com region: auto bucket: my-bucket accessKeyId: _secret: R2_ACCESS_KEY_ID secretAccessKey: _secret: R2_SECRET_ACCESS_KEY write: true ``` The same connection and requests work against any S3-compatible service — set `endpoint` (and `forcePathStyle: true` for MinIO and most self-hosted setups). Remember to configure CORS on the provider for direct browser uploads. ## Requests Request types: - AwsS3DeleteObject - AwsS3GetObject - AwsS3HeadObject - AwsS3PresignedGetObject - AwsS3PresignedPostPolicy - AwsS3PutObject ### AwsS3PresignedGetObject The `AwsS3PresignedGetObject` request is used to get a download link for an object in AWS S3. Reference the request id in the Download block's `downloadPolicyRequestId` property, or open the returned URL with the `Link` action. #### Properties - `versionId: string`: VersionId used to reference a specific version of the object. - `expires: number`: Number of seconds for which the policy should be valid. - `key: string`: __Required__ - Key (or filename) under which object will be stored. If another file is saved with the same key, that file will be overwritten, so a random string in this field is probably needed. - `responseContentType: string`: Sets the Content-Type header of the response. - `responseContentDisposition: string`: Sets the Content-Disposition header of the response. - `public: boolean`: Default: `false` - Return a stable, non-expiring public URL instead of a signed URL — virtual-hosted style by default, path-style on a custom `endpoint`, or `${publicUrlBase}/${key}` when the connection sets `publicUrlBase`. The object must be publicly readable (e.g. uploaded with acl `public-read`); this is author-declared and never checked at runtime. `versionId`, `responseContentType` and `responseContentDisposition` do not apply to public URLs (providers only honor them on signed requests). #### Examples ###### Download a pdf and open in a new tab: ```yaml requests: - id: my_file_link type: AwsS3PresignedGetObject connectionId: my_bucket_connection properties: key: pdf-filename-as4dacd.pdf responseContentType: application/pdf ... blocks: - id: getFileButton type: Button events: onClick: - id: open_file type: Link params: url: _request: my_file_link newTab: true ``` ### AwsS3PresignedPostPolicy The `AwsS3PresignedPostPolicy` request is used to create a policy that allows a file to be uploaded to AWS S3. It returns the standard upload-policy descriptor `{ method: 'POST', url, key, bucket, fields }` used by the Upload, UploadPhoto and UploadDragger blocks — reference the request id in the block's `uploadPolicyRequestId` property. #### Properties - `acl: enum`: Access control lists used to grant read and write access. - private - public-read - public-read-write - aws-exec-read - authenticated-read - bucket-owner-read - bucket-owner-full-control - `conditions: object[] | string[][]`: Conditions to be enforced on the request. An array of objects, or condition arrays. See [here](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html). - `expires: number`: Number of seconds for which the policy should be valid. - `key: string`: __Required__ - Key (or filename) under which object will be stored. If another file is saved with the same key, that file will be overwritten, so a random string in this field is probably needed. - `fields: object`: Fields to be included in the form. Useful for adding metadata to the object such as `Content-Type`. See [S3 POST policy](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html). #### Examples ###### Upload a file with user filename and random id: ```yaml - id: my_post_policy type: AwsS3PresignedPostPolicy connectionId: my_bucket_connection payload: filename: _args: filename uid: _args: uid properties: key: _nunjucks: template: uploads/{{filename}}-{{uid}} on: filename: _payload: filename uid: _payload: uid ``` ### AwsS3GetObject The `AwsS3GetObject` request reads an object from the bucket on the server and returns its content as a base64 encoded string, so [API endpoint](/lowdefy-api) routine steps can process file content with operators and pass it to other steps (e.g. transform an uploaded file and store the result with `AwsS3PutObject`). The connection must allow reads (`read` is `true` by default). Content rides through the routine as base64, so this suits small and medium files. #### Properties - `key: string`: __Required__ - Key under which the object is stored. - `versionId: string`: VersionId used to reference a specific version of the object. Returns `{ bucket, key, content, contentType, size }` — `content` is the base64 encoded object. #### Examples ###### Read a stored file and re-store a processed copy: ```yaml api: - id: copy_report type: Api routine: - id: get_object type: AwsS3GetObject connectionId: my_bucket properties: key: _payload: key - id: put_copy type: AwsS3PutObject connectionId: my_bucket properties: key: _string.concat: ['copies/', { _payload: key }] content: _step: get_object.content contentType: _step: get_object.contentType - ':return': key: _step: put_copy.key ``` ### AwsS3HeadObject The `AwsS3HeadObject` request checks on the server whether an object exists in the bucket and returns its metadata without reading its content. Use it in an [API endpoint](/lowdefy-api) routine to confirm a file uploaded from the browser with an `AwsS3PresignedPostPolicy` actually arrived, and to take its size and content type from S3 rather than from the caller. The connection must allow reads (`read` is `true` by default). #### Properties - `key: string`: __Required__ - Key under which the object is stored. - `versionId: string`: VersionId used to reference a specific version of the object. Returns `{ exists: true, bucket, key, size, contentType, etag, lastModified, versionId }` for an object that exists (`size` in bytes, `lastModified` a date, `contentType` and `versionId` only when S3 returns them), and `{ exists: false, bucket, key }` when S3 answers not found. Any other S3 error throws. S3 answers not found only when the credentials may list the bucket (`s3:ListBucket`); without that permission a missing object is a 403 error, which throws. #### Examples ###### Record an upload only once it is in the bucket: ```yaml api: - id: record_upload type: Api routine: - id: head_object type: AwsS3HeadObject connectionId: my_bucket properties: key: _payload: key - :if: _not: _step: head_object.exists :then: - :reject: The upload did not arrive. - id: insert_file type: MongoDBInsertOne connectionId: files properties: doc: key: _step: head_object.key size: _step: head_object.size contentType: _step: head_object.contentType - ':return': size: _step: head_object.size ``` ### AwsS3PutObject The `AwsS3PutObject` request writes an object to the bucket from the server. It accepts base64 encoded content, which makes it the storage half of the server-side file handling pattern: an upload block with `emitFileContent: true` sends the file as a base64 payload to an [API endpoint](/lowdefy-api) with the `CallAPI` action, and a routine step stores it. The connection must set `write: true`. In place of `content`, it takes a `url` and streams what that https link answers into the bucket, so a routine can copy a file it is handed as a presigned link without the bytes passing through the routine. The copy is capped at `maxBytes`, can be limited to `contentTypes`, and must finish within `timeout`. #### Properties - `key: string`: __Required__ - Key under which the object will be stored. - `content: string`: Object content as a base64 encoded string. Give either `content` or `url`. - `url: string`: An `https:` link to a public address whose answer is streamed into the bucket. Give either `content` or `url`. - `maxBytes: integer`: __Required with `url`__ - The most bytes the link may answer with. A `Content-Length` over it is refused before the body is read, and a body that passes it is cut off and refused, so nothing larger is stored. - `contentTypes: string[]`: With `url`, the content types the link may answer with, such as `image/png`, `image/*` or `application/pdf`. Any other answer is refused. - `timeout: integer`: Default: `20000` - With `url`, the milliseconds the whole fetch and upload may take. - `contentType: string`: MIME type of the object (sets the Content-Type of the stored object). With `url`, the stored object takes the link's `Content-Type` unless this is given. - `acl: enum`: Access control lists used to grant read and write access (same values as AwsS3PresignedPostPolicy). Returns `{ bucket, key, size, contentType }`, with `size` in bytes and `contentType` the stored content type, or `null` when there is none. A refused copy from a `url` stores nothing and throws an error whose `code` names the reason, so a `:catch` can answer each its own way (`_error: code`): - `url_not_https`: the link, or a link it redirects to, is not `https:`. Refused before that link is requested. - `url_not_public`: the link, or a link it redirects to, leads to an address that is not public: loopback, private, link-local (such as a cloud metadata address), unique-local, carrier-grade NAT, unspecified or multicast. The address is checked as the connection is opened, so nothing is sent to it. - `too_large`: the answer is larger than `maxBytes`. - `content_type`: the answer's `Content-Type` is not one of `contentTypes`. - `fetch_failed`: the link answered with a status that is not 2xx (the error's `statusCode` holds it), could not be reached, or sent a body that does not match its `Content-Length`. - `timeout`: the fetch and upload took longer than `timeout`. #### Examples ###### Store a file sent as an endpoint payload: ```yaml api: - id: store_report type: Api routine: - id: put_object type: AwsS3PutObject connectionId: my_bucket properties: key: _string.concat: - 'reports/' - _payload: name content: _payload: content contentType: _payload: type - ':return': key: _step: put_object.key ``` ###### Copy a screenshot handed over as a presigned link: ```yaml api: - id: copy_screenshot type: InternalApi routine: - :try: - id: copy type: AwsS3PutObject connectionId: my_bucket properties: key: _string.concat: - 'screenshots/' - _payload: ticket_id url: _payload: screenshot_url maxBytes: 10000000 contentTypes: - image/* :catch: - :if: _eq: - _error: code - too_large :then: :reject: The screenshot is larger than 10 MB. - :throw: _error: true - ':return': size: _step: copy.size ``` ### AwsS3DeleteObject The `AwsS3DeleteObject` request deletes one object, or up to 1000 objects in one call, from the connection's bucket. Use it in an [API endpoint](/lowdefy-api) routine to remove objects the routine stored and no longer wants, such as the files it copied before a later step refused the call. It deletes only from the bucket the connection names; the request takes no bucket. The connection must set `write: true`. #### Properties - `key: string`: Key of the object to delete. Give either `key` or `keys`. - `keys: string[]`: Keys of the objects to delete, 1 to 1000. Give either `key` or `keys`. Returns `{ bucket, deleted, errors }`: `deleted` lists the keys deleted, and `errors` lists each key S3 could not delete as `{ key, code, message }`, such as `AccessDenied`. A key with no object counts as deleted, as S3 reports it. A failure of the call itself, such as wrong credentials or a missing bucket, throws. With one `key`, a key S3 cannot delete throws too, so `errors` is always empty. #### Examples ###### Remove both copies when either copy is refused: ```yaml api: - id: attach_files type: Api routine: - :try: - id: copy_screenshot type: AwsS3PutObject connectionId: my_bucket properties: key: _string.concat: - 'tickets/' - _payload: ticket_id - '/screenshot' url: _payload: screenshot_url maxBytes: 10000000 - id: copy_log type: AwsS3PutObject connectionId: my_bucket properties: key: _string.concat: - 'tickets/' - _payload: ticket_id - '/log' url: _payload: log_url maxBytes: 10000000 :catch: - id: remove_copies type: AwsS3DeleteObject connectionId: my_bucket properties: keys: - _string.concat: - 'tickets/' - _payload: ticket_id - '/screenshot' - _string.concat: - 'tickets/' - _payload: ticket_id - '/log' - :reject: The files could not be stored. ``` ## Creating a bucket ###### Step 1 - Create an AWS account Go to the [AWS homepage](https://aws.amazon.com/) and create an account if you don't already have one. you can find more information [here](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/). ###### Step 2 - Create an S3 bucket 1. Once logged in, search for S3 in the search box. 2. Click 'Create Bucket' 3. Choose a name and AWS region for your bucket 4. Continue creating the bucket with any extra settings is needed. 5. Make sure the 'Block all public access' box is ticked if you don't want to allow public access. 6. Click 'Create Bucket' ###### Step 3 - Set the CORS settings for your bucket CORS, or Cross-Origin Resource Sharing is a security feature that is used to restrict web applications from accessing resources from different origins, or domain names. To allow your Lowdefy app to access the contents of the bucket, you need to add a CORS rule on the bucket that authorizes your app 1. Click on you bucket. 2. Click on the 'Permissions' tab. 3. Click on the 'CORS Configuration' tab. 4. Paste this configuration and save (Fill in your own Lowdefy domain name). ```json [ { "AllowedHeaders": [ "*" ], "AllowedMethods": [ "GET", "PUT", "POST", "DELETE", "HEAD" ], "AllowedOrigins": [ "https://YOUR_LOWDEFY_DOMAIN_HERE" ], "ExposeHeaders": [] } ] ``` ###### Step 4 - Create an IAM user IAM is the AWS identity and access management system. This can be used to give very granular access permissions. We will create a programmatic user that has the rights to read and write objects in the Bucket. 1. In the 'Services' dropdown in the header, search for 'IAM'. 2. Click on 'Users' in the menu on the left. 3. Click 'Add User' 4. Choose a descriptive name for the user. 5. Choose programmatic access and click next. 6. Choose 'Attach existing policies directly'. 7. Choose 'Create Policy'. 8. Choose the JSON tab and paste the following (Fill in your own bucket name): ```JSON { "Version": "2012-10-17", "Statement": [ { "Sid": "ListObjectsInBucket", "Effect": "Allow", "Action": ["s3:ListBucket"], "Resource": ["arn:aws:s3:::YOUR_BUCKET_NAME_HERE"] }, { "Sid": "AllObjectActions", "Effect": "Allow", "Action": "s3:*Object", "Resource": ["arn:aws:s3:::YOUR_BUCKET_NAME_HERE/*"] } ] } ``` 9. Choose review policy and save. 10. In your previous tab, refresh and search for the policy you just created. Attach it to the user. 11. Click next, review and create the user. 12. Set the access key id and secret access key in your Lowdefy secrets store. --- url: https://docs.lowdefy.com/md/connections/azureblobstorage.md --- # Azure Blob Storage Azure Blob Storage is the object storage service provided by Microsoft Azure. Data is stored as blobs in containers within a storage account. You can read more [here](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blobs-introduction). Lowdefy integrates with Azure Blob Storage using SAS (shared access signature) tokens — short-lived credentials that let the browser upload and download blobs directly, without file content passing through the Lowdefy server. Azure uploads use a `PUT` request with the file as the raw request body, which the provider-neutral Upload and Download blocks handle automatically. Direct browser uploads require [CORS configuration on the storage account](https://learn.microsoft.com/en-us/rest/api/storageservices/cross-origin-resource-sharing--cors--support-for-the-azure-storage-services) that allows your app's origin. ## Connections Connection types: - AzureBlobContainer ### AzureBlobContainer The `AzureBlobContainer` connection is used to connect to an Azure Blob Storage container. #### Properties - `account: string`: __Required__ - Azure storage account name. - `accountKey: string`: __Required__ - Azure storage account access key. - `container: string`: __Required__ - Blob container name. - `publicUrlBase: string`: Base URL used to construct stable public object URLs (e.g. a CDN domain in front of the container). Used by download requests when the request sets `public: true`. - `read: boolean`: Default: `true` - Allow reads from the container. - `write: boolean`: Default: `false` - Allow writes to the container. #### Examples ###### Reads and writes on a container: ```yaml connections: - id: blob type: AzureBlobContainer properties: account: mystorageacct container: uploads accountKey: _secret: AZURE_STORAGE_KEY write: true ``` Environment variables: ``` LOWDEFY_SECRET_AZURE_STORAGE_KEY = your-storage-account-key ``` ## Requests Request types: - AzureBlobUploadSas - AzureBlobDownloadSas - AzureBlobGet - AzureBlobPut ### AzureBlobUploadSas The `AzureBlobUploadSas` request mints a write SAS URL for direct browser uploads. It returns the standard upload-policy descriptor `{ method: 'PUT', url, key, bucket, headers }` (with the `x-ms-blob-type: BlockBlob` header) used by the Upload, UploadPhoto and UploadDragger blocks — reference the request id in the block's `uploadPolicyRequestId` property. The connection must set `write: true`. #### Properties - `key: string`: __Required__ - Key (blob name) under which the object will be stored. If another file is saved with the same key, that file will be overwritten, so a random string in this field is probably needed. - `expires: number`: Default: `3600` - Number of seconds for which the SAS token should be valid. - `contentType: string`: MIME type set as the Content-Type header of the upload. #### Examples ###### Upload a file with the user's filename: ```yaml requests: - id: upload_policy type: AzureBlobUploadSas connectionId: blob payload: name: _event: file.name properties: key: _string.concat: - 'uploads/' - _payload: name ``` ### AzureBlobDownloadSas The `AzureBlobDownloadSas` request mints a read SAS URL for downloads. Reference the request id in the Download block's `downloadPolicyRequestId` property, or open the returned URL with the `Link` action. #### Properties - `key: string`: __Required__ - Key (blob name) under which the object is stored. - `expires: number`: Default: `3600` - Number of seconds for which the SAS token should be valid. - `public: boolean`: Default: `false` - Return a stable, non-expiring public URL (`https://{account}.blob.core.windows.net/{container}/{key}`, or `${publicUrlBase}/${key}` when the connection sets `publicUrlBase`) instead of a SAS URL. The blob must be publicly readable (public container access); this is author-declared and never checked at runtime. - `contentDisposition: string`: Sets the Content-Disposition header of the response. - `contentType: string`: Sets the Content-Type header of the response. ### AzureBlobGet The `AzureBlobGet` request reads a blob from the container on the server and returns its content as a base64 encoded string, so [API endpoint](/lowdefy-api) routine steps can process file content with operators and pass it to other steps. The connection must allow reads (`read` is `true` by default). Returns `{ bucket, key, content, contentType, size }`. #### Properties - `key: string`: __Required__ - Key (blob name) under which the blob is stored. ### AzureBlobPut The `AzureBlobPut` request writes a block blob to the container from the server. It accepts base64 encoded content, which makes it the storage half of the server-side file handling pattern: an upload block with `emitFileContent: true` sends the file as a base64 payload to an [API endpoint](/lowdefy-api) with the `CallAPI` action, and a routine step stores it. The connection must set `write: true`. #### Properties - `key: string`: __Required__ - Key (blob name) under which the blob will be stored. - `content: string`: __Required__ - Blob content as a base64 encoded string. - `contentType: string`: MIME type of the blob (sets the Content-Type of the stored blob). #### Examples ###### Store a file sent as an endpoint payload: ```yaml api: - id: store_report type: Api routine: - id: put_object type: AzureBlobPut connectionId: blob properties: key: _string.concat: - 'reports/' - _payload: name content: _payload: content contentType: _payload: type - ':return': key: _step: put_object.key ``` --- url: https://docs.lowdefy.com/md/connections/googlecloudstorage.md --- # Google Cloud Storage Google Cloud Storage is the object storage service provided by Google Cloud Platform. Data is stored as objects in buckets, and objects can be public or private. You can read more [here](https://cloud.google.com/storage/docs). Lowdefy integrates with Google Cloud Storage using signed URLs and signed POST policies — short-lived credentials that let the browser upload and download objects directly, without file content passing through the Lowdefy server. The connection plugs into the provider-neutral Upload and Download blocks. Direct browser uploads require [CORS configuration on the bucket](https://cloud.google.com/storage/docs/using-cors) that allows your app's origin. ## Connections Connection types: - GoogleCloudStorageBucket ### GoogleCloudStorageBucket The `GoogleCloudStorageBucket` connection is used to connect to a Google Cloud Storage bucket. #### Properties - `bucket: string`: __Required__ - Google Cloud Storage bucket name. - `client_email: string`: The email of your service account. If omitted, Application Default Credentials are used (e.g. the attached service account when running on GCP). - `private_key: string`: The private key for your service account. If omitted, Application Default Credentials are used. - `projectId: string`: The Google Cloud project ID the bucket belongs to. - `publicUrlBase: string`: Base URL used to construct stable public object URLs (e.g. a CDN domain in front of the bucket). Used by download requests when the request sets `public: true`. - `read: boolean`: Default: `true` - Allow reads from the bucket. - `write: boolean`: Default: `false` - Allow writes to the bucket. #### Examples ###### Reads and writes with a service account: ```yaml connections: - id: my_bucket type: GoogleCloudStorageBucket properties: bucket: my-bucket-name projectId: my-project client_email: _secret: GCS_CLIENT_EMAIL private_key: _secret: GCS_PRIVATE_KEY write: true ``` Environment variables: ``` LOWDEFY_SECRET_GCS_CLIENT_EMAIL = service-account@my-project.iam.gserviceaccount.com LOWDEFY_SECRET_GCS_PRIVATE_KEY = -----BEGIN PRIVATE KEY-----\n... ``` ## Requests Request types: - GcsSignedPostPolicy - GcsSignedGetUrl - GcsGetObject - GcsPutObject ### GcsSignedPostPolicy The `GcsSignedPostPolicy` request signs a V4 POST policy for direct browser uploads. It returns the standard upload-policy descriptor `{ method: 'POST', url, key, bucket, fields }` used by the Upload, UploadPhoto and UploadDragger blocks — reference the request id in the block's `uploadPolicyRequestId` property. The connection must set `write: true`. #### Properties - `key: string`: __Required__ - Key under which the object will be stored. If another file is saved with the same key, that file will be overwritten, so a random string in this field is probably needed. - `expires: number`: Default: `3600` - Number of seconds for which the policy should be valid. - `fields: object`: Additional form fields to include in the POST policy (e.g. `acl`). - `conditions: array`: Conditions to be enforced on the request. See [POST policy conditions](https://cloud.google.com/storage/docs/xml-api/post-object-forms#policy_document). #### Examples ###### Upload a file with the user's filename: ```yaml requests: - id: upload_policy type: GcsSignedPostPolicy connectionId: my_bucket payload: name: _event: file.name properties: key: _string.concat: - 'uploads/' - _payload: name ``` ### GcsSignedGetUrl The `GcsSignedGetUrl` request signs a V4 read URL for downloads. Reference the request id in the Download block's `downloadPolicyRequestId` property, or open the returned URL with the `Link` action. #### Properties - `key: string`: __Required__ - Key under which the object is stored. - `expires: number`: Default: `3600` - Number of seconds for which the signed URL should be valid. - `public: boolean`: Default: `false` - Return a stable, non-expiring public URL (`https://storage.googleapis.com/{bucket}/{key}`, or `${publicUrlBase}/${key}` when the connection sets `publicUrlBase`) instead of a signed URL. The object must be publicly readable; this is author-declared and never checked at runtime. - `responseDisposition: string`: Sets the Content-Disposition header of the response. - `responseType: string`: Sets the Content-Type header of the response. ### GcsGetObject The `GcsGetObject` request reads an object from the bucket on the server and returns its content as a base64 encoded string, so [API endpoint](/lowdefy-api) routine steps can process file content with operators and pass it to other steps. The connection must allow reads (`read` is `true` by default). Returns `{ bucket, key, content, contentType, size }`. #### Properties - `key: string`: __Required__ - Key under which the object is stored. ### GcsPutObject The `GcsPutObject` request writes an object to the bucket from the server. It accepts base64 encoded content, which makes it the storage half of the server-side file handling pattern: an upload block with `emitFileContent: true` sends the file as a base64 payload to an [API endpoint](/lowdefy-api) with the `CallAPI` action, and a routine step stores it. The connection must set `write: true`. #### Properties - `key: string`: __Required__ - Key under which the object will be stored. - `content: string`: __Required__ - Object content as a base64 encoded string. - `contentType: string`: MIME type of the object (sets the Content-Type of the stored object). - `public: boolean`: Default: `false` - Make the stored object publicly readable. #### Examples ###### Store a file sent as an endpoint payload: ```yaml api: - id: store_report type: Api routine: - id: put_object type: GcsPutObject connectionId: my_bucket properties: key: _string.concat: - 'reports/' - _payload: name content: _payload: content contentType: _payload: type - ':return': key: _step: put_object.key ``` --- url: https://docs.lowdefy.com/md/connections/axioshttp.md --- # AxiosHttp The `AxiosHttp` connection is used to connect to APIs and web servers using HTTP or HTTPS. It uses the [axios](https://github.com/axios/axios) library. The same properties can be set on both connections and requests. The properties will be merged, with the request properties overwriting connection properties. This allows properties like authentication headers and the baseURL to be set on the connection, with request specific properties like the request. >Secrets like authentication headers and API keys should be stored using the [`_secret`](_secret) operator. ## Connections Connection types: - AxiosHttp ## Requests Request types: - AxiosHttp ### AxiosHttp #### Properties - `url: string`: The server URL that will be used for the request. - `method: enum`: Default: `'get'` - The request method to be used when making the request. Options are: - `'get'` - `'delete'` - `'head'` - `'options'` - `'post'` - `'put'` - `'patch'` - `baseURL: string`: `baseURL` will be prepended to `url` unless `url` is absolute. It can be convenient to set `baseURL` for an axios connection to pass relative URLs to requests or mutations using that connection. - `headers: object`: An object with custom headers to be sent sent with the request. The object keys should be header names, and the values should be the string header values. - `params: object`: An object with URL parameters to be sent with the request. - `data: string | object | array`: The data to be sent as the request body. Only applicable for request methods `'put'`, `'post'`, and `'patch'`. Can be an object, array or a string in the format `'Country=Brasil&City=Belo Horizonte'`. - `auth: object`: Indicates that HTTP Basic authorization should be used, and supplies credentials. This will set an `Authorization` header, overwriting any existing `Authorization` custom headers you have set using `headers`. Only HTTP Basic auth is configurable through this parameter, for Bearer tokens and such, use `Authorization` custom headers instead. The `auth` object should have the following fields: - `username: string` - `password: string` - `httpAgentOptions: object`: Options to pass to the Node.js [`http.Agent`](https://nodejs.org/api/http.html#http_class_http_agent) class. - `httpsAgentOptions: object`: Options to pass to the Node.js [`https.Agent`](https://nodejs.org/api/http.html#http_class_http_agent) class. - `maxContentLength: number`: Defines the max size of the http response content allowed in bytes. - `maxRedirects: number`: Defines the maximum number of redirects to follow. If set to 0, no redirects will be followed. - `proxy: object`: Defines the hostname and port of the proxy server. The `proxy` object should have the following fields: - `host: string`: Host IP address (eg. `'127.0.0.1'`). - `port: number`: Port number. - `auth: object`: Object with username and password. - `responseType: enum`: Default: `'json'` - The type of data that the server will respond with. Options are: - `'document'` - `'json'` - `'text'` - `responseEncoding: string`: Default: `'utf8'` - Indicates encoding to use for decoding responses. - `timeout: number`: Default: `0` (no timeout) - The number of milliseconds before the request times out. If the request takes longer than `timeout`, the request will be aborted. Set to `0` for no timeout. - `transformRequest: function`: A function to transform the request data before it is sent. Receives `data` and `headers` as arguments and should return the data to be sent. - `transformResponse: function`: A function to transform the received data. Receives `data` as an argument and should return the transformed data. - `validateStatus: function`: A function to validate whether the response should complete successfully given a status code. Receives `status` as an argument and should `true` if the status code is valid. #### Examples Properties for a AxiosHttp can be set on either the connection, the request, or both. However, both a connection and request need to be defined. ###### A basic full example requesting data from https://jsonplaceholder.typicode.com ```yaml lowdefy: 7.1.0 name: Lowdefy starter connections: - id: my_api type: AxiosHttp properties: baseURL: https://jsonplaceholder.typicode.com pages: - id: welcome type: PageHeaderMenu requests: - id: get_posts type: AxiosHttp connectionId: my_api properties: url: /posts events: onInit: - id: fetch_get_posts type: Request params: get_posts blocks: - id: rest_data type: Markdown properties: content: _string.concat: - | ```yaml - _custom_yaml_stringify: - _request: get_posts - | ``` ``` ###### Using the connection to set baseURL and authorization, and handle specific requests as requests. ```yaml connections: - id: app_api type: AxiosHttp properties: baseURL: app.com/api auth: username: api_user password: _secret: API_PASSWORD # ... requests: - id: get_orders type: AxiosHttp connectionId: app_api properties: url: /orders - id: update_order type: AxiosHttp connectionId: app_api payload: order_id: _state: order_id data: _state: true properties: url: _nunjucks: template: /orders/{{ order_id }} on: order_id: _payload: order_id method: post data: _payload: data ``` ###### Setting properties on only the connection: ```yaml connections: - id: get_count type: AxiosHttp properties: url: myapp.com/api/count headers: X-Api-Key: _secret: API_KEY # ... requests: - id: get_count type: AxiosHttp connectionId: get_count ``` ###### Setting properties on only the request, and using a generic connection: ```yaml connections: - id: axios type: AxiosHttp # ... requests: - id: get_api_1 type: AxiosHttp connectionId: axios properties: url: app1.com/api/things - id: post_to_api_2 type: AxiosHttp connectionId: axios payload: data: _state: true properties: url: api.otherapp.org/other/thing method: post data: _payload: data ``` --- url: https://docs.lowdefy.com/md/connections/elasticsearch.md --- # Elasticsearch ## Connections Connection types: - Elasticsearch ### Elasticsearch [`Elasticsearch`](https://www.elastic.co/elasticsearch/) is a distributed, RESTful search and analytics engine capable of addressing a growing number of use cases. As the heart of the Elastic Stack, it centrally stores your data for lightning fast search, fine‑tuned relevancy, and powerful analytics that scale with ease. The Elasticsearch connection can be used to search for documents in your Elasticsearch cluster. Lowdefy integrates with Elasticsearch using the official [Node.js client from Elastic Co.](https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/introduction.html), and provides direct access to the full query DSL supported by Elasticsearch. #### Properties - `node: string|string[]`: __Required__ - Elasticsearch node(s) to connect to. - `index: string`: The default index to query. You can also provide a per-request index to override this setting. - `auth: BasicAuth|CloudAuth`: The authentication data as an object, containing either basic auth credentials (as `username` and `password`), an API key (as `apiKey`), or Elastic Cloud credentials. - `maxRetries: number`: Max number of retries for each request. - `requestTimeout: number`: Max request timeout in milliseconds for each request. The connection accepts many more advanced configuration options. They will be passed to the Elasticsearch client verbatim, so check out the [configuration instructions](https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/basic-config.html) provided by Elastic. #### Examples ###### Authenticated connection with reads and writes: ```yaml connections: - id: elasticsearch type: Elasticsearch properties: write: true node: _secret: ELASTICSEARCH_HOST auth: username: _secret: ELASTICSEARCH_USER password: _secret: ELASTICSEARCH_PASSWORD ``` Environment variables: ``` LOWDEFY_SECRET_ELASTICSEARCH_HOST = http://localhost:9200 LOWDEFY_SECRET_ELASTICSEARCH_USER = es LOWDEFY_SECRET_ELASTICSEARCH_PASSWORD = password ``` ###### Unauthenticated connection with default index: ```yaml connections: - id: elasticsearch type: Elasticsearch properties: write: true node: _secret: ELASTICSEARCH_HOST index: _secret: ELASTICSEARCH_INDEX ``` Environment variables: ``` LOWDEFY_SECRET_ELASTICSEARCH_HOST = http://localhost:9200 LOWDEFY_SECRET_ELASTICSEARCH_INDEX = articles ``` ## Requests Request types: - ElasticsearchDelete - ElasticsearchDeleteByQuery - ElasticsearchIndex - ElasticsearchSearch - ElasticsearchUpdate - ElasticsearchUpdateByQuery ### ElasticsearchDelete The `ElasticsearchDelete` request removes a JSON document from the specified index. #### Properties - `id: string|number`: __Required__ - Unique identifier for the document to be deleted. - `if_seq_no: number` Only perform the operation if the document has this sequence number. See [Optimistic concurrency control](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/docs-index_.html#optimistic-concurrency-control-index). - `if_primary_term: number` Only perform the operation if the document has this primary term. See [Optimistic concurrency control](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/docs-index_.html#optimistic-concurrency-control-index). - `refresh: string|boolean` If `true`, Elasticsearch refreshes the affected shards to make this operation visible to search, if `wait_for` then wait for a refresh to make this operation visible to search, if `false` do nothing with refreshes. Valid values: `true`, `false`, `wait_for`. Default: `false`. - `routing: string` Custom value used to route operations to a specific shard. - `timeout: string` Period to [wait for active shards](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/docs-index_.html#index-wait-for-active-shards). Defaults to 1m (one minute). - `version: integer` Explicit version number for concurrency control. The specified version must match the current version of the document for the request to succeed. - `version_type` Specific version type: `external`, `external_gte`. - `wait_for_active_shards: string` The number of shard copies that must be active before proceeding with the operation. Set to `all` or any positive integer up to the total number of shards in the index (`number_of_replicas+1`). Default: `1`, the primary shard. See [Active Shards](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/docs-index_.html#index-wait-for-active-shards). #### Examples ###### Delete a document: ```yaml requests: - id: delete_document type: ElasticsearchDelete payload: selected_id: _state: selected_id properties: id: _payload: selected_id ``` ### ElasticsearchDeleteByQuery The `ElasticsearchDeleteByQuery` request deletes documents that match the specified query. #### Properties - `body: object` The Elasticsearch query body, expressed in the [JSON Query DSL](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html). - `query: object`: The Elasticsearch query. - `_source: string|string[]|boolean`: True or false to return the `_source` field or not, or a list of fields to return. - `_source_excludes: string|string[]`: A list of fields to exclude from the returned `_source` field. - `_source_includes: string|string[]`: A list of fields to extract and return from the `_source` field. - `allow_no_indices: boolean`: Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes _all string or when no indices have been specified). - `max_docs: number` Maximum number of documents to process. Defaults to all documents. - `refresh: string|boolean` If `true`, Elasticsearch refreshes the affected shards to make this operation visible to search, if `wait_for` then wait for a refresh to make this operation visible to search, if `false` do nothing with refreshes. Valid values: `true`, `false`, `wait_for`. Default: `false`. - `requests_per_second: number` The throttle for this request in sub-requests per second. Defaults to -1 (no throttle). - `analyzer: string`: The analyzer to use for the query string - `default_operator: string`: The default operator for query string query (AND or OR). - `df: string`: The field to use as default where no field prefix is given in the query string. - `from: number`: Starting offset. - `size: number`: Number of hits to return. - `index: string|string[]`: A comma-separated list of index names to search; use _all or empty string to perform the operation on all indices. - `lenient: boolean`: Specify whether format-based query failures (such as providing text to a numeric field) should be ignored. - `scroll: string`: Specify how long a consistent view of the index should be maintained for scrolled search. - `sort: string|string[]`: A comma-separated list of : pairs. The request accepts many more advanced configuration options. They will be passed to the Elasticsearch client verbatim, so check out the [available options](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/docs-delete-by-query.html#docs-delete-by-query-api-query-params) provided by Elastic. #### Examples ###### Delete all comments by a user: ```yaml requests: - id: delete_multiple type: ElasticsearchUpdate payload: selected_user: _state: selected_user properties: body: query: term: user.id: _payload: selected_user ``` ### ElasticsearchIndex The `ElasticsearchIndex` request adds a JSON document to the specified data stream or index and makes it searchable. If the target is an index and the document already exists, the request updates the document and increments its version. #### Properties - `body: object` The Elasticsearch request body contains the JSON source for the document data. - `if_seq_no: number` Only perform the operation if the document has this sequence number. See [Optimistic concurrency control](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/docs-index_.html#optimistic-concurrency-control-index). - `if_primary_term: number` Only perform the operation if the document has this primary term. See [Optimistic concurrency control](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/docs-index_.html#optimistic-concurrency-control-index). - `op_type: 'create'|'index'` Set to `create` to only index the document if it does not already exist (_put if absent_). If a document with the specified `_id` already exists, the indexing operation will fail. Same as using the `/_create` endpoint. Valid values: `index`, `create`. If document id is specified, it defaults to `index`. Otherwise, it defaults to `create`. - `pipeline: string` ID of the pipeline to use to preprocess incoming documents. - `refresh: string|boolean` If `true`, Elasticsearch refreshes the affected shards to make this operation visible to search, if `wait_for` then wait for a refresh to make this operation visible to search, if `false` do nothing with refreshes. Valid values: `true`, `false`, `wait_for`. Default: `false`. - `routing: string` Custom value used to route operations to a specific shard. - `timeout: string` Period the request waits for the following operations: - [Automatic index creation](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/docs-index_.html#index-creation) - [Dynamic mapping](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/dynamic-mapping.html) updates - [Waiting for active shards](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/docs-index_.html#index-wait-for-active-shards) Defaults to `1m` (one minute). This guarantees Elasticsearch waits for at least the timeout before failing. The actual wait time could be longer, particularly when multiple waits occur. - `version: integer` Explicit version number for concurrency control. The specified version must match the current version of the document for the request to succeed. - `version_type` Specific version type: `external`, `external_gte`. - `wait_for_active_shards: string` The number of shard copies that must be active before proceeding with the operation. Set to `all` or any positive integer up to the total number of shards in the index (`number_of_replicas+1`). Default: `1`, the primary shard. See [Active Shards](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/docs-index_.html#index-wait-for-active-shards). - `require_alias: boolean` If `true`, the destination must be an [index alias](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/alias.html). Defaults to `false`. #### Examples ###### Index a new document. ```yaml requests: - id: insert_new_comment type: Elasticsearch connectionId: elasticsearch payload: comment: _state: comment_input properties: body: comment: _payload: comment user_id: _user: id timestamp: _date: now ``` ### ElasticsearchSearch The `ElasticsearchSearch` request searches the Elasticsearch index. #### Properties - `body: object` The Elasticsearch query body, expressed in the [JSON Query DSL](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html). - `query: object`: The Elasticsearch query. - `aggs: object`: The [aggregations](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html) to request from the server. - `_source: string|string[]|boolean`: True or false to return the _source field or not, or a list of fields to return. - `_source_excludes: string|string[]`: A list of fields to exclude from the returned _source field. - `_source_includes: string|string[]`: A list of fields to extract and return from the _source field. - `allow_no_indices: boolean`: Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes _all string or when no indices have been specified). - `allow_partial_search_results: boolean`: Indicate if an error should be returned if there is a partial search failure or timeout. - `analyzer: string`: The analyzer to use for the query string - `default_operator: string`: The default operator for query string query (AND or OR). - `df: string`: The field to use as default where no field prefix is given in the query string. - `from: number`: Starting offset. - `size: number`: Number of hits to return. - `index: string|string[]`: A comma-separated list of index names to search; use _all or empty string to perform the operation on all indices. - `lenient: boolean`: Specify whether format-based query failures (such as providing text to a numeric field) should be ignored. - `scroll: string`: Specify how long a consistent view of the index should be maintained for scrolled search. - `sort: string|string[]`: A comma-separated list of : pairs. The request accepts many more advanced configuration options. They will be passed to the Elasticsearch client verbatim, so check out the [available options](https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/api-reference.html#_search) provided by Elastic. #### Examples ###### Fetch the first 10 matching documents. ```yaml requests: - id: fetch_docs type: Elasticsearch connectionId: elasticsearch properties: size: 10 body: query: match_all: {} ``` ###### Find a document with ID 42. ```yaml requests: - id: fetch_docs type: Elasticsearch connectionId: elasticsearch properties: size: 1 body: query: term: _id: 42 ``` ###### Fetch articles between yesterday and today, aggregate by author. ```yaml requests: - id: fetch_docs type: Elasticsearch connectionId: elasticsearch properties: size: 1 type: article body: query: range: timestamp: gte: now-1d/d lt: now/d aggs: author_aggregation: terms: field: author ``` ### ElasticsearchUpdate The `ElasticsearchUpdate` request updates a document using a script or partial document. #### Properties - `id: string|number` Unique identifier for the document to be updated. - `body: object` The Elasticsearch request body contains either a [script](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-scripting-using.html) or a `doc` property with fields to update. - `if_seq_no: number` Only perform the operation if the document has this sequence number. See [Optimistic concurrency control](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/docs-index_.html#optimistic-concurrency-control-index). - `if_primary_term: number` Only perform the operation if the document has this primary term. See [Optimistic concurrency control](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/docs-index_.html#optimistic-concurrency-control-index). - `require_alias: boolean` If `true`, the destination must be an [index alias](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/alias.html). Defaults to `false`. - `refresh: string|boolean` If `true`, Elasticsearch refreshes the affected shards to make this operation visible to search, if `wait_for` then wait for a refresh to make this operation visible to search, if `false` do nothing with refreshes. Valid values: `true`, `false`, `wait_for`. Default: `false`. - `retry_on_conflict: boolean` Specify how many times should the operation be retried when a conflict occurs. Default: `0`. - `routing: string` Custom value used to route operations to a specific shard. - `_source: string[]|boolean` Set to `false` to disable source retrieval (default: `true`). You can also specify a comma-separated list of the fields you want to retrieve. - `_source_excludes: string[]` Specify the source fields you want to exclude. - `_source_includes: string[]` Specify the source fields you want to include. - `timeout: string` Period the request waits for the following operations: - [Dynamic mapping](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/dynamic-mapping.html) updates - [Waiting for active shards](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/docs-index_.html#index-wait-for-active-shards) Defaults to `1m` (one minute). This guarantees Elasticsearch waits for at least the timeout before failing. The actual wait time could be longer, particularly when multiple waits occur. - `wait_for_active_shards: string` The number of shard copies that must be active before proceeding with the operation. Set to `all` or any positive integer up to the total number of shards in the index (`number_of_replicas+1`). Default: `1`, the primary shard. See [Active Shards](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/docs-index_.html#index-wait-for-active-shards). #### Examples ###### Update a document: ```yaml requests: - id: update type: ElasticsearchUpdate payload: id: _state: _id name: _state: name properties: id: _payload: id body: doc: _payload: name ``` ###### Increase the like counter using a script: ```yaml requests: - id: increase_like_counter type: ElasticsearchUpdate payload: id: _state: _id properties: id: _payload: id body: script: source: | ctx._source.likes++ ``` ### ElasticsearchUpdateByQuery The `ElasticsearchUpdateByQuery` request updates documents that match the specified query. If no query is specified, performs an update on every document in the data stream or index without modifying the source, which is useful for picking up mapping changes. #### Properties - `body: object` The Elasticsearch query body, expressed in the [JSON Query DSL](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html). - `query: object`: The Elasticsearch query. - `script: object` A [script](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-scripting-using.html) to update documents. - `_source: string|string[]|boolean`: True or false to return the `_source` field or not, or a list of fields to return. - `_source_excludes: string|string[]`: A list of fields to exclude from the returned `_source` field. - `_source_includes: string|string[]`: A list of fields to extract and return from the `_source` field. - `allow_no_indices: boolean`: Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes _all string or when no indices have been specified). - `max_docs: number` Maximum number of documents to process. Defaults to all documents. - `refresh: string|boolean` If `true`, Elasticsearch refreshes the affected shards to make this operation visible to search, if `wait_for` then wait for a refresh to make this operation visible to search, if `false` do nothing with refreshes. Valid values: `true`, `false`, `wait_for`. Default: `false`. - `requests_per_second: number` The throttle for this request in sub-requests per second. Defaults to -1 (no throttle). - `analyzer: string`: The analyzer to use for the query string - `default_operator: string`: The default operator for query string query (AND or OR). - `df: string`: The field to use as default where no field prefix is given in the query string. - `from: number`: Starting offset. - `size: number`: Number of hits to return. - `index: string|string[]`: A comma-separated list of index names to search; use _all or empty string to perform the operation on all indices. - `lenient: boolean`: Specify whether format-based query failures (such as providing text to a numeric field) should be ignored. - `scroll: string`: Specify how long a consistent view of the index should be maintained for scrolled search. - `sort: string|string[]`: A comma-separated list of : pairs. The request accepts many more advanced configuration options. They will be passed to the Elasticsearch client verbatim, so check out the [available options](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/docs-update-by-query.html#docs-update-by-query-api-query-params) provided by Elastic. #### Examples ###### Update all red cars: ```yaml requests: - id: update_red_cars type: ElasticsearchUpdateByQuery properties: body: query: term: color: value: red script: source: 'ctx._source.out_of_stock=true' ``` --- url: https://docs.lowdefy.com/md/connections/googlesheet.md --- # Google Sheets ## Connections Connection types: - GoogleSheet ### GoogleSheet The `GoogleSheet` connection offers the flexibility to connect to Google Sheets using either an API key or a service account. While an API key is suitable for read-only access to public sheets, most applications will benefit more from using a service account, which offers broader capabilities. For detailed instructions on creating a service account, you can refer to the [Google Sheets API Quickstart Guide](https://developers.google.com/sheets/api/quickstart/js) and the [Google Workspace Guide on Creating a Project](https://developers.google.com/workspace/guides/create-project). This connection refers to the entire document as the `spreadsheet`, and the individual sheets in the document as `sheets`. The spreadsheet is identified by it's `spreadsheetId`, and sheets can either be identified by their `sheetId` or their `index` (position in the document starting from 0). When a sheet is accessed in a browser the url either looks like: `https://docs.google.com/spreadsheets/d/{spreadsheetId}/edit#gid={sheetId}` The `GoogleSheet` connection works with sheets as a row based data store. Each row is a data record, and a header row needs to be present to define the column names. This header needs to be created in the Google Sheets web interface. Types can be specified for any of the columns in the sheet. If a type is not specified, the value will be read as a string. The `json` type will stringify a object as JSON before saving it, and parse it again when reading. #### Properties - `apiKey: string`: API key for your google project. - `client_email: string`: The email address of your service account. - `private_key: string`: The private key for your service account. Base64 encode the string if you have issues with the newline characters in the string. - `sheetId: string`: The ID of the worksheet. Can be found in the URL as the "gid" parameter. One of `sheetId` or `sheetIndex` is required. - `sheetIndex: number`: The position of the worksheet as they appear in the Google sheets UI. Starts from 0. One of `sheetId` or `sheetIndex` is required. - `spreadsheetId: string`: __Required__ - The document ID from the URL of the spreadsheet. - `columnTypes: object`: An object that defines the data types for each column. Each key should be the column name, and the value should be one of: `string`, `number`, `boolean`, `date`, or `json`. - `read: boolean`: Default: `true` - Allow read operations like find on the sheet. - `write: boolean`: Default: `false` - Allow write operations like update on the sheet. #### Examples ###### Read only access to first sheet in a public spreadsheet: ```yaml connections: - id: public_sheet type: GoogleSheet properties: apiKey: _secret: GOOGLE_SHEETS_API_KEY sheetIndex: 0 spreadsheetId: ubQsWYNGRUq0gFB1sAp2r9oYE19lZ8yGA1T6y0yBoLPW ``` ###### Access with a service account, with defined types: ```yaml connections: - id: my_sheet type: GoogleSheet properties: client_email: _secret: GOOGLE_SHEETS_CLIENT_EMAIL private_key: _base64.decode: _secret: GOOGLE_SHEETS_PRIVATE_KEY sheetId: '1199545345' spreadsheetId: ubQsWYNGRUq0gFB1sAp2r9oYE19lZ8yGA1T6y0yBoLPW columnTypes: name: string age: number birthday: date subscribed: boolean address: json ``` ## Requests Request types: - GoogleSheetAppendMany - GoogleSheetAppendOne - GoogleSheetDeleteOne - GoogleSheetGetMany - GoogleSheetGetOne - GoogleSheetUpdateMany - GoogleSheetUpdateOne ### GoogleSheetAppendMany The `GoogleSheetAppendMany` request inserts an array of documents as rows in a Google Sheet. #### Properties - `rows: object[]`: __Required__ - The rows to insert into the sheet. An an array of objects where keys are the column names and values are the values to insert. - `options: object`: Optional settings. Supported settings are: - `raw: boolean`: Store raw values instead of converting as if typed into the sheets UI. #### Examples ###### Insert a list of rows. ```yaml requests: - id: append_rows type: GoogleSheetAppendMany connectionId: google_sheets properties: rows: - name: D.Va role: Tank - name: Tracer role: Damage - name: Genji role: Damage - name: Reinhart role: Tank - name: Mercy role: Support ``` ### GoogleSheetAppendOne The `GoogleSheetAppendOne` request inserts a documents as a row in a Google Sheet. #### Properties - `row: object`: __Required__ - The row to insert into the sheet. An object where keys are the column names and values are the values to insert. - `options: object`: Optional settings. Supported settings are: - `raw: boolean`: Store raw values instead of converting as if typed into the sheets UI. #### Examples ###### Insert a single row: ```yaml requests: - id: insert_dva type: GoogleSheetAppendOne connectionId: google_sheets properties: row: name: D.Va role: Tank real_name: Hana Song age: 19 ``` ### GoogleSheetDeleteOne The `GoogleSheetDeleteOne` request deletes a row from a Google Sheet. It deletes the first row matched by the filter, written as a MongoDB query expression. #### Properties - `filter: object`: __Required__ - A MongoDB query expression to filter the data. The first row matched by the filter will be deleted. - `options: object`: Optional settings. Supported settings are: - `limit: number`: The maximum number of rows to fetch. - `skip: number`: The number of rows to skip from the top of the sheet. #### Examples ###### Delete the row where name is "Hanzo". ```yaml requests: - id: delete_hanzo type: GoogleSheetDeleteOne connectionId: google_sheets properties: filter: name: $eq: Hanzo ``` ### GoogleSheetGetMany The `GoogleSheetGetMany` request fetches a number of rows from the spreadsheet. The `limit` and `skip` options can be used to control which documents are read from the spreadsheet. The filter or pipeline options can then be used to filter or transform the fetched data. #### Properties - `filter: object`: A MongoDB query expression to filter the data. - `pipeline: object[]`: A MongoDB aggregation pipeline to transform the data. - `options: object`: Optional settings. Supported settings are: - `limit: number`: The maximum number of rows to fetch. - `skip: number`: The number of rows to skip from the top of the sheet. #### Examples ###### Get the first 10 rows. ```yaml requests: - id: get_10_rows type: GoogleSheetGetMany connectionId: google_sheets properties: options: limit: 10 ``` ###### Pagination using limit and skip. ```yaml requests: - id: get_10_rows type: GoogleSheetGetMany connectionId: google_sheets payload: page_size: _state: page_size page_number: _state: page_number properties: options: skip: _product: - _payload: page_size - _subtract: - _payload: page_number - 1 limit: _payload: page_size ``` ###### Get all records where age is greater than 25. ```yaml requests: - id: get_10_rows type: GoogleSheetGetMany connectionId: google_sheets properties: filter: age: $gt: 25 ``` ###### Use an aggregation pipeline to aggregate data. ```yaml requests: - id: get_10_rows type: GoogleSheetGetMany connectionId: google_sheets properties: pipeline: - $group: _id: $region score: $avg: $score - $project: _id: 0 region: $_id score: 1 - $sort: score: 1 ``` ### GoogleSheetGetOne The `GoogleSheetGetOne` request fetches a single rows from the spreadsheet. The `limit` and `skip` options can be used to control which documents are read from the spreadsheet. The filter option can then be used to filter or select which row is returned. #### Properties - `filter: object`: A MongoDB query expression to filter the data. - `options: object`: Optional settings. Supported settings are: - `limit: number`: The maximum number of rows to fetch. - `skip: number`: The number of rows to skip from the top of the sheet. #### Examples ###### Get row where name is "Zarya". ```yaml requests: - id: get_10_rows type: GoogleSheetGetOne connectionId: google_sheets properties: filter: name: $eq: Zarya ``` ### GoogleSheetUpdateMany The `GoogleSheetUpdateMany` request updates all the rows matched by the filter. #### Properties - `filter: object`: A MongoDB query expression to filter the data. . All rows matched by the filter will be updated. - `update: object`: The update to apply to the row. An object where keys are the column names and values are the updated values. - `options: object`: Optional settings. Supported settings are: - `limit: number`: The maximum number of rows to fetch. - `raw: boolean`: Store raw values instead of converting as if typed into the sheets UI. - `skip: number`: The number of rows to skip from the top of the sheet. #### Examples ###### Update all rows where age is less than 18. ```yaml requests: - id: get_10_rows type: GoogleSheetUpdateMany connectionId: google_sheets properties: filter: age: $lt: 18 update: minor: true ``` ### GoogleSheetUpdateOne The `GoogleSheetUpdateOne` request updates the first row matched by the filter. #### Properties - `filter: object`: A MongoDB query expression to filter the data. . The first row matched by the filter will be updated. - `update: object`: The update to apply to the row. An object where keys are the column names and values are the updated values. - `options: object`: Optional settings. Supported settings are: - `limit: number`: The maximum number of rows to fetch. - `raw: boolean`: Store raw values instead of converting as if typed into the sheets UI. - `skip: number`: The number of rows to skip from the top of the sheet. - `upsert: boolean`: Insert the row if no rows are matched by the filter. #### Examples ###### Update the row for "Doomfist" ```yaml requests: - id: get_10_rows type: GoogleSheetUpdateOne connectionId: google_sheets properties: filter: name: $eq: Doomfist update: overpowered: true ``` --- url: https://docs.lowdefy.com/md/connections/knex.md --- # Knex [`Knex.js`](http://knexjs.org) is a SQL query builder that can be used to connect to [PostgreSQL](/PostgreSQL), [MS SQL Server](/MSSQL), [MySQL](/MySQL), [MariaDB](/MariaDB), [SQLite](/SQLite), Oracle, and [Amazon Redshift](/AmazonRedshift) databases. The Knex connection can be used to execute raw SQL queries using the `KnexRaw` requests, or the Knex query builder can be used with the `KnexBuilder` request. For more details on specific databases, see the database documentation: ## Connections Connection types: - Knex ### Knex #### Properties - `client: enum`: __Required__ - The database client to use. One of: - `better-sqlite3` - `sqlite` (alias of `better-sqlite3`) - `mssql` - `mysql2` - `oracledb` - `postgres` - `pg` (alias of `postgres`) - `postgresql` (alias of `postgres`) - `redshift` - `connection: object | string`: __Required__ - Connection string or object to pass to the database client. See the specific client documentation for more details. - `searchPath: string`: Set PostgreSQL search path. - `version: string`: Set database version. - `useNullAsDefault: boolean`: If true, undefined keys are replaced with NULL instead of DEFAULT. #### Examples ###### MySQL with connection object: ```yaml connections: - id: mysql type: Knex properties: client: mysql2 connection: host: _secret: MYSQL_HOST user: _secret: MYSQL_USER database: _secret: MYSQL_DB password: _secret: MYSQL_PASSWORD ``` Environment variables: ``` LOWDEFY_SECRET_MYSQL_HOST = database.server.com LOWDEFY_SECRET_MYSQL_DB = db LOWDEFY_SECRET_MYSQL_USER = user LOWDEFY_SECRET_MYSQL_PASSWORD = password ``` ###### PostgreSQL with secret connection string: ```yaml connections: - id: postgres type: Knex properties: client: postgres connection: _secret: PG_CONNECTION_STRING ``` Environment variables: ``` LOWDEFY_SECRET_PG_CONNECTION_STRING = postgresql://user:password@database.server.com:5432/db ``` ## Requests Request types: - KnexBuilder - KnexRaw ### KnexBuilder #### Properties - `query: object[]`: **Required** - SQL query builder array. An array of objects, with a single key which is the name of the knex builder function. The value should be an array of arguments to pass to the builder function. - `tableName: string | object`: The name of the table to query from. #### Examples ###### Build a query: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex payload: name: _state: name properties: query: - select: - '*' - from: - users - where: - name - _payload: name ``` ###### Using `tableName`: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex payload: name: _state: name properties: tableName: users query: - select: - '*' - where: - name - _payload: name ``` ###### Aliases: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex properties: tableName: a: tableA b: tableB query: - select: - aField: 'a.field' - bField: 'b.field' - limit: - 1 ``` ### KnexRaw #### Properties - `query: string`: **Required** - SQL query string. - `parameters: string | number | array | object`: SQL query parameters. #### Examples ###### Simple raw query: ```yaml id: knexRaw type: KnexRaw connectionId: knex properties: query: SELECT * FROM "my_table"; ``` ###### Query with named parameters: ```yaml id: knexRaw type: KnexRaw connectionId: knex payload: selected_name: _state: selected_name properties: query: select * from users where name = :name parameters: name: _payload: selected_name ``` ###### Query with positional parameters: ```yaml id: knexRaw type: KnexRaw connectionId: knex payload: selected_name: _state: selected_name properties: query: select * from users where name = ? parameters: - _payload: selected_name ``` ###### Reference a `.sql` file: ```yaml id: knexRaw type: KnexRaw connectionId: knex properties: query: _ref: my_query.sql ``` --- url: https://docs.lowdefy.com/md/connections/mariadb.md --- # MariaDB The [Knex](/Knex) connection can be used to connect to a [MariaDB](https://www.mysql.com) database. To connect to a MariaDB database, use the `mysql2` client — knex does not have a separate `mariadb` client. ## Connections Connection types: - Knex ### Knex #### Properties - `client: enum`: __Required__ - Use the `mysql2` client to connect to MariaDB. - `connection: object | string `: __Required__ - Connection object or string to pass to the [`mysql2`](https://www.npmjs.com/package/mysql2) database client. - `version: string`: Set database version. - `useNullAsDefault: boolean`: If true, undefined keys are replaced with NULL instead of DEFAULT. #### Examples ###### Connection object: ```yaml connections: - id: mariadb type: Knex properties: client: mysql2 connection: host: _secret: MARIADB_HOST database: _secret: MARIADB_DB user: _secret: MARIADB_USER password: _secret: MARIADB_PASSWORD ``` Environment variables: ``` LOWDEFY_SECRET_MARIADB_HOST = database.server.com LOWDEFY_SECRET_MARIADB_DB = db LOWDEFY_SECRET_MARIADB_USER = user LOWDEFY_SECRET_MARIADB_PASSWORD = password ``` ## Requests Request types: - KnexBuilder - KnexRaw ### KnexBuilder #### Properties - `query: object[]`: **Required** - SQL query builder array. An array of objects, with a single key which is the name of the knex builder function. The value should be an array of arguments to pass to the builder function. - `tableName: string | object`: The name of the table to query from. #### Examples ###### Build a query: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex payload: name: _state: name properties: query: - select: - '*' - from: - users - where: - name - _payload: name ``` ###### Using `tableName`: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex payload: name: _state: name properties: tableName: users query: - select: - '*' - where: - name - _payload: name ``` ###### Aliases: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex properties: tableName: a: tableA b: tableB query: - select: - aField: 'a.field' - bField: 'b.field' - limit: - 1 ``` ### KnexRaw #### Properties - `query: string`: **Required** - SQL query string. - `parameters: string | number | array | object`: SQL query parameters. #### Examples ###### Simple raw query: ```yaml id: knexRaw type: KnexRaw connectionId: knex properties: query: SELECT * FROM "my_table"; ``` ###### Query with named parameters: ```yaml id: knexRaw type: KnexRaw connectionId: knex payload: selected_name: _state: selected_name properties: query: select * from users where name = :name parameters: name: _payload: selected_name ``` ###### Query with positional parameters: ```yaml id: knexRaw type: KnexRaw connectionId: knex payload: selected_name: _state: selected_name properties: query: select * from users where name = ? parameters: - _payload: selected_name ``` ###### Reference a `.sql` file: ```yaml id: knexRaw type: KnexRaw connectionId: knex properties: query: _ref: my_query.sql ``` --- url: https://docs.lowdefy.com/md/connections/mongodb.md --- # MongoDB MongoDB is a NoSQL database that stores JSON-like documents. These documents are stored in collections, which are like database tables. The fields inside these document can differ from document to document, but generally they are all more or less the same. However documents with different schemas can be stored in the same collection. ##### ObjectIds MongoDB uses the _id field as the id for a document. This has to be unique for every document in the collection. If no _id is provided when the document is created, a MongoDB [ObjectId](https://docs.mongodb.com/manual/reference/method/ObjectId/) is created for that document. This id includes a timestamp, a random element and an incrementing counter, to ensure it is unique even if multiple ids are created at the same time. The _id is often represented as: ```js { _id: ObjectId("507f1f77bcf86cd799439011") } ``` To be able to transmit these ids over JSON network connections, and to use them in Lowdefy apps, Lowdefy serializes these ids as (in YAML): ```yaml _id: _oid: 507f1f77bcf86cd799439011 ``` Ids specified in this way will be treated as ObjectIds by MongoDB requests and mutations. ## Connections Connection types: - MongoDBCollection ### MongoDBCollection The `MongoDBCollection` connection sets up a connection to a MongoDB deployment. A [connection URI](https://docs.mongodb.com/manual/reference/connection-string/index.html) with authentication credentials (username and password) is required. The URI can be in the standard or dns seedlist (srv) formats. Connections are defined on a collection level, since this allows for read/write access control on a per collection level. Access control can also be managed using the roles in the database. >Since the connection URI contains authentication secrets, it should be stored using the [`_secret`](operators/secret.md) operator. When the connection has a `changeLog` collection, all write requests log a change record to that collection. The record contains the request arguments, the request context (`blockId`, `pageId`, `requestId`, `connectionId`, `payload`), a `timestamp`, the request `type`, the connection's `changeLog.meta` value, and either the driver response or `before`/`after` document snapshots (for `MongoDBUpdateOne`, `MongoDBVersionedUpdateOne` and `MongoDBDeleteOne`). The response shape of every request stays the same whether or not a log collection is configured. #### Properties - `databaseUri: string`: __Required__ - Connection uri string for the MongoDb deployment. Should be stored using the [_secret](operators/secret.md) operator. - `databaseName: string`: Default: Database specified in connection string - The name of the database in the MongoDB deployment. - `collection: string`: __Required__ - The name of the MongoDB collection. - `changeLog: object`: Log all changes made by write requests to a log collection. - `collection: string`: __Required__ - The name of the collection change log records are written to. - `meta: object`: Additional data to include in every change log record, for example the user making the change. - `read: boolean`: Default: `true` - Allow read operations like find on the collection. - `write: boolean`: Default: `false` - Allow write operations like update on the collection. - `options: object`: See the [driver documentation](https://mongodb.github.io/node-mongodb-native/4.0/interfaces/mongoclientoptions.html) for more information. #### Connection pooling The MongoDB client is created once per unique `databaseUri` and `options` combination and reused for all requests, so requests share the driver's connection pool instead of opening a new connection for every request. The pool can be tuned with the standard driver options, for example: ```yaml connections: - id: my_collection type: MongoDBCollection properties: databaseUri: _secret: MONGODB_URI collection: my_collection_name options: maxPoolSize: 20 minPoolSize: 0 maxIdleTimeMS: 60000 ``` #### Examples ###### MongoDB collection with reads and writes: ```yaml connections: - id: my_collection type: MongoDBCollection properties: databaseUri: _secret: MONGODB_URI collection: my_collection_name write: true ``` ###### MongoDB collection with reads, writes and a log collection: ```yaml connections: - id: my_collection type: MongoDBCollection properties: databaseUri: _secret: MONGODB_URI collection: my_collection_name write: true changeLog: collection: log_collection_name meta: user: _user: true ``` Environment variables: ``` LOWDEFY_SECRET_MONGODB_URI = mongodb+srv://username:password@server.example.com/database ``` ## Requests Request types: - MongoDBAggregation - MongoDBBulkWrite - MongoDBDeleteMany - MongoDBDeleteOne - MongoDBEnrichmentClaim - MongoDBEnrichmentComplete - MongoDBEnrichmentEnqueue - MongoDBFind - MongoDBFindOne - MongoDBInsertConsecutiveId - MongoDBInsertMany - MongoDBInsertManyConsecutiveIds - MongoDBInsertOne - MongoDBTableChanges - MongoDBTableQuery - MongoDBUpdateMany - MongoDBUpdateOne - MongoDBVersionedUpdateOne ### MongoDBAggregation The `MongoDBAggregation` request executes an [aggregation pipeline](https://docs.mongodb.com/manual/core/aggregation-pipeline/) in the collection specified in the connectionId. It returns the array of documents returned by the aggregation. Aggregation pipelines are MongoDB's data processing and aggregation framework. They are based on a series of stages, each of which apply a transformation to the data passed through them, like sorting, grouping or calculating additional fields. >Cursors are not supported. The request will return the whole body of the response as an array. #### Properties - `pipeline: object[]`: __Required__ - Array containing all the aggregation framework commands for the execution. - `options: object`: Optional settings. See the [driver documentation](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#aggregate) for more information. Supported settings are: - `allowDiskUse: boolean`: Default: `false` - Allow disk use on the MongoDB server to store temporary results for the aggregation. - `authdb: string`: Specifies the authentication information to be used. - `batchSize: number`: The number of documents to return per batch. - `bsonRegExp: boolean`: Return the BSON regular expressions as BSONRegExp instances. - `bypassDocumentValidation: boolean`: Default: `false` - Allow driver to bypass schema validation in MongoDB 3.2 or higher. - `checkKeys: boolean`: The serializer will check if keys are valid. - `collation: object`: Specify collation (MongoDB 3.4 or higher) settings for update operation. - `comment: string`: Add a [comment](https://docs.mongodb.com/manual/reference/operator/query/comment/index.html) to the aggregation. These comments are visible in the MongoDB profile log, making them easier to interpret. - `dbName: string`: The database name. - `explain: boolean`: Specifies to return the information on the processing of the pipeline. - `fullResponse: boolean`: Return the full server response for the command. - `hint: string | object`: Add an index selection hint to an aggregation command. - `ignoreUndefined: boolean`: Default: `true` - Serialize will not emit undefined fields. - `let: object`: Specifies an object with a list of variables. This allows you to improve command readability by separating the variables from the query text. - `maxAwaitTimeMS: number`: The maximum amount of time for the server to wait on new documents to satisfy a tailable cursor query. - `maxTimeMS: number`: Specifies a cumulative time limit in milliseconds for processing operations on the cursor. - `noResponse: boolean`: Admin command option. - `readConcern: object`: Specifies the level of isolation for read operations. - `readPreference: string | object`: The read preference. - `retryWrites: boolean`: Should retry failed writes. - `serializeFunctions: boolean`: Default: `false` - Serialize the javascript functions. - `willRetryWrites: boolean`: Option whether to retry writes. - `writeConcern: object`: An object that expresses the write concern to use with the $out or $merge stage. #### Examples ###### Calculate average score by region: ```yaml requests: - id: avg_spend_by_region type: MongoDBAggregation connectionId: my_mongodb_collection_id properties: pipeline: - $group: _id: $region score: $avg: $score - $project: _id: 0 region: $_id score: 1 - $sort: score: 1 ``` ### MongoDBBulkWrite The `MongoDBBulkWrite` request executes [bulkWrite operations](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/#write-operations) in the collection specified in the connectionId. #### Properties - `operations: object[]`: __Required__ - Array containing all the bulkWrite operations for the execution. - `insertOne: object`: - `document: object`: The document to be inserted. - `deleteOne: object`: - `filter: object`: __Required__ - The filter used to select the document to delete. - `collation: object`: Specify collation settings for update operation. - `deleteMany: object`: - `filter: object`: __Required__ - The filter used to select the documents to delete. - `collation: object`: Specify collation settings for update operation. - `updateOne: object`: - `filter: object`: __Required__ - The filter used to select the document to update. - `update: object | object[]`: __Required__ - The update operations to be applied to the document. - `upsert: object`: Insert document if no match is found. - `arrayFilters: string[]`: Array filters for the [`$[]`](https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/) array update operator. - `collation: object`: Specify collation settings for update operation. - `hint: object | string`: 'An optional hint for query optimization.' - `updateMany: object`: - `filter: object`: __Required__ - The filter used to select the documents to update. - `update: object | object[]`: __Required__ - The update operations to be applied to the documents. - `upsert: object`: Insert document if no match is found. - `arrayFilters: string[]`: Array filters for the [`$[]`](https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/) array update operator. - `collation: object`: Specify collation settings for update operation. - `hint: object | string`: An optional hint for query optimization. - `replaceOne: object`: - `filter: object`: __Required__ - The filter used to select the document to replace. - `replacement: object`: __Required__ - The document to be inserted. - `upsert: object`: Insert document if no match is found. - `collation: object`: Specify collation settings for update operation. - `hint: object | string`: An optional hint for query optimization. - `options: object`: Optional settings. See the [driver documentation](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#aggregate) for more information. Supported settings are: - `ordered: boolean`: Default: `true` - A boolean specifying whether the mongod instance should perform an ordered or unordered operation execution. - `writeConcern: object`: An object that expresses the write concern to use. #### Examples ###### Update pizzas: ```yaml requests: - id: update_pizzas type: MongoDBBulkWrite connectionId: my_mongodb_collection_id properties: operations: - insertOne: document: _id: 3 type: "beef" size: "medium" price: 6 - insertOne: document: _id: 4 type: "sausage" size: "large" price: 10 - updateOne: filter: type: "cheese" update: $set: price: 8 - deleteOne: filter: type: "pepperoni" - replaceOne: filter: type: "vegan" replacement: type: "tofu" size: "small" price: 4 ``` ### MongoDBDeleteMany The `MongoDBDeleteMany` request deletes multiple documents in the collection specified in the connectionId. It requires a filter, which is written in the query syntax, to select a documents to delete. #### Properties - `filter: object`: __Required__ - The filter used to select the document to update. - `options: object`: Optional settings. See the [driver documentation](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#deletemany) for more information. Supported settings are: - `authdb: string`: Specifies the authentication information to be used. - `bsonRegExp: boolean`: Return the BSON regular expressions as BSONRegExp instances. - `checkKeys: boolean`: Default: `false` - If true, will throw if bson documents start with $ or include a . in any key value. - `collation: object`: Specify collation (MongoDB 3.4 or higher) settings for update operation. - `comment: string`: A user-provided comment to attach to this command. - `dbName: string`: The database name. - `explain: boolean`: Specifies the verbosity mode for the explain output. - `fullResponse: boolean`: Return the full server response for the command. - `hint: string | object`: An optional hint for query optimization. - `ignoreUndefined: boolean`: Default: `false` - Specify if the BSON serializer should ignore undefined fields. - `let: object`: Map of parameter names and values that can be accessed using `$$var` (requires MongoDB 5.0). - `maxTimeMS: number`: Specifies a cumulative time limit in milliseconds for processing operations on the cursor. - `noResponse: boolean`: Admin command option. - `ordered: boolean`: If true, when an insert fails, don't execute the remaining writes. If false, continue with remaining inserts when one fails. - `readConcern: object`: Specify a read concern and level for the collection. (only MongoDB 3.2 or higher supported). - `readPreference: string | object`: The read preference. - `retryWrites: boolean`: Should retry failed writes. - `serializeFunctions: boolean`: Default: `false` - Serialize the javascript functions. - `willRetryWrites: boolean`: Option whether to retry writes. - `writeConcern: object`: An object that expresses the write concern. #### Examples ###### Delete all documents older than a specific date: ```yaml requests: - id: delete_old_documents type: MongoDBDeleteMany connectionId: my_mongodb_collection_id properties: filter: created_date: $lt: _date: 2020-01-01 ``` ### MongoDBDeleteOne The `MongoDBDeleteOne` request deletes a single document in the collection specified in the connectionId. It requires a filter, which is written in the query syntax, to select a document to delete. It will delete the first document that matches the filter. > When the connection has a log collection, a findOneAndDelete operation is performed instead of the standard deleteOne operation so the deleted document can be captured in the change log. The response shape is the same in both cases. #### Properties - `filter: object`: __Required__ - The filter used to select the document to update. - `options: object`: Optional settings. See the [driver documentation](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#deleteone) for more information. Supported settings are: - `authdb: string`: Specifies the authentication information to be used. - `bsonRegExp: boolean`: Return the BSON regular expressions as BSONRegExp instances. - `checkKeys: boolean`: Default: `false` - If true, will throw if bson documents start with $ or include a . in any key value. - `collation: object`: Specify collation (MongoDB 3.4 or higher) settings for update operation. - `comment: string`: A user-provided comment to attach to this command. - `dbName: string`: The database name. - `explain: boolean`: Specifies the verbosity mode for the explain output. - `fullResponse: boolean`: Return the full server response for the command. - `hint: string | object`: An optional hint for query optimization. - `ignoreUndefined: boolean`: Default: `false` - Specify if the BSON serializer should ignore undefined fields. - `let: object`: Map of parameter names and values that can be accessed using `$$var` (requires MongoDB 5.0). - `maxTimeMS: number`: Specifies a cumulative time limit in milliseconds for processing operations on the cursor. - `noResponse: boolean`: Admin command option. - `ordered: boolean`: If true, when an insert fails, don't execute the remaining writes. If false, continue with remaining inserts when one fails. - `readConcern: object`: Specify a read concern and level for the collection. (only MongoDB 3.2 or higher supported). - `readPreference: string | object`: The read preference. - `retryWrites: boolean`: Should retry failed writes. - `serializeFunctions: boolean`: Default: `false` - Serialize the javascript functions. - `willRetryWrites: boolean`: Option whether to retry writes. - `writeConcern: object`: An object that expresses the write concern. #### Examples ###### Delete a document by _id: ```yaml requests: - id: delete_selected_document type: MongoDBDeleteOne connectionId: my_mongodb_collection_id payload: selected_id _state: selected_id properties: filter: _id: _payload: selected_id ``` ### Enrichment run queue Three requests run the enrichment columns of a `Table`: columns whose cells are computed per row by a provider (an API endpoint of the app) or an AI prompt, from the values of other columns. `MongoDBEnrichmentEnqueue` marks cells queued, `MongoDBEnrichmentClaim` hands a worker a batch of queued cells, and `MongoDBEnrichmentComplete` writes the worker's results. There is no queue collection: each cell's run state is stored in its row, under `_enrich.`, next to the row's data. ```yaml _enrich: email: # the column key status: ok # queued | running | ok | error | empty value: ada@acme.test # the result the column shows (its field is _enrich.email.value) raw: { ... } # the provider's response, for the details panel error: null # the message, with status error (or the last failed attempt, while queued again) inputHash: 0659b6fcca796a # the hash of the inputs the value was computed from runId: 66f9… # the enqueue that queued the cell attempts: 1 claimToken: … # the claim of the worker that ran it queuedAt, startedAt, finishedAt, leaseUntil waitingFor: [company] # while queued behind the enrichment columns it reads ``` A cell moves from `queued` to `running` (claimed by a worker, with a lease) to `ok`, `empty` (the provider found nothing) or `error`. A failed attempt goes back to `queued` after a backoff until `maxAttempts`. While a cell is queued or running again, its previous `value`, `raw` and `inputHash` stay, so the table keeps showing the old value until a new result lands. The three requests take the table's columns as `columnDefs`, the declared and user-defined columns merged, as the app passes them to the `Table` (a Table columns list can be passed as it is). Enrichment and ai columns run; other columns are only looked up by key: ```yaml - key: email kind: enrichment # or ai provider: finder # the worker calls the endpoint enrich_finder; ai columns default to ai inputs: domain: { column: domain } # another column's value; required unless required: false country: { value: ZA } # a literal value autoRun: true # run when an input column completes - key: pitch kind: ai # provider defaults to ai (the enrich_ai endpoint) prompt: Write a two line pitch for {{ name }} inputs: name: { column: name } # list every column the prompt uses email: { column: email } # an enrichment column: its value, once its cell is ok output: { type: tag, options: [Hot, Warm, Cold] } # text, number, boolean, tag or tags ``` >Read the columns on the server, never from the browser. A request payload is whatever the browser sends: an endpoint that passes `_payload: columnDefs` to these requests lets any user run any column config (another provider, other inputs, another prompt) against the rows the filter allows. Build `columnDefs` in the endpoint from your declared columns and the stored user columns, as the examples below do, and check user columns when they are saved. >AI prompts and formula templates are user content: they only take `{{ column }}` placeholders, filled in as plain text. Never render a prompt with a template engine (`_nunjucks` or a `_js` template) on the server: a template can run code. The `Table` refuses prompts and formulas with tags (`{% %}`), comments (`{# #}`) or expressions (`{{ a | upper }}`); check the same when you save a user column. A `{ column }` input reads another enrichment or ai column's value (only when that cell is `ok`; while it is queued or running, the cell waits for it: it is queued with `waitingFor`, out of claims until that cell finishes), or a field of `fields`, the table's `MongoDBTableQuery` fields keyed by column key, at its `path`. A column outside both is refused, so a user-defined column can never send a field the table does not list to a provider. A required input that is `null`, missing or `''` sets the cell to `empty` with `error: Missing input: ` instead of running it. A chain of inputs that comes back to a column is refused, since `autoRun` would run it forever. The browser never sends MongoDB syntax, and the requests only ever write the cell properties above, under `_enrich.` of an enrichment or ai column of `columnDefs` (column keys are 1 to 128 letters, digits, `_` or `-`). Every read and write is scoped by the base `filter` (required unless the connection is tenant-scoped; `filter: {}` for every document) and, on a tenant connection, by the tenant; the `filter` may not name `_enrich`. No request runs JavaScript on the database server. ##### Input hash `inputHash` is the hash of a cell's inputs, `{ [param]: value }` with optional inputs that have no value left out: [cyrb53](https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js) (seed 0) of the canonical JSON of the inputs, as 14 lowercase hex digits. The canonical JSON has object keys sorted at every depth, no whitespace, dates as ISO strings, ObjectIds as hex strings and undefined values left out. The `Table` computes the same hash in the browser, so a cell whose row's inputs changed since its value was computed shows as stale, and `mode: stale` re-runs exactly those cells. A claim hashes the inputs it gives the worker, and the result is stored with that hash. ##### Columns and fields on the server Every endpoint that runs these requests reads the table's columns itself, with a request or step (declared columns merged with the stored user columns), and builds `fields` the same way: the declared `MongoDBTableQuery` fields, plus one field per user-defined input column. The recommended layout keeps what users type under `values.` (set the Table's `inputFieldPrefix: values`), so a user column can never name, read or write another field of the row: ```yaml # leads/load_table.yaml, referenced by every enrichment endpoint - id: load_columns type: MongoDBAggregation connectionId: table_columns properties: pipeline: - $match: { tableId: leads } - $sort: { position: 1 } - :set_state: columnDefs: _array.concat: - _ref: leads/columns.yaml # the declared columns - _step: load_columns # the user columns, as stored fields: _js: fn: | const fields = { ...args.declared }; args.columns .filter((column) => column.userDefined === true && column.kind === 'input') .forEach((column) => { fields[column.key] = { type: column.type ?? 'text', path: `values.${column.key}` }; }); return fields; args: declared: _ref: leads/fields.yaml # the declared MongoDBTableQuery fields columns: _step: load_columns ``` `columnDefs` and `fields` may come from any operator (`_state`, `_step`, `_payload` for data you checked); the requests validate them each time they run. ##### The worker The worker is an API endpoint: it claims a batch of cells, calls each cell's provider endpoint with `:parallel_for` (`:concurrency` limits how many run at once), and completes each cell. Complete queues the `autoRun` columns a completed cell feeds (a waterfall between columns) and releases the cells waiting for it, so a later round runs them. It loops until a claim returns nothing, or for at most ten rounds. A schedule runs it every minute, and the endpoint that enqueues a run starts it at once with a detached call. A worker that crashes loses nothing: its cells are claimed again when their lease runs out, and a result from a worker whose lease ran out is ignored. ```yaml api: - id: enrichment_worker type: InternalApi schedules: - cron: '* * * * *' routine: - _ref: leads/load_table.yaml # columnDefs and fields, read on the server - :set_state: claimed: 1 rounds: 0 - :while: _and: - _gt: [{ _state: claimed }, 0] - _lt: [{ _state: rounds }, 10] :do: - id: claim type: MongoDBEnrichmentClaim connectionId: leads properties: filter: {} # cells were scoped when they were queued fields: _state: fields columnDefs: _state: columnDefs limit: 20 - :parallel_for: cell :in: _step: claim :concurrency: 5 # at most five provider calls at once :do: - :try: - id: call_provider type: CallApi properties: endpointId: _string.concat: [enrich_, { _item: cell.provider }] payload: inputs: _item: cell.inputs prompt: _item: cell.prompt # ai columns output: _item: cell.output # the column's output config - id: complete type: MongoDBEnrichmentComplete connectionId: leads properties: filter: {} columnDefs: _state: columnDefs results: # The claim's cell, with the provider's answer merged in: # { status, value, raw } or { status, error, retry }. - _object.assign: - rowKey: _item: cell.rowKey columnKey: _item: cell.columnKey claimToken: _item: cell.claimToken - _step: call_provider.$ :catch: - id: complete_error type: MongoDBEnrichmentComplete connectionId: leads properties: filter: {} columnDefs: _state: columnDefs results: - rowKey: _item: cell.rowKey columnKey: _item: cell.columnKey claimToken: _item: cell.claimToken status: error error: _error: message - :set_state: claimed: _array.length: _step: claim rounds: _sum: [{ _state: rounds }, 1] ``` A request or service error a `:catch` handles is logged at debug, so a provider that fails on purpose (a 404 in a waterfall) does not fill the log with errors. Config, operator and internal errors are still logged as errors, with their config location, even when a `:catch` handles them. Each provider endpoint `enrich_` takes `{ inputs, prompt, output }` and returns `{ status: ok | empty, value, raw }`, or `{ status: error, error, retry: false }` for a final failure; throwing is a failed attempt that is retried. `null` for `error` or `retry` (what `_step` gives for a key the provider did not return) is the same as leaving it out. The app declares these endpoints, so a column can only call what the app exposes, and API keys stay on the server. For a concurrency per provider, group the claimed cells by `provider` and run each group with its own `:concurrency`, or run a worker per provider with `providers` on the claim. The AI provider (`enrich_ai`) fills the prompt's `{{ input }}` and `{{ input.path }}` placeholders (a dot path reads into the input's value, the pattern the Table accepts) with the cell's inputs by plain string replacement, in one pass, so a row value that looks like a template stays text, and asks the model for an answer of the column's `output.type`. The endpoint the table's run events call queues the cells, then starts the worker without waiting for it (a detached call needs the `CRON_SECRET` environment variable): ```yaml api: - id: run_enrichment type: Api routine: - _ref: leads/load_table.yaml - id: enqueue type: MongoDBEnrichmentEnqueue connectionId: leads properties: filter: org_id: _user: organization.id fields: _state: fields columnDefs: _state: columnDefs # read on the server, never _payload columns: _payload: columns mode: _payload: mode selection: _payload: selection user: _user: true - id: start_worker type: CallApi properties: endpointId: enrichment_worker detached: true - :return: _step: enqueue ``` A claim reads the oldest due cells of each column. On a large table, index each enrichment column's queue fields, for example `{ "_enrich.email.status": 1, "_enrich.email.queuedAt": 1 }`, with the base filter fields first when every run is scoped by them. ### MongoDBEnrichmentClaim The `MongoDBEnrichmentClaim` request claims up to `limit` enrichment cells for a worker: the oldest cells that are queued and due (`queuedAt` passed, so a retry waits out its backoff), and running cells whose lease ran out. Each claimed cell is set to `running` with `startedAt`, `leaseUntil` (now plus `leaseMs`), `attempts` plus one and a new random `claimToken`. See [Enrichment run queue](#enrichment-run-queue). Each claim carries the column config the worker needs (`kind`, `title`, `provider`, `prompt`, `output`), so the worker never looks the column up again. Concurrent workers never claim the same cell. Every claim is a compare-and-set: the write matches the cell only in the state the claim read (its status, token, attempts and run) and while it is still claimable, so when two workers read the same cell, only the first write changes it. A worker that lost cells to another reads further candidates, up to five rounds, so it does not stop while cells are still queued. A claim also settles cells it can not hand out: a cell whose required input is now missing is set to `empty` with the missing column named, a cell whose enrichment input is queued or running again waits for it (queued with `waitingFor`, released when that input finishes), and a running cell whose lease ran out on its last attempt (`maxAttempts`) becomes an `error`. A cell a claim sets to `empty` or `error` releases the cells waiting for it, as a final result does. ##### Response ```yaml - rowKey: 66f9… # the row key (rowKeyField) columnKey: email kind: enrichment # or ai title: Email # when the column has one provider: finder # ai columns: ai unless they name another prompt: … # ai columns output: email # when set: the enrichment result path, or the ai answer's { type, options? } runId: 66f9… claimToken: 3b1f…:0659b6fcca796a attempt: 1 inputHash: 0659b6fcca796a inputs: # resolved from columnDefs domain: acme.test row: # _id, the row key and the fields paths only _id: … name: Acme domain: acme.test ``` #### Properties - `columnDefs: object[]`: __Required__ - The table columns, declared and user-defined merged. See [Enrichment run queue](#enrichment-run-queue). - `fields: object`: __Required__ - The table's `MongoDBTableQuery` fields, keyed by column key: the fields column inputs read and claimed rows return. - `columns: string[]`: Claim only cells of these enrichment or ai columns. Defaults to every enrichment and ai column. - `providers: string[]`: Claim only cells of columns that use these providers, for a worker per provider. - `filter: object`: The base filter every read and write is scoped by. Required unless the connection is tenant-scoped. Set it to `{}` to claim cells of every document. - `limit: integer`: Default: `20` - The most cells one claim returns, at most 200. - `leaseMs: integer`: Default: `120000` - How long a claimed cell stays with its worker. After it, the cell can be claimed again, and the first worker's result is ignored. Keep it longer than a provider call, and keep the servers' clocks in sync. - `maxAttempts: integer`: Default: `3` - A cell whose lease ran out on this attempt becomes an error instead of being claimed again. Use the same value as `MongoDBEnrichmentComplete`. - `rowKeyField: string`: Default: `_id` - The document field returned as `rowKey`. ### MongoDBEnrichmentComplete The `MongoDBEnrichmentComplete` request writes a worker's results to the cells it claimed. A result is applied only while its cell still holds the claim, its `claimToken` with status `running`, so a worker whose lease ran out (and whose cell was claimed again or queued again since) changes nothing, and a result sent twice is applied once. See [Enrichment run queue](#enrichment-run-queue). - `ok` and `empty` results replace the cell's `value` and `raw` (an empty result has no value), and store `finishedAt` and the `inputHash` of the inputs the claim gave the worker. - An `error` below `maxAttempts` goes back to `queued` with its error message, due after `backoffMs * 2^(attempt - 1)` (30 seconds, then 60, …, at most a day), or after the result's `retryAfterMs` when the provider said how long to wait (a rate limit's `Retry-After`). On the last attempt, or with `retry: false`, the error is final. The previous value stays in both cases. - A final result (`ok`, `empty`, or an error that is not retried) releases the cells of the row waiting for it (`waitingFor`), so they are claimed next; one whose input failed then finds it missing. - An `ok` result queues the `autoRun` columns that read the column, in its row, unless they are queued or running already. - A `raw` larger than `rawMaxBytes` is stored as `{ _truncated: true, bytes, maxBytes, preview }`, with the first 1000 characters of its JSON, so one large response can not fill the row. A `value` larger than `rawMaxBytes` makes the result a final error. ##### Response ```yaml applied: 18 # results written ignored: 2 # results whose claim no cell holds any more requeued: 1 # errors queued again for a retry released: 4 # waiting cells released by the results downstream: # autoRun columns that read a column that just completed ok, now queued - rowKey: 66f9… columns: [pitch] ``` The `downstream` columns are already queued: the worker's next claim runs them. #### Properties - `results: object[]`: __Required__ - At most 1000 results, each: - `rowKey: any`: __Required__ - The `rowKey` of the claim. - `columnKey: string`: __Required__ - The `columnKey` of the claim. - `claimToken: string`: __Required__ - The `claimToken` of the claim. - `status: enum`: __Required__ - `ok`, `empty` (no result) or `error`. - `value: any`: The result the column shows. - `raw: any`: The provider's response. - `cost: integer`: What the provider call behind the result cost, in micro-USD, such as a [`TregCall`](/Treg) `cost.micro`. Stored as the cell's `cost` with any status; a result without a cost leaves the cell's cost as it was. - `error: string | null`: The error message, with status `error`. `null` is the same as leaving it out. - `retry: boolean | null`: Default: `true` - With status `error`, `false` makes the error final. `null` is the same as leaving it out. - `retryAfterMs: integer | null`: With status `error` below `maxAttempts`, the wait before the cell is claimed again, in place of the exponential backoff, at most a day. A worker passes a `ServiceError`'s `retryAfter` (seconds, such as a [`TregCall`](/Treg) 429 or 503) times 1000. `null` is the same as leaving it out. - `columnDefs: object[]`: __Required__ - The table columns, declared and user-defined merged. - `filter: object`: The base filter every read and write is scoped by. Required unless the connection is tenant-scoped. - `maxAttempts: integer`: Default: `3` - The attempts a cell gets. - `backoffMs: integer`: Default: `30000` - The wait before a failed cell is claimed again, doubled per attempt made. - `rawMaxBytes: integer`: Default: `65536` - The largest raw response (BSON bytes) stored as it is. - `rowKeyField: string`: Default: `_id` - The document field a row key matches. - `rowKeyType: enum`: Default: `auto` - How row keys are read, as for `MongoDBTableChanges`. ### MongoDBEnrichmentEnqueue The `MongoDBEnrichmentEnqueue` request queues cells of enrichment columns: the cells of `columns` in the selected rows that `mode` names. See [Enrichment run queue](#enrichment-run-queue). - `all`: every cell that is not queued, or running with a live lease. A run in progress is never restarted, whatever the mode. - `empty`: cells never run, or whose last result was empty. - `errors`: cells that failed. - `stale`: cells with a result whose inputs changed since it was computed. The request reads each candidate row's inputs (in batches of 1000 rows, with only the input paths projected) and compares their hash with the cell's `inputHash`. A queued cell gets `status: queued`, the `runId`, `queuedAt` and `attempts: 0`; its previous value, raw and inputHash stay until a new result lands. A cell whose required input has no value is set to `empty` with `error: Missing input: ` instead, and its previous result is cleared. Columns can run together with the columns they read: the request plans them upstream first, and a cell whose required enrichment input is queued in the same call (or is queued or running already) is queued with `waitingFor: []`, out of claims until that input finishes, never `Missing input`. So one call can run `[email, pitch]` where the pitch reads the email. `selection` is the table's `selected` value: row keys, or `{ all: true, except, filter, search }`, compiled against `fields` like `MongoDBTableChanges` compiles it, with the same limits. Leave it out to run every row inside the base `filter`. Row keys are read as in `MongoDBTableChanges` Row keys: an ObjectId as `{ _oid }` or its Table key text, and a numeric key as the number or its text. The cells are written as unordered bulkWrites of 1000 cells, each write guarded by the mode's condition, so a cell a worker took since it was read is left alone. When more than `maxCells` cells would be written, nothing is written and the request throws, so a large run can be confirmed first. ##### Response ```yaml queued: 2400 # cells queued, those waiting for an input column included skipped: 12 # selected cells the mode left alone (live, fresh or not matching) missingInputs: 3 # cells set to empty for a missing input byColumn: # the same counts for each column run, summing to the totals email: { queued: 1200, missingInputs: 3 } pitch: { queued: 1200, missingInputs: 0 } runId: 66f9… ``` ##### Dry run With `dryRun: true` the request reads and plans exactly as a run does (the same selection, mode, `maxCells`, `maxTimeMS` and tenant scope) and returns the counts the run would write, writing no cell and no change log record. The response is `{ dryRun: true, queued, missingInputs, skipped, byColumn }`, with no `runId`. Over `maxCells` it throws as a run would, so an app learns a run is too large before it asks. Use it to confirm a run that costs money before it starts: in the run event, call the request with `dryRun: true`, show a confirm with each column's `queued` count times that column's provider cost, and on a yes call it again without `dryRun`. Cells can change between the two calls, so the run's own response, whose `byColumn` has the same shape, says what was queued. #### Properties - `columns: string[]`: __Required__ - The keys of the enrichment or ai columns to run. - `columnDefs: object[]`: __Required__ - The table columns, declared and user-defined merged. - `fields: object`: __Required__ - The table's `MongoDBTableQuery` fields, keyed by column key: the fields column inputs read and a select-all selection is compiled against. - `selection: object | any[]`: The rows to run, the `Table` `selected` value. Defaults to every row inside `filter`. - `mode: enum`: Default: `all` - `all`, `empty`, `errors` or `stale`. - `filter: object`: The base filter every read and write is scoped by, for example `{ org_id: { _user: organization.id } }`. Required unless the connection is tenant-scoped. Set it to `{}` to run on every document. - `maxCells: integer`: Default: `10000` - The most cells one enqueue may write, queued and missing input cells together. - `maxTimeMS: integer`: Default: `30000` - The time limit of each read. - `dryRun: boolean`: Default: `false` - Count the cells the run would queue and set empty, by column, and write nothing. See [Dry run](#dry-run). - `runId: string`: The `runId` the queued cells get, 1 to 64 letters, digits, `_` or `-`. Generated when left out. - `user: object`: The user that `{ $user: path }` values in a selection filter resolve from. Set it to `{ _user: true }`. - `timezone: string`: Default: `UTC` - The IANA time zone whose days the date filters of a selection compare. - `rowKeyField: string`: Default: `_id` - The document field a row key matches. - `rowKeyType: enum`: Default: `auto` - How row keys are read, as for `MongoDBTableChanges`. ### MongoDBFind The `MongoDBFind` request executes a MongoDB [query](https://docs.mongodb.com/manual/tutorial/query-documents/) on the collection specified in the connectionId. It returns the array of documents returned by the query. >Cursors are not supported. The request will return the whole body of the response as an array. #### Properties - `query: object`: __Required__ - A MongoDB query object. - `options: object`: Optional settings. See the [driver documentation](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#find) for more information. Supported settings are: - `allowDiskUse: boolean`: Allows disk use for blocking sort operations exceeding 100MB memory. (MongoDB 3.2 or higher) - `allowPartialResults: boolean`: For queries against a sharded collection, allows the command (or subsequent getMore commands) to return partial results, rather than an error, if one or more queried shards are available. - `authdb: string`: Specifies the authentication information to be used. - `awaitData: boolean`: Specify if the cursor is a tailable-await cursor. Requires `tailable` to be true. - `batchSize: number`: Set the batchSize for the getMoreCommand when iterating over the query results. - `bsonRegExp: boolean`: Return the BSON regular expressions as BSONRegExp instances. - `checkKeys: boolean`: The serializer will check if keys are valid. - `collation: object`: Specify collation (MongoDB 3.4 or higher) settings for update operation. - `comment: string | object`: Add a [comment](https://docs.mongodb.com/manual/reference/operator/query/comment/index.html) to the query. These comments are visible in the MongoDB profile log, making them easier to interpret. - `dbName: string`: The database name. - `explain: boolean`: Specifies the verbosity mode for the explain output. - `fullResponse: boolean`: Return the full server response for the command. - `hint: string | object`: Tell the query to use specific indexes in the query. Object of indexes to use, `{'_id':1}`. - `ignoreUndefined: boolean`: Default: `true` - Serialize will not emit undefined fields. - `let: object`: Map of parameter names and values that can be accessed using `$$var` (requires MongoDB 5.0). - `limit: number`: Sets the limit of documents returned in the query. - `max: object`: The exclusive upper bound for a specific index. - `maxAwaitTimeMS: number`: The maximum amount of time for the server to wait on new documents to satisfy a tailable cursor query. Requires `tailable` and `awaitData` to be true. - `maxTimeMS: number`: Number of milliseconds to wait before aborting the command. - `min: object`: The inclusive lower bound for a specific index. - `noCursorTimeout: boolean`: The server normally times out idle cursors after an inactivity period (10 minutes) to prevent excess memory use. Set this option to prevent that. - `noResponse: boolean`: Admin command option. - `projection: object`: The fields to return in the query. Object of fields to either include or exclude (one of, not both), `{'a':1, 'b': 1}` or `{'a': 0, 'b': 0}`. - `readConcern: object`: Specify a read concern and level for the collection. (only MongoDB 3.2 or higher supported). - `readPreference: string | object`: The preferred read preference. - `retryWrites: boolean`: Should retry failed writes. - `returnKey: boolean`: If true, returns only the index keys in the resulting documents. - `serializeFunctions: boolean`: Default: `false` - Serialize the javascript functions. - `showRecordId: boolean`: Determine whether to return the record identifier for each document. If true, adds a field $recordId to the returned documents. - `singleBatch: boolean`: Default: `false` - Determines whether to close the cursor after the first batch. - `skip: number`: Set to skip N documents ahead in your query (useful for pagination). - `sort: array | object`: Set to sort the documents coming back from the query. - `tailable: boolean`: Specify if the cursor is tailable. - `timeout: boolean`: Specify if the cursor can timeout. - `willRetryWrites: boolean`: Option whether to retry writes. - `writeConcern: object`: An object that expresses the write concern. #### Examples ###### Find top ten scores above 90: ```yaml requests: - id: scores_top_ten_scores_above_90 type: MongoDBFind connectionId: my_mongodb_collection_id properties: query: score: $gt: 90 options: sort: - - score - -1 limit: 10 projection: score: 1 name: 1 ``` ### MongoDBFindOne The `MongoDBFindOne` request executes a MongoDB [query](https://docs.mongodb.com/manual/tutorial/query-documents/) on the collection specified in the connectionId. It returns the first document that matches the specified query. >Cursors are not supported. The request will return the whole body of the response as an array. #### Properties - `query: object`: __Required__ - A MongoDB query object. - `options: object`: Optional settings. See the [driver documentation](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#findone) for more information. Supported settings are: - `allowDiskUse: boolean`: Allows disk use for blocking sort operations exceeding 100MB memory. (MongoDB 3.2 or higher) - `allowPartialResults: boolean`: For queries against a sharded collection, allows the command (or subsequent getMore commands) to return partial results, rather than an error, if one or more queried shards are available. - `authdb: string`: Specifies the authentication information to be used. - `awaitData: boolean`: Specify if the cursor is a tailable-await cursor. Requires `tailable` to be true. - `batchSize: number`: Set the batchSize for the getMoreCommand when iterating over the query results. - `bsonRegExp: boolean`: Return the BSON regular expressions as BSONRegExp instances. - `checkKeys: boolean`: The serializer will check if keys are valid. - `collation: object`: Specify collation (MongoDB 3.4 or higher) settings for update operation. - `comment: string | object`: Add a [comment](https://docs.mongodb.com/manual/reference/operator/query/comment/index.html) to the query. These comments are visible in the MongoDB profile log, making them easier to interpret. - `dbName: string`: The database name. - `explain: boolean`: Specifies the verbosity mode for the explain output. - `fullResponse: boolean`: Return the full server response for the command. - `hint: string | object`: Tell the query to use specific indexes in the query. Object of indexes to use, `{'_id':1}`. - `ignoreUndefined: boolean`: Default: `true` - Serialize will not emit undefined fields. - `let: object`: Map of parameter names and values that can be accessed using `$$var` (requires MongoDB 5.0). - `limit: number`: Sets the limit of documents returned in the query. - `max: object`: The exclusive upper bound for a specific index. - `maxAwaitTimeMS: number`: The maximum amount of time for the server to wait on new documents to satisfy a tailable cursor query. Requires `tailable` and `awaitData` to be true. - `maxTimeMS: number`: Number of milliseconds to wait before aborting the command. - `min: object`: The inclusive lower bound for a specific index. - `noCursorTimeout: boolean`: The server normally times out idle cursors after an inactivity period (10 minutes) to prevent excess memory use. Set this option to prevent that. - `noResponse: boolean`: Admin command option. - `projection: object`: The fields to return in the query. Object of fields to either include or exclude (one of, not both), `{'a':1, 'b': 1}` or `{'a': 0, 'b': 0}`. - `readConcern: object`: Specify a read concern and level for the collection. (only MongoDB 3.2 or higher supported). - `readPreference: string | object`: The preferred read preference. - `retryWrites: boolean`: Should retry failed writes. - `returnKey: boolean`: If true, returns only the index keys in the resulting documents. - `serializeFunctions: boolean`: Default: `false` - Serialize the javascript functions. - `showRecordId: boolean`: Determine whether to return the record identifier for each document. If true, adds a field $recordId to the returned documents. - `singleBatch: boolean`: Default: `false` - Determines whether to close the cursor after the first batch. - `skip: number`: Set to skip N documents ahead in your query (useful for pagination). - `sort: array | object`: Set to sort the documents coming back from the query. - `tailable: boolean`: Specify if the cursor is tailable. - `timeout: boolean`: Specify if the cursor can timeout. - `willRetryWrites: boolean`: Option whether to retry writes. - `writeConcern: object`: An object that expresses the write concern. #### Examples ###### Find a document by id: ```yaml requests: - id: find_by_id type: MongoDBFindOne connectionId: my_mongodb_collection_id payload: _id: _input: _id properties: query: _id: _payload: _id ``` ### MongoDBInsertConsecutiveId The `MongoDBInsertConsecutiveId` request inserts a single document into the collection specified in the connectionId, assigning a sequential, human-readable `_id` like `INV0001`. The id is built from a prefix and the next consecutive number for that prefix, zero-padded to `length` digits. The id read and the insert run inside a transaction so concurrent requests cannot claim the same id. > Transactions require MongoDB to run as a replica set. Standalone deployments are not supported by this request. #### Properties - `doc: object`: __Required__ - The document to be inserted. The `_id` is assigned by the request. - `prefix: string`: __Required__ - Prefix to add to the id, for example `INV`. - `length: number`: The numeric part of the id is zero-padded to this number of digits. If omitted, the number is not padded. - `options: object`: Optional settings passed to the insert. See the [driver documentation](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#insertone) for more information. #### Examples ###### Insert an invoice with a sequential id (INV0001, INV0002, ...): ```yaml requests: - id: insert_invoice type: MongoDBInsertConsecutiveId connectionId: my_mongodb_collection_id properties: prefix: INV length: 4 doc: amount: _state: amount ``` ### MongoDBInsertMany The `MongoDBInsertMany` request inserts an array of documents into the collection specified in the connectionId. If a `_id` field is not specified on a document, a MongoDB `ObjectID` will be generated. #### Properties - `docs: object[]`: __Required__ - The array of documents to be inserted. - `options: object`: Optional settings. See the [driver documentation](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#insertmany) for more information. Supported settings are: - `authdb: string`: Specifies the authentication information to be used. - `bsonRegExp: boolean`: Return the BSON regular expressions as BSONRegExp instances. - `bypassDocumentValidation: boolean`: Default: `false` - Allow driver to bypass schema validation in MongoDB 3.2 or higher - `checkKeys: boolean`: Default: `true` - If true, will throw if bson documents start with $ or include a . in any key value. - `collation: object`: Specify collation (MongoDB 3.4 or higher) settings for update operation. - `comment: string | object`: Add a [comment](https://docs.mongodb.com/manual/reference/operator/query/comment/index.html) to the query. These comments are visible in the MongoDB profile log, making them easier to interpret. - `dbName: string`: The database name. - `explain: object`: Specifies the verbosity mode for the explain output. - `forcesServerObjectId: boolean`: Default: `false` - Force server to assign _id values instead of driver. - `fullResponse: boolean`: Return the full server response for the command. - `ignoreUndefined: boolean`: Default: `false` - Specify if the BSON serializer should ignore undefined fields. - `maxTimeMS: number`: Number of milliseconds to wait before aborting the command. - `noResponse: boolean`: Admin command option. - `ordered: boolean`: If true, when an insert fails, don't execute the remaining writes. If false, continue with remaining inserts when one fails. - `readConcern: object`: Specify a read concern and level for the collection. (only MongoDB 3.2 or higher supported). - `readPreference: object`: The preferred read preference. - `retryWrites: boolean`: Should retry failed writes. - `serializeFunctions: boolean`: Default: `false` - Serialize the javascript functions. - `willRetryWrites: boolean`: Option whether to retry writes. - `writeConcern: object`: An object that expresses the write concern. #### Examples ###### Insert a set of documents: ```yaml requests: - id: insert_documents type: MongoDBInsertMany connectionId: my_mongodb_collection_id properties: docs: - _id: 1 value: 4 - _id: 2 value: 1 - _id: 3 value: 7 ``` ### MongoDBInsertManyConsecutiveIds The `MongoDBInsertManyConsecutiveIds` request inserts an array of documents into the collection specified in the connectionId, assigning each document a sequential, human-readable `_id` like `INV0001`. Ids continue from the highest existing id for the prefix. The id read and the insert run inside a transaction so concurrent requests cannot claim the same ids. > Transactions require MongoDB to run as a replica set. Standalone deployments are not supported by this request. #### Properties - `docs: object[]`: __Required__ - The documents to be inserted. The `_id` values are assigned by the request. - `prefix: string`: __Required__ - Prefix to add to the ids, for example `INV`. - `length: number`: The numeric part of each id is zero-padded to this number of digits. If omitted, the numbers are not padded. - `options: object`: Optional settings passed to the insert. See the [driver documentation](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#insertmany) for more information. #### Examples ###### Insert order lines with sequential ids: ```yaml requests: - id: insert_order_lines type: MongoDBInsertManyConsecutiveIds connectionId: my_mongodb_collection_id properties: prefix: LINE length: 6 docs: _state: order_lines ``` ### MongoDBInsertOne The `MongoDBInsertOne` request inserts a document into the collection specified in the connectionId. If a `_id` field is not specified, a MongoDB `ObjectID` will be generated. #### Properties - `doc: object`: __Required__ - The document to be inserted. - `options: object`: Optional settings. See the [driver documentation](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#insertone) for more information. Supported settings are: - `authdb: string`: Specifies the authentication information to be used. - `bsonRegExp: boolean`: Return the BSON regular expressions as BSONRegExp instances. - `bypassDocumentValidation: boolean`: Default: `false` - Allow driver to bypass schema validation in MongoDB 3.2 or higher - `checkKeys: boolean`: Default: `true` - If true, will throw if bson documents start with $ or include a . in any key value. - `collation: object`: Specify collation (MongoDB 3.4 or higher) settings for update operation. - `comment: string | object`: Add a [comment](https://docs.mongodb.com/manual/reference/operator/query/comment/index.html) to the query. These comments are visible in the MongoDB profile log, making them easier to interpret. - `dbName: string`: The database name. - `explain: object`: Specifies the verbosity mode for the explain output. - `forcesServerObjectId: boolean`: Default: `false` - Force server to assign _id values instead of driver. - `fullResponse: boolean`: Return the full server response for the command. - `ignoreUndefined: boolean`: Default: `false` - Specify if the BSON serializer should ignore undefined fields. - `maxTimeMS: number`: Number of milliseconds to wait before aborting the command. - `noResponse: boolean`: Admin command option. - `readConcern: object`: Specify a read concern and level for the collection. (only MongoDB 3.2 or higher supported). - `readPreference: object`: The preferred read preference. - `retryWrites: boolean`: Should retry failed writes. - `serializeFunctions: boolean`: Default: `false` - Serialize the javascript functions. - `willRetryWrites: boolean`: Option whether to retry writes. - `writeConcern: object`: An object that expresses the write concern. #### Examples ###### Insert a document: ```yaml requests: - id: insert_new_comment type: MongoDBInsertOne connectionId: my_mongodb_collection_id payload: comment: _state: comment_input properties: doc: comment: _payload: comment user_id: _user: id timestamp: _date: now ``` ### MongoDBTableChanges The `MongoDBTableChanges` request saves the value of a `TableInput` block. The value of a `TableInput` is not its rows but a changeset: only what changed since `data`. The request validates the changeset against an allowlist of `fields` and compiles it to one `bulkWrite` on the server. The browser never sends MongoDB syntax: keys that are not in `fields`, keys that start with `$` (operators such as `$where`, positional paths such as `items.$[x]`), values of the wrong type, and operator objects such as `{ $ne: null }` as values or row keys are refused before anything is written. The changeset is the `TableInput` value: ```yaml updated: # changed fields of existing rows: { [rowKey]: { [field]: value } } flour: qty: 450 # the key is the column field, a dot path details.note: strong added: # new rows: [{ rowKey, ...fields }] - rowKey: 3f2c9a61-… # a temporary key the browser generated ingredient: Yeast qty: 7 removed: # existing rows to delete: [rowKey] - salt moved: # with rowDrag.positionField: { [rowKey]: position } water: 1536 order: [salt, flour] # with rowDrag and no positionField: every row key in the new order ``` The `fields` keys are the column `field` dot paths, because those are the keys of the changeset, and only those fields can be written (at their `path`). This differs from `MongoDBTableQuery`, whose `fields` are keyed by the column `key`: a `TableInput` column `{ key: owner, field: owner.name }` is `owner.name` here and `owner` (with `path: owner.name`) in `MongoDBTableQuery`. A changeset key that is the `path` of a field keyed otherwise is refused with an error that names the mismatch. Values are coerced to the field type like `MongoDBTableQuery` values: numbers from numeric strings, dates from date strings and timestamps, and `null` clears a value of any type. `avatar`, `image` and `json` fields accept documents, but not with keys that start with `$`. Every operation is scoped by the base `filter`, for example the organization or owner of the rows. The row key is combined with the filter using `$and`, so it can only narrow it. `filter` is required, unless the connection is tenant-scoped: then the tenant is merged into every operation and stamped on new rows, as for `MongoDBBulkWrite`. Set `filter: {}` to allow writes to every document in the collection. ##### Scope fields The `filter` and `insertDefaults` fields are the scope of the rows, such as their organization or owner, and the table can never write them: - A field (or the `positionField`) whose path is a `filter` field, or inside or over one, is refused (with a `filter` on `owner.id`, the fields `owner.id`, `owner` and `owner.id.name` are all refused). Otherwise an update could move a row out of scope. This includes fields named inside `$and`, `$or` and `$nor`. - A field whose path is set by `insertDefaults`, or inside or over one of its values, is refused, so a new row can not replace a default. A default `{ address: { country: FR } }` still leaves `address.city` writable. For a starting value the user may change, give the `TableInput` column a default instead. - New rows are stamped with the equality conditions of the `filter`: `{ org_id: X }`, `{ org_id: { $eq: X } }` and the equalities inside `$and`. A row added with a `filter` of `{ org_id: { _user: organization.id } }` belongs to that organization without an `insertDefaults` entry. `insertDefaults` may repeat such a value, but a different value is refused. - Any other `filter` condition (`$ne`, `$in`, a regex, the clauses of `$or`) can not be stamped. A save that adds rows then needs that field set by `insertDefaults`. A `filter` with an operator such as `$expr` or `$where` refuses added rows. Saves that only update, move or remove rows are not affected. In array mode the `filter` scopes the document: every operation, the `$push` of new items included, matches only the document with `array.documentId` inside the `filter`. Item fields are compared with the `filter` at their path in the document (`items.`), and new items are not stamped, since they are not documents of the collection. ##### Collection mode By default every row is a document, matched by `rowKeyField` (default `_id`). The changeset compiles to: ```yaml - deleteOne: { filter: { $and: [, { _id: }] } } # one per removed row - updateOne: # one per updated or moved row filter: { $and: [, { _id: }] } update: { $set: { qty: 450, details.note: strong, position: 1536 } } # its fields and position in one $set - insertOne: { document: { , , _id: } } # one per added row ``` An update sets only the changed dot paths, so the rest of the document is kept. A move writes one position field on one row. An `order` needs a `positionField` in collection mode: it writes positions 1024, 2048, … to every row in the order. ##### Array mode With `array: { documentId, path }`, the rows are the items of an embedded array in one document, such as the ingredients of a recipe. Every operation matches that document inside the base filter, and field paths and `positionField` are paths in the item. MongoDB can not `$set` into, `$pull` from and `$push` to the same array in one update, so the changeset compiles to up to four updates of that document, run in order: ```yaml - $set: { items.$[r0].qty: 450, items.$[r1].position: 1536 } # arrayFilters: [{ r0._id: flour }, { r1._id: water }] - $pull: { items: { _id: { $in: [salt] } } } - $push: { items: { $each: [] } } - # only for an order without a positionField ``` New items get a generated ObjectId `_id` when `itemKeyField` is `_id`. Without a `positionField`, the array order is the row order, and an `order` is applied by a pipeline update that reorders the items on the server, as the array is when the update runs. Items are moved, never rewritten, so an edit someone else saved to an item in the meantime is kept; items the order does not name (added since the table loaded) follow in their current order, and keys of items that are gone are skipped. Setting the whole array from the browser's copy instead would overwrite such edits. When the document is not found inside the filter, nothing is written and the request throws. ##### Response ```yaml matchedCount: 2 # collection mode: rows matched by updates and moves modifiedCount: 2 insertedCount: 1 deletedCount: 1 insertedKeys: # the key each added row got, by its temporary rowKey 3f2c9a61-…: { _oid: 66f9… } unmatchedKeys: [] # updated, moved or removed rows that matched nothing ``` In array mode `matchedCount` and `modifiedCount` count the document (0 or 1), `insertedCount` is the items pushed and `deletedCount` the removed items that were in the array. `unmatchedKeys` lists the existing rows the changeset named that matched nothing: outside the `filter` (another organization's row), deleted by someone else, or never there. Treat a non-empty `unmatchedKeys` as a failed save, for example with a `Throw` action after the request, since nothing was written for those rows. Numeric keys are listed as numbers and ObjectId keys as `{ _oid }`. The rows are found with a read of their keys, scoped like the writes: removed rows are read before the write (a removed row that someone else deletes between that read and the write counts as matched, since it is gone as asked), and updated rows after it, only when fewer rows matched than were updated. A save that only updates rows that all match is one round trip. The operations are one `bulkWrite`, not a transaction: with `ordered: true` the first failing operation stops the rest, and the ones before it stay written. ##### Row keys The `TableInput` keys rows by `rowKey` (default `_id`, then `id`). An ObjectId key comes back from the browser as the text `{"_oid":"…"}`, which the default `rowKeyType: auto` reads as an ObjectId. Object keys are always strings, so a numeric key `5` arrives as `"5"` in `updated` and `moved`, and as `5` in `removed` and `order`. With `rowKeyType: auto` both are the same row, and it matches a document key `5` or `"5"` (`{ $in: [5, "5"] }`). A string is read as a number only when it is exactly how that number prints (`"5"`, `"-2"`, `"1.5"`; not `"05"` or `"1e3"`). If a collection holds both `5` and `"5"` as different rows, set `rowKeyType: number` or `rowKeyType: string` to match one form only. `rowKeyType: objectId` also reads 24 character hex strings as ObjectIds. ##### Bulk selection Set `selection` instead of `changes` to write the same values to many rows, such as a bulk "Assign owner" action on the rows selected in a `Table`. `selection` is the table's `selected` value: - an array of row keys, which compiles to `{ : { $in: [keys] } }`; - `{ all: true, except, filter, search }` when the header checkbox selects every row the view matches: the keys in `except` were deselected since, and `filter` and `search` are the view's. It compiles to the view's filter and search, validated and compiled like `MongoDBTableQuery` against `queryFields` (the table's `MongoDBTableQuery` `fields`, with the same limits and refusals), and `{ : { $nin: [except] } }`. `set` is `{ [field]: value }` and `unset` a list of fields, both `fields` keys, checked and coerced like `updated` values, and never a scope field. The save is one `updateMany`: ```yaml - updateMany: filter: { $and: [, , , { _id: { $nin: [] } }] } update: { $set: { owner.name: Ada }, $unset: { due: '' } } ``` The base `filter` (and the tenant, on a tenant connection) is one clause of the `$and`, so a crafted view can only narrow it. Row keys are read as in Row keys, and a `selection` has at most `maxChanges` keys. The response is `{ matchedCount, modifiedCount }`. `selection` can not be used in array mode. #### Properties - `changes: object`: The `TableInput` value, `{ _payload: changes }`: `{ updated, added, removed, moved, order }`. Required unless `selection` is set. - `selection: object | any[]`: Bulk mode: the rows to write `set` and `unset` to, the `Table` `selected` value, `{ _payload: selected }`. See Bulk selection. - `set: object`: Bulk mode: `{ [field]: value }` to set on every selected row. - `unset: string[]`: Bulk mode: fields to remove from every selected row. - `queryFields: object`: Bulk mode: the `MongoDBTableQuery` `fields` of the table, keyed by column key. Needed when a `selection` has a `filter` or `search`. - `user: object`: Bulk mode: the user that `{ $user: path }` values in a `selection` filter resolve from. Set it to `{ _user: true }`. - `timezone: string`: Default: `UTC` - Bulk mode: the IANA time zone whose days the date filters of a `selection` compare, as for `MongoDBTableQuery`. - `fields: object`: __Required__ - The allowlist, keyed by the `TableInput` column `field` (its dot path, the changeset key), not the column `key` that `MongoDBTableQuery` fields use. Each field is an object: - `type: enum`: __Required__ - The column type, as for `MongoDBTableQuery`. Values are checked and coerced to it. - `path: string`: Default: the field key - Dot path the value is written to, in the document or, in array mode, in the item. - `filter: object`: The base filter that scopes every operation, for example `{ org_id: { _user: organization.id } }`. New rows are stamped with its equality conditions, and `fields` can not write its fields. Required unless the connection is tenant-scoped. Set it to `{}` to allow writes to every document. See Scope fields. - `rowKeyField: string`: Default: `_id` - The document field a row key matches, in collection mode. With any other field, added rows need their key from a column or `insertDefaults`. - `rowKeyType: enum`: Default: `auto` - How row keys are read and matched: `auto`, `objectId`, `string` or `number`. See Row keys. - `array: object`: Array mode: the rows are the items of an embedded array in one document. - `documentId: any`: __Required__ - The `_id` of the document. Use `{ _oid: }` for an ObjectId from a string. - `path: string`: __Required__ - Dot path of the array in the document. - `itemKeyField: string`: Default: `_id` - The item field a row key matches. - `positionField: string`: Dot path of the numeric position field, the `TableInput` `rowDrag.positionField`. Needed to save `moved`, and to save `order` in collection mode. - `insertDefaults: object`: Values every added row gets, for example `{ created_by: { _user: id } }` or a created date. `fields` can not write them. Keys may be dot paths. See Scope fields. - `ordered: boolean`: Default: `true` - Run the operations in order and stop at the first error. `false` (collection mode only) runs every operation and reports every error. - `maxChanges: integer`: Default: `1000` - The most row changes one request may apply (updated, added, removed and moved rows, plus order entries). A larger changeset throws. - `options: object`: Optional `bulkWrite` settings, for example `writeConcern`, `bypassDocumentValidation` or `comment`. See `MongoDBBulkWrite`. #### Examples ###### Save the ingredients of a recipe (array mode): The ingredients are an array in the recipe document, ordered by a `position` field. Dragging a row gives it a position between its neighbours, so a move saves one field on one item. ```yaml id: recipe type: Box requests: - id: get_recipe type: MongoDBAggregation connectionId: recipes payload: recipe_id: _url_query: id properties: pipeline: - $match: _id: _payload: recipe_id org_id: _user: organization.id - $project: title: 1 items: $sortArray: input: $items sortBy: position: 1 - id: save_items type: MongoDBTableChanges connectionId: recipes payload: recipe_id: _url_query: id changes: _state: items properties: array: documentId: _payload: recipe_id path: items filter: org_id: _user: organization.id positionField: position fields: ingredient: type: text qty: type: number unit: type: text changes: _payload: changes events: onMount: - id: fetch type: Request params: get_recipe blocks: - id: items type: TableInput properties: data: _request: get_recipe.0.items addRow: true rowDrag: positionField: position rowActions: delete: true columns: - key: ingredient editable: true - key: qty type: number editable: true - key: unit editable: true - id: save type: Button properties: title: Save events: onClick: - id: save_items type: Request params: save_items - id: check_saved type: Throw params: throw: _gt: - _array.length: _if_none: - _request: save_items.unmatchedKeys - [] - 0 message: Some items were changed or removed by someone else. Reload and try again. - id: refetch type: Request params: get_recipe - id: reset type: CallMethod params: blockId: items method: resetChanges ``` ###### Save edits to a list of deals (collection mode): Every row is a document. New deals get the organization from the `filter` and the creator from `insertDefaults`. The table can not set either, and a `fields` entry for `org_id` or `created_by` would be refused. ```yaml requests: - id: save_deals type: MongoDBTableChanges connectionId: deals payload: changes: _state: deals_table properties: filter: org_id: _user: organization.id fields: # Keyed by the TableInput column field, the changeset keys. name: type: text stage: type: tag amount: type: currency owner.name: type: text close_date: type: date insertDefaults: org_id: _user: organization.id created_by: _user: id created: _date: now changes: _payload: changes ``` ###### Assign an owner to the selected deals (bulk selection): The `deals_table` `Table` in server mode shows the deals of `deals_page` (the `MongoDBTableQuery` example below). A button in its `bulkActions` sends the table's `selected` value, a list of keys or every row matching the view, and the request sets the owner on those rows inside the organization. ```yaml requests: - id: assign_owner type: MongoDBTableChanges connectionId: deals payload: selected: _state: deals_table.selected owner: _state: new_owner properties: filter: org_id: _user: organization.id fields: owner.name: type: text # The table's MongoDBTableQuery fields, keyed by column key. queryFields: name: type: text search: true stage: type: tag owner: type: text path: owner.name search: true amount: type: currency user: _user: true selection: _payload: selected set: owner.name: _payload: owner ``` ### MongoDBTableQuery The `MongoDBTableQuery` request serves a `Table` block in server mode. The table sends its view (sort, filter, search, grouping and aggregates) and the rows it needs, and the request compiles them to one aggregation on the server, against an allowlist of `fields`. The browser never sends MongoDB syntax: view keys that are not in `fields`, operators the field type does not allow and values of the wrong type are refused, regex input is escaped, and no `$where`, `$expr` or raw stages are accepted from the view. The base `pipeline` always runs first, so use it for tenant or permission scoping. The view can only narrow what the base pipeline returns. On a tenant connection the tenant scope is added before the base pipeline, as for `MongoDBAggregation`. Rows return only `_id`, the `fields` paths and the `returnFields` paths (`project: true`, the default), so a field the table does not list never reaches the browser, even when the documents hold it. Add the paths that cells read without a field of their own to `returnFields`, such as an avatar `srcField`, a link `labelField` or a `rowKey` other than `_id`. Set `project: false` to return the documents as the base pipeline leaves them, for example when it ends with its own `$project`. The request compiles to: ```yaml - - $match: - $match: - $match: - $sort: # when rows are returned - $facet: rows: [$skip, $limit, $project] # or groups: [$group, $sort, $skip, $limit] total: [$count] aggregates: [$group] # when the view has aggregates distinct_a0: [$group, $count] # one per countDistinct ``` Rows are sorted by the view sort with `_id` as a tiebreak, so blocks of rows never repeat or skip a row. The sort runs before `$facet`, so an index on the base pipeline match, filter and sort fields (for example `{ org_id: 1, amount: -1, _id: 1 }` for a table of one organization sorted by amount) serves it, and each block reads the rows in index order. Without such an index, MongoDB sorts every matching document for each block, so add indexes for the sorts a large table offers. ##### Response ```yaml rows: [...] # the rows from startRow to endRow, [] when groups are returned total: 12408 # rows matching the view, or the number of groups at this level groups: # only when view.group has more levels than groupPath - key: won # the group value (null for missing values) count: 210 aggregates: { amount: 125000 } aggregates: # only when view.aggregates is set, over the rows this request matches amount: 3400000 sentence: Stage is any of Lead or Won # only when view.filter is set ``` `sentence` is the filter in words, from the field `title` and `options` (`describeCondition` in `@lowdefy/helpers`). It reads the same as the Table's Filter button tooltip when each field carries its column's plain-text `title` and its options as `[{ value, label }]`; a field without a `title` reads as its key, where the Table shows the key humanized. An agent or a server routine that runs the request with no browser can then say what a saved view matches. When `view.group` is set and `groupPath` is shorter than it, the request returns the groups of the next level, inside the groups in `groupPath`. When `groupPath` has a value for every level, it returns the leaf rows of that group. ##### Filters The view `filter` is a condition: `{ and: [...] }`, `{ or: [...] }` or `{ key, op, value }`. The operators each field type allows: | Field types | Operators | | --- | --- | | all | `eq`, `ne`, `in`, `nin`, `empty`, `notEmpty` | | `text`, `email`, `phone`, `url`, `link`, `html`, `relation`, `tag`, `status`, `avatar` | `contains`, `notContains`, `startsWith`, `endsWith` | | `number`, `currency`, `percent`, `progress`, `rating` | `gt`, `gte`, `lt`, `lte`, `between` (`value: [from, to]`) | | `date`, `datetime` | `before`, `after`, `between`, `within` (`value: { last or next: n, unit: day, week, month or year }`); `before` and `after` take a date or the same relative value | | `boolean` | `isTrue`, `isFalse` | | `tags`, `people` | `contains` (has the value), `notContains` (does not have the value); `in` is has any of, `nin` is has none of | | `image`, `json` | the operators for all types only | These are the operators the `Table` filter menus offer for each column type, so any filter a user builds is accepted. An `avatar` field filters on the text at its `path` (the name the cell shows), like a `text` field. - Text comparisons are case-insensitive. - `empty` matches null, missing, `''` and `[]`. - `eq` and `ne` on dates compare whole days, and so do `before`, `after` and `between` on `date` fields. `within` covers whole days, from the start of the day `n` units ago to the end of today (or from the start of today to the end of the day `n` units ahead), as the table filters in the browser. - `before` and `after` with a relative value, such as `{ last: 3, unit: month }`, resolve when the query runs to the day it names in `timezone`: `before` matches values before the start of that day and `after` values from the end of it, on `date` and `datetime` fields alike. A bad relative value is refused with the message `within` gives. - Days are the days of `timezone`, UTC by default. The browser filters by the user's local days, so pass the user's time zone for day-based filters, for example `timezone: { _payload: timezone }` with `timezone: { _js: 'return Intl.DateTimeFormat().resolvedOptions().timeZone;' }` in the request `payload`. A date-only value such as `2026-03-01` (what `date` column filters send) is that day in the time zone. An instant is compared by the day it falls on there. - `isFalse` matches every value that is not `true`. - Numeric values may be numbers or numeric strings, and date values dates, date strings or timestamps. A value can be `{ $user: path }`, for example `{ key: owner, op: eq, value: { $user: id } }`. It is resolved on the server from the request's `user` property, which should be set to `{ _user: true }` (evaluated on the server from the session), never from a value the browser sends. The request throws when the user value is not set, rather than matching rows where the field is empty. ##### Aggregates `view.aggregates` maps field keys to a function. All field types allow `count`, `countDistinct`, `countEmpty`, `countNotEmpty` and `percentEmpty` (a fraction from 0 to 1). Numeric types also allow `sum`, `avg`, `min` and `max`, text types (`text`, `email`, `phone`, `url`, `link`, `html`, `relation`, `tag`, `status`, `avatar`) `min` and `max`, and date types `earliest` and `latest`. `min` and `max` of text leave out empty values and compare strings as MongoDB does: by code point, so `Z` comes before `a`, unless `options.collation` sets a locale. `countDistinct` groups by the field's values, so it needs `groupable: true` on the field, like grouping by it. Over the whole result it counts the groups in their own `$facet` branch, so a field with many distinct values never builds them all into one document; in a group level it collects the values of each group. ##### Limits The view comes from the browser, so the work one request can ask of MongoDB is bounded: - A filter has at most 200 conditions and groups (empty groups count), nested at most 8 levels deep. - A filter has at most 1000 values in all, and `in` and `nin` at most 500 each. - A string value (a `contains` text, an `eq` value, a list item) has at most 200 characters, like the search. - A sort has at most 10 keys, and a view at most 5 group levels. - One request returns at most `maxRows` rows or groups (default 1000). - The aggregation stops after `options.maxTimeMS`, 10000 (10 seconds) by default. Set a larger value in `options` for a slow collection, or add indexes for the filters and sorts the table uses. A request over a limit throws before the query runs. #### Properties - `fields: object`: __Required__ - The allowlist, keyed by the Table column `key` (the view's keys), not the column `field` that `MongoDBTableChanges` fields use; `path` gives the field path. Each field is an object: - `type: enum`: __Required__ - The column type: `text`, `email`, `phone`, `url`, `link`, `html`, `relation`, `tag`, `status`, `number`, `currency`, `percent`, `progress`, `rating`, `date`, `datetime`, `boolean`, `tags`, `people`, `avatar`, `image` or `json`. It decides the operators and aggregates the field allows. - `path: string`: Default: the field key - Dot path of the value in the documents the base pipeline returns. - `search: boolean`: Default: `false` - Include the field in the view search. Each search word must match at least one search field. - `sortable: boolean`: Default: `true` - Allow sorting by the field. - `filterable: boolean`: Default: `true` - Allow filtering by the field. - `groupable: boolean`: Default: `false` - Allow grouping by the field, and `countDistinct` on it. `tags` and `people` fields can not be grouped. - `title: string`: Default: the field key - The field's name in the response `sentence`, as the Table column's title. - `options: object[]`: The field's options as `[{ value, label }]`. The response `sentence` reads option values as their labels. - `pipeline: object[]`: Default: `[]` - Base aggregation stages that always run first, for example tenant scoping and a `$project`. `$out` and `$merge` are not allowed. - `view: object`: The Table view, `{ _payload: view }`. Only `sort`, `filter`, `search`, `group` and `aggregates` are read. - `startRow: integer`: Default: `0` - Index of the first row to return, `{ _payload: startRow }`. - `endRow: integer`: Default: `startRow + maxRows` - Index after the last row to return, `{ _payload: endRow }`. - `groupPath: any[]`: Default: `[]` - The group values of the expanded group, one per group level, `{ _payload: groupPath }`. - `maxRows: integer`: Default: `1000` - The most rows or groups one request may return. A request for more throws. - `user: object`: The user that `{ $user: path }` filter values resolve from. Set it to `{ _user: true }`. - `timezone: string`: Default: `UTC` - The IANA time zone, for example `Europe/London`, whose days date filters compare. See Filters. - `project: boolean`: Default: `true` - Return only `_id`, the `fields` paths and `returnFields` in each row. Set it to `false` to return the documents as the base pipeline leaves them. - `returnFields: string[]`: More dot paths each row returns, for values that cells read without a field of their own, for example an avatar `srcField` or a link `labelField`. - `options: object`: Optional aggregate settings, for example `collation`, `maxTimeMS` (default `10000`), `allowDiskUse` or `hint`. See `MongoDBAggregation`. #### Examples ###### A Table in server mode: The Table sends `{ startRow, endRow, view, groupPath }` as the fetch event. The request `payload` reads it with `_event`, and the properties read the payload with `_payload`. Field keys match the Table column keys. ```yaml id: deals type: PageHeaderMenu requests: - id: deals_page type: MongoDBTableQuery connectionId: deals payload: view: _event: view startRow: _event: startRow endRow: _event: endRow groupPath: _event: groupPath properties: pipeline: - $match: org_id: _user: organization.id # Rows return _id, the field paths and these paths only. returnFields: - owner.avatar fields: name: type: text search: true stage: type: tag groupable: true owner: type: text path: owner.name search: true groupable: true amount: type: currency created: type: date user: _user: true view: _payload: view startRow: _payload: startRow endRow: _payload: endRow groupPath: _payload: groupPath blocks: - id: deals_table type: Table properties: data: mode: server request: deals_page columns: - key: name - key: stage type: tag groupable: true - key: owner field: owner.name type: avatar groupable: true cell: srcField: owner.avatar - key: amount type: currency aggregate: sum - key: created type: date ``` ###### Only the current user's deals, with a filter the table can not remove: ```yaml requests: - id: my_deals_page type: MongoDBTableQuery connectionId: deals payload: view: _event: view startRow: _event: startRow endRow: _event: endRow properties: pipeline: - $match: owner_id: _user: id fields: name: type: text search: true amount: type: currency closed: type: boolean view: _payload: view startRow: _payload: startRow endRow: _payload: endRow maxRows: 500 ``` ### MongoDBUpdateMany The `MongoDBUpdateMany` request updates multiple documents that match a certain criteria in the collection specified in the connectionId. It requires a filter, which is written in the query syntax, to select the documents to update. #### Properties - `filter: object`: __Required__ - The filter used to select the document to update. - `update: object | object[]`: __Required__ - The update operations to be applied to the document. - `options: object`: Optional settings. See the [driver documentation](https://mongodb.github.io/node-mongodb-native/3.3/api/Collection.html#updateOne) for more information. Supported settings are: - `arrayFilters: string[]`: Array filters for the [`$[]`](https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/) array update operator. - `bypassDocumentValidation: boolean`: Default: `false` - Allow driver to bypass schema validation in MongoDB 3.2 or higher. - `checkKeys: boolean`: Default: `false` - If true, will throw if bson documents start with $ or include a . in any key value. - `collation: object`: Specify collation (MongoDB 3.4 or higher) settings for update operation. - `forceServerObjectId: boolean`: Force server to assign _id values instead of driver. - `hint: object`: An optional hint for query optimization. - `ignoreUndefined: boolean`: Default: `false` - Specify if the BSON serializer should ignore undefined fields. - `j: boolean`: Specify a journal write concern. - `upsert: boolean`: Default: `false` - Insert document if no match is found. - `w: number | string`: The write concern. - `wtimeout: number`: The write concern timeout. #### Examples ###### Set a list of documents as resolved: ```yaml requests: - id: set_resolved type: MongoDBUpdateMany connectionId: my_mongodb_collection_id payload: selected_issues_list: _state: selected_issues_list properties: # Select all documents where the _id is in selected_issues_list in state filter: _id: $in: _payload: selected_issues_list update: $set: resolved: true ``` ###### Mark all documents with score less than 6 as urgent: ```yaml requests: - id: set_resolved type: MongoDBUpdateMany properties: filter: score: $lt: 6 update: $set: status: urgent ``` ### MongoDBUpdateOne The `MongoDBUpdateOne` request updates a single document in the collection specified in the connectionId. It requires a filter, which is written in the query syntax, to select a document to update. It will update the first document that matches the filter. If the `upsert` option is set to true, it will insert a new document if no document is found to update. If no document matches the filter, the request throws `No matching record to update.` — a silent no-op update usually indicates a bug. Set `disableNoMatchError: true` on the request to restore the pre-5.5.0 behavior of returning `matchedCount: 0`. Requests with the `upsert` option never throw this error. Running `lowdefy upgrade` adds `disableNoMatchError: true` to existing requests so upgraded apps keep their behavior. > When the connection has a log collection, a findOneAndUpdate operation is performed instead of the standard updateOne operation so before and after document snapshots can be captured in the change log. The response shape is the same in both cases. #### Properties - `filter: object`: __Required__ - The filter used to select the document to update. - `update: object | object[]`: __Required__ - The update operations to be applied to the document. - `disableNoMatchError: boolean`: Default: `false` - Do not throw an error when no document matches the filter. - `options: object`: Optional settings. See the [driver documentation](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#updateone) for more information. Supported settings are: - `arrayFilters: object[]`: _Array_ - Array filters for the [`$[]`](https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/) array update operator. - `authdb: string`: Specifies the authentication information to be used. - `bsonRegExp: boolean`: Return the BSON regular expressions as BSONRegExp instances. - `bypassDocumentValidation: boolean`: Default: `false` - Allow driver to bypass schema validation in MongoDB 3.2 or higher. - `checkKeys: boolean`: Default: `false` - If true, will throw if bson documents start with $ or include a . in any key value. - `collation: object`: Specify collation (MongoDB 3.4 or higher) settings for update operation. - `comment: string | object`: A user-provided comment to attach to this command. - `dbName: string`: The database name. - `explain: object`: Specifies the verbosity mode for the explain output. - `fullResponse: boolean`: Return the full server response for the command. - `hint: string | object`: An optional hint for query optimization. - `ignoreUndefined: boolean`: Default: `false` - Specify if the BSON serializer should ignore undefined fields. - `let: object`: Map of parameter names and values that can be accessed using `$$var` (requires MongoDB 5.0). - `maxTimeMS: number`: Number of milliseconds to wait before aborting the command. - `noResponse: boolean`: Admin command option. - `readConcern: object`: Specify a read concern and level for the collection. (only MongoDB 3.2 or higher supported). - `readPreference: object`: The preferred read preference. - `retryWrites: boolean`: Should retry failed writes. - `serializeFunctions: boolean`: Default: `false` - Serialize the javascript functions. - `upsert: boolean`: Default: `false` - Insert document if no match is found. - `willRetryWrites: boolean`: Option whether to retry writes. - `writeConcern: object`: An object that expresses the write concern. #### Examples ###### Update a document: ```yaml requests: - id: update type: MongoDBUpdateOne connectionId: my_mongodb_collection_id payload: _id: _state: _id properties: filter: _id: _payload: _id update: $set: _state: true ``` Like a comment: ```yaml requests: - id: like_comment type: MongoDBUpdateOne connectionId: my_mongodb_collection_id payload: comment_id: _state: comment._id properties: filter: _id: _payload: comment_id update: $inc: likes: 1 $push: liked_by: _user.id: $set: last_liked: _date: now ``` ### MongoDBVersionedUpdateOne The `MongoDBVersionedUpdateOne` request updates a single document while preserving the previous version. The matched document is re-inserted under a new `_id`, and the update is applied to the new copy — so the collection keeps a full version history of the document. If no document matches the filter, the request throws `No matching record to update.`. Set `disableNoMatchError: true` on the request to suppress the error. Requests with the `upsert` update option never throw this error. > The version insert and the update are separate operations, not a transaction. If the update fails, the inserted version copy remains in the collection. > When the connection has a log collection, a findOneAndUpdate operation is performed instead of the standard updateOne operation so before and after document snapshots can be captured in the change log. The response shape is the same in both cases. #### Properties - `filter: object`: __Required__ - The filter used to select the document to version and update. - `update: object | object[]`: __Required__ - The update operations to be applied to the new copy of the document. - `disableNoMatchError: boolean`: Default: `false` - Do not throw an error when no document matches the filter. - `options: object`: Optional settings for each underlying operation: - `find: object`: Options for the find one operation. See the [driver documentation](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#findone). - `insert: object`: Options for the insert one operation that creates the version copy. See the [driver documentation](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#insertone). - `update: object`: Options for the update operation. See the [driver documentation](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#updateone). #### Examples ###### Update a document, keeping the previous version: ```yaml requests: - id: versioned_update type: MongoDBVersionedUpdateOne connectionId: my_mongodb_collection_id payload: doc_id: _state: doc_id properties: filter: doc_id: _payload: doc_id current: true update: $set: status: _state: status ``` ## Websockets Websocket types: - MongoDBChangeStream ### MongoDBChangeStream The `MongoDBChangeStream` [websocket](/websockets-introduction) opens a MongoDB [change stream](https://www.mongodb.com/docs/manual/changeStreams/) on the collection specified in the connection, and pushes every change event to subscribed pages in real time. The source starts when the first page subscribes and stops when the last one leaves — any number of subscribers share one change stream. Properties are evaluated per subscription, so `_user` and `_payload` operators in the `pipeline` produce user-specific streams. A change stream reads the collection, so a connection with `read: false` refuses subscriptions, as it refuses read requests. > Change streams require MongoDB to run as a replica set — they are not available on standalone deployments. MongoDB Atlas clusters support them out of the box. #### Properties - `pipeline: object[]`: An array of [aggregation pipeline stages](https://www.mongodb.com/docs/manual/changeStreams/#modify-change-stream-output) applied to the change stream, e.g. `$match` on `operationType` or `fullDocument` fields. - `fullDocument: string`: The change stream [fullDocument](https://www.mongodb.com/docs/manual/reference/method/db.collection.watch/) option. Defaults to `updateLookup`, which includes the current document on update events. #### Examples ###### Refetch a table when the user's orders change: ```yaml websockets: - id: order_updates type: MongoDBChangeStream connectionId: my_mongodb_collection_id properties: pipeline: - $match: operationType: $in: - insert - update fullDocument.owner_id: _user: id pages: - id: orders type: PageHeaderMenu requests: - id: get_orders type: MongoDBFind connectionId: my_mongodb_collection_id properties: query: owner_id: _user: id subscriptions: - websocketId: order_updates events: onMessage: - id: refetch type: Request params: get_orders blocks: - id: orders_table type: AgGridAlpine properties: rowData: _request: get_orders ``` The change event payload is available on `_event: messages` in `onMessage` and via the [`_websocket`](/websocket-subscriptions) operator — each message is the MongoDB change event, including `operationType`, `documentKey` and `fullDocument`. ## Errors When MongoDB rejects an operation, the request fails with a `RequestError` that carries: - `code` — the MongoDB error code, for example `11000` for a duplicate key. - `message` — a plain sentence naming the collection and the request type, like `Duplicate key on collection "orders".` - `hint` — a sentence saying what to do about it, for example to insert with `MongoDBUpdateOne` and `upsert: true`, or to grant the database user a role. A time limit (codes `50` and `89`) or the aggregation memory limit (code `292`) is a `ServiceError` instead, with the same `code` and a `hint` such as adding an index covering the filter. Network, DNS and TLS failures are also `ServiceError`s, classified by the connection layer rather than by an error code. A `ServiceError` is an outage that can pass with time; a `RequestError` repeats until the request or the database changes. The driver's own message is never sent to the browser — it can quote values from the document that triggered the error. The dev server terminal prints it under the located error line as `Caused by: MongoServerError: E11000 …`. --- url: https://docs.lowdefy.com/md/connections/mssql.md --- # Microsoft SQL Server The [Knex](/Knex) connection can be used to connect to [Microsoft SQL Server](https://www.microsoft.com/en-us/sql-server/sql-server-2019). ## Connections Connection types: - Knex ### Knex #### Properties - `client: enum`: __Required__ - Should be `mssql` to connect to Microsoft SQL Server. - `connection: object | string `: __Required__ - Connection object or string to pass to the [`tedious`](https://www.npmjs.com/package/tedious) database client (knex's `mssql` dialect uses tedious internally). - `useNullAsDefault: boolean`: If true, undefined keys are replaced with NULL instead of DEFAULT. #### Examples ###### Connection object: ```yaml connections: - id: mssql type: Knex properties: client: mssql connection: host: _secret: MSSQL_HOST database: _secret: MSSQL_DB user: _secret: MSSQL_USER password: _secret: MSSQL_PASSWORD ``` Environment variables: ``` LOWDEFY_SECRET_MSSQL_HOST = database.server.com LOWDEFY_SECRET_MSSQL_DB = db LOWDEFY_SECRET_MSSQL_USER = user LOWDEFY_SECRET_MSSQL_PASSWORD = password ``` ###### Secret connection string: ```yaml connections: - id: mssql type: Knex properties: client: mssql connection: _secret: MSSQL_CONNECTION_STRING ``` Environment variables: ``` LOWDEFY_SECRET_MSSQL_CONNECTION_STRING = mssql://user:password@database.server.com:1433/db ``` ## Requests Request types: - KnexBuilder - KnexRaw ### KnexBuilder #### Properties - `query: object[]`: **Required** - SQL query builder array. An array of objects, with a single key which is the name of the knex builder function. The value should be an array of arguments to pass to the builder function. - `tableName: string | object`: The name of the table to query from. #### Examples ###### Build a query: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex payload: name: _state: name properties: query: - select: - '*' - from: - users - where: - name - _payload: name ``` ###### Using `tableName`: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex payload: name: _state: name properties: tableName: users query: - select: - '*' - where: - name - _payload: name ``` ###### Aliases: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex properties: tableName: a: tableA b: tableB query: - select: - aField: 'a.field' - bField: 'b.field' - limit: - 1 ``` ### KnexRaw #### Properties - `query: string`: **Required** - SQL query string. - `parameters: string | number | array | object`: SQL query parameters. #### Examples ###### Simple raw query: ```yaml id: knexRaw type: KnexRaw connectionId: knex properties: query: SELECT * FROM "my_table"; ``` ###### Query with named parameters: ```yaml id: knexRaw type: KnexRaw connectionId: knex payload: selected_name: _state: selected_name properties: query: select * from users where name = :name parameters: name: _payload: selected_name ``` ###### Query with positional parameters: ```yaml id: knexRaw type: KnexRaw connectionId: knex payload: selected_name: _state: selected_name properties: query: select * from users where name = ? parameters: - _payload: selected_name ``` ###### Reference a `.sql` file: ```yaml id: knexRaw type: KnexRaw connectionId: knex properties: query: _ref: my_query.sql ``` --- url: https://docs.lowdefy.com/md/connections/mysql.md --- # MySQL The [Knex](/Knex) connection can be used to connect to a [MySQL](https://www.mysql.com) database. ## Connections Connection types: - Knex ### Knex #### Properties - `client: enum`: __Required__ - Should be `mysql2` to connect to MySQL. MySQL uses the [`mysql2`](https://www.npmjs.com/package/mysql2) driver. - `connection: object | string `: __Required__ - Connection object or string to pass to the database client. - `version: string`: Set database version. - `useNullAsDefault: boolean`: If true, undefined keys are replaced with NULL instead of DEFAULT. #### Examples ###### Connection Object: ```yaml connections: - id: mysql type: Knex properties: client: mysql2 connection: host: _secret: MYSQL_HOST user: _secret: MYSQL_USER database: _secret: MYSQL_DB password: _secret: MYSQL_PASSWORD ``` Environment variables: ``` LOWDEFY_SECRET_MYSQL_HOST = database.server.com LOWDEFY_SECRET_MYSQL_DB = db LOWDEFY_SECRET_MYSQL_USER = user LOWDEFY_SECRET_MYSQL_PASSWORD = password ``` ###### Connection string: ```yaml connections: - id: mysql type: Knex properties: client: mysql2 connection: _secret: MYSQL_CONNECTION_STRING ``` Environment variables: ``` LOWDEFY_SECRET_MYSQL_CONNECTION_STRING = mysql://user:password@database.server.com/db ``` ## Requests Request types: - KnexBuilder - KnexRaw ### KnexBuilder #### Properties - `query: object[]`: **Required** - SQL query builder array. An array of objects, with a single key which is the name of the knex builder function. The value should be an array of arguments to pass to the builder function. - `tableName: string | object`: The name of the table to query from. #### Examples ###### Build a query: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex payload: name: _state: name properties: query: - select: - '*' - from: - users - where: - name - _payload: name ``` ###### Using `tableName`: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex payload: name: _state: name properties: tableName: users query: - select: - '*' - where: - name - _payload: name ``` ###### Aliases: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex properties: tableName: a: tableA b: tableB query: - select: - aField: 'a.field' - bField: 'b.field' - limit: - 1 ``` ### KnexRaw #### Properties - `query: string`: **Required** - SQL query string. - `parameters: string | number | array | object`: SQL query parameters. #### Examples ###### Simple raw query: ```yaml id: knexRaw type: KnexRaw connectionId: knex properties: query: SELECT * FROM "my_table"; ``` ###### Query with named parameters: ```yaml id: knexRaw type: KnexRaw connectionId: knex payload: selected_name: _state: selected_name properties: query: select * from users where name = :name parameters: name: _payload: selected_name ``` ###### Query with positional parameters: ```yaml id: knexRaw type: KnexRaw connectionId: knex payload: selected_name: _state: selected_name properties: query: select * from users where name = ? parameters: - _payload: selected_name ``` ###### Reference a `.sql` file: ```yaml id: knexRaw type: KnexRaw connectionId: knex properties: query: _ref: my_query.sql ``` --- url: https://docs.lowdefy.com/md/connections/postgresql.md --- # PostgreSQL The [Knex](/Knex) connection can be used to connect to a [PostgreSQL](https://www.postgresql.org) database. ## Connections Connection types: - Knex ### Knex #### Properties - `client: enum`: __Required__ - The database client to use. To connect to PostgreSQL, use one of: - `postgres` - `pg` (alias of `postgres`) - `postgresql` (alias of `postgres`) - `connection: object | string`: __Required__ - Connection string or object to pass to the [`pg`](https://www.npmjs.com/package/pg) database client. - `searchPath: string`: Set PostgreSQL search path. - `version: string`: Set database version. - `useNullAsDefault: boolean`: If true, undefined keys are replaced with NULL instead of DEFAULT. #### Examples ###### Connection Object: ```yaml connections: - id: postgres type: Knex properties: client: postgres connection: user: _secret: PG_USER host: _secret: PG_HOST database: _secret: PG_DB password: _secret: PG_PASSWORD ``` Environment variables: ``` LOWDEFY_SECRET_PG_HOST = database.server.com LOWDEFY_SECRET_PG_DB = db LOWDEFY_SECRET_PG_USER = user LOWDEFY_SECRET_PG_PASSWORD = password ``` ###### Secret connection string, with version and searchPath: ```yaml connections: - id: postgres type: Knex properties: client: postgres connection: _secret: PG_CONNECTION_STRING version: '7.2' searchPath: - knex - public ``` Environment variables: ``` LOWDEFY_SECRET_PG_CONNECTION_STRING = postgresql://user:password@database.server.com:5432/db ``` ## Requests Request types: - KnexBuilder - KnexRaw ### KnexBuilder #### Properties - `query: object[]`: **Required** - SQL query builder array. An array of objects, with a single key which is the name of the knex builder function. The value should be an array of arguments to pass to the builder function. - `tableName: string | object`: The name of the table to query from. #### Examples ###### Build a query: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex payload: name: _state: name properties: query: - select: - '*' - from: - users - where: - name - _payload: name ``` ###### Using `tableName`: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex payload: name: _state: name properties: tableName: users query: - select: - '*' - where: - name - _payload: name ``` ###### Aliases: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex properties: tableName: a: tableA b: tableB query: - select: - aField: 'a.field' - bField: 'b.field' - limit: - 1 ``` ### KnexRaw #### Properties - `query: string`: **Required** - SQL query string. - `parameters: string | number | array | object`: SQL query parameters. #### Examples ###### Simple raw query: ```yaml id: knexRaw type: KnexRaw connectionId: knex properties: query: SELECT * FROM "my_table"; ``` ###### Query with named parameters: ```yaml id: knexRaw type: KnexRaw connectionId: knex payload: selected_name: _state: selected_name properties: query: select * from users where name = :name parameters: name: _payload: selected_name ``` ###### Query with positional parameters: ```yaml id: knexRaw type: KnexRaw connectionId: knex payload: selected_name: _state: selected_name properties: query: select * from users where name = ? parameters: - _payload: selected_name ``` ###### Reference a `.sql` file: ```yaml id: knexRaw type: KnexRaw connectionId: knex properties: query: _ref: my_query.sql ``` --- url: https://docs.lowdefy.com/md/connections/redis.md --- # Redis [`Redis`](http://redis.io/) is an open-source, in-memory key-value data structure store. Redis offers a set of versatile in-memory data structures (strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes, and streams) that allow the creation of many custom applications. Key use cases for Redis include database, caching, session management and message broker. Lowdefy integrates with Redis using the one of the recommended [Node.js clients (node-redis)](https://github.com/redis/node-redis). ## Connections Connection types: - Redis ### Redis #### Properties - `connection: object | string `: __Required__ - Connection object or string to pass to the [`redis client`](https://github.com/redis/node-redis) redis client. The connection object accepts will be passed to the redis client verbatim, so check out the [configuration instructions](https://github.com/redis/node-redis/blob/master/docs/client-configuration.md). #### Examples ###### Redis with connection object: ```yaml connections: - id: redis type: Redis properties: connection: username: _secret: REDIS_USERNAME password: _secret: REDIS_PASSWORD database: _secret: REDIS_DATABASE socket: host: _secret: REDIS_HOST port: _secret: REDIS_PORT ``` Environment variables: ``` LOWDEFY_SECRET_REDIS_USERNAME = user LOWDEFY_SECRET_REDIS_PASSWORD = password LOWDEFY_SECRET_REDIS_DATABASE = 4 LOWDEFY_SECRET_REDIS_HOST = redis.server.com LOWDEFY_SECRET_REDIS_PORT = 5000 ``` ###### Redis with connection string: ```yaml connections: - id: redis type: Redis properties: connection: _secret: REDIS_CONNECTION_STRING ``` Environment variables: ``` LOWDEFY_SECRET_REDIS_CONNECTION_STRING = redis://user:password@redis:server.com:5000/4' ``` ## Requests Request types: - Redis ### Redis #### Properties - `command: string`: **Required** - Redis command to be executed, accepts all of the [out-of-the-box Redis commands](https://redis.io/commands). - `parameters: array`: An array of parameters to be passed to the redis command. - `modifiers: object`: The redis modififers to be passed to the redis command. #### Examples ###### Setting a key-value pair in redis: ```yaml id: redisRequest type: Redis connectionId: redis properties: command: set parameters: - key - value ``` ###### Getting a value from redis: ```yaml id: redisRequest type: Redis connectionId: redis properties: command: get parameters: - key ``` ###### Setting a key-value pair only if key does not exist: ```yaml id: redisRequest type: Redis connectionId: redis properties: command: set parameters: - key - value modififers: nx: true ``` --- url: https://docs.lowdefy.com/md/connections/sendgridmail.md --- # SendGrid Email [SendGrid](https://sendgrid.com/) is a popular email API provider which allows you to easily setup a email service. The `SendGridMail` connection can be used to connect to an existing SendGrid API account. The `SendGridMail` connector uses the [@sendgrid/mail](https://github.com/@sendgrid/mail) library. In order to use the `SendGridMail` connector, you first need to create a [SendGrid](https://sendgrid.com/) account and setup an API key. > Secrets like API keys should be stored using the [`_secret`](operators/secret.md) operator. In order to send from your custom domain your will need to [authenticate your domain](https://app.sendgrid.com/settings/sender_auth) through SendGrid. See the [SendGrid Docs](https://sendgrid.com/docs) for help on getting started with SendGrid. ## Connections Connection types: - SendGridMail ## Requests Request types: - SendGridMailSend ### Types - `email: string | object`: SendGrid accepts emails as either a `string` or an `object`. - `string`: Can be in the following format; `someone@example.org` or `Some One `. - `object`: With `name' and `email` properties, for example: `{"name": "Some One", "email": "someone@example.org"}`. ### SendGridMail #### Properties - `apiKey: string`: __Required__ - SendGrid API key. - `from: email`: __Required__ - Email address to send email from. - `replyTo: email`: Default email address replies should be sent to. - `mailSettings: object`: SendGrid mail settings. See [SendGrid API-Reference](https://sendgrid.com/docs/api-reference/) - `sandboxMode: object`: SendGrid mail sandbox mode settings. - `enable: boolean`: Sandbox mode enabled when `true`. - `templateId: string`: SendGrid email template ID to render email when sending. - `filter: object`: Environment-aware delivery filter, applied to every send. Filter values are usually supplied by [`_secret`](operators/secret.md); a field that resolves to `null` is ignored, so one config serves every environment (a dev catch-all, a sandbox allowlist, nothing in production). - `replaceAddress: string`: Redirect all mail to this single address. When set, it takes precedence — every message goes to this address only, with `cc` and `bcc` dropped. - `allowlist: string[]`: A list of domains. Only recipients whose domain is in the list are sent to. - `regex: string`: A regular expression. Only recipient addresses matching the pattern are sent to. When the connection sets no `filter`, or every field of it resolves to `null`, the current environment's `email.filter` from [`config.environments`](/lowdefy-schema) applies instead. A connection's own `filter` wins when it sets at least one field, and `filter: false` turns filtering off entirely, the environment's too. With `email.enabled: false` on the current environment, mail requests send nothing and report each message as `{ disabled: true }`. Addresses on the reserved `.invalid` domain, which never resolves (RFC 6761), are always dropped from `to`, `cc` and `bcc` before the filter runs, in every environment, with no filter and with `filter: false` too. Case, surrounding spaces and a trailing dot are ignored. SendGrid reads a string recipient as one address, so a comma-separated string is dropped only when every address in it is on `.invalid`. Only those addresses are dropped: when every `to` recipient is on `.invalid`, the remaining `cc` and `bcc` recipients each get their own copy, addressed to them, since SendGrid needs a `to` recipient. When no recipient is left, the message is not sent and reports `{ messageId: null, to: null, filtered: true }`. So test users with addresses such as `user@audit.invalid` never reach a `replaceAddress` catch-all. Use `.invalid` for test users that must never receive mail, and `example.com` or `.test` for journey users whose mail a journey reads. ### SendGridMailSend #### Properties ##### object A `mail description`: - `to: email | email[]`: __Required__ - Email address or addresses to send to. - `cc: email | email[]`: Email address to cc in communication. - `bcc: email | email[]`: Email address to bcc in communication. - `replyTo: email | email[]`: Email address to reply to. - `subject: string`: Email subject. - `text: string`: Email message in plain text format. - `html: string`: Email message in html format. - `dynamicTemplateData: object`: SendGrid template data to render into email template. - `sendAt: integer`: A unix timestamp allowing you to specify when you want your email to be delivered. You can't schedule more than 72 hours in advance. - `attachments: object[]`: List of email attachments to include with email. See [SendGrid API-Reference](https://d2w67tjf43xwdp.cloudfront.net/Classroom/Build/Add_Content/attachments.html]). - `content: string`: __Required__ - Base 64 encoded attachment content. - `filename: string`: __Required__ - Name of the attachment file. - `type: string`: The mime type of the content you are attaching. For example, `text/plain` or `text/html`. ##### array An array of `mail description` objects can also be provided. ### Examples ###### Send a reminder email ```yaml connections: - id: my_sendgrid type: SendGridMail properties: apiKey: _secret: SENDGRID_API_KEY from: reminders@example.org # ... requests: - id: send_reminder type: SendGridMailSend connectionId: my_sendgrid properties: to: Harry Potter subject: Reminder for Mr. Potter to water the 🌱 text: | Hi Harry Please remember to water the magic plants today :) Thank you # ... ``` --- url: https://docs.lowdefy.com/md/connections/smtp.md --- # SMTP Email The `SMTP` connection sends email over any SMTP server — Amazon SES, Postmark, Mailgun, Resend, SendGrid's SMTP relay, or a self-hosted server. One connection type covers every provider. The `SMTP` connector uses the [nodemailer](https://nodemailer.com/) library. Connection properties are passed through to nodemailer's transport, so any [transport option](https://nodemailer.com/smtp/) nodemailer accepts (pooling, TLS options, a `service` shortcut, a connection `url`) can be set alongside the documented properties. > Secrets like passwords and API keys should be stored using the [`_secret`](operators/secret.md) operator. ## Connections Connection types: - SMTP ## Requests Request types: - SMTPMailSend ### Types - `email: string | object`: An email address as either a `string` or an `object`. - `string`: In the format `someone@example.org` or `Some One `. - `object`: With `name` and `email` properties, for example `{ "name": "Some One", "email": "someone@example.org" }`. - `emails: email | email[]`: A single email address or a list of email addresses. ### SMTP #### Properties - `host: string`: Hostname or IP address of the SMTP server to connect to. - `port: integer`: Port to connect to. Defaults to `587` when `secure` is `false`, or `465` when `secure` is `true`. - `secure: boolean`: If `true`, the connection uses TLS when connecting to the server. - `auth: object`: Authentication credentials for the SMTP server. - `user: string`: Username to authenticate with. - `pass: string`: Password to authenticate with. - `from: emails`: __Required__ - Default email address to send email from. - `replyTo: emails`: Default email address replies should be sent to. - `filter: object`: Environment-aware delivery filter, applied to every send. See [Delivery filter](#delivery-filter) below. Additional nodemailer transport options (for example `service`, `url`, `pool`, `tls`) are passed through unchanged. ### SMTPMailSend Sends one email, or an array of emails. When an array is given, each message is sent in turn. #### Properties ##### object A `mail description`: - `to: emails`: __Required__ - Email address or addresses to send to. - `cc: emails`: Email address to cc in communication. - `bcc: emails`: Email address to bcc in communication. - `replyTo: emails`: Email address to reply to. Overrides the connection `replyTo`. - `subject: string`: Email subject. - `text: string`: Email message in plain text format. - `html: string`: Email message in html format. - `attachments: object[]`: List of email attachments to include with the email. - `filename: string`: Name of the attachment file. - `content: string`: String content of the attachment. - `path: string`: File path or URL to stream the attachment from. - `contentType: string`: The mime type of the content you are attaching. For example, `text/plain` or `text/html`. - `encoding: string`: Encoding used to decode string `content` into a buffer. For example, `base64` or `hex`. - `cid: string`: Content id to reference the attachment as an embedded image in html. ##### array An array of `mail description` objects can also be provided. ### Delivery filter The connection's `filter` is applied to every message before it is sent — from `SMTPMailSend` requests, notification pipelines, and any other send over the connection. It has one enforcement point, so no send can bypass it. Because filter values are usually supplied by [`_secret`](operators/secret.md), any field that resolves to `null` or `undefined` is ignored — an unset filter is disabled. This lets one connection config serve every environment: development sets a catch-all, a sandbox sets a domain allowlist or regex, production sets nothing. - `replaceAddress: string`: Redirect all mail to this single address. When set, it takes precedence — every message is delivered to this address only, with `cc` and `bcc` dropped. Use it to catch all outbound mail in development. - `allowlist: string[]`: A list of domains. Only recipients whose domain is in the list are sent to; others are dropped. When every `to` recipient is filtered out, the message is not sent. - `regex: string`: A regular expression. Only recipient addresses matching the pattern are sent to. Addresses on the reserved `.invalid` domain, which never resolves (RFC 6761), are always dropped from `to`, `cc` and `bcc` before the filter runs. This happens in every environment, with no filter and with `filter: false`, and for auth emails too. Case, surrounding spaces and a trailing dot are ignored, and each address in a comma-separated string is checked on its own. Only those addresses are dropped: when every `to` recipient is on `.invalid`, the remaining `cc` and `bcc` recipients still get the mail. When no recipient is left, the message is not sent and reports `{ messageId: null, to: null, filtered: true }`. So test users with addresses such as `user@audit.invalid` never reach a `replaceAddress` catch-all. Use `.invalid` for test users that must never receive mail, and `example.com` or `.test` for journey users whose mail a journey reads. When the connection sets no `filter`, or every field of it resolves to `null`, the current environment's `email.filter` from [`config.environments`](/lowdefy-schema) applies instead, so the per-environment rule lives in one place for every mail connection. A connection's own `filter` wins when it sets at least one field, and `filter: false` turns filtering off entirely — the environment's too — for mail that must reach its real recipient in every environment (an invite, say). Auth emails (magic links, email codes, verification, password reset, invitations) send through the `auth.email` connection with only that connection's own `filter`: the environment's `email.filter` and `email.enabled` do not apply to them, so sign-in keeps working in every environment. To catch auth emails outside production, set a `filter` on the auth connection itself. With `email.enabled: false` on the current environment, mail requests send nothing and report each message as `{ disabled: true }`. ```yaml config: environments: prod: url: https://app.example.com staging: url: https://staging.example.com email: filter: replaceAddress: team+staging@example.com ``` `allowlist` and `regex` combine — a recipient must satisfy both to receive mail. ### Examples ###### Send a reminder email ```yaml connections: - id: my_smtp type: SMTP properties: host: smtp.example.com port: 465 secure: true auth: user: my-smtp-user pass: _secret: SMTP_PASS from: Reminders # ... requests: - id: send_reminder type: SMTPMailSend connectionId: my_smtp properties: to: Harry Potter subject: Reminder for Mr. Potter to water the 🌱 text: | Hi Harry Please remember to water the magic plants today :) Thank you # ... ``` ###### Catch all mail in a development environment ```yaml connections: - id: my_smtp type: SMTP properties: host: smtp.example.com auth: user: my-smtp-user pass: _secret: SMTP_PASS from: App filter: # In dev, DEV_CATCH_ALL is set and all mail is redirected to it. # In production the secret is unset, so the filter is disabled. replaceAddress: _secret: DEV_CATCH_ALL ``` ###### Send over SendGrid's SMTP relay ```yaml connections: - id: my_smtp type: SMTP properties: host: smtp.sendgrid.net port: 465 secure: true auth: user: apikey pass: _secret: SENDGRID_API_KEY from: App ``` --- url: https://docs.lowdefy.com/md/connections/sqlite.md --- # SQLite The [Knex](/Knex) connection can be used to connect to a [SQLite](https://www.sqlite.org) database. ## Connections Connection types: - Knex ### Knex #### Properties - `client: enum`: __Required__ - Should be `better-sqlite3` or `sqlite` (alias of `better-sqlite3`) to connect to SQLite. SQLite uses the [`better-sqlite3`](https://github.com/WiseLibs/better-sqlite3) driver. - `connection: object`: - `filename: string`: __Required__ - The path to the SQLite file (relative to the project root). - `useNullAsDefault: boolean`: If true, undefined keys are replaced with NULL instead of DEFAULT. #### Examples ##### Specify filename: ```yaml connections: - id: sqlite type: Knex properties: client: better-sqlite3 connection: filename: ./mydb.sqlite ``` ###### Different connections in deployment and production environments: ```yaml connections: - id: knex type: Knex properties: client: _secret: KNEX_CLIENT connection: _json.parse: _secret: KNEX_CONNECTION ``` Environment variables in development: ``` LOWDEFY_SECRET_KNEX_CLIENT = better-sqlite3 LOWDEFY_SECRET_KNEX_CONNECTION = {"filename": "./mydb.sqlite"} ``` Environment variables in production: ``` LOWDEFY_SECRET_KNEX_CLIENT = postgres LOWDEFY_SECRET_KNEX_CONNECTION = {"user": "dbuser", "host": "database.server.com", "database": "mydb", "password": "secretpassword"} ``` ## Requests Request types: - KnexBuilder - KnexRaw ### KnexBuilder #### Properties - `query: object[]`: **Required** - SQL query builder array. An array of objects, with a single key which is the name of the knex builder function. The value should be an array of arguments to pass to the builder function. - `tableName: string | object`: The name of the table to query from. #### Examples ###### Build a query: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex payload: name: _state: name properties: query: - select: - '*' - from: - users - where: - name - _payload: name ``` ###### Using `tableName`: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex payload: name: _state: name properties: tableName: users query: - select: - '*' - where: - name - _payload: name ``` ###### Aliases: ```yaml id: knexBuilder type: KnexBuilder connectionId: knex properties: tableName: a: tableA b: tableB query: - select: - aField: 'a.field' - bField: 'b.field' - limit: - 1 ``` ### KnexRaw #### Properties - `query: string`: **Required** - SQL query string. - `parameters: string | number | array | object`: SQL query parameters. #### Examples ###### Simple raw query: ```yaml id: knexRaw type: KnexRaw connectionId: knex properties: query: SELECT * FROM "my_table"; ``` ###### Query with named parameters: ```yaml id: knexRaw type: KnexRaw connectionId: knex payload: selected_name: _state: selected_name properties: query: select * from users where name = :name parameters: name: _payload: selected_name ``` ###### Query with positional parameters: ```yaml id: knexRaw type: KnexRaw connectionId: knex payload: selected_name: _state: selected_name properties: query: select * from users where name = ? parameters: - _payload: selected_name ``` ###### Reference a `.sql` file: ```yaml id: knexRaw type: KnexRaw connectionId: knex properties: query: _ref: my_query.sql ``` --- url: https://docs.lowdefy.com/md/connections/stripe.md --- # Stripe ## Connections Connection types: - Stripe ### Stripe [Stripe](https://stripe.com/) is a popular payment provider which allows you to accept payments, send payouts, and manage your business online. The `Stripe` connector uses the official [Node.js client from Stripe](https://github.com/stripe/stripe-node). In order to use the `Stripe` connection, you first need to create a [Stripe](https://stripe.com/) account and setup an API key. > Secrets like API keys should be stored using the [`_secret`](operators/secret.md) operator. #### Properties - `secretKey: string`: __Required__ - Stripe [secret key](https://stripe.com/docs/keys). - `apiVersion: string`: Stripe API version to use. Defaults to the account-wide version. - `timeout: number`: Timeout for requests to the Stripe API. - `maxNetworkRetries: number`: Maximum number of times failed requests are repeated before throwing an error. - `telemetry: boolean`: Whether to send telemetry data to Stripe (this is forwarded to the Stripe client library. Lowdefy does not receive any telemetry data from your Stripe connection.) #### Examples ###### Simple connection: ```yaml connections: - id: stripe type: Stripe properties: secretKey: _secret: STRIPE_SECRET_KEY ``` Environment variables: ``` LOWDEFY_SECRET_STRIPE_SECRET_KEY = sk_test_KyvNyie... ``` ###### Using an older API version: ```yaml connections: - id: stripe type: Stripe properties: secretKey: _secret: STRIPE_SECRET_KEY apiVersion: 2017-12-14 ``` Environment variables: ``` LOWDEFY_SECRET_STRIPE_SECRET_KEY = sk_test_KyvNyie... ``` ## Requests Request types: - StripeRequest ### StripeRequest The `StripeRequest` request allows calls to all modules supported by the [Stripe API client](https://stripe.com/docs/api?lang=node) by nesting the resource and method calls: ```yaml resource: method: - parameter1 - parameter2 ``` #### Properties - `{{ apiResource }}: object`: A Stripe API resource, eg. `customers`. - `{{ method }}: array | null`: A resource method, eg. `create`. The arguments array will be passed on to the client method. The Stripe client exposes all resources as objects, with the API methods being available as function properties on those resource objects. In Lowdefy, you may access these properties by nesting them. ### Examples ###### List the 30 most recent customers ```yaml requests: - id: list_customers type: StripeRequest connectionId: stripe properties: customers: list: limit: 30 ``` ###### Create a payment intent ```yaml requests: - id: create_payment_intent type: StripeRequest connectionId: stripe properties: paymentIntents: create: - amount: 2000 currency: eur payment_method_types: [ card ] ``` ###### Retrieve a checkout session by ID ```yaml requests: - id: retrieve_checkout_session type: StripeRequest connectionId: stripe properties: checkout: sessions: retrieve: - cs_test_onpT2icY2lrSU0IgDGXEhhcOHcWeJS5BpLcQGMx0uI9TZHLMBdzvWpvx ``` --- url: https://docs.lowdefy.com/md/connections/treg.md --- # treg [treg](https://treg.to) is an open-source catalog of external API endpoints (SEO and SERP data, people and company enrichment, social data, scraping, generation and more) behind one token. treg holds the provider credentials and bills each call from the team's prepaid balance, or runs it on the team's own provider key for free. Its routed endpoints, such as `treg.people.email.find`, try provider after provider for one job and answer with the first hit. The `TregConnection` connection calls treg from server routines. It is a good provider backend for [enrichment tables](/Table): one connection gives every enrichment column access to the whole catalog, with an exact cost per call. > Store the token with the [`_secret`](/_secret) operator. `TregCall` spends money, so call it from API endpoints, and bound it with `maxCost` and `idempotencyKey`. ## Connections Connection types: - TregConnection ## Requests Request types: - TregCall - TregCatalogSearch - TregCatalogGet - TregBalance ### TregConnection #### Properties - `token: string`: __Required__ - The treg token, sent as `X-Treg-Token`. Use a per-team token (`treg org agent-new `, or a dashboard API key), which carries its team. - `org: string`: The team slug, sent as `X-Treg-Org`. Only needed with a personal identity token (from `treg login`). - `baseUrl: string`: Default: `https://treg.to` - The treg URL, for a self-hosted treg. An `https` URL, or `http` on `localhost`. - `timeout: integer`: Default: `30000` - The time in milliseconds each HTTP call to treg may take. - `maxCost: number`: The default spend ceiling per call in USD, sent as `X-Treg-Route-Max-Cost`. treg refuses a call whose reserve would exceed it and charges nothing. treg itself caps routed calls at $1 and direct calls not at all. - `meta: object`: Default tags for every call, sent as `X-Treg-Meta`, for treg's per-tag usage reports and budgets. Up to 5 pairs; keys are lowercase letters, digits and `_`; values are letters, digits and `. _ - :`. Request `meta` is merged over it. - `allowCustomTools: boolean`: Default: `false` - Allow `TregCall` to call the team's own registered tools with `tool` and `path`. Without it a request can only reach catalog and routed endpoints. ### TregCall Calls one treg endpoint by its id, `POST` (or `GET` without a body) to `/call/`. `endpoint` must be a catalog or routed endpoint id, such as `treg.people.email.find`: lowercase letters, digits, `.`, `_` and `-`. The raw upstream-URL form (`/call/https://…`) is never accepted, because it would let config send any URL through the team's stored credentials. The team's own registered tools are called with `tool` and `path` instead, and only when the connection sets `allowCustomTools: true`. #### Properties - `endpoint: string`: The catalog or routed endpoint id to call. Find ids with `TregCatalogSearch`. - `tool: string`: The name of one of the team's own tools, with `path`. Needs `allowCustomTools` on the connection. - `path: string`: The path on the tool's API, such as `/v1/charges`. A path only: printable ASCII (also once percent-decoded) with no scheme, host, query, backslash, `.` or `..` segment, whitespace or control characters. A path the URL parser would change (it strips tabs and newlines and resolves `..`) is refused before anything is sent. - `method: enum`: `GET`, `POST`, `PUT`, `PATCH` or `DELETE`. Defaults to `POST` when `body` is set, otherwise `GET`. - `query: object`: Query parameters. An array value sends the parameter once per item. - `body: any`: The JSON request body. A routed endpoint takes its inputs here. - `idempotencyKey: string`: Sent as `Idempotency-Key`. A repeat of a call with the same key gets treg's stored answer and is not charged again (`replayed: true`, cost 0). treg keeps keys for 24 hours, frees the key of a failed call, and refuses a key reused for a different request with a 422. Use a new key for genuinely new work. - `maxCost: number`: The spend ceiling for this call in USD, overriding the connection's `maxCost`. On a routed endpoint it bounds the whole waterfall. - `maxAge: integer`: Only accept a cached answer younger than this many seconds (`X-Treg-Max-Age`). - `noCache: boolean`: Force a live call instead of a cached answer (`Cache-Control: no-cache`). - `waterfall: boolean`: Routed endpoints: `false` stops at the first provider that misses. treg falls through to the next provider by default. - `strictFilters: boolean`: Routed endpoints: `true` refuses with a 422 (nothing charged) instead of answering from a provider that could not apply a filter. - `meta: object`: Tags for this call, merged over the connection's `meta`. - `await: object`: Wait for an async task. Without it, a task that is still running returns `pending: true`. - `timeoutMs: integer`: Default: `60000` - How long to poll, from 100 to 900000. - `intervalMs: integer`: The wait between polls. Defaults to the interval treg's descriptor names (at least 100), or 2000. #### Response ```yaml output: # routed: the job's normalised answer; catalog: the provider body (without _treg) email: ada@example.com verified: false raw: # routed: the serving provider's body; catalog: the provider body as it came data: { … } cost: # what treg charged, from X-Treg-Cost-Micro. 0 on the team's own key and on a replay. micro: 4000 usd: 0.004 callId: call_8f2… # X-Treg-Call-Id: store it with the cost; treg support looks calls up by it servedBy: hunter.people.email.find tried: [ … ] # routed: every provider tried, with its outcome and charge outcome: hit # routed: hit, weak, miss or pending; null for catalog endpoints cached: false # answered from treg's cache (X-Treg-Cache: hit) replayed: false # an idempotent replay of an earlier call pending: false # an async task that is still running (only without await) task: null # the async task: { id, status, pollEndpoint, reserved } httpStatus: 200 ``` The cost is always what treg reports it charged, never the catalog estimate. An async task is charged only when it succeeds: while it is pending `cost` is 0 and `task.reserved` is the hold, and after `await` it is the reserve treg settles. #### Async tasks Generation endpoints, and routed endpoints whose provider is still working after treg's own 60 second wait, answer with a task and an `X-Treg-Async` poll descriptor. With `await`, `TregCall` polls the descriptor's catalog endpoint (`GET /call/?=`, which is free) every `intervalMs` until the task's status is a success or failure value, and returns the terminal answer (the descriptor's result path, or the whole body). Transient poll failures are retried; five in a row end the wait. - A failed task throws a final error; treg refunds its hold. - A task still running at `timeoutMs` throws a retryable `ServiceError` naming the call id and task id. treg keeps a routed endpoint's pending answer, with its task, under the `Idempotency-Key`, so a retry with the same key replays it at no charge and resumes waiting for the same task instead of starting a new one. - A descriptor that polls a dynamic URL is not followed. ### TregCatalogSearch Searches the catalog by what an endpoint does, `GET /catalog/search`. Returns treg's answer: `{ query, count, total, results, hints }`, each result with its `id`, provider, `cost` and measured reliability (`observed`). #### Properties - `q: string`: __Required__ - The job, in words, such as `find a work email`. - `limit: integer`: The most results, 1 to 100. treg defaults to 25. ### TregCatalogGet One endpoint in full, `GET /catalog/endpoints/`: its parameters, cost, sibling providers for the same job with their price and reliability, and an example response. #### Properties - `endpoint: string`: __Required__ - The endpoint id. - `access: boolean`: Also read how this team would be served and at what price, as `access`: `{ tier, detail, estimated_cost_micro, estimated_cost_usd }`. Use it for the `cost` of a provider in an enrichment catalogue. ### TregBalance The team's prepaid balance. Returns `{ orgId, org, balance: { micro, usd }, held: { micro, usd }, holds, entries }`: `held` is the spend reserved by calls in flight, and `entries` the recent ledger (listed to team admins only). #### Properties - `limit: integer`: Default: `20` - The ledger entries to return, 1 to 200. ### Errors `TregCall` sorts treg's answers into errors a routine can branch on with [`_error`](/_error): a `ServiceError` passes with time and can be retried; anything else is final, and retrying it would fail or cost the same again. | Answer | Error | Retry | | --- | --- | --- | | 402 `insufficient_balance` | "treg balance too low: needs ~$X, has $Y." The top-up link is only in the server log. | No | | 402 `route_max_cost` | The call would exceed `maxCost`. Nothing was charged. | No | | 422 from treg | `ConfigError` with treg's explanation: malformed `meta`, an idempotency key reused for another request, a strict filter. | No | | 429 | `ServiceError`, with `retryAfter` from `Retry-After`. | Yes | | 503 `provider_capacity_unavailable` | `ServiceError`: treg's own provider account is out, nothing charged. `retryAfter` is the reset time, or 60 seconds. | Yes | | 503 `treg_saturated`, other 5xx | `ServiceError`, with `retryAfter` when treg sent one. | Yes | | 409 from treg | `ServiceError`: a call with the same idempotency key is still in flight. | Yes | | 502 `response_buffer_limit` | The answer is larger than treg's 8 MiB settlement buffer. Nothing was charged. | No | | Network failure or `timeout` | `ServiceError`. | Yes | | 401, 403, other 4xx | The status, and treg's own explanation when the answer is treg's. | No | `_error: retryAfter` reads the seconds treg asked to wait, `_error: statusCode` the HTTP status and `_error: code` treg's error name (such as `insufficient_balance`). Error messages never contain the token, a provider's response body or the body of an authentication failure. ### Examples ###### A treg connection ```yaml connections: - id: treg type: TregConnection properties: token: _secret: TREG_TOKEN maxCost: 0.1 meta: app: crm ``` Environment variables: ``` LOWDEFY_SECRET_TREG_TOKEN = treg_… ``` ###### Find a work email ```yaml id: find_email type: TregCall connectionId: treg properties: endpoint: treg.people.email.find body: full_name: _payload: full_name domain: _payload: domain maxCost: 0.05 ``` ###### An enrichment provider endpoint An enrichment column's provider endpoint (`enrich_`) that finds a work email through treg. The worker calls it with the claimed cell's inputs and an idempotency key, and completes the cell with its answer, including the cost treg charged. The idempotency key must stay the same across the attempts of one run of a cell, so that a cell retried after treg charged for an answer that was then lost gets the stored answer instead of paying again, and a timed-out async task is resumed. A claim token is new on every attempt, so build the key from the claim's `runId`, `columnKey`, `rowKey` and `inputHash` instead. When the provider endpoint throws a `ServiceError`, the worker completes the cell with `retryAfterMs`, so the retry waits as long as treg asked. In the worker: ```yaml - :try: - id: provider type: CallApi properties: endpointId: _string.concat: - enrich_ - _item: cell.provider payload: inputs: _item: cell.inputs idempotencyKey: _js: fn: | const { cell } = args; // An ObjectId row key reaches the routine as { _oid }. const rowKey = cell.rowKey?._oid ?? String(cell.rowKey); return [cell.runId, cell.columnKey, rowKey, cell.inputHash].join(':'); args: cell: _item: cell # MongoDBEnrichmentComplete with the claim's cell and the provider's answer, # { status, value, raw, cost }. :catch: - id: complete_error type: MongoDBEnrichmentComplete connectionId: leads properties: columnDefs: _state: columnDefs results: - rowKey: _item: cell.rowKey columnKey: _item: cell.columnKey claimToken: _item: cell.claimToken status: error error: _error: message retryAfterMs: _js: fn: | const { retryAfter } = args; return typeof retryAfter === 'number' ? retryAfter * 1000 : null; args: retryAfter: _error: retryAfter ``` The provider endpoint: ```yaml id: enrich_find_work_email_treg type: InternalApi routine: - :try: - id: find type: TregCall connectionId: treg properties: endpoint: treg.people.email.find body: full_name: _payload: inputs.full_name domain: _payload: inputs.domain idempotencyKey: _payload: idempotencyKey maxCost: 0.05 meta: feature: enrichment await: timeoutMs: 60000 - :return: status: _if: test: _eq: - _step: find.output.email - null then: empty else: ok value: _step: find.output.email raw: _step: find.raw cost: _step: find.cost.micro :catch: # A ServiceError (a rate limit, treg saturated or out of capacity, a timeout) # passes with time: throw it, and the worker queues the cell again after its # backoff. `_error: retryAfter` has the seconds treg asked to wait. - :if: _eq: - _error: name - ServiceError :then: - :throw: _error: true # Anything else, such as a balance that is too low, is final. - :return: status: error error: _error: message retry: false ``` ###### Find endpoints for a provider catalogue ```yaml id: search_treg type: TregCatalogSearch connectionId: treg properties: q: _payload: q limit: 10 ``` ###### Read an endpoint's price for this team ```yaml id: email_find_price type: TregCatalogGet connectionId: treg properties: endpoint: treg.people.email.find access: true ``` `_step: email_find_price.access.estimated_cost_usd` is the price per call. ###### Call one of the team's own tools ```yaml connections: - id: treg_tools type: TregConnection properties: token: _secret: TREG_TOKEN allowCustomTools: true ``` ```yaml id: stripe_balance type: TregCall connectionId: treg_tools properties: tool: stripe path: /v1/balance ``` --- url: https://docs.lowdefy.com/md/actions/callapi.md --- # CallAPI ``` (params: { endpointId: string, payload?: object, holdValue?: boolean }): void ``` The `CallAPI` action invokes a server-side API endpoint defined in your Lowdefy application's configuration. It sends an optional payload to the API and returns the response data, making it a way to execute server-side logic and orchestrate database operations from your Lowdefy pages. > **Note:** `CallAPI` can only target endpoints with `type: Api`. Endpoints with `type: InternalApi` are server-only and cannot be called from client pages. See [Lowdefy APIs](/lowdefy-api) for details. #### Parameters ###### object - `endpointId: string`: __Required__ - The id of the API endpoint to call. - `payload: object`: Used to pass data such as state values from the app client to the endpoint as required. - `holdValue: boolean`: Optional. When `true`, the previous response is retained on `apiResponses[endpointId][0].response` while the new call is loading. UI bound to `_api: .response` continues to display the previous response instead of going to `null`. The previous response is also retained if the new call errors out — inspect `_api: .error` to detect failures. Defaults to `false`. `holdValue` is currently only settable on the action call. #### Examples ###### Call an API endpoint: ```yaml - id: call_my_api type: CallAPI params: endpointId: my_api_endpoint ``` ###### Call an API endpoint with a payload: ```yaml - id: call_rating_api type: CallAPI params: endpointId: review_product payload: product_id: _url_query: product_id rating: _state: rating comment: _state: comment ``` ###### Call an API endpoint with client state as the payload: ```yaml - id: call_my_api type: CallAPI params: endpointId: my_api_endpoint payload: _state: true ``` ###### Refresh API data without flashing the previous response: ```yaml - id: refresh_dashboard type: CallAPI params: endpointId: dashboard_data holdValue: true ``` --- url: https://docs.lowdefy.com/md/actions/callmethod.md --- # CallMethod ``` (params: { blockId: string, method: string, args?: any[] }): void ``` The `CallMethod` action is used to call a method defined by another block. #### Parameters ###### object - `blockId: string`: __Required__ - The id of the block. - `method: string`: __Required__ - The name of the method that should be called. - `args: any[]`: The array of positional arguments with which the method should be called. #### Examples ###### Open a modal: ```yaml - id: toggle_modal type: CallMethod params: blockId: my_modal method: toggleOpen ``` ###### Display a message with args: ```yaml - id: display_message type: CallMethod params: blockId: my_message method: open args: - content: Hello duration: 4 ``` --- url: https://docs.lowdefy.com/md/actions/copytoclipboard.md --- # CopyToClipboard ``` (params: { copy: string }): void ``` The `CopyToClipboard` action is used to copy content to the clipboard. It implements the [`navigator.clipboard.writeText`](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText) web API. #### Parameters ###### object - `copy: string`: __Required__ - Text to be copied to the clipboard. #### Examples ###### Copy text button: ```yaml - id: copy_button type: Button properties: title: Copy Text events: onClick: - id: copy type: CopyToClipboard params: copy: Lorem ipsum dolor sit amet messages: success: Copied! ``` --- url: https://docs.lowdefy.com/md/actions/displaymessage.md --- # DisplayMessage ``` (params: { status?: enum, duration?: number, content?: string, }): void ``` The `DisplayMessage` action is used to display a message to a user. #### Parameters ###### object - `status: enum`: DisplayMessage status type. Defaults to `success`. One of: - `success` - `error` - `info` - `warning` - `loading`. - `duration: number`: Time in seconds before message disappears. The default is 5. - `content: string`: The content of the message. #### Examples ###### Display a success message: ```yaml - id: success_message type: DisplayMessage params: content: Success ``` ###### Display an info message that remains visible for 10 seconds: ```yaml - id: info_message type: DisplayMessage params: content: Something happened status: info duration: 10 ``` ###### Display an error message that never disappears: ```yaml - id: error_message type: DisplayMessage params: content: Something bad happened status: error duration: 0 ``` --- url: https://docs.lowdefy.com/md/actions/downloadcsv.md --- # DownloadCsv ``` (params: { filename: string, data: any[], fields: string[] }): void ``` The `DownloadCsv` action is used to generate and download a CSV file from a set of data. #### Parameters ###### object - `filename: string`: __Required__ - The name for the generated CSV file. - `data: any[]`: __Required__ - The set of data used to generate the CSV file, as an array of objects. If it is empty or missing, the file contains only the header row from `fields`. - `fields: string[]`: __Required__ - The field definitions the set of data will be mapped to in the generated CSV file. #### Examples ###### Generate a CSV using an array of data: ```yaml - id: generate_csv type: Button properties: size: large title: Generate a CSV color: '#1890ff' events: onClick: - id: generate_csv type: DownloadCsv params: filename: profiles.csv data: - Username: booker12 # an array of data, usually a request Identifier: 9012 FirstName: Rachel LastName: Booker - Username: grey07 Identifier: 2070 FirstName: Laura LastName: Grey - Username: johnson81 Identifier: 4081 FirstName: Craig LastName: Johnson - Username: jenkins46 Identifier: 9346 FirstName: Mary LastName: Jenkins - Username: smith79 Identifier: 5079 FirstName: Jamie LastName: Smith fields: - Username # an array of field definitions - Identifier - FirstName - LastName ``` ###### Generate a CSV using a request: ```yaml - id: generate_csv type: Button properties: size: large title: Generate a CSV color: '#1890ff' events: onClick: - id: fetch_data type: Request params: - get_data - id: generate_csv type: DownloadCsv params: filename: profiles.csv data: _request: get_data # an array of data, from a request fields: - Username # an array of field definitions - Identifier - FirstName - LastName ``` --- url: https://docs.lowdefy.com/md/actions/getlocalstorage.md --- # GetLocalStorage ``` (params: { key: string, default?: any }): any ``` The `GetLocalStorage` action reads a value from the browser's [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). Values written by the [`SetLocalStorage`](/SetLocalStorage) action are deserialized, so objects, arrays and dates are returned as the same types that were stored. Local storage is per browser, so a value stored in one browser is not available in another. Keys are kept per app: Lowdefy stores the value under a key that names the app's `slug` and base path, so apps served from the same origin do not read or overwrite each other's values. A value stored under the bare key before keys were kept per app is moved to the app's key the first time `GetLocalStorage` reads it. Values are kept when a user signs out, so do not store secrets, tokens or personal data in local storage. A value that was not written by `SetLocalStorage` and is not valid JSON is returned as the plain string that is stored. If the browser blocks local storage, the action returns the `default` value, as if the key were not set. Keys starting with `lowdefy_` or `lf-` are reserved for settings Lowdefy stores itself, such as the dark mode preference, and the action throws an error if one is used. #### Parameters ###### object - `key: string`: __Required__ - The local storage key to read. Keys starting with `lowdefy_` or `lf-` are reserved. - `default: any`: The value returned when the key is not set in local storage, or when the browser blocks local storage. #### Response The value stored at the key, or the `default` value when the key is not set or local storage is blocked. When the key is not set and no `default` is given, the response is `undefined`. #### Examples ###### Restore a user preference on page load: ```yaml - id: read_preference type: GetLocalStorage params: key: dashboard_filters default: status: open - id: apply_preference type: SetState params: filters: _actions: read_preference.response ``` --- url: https://docs.lowdefy.com/md/actions/emailotpsend.md --- # EmailOtpSend ``` (params: { email: string captchaToken?: string }): void ``` The `EmailOtpSend` action emails a one-time sign-in code to an address. It requires `auth.emailOTP` to be enabled — see [Configuration](/auth-configuration). A code is typed into the tab the person started from, so the sign-in survives corporate mail security that pre-fetches links (which consumes a single-use magic link before the person clicks it), and it works when the email is read on another device. Use [`EmailOtpVerify`](/EmailOtpVerify) to complete the sign-in with the code. When `auth.magicLink` is enabled alongside `auth.emailOTP`, the magic-link email already carries the code: the [`Login`](/Login) action with `magicLink: true` sends one email that serves both paths, and `EmailOtpSend` is then only needed for a code-only page that offers no link at all. Under an invite-only signup policy an address that is not admitted gets no email, and the action still resolves successfully — the response is identical either way, so the "check your email" screen gives nothing away about who has an account. #### Parameters ###### object - `email: string`: __required__ - The email address to send the sign-in code to. - `captchaToken: string`: A captcha token minted by a `Captcha` block, sent as the `x-captcha-response` header when `auth.captcha` is enabled. Tokens are single-use, so reset the `Captcha` block in `onError` for retries. #### Examples ###### Send a sign-in code: ```yaml - id: send_code type: EmailOtpSend params: email: _state: email ``` ###### Send a code and tell the user to check their inbox: ```yaml - id: send_code type: Button properties: title: Send code events: onClick: - id: validate type: Validate params: regex: ^email$ - id: send type: EmailOtpSend params: email: _state: email - id: sent type: DisplayMessage params: content: Check your email for the sign-in code. ``` --- url: https://docs.lowdefy.com/md/actions/emailotpverify.md --- # EmailOtpVerify ``` (params: { email: string otp: string callbackUrl?: { home?: boolean pageId?: string url?: string urlQuery?: object pathParams?: object } | false }): void ``` The `EmailOtpVerify` action signs a user in with the one-time code emailed to them by [`EmailOtpSend`](/EmailOtpSend), or carried on the magic-link email when `auth.magicLink` and `auth.emailOTP` are both enabled. It requires `auth.emailOTP` to be enabled — see [Configuration](/auth-configuration). On success the session cookie is set — creating the account on first sign-in, unless `emailOTP.disableSignUp` is `true` — and the browser lands on the resolved `callbackUrl`. Three sources are consulted, in order: 1. The `callbackUrl` param, if given. 2. The `?callbackUrl=` query parameter, which Lowdefy sets when it redirects an unauthenticated user to the sign-in page. 3. The app's home page. To sign in without navigating, set `callbackUrl: false` — valid here, unlike on the magic-link and social paths, because this is a plain JSON sign-in with no redirect hop. If the user has two-factor authentication enrolled, the code mints a challenge rather than a session: the action navigates to `authPages.twoFactor` carrying the resolved destination as `?callbackUrl=`, where [`TwoFactorVerify`](/two-factor) finishes the sign-in, and the event chain stops. An expired, mistyped or already-used code is the auth server rejecting the attempt, not an app fault. The action throws a `UserError`: `catch` actions still run and the message still displays, but it is logged to the browser console only and is never reported to the server. #### Parameters ###### object - `email: string`: __required__ - The email address the code was sent to. - `otp: string`: __required__ - The one-time code from the email. - `callbackUrl: object | false`: Set to `false` to sign in without navigating. As an object: - `home: boolean`: Land on the app's home page. - `pageId: string`: The pageId to land on. - `url: string`: The URL to land on. An absolute URL is not `basePath`-prefixed, so it can be an external landing page. - `urlQuery: object`: The urlQuery to set on the destination. - `pathParams: object`: Values for the placeholders in the destination page's [`path`](/page-paths), by placeholder name. #### Examples ###### Sign in with the code and land on the home page (or the page the user was bounced from): ```yaml - id: verify_code type: EmailOtpVerify params: email: _state: email otp: _state: otp ``` ###### Sign in and land on a specific page: ```yaml - id: verify_code type: EmailOtpVerify params: email: _state: email otp: _state: otp callbackUrl: pageId: dashboard ``` ###### Sign in inside a modal, without navigating: ```yaml - id: verify_code type: EmailOtpVerify params: email: _state: email otp: _state: otp callbackUrl: false ``` --- url: https://docs.lowdefy.com/md/actions/fetch.md --- # Fetch ``` (params: { url: string, options: object, responseFunction: string }): string | object ``` The `Fetch` implements the [fetch web API](https://developer.mozilla.org/en-US/docs/Web/API/fetch) and can be used to make HTTP requests directly from the web client. #### Parameters ###### object - `url: string`: __Required__ - The URL of the resource you want to fetch.. - `options: object`: The options object of the `fetch` global function. These include, but are not limited to: - `method: string`: The request method, e.g., `"GET"`, `"POST"`. The default is `"GET"`. - `headers: object`: An object with headers to add to the request. - `body: string`: The request body. Use the [`_json.stringify`](/_json) operator to create a JSON body. - `responseFunction: enum`: If the `responseFunction` is specified, that function will be executed on the returned Response object. If specified this is equavalent to `await fetch(url, options).json()`. Should be one of of `'json'`, `'text'`, `'blob'`, `'arrayBuffer'`, or `'formData'`. #### Examples ###### Call a JSON API endpoint: ```yaml - id: fetch type: Fetch params: url: https://example.com/api/products options: method: GET responseFunction: json ``` ###### Make a post request: ```yaml - id: fetch type: Fetch params: url: https://example.com/api/products/abc options: method: POST headers: Content-Type: application/json body: _json.stringify: - _state: product responseFunction: json ``` --- url: https://docs.lowdefy.com/md/actions/geolocationcurrentposition.md --- # GeolocationCurrentPosition ``` (params: { enableHighAccuracy?: boolean, maximumAge?: number, timeout?: number, }) : object ``` The `GeolocationCurrentPosition` action is used to get the current position of the device. #### Parameters ###### object - `enableHighAccuracy: boolean`: Indicates that the most accurate possible position should be returned. Defaults to false. - `maximumAge: number`: The maximum age in milliseconds of a possible cached position that is acceptable to return. The default is 0. - `timeout: number`: The maximum length of time in milliseconds the device is allowed to take to return a position. The default is infinity. #### Response ##### Successful: ###### object - `coords: object`: A GeolocationCoordinates object instance. - `accuracy: float`: The accuracy of the latitude and longitude properties, expressed in meters. - `altitude: float`: The position's altitude in meters, relative to sea level. - `altitudeAccuracy: float`: The accuracy of the altitude expressed in meters. - `heading: float`: The direction, in degrees clockwise from true North, towards which the device is facing. - `latitude: float`: The position's latitude in decimal degrees. - `longitude: float`: The position's longitude in decimal degrees. - `speed: float`: The velocity of the device in meters per second. - `timestamp: number`: A Unix time in milliseconds. ##### Error: Note that this error response does not break the action chain. The possible errors can be viewed [here](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPositionError). ###### object - `code: number`: The error code associated with the error. - `error: string`: The constant associated with the error code. - `message: string`: A string describing the details of the error. #### Examples ###### Get geolocation with specified params: ```yaml - id: get_geolocation type: GeolocationCurrentPosition params: enableHighAccuracy: true timeout: 5000 maximumAge: 0 - id: set_data type: SetState params: geolocation: _actions: get_geolocation.response ``` --- url: https://docs.lowdefy.com/md/actions/link.md --- # Link ``` (pageId: string): void (params: { back?: boolean, home?: boolean, href?: string, input?: object, newTab?:boolean, pageId?: string, pathParams?: object, replace?: boolean, scroll?: boolean, url?: string, urlQuery?: object }): void ``` The `Link` action is used to link a user to another page. An input can be passed to the next page using either the `urlQuery`, which is visible to the user, but persists if the browser is refreshed, or by using the `input` object, which is not visible to the user. To link to a page with a [`path`](/page-paths), give the values of its placeholders in `pathParams`. A page with `path: tickets/{space}/{ticket_id}`, linked to with `pageId: ticket` and `pathParams: { space: support, ticket_id: '1234' }`, opens at `/tickets/support/1234`. Every placeholder needs a value: a missing, `null` or empty value, or a value of `.` or `..`, fails the link with an error naming the page and the placeholder. For a `Link` action with a literal `pageId`, the build checks that `pathParams` names every placeholder. Links set as block properties, such as the [`Anchor`](/Anchor) block's `pageId` and table or grid cell links, can only be built for pages the user may open. On a public page, such a link to a protected page with a `path` fails while the user is logged out. Use a `Link` action, or an HTML link with `data-page-id` (see [HTML attributes](/html-attributes#links)), there instead: the build collects these links, so a logged-out user is sent to sign in and then on to the page. #### Parameters ###### string The pageId of a page in the app to link to. ###### object - `back: boolean`: Go to the previous page if true (has the same effect as using the browser back button). - `home: boolean`: Link to the home page. This is either the configured public or authenticated homepage, or the first page in the default menu visible to the user. - `href: string`: Link to an address, used exactly as written. Unlike `url`, no protocol is added and `urlQuery` is not appended, so this is the parameter for a target `url` cannot express — a root-relative path, a fragment, or a scheme-less same-origin address. - `input: object`: Object to set as the input for the linked page. - `newTab: boolean`: Open the link in a new tab. - `pageId: string`: The pageId of a page in the app to link to. - `pathParams: object`: Values for the placeholders in the linked page's [`path`](/page-paths), by placeholder name. Values are converted to strings. - `replace: boolean`: Replace the current browser history entry instead of pushing a new one, so the browser back button skips this navigation. Defaults to `false`. - `scroll: boolean`: Scroll to the top of the page after navigating. Set to `false` to keep the current scroll position, for example when a same-page link only updates the `urlQuery`. Defaults to `true`. - `url: string`: Link to an external url. - `urlQuery: object`: Object to set as the urlQuery for the linked page. #### Examples ###### Shorthand, only specify pageId as string: ```yaml - id: shorthand type: Link params: my_page_id ``` ###### Specify pageId: ```yaml - id: link_page_id type: Link params: pageId: myPageId ``` ###### Link to home page: ```yaml - id: link_home type: Link params: home: true ``` ###### Link to an address exactly as written: ```yaml - id: link_href type: Link params: href: /downloads/report.pdf ``` ###### Link to an external url: ```yaml - id: link_url type: Link params: url: www.lowdefy.com ``` ###### Open a link in a new tab: ```yaml - id: link_new_tab type: Link params: pageId: my_page_id newTab: true ``` ###### Set the urlQuery of the page that is linked to: ```yaml - id: link_url_query type: Link params: pageId: my_page_id urlQuery: id: _state: id ``` ###### Link to a page with a path: For a page with `path: tickets/{space}/{ticket_id}`: ```yaml - id: link_path_params type: Link params: pageId: ticket pathParams: space: support ticket_id: _state: ticket_id urlQuery: tab: comments ``` ###### Set the input of the page that is linked to: ```yaml - id: link_input type: Link params: pageId: my_page_id input: id: _args: row.id ``` ###### Reflect state into the urlQuery without moving the page: ```yaml - id: sync_url type: Link params: pageId: my_page_id urlQuery: selected: _state: selected_id replace: true scroll: false ``` ###### Go to the previous page: ```yaml - id: link_back type: Link params: back: true ``` --- url: https://docs.lowdefy.com/md/actions/login.md --- # Login ``` (params: { authUrl?: { urlQuery?: object, } callbackUrl?: { home?: boolean pageId?: string url?: string urlQuery?: object pathParams?: object } | false providerId?: string, }): void ``` The `Login` action is used to start the user login flow. If only one provider is configured, or the `Login` action is called with a `providerId`, the `Login` action requests the Provider's authorization URL from the Lowdefy server, and redirects the user to this URL. Otherwise, the action redirects the user to a page where the user can choose which provider to use to sign in. The authorization url usually hosts a page where the user can input their credentials. After the user has logged in successfully, the user is redirected to the `api/auth/callback/[provider_id]` route in the Lowdefy app, where the rest of the authorization code flow is completed. The `callbackUrl` parameter of the Login action specifies where the user lands after login is complete. Three sources are consulted, in order: 1. The `callbackUrl` param, if given. 2. The `?callbackUrl=` query parameter, which Lowdefy sets when it redirects an unauthenticated user to the sign-in page - so a user who was bounced there returns to the page they asked for. 3. The app's home page. If none of the three resolves - an app with no `homePageId` whose menu offers the signed-out user no page to fall back on - the action throws rather than staying silently on the sign-in page. To sign in without navigating at all, set `callbackUrl: false`. This is for a login form in a modal or an embedded panel: the page renders the new user in place, as the server resolves them, before the next action in the chain runs. It is not valid for magic-link or social/OAuth sign-in, which redirect through a hop Lowdefy does not control. A sign-in the auth server rejects - a wrong password, an expired or already-used link, an invalid code - is an expected outcome, not an app error. The action throws a `UserError`: `catch` actions on the event still run and the error message still displays, but it is logged to the browser console only and is never reported to the server. The auth server's response is available to `catch` actions as `_actions..error.metaData`, with a `code` (for example `INVALID_EMAIL_OR_PASSWORD`) and the HTTP `status`. A network failure or a server fault is still thrown as an action error and reported. #### Parameters ###### object - `authUrl: object`: - `urlQuery: object`: Query parameters to set for the authorization URL. - `callbackUrl: object | false`: Set to `false` to sign in without navigating. As an object: - `home: boolean`: Redirect to the home page after the login flow is complete. - `pageId: string`: The pageId of the page to redirect to after the login flow is complete. - `url: string`: The URL to redirect to after the login flow is complete. An absolute URL is not `basePath`-prefixed, so it can be an external landing page. - `urlQuery: object`: The urlQuery to set for the page the user is redirected to after login. - `pathParams: object`: Values for the placeholders in the destination page's [`path`](/page-paths), by placeholder name. - `providerId: string`: The ID of the provider that should be used for login. If not set and only one provider is configured the configured provider will be used. Else the user will be redirected to a sign in page where they can choose a provider. #### Examples ###### Login and land on the home page (or the page the user was bounced from): ```yaml - id: login type: Login ``` ###### Login in a modal, without navigating: ```yaml - id: login type: Login params: callbackUrl: false ``` ###### Login with the google provider: ```yaml - id: login_with_google type: Login params: providerId: google ``` ###### Login, with pageId and urlQuery: ```yaml - id: login type: Login params: callbackUrl: pageId: page1 urlQuery: url1: value ``` ###### Only login if user is not logged in: ```yaml - id: login type: Login skip: _ne: - _user: sub - null ``` ###### Request the signup page from the provider: ```yaml - id: Signup type: Button events: onClick: - id: login type: Login params: authUrl: urlQuery: screen_hint: signup ``` --- url: https://docs.lowdefy.com/md/actions/logout.md --- # Logout ``` (params: { callbackUrl?: { home?: boolean pageId?: string url?: string urlQuery?: object pathParams?: object } redirect?: boolean, }): void ``` When the `Logout` action is called, the user data and authorization cookies are cleared by the app. The `callbackUrl` parameter of the Logout action specifies where the user lands after logout is complete. Unlike the `Login` action it has no default and does not read the `?callbackUrl=` query: with no `callbackUrl` the page is reloaded and the server re-applies the page's auth rules, which sends a signed-out user off a protected page to the sign-in page. Defaulting to the home page instead could land them on a page they may no longer see. #### Parameters ###### object - `callbackUrl: object`: - `home: boolean`: Redirect to the home page after the logout flow is complete. - `pageId: string`: The pageId of the page to redirect to after the logout flow is complete. - `url: string`: The URL to redirect to after the logout flow is complete. An absolute URL is not `basePath`-prefixed, so it can be an external logout landing page. - `urlQuery: object`: The urlQuery to set for the page the user is redirected to after logout. - `pathParams: object`: Values for the placeholders in the destination page's [`path`](/page-paths), by placeholder name. - `redirect: boolean`: If set to `false` the user session will be cleared, but the page will not be reloaded. #### Examples ###### A logout button: ```yaml - id: logout_button type: Logout properties: title: Logout events: onClick: - id: logout type: Logout ``` ###### Redirect to the `logged-out` page in the app after logout: ```yaml - id: logout_button type: Logout properties: title: Logout events: onClick: - id: logout type: Logout params: callbackUrl: pageId: logged-out ``` --- url: https://docs.lowdefy.com/md/actions/magiclinkverify.md --- # MagicLinkVerify ``` (params?: { callbackUrl?: { home?: boolean pageId?: string url?: string urlQuery?: object pathParams?: object } errorCallbackUrl?: { home?: boolean pageId?: string url?: string urlQuery?: object pathParams?: object } newUserCallbackUrl?: { home?: boolean pageId?: string url?: string urlQuery?: object pathParams?: object } token?: string }): void ``` The `MagicLinkVerify` action completes a magic-link sign-in from the landing page the sign-in email points at. It navigates the browser to the auth server's verify endpoint, which sets the session cookie and redirects the user on to their destination. ## Why a landing page A magic-link token is single-use, and the verify endpoint consumes it on the **first** `GET` of the URL. Corporate mail security — Microsoft Defender Safe Links, Proofpoint URL Defense, Mimecast — fetches every link in a message as it is delivered. When the email links straight at the verify endpoint the scanner spends the token minutes before the person clicks, and the person lands on the auth error page with `INVALID_TOKEN`. Setting [`auth.authPages.magicLink`](/auth-configuration#the-magic-link-landing-page) to a page in your app moves the email's link to that page, carrying the token and callback destinations on the URL query. Fetching a Lowdefy page has no side effect, so the token stays unspent until a person clicks a button that runs `MagicLinkVerify`. This action **must** be bound to a click. Do not run it in `onMount` and do not auto-redirect from the landing page: scanning sandboxes that execute JavaScript would consume the token anyway, and you would be no better off than linking at the verify endpoint directly. ## Defaults come from the URL query Every parameter is optional. The auth server minted the landing URL with the verify query on it, so the action reads its defaults from the page's own URL: | Parameter | Default | | --------- | ------- | | `token` | the `?token=` query parameter | | `callbackUrl` | the `?callbackURL=` query parameter, else the auth server's default | | `newUserCallbackUrl` | the `?newUserCallbackURL=` query parameter, else `callbackUrl` | | `errorCallbackUrl` | the `?errorCallbackURL=` query parameter, else `auth.authPages.error` | Pass a parameter only to override what the email carried. The action throws if there is no token in either place, because there is nothing to verify. `callbackUrl: false` is not valid here. Verification redirects through a hop the app does not control, so there is no staying put. The event chain ends after this action, like every other action that navigates the browser away — a step after it would race the page being replaced. #### Parameters ###### object - `token: string`: The single-use magic-link token. Defaults to the `?token=` URL query parameter of the landing page. - `callbackUrl: object`: Where a successful sign-in lands. Defaults to the `?callbackURL=` URL query parameter. - `home: boolean`: Land on the app's home page. - `pageId: string`: The pageId to land on. - `url: string`: The URL to land on. An absolute URL is not `basePath`-prefixed, so it can be an external landing page. - `urlQuery: object`: The urlQuery to set on the destination. - `pathParams: object`: Values for the placeholders in the destination page's [`path`](/page-paths), by placeholder name. - `newUserCallbackUrl: object`: Where a sign-in that creates a new account lands, for a first-run or onboarding page. Defaults to the `?newUserCallbackURL=` URL query parameter. Same fields as `callbackUrl`. - `errorCallbackUrl: object`: Where a failed verification lands, with the reason in `?error=`. Defaults to the `?errorCallbackURL=` URL query parameter, then to `auth.authPages.error`. Same fields as `callbackUrl`. #### Examples ###### The landing page, with everything taken from the URL query: ```yaml id: magic-link type: PageHeaderMenu blocks: - id: content type: Box blocks: - id: title type: Title properties: content: Sign in level: 3 - id: description type: Paragraph properties: content: Click the button below to finish signing in. - id: verify_button type: Button properties: title: Sign in events: onClick: - id: verify type: MagicLinkVerify ``` ###### Send a new user to an onboarding page instead: ```yaml - id: verify type: MagicLinkVerify params: newUserCallbackUrl: pageId: welcome ``` ###### Override where a successful sign-in lands: ```yaml - id: verify type: MagicLinkVerify params: callbackUrl: pageId: dashboard urlQuery: source: magic-link ``` --- url: https://docs.lowdefy.com/md/actions/pdfmake.md --- # PdfMake ``` (params: { docDefinition: object, tableLayouts?: object, filename: string, fonts?: object, }): void ``` The `PdfMake` action is used to generate and download a PDF file from a set of defined content using the [pdfMake](https://github.com/bpampuch/pdfmake) open-source library. #### Parameters ###### object - `docDefinition: object`: __Required__ - The document definition object which includes styling, columns, tables etc. - `tableLayouts: object`: The table layouts for the generated PDF file. - `filename: string`: __Required__ - The name for the generated PDF file. - `fonts: object`: The fonts for the generated PDF file. ###### See the [pdfMake docs](https://pdfmake.github.io/docs) for more info on these properties. #### Examples ###### Generate a PDF (simple example): ```yaml - id: generate_pdf_button type: Button properties: title: Download PDF icon: download events: onClick: - id: make_pdf type: PdfMake params: filename: my_file_name.pdf docDefinition: pageMargins: 50 defaultStyle: fontSize: 10 content: - text: This pdf has been generated with Lowdefy and pdfMake. bold: true ``` ###### Generate a PDF (detailed example): ```yaml - id: pdf_generate_button type: Button style: textAlign: center properties: title: Generate & Download PDF icon: download color: '#6293F8' events: onMount: - id: init_data type: SetState params: invoice: id: '0030135' account_id: 'A-11344' inv_date: _date: now subtotal: 397.034 discount: -19.8517 vat: 59.5551 total: 436.7374 balance: 413.2330 customer: name: Service Center phone: +123-456-7890 vat_nmr: 12-333-4567 address: | 123 Main St. Anytown CA US 9999 services: - name: Hosting and Maintannce qty: 1 price: 235.90 code: X12-33C - name: Developer Hours qty: 16 price: 60.345 code: X12-39A - name: Designer Hours qty: 4 price: 40.122 code: X12-21A - name: Project Management qty: 2 price: 60.667 code: X12-49A onClick: - id: generate_pdf type: PdfMake params: _ref: inv_template.yaml ``` ###### `/inv_template.yaml` ```yaml filename: _nunjucks: on: _state: invoice template: 'INV-{{ id }}-{{ inv_date | date("DD-MM-YYYY") }}.pdf' docDefinition: pageMargins: [50, 25, 50, 70] defaultStyle: fontSize: 10 footer: _function: - columns: - qr: _string.concat: - _location: origin - /invoice?id=" - _state: invoice.id - '"' margin: [50, 0, 0, 0] fit: '64' - alignment: 'right' fontSize: 7 margin: [0, 0, 50, 0] text: __nunjucks: template: 'Page {{ page }} of {{ total }}' on: page: __args: 0 total: __args: 1 content: - columns: - width: 'auto' margin: [0, 20, 0, 0] stack: - fontSize: 9 text: | - fontSize: 7 text: | Example Services Ltd. 112 Street Name City, State 12345 Country 001-AB +00-1234-5566 info@example.com Vat Number: 444 5555 0000 - width: '*' text: ' ' - width: 110 stack: - margin: [0, 5, 0, 0] alignment: right fontSize: 7 text: | Example Services Ltd. Reg Number: 2001/22224/09 - margin: [0, 20, 0, 20] text: Customer Invoice bold: true alignment: center fontSize: 14 - columns: - width: 150 bold: true text: | INVOICE NUMBER: DATE ISSUED: ACCOUNT NUMBER: - width: '*' text: _nunjucks: template: | {{ id }} {{ inv_date | date("YYYY/MM/DD") }} {{ account_id }} on: _state: invoice - width: 150 bold: true text: | CUSTOMER: ADDRESS: - width: '*' text: _nunjucks: template: | {{ customer.name }} {{ customer.address }} on: _state: invoice - layout: 'lightHorizontalLines' margin: [0, 10, 0, 0] table: widths: [70, '*', 70, 70, 70] headerRows: 1 body: _json.parse: _nunjucks: on: services: _state: invoice.services template: | [ [ { "text": "ITEM CODE", "bold": true }, { "text": "SERVICE", "bold": true }, { "text": "UNIT PRICE", "bold": true, "alignment": "right" }, { "text": "QTY", "bold": true, "alignment": "right" }, { "text": "COST", "bold": true, "alignment": "right" } ], {% for item in services %} [ "{{ loop.index }}: {{ item.code }}", "{{ item.name | safe }}", { "text": "{{ ( item.price / item.qty ).toFixed(2) }}", "alignment": "right"}, { "text": "{{ item.qty }}", "alignment": "right"}, { "text": "{{ item.price.toFixed(2) }}", "alignment": "right"} {% if loop.last %} ] {% else %} ], {% endif %} {% endfor %} ] - layout: 'headerLineOnly' margin: [0, -5, 0, 0] table: widths: ['*', 70, 70, 70] headerRows: 1 body: - - '' - '' - '' - '' - - '' - alignment: right text: 'Subtotal:' - '' - alignment: right text: _number.toFixed: - _state: invoice.subtotal - 2 - - '' - alignment: right text: 'Discount (5%):' - '' - alignment: right text: _number.toFixed: - _state: invoice.discount - 2 - - '' - alignment: right text: 'VAT (15%):' - '' - alignment: right text: _number.toFixed: - _state: invoice.vat - 2 - - '' - alignment: right text: 'Total:' - '' - alignment: right text: _number.toFixed: - _state: invoice.total - 2 - layout: 'headerLineOnly' margin: [0, -5, 0, 0] table: widths: ['*', 70, 70, 70] headerRows: 1 body: - - '' - '' - '' - '' - - '' - alignment: right bold: true text: 'BALANCE DUE:' - '' - alignment: right bold: true text: _number.toFixed: - _state: invoice.balance - 2 ``` --- url: https://docs.lowdefy.com/md/actions/posthogalias.md --- # PostHogAlias ``` (params: { alias: string, }): Promise ``` The `PostHogAlias` action points a second id at the person PostHog already knows, for example when an app has its own identifier alongside the one used by [`PostHogIdentify`](/PostHogIdentify). When PostHog is disabled or `posthog-js` could not be loaded, the action does nothing. Invalid params always throw, even when PostHog is disabled. The action is part of the [`@lowdefy/plugin-posthog`](/PostHog) plugin, which is included by default. See the [PostHog guide](/PostHog) for how the actions fit together. #### Parameters ###### object - `alias: string`: __Required__ - The second id for the current person. #### Examples ###### Alias a customer number: ```yaml - id: alias_person type: PostHogAlias params: alias: _user: customerNumber ``` --- url: https://docs.lowdefy.com/md/actions/posthogcapture.md --- # PostHogCapture ``` (params: { event: string, properties?: object, groups?: object, }): Promise ``` The `PostHogCapture` action captures a named product event in [PostHog](https://posthog.com). Autocapture already records clicks and pageviews, so keep the set of named events small. Every event is billable, and a funnel of thirty near identical events tells you less than one of five. Never send personally identifiable information as a property. When PostHog is disabled or `posthog-js` could not be loaded, the action does nothing. Invalid params always throw, even when PostHog is disabled. The action is part of the [`@lowdefy/plugin-posthog`](/PostHog) plugin, which is included by default. See the [PostHog guide](/PostHog) for how the actions fit together. #### Parameters ###### object - `event: string`: __Required__ - The event name. Snake case names that read as a completed action, like `report_submitted`, work best in funnels. - `properties: object`: Event properties. - `groups: object`: Groups to attach to this one event, as `{ groupType: groupKey }`. Sent to PostHog as the `$groups` property. #### Examples ###### Capture an event after a request: ```yaml - id: submit_report type: Button properties: title: Submit report events: onClick: - id: submit type: Request params: submit_report - id: capture_event type: PostHogCapture params: event: report_submitted properties: report_type: _state: report_type line_item_count: _array.length: _state: line_items ``` --- url: https://docs.lowdefy.com/md/actions/posthogcapturepageleave.md --- # PostHogCapturePageLeave ``` (params: { properties?: object, }): Promise ``` The `PostHogCapturePageLeave` action captures a `$pageleave` event by hand, the counterpart to [`PostHogPageview`](/PostHogPageview). PostHog uses pageleaves to compute bounce rate and time on page. `posthog-js` captures a pageleave itself when the tab is closed, as long as automatic pageviews are on, because `capture_pageleave` defaults to `if_capture_pageview`. With `capture_pageview: false`, set `capture_pageleave: true` in the [`PostHogInit`](/PostHogInit) options to keep them, or use this action, for example before a `Link` or `Logout` action. The page `onHidden` event is not a page leave: it also fires when the browser window loses focus. When PostHog is disabled or `posthog-js` could not be loaded, the action does nothing. Invalid params always throw, even when PostHog is disabled. The action is part of the [`@lowdefy/plugin-posthog`](/PostHog) plugin, which is included by default. See the [PostHog guide](/PostHog) for how the actions fit together. #### Parameters ###### object - `properties: object`: Properties to add to the `$pageleave` event. #### Examples ###### Capture a pageleave before signing out: ```yaml - id: sign_out type: Button properties: title: Sign out events: onClick: - id: capture_pageleave type: PostHogCapturePageLeave - id: reset_posthog type: PostHogReset - id: logout type: Logout ``` --- url: https://docs.lowdefy.com/md/actions/posthogfeatureflag.md --- # PostHogFeatureFlag ``` (params: { key: string, default?: any, enabled?: boolean, payload?: boolean, }): Promise ``` The `PostHogFeatureFlag` action reads a PostHog feature flag and returns its value, so it can be used further down the action chain with the [`_actions`](/_actions) operator. The value is whatever PostHog resolved for this person when flags were last loaded; the action does not wait for a network call. When the person or their groups just changed, reload the flags first with [`PostHogReloadFeatureFlags`](/PostHogReloadFeatureFlags). Reading a flag value, with or without `enabled`, sends a `$feature_flag_called` event to PostHog, which is what PostHog experiments use to count exposures. Reading a payload does not. When PostHog is disabled, not initialised, or `posthog-js` could not be loaded, the action returns `default`. Invalid params always throw, even when PostHog is disabled. The action is part of the [`@lowdefy/plugin-posthog`](/PostHog) plugin, which is included by default. See the [PostHog guide](/PostHog) for how the actions fit together. #### Parameters ###### object - `key: string`: __Required__ - The feature flag key. - `default: any`: Returned when the flag has no value for this person, and when PostHog is disabled. Defaults to `null`. - `enabled: boolean`: Return a boolean, using `isFeatureEnabled`, instead of the variant value. - `payload: boolean`: Return the flag payload, using `getFeatureFlagPayload`, instead of the flag value. Takes precedence over `enabled`. #### Response The flag value, the payload, or a boolean, depending on the params. `default` when the flag has no value. #### Examples ###### Store a flag variant in state: ```yaml events: onMount: - id: read_flag type: PostHogFeatureFlag params: key: new-checkout default: control - id: store_flag type: SetState params: checkout_variant: _actions: read_flag.response ``` --- url: https://docs.lowdefy.com/md/actions/posthoggroup.md --- # PostHogGroup ``` (params: { type: string, key: string, properties?: object, }): Promise ``` The `PostHogGroup` action associates the current person with a group, so events can be analysed per organization, team or account. The association sticks until [`PostHogReset`](/PostHogReset) is called, so run it after [`PostHogIdentify`](/PostHogIdentify). Group types have to be enabled on the PostHog project. When PostHog is disabled or `posthog-js` could not be loaded, the action does nothing. Invalid params always throw, even when PostHog is disabled. The action is part of the [`@lowdefy/plugin-posthog`](/PostHog) plugin, which is included by default. See the [PostHog guide](/PostHog) for how the actions fit together. #### Parameters ###### object - `type: string`: __Required__ - The group type, for example `organization`. - `key: string`: __Required__ - The id of the group instance. - `properties: object`: Group properties. #### Examples ###### Associate the person with their organization: ```yaml - id: set_group type: PostHogGroup params: type: organization key: _user: organizationId properties: plan: _user: plan ``` --- url: https://docs.lowdefy.com/md/actions/posthogidentify.md --- # PostHogIdentify ``` (params: { id?: string | null, properties?: object, propertiesOnce?: object, }): Promise ``` The `PostHogIdentify` action ties the current browser session to a person in [PostHog](https://posthog.com). Run it once the user is known, usually right after [`PostHogInit`](/PostHogInit) in the shared page `onInit` actions, or after a successful login. A missing, `null` or empty `id` is a no-op, so on a public page the anonymous session is left alone. That anonymous session is what a later `PostHogIdentify` merges into the person, which is what makes signup funnels work. The action is safe to run on every page. It reads the identity `posthog-js` already holds: - __Same person__: when `id` is already the current `distinct_id`, it only updates the person properties, if any are given. `posthog-js` skips a property update identical to the previous one. - __Person swap__: PostHog refuses to move an identified `distinct_id` onto a different person, so when the browser is identified as someone else, the action calls `reset()` before identifying the new person. - __Anonymous visitor__: identified without a reset, so the anonymous session merges into the person. Never send personally identifiable information as a person property. When PostHog is disabled or `posthog-js` could not be loaded, the action does nothing. Invalid params always throw, even when PostHog is disabled. The action is part of the [`@lowdefy/plugin-posthog`](/PostHog) plugin, which is included by default. See the [PostHog guide](/PostHog) for how the actions fit together. #### Parameters ###### object - `id: string`: The person's id. - `properties: object`: Person properties to set. `null` and `undefined` values are dropped, so an absent value never overwrites one PostHog already has. - `propertiesOnce: object`: Person properties that are only set the first time, for example a signup date. #### Examples ###### Identify the logged in user on every page: ```yaml # shared/posthog_init.yaml - id: init_posthog type: PostHogInit params: apiKey: _build.env: POSTHOG_API_KEY - id: identify_person type: PostHogIdentify params: id: _user: id properties: role: _user: role locale: _locale: active ``` --- url: https://docs.lowdefy.com/md/actions/posthoginit.md --- # PostHogInit ``` (params: { apiKey?: string, apiHost?: string, options?: object, debug?: boolean, enabled?: boolean, captureEventFailures?: boolean, maskDataText?: boolean, }): Promise ``` The `PostHogInit` action downloads and initialises the [PostHog](https://posthog.com) `posthog-js` browser SDK. Every other PostHog action does nothing until it has run, so run it from the app's root `events.onInitAsync` in `lowdefy.yaml`, which runs once per app load on whichever page opens first and does not hold the first render. `PostHogInit` is idempotent: the first call loads PostHog, and later calls with the same `apiKey` do nothing, so running it from every page's events instead is also safe. Calling it with a different `apiKey` throws, because a second PostHog instance would split the session. With `enabled: false`, `posthog-js` is never downloaded and every other PostHog action becomes a silent no-op. A later call with `enabled: true` still loads PostHog; a call with `enabled: false` after PostHog was loaded throws. Every event carries the deployment environment: when the app declares [`config.environments`](/lowdefy-schema), `PostHogInit` registers the current environment's name (`LOWDEFY_ENVIRONMENT`) as the `environment` super property, so staging and production events can be told apart in one PostHog project. It also registers `lowdefy_build_id` and `lowdefy_app_version` (the app's `version`), so events can be split before and after a deploy, and a `lowdefy_config_key` can be mapped back to the config of the build that sent it. Events carry Lowdefy semantics. Autocaptured clicks, rage clicks and dead clicks get `lowdefy_page_id`, `lowdefy_block_id`, `lowdefy_block_type`, `lowdefy_block_ids` (every enclosing block, innermost first), `lowdefy_option: true` on a dropdown option, `lowdefy_text` (the text of the control the click reached, such as a button or menu item, even when the click landed on its icon or a wrapper) and, inside a grid, `lowdefy_row` and `lowdefy_column`. Every other event gets `lowdefy_page_id`. This adds properties only, never events, and has no switch. A block with the `ph-no-capture` class is not autocaptured, so it is never enriched. Click text that shows the app's data never leaves the browser. On every event, a clicked text (`$el_text`, `lowdefy_text`, the `text` entries of `$elements_chain` and `$elements`, `$selected_content` and link targets) is kept only when the config of the page, the menus, the i18n messages or the antd locale spell it out, so grid and table cell values, options and menu items a request fills, labels built from records and `Dynamic` block output are removed, while button titles, menu and tab labels, column headers, literal options and modal __OK__ and __Cancel__ stay. Element attributes other than the tag, classes, `class`, `role`, `type`, `row-index`, `col-id`, `data-row-index`, `data-col-key`, `nth-child` and `nth-of-type` are removed, including `title`, `aria-label`, `value`, `placeholder`, `alt` and other `data-*`, and `id` is kept only as a block wrapper id (`bl-`). Classes and `data-ph-capture-attribute-` values (sent as property ``) are not masked, so never build them from data. The other `lowdefy_*` properties stay, so a click on data still records its block, row and column. URLs are not masked: `$current_url`, `$pathname`, query values and `lowdefy_path_params` are sent unchanged. Set `maskDataText: false` to send full click text, or `options.mask_all_text: true` to send none. A block `class` of `ph-sensitive` masks that block's config text too, but only within the block's own DOM: popups it renders into the page body (`Dropdown` and `DropdownButton` items, table and grid `MenuCell` menus, `Modal`, `Drawer`) are outside it. Session replay is not covered: set `options.session_recording.maskTextSelector: '*'` to mask all text in recordings, or give a block a `class` of `ph-mask`. See [Click text masking](/PostHog#click-text-masking). When a block or app event fails, `PostHogInit` captures a `lowdefy_event_failed` event with `lowdefy_event_scope` (`page` or `app`), `lowdefy_page_id`, `lowdefy_block_id`, `lowdefy_block_type`, `lowdefy_event_name`, `lowdefy_debounce_ms`, `lowdefy_action_id`, `lowdefy_action_type`, `lowdefy_error_name`, `lowdefy_config_key` and `lowdefy_invalid_blocks` (the blocks a `Validate` found invalid). It carries ids only, never error messages or values, and is timestamped when the event's actions started. Failures in the app's `onInit` and in the first page's `onInit` are captured too, although `PostHogInit` runs later in `onInitAsync`: Lowdefy holds up to 20 failures until PostHog starts. At most 50 are captured per app load, held failures included. Set `captureEventFailures: false` to turn it off. An environment with `posthog: { enabled: false }` in `config.environments` switches PostHog off there: `PostHogInit` behaves as `enabled: false` whatever its params say, needs no `apiKey`, and every other PostHog action is a no-op. If `posthog-js` fails to download, for example on a flaky network, a warning is logged to the browser console and the other PostHog actions do nothing. The action is part of the [`@lowdefy/plugin-posthog`](/PostHog) plugin, which is included by default. See the [PostHog guide](/PostHog) for how the actions fit together. #### Parameters ###### object - `apiKey: string`: __Required__ unless `enabled` is `false` - The PostHog project API key. This is a public, write only key that is meant to ship in the browser bundle, so read it with [`_build.env`](/_build). Never use a personal API key here. - `apiHost: string`: The PostHog ingestion host. Defaults to `https://us.i.posthog.com`. Use `https://eu.i.posthog.com` for the EU cloud, or the host of a self hosted instance. - `options: object`: Passed through to `posthog.init` as its config object. Set `capture_pageview: history_change` to capture a pageview on every client side navigation. Other common options are `person_profiles`, `capture_pageleave`, `autocapture`, `disable_session_recording`, `persistence` and `opt_out_capturing_by_default`. See the [posthog-js config reference](https://posthog.com/docs/libraries/js/config). - `debug: boolean`: Log everything PostHog does to the browser console. - `enabled: boolean`: Set to `false` to skip loading PostHog. Defaults to `true`. - `captureEventFailures: boolean`: Capture a `lowdefy_event_failed` event when a block or app event fails. Defaults to `true`. - `maskDataText: boolean`: Mask click text that the page config, menus, i18n messages or antd locale do not spell out, and element attributes outside the structural allow-list, before events are sent. Set to `false` to send full click text. Defaults to `true`. #### Response `null`, once `posthog-js` has loaded. #### Examples ###### Initialise PostHog once per app load: ```yaml # lowdefy.yaml events: onInitAsync: _ref: shared/posthog_init.yaml ``` ```yaml # shared/posthog_init.yaml - id: init_posthog type: PostHogInit params: apiKey: _build.env: POSTHOG_API_KEY enabled: _build.ne: - _build.env: POSTHOG_API_KEY - null options: capture_pageview: history_change person_profiles: identified_only ``` With `POSTHOG_API_KEY` unset, for example in local development, `enabled` is `false` and PostHog is never loaded. ###### Turn off the failure event: ```yaml - id: init_posthog type: PostHogInit params: apiKey: _build.env: POSTHOG_API_KEY captureEventFailures: false ``` ###### Send full click text: ```yaml - id: init_posthog type: PostHogInit params: apiKey: _build.env: POSTHOG_API_KEY maskDataText: false ``` Only turn masking off when the people who use the app have agreed that PostHog may store the data they click on. --- url: https://docs.lowdefy.com/md/actions/posthogoptin.md --- # PostHogOptIn ``` (void): Promise ``` The `PostHogOptIn` action starts capturing for this person. Pair it with [`PostHogOptOut`](/PostHogOptOut) and a [`PostHogInit`](/PostHogInit) that sets `options.opt_out_capturing_by_default: true` when consent is required before any data is sent. The choice is remembered in the persistence store configured on `PostHogInit`. When PostHog is disabled or `posthog-js` could not be loaded, the action does nothing. The action is part of the [`@lowdefy/plugin-posthog`](/PostHog) plugin, which is included by default. See the [PostHog guide](/PostHog) for how the actions fit together. #### Parameters The `PostHogOptIn` action does not take any parameters. #### Examples ###### Accept analytics from a consent banner: ```yaml - id: accept_analytics type: Button properties: title: Accept events: onClick: - id: opt_in type: PostHogOptIn ``` --- url: https://docs.lowdefy.com/md/actions/posthogoptout.md --- # PostHogOptOut ``` (void): Promise ``` The `PostHogOptOut` action stops capturing for this person. The choice is remembered in the persistence store configured on [`PostHogInit`](/PostHogInit). See [`PostHogOptIn`](/PostHogOptIn) for consent flows. When PostHog is disabled or `posthog-js` could not be loaded, the action does nothing. The action is part of the [`@lowdefy/plugin-posthog`](/PostHog) plugin, which is included by default. See the [PostHog guide](/PostHog) for how the actions fit together. #### Parameters The `PostHogOptOut` action does not take any parameters. #### Examples ###### Reject analytics from a consent banner: ```yaml - id: reject_analytics type: Button properties: title: Reject events: onClick: - id: opt_out type: PostHogOptOut ``` --- url: https://docs.lowdefy.com/md/actions/posthogpageview.md --- # PostHogPageview ``` (params: { properties?: object, }): Promise ``` The `PostHogPageview` action captures a `$pageview` event by hand. A Lowdefy app routes on the client without a page load, so the `posthog-js` default only captures the first page. The simplest fix is to set `capture_pageview: history_change` in the [`PostHogInit`](/PostHogInit) options, which captures a pageview on every navigation, and not use this action at all. Use `PostHogPageview` when pageviews need extra properties or tighter control: set `capture_pageview: false` and run it from each page's `onMountAsync` event. When PostHog is disabled or `posthog-js` could not be loaded, the action does nothing. Invalid params always throw, even when PostHog is disabled. The action is part of the [`@lowdefy/plugin-posthog`](/PostHog) plugin, which is included by default. See the [PostHog guide](/PostHog) for how the actions fit together. #### Parameters ###### object - `properties: object`: Properties to add to the `$pageview` event. #### Examples ###### Capture pageviews by hand: ```yaml id: reports type: PageHeaderMenu events: onInit: _ref: shared/posthog_init.yaml # PostHogInit with options.capture_pageview: false onMountAsync: - id: capture_pageview type: PostHogPageview params: properties: section: reports ``` --- url: https://docs.lowdefy.com/md/actions/posthogreloadfeatureflags.md --- # PostHogReloadFeatureFlags ``` (params: { timeout?: integer, }): Promise<{ flags: string[], variants: object }> ``` The `PostHogReloadFeatureFlags` action asks PostHog to evaluate this person's feature flags again, and resolves once the new flags have arrived. Run it after [`PostHogIdentify`](/PostHogIdentify) or [`PostHogGroup`](/PostHogGroup) changed the targeting, then read the flags from its response or with [`PostHogFeatureFlag`](/PostHogFeatureFlag). It resolves with empty results if PostHog does not answer within `timeout` milliseconds, so a flaky network can never stall the action chain. When PostHog is disabled or `posthog-js` could not be loaded, it resolves with empty results straight away. The action is part of the [`@lowdefy/plugin-posthog`](/PostHog) plugin, which is included by default. See the [PostHog guide](/PostHog) for how the actions fit together. #### Parameters ###### object - `timeout: integer`: Milliseconds to wait for PostHog to answer. Defaults to `5000`. Must not be negative. #### Response An object: - `flags: string[]`: The keys of the flags enabled for this person. - `variants: object`: Every flag key mapped to its value. When PostHog does not answer in time, or is disabled, the response is `{ flags: [], variants: {} }`. #### Examples ###### Reload flags after switching organization: ```yaml - id: set_group type: PostHogGroup params: type: organization key: _state: organization_id - id: reload_flags type: PostHogReloadFeatureFlags params: timeout: 3000 - id: store_flags type: SetState params: feature_flags: _actions: reload_flags.response.variants ``` --- url: https://docs.lowdefy.com/md/actions/posthogreset.md --- # PostHogReset ``` (params: { resetDeviceId?: boolean, }): Promise ``` The `PostHogReset` action forgets the current person and starts a fresh anonymous session. Run it on sign out so the next person on a shared browser is not merged into this one. When PostHog is disabled or `posthog-js` could not be loaded, the action does nothing. Invalid params always throw, even when PostHog is disabled. The action is part of the [`@lowdefy/plugin-posthog`](/PostHog) plugin, which is included by default. See the [PostHog guide](/PostHog) for how the actions fit together. #### Parameters ###### object - `resetDeviceId: boolean`: Also generate a new device id, so the browser is not recognised as the same device. Defaults to `false`. #### Examples ###### Reset PostHog on sign out: ```yaml - id: reset_posthog type: PostHogReset - id: logout type: Logout ``` --- url: https://docs.lowdefy.com/md/actions/posthogsetpersonproperties.md --- # PostHogSetPersonProperties ``` (params: { set?: object, setOnce?: object, }): Promise ``` The `PostHogSetPersonProperties` action updates the properties of the person [`PostHogIdentify`](/PostHogIdentify) identified. Each call is a billable event; `posthog-js` skips a call identical to the previous one. Never send personally identifiable information. When PostHog is disabled or `posthog-js` could not be loaded, the action does nothing. Invalid params always throw, even when PostHog is disabled. The action is part of the [`@lowdefy/plugin-posthog`](/PostHog) plugin, which is included by default. See the [PostHog guide](/PostHog) for how the actions fit together. #### Parameters ###### object At least one of `set` or `setOnce` is required. - `set: object`: Person properties to set. - `setOnce: object`: Person properties that are only set if the person does not have them yet. #### Examples ###### Record a plan change: ```yaml - id: set_person_properties type: PostHogSetPersonProperties params: set: plan: _state: selected_plan setOnce: first_plan: _state: selected_plan ``` --- url: https://docs.lowdefy.com/md/actions/request.md --- # Request ``` (params: { all: boolean, holdValue?: boolean }): void (params: { requestId: string, holdValue?: boolean }): void (params: { requestIds: string[], holdValue?: boolean }): void (params: { requestId: string }): void (params: { requestIds: string[] }): void ``` The `Request` action calls a request, or if used during an `onInit` event, calls those requests while a page loads. `Request` can be used to call all requests on a page, a list of requests, or a single request. The `Request` action is synchronous, actions defined after it will only run once all the called requests have returned. To call requests that load data, the `onInitAsync`, `onMount` and `onMountAsync` events can be used. These will execute the actions while the page begins to render. If the `onInit` event is used, the page will only start rendering after the actions have completed. `Request` can be called without any parameters to call all requests in the page. It can also be called with a list of requestIds or a single requestId to call. #### Parameters ###### object - `all: boolean`: All requests in the page are called if `all` is set to true. - `requestId: string`: A single requestId to call. Use this object form when you also want to set `holdValue`. - `requestIds: string[]`: An array of requestIds to call. Use this object form when you also want to set `holdValue`. - `holdValue: boolean`: Optional. When `true`, the previous response value is retained on `requests[id][0].response` while the new request is loading. UI bound to `_request: ` continues to display the previous response instead of going to `null`. The previous response is also retained if the new request errors out — inspect `_request_details: .error` to detect failures. Defaults to `false`. ###### string A requestId of the request to call. ###### string[] An array of requestIds of the requests to call. #### Examples ###### Call a single request: ```yaml - id: call_one_request type: Request params: my_request_id ``` ###### Call a list of requests: ```yaml - id: call_many_requests type: Request params: - my_request_id_1 - my_request_id_2 - my_request_id_3 ``` ###### Call all requests: ```yaml - id: call_all type: Request params: all: true ``` ###### Refresh a table without flashing the previous data: ```yaml - id: refresh_table type: Request params: requestId: my_table_request holdValue: true ``` --- url: https://docs.lowdefy.com/md/actions/removelocalstorage.md --- # RemoveLocalStorage ``` (params: { key: string }): void ``` The `RemoveLocalStorage` action removes a key from the browser's [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). Removing a key that is not set does nothing. Keys are kept per app, under a key that names the app's `slug` and base path. The action removes the app's key, and a value left under the bare key from before keys were kept per app. Keys starting with `lowdefy_` or `lf-` are reserved for settings Lowdefy stores itself, such as the dark mode preference, and the action throws an error if one is used. If the browser blocks local storage, the action fails with an error that is shown to the user and triggers the event's `catch` actions, but is not logged as an app error. #### Parameters ###### object - `key: string`: __Required__ - The local storage key to remove. Keys starting with `lowdefy_` or `lf-` are reserved. #### Examples ###### Clear a stored preference: ```yaml - id: clear_preference type: RemoveLocalStorage params: key: dashboard_filters ``` --- url: https://docs.lowdefy.com/md/actions/reset.md --- # Reset ``` (void): void ``` The `Reset` actions resets a page to the state it was in just after the `onInit` event was executed. This clears the user's inputs. > The `Reset` action resets the state to the state after the `onInit` event was executed, and the `onMount` events are not executed after the reset happens. This might cause unexpected behavior if you used an `onInitAsync`, `onMount` or `onMountAsync` event to initialize the state. #### Parameters The `Reset` action does not take any parameters. #### Examples ###### A reset button: ```yaml - id: reset_button type: Button properties: title: Reset events: onClick: - id: reset type: Reset ``` --- url: https://docs.lowdefy.com/md/actions/resetvalidation.md --- # ResetValidation ``` (void): void (blockId: string): void (blockIds: string[]): void (params: { blockIds?: string|string[], regex?: string|string[], }): void ``` The `ResetValidation` action is used to reset validation flags on input fields. By default when a page is opened, no validation flags are set on any input fields. When the `Validate` is called, a validation flag is set on input fields matching the validation params. To reset the validation flags, use the `ResetValidation` action. The `ResetValidation` action `blockIds` or `regex` params are used to limit which blocks validation errors and warnings should be reset. A container's blockId matches the container and every block inside it, so `params: contact_card` lowers the validation flags of the inputs in the `contact_card` card. A blockId that matches no block logs a warning in the browser console. When the action runs in a list row, `$` in a blockId is replaced with the row index, as in `SetState` and `CallMethod`, so `list.$.name` matches the `name` input in the same row. `$` in a `regex` pattern keeps its regex meaning. #### Parameters ###### void The `ResetValidation` action lowers validation flags on all blocks on the page if called without params. ###### string A blockId of the block for which to lower validation flags. ###### string[] An array of blockIds of the blocks for which to lower validation flags. ###### object - `blockIds?: string|string[]`: A blockId or an array of the blockIds of the blocks for which to lower validation flags - `regex?: string|string[]`: A regex string pattern or an array of regex string patterns to match the blockIds for which to lower validation flags. #### Examples ###### Lower validation flags for all inputs on the page: ```yaml - id: reset_all type: ResetValidation ``` ###### Lower validation flags for a single input: ```yaml - id: reset_my_input type: ResetValidation params: my_input ``` ###### Lower validation flags for a list of inputs: ```yaml - id: reset_input_a_and_b type: ResetValidation params: - my_input_a - my_input_b ``` ###### Lower validation flags for all inputs matching a regex pattern: ```yaml - id: reset_foo type: ResetValidation params: regex: ^foo\. ``` ###### Lower validation flags for all inputs matching a list of regex patterns: ```yaml - id: reset_foo_and_price type: ResetValidation params: regex: - ^foo\. - ^.*price.*$ ``` ###### Lower validation flags for all inputs matching a list of regex patterns and a blockId: ```yaml - id: reset_foo_price_and_my_input type: ResetValidation params: blockIds: my_input regex: - ^foo\. - ^.*price.*$ ``` --- url: https://docs.lowdefy.com/md/actions/scrollto.md --- # ScrollTo ``` (params: { top?: number, left?: number, behavior?: enum }): void (params: { blockId: string, options: { behavior?: enum, block? enum, inline?: enum } }): void ``` The `ScrollTo` action is used to scroll the users browser. It is often used to scroll back to the top of a long form after resetting the form, or to scroll the user to the top of a page after linking to a new page. The `ScrollTo` action has two modes - scrolling to a block and scrolling to x and y coordinates on a page. > When scrolling to a block, `ScrollTo` implements [`Element.scrollIntoView()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView), while when scrolling to coordinates, it implements [`Window.scrollTo()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollTo). #### Parameters ###### Scroll to coordinates - `top: number`: Specifies the number of pixels along the Y axis to scroll the window. - `left: number`: Specifies the number of pixels along the X axis to scroll the window. - `behavior: enum`: Defines the transition animation. One of `auto` or `smooth`. ###### Scroll to a block - `blockId: string`: __Required__ - The blockId of a block to scroll to. - `options: object`: _Object_ - `behavior: enum`: Defines the transition animation. One of `auto` or `smooth`. Defaults to `auto`. - `block: enum`: Defines vertical alignment. One of `start`, `center`, `end`, or `nearest`. Defaults to `start`. - `inline: enum`: Defines horizontal alignment. One of `start`, `center`, `end`, or `nearest`. Defaults to `nearest`. #### Examples ###### Scroll to a block: ```yaml - id: scroll_to_block type: ScrollTo params: blockId: my_block ``` ###### Scroll to the top of the page: ```yaml - id: scroll_to_top type: ScrollTo params: top: 0 ``` ###### Scroll to the top of the page after linking from a previous page: ```yaml - id: link_button type: Button events: onClick: - id: link_ type: Link params: pageId: next_page - id: scroll_to_top type: ScrollTo params: top: 0 ``` ###### Scroll to the centre of a block: ```yaml - id: scroll_to_block type: ScrollTo params: blockId: my_block options: block: center inline: center ``` --- url: https://docs.lowdefy.com/md/actions/setdarkmode.md --- # SetDarkMode ``` (params?: { darkMode?: 'system' | 'light' | 'dark' }): void ``` The `SetDarkMode` action sets the user's dark mode preference. When called without params, it cycles through `light`, `dark`, and `system`. When `darkMode` is provided, it sets the preference to that value. - `'system'`: Follow the operating system's dark mode preference (and update live when the OS setting changes). - `'light'`: Force light mode. - `'dark'`: Force dark mode. The preference is persisted to `localStorage`, per app, and triggers re-rendering of the root Ant Design `ConfigProvider`. All blocks and `_media: darkMode` expressions update automatically. > **Note:** If the app's `theme.darkMode` config is set to `'light'` or `'dark'`, the developer has locked the mode. `SetDarkMode` still stores the user's preference, but it won't take visual effect until the config is changed to `'system'`. #### Parameters ###### object (optional) - `darkMode: 'system' | 'light' | 'dark'`: Set the dark mode preference. When omitted, cycles through light, dark, and system. #### Examples ###### Cycle dark mode preference: ```yaml - id: toggle_button type: Button properties: title: Toggle Dark Mode icon: _if: test: _media: darkMode then: Sun else: Moon events: onClick: - id: toggle type: SetDarkMode ``` ###### Set dark mode to follow system preference: ```yaml events: onClick: - id: use_system type: SetDarkMode params: darkMode: system ``` ###### Force dark mode: ```yaml events: onClick: - id: enable_dark type: SetDarkMode params: darkMode: dark ``` --- url: https://docs.lowdefy.com/md/actions/setfocus.md --- # SetFocus ``` (params: string): void ``` This action implements the [HTMLElement.focus()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) method. The `SetFocus` action sets focus on the specified HTML element, provided it can receive focus. It takes an id of the HTML element as a parameter. By default, when focusing an element, the browser will scroll it into view and may provide visible indication of the focus, typically by displaying a "focus ring" around the element. Note that for blocks like `TextInput`, the id of the HTML input element is not equal to the block id, since the block includes other HTML elements like the label, extra text and icons. You can inspect the HTML elements in the browser to find the correct element id. #### Parameters ###### string String that is the ID of the HTML element to focus. #### Examples ###### Set a TextInput block in focus when another TextInput's value changes: ```yaml - id: text_input_1 type: TextInput events: onChange: - id: set_focus type: SetFocus # Note that the params is an HTML element ID, and not a Block ID. params: text_input_2_input - id: text_input_2 type: TextInput ``` --- url: https://docs.lowdefy.com/md/actions/setglobal.md --- # SetGlobal ``` (toSet: object): void ``` The `SetGlobal` action sets values to the `global` object. It takes an object as parameters, and sets each of those values to the `global` object. This is useful if you need to set a value that needs to be available across multiple pages in the app, like the id of a currently selected object. `SetGlobal` works just like [`SetState`](/SetState), except it sets to the `global` object and not `state`. To set global values once when the app loads, before the first page's events run, use `SetGlobal` in the app [`onInit` or `onInitAsync` events](/events-and-actions#app-events). #### Parameters ###### object Object with key value pairs to set in `global`. #### Examples ###### Set a single value to global: ```yaml - id: set_selected_company type: SetGlobal params: selected_selected_company: _state: company.id ``` ###### Set multiple values to state: ```yaml - id: multiple_values type: SetGlobal params: firstName: Monica lastName: Geller address: street: 90 Bedford St city: New York zipCode: '10014' country: US friends: - Ross Geller - Rachel Green - Chandler Bing - Phoebe Buffay - Joey Tribbiani ``` ###### Using dot notation: ```yaml - id: dot_notation type: SetGlobal params: firstName: Monica lastName: Geller address.street: 90 Bedford St address.city: New York address.zipCode: '10014' address.country: US friends.0: Ross Geller friends.1: Rachel Green friends.2: Chandler Bing friends.3: Phoebe Buffay friends.5: Joey Tribbiani ``` --- url: https://docs.lowdefy.com/md/actions/setlocale.md --- # SetLocale ``` (params?: { locale?: string }): void ``` The `SetLocale` action sets the user's active locale preference for the app. The preference is persisted to `localStorage`, per app, and triggers re-rendering of the root Ant Design `ConfigProvider`. All blocks, `_t` translations, `_locale` operators, antd-built-in strings (date pickers, modals, validation), and dayjs date formatting update automatically. - `locale`: A BCP 47 code declared in `config.i18n.locales` (e.g. `'en-US'`, `'de-DE'`). - `'auto'` (or no params): Clears the user preference. The app falls back to the browser language best-fit, then to `config.i18n.defaultLocale`. `SetLocale` is a no-op when `config.i18n` is not configured. #### Parameters ###### object (optional) - `locale: string`: A BCP 47 locale code declared in `config.i18n.locales`, or `'auto'` to clear the preference. #### Examples ###### Switch to German: ```yaml events: onClick: - id: switch_de type: SetLocale params: locale: de-DE ``` ###### Wire a language picker: `_locale: supported` returns an array of locale descriptors `{ code, label, antd, dayjs }`. Selector options must be `{ value, label }` pairs, so map each descriptor onto that shape: ```yaml - id: lang_picker type: Selector properties: options: _array.map: on: _locale: supported callback: _function: value: __args: 0.code label: __args: 0.label value: _locale: active events: onChange: - id: set_locale type: SetLocale params: locale: _state: lang_picker ``` ###### Reset to browser/default: ```yaml events: onClick: - id: reset_locale type: SetLocale params: locale: auto ``` --- url: https://docs.lowdefy.com/md/actions/setlocalstorage.md --- # SetLocalStorage ``` (params: { key: string, value: any }): void ``` The `SetLocalStorage` action stores a value in the browser's [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage), under the given key. Values are serialized, so objects, arrays and dates are read back as the same types by the [`GetLocalStorage`](/GetLocalStorage) action. Local storage is per browser and it is not sent to the server. Keys are kept per app: Lowdefy stores the value under a key that names the app's `slug` and base path, so apps served from the same origin do not read or overwrite each other's values. A value stored under the bare key before keys were kept per app is moved to the app's key the first time `GetLocalStorage` reads it. Values set with this action are kept when a user signs out, so the next person to use the browser can read them. Do not store secrets, tokens or personal data in it. Keys starting with `lowdefy_` or `lf-` are reserved for settings Lowdefy stores itself, such as the dark mode preference, and the action throws an error if one is used. If the browser blocks local storage, or the storage quota is full, the action fails with an error that is shown to the user and triggers the event's `catch` actions, but is not logged as an app error. #### Parameters ###### object - `key: string`: __Required__ - The local storage key to write to. Keys starting with `lowdefy_` or `lf-` are reserved. - `value: any`: __Required__ - The value to store. #### Examples ###### Remember a user preference: ```yaml - id: save_preference type: SetLocalStorage params: key: dashboard_filters value: _state: filters ``` --- url: https://docs.lowdefy.com/md/actions/setstate.md --- # SetState ``` (toSet: object): void ``` The `SetState` action sets values in `state`. It takes an object as parameters, and sets each of those values to the `state` object. This is useful if you want to initialize `state`, set a flag after an action has executed (eg. to disable a button), or to set the result of a request to state. #### Parameters ###### object Object with key value pairs to set in `state`. #### Examples ###### Set a single value to state: ```yaml - id: single_value type: SetState params: message: Hello ``` ###### Set multiple values to state: ```yaml - id: multiple_values type: SetState params: firstName: Monica lastName: Geller address: street: 90 Bedford St city: New York zipCode: '10014' country: US friends: - Ross Geller - Rachel Green - Chandler Bing - Phoebe Buffay - Joey Tribbiani ``` ###### Using dot notation: ```yaml - id: dot_notation type: SetState params: firstName: Monica lastName: Geller address.street: 90 Bedford St address.city: New York address.zipCode: '10014' address.country: US friends.0: Ross Geller friends.1: Rachel Green friends.2: Chandler Bing friends.3: Phoebe Buffay friends.5: Joey Tribbiani ``` ###### Initialize state with the value of a request: ```yaml - id: initialize type: SetState params: _request: getUser ``` --- url: https://docs.lowdefy.com/md/actions/throw.md --- # Throw ``` (params: { throw?: boolean, message?: string, metaData?: any }): void ``` The `Throw` action is used throw an error to the user and log to the console. If `throw: true`, the `Throw` action will throw an error, and this will stop the execution of actions that are defined after it. If the action does not thrown, the `Throw` action will do nothing and the actions defined after it will be executed. #### Parameters - `throw: boolean`: Throws an error and stops the action chain when `true` or continues the action chain when `false` or undefined. - `message: string`: The error message to show to the user and log to the console if `throw: true`. This message can be overridden by setting the action's `messages.error`. - `metaData: any`: Data to log to the console if `throw: true`. #### Examples ###### Throw with custom message: ```yaml - id: foo_throw type: Throw params: throw: _eq: - _state: lukes_father - Darth Vader message: Nooooooooooooooooo ``` ###### Throw with metaData: ```yaml - id: foo_throw type: Throw params: throw: _eq: - _state: lukes_father - Darth Vader message: Nooooooooooooooooo metaData: realisation: Luke kissed his sister ``` ###### Override custom message: ```yaml - id: foo_throw type: Throw messages: error: Meh. params: throw: _eq: - _state: lukes_father - Darth Vader message: Nooooooooooooooooo ``` ###### Fail silently: ```yaml - id: foo_throw type: Throw messages: error: false params: throw: _eq: - _state: lukes_father - Darth Vader message: Nooooooooooooooooo ``` --- url: https://docs.lowdefy.com/md/actions/updatesession.md --- # UpdateSession ``` (void): void ``` The `UpdateSession` action is used to update the user session. If a adapter is used, any new user data in the database will be added to the user object. #### Parameters The `UpdateSession` action does not take any parameters. #### Examples ###### Update session after user updates their profile: ```yaml id: update_profile type: Button events: onClick: - id: update_profile_in_db type: Request params: update_user - id: update_session type: UpdateSession ``` --- url: https://docs.lowdefy.com/md/actions/validate.md --- # Validate ``` (void): void (blockId: string): void (blockIds: string[]): void (params: { blockIds?: string|string[], regex?: string|string[], }): void ``` The `Validate` action is used to validate a users input, usually before information is inserted into a database using a request. It is used in conjunction with the `required` and `validate` fields on input blocks. If the validation fails, the `Validate` action will fail, and this will stop the execution of actions that are defined after it. The first time a `Validate` action is called, validation errors and warnings are shown to the user. The [`Reset`](/Reset) action resets the validation status and the page `state`. The [`ResetValidation`](/ResetValidation) action resets only the validation status. The `Validate` action `blockIds` or `regex` params are used to limit which blocks are validated. Only the matched blocks will be validated, and validation results are shown for only those matched blocks. A container's blockId matches the container and every block inside it, so `params: contact_card` validates the inputs in the `contact_card` card. Matching follows the blocks on the page, not id prefixes, so it reaches `contact.name` inside `contact_card`. A blockId that matches no block logs a warning in the browser console and the action succeeds. When the action runs in a list row, `$` in a blockId is replaced with the row index, as in `SetState` and `CallMethod`, so `list.$.name` matches the `name` input in the same row. `$` in a `regex` pattern keeps its regex meaning. #### Parameters ###### void The `Validate` action validates all blocks on the page if called without params. ###### string A blockId of the block to validate. ###### string[] An array of blockIds of the blocks to validate. ###### object - `blockIds?: string|string[]`: A blockId or an array of the blockIds of the blocks to validate. - `regex?: string|string[]`: A regex string pattern or an array of regex string patterns to match the blockIds to validate. #### Examples ###### Validate all inputs on the page: ```yaml - id: validate_all type: Validate ``` ###### Validate a single input: ```yaml - id: validate_my_input type: Validate params: my_input ``` ###### Validate a list of inputs: ```yaml - id: validate_input_a_and_b type: Validate params: - my_input_a - my_input_b ``` ###### Validate all inputs matching a regex pattern: ```yaml - id: validate_foo type: Validate params: regex: ^foo\. ``` ###### Validate all inputs matching a list of regex patterns: ```yaml - id: validate_foo_and_price type: Validate params: regex: - ^foo\. - ^.*price.*$ ``` ###### Validate all inputs matching a list of regex patterns and a blockId: ```yaml - id: validate_foo_price_and_my_input type: Validate params: blockIds: my_input regex: - ^foo\. - ^.*price.*$ ``` --- url: https://docs.lowdefy.com/md/actions/wait.md --- # Wait ``` (params: { ms: integer, }): void ``` The `Wait` waits for the set number of milliseconds before returning. #### Parameters ###### object - `ms: integer`: __Required__ - The number of milliseconds to wait. #### Examples ###### Wait for 500 milliseconds: ```yaml - id: wait type: Wait params: ms: 500 ``` --- url: https://docs.lowdefy.com/md/operators/_actions.md --- # _actions ``` (key: string): any (all: boolean): any (arguments: { all?: boolean, key?: string, default?: any, }): any ``` The `_actions` operator returns the response value for a preceding action in the same event list. The action response object has the following structure: ```yaml error: Error, index: number, response: any, skipped: boolean, type: string, ``` Actions the event does not execute for a control-flow reason — actions in an untaken [`:if`](/:if) branch, in an unmatched [`:switch`](/:switch) case, or after a [`:return`](/:return) — are recorded as `skipped: true`, the same as actions skipped by their own `skip` field. Their operators are not evaluated, and `_actions` lookups on them return the skipped response object. Skipped entries are recorded in config order: when a branch executes, untaken branches written above it (an untaken `:then` when `:else` runs, or already-unmatched earlier cases) are recorded and readable, while branches below it are only recorded once the control completes. Actions after the control can read all of them. #### Arguments ###### string If the `_actions` operator is called with a string equal to a preceding action id in the same event list, the action response object returned. If a string is passed which does not match preceding action id in the same event list, `null` is returned. Dot notation is supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. ###### boolean If the `_actions` operator is called with boolean argument `true`, an object with all the preceding action id responses in the same event list is returned. #### Examples ##### Using a action response: ```yaml _actions: my_action.response ``` Returns: The response returned by the action. ##### Setting a action response to `state`: ```yaml id: refresh type: Button events: onClick: - id: get_fresh_data type: Request skip: _state: should_not_fetch params: get_data - id: set_data type: SetState params: did_not_fetch_data: _actions: get_fresh_data.skipped ``` --- url: https://docs.lowdefy.com/md/operators/_agent.md --- # _agent ``` (key: 'id' | 'conversationId'): string | null (key: 'files'): { key: string, filename: string, mediaType: string }[] | null (all: boolean): { id: string, conversationId: string | null, files: object[] } | null (arguments: { all?: boolean, key?: 'id' | 'conversationId' | 'files', default?: any, }): any ``` The `_agent` operator returns the [agent](/agents-introduction) that called the endpoint. Use it in an [endpoint tool](/agent-endpoint-tools) to tell agent work apart from a person's, for example to record who made a change. - `_agent: id` returns the agent's `id` in the `agents` section. - `_agent: conversationId` returns the id of the chat conversation the agent runs in. It is `null` for an agent run by a [`CallAgent`](/api) routine step, which has no conversation. - `_agent: files` returns the files the person attached in the chat, as `[{ key, filename, mediaType }]`: every file the [`AgentChat`](/AgentChat) block uploaded to storage, from every message of the conversation the chat request carried, in message order. `key` is the file's storage key. Files pasted inline (no upload policy) have no key and are left out. It is `[]` for an agent run by a `CallAgent` step. - `_agent: true` returns all three as `{ id, conversationId, files }`. The model sees each attached file with its name (`Attached file: screenshot.png`), so it can pass the name to a tool. Have the tool take file names and look each one up in `_agent: files`, never a storage key from the tool's input: the model can then only reach files of its own conversation. The files come from the person's own chat request, so the browser decides what they hold; before reading one, check that its key is one the caller may read, for example that it starts with the prefix the upload policy gives the caller's uploads. The value is set by Lowdefy when an agent calls the endpoint as a tool or a hook. Endpoints that endpoint calls in turn, through a `CallApi` step (including a `detached` one) or a connection's `callApi`, see the same agent. Nothing in the tool's payload or the chat request can set or change it, so the model cannot claim to be another agent or hide that it made the call. On any call that did not come from an agent (a page request or `CallAPI` action, an MCP tool call, a scheduled or webhook run), `_agent` returns `null`, and reading a key returns `null` or the `default` when one is given. It never throws. Tools that a sub-agent calls see the agent the chat or `CallAgent` step runs, not the sub-agent. #### Arguments ###### string The field of the calling agent to return: `id`, `conversationId` or `files`. ###### boolean If `true`, returns the calling agent as `{ id, conversationId, files }`, or `null` when no agent called the endpoint. ###### object - `key: string`: The field to return, `id`, `conversationId` or `files`. - `default: any`: A value to return when no agent called the endpoint. - `all: boolean`: If `true`, returns the calling agent as `{ id, conversationId, files }`. #### Examples ###### Record whether an agent or a person made a change: ```yaml api: - id: update-ticket type: Api description: Update a support ticket's status. payloadSchema: type: object properties: ticketId: type: string status: type: string routine: - id: update type: MongoDBUpdateOne connectionId: tickets properties: filter: _id: _payload: ticketId update: $set: status: _payload: status updated: userId: _user: id agentId: _agent: id conversationId: _agent: conversationId ``` When the `support_agent` agent calls the endpoint from a chat, `agentId` is `support_agent` and `conversationId` is the chat's conversation id. When a person calls it from a page, both are `null`. ###### Attach a file from the chat by its name: ```yaml api: - id: attach-chat-file type: Api description: Attach a file from this chat to a ticket. Pass the file's name as it was attached. payloadSchema: type: object properties: ticketId: type: string filename: type: string routine: - :set_state: file: _array.find: on: _agent: key: files default: [] callback: _function: __eq: - __args: 0.filename - _payload: filename - :if: _not: _string.startsWith: - _if_none: - _state: file.key - '' - _string.concat: - uploads/ - _user: id - / :then: - :reject: No file of yours by that name in this chat. - id: attach type: MongoDBUpdateOne connectionId: tickets properties: filter: _id: _payload: ticketId update: $push: files: key: _state: file.key filename: _state: file.filename mediaType: _state: file.mediaType ``` The model names the file; the key comes from the chat request, and the endpoint checks that it is one of the caller's uploads. ###### Branch on whether an agent called the endpoint: ```yaml routine: - :if: _ne: - _agent: true - null :then: - :return: message: Changes made by agents are held for review. ``` ###### Use a default when no agent called the endpoint: ```yaml source: _agent: key: id default: person ``` Returns: The agent id, or `person` when a person called the endpoint. --- url: https://docs.lowdefy.com/md/operators/_and.md --- # _and ``` (values: any[]): boolean ``` The `_and` operator performs a logical `and` over an array of inputs, using javascript [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) and [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) rules. It only returns true if all the values in the array are truthy (not `false`, `0`, `null`, `undefined`, or the empty string `""`). Else it returns false. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### array An array of values over which to perform a logical and. #### Examples ###### Two `true` values: ```yaml _and: - true - true ``` Returns: `true` ###### Array of `true` and `false` values: ```yaml _and: - true - true - true - false ``` Returns: `false` ###### Check if two boolean inputs are true: ```yaml _and: - _state: confirm_accept_terms - _state: confirm_accept_privacy_policy ``` Returns: `true` if both inputs are `true` ###### Truthy values: ```yaml _and: - "Hello" - 42 - [] - key: value ``` Returns: `true` ###### Falsy values: ```yaml _and: - true - null ``` Returns: `false` ```yaml _and: - true - 0 ``` Returns: `false` ```yaml _and: - true - "" ``` Returns: `false` --- url: https://docs.lowdefy.com/md/operators/_api.md --- # _api ``` (endpointId: string): any ``` The `_api` operator returns the response object from an API endpoint that has been called using the [`CallAPI`](/CallAPI) action. This operator is only available on the client side (in pages) and provides access to the full API response object including the response data, loading state, success status, and any errors. If the API endpoint has not yet been called or is still executing, the returned value is `null`. Dot notation is supported for accessing nested properties. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. The leading segment of an `_api` path is the endpoint id and is matched exactly, so the rule above applies to the part of the path after it. If the call was made with `holdValue: true` on the [`CallAPI`](/CallAPI) action, the previous response is retained on the entry while the new call is loading. `_api: .response` keeps returning the previous data instead of going to `null`, while `_api: .loading` still reflects the in-flight state. The previous response is also retained if the new call errors. The response includes the following fields: - `blockId: string`: The block id of the block from which the API call was initiated. - `endTimestamp: date`: The timestamp when the API call ended. - `endpointId: string`: The endpoint id of the API endpoint that was called. - `error: object`: Any error if the API endpoint failed or used :reject. - `holdValue: boolean`: Present (and `true`) when the call was made with `holdValue: true`. - `loading: boolean`: When `true`, the API endpoint is awaiting a response. - `pageId: string`: The page id of the page from which the API call was initiated. - `payload: object`: The payload sent with the API call. - `response: object`: The data returned by the API endpoint's :return statement. - `responseTime: number`: The time taken to get the response in milliseconds. - `startTimestamp: date`: The timestamp when the API call started. - `status: string`: The status of the API call. - `success: boolean`: When `true`, the API call completed successfully. #### Arguments ###### string The endpoint id of the API endpoint that was called using the CallAPI action. #### Examples ###### Accessing the full API response object: ```yaml blocks: - id: fetch_button type: Button properties: title: Load User Data events: onClick: - id: call_user_api type: CallAPI params: endpointId: get_user_profile payload: user_id: _state: selected_user_id - id: api_response_display type: Descriptions properties: items: _api: get_user_profile # Returns the full response object ``` Returns: The complete API response object containing response, error, success, status, loading, and timing information. ###### Using dot notation to access the response data: ```yaml blocks: - id: user_name_display type: Paragraph properties: content: _if: test: _api: get_user_profile.success then: _string.concat: - 'Welcome, ' - _api: get_user_profile.response.name else: 'User not loaded' - id: user_email_display type: Html properties: html: _nunjucks: template: |
Email: {{ email }}
on: email: _api: get_user_profile.response.email ``` ###### Checking loading and error states: ```yaml events: onMount: - id: fetch_products type: CallAPI params: endpointId: get_products - id: set_products_list type: SetState params: products_list _api: fetch_products.response.products blocks: - id: loading_text type: Html visible: _api: fetch_products.loading # Show loading text while API is loading properties: html: '
Loading...
' - id: error_message type: Alert visible: _not: _api: fetch_products.success properties: type: error message: _if: test: _api: fetch_products.error then: _api: fetch_products.error.message else: 'An error occurred' - id: products_list type: List visible: _and: - _api: fetch_products.success - _not: _api: fetch_products.loading ``` ###### Working with multiple API calls: ```yaml blocks: - id: load_all_button type: Button properties: title: Load All Data events: onClick: - id: fetch_user type: CallAPI params: endpointId: get_current_user - id: fetch_permissions type: CallAPI params: endpointId: get_user_permissions payload: user_id: _api: get_current_user.response._id - id: fetch_dashboard_data type: CallAPI params: endpointId: get_dashboard_stats payload: permission_level: _api: get_user_permissions.response.level - id: dashboard_container type: Box visible: _and: - _api: get_current_user.success - _api: get_user_permissions.success - _api: get_dashboard_stats.success blocks: - id: welcome_message type: Title properties: content: _string.concat: - 'Dashboard for ' - _api: get_current_user.response.name - id: stats_display type: Statistic properties: title: Total Revenue value: _api: get_dashboard_stats.response.total_revenue ``` ###### Using API response in form validation: ```yaml blocks: - id: check_username_button type: Button properties: title: Check Availability events: onClick: - id: check_username type: CallAPI params: endpointId: validate_username payload: username: _state: username_input - id: username_input type: TextInput properties: title: Username disabled: _eq: - _api: validate_username.response.available - false label: extra: _if: test: _api: validate_username.response then: _if: test: _api: validate_username.response.available then: 'Username is available!' else: 'Username is already taken' else: 'Enter a username and check availability' ``` ###### Accessing API response time and metadata: ```yaml blocks: - id: api_debug_info type: Descriptions properties: items: - label: API Status value: _api: fetch_data.status - label: Success value: _api: fetch_data.success - label: Response Time value: _string.concat: - _api: fetch_data.responseTime - 'ms' - label: Timestamp value: _date.format: - _api: fetch_data.endTimestamp - 'HH:mm:ss' ``` --- url: https://docs.lowdefy.com/md/operators/_app.md --- # _app ``` (key: string): any (all: boolean): any (arguments: { all?: boolean, key?: string, default?: any, }): any ``` The `_app` operator reads structured app metadata declared in `lowdefy.yaml` together with build-environment facts captured by the build. It runs on the client, the server, and at build time. The metadata object has the following keys: - `slug`: From the root `slug` field. **Required when referenced in string form** — `_app: slug` (or `_build.app: slug`) fails the build when `slug` is not declared in `lowdefy.yaml`. This guards against a `null` slug silently scoping namespaced data (e.g. MongoDB filters). The object form with an explicit `default` (`_app: { key: slug, default: ... }`) is the deliberate escape hatch: it returns the default instead of throwing, for the rare site that knowingly tolerates an unset slug. When set, the slug must be kebab-case (`^[a-z][a-z0-9]*(-[a-z0-9]+)*$`) — lowercase letters and digits, hyphen-separated, starts with a letter, no leading/trailing/consecutive hyphens, no underscores. An app that never references `slug` need not declare it. - `name`: From the root `name` field. Free-form string. - `version`: From the root `version` field. Free-form string. - `description`: From the root `description` field. Free-form string. - `license`: From the root `license` field. Free-form string. - `lowdefyVersion`: From the root `lowdefy` field — the framework version the app targets. - `gitSha`: The build's git commit SHA. Resolved at build time in three steps: the `LOWDEFY_GIT_SHA` environment variable (when set non-empty) wins; otherwise the build runs `git rev-parse HEAD`; if both fail (no `.git`, no `git` binary, env unset), `gitSha` is `null`. The build does not error in any case. Build-time and runtime reads return the same value. - `buildId`: A random identifier generated by each build, so two builds never share one. The production server sends it with every page config fetched during in-app navigation, and the client bundle carries the id of the build it came from; when the two differ the tab reloads once to pick up the new bundle instead of failing on functions or plugins it does not have. Useful in config as a cache-busting value that changes on every deploy. Missing root fields yield `null` — every key in the metadata is always present. The sole exception is `slug`: referencing it fails the build when it is not set (see above). Unknown keys return `null`. Dot-paths against the flat shape also return `null` (no exception). #### Build time and runtime `_app` resolves at **build time** and at runtime, and both produce the same value (app metadata is fixed at build). In most positions, write `_app: slug` — the build bakes it into the artifact. Inside a `_build.*` operator (for example as a [`_build.object.fromEntries`](/_build) map key), use the `_build.app` form so it resolves in time to be consumed by the surrounding build operator. See [`_build`](/_build). #### Root metadata fields accept literals and `_build.*` only The root metadata fields (`slug`, `name`, `description`, `version`, `license`, `lowdefy`) are resolved before the rest of the config, so they accept only literals and [`_build.*`](/_build) operators. `_ref`, `_var`, and static `_` operators are not resolved in these positions and fail the build with a clear error naming the field. Use `_build.env` for a deploy-time slug or name (for example `slug: { _build.env: APP_SLUG }`). #### Pinning `gitSha` in deploy environments Several common deploy paths strip `.git` before the build runs — Docker images that `COPY` only built output, hermetic PaaS sandboxes (Vercel, Netlify, Render), and CI runners with `actions/checkout` configured to fetch only a shallow tree without `.git`. In those environments, `git rev-parse HEAD` fails and `_app: gitSha` resolves to `null` unless `LOWDEFY_GIT_SHA` is set. Map your platform's commit env var via shell expansion in the build command: | Platform | Build command | | --------------- | ---------------------------------------------------------- | | Vercel | `LOWDEFY_GIT_SHA=$VERCEL_GIT_COMMIT_SHA lowdefy build` | | Netlify | `LOWDEFY_GIT_SHA=$COMMIT_REF lowdefy build` | | Render | `LOWDEFY_GIT_SHA=$RENDER_GIT_COMMIT lowdefy build` | | GitHub Actions | `LOWDEFY_GIT_SHA=$GITHUB_SHA lowdefy build` | | Docker (CLI) | `--build-arg LOWDEFY_GIT_SHA=$(git rev-parse HEAD)` | #### Arguments ###### string If the `_app` operator is called with a string argument, the value of the key in the app metadata is returned. If the key is not found, `null` is returned. Dot notation is supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. ###### boolean If the `_app` operator is called with the boolean argument `true`, the entire app metadata object is returned. ###### object - `all: boolean`: If `all` is set to `true`, the entire app metadata object is returned. One of `all` or `key` are required. - `key: string`: The value of the key in the app metadata is returned. If the value is not found, `null`, or the specified default value is returned. One of `all` or `key` are required. - `default: any`: A value to return if the `key` is not found. By default, `null` is returned if a value is not found. #### Examples ###### Get the app slug: ```yaml _app: slug ``` ```yaml _app: key: slug ``` Returns: The value of `slug` declared in `lowdefy.yaml`. ###### Get the app name: ```yaml _app: name ``` Returns: The value of the root `name` field. ###### Get the entire app metadata object: ```yaml _app: true ``` ```yaml _app: all: true ``` Returns: The full `{ slug, name, version, description, license, lowdefyVersion, gitSha, buildId }` object. ###### Return a default value if `gitSha` was not captured: ```yaml _app: key: gitSha default: dev ``` Returns: The git SHA, or `"dev"` when unavailable (e.g. local dev with no `LOWDEFY_GIT_SHA` and no `.git`). ###### Scope a MongoDB request by app slug: ```yaml filter: created.app_name: _app: slug ``` Returns: A filter document with `created.app_name` equal to the app's slug. ###### Show the version in a page title: ```yaml title: _string.concat: - { _app: name } - ' v' - { _app: version } ``` Returns: e.g. `"My App v1.2.3"`. --- url: https://docs.lowdefy.com/md/operators/_args.md --- # _args ``` (key: string): any (key: integer): any (all: boolean): any (arguments: { all?: boolean, key?: string | integer, default?: any }): any ``` The `_args` operator gets a value from the `arguments` array passed to a function operator. The `arguments` array is an array of all the positional arguments passed to the function. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### string If the `_args` operator is called with a string argument, the value of the key in the `arguments` array is returned. If the value is not found, `null` is returned. Dot notation and [block list indexes](/lists) are supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. ###### integer If the `_args` operator is called with a integer argument, the value at that index in the `arguments` array is returned. If the value is not found, `null` is returned. Dot notation and [block list indexes](/lists) are supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. ###### boolean If the `_args` operator is called with boolean argument `true`, the entire `arguments` array is returned. ###### object - `all: boolean`: If `all` is set to `true`, the entire `arguments` array is returned. One of `all` or `key` are required. - `key: string | integer`: The value of the key or index in the `arguments` array is returned. If the value is not found, `null`, or the specified default value is returned. Dot notation and [block list indexes](/lists) are supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. One of `all` or `key` are required. - `default: any`: A value to return if the `key` is not found in `arguments`. By default, `null` is returned if a value is not found. #### Examples ###### Map over an array: ```yaml _array.map: on: - firstName: Ted lastName: Mosby - firstName: Robin lastName: Scherbatsky - firstName: Marshall lastName: Eriksen - firstName: Lily lastName: Aldrin - firstName: Barney lastName: Stinson callback: _function: __string.concat: - __args: 0.firstName - ' ' - __args: 0.lastName ``` Returns: ```yaml - Ted Mosby - Robin Scherbatsky - Marshall Eriksen - Lily Aldrin - Barney Stinson ``` --- url: https://docs.lowdefy.com/md/operators/_array.md --- # _array The `_array` operator can be used to run javascript [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) methods. > This operator can be used as a [`_build`](/_build) operator method. # Operator methods: ## _array.at ``` (arguments: { on: any[], index: number }): any (arguments: [ on: any[], index: number ]): any ``` The `_array.at` method returns [the item at an index](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at). A negative index counts back from the end of the array, so `-1` returns the last item. ## _array.concat ``` (arrays: any[][]): any[] ``` The `_array.concat` method [concatenates](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat) arrays. ## _array.copyWithin ``` (arguments: { on: any[], target: number, start?: number, end?: number }): any[] (arguments: [ on: any[], target: number, start?: number, end?: number ]): any[] ``` The `_array.copyWithin` method [copies part of an array to another location in the same array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin) without modifying its length. ## _array.every ``` (arguments: { on: any[], callback: function, }): boolean (arguments: [ on: any[], callback: function, ]): boolean ``` The `_array.every` method tests whether [all elements in the array pass](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every) the test implemented by the provided function. It returns a Boolean value. ## _array.fill ``` (arguments: { on: any[], value: any, start?: number, end?: number }): any[] (arguments: [ on: any[], value: number, start?: number, end?: number ]): any[] ``` The `_array.fill` method [changes all elements in an array to a static value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill), from a `start` index to an `end` index. ## _array.filter ``` (arguments: { on: any[], callback: function, }): any[] (arguments: [ on: any[], callback: function, ]): any[] ``` The `_array.filter` method returns [an array with all elements that pass the test](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) implemented by the provided function. ## _array.find ``` (arguments: { on: any[], callback: function, }): any (arguments: [ on: any[], callback: function, ]): any ``` The `_array.find` method returns the value of the [first element in the provided array that satisfies the provided testing function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find). If no values satisfies the testing function, undefined is returned. ## _array.findIndex ``` (arguments: { on: any[], callback: function, }): number (arguments: [ on: any[], callback: function, ]): number ``` The `_array.findIndex` method returns [the index of the first element in the array that satisfies the provided testing function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex). Otherwise, it returns -1, indicating that no element passed the test. ## _array.findLast ``` (arguments: { on: any[], callback: function, }): any (arguments: [ on: any[], callback: function, ]): any ``` The `_array.findLast` method returns [the last element in the array that satisfies](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findLast) the provided testing function. ## _array.findLastIndex ``` (arguments: { on: any[], callback: function, }): number (arguments: [ on: any[], callback: function, ]): number ``` The `_array.findLastIndex` method returns [the index of the last element in the array that satisfies](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findLastIndex) the provided testing function, or `-1` if no element passes. ## _array.flat ``` (arguments: {on: any[], depth?: number}): any[] (arguments: [on: any[], depth?: number]): any[] ``` The `_array.flat` method returns a array with all [sub-array elements concatenated into it recursively](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat) up to the specified `depth`. ## _array.flatMap ``` (arguments: { on: any[], callback: function, }): any[] (arguments: [ on: any[], callback: function, ]): any[] ``` The `_array.flatMap` method [maps each element with the callback and flattens the result](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap) by one level. ## _array.from ``` (arguments: { on: any[] | string | { length: number }, callback?: function, }): any[] (arguments: [ on: any[] | string | { length: number }, callback?: function, ]): any[] ``` The `_array.from` method [creates a new array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) from an array, a string, or an object with a `length`. Use `on: { length: n }` to create an array with `n` items, and the optional `callback` (called with the item and its index) to set each item, for example `{ _function: { __args: 1 } }` creates `[0, 1, ..., n - 1]`. Combine it with `_array.fill` to create an array of `n` copies of a value. ## _array.includes ``` (arguments: {on: any[], value: any}): boolean (arguments: [on: any[], value: any]): boolean ``` The `_array.includes` method determines whether an array [includes a certain value among its entries](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes), returning `true` or `false` as appropriate. ## _array.indexOf ``` (arguments: {on: any[], value: any}): number (arguments: [on: any[], value: any]): number ``` The `_array.indexOf` method returns the [first index at which a given element can be found](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf) in the array, or `-1` if it is not present. ## _array.join ``` (arguments: {on: any[], separator?: string}): string (arguments: [on: any[], separator?: string]): string ``` The `_array.join` method returns [a string by concatenating all of the elements in an array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join), separated by commas or a specified `separator` string. If the array has only one item, then that item will be returned without using the separator. ## _array.lastIndexOf ``` (arguments: {on: any[], value: any}): number (arguments: [on: any[], value: any]): number ``` The `_array.lastIndexOf` method returns the [last index at which a given element can be found](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf) in the array, or -1 if it is not present. ## _array.length ``` (array: any[]}): number ``` The `_array.length` method returns the [number of elements](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/length) in the array. ## _array.map ``` (arguments: { on: any[], callback: function, }): any[] (arguments: [ on: any[], callback: function, ]): any[] ``` The `_array.map` method returns an array populated with the results of [calling a provided function on every element](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) in the provided array. ## _array.pop ``` (array: any[]): any[] ``` The `_array.pop` method returns the array [with its last item removed](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop). Unlike the JavaScript method, it returns the changed array, not the removed item; use `_array.at` with index `-1` to get the last item. ## _array.push ``` (arguments: { on: any[], items: any[] }): any[] (arguments: [ on: any[], ...items: any[] ]): any[] ``` The `_array.push` method returns the array [with the items added to the end](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push). Unlike the JavaScript method, it returns the changed array, not its new length. ## _array.reduce ``` (arguments: { on: any[], callback: function, initialValue?: any }): any (arguments: [ on: any[], callback: function, initialValue?: any ]): any ``` The `_array.reduce` method [executes a reducer function on each element of the array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce), resulting in single output value. #### Examples The simplest example would probably be adding all the elements in an array: ```yaml sum: _array.reduce: on: [1, 2, 3, 4] callback: _function: __sum: - __args: 0 - __args: 1 ``` This will return `sum: 10` You can start off by counting from 10 by specifying an `initialValue` for the reducer: ```yaml sum: _array.reduce: on: [1, 2, 3, 4] callback: _function: __sum: - __args: 0 - __args: 1 initialValue: 10 ``` This will return `sum: 20` You can use the index of the array element to add some logic to your `callback`. For instance, when you reach index 2 of your array (the 3rd entry), add 100 instead of the current element value: ```yaml sum: _array.reduce: on: [1, 2, 3, 4] callback: _function: __sum: - __args: 0 - __if: test: __eq: - __args: 2 - 2 then: 100 else: __args: 1 ``` This will return `sum: 107` ## _array.reduceRight ``` (arguments: { on: any[], callback: function, initialValue?: any }): any (arguments: [ on: any[], callback: function, initialValue?: any ]): any ``` The `_array.reduceRight` method [applies a function against an accumulator and each value of the array (from right-to-left)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/ReduceRight) to reduce it to a single value. ## _array.reverse ``` (array: any[]}): any[] ``` The `_array.reverse` method [reverses](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse) an array. ## _array.shift ``` (array: any[]): any[] ``` The `_array.shift` method returns the array [with its first item removed](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift). Unlike the JavaScript method, it returns the changed array, not the removed item; use `_array.at` with index `0` to get the first item. ## _array.slice ``` (arguments: { on: any[], start?: number, end?: number }): number (arguments: [ on: any[], start?: number, end?: number ]): number ``` The `_array.slice` method returns [a portion of an array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) selected from `start` to `end` (end not included) where `start` and `end` represent the index of items in that array. ## _array.some ``` (arguments: { on: any[], callback: function, }): boolean (arguments: [ on: any[], callback: function, ]): boolean ``` The `_array.some` method tests whether [at least one element in the array passes the test](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) implemented by the provided function. It returns a Boolean value. ## _array.sort ``` (arguments: {on: any[]}): number ``` The `_array.sort` method [sorts](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) the elements of an array. The sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values. ## _array.splice ``` (arguments: { on: any[], start: number, deleteCount?: number insert: any[] }): number (arguments: { on: any[], start: number, deleteCount?: number, insert: any[] }): number ``` The `_array.splice` method [changes the contents of an array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) by removing or replacing existing elements and/or adding new elements. ## _array.unshift ``` (arguments: { on: any[], items: any[] }): any[] (arguments: [ on: any[], ...items: any[] ]): any[] ``` The `_array.unshift` method returns the array [with the items added to the start](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift). Unlike the JavaScript method, it returns the changed array, not its new length. --- url: https://docs.lowdefy.com/md/operators/_base64.md --- # _base64 The `_base64` operator converts strings to and from [base64](https://en.wikipedia.org/wiki/Base64) format. > This operator can be used as a [`_build`](/_build) operator method. # Operator methods: ## _base64.decode ``` (value: string): string ``` The `_base64.decode` method decodes base64 or base64url encoded content into a UTF-8 string. In the browser, it throws an error if the decoded bytes are not valid UTF-8. #### Arguments ###### string The string to decode. #### Examples ###### Decode a base64 string: ```yaml _base64.decode: SGVsbG8= ``` Returns: `"Hello"`. ## _base64.encode ``` (value: string): string ``` The `_base64.encode` method base64 encodes the UTF-8 bytes of a string. #### Arguments ###### string The string to encode. #### Examples ###### Encode a string as base64: ```yaml _base64.encode: Hello ``` Returns: `"SGVsbG8="`. --- url: https://docs.lowdefy.com/md/operators/_boolean.md --- # _boolean ``` (value: any): boolean ``` The `_boolean` operator coerces the input to a boolean using javascript [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) and [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) rules. It is equivalent to a double negation (`_not` of `_not`), or the javascript `!!value` expression. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### any #### Examples ###### Coerce a truthy value to `true`: ```yaml _boolean: 100 ``` Returns: `true` ###### Coerce a falsy value to `false`: ```yaml _boolean: null ``` Returns: `false` ###### Coerce an empty string to `false`: ```yaml _boolean: '' ``` Returns: `false` --- url: https://docs.lowdefy.com/md/operators/_build.md --- # _build The `_build` operator evaluates at build time. This allows looping over a list in a JSON file generated by another application to calculate a value at build time, or to read environment variables. # Operator methods: ## _build.env ``` (key: string): any (all: boolean): any ``` The `_build.env` method reads from your environment variables. The return value of `_build.env` will form part of the build output, so the [`_secret`](/_secret) operator should rather be used for reading any secrets. #### Examples ###### Read environment from environment variables: With the following development and production environment variables, we can use the `_build.env` method to determine which environment we are in. `Development environment .env` ``` MY_ENVIRONMENT="development" ``` `Production environment` ``` MY_ENVIRONMENT="production" ``` ```yaml _build.env: MY_ENVIRONMENT ``` This will return "development" in the development environment and "production" in the production environment. ## _build.[operator] The `_build` operator can be used to evaluate normal operators at build time. Note that when using an operator as a method, the `_` gets left out of the original operator. For example, when using `_and` as a method, we write `_build.and`. When using an operator which has methods itself, we can just chain the methods: `_array.map` as a method becomes `_build.array.map`. The following operators can be used during the build: - [_and](/_and) - [_app](/_app) - [_args](/_args) - [_array](/_array) - [_base64](/_base64) - [_date](/_date) - [_divide](/_divide) - [_eq](/_eq) - [_function](/_function) - [_get](/_get) - [_gt](/_gt) - [_gte](/_gte) - [_hash](/_hash) - [_hmac](/_hmac) - [_if](/_if) - [_if_none](/_if_none) - [_intl](/_intl) - [_json](/_json) - [_log](/_log) - [_lt](/_lt) - [_lte](/_lte) - [_math](/_math) - [_ne](/_ne) - [_not](/_not) - [_number](/_number) - [_object](/_object) - [_operator](/_operator) - [_or](/_or) - [_product](/_product) - [_random](/_random) - [_regex](/_regex) - [_string](/_string) - [_subtract](/_subtract) - [_sum](/_sum) - [_switch](/_switch) - [_type](/_type) - [_uri](/_uri) #### Examples ###### Use the app slug as a map key at build time: The [`_app`](/_app) operator also resolves at build time, so `_build.app: slug` can supply a key for a build-time map. Because the key must be a string when `_build.object.fromEntries` runs, the runtime `_app` form would arrive too late here. ```yaml _build.object.fromEntries: - - _build.app: slug - enabled: true ``` Returns (when `slug` is `my-app`): ```yaml my-app: enabled: true ``` ###### Check environment variables to determine which url to link to: Use the `Link` action to link to a production url if the build is a production build in Vercel (the `VERCEL_ENV` variable will be set to "production"), otherwise link to a preview url. ```yaml id: link_to_website type: Link params: url: _build.if: test: _build.eq: - _build.env: VERCEL_ENV - production then: "https://www.productionsite.com" else: "https://productionsite-git-develop-org.vercel.app" ``` ###### Loop over a static list at build time: Let's say we have a static list of fruit and their sales in a file called `fruit_sales.json` which is generated by another application. We can map over this list at build time to return a list of the fruit and their total sales. `fruit_sales.json` ```json [ { "name": "Apples", "sales": [ 15, 20, 10, 25 ] }, { "name": "Bananas", "sales": [ 20, 5, 25, 17 ] }, { "name": "Oranges", "sales": [ 21, 12, 31, 40 ] } ] ``` ```yaml _build.array.map: on: _ref: fruit_sales.json callback: _build.function: name: __build.args: 0.name total_sales: __build.sum: __build.args: 0.sales ``` Returns: ``` - name: Apples total_sales: 70 - name: Bananas total_sales: 67 - name: Oranges total_sales: 104 ``` --- url: https://docs.lowdefy.com/md/operators/_change_case.md --- # _change_case The `_change_case` operator uses the [`change-case`](https://www.npmjs.com/package/change-case) package to transform an input between camelCase, PascalCase, Capital Case, snake_case, param-case, CONSTANT_CASE and others. #### Arguments The `_change_case` operator takes `on` and `options` as arguments. The `on` argument is the input to be transformed and can be a `string`, `array` or `object`. The `options` argument is an object with the following properties: - `convertKeys`: Only used when `on` is an object. Toggles whether the object keys are converted to the new case (`true`) or left as-is (`false`). Default `false`. - `convertValues`: Only used when `on` is an object. Toggles whether the object values are converted to the new case (`true`) or left as-is (`false`). Default `true`. - `delimiter`: Value used between words (e.g. " "). - `locale`: Lower/upper according to specified locale, defaults to host environment. Set to false to disable. - `mergeAmbiguousCharacters`: By default, pascalCase and snakeCase separate ambiguous characters with _. To merge them instead, set mergeAmbiguousCharacters to true. - `prefixCharacters``: A string that specifies characters to retain at the beginning of the string. Defaults to "". - `split`: A function to define how the input is split into words. - `suffixCharacters``: A string that specifies characters to retain at the end of the string. Defaults to "". The `on` argument behaves as follows: ###### string Transforms the case of the string. ###### array Transform the case of each string in the array. ###### object Transforms the case of the values (when `options.convertValues: true`) and keys (when `options.convertKeys: true`) of the object. > Note that the `key` and `value` pairs must be strings. #### Examples ###### String with no options: ```yaml _change_case.capitalCase: on: 'foo bar' ``` Returns: `"Foo Bar"` ###### Array with no options: ```yaml _change_case.capitalCase: on: - 'foo' - 'bar' ``` Returns: `["Foo", "Bar"]` ###### Object with no options: ```yaml _change_case.capitalCase: on: foo: 'bar' ``` Returns: `{ "foo": "Bar" }` ###### Object with options.convertKeys: true: ```yaml _change_case.capitalCase: on: foo: 'bar' options: convertKeys: true ``` Returns: `{ "Foo": "Bar" }` ###### Object with options.convertValues: false: ```yaml _change_case.capitalCase: on: foo: 'bar' options: convertValues: false ``` Returns: `{ "foo": "bar" }` ###### Object with options.SPLT: ```yaml _change_case.capitalCase: on: this123is_an example string options: split: _function: __string.split: - __string.replace: - __args: 0 - '123' - '_' - '_' ``` Returns: `This Is An example string` # Operator methods: ## _change_case.camelCase ``` ({on: string, options: object}): string ([on: string, options: object]): string ({on: object, options: object}): object ([on: object, options: object]): object ({on: array, options: object}): array ([on: array, options: object]): array ``` The `_change_case.camelCase` method transforms `on` into a string with the separator denoted by the next word capitalized. #### Examples ###### Transform snake_case variable to camelCase: ```yaml _change_case.camelCase: on: 'my_variable' ``` Returns: `"myVariable"` ## _change_case.capitalCase ``` ({on: string, options: object}): string ([on: string, options: object]): string ({on: object, options: object}): object ([on: object, options: object]): object ({on: array, options: object}): array ([on: array, options: object]): array ``` The `_change_case.capitalCase` method transforms `on` into a space separated string with each word capitalized. #### Examples ###### Transform snake_case variable to capitalCase: ```yaml _change_case.capitalCase: on: 'my_variable' ``` Returns: `"My Variable"` ## _change_case.constantCase ``` ({on: string, options: object}): string ([on: string, options: object]): string ({on: object, options: object}): object ([on: object, options: object]): object ({on: array, options: object}): array ([on: array, options: object]): array ``` The `_change_case.constantCase` method transforms `on` into upper case string with an underscore between words. #### Examples ###### Transform camelCase variable to constantCase: ```yaml _change_case.constantCase: on: 'myVariable' ``` Returns: `"MY_VARIABLE"` ## _change_case.dotCase ``` ({on: string, options: object}): string ([on: string, options: object]): string ({on: object, options: object}): object ([on: object, options: object]): object ({on: array, options: object}): array ([on: array, options: object]): array ``` The `_change_case.dotCase` method transforms `on` into a lower case string with a period between words. #### Examples ###### Transform camelCase variable to dotCase: ```yaml _change_case.dotCase: on: 'myVariable' ``` Returns: `"my.variable"` ## _change_case.kebabCase ``` ({on: string, options: object}): string ([on: string, options: object]): string ({on: object, options: object}): object ([on: object, options: object]): object ({on: array, options: object}): array ([on: array, options: object]): array ``` The `_change_case.kebabCase` method transforms `on` into a lower cased string with dashes between words. #### Examples ###### Transform snake_case variable to kebabCase: ```yaml _change_case.kebabCase: on: 'my_variable' ``` Returns: `"my-variable"` ## _change_case.noCase ``` ({on: string, options: object}): string ([on: string, options: object]): string ({on: object, options: object}): object ([on: object, options: object]): object ({on: array, options: object}): array ([on: array, options: object]): array ``` The `_change_case.noCase` method transforms `on` into a lower cased string with spaces between words. #### Examples ###### Transform camelCase variable to noCase: ```yaml _change_case.noCase: on: 'myVariable' ``` Returns: `"my variable"` ## _change_case.pascalCase ``` ({on: string, options: object}): string ([on: string, options: object]): string ({on: object, options: object}): object ([on: object, options: object]): object ({on: array, options: object}): array ([on: array, options: object]): array ``` The `_change_case.pascalCase` method transforms `on` into a string of capitalized words without separators. #### Examples ###### Transform snake_case variable to pascalCase: ```yaml _change_case.pascalCase: on: 'my_variable' ``` Returns: `"MyVariable"` ## _change_case.pascalSnakeCase ``` ({on: string, options: object}): string ([on: string, options: object]): string ({on: object, options: object}): object ([on: object, options: object]): object ({on: array, options: object}): array ([on: array, options: object]): array ``` The `_change_case.pascalSnakeCase` method transforms `on` into a string of capitalized words with underscores between words. #### Examples ###### Transform snake_case variable to pascalSnakeCase: ```yaml _change_case.pascalCase: on: 'my_variable' ``` Returns: `"My_Variable"` ## _change_case.pathCase ``` ({on: string, options: object}): string ([on: string, options: object]): string ({on: object, options: object}): object ([on: object, options: object]): object ({on: array, options: object}): array ([on: array, options: object]): array ``` The `_change_case.pathCase` method transforms `on` into a lower case string with slashes between words. #### Examples ###### Transform snake_case variable to pathCase: ```yaml _change_case.pathCase: on: 'my_variable' ``` Returns: `my/variable` ## _change_case.sentenceCase ``` ({on: string, options: object}): string ([on: string, options: object]): string ({on: object, options: object}): object ([on: object, options: object]): object ({on: array, options: object}): array ([on: array, options: object]): array ``` The `_change_case.sentenceCase` method transforms `on` into a lower case with spaces between words, then capitalize the first word. #### Examples ###### Transform snake_case variable to sentenceCase: ```yaml _change_case.sentenceCase: on: 'my_variable' ``` Returns: `"My variable"` ## _change_case.snakeCase ``` ({on: string, options: object}): string ([on: string, options: object]): string ({on: object, options: object}): object ([on: object, options: object]): object ({on: array, options: object}): array ([on: array, options: object]): array ``` The `_change_case.snakeCase` method transforms `on` into a lower case string with underscores between words. #### Examples ###### Transform sentence case variable to snakeCase: ```yaml _change_case.snakeCase: on: 'My variable' ``` Returns: `"my_variable"` ## _change_case.trainCase ``` ({on: string, options: object}): string ([on: string, options: object]): string ({on: object, options: object}): object ([on: object, options: object]): object ({on: array, options: object}): array ([on: array, options: object]): array ``` The `_change_case.trainCase` method transforms `on` into a dash separated string of capitalized words. #### Examples ###### Transform snake_case variable to trainCase: ```yaml _change_case.trainCase: on: 'my_variable' ``` Returns: `"My-Variable"` --- url: https://docs.lowdefy.com/md/operators/_credential.md --- # _credential ``` (value: any): any ``` The `_credential` operator marks a value a routine makes at runtime as a credential, and returns it unchanged. Use it for a new API key, a token, or a secret derived with [`_hmac`](/_hmac): values the server does not hold as a [`_secret`](/_secret), so the log scrub would not know them. For the rest of the request, every server log line and Sentry event shows `[REDACTED]` in place of the value, including the debug lines that print `:set_state`, `:return`, step results and an error's `received`, and the request's Sentry transaction, which is sent after the response. An endpoint the routine calls through a `CallApi` step with `detached: true` runs as a new request, and its logs scrub the values the dispatcher marked before the call too. The routine still uses the real value, and the caller still gets it. Marked values are scrubbed on the development server too, which otherwise logs secrets in full. Every string in the value is marked, so one `_credential` can wrap an object holding several. Strings shorter than 8 characters are not scrubbed, as with secrets. Wrap the value where it is made: a line logged before it is marked still holds it. #### Arguments ###### any The value to mark and return. #### Examples ###### Return a new API key once, without it reaching the logs: ```yaml - ':set_state': key: _credential: _string.concat: - sk_ - _uuid: true - id: save_key_hash type: MongoDBUpdateOne connectionId: apps properties: filter: _id: _payload: app_id update: $set: key_hash: _hash.sha256: _state: key - ':return': key: _state: key ``` Returns: `{ key: "sk_..." }` to the caller. The debug lines for `:set_state` and `:return` show `[REDACTED]`. ###### Mark a secret derived from a root secret: ```yaml _credential: _hmac.sha256: key: _secret: SUPPORT_ROOT_SECRET data: _string.concat: - 'app:' - _payload: app_id ``` Returns: The hex HMAC, which the server logs as `[REDACTED]`. --- url: https://docs.lowdefy.com/md/operators/_cron.md --- # _cron The `_cron` operator works with [cron expressions](https://en.wikipedia.org/wiki/Cron#CRON_expression). It can calculate the next and previous occurrences of a schedule, describe a schedule in a human readable sentence, test if an expression is valid, and return the parsed fields of an expression. A cron expression has five fields, `minute hour day-of-month month day-of-week`, and an optional leading seconds field. For example, `0 9 * * 1` is "At 09:00 AM, only on Monday". The supported syntax is: - `*` for any value, `?` as an alias of `*`, lists like `1,3,5`, ranges like `1-5` and steps like `*/15` or `0-30/10`. - Month names (`JAN` to `DEC`) and day names (`SUN` to `SAT`). Day of week `0` and `7` are both Sunday. - `L` for the last day of the month in the day-of-month field, and `5L` for the last Friday of the month in the day-of-week field. - `#` for the nth weekday of the month, like `1#2` for the second Monday. - The aliases `@yearly`, `@annually`, `@monthly`, `@weekly`, `@daily`, `@hourly`, `@minutely`, `@secondly`, `@weekdays` and `@weekends`. The Jenkins style `H` hash token is not supported, since it would pick a different random time each time the operator is evaluated. `W` (nearest weekday), a year field and `@reboot` are not supported either. A blank expression (`""` or only whitespace) is treated as a missing expression. When both day-of-month and day-of-week are restricted, an occurrence matches either of them, as in standard cron. > `_cron` accepts a wider grammar than the `schedules` of [scheduled endpoints](/api#scheduled-endpoints-cron), which only accept five numeric fields and can not restrict day-of-month and day-of-week together. An expression that `_cron.validate` accepts is not always a valid endpoint schedule. Every method takes the cron expression as a string, or as the `expression` property of an object when more arguments are needed: ```yaml _cron.next: 0 9 * * 1 ``` ```yaml _cron.next: expression: 0 9 * * 1 timezone: Europe/London ``` This operator is provided by the `@lowdefy/operators-cron` plugin package. # Operator methods: ## _cron.next ``` (expression: string): date (arguments: { expression: string, from?: date | string, timezone?: string, count?: integer }): date | date[] ``` The `_cron.next` method returns the next occurrence of a cron expression after the `from` date. An occurrence exactly at `from` is not included. When `count` is greater than 1, an array of the next `count` occurrences is returned, in chronological order. On a daylight saving time change in the evaluated `timezone`, a time that is skipped when the clocks go forward runs an hour later, and a time that repeats when the clocks go back runs once, at its first occurrence. Schedules that run every hour or more often run through both repeated hours. An error is thrown if the cron expression is missing, is invalid, or never occurs (like `0 0 31 2 *`). #### Arguments ###### expression The cron expression, for example `0 9 * * 1`. Required. ###### from The date to calculate the next occurrence from, as a date or an ISO 8601 date string like `2024-01-01` or `2024-01-01T09:00:00Z`. Other date strings are not accepted, since JavaScript engines parse them differently. Defaults to the current date and time. ###### timezone The [IANA timezone name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones), for example `Europe/London`, that the cron expression is evaluated in. Defaults to the timezone of the environment that evaluates the operator. ###### count The number of occurrences to return, an integer from 1 to 1000. A `count` of 1 returns a single date, a `count` greater than 1 returns an array of dates. Defaults to 1. #### Examples ###### Next occurrence: ```yaml _cron.next: 0 9 * * 1 ``` Returns: The date of the next Monday at 09:00. ###### Next occurrence in a timezone, from a given date: ```yaml _cron.next: expression: 0 9 * * 1 from: _date: 2024-01-01T00:00:00.000Z timezone: UTC ``` Returns: `2024-01-01T09:00:00.000Z` ###### The next three occurrences: ```yaml _cron.next: expression: 0 9 * * 1 from: _date: 2024-01-01T00:00:00.000Z timezone: UTC count: 3 ``` Returns: `[2024-01-01T09:00:00.000Z, 2024-01-08T09:00:00.000Z, 2024-01-15T09:00:00.000Z]` ## _cron.previous ``` (expression: string): date (arguments: { expression: string, from?: date | string, timezone?: string, count?: integer }): date | date[] ``` The `_cron.previous` method returns the previous occurrence of a cron expression before the `from` date. An occurrence exactly at `from` is not included. When `count` is greater than 1, an array of the previous `count` occurrences is returned, most recent first. An error is thrown if the cron expression is missing, is invalid, or never occurs (like `0 0 31 2 *`). #### Arguments ###### expression The cron expression, for example `0 9 * * 1`. Required. ###### from The date to calculate the previous occurrence from, as a date or an ISO 8601 date string like `2024-01-01` or `2024-01-01T09:00:00Z`. Other date strings are not accepted, since JavaScript engines parse them differently. Defaults to the current date and time. ###### timezone The [IANA timezone name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones), for example `Europe/London`, that the cron expression is evaluated in. Defaults to the timezone of the environment that evaluates the operator. ###### count The number of occurrences to return, an integer from 1 to 1000. A `count` of 1 returns a single date, a `count` greater than 1 returns an array of dates. Defaults to 1. #### Examples ###### Previous occurrence: ```yaml _cron.previous: expression: 0 9 * * 1 from: _date: 2024-01-01T00:00:00.000Z timezone: UTC ``` Returns: `2023-12-25T09:00:00.000Z` ###### The previous three occurrences: ```yaml _cron.previous: expression: 0 9 * * 1 from: _date: 2024-01-01T00:00:00.000Z timezone: UTC count: 3 ``` Returns: `[2023-12-25T09:00:00.000Z, 2023-12-18T09:00:00.000Z, 2023-12-11T09:00:00.000Z]` ## _cron.describe ``` (expression: string): string (arguments: { expression: string, locale?: string, verbose?: boolean, use24HourTimeFormat?: boolean }): string ``` The `_cron.describe` method returns a human readable description of a cron expression, like "At 09:00 AM, only on Monday". An empty string is returned when the expression is `null`, `undefined`, `""` or only whitespace, so the method is safe to use in a label that might not have a schedule yet. Any other expression that `_cron.validate` would return `false` for throws an error, including expressions with the `H` hash token and expressions that never occur. #### Arguments ###### expression The cron expression, for example `0 9 * * 1`. Required. ###### locale The locale to describe the expression in, for example `fr`. Defaults to `en`. The supported locale codes are the [cronstrue locales](https://github.com/bradymholt/cRonstrue#supported-locales): `af`, `ar`, `az`, `be`, `bg`, `ca`, `cs`, `da`, `de`, `en`, `es`, `fa`, `fi`, `fr`, `he`, `hr`, `hu`, `id`, `it`, `ja`, `ko`, `my`, `nb`, `nl`, `nn`, `pl`, `pt_BR`, `pt_PT`, `ro`, `ru`, `sk`, `sl`, `sr`, `sv`, `sw`, `tg`, `th`, `tr`, `uk`, `vi`, `zh_CN` and `zh_TW`. Regional codes can be written with a hyphen or an underscore, in any case, so `pt-BR` and `pt_br` are the same as `pt_BR`. An error is thrown for an unsupported locale. All locales are included in the plugin, so there is nothing to install or configure to use them. ###### verbose Describe the expression in a more verbose sentence. Defaults to `false`. ###### use24HourTimeFormat Describe times in the 24 hour format. Defaults to the format used by the locale. #### Examples ###### Describe a schedule: ```yaml _cron.describe: 0 9 * * 1 ``` Returns: `"At 09:00 AM, only on Monday"`. ###### Describe a schedule in the 24 hour format: ```yaml _cron.describe: expression: 0 9 * * 1 use24HourTimeFormat: true ``` Returns: `"At 09:00, only on Monday"`. ###### Describe a schedule in another locale: ```yaml _cron.describe: expression: 0 9 * * 1 locale: fr ``` Returns: `"À 09:00, uniquement le lundi"`. ###### Describe a schedule verbosely: ```yaml _cron.describe: expression: '*/5 * * * *' verbose: true ``` Returns: `"Every 5 minutes, every hour, every day"`. ## _cron.validate ``` (expression: any): boolean (arguments: { expression: any }): boolean ``` The `_cron.validate` method returns `true` when the cron expression is valid, and `false` when it is not. A missing or blank expression, a value that is not a string, an expression with the `H` hash token and an expression that never occurs (like `0 0 31 2 *`) all return `false`. This method never throws, so it can be used to validate a cron expression captured by an input block. `_cron.validate` checks the `_cron` grammar, which is wider than the `schedules` of [scheduled endpoints](/api#scheduled-endpoints-cron) accept. #### Arguments ###### expression The cron expression to validate. Any type is accepted. #### Examples ###### Validate a schedule: ```yaml _cron.validate: 0 9 * * 1 ``` Returns: `true`. ###### Validate user input: ```yaml - id: schedule type: TextInput validate: - status: error message: Not a valid cron expression. pass: _cron.validate: _state: schedule ``` ## _cron.fields ``` (expression: string): object (arguments: { expression: string }): object ``` The `_cron.fields` method returns the parsed fields of a cron expression as an object with a `second`, `minute`, `hour`, `dayOfMonth`, `month` and `dayOfWeek` key. Each field has the list of `values` it matches, and a `wildcard` boolean that is `true` when the field was given as `*`. An error is thrown if the cron expression is missing, is invalid, or never occurs. #### Arguments ###### expression The cron expression, for example `0 9 * * 1`. Required. #### Examples ###### Fields of a schedule: ```yaml _cron.fields: 0 9 * * 1 ``` Returns: ```yaml second: wildcard: false values: [0] minute: wildcard: false values: [0] hour: wildcard: false values: [9] dayOfMonth: wildcard: true values: [1, 2, 3, '...', 31] month: wildcard: true values: [1, 2, 3, '...', 12] dayOfWeek: wildcard: false values: [1] ``` --- url: https://docs.lowdefy.com/md/operators/_date.md --- # _date ``` (dateString: string): date (unixTimestamp: number): date ``` The `_date` operator returns a date object representing a single moment in time. It can take a string in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format, or a number that is the number of milliseconds since 1 January 1970 UTC (the [UNIX epoch](https://en.wikipedia.org/wiki/Unix_time)). `_date` creates and reads dates but cannot format them with a pattern. To format a date for display, or to add, subtract or compare dates, use [`_dayjs`](/_dayjs). > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### string An string in ISO 8601 format representing a date and time. ###### number The number of milliseconds since 1 January 1970 UTC. #### Examples ###### Timestamp: ```yaml _date: 1611837509802 ``` Returns: Thu Jan 28 2021 12:38:29 GMT+0000 ###### ISO 8601 string, only date: ```yaml _date: 2021-01-28 ``` Returns: Thu Jan 28 2021 00:00:00 GMT+0000 ###### ISO 8601 string, date and time: ```yaml _date: 2021-01-28T12:36:03.957Z ``` Returns: Thu Jan 28 2021 12:38:29 GMT+0000 # Operator methods: ## _date.getDate ``` (value: date): number ``` The `_date.getDate` method returns the [day of the month](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getDate) of a date. ## _date.getDay ``` (value: date): number ``` The `_date.getDay` method returns the [day of the week](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getDay) of a date. ## _date.getFullYear ``` (value: date): number ``` The `_date.getFullYear` method returns the [year](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getFullYear) of a date. ## _date.getHours ``` (value: date): number ``` The `_date.getHours` method returns the [hour](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getHours) of a date. ## _date.getMilliseconds ``` (value: date): number ``` The `_date.getMilliseconds` method returns the [milliseconds](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getMilliseconds) of a date. ## _date.getMinutes ``` (value: date): number ``` The `_date.getMinutes` method returns the [minutes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getMinutes) of a date. ## _date.getMonth ``` (value: date): number ``` The `_date.getMonth` method returns the [month](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getMonth) of a date. ## _date.getSeconds ``` (value: date): number ``` The `_date.getSeconds` method returns the [seconds](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getSeconds) of a date. ## _date.getTime ``` (value: date): number ``` The `_date.getTime` method returns the number of [milliseconds since the epoch](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTime), which is defined as the midnight at the beginning of January 1, 1970, UTC. ## _date.getTimezoneOffset ``` (value: date): number ``` The `_date.getTimezoneOffset` method returns the [difference, in minutes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset), between a date as evaluated in the UTC time zone, and the same date as evaluated in the local time zone. ## _date.getUTCDate ``` (value: date): number ``` The `_date.getUTCDate` method returns the [day of the month](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCDate) of a date according to universal time. ## _date.getUTCDay ``` (value: date): number ``` The `_date.getUTCDay` method returns the [day of the week](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCDay) of a date according to universal time. ## _date.getUTCFullYear ``` (value: date): number ``` The `_date.getUTCFullYear` method returns the [year](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCFullYear) of a date according to universal time. ## _date.getUTCHours ``` (value: date): number ``` The `_date.getUTCHours` method returns the [hour](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCHours) of a date according to universal time. ## _date.getUTCMilliseconds ``` (value: date): number ``` The `_date.getUTCMilliseconds` method returns the [milliseconds](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCMilliseconds) of a date according to universal time. ## _date.getUTCMinutes ``` (value: date): number ``` The `_date.getUTCMinutes` method returns the [minutes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCMinutes) of a date according to universal time. ## _date.getUTCMonth ``` (value: date): number ``` The `_date.getUTCMonth` method returns the [month](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCMonth) of a date according to universal time. ## _date.getUTCSeconds ``` (value: date): number ``` The `_date.getUTCSeconds` method returns the [seconds](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCSeconds) of a date according to universal time. ## _date.now ``` (void): date ``` The `_date.now` method returns a date object representing the moment in time it was called. The method can also be used as `_date: now` #### Arguments The `_date.now` method does not take any arguments. #### Examples ###### Get the current date and time: ```yaml _date.now: null ``` ```yaml _date: now ``` Returns: The current date and time. ## _date.parse ``` (value: date): string ``` The `_date.parse` method parses a string representation of a date, and returns the number of [milliseconds](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse) since January 1, 1970, 00:00:00 UTC. ## _date.setDate ``` (arguments: {on: date, dayOfMonth: number}): number (arguments: [on: date, dayOfMonth: number]): number ``` The `_date.setDate` method changes the [day of the month](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setDate) of a date. ## _date.setFullYear ``` (arguments: {on: date, year: number}): number (arguments: [on: date, year: number]): number ``` The `_date.setFullYear` method sets the [full year](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setFullYear) of a date. ## _date.setHours ``` (arguments: {on: date, hours: number}): number (arguments: [on: date, hours: number]): number ``` The `_date.setHours` method sets the [hours](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setHours) of a date. ## _date.setMilliseconds ``` (arguments: {on: date, milliseconds: number}): number (arguments: [on: date, milliseconds: number]): number ``` The `_date.setMilliseconds` method sets the [milliseconds](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setMilliseconds) of a date. ## _date.setMinutes ``` (arguments: {on: date, minutes: number}): number (arguments: [on: date, minutes: number]): number ``` The `_date.setMinutes` method sets the [minutes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setMinutes) of a date. ## _date.setMonth ``` (arguments: {on: date, month: number}): number (arguments: [on: date, month: number]): number ``` The `_date.setMonth` method sets the [month](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setMonth) of a date. ## _date.setSeconds ``` (arguments: {on: date, seconds: number}): number (arguments: [on: date, seconds: number]): number ``` The `_date.setSeconds` method sets the [seconds](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setSeconds) of a date. ## _date.setTime ``` (arguments: {on: date, time: number}): number (arguments: [on: date, time: number]): number ``` The `_date.setTime` method sets the [time](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setTime) represented by a number of milliseconds since January 1, 1970, 00:00:00 UTC. ## _date.setUTCDate ``` (arguments: {on: date, dayOfMonth: number}): number (arguments: [on: date, dayOfMonth: number]): number ``` The `_date.setUTCDate` method changes the [day of the month](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCDate) of a date, based on UTC time. ## _date.setFullYear ``` (arguments: {on: date, year: number}): number (arguments: [on: date, year: number]): number ``` The `_date.setUTCFullYear` method sets the [full year](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCFullYear) of a date, based on UTC time. ## _date.setUTCHours ``` (arguments: {on: date, hours: number}): number (arguments: [on: date, hours: number]): number ``` The `_date.setUTCHours` method sets the [hours](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCHours) of a date, based on UTC time. ## _date.setUTCMilliseconds ``` (arguments: {on: date, milliseconds: number}): number (arguments: [on: date, milliseconds: number]): number ``` The `_date.setUTCMilliseconds` method sets the [milliseconds](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCMilliseconds) of a date, based on UTC time. ## _date.setUTCMinutes ``` (arguments: {on: date, minutes: number}): number (arguments: [on: date, minutes: number]): number ``` The `_date.setUTCMinutes` method sets the [minutes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCMinutes) of a date, based on UTC time. ## _date.setUTCMonth ``` (arguments: {on: date, month: number}): number (arguments: [on: date, month: number]): number ``` The `_date.setUTCMonth` method sets the [month](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCMonth) of a date, based on UTC time. ## _date.setUTCSeconds ``` (arguments: {on: date, seconds: number}): number (arguments: [on: date, seconds: number]): number ``` The `_date.setUTCSeconds` method sets the [seconds](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCSeconds) of a date, based on UTC time. ## _date.toDateString ``` (value: date): string ``` The `_date.toDateString` method returns the [date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toDateString) as a string. ## _date.toISOString ``` (value: date): string ``` The `_date.toISOString` method returns the [date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) as a string in ISO format. ## _date.toJSON ``` (value: date): string ``` The `_date.toJSON` method returns the [date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toJSON) as a string in ISO format. ## _date.toString ``` (value: date): string ``` The `_date.toString` method returns the [date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toString) as a string. ## _date.toTimeString ``` (value: date): string ``` The `_date.toTimeString` method returns the [time](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toTimeString) of a date as a string. ## _date.toUTCString ``` (value: date): string ``` The `_date.toUTCString` method returns the [date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toUTCString) as a string in UTC time. ## _date.UTC ``` (arguments: {year: number, month: number, day: number, hours: number, minutes: number, seconds: number}): number (arguments: [year: number, month: number, day: number, hours: number, minutes: number, seconds: number]): number ``` The `_date.UTC` method accepts [year, month, day, hours, minutes, seconds parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/UTC) of a date but treats them as UTC. ## _date.valueOf ``` (value: date): number ``` The `_date.valueOf` method returns the [primitive value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/valueOf) of a date. --- url: https://docs.lowdefy.com/md/operators/_diff.md --- # _diff # Operator methods: ## _diff.deep ``` (arguments: {lhs: any, rhs: any}): object[] ([lhs: any, rhs: any]): object[] ``` The `_diff.deep` method compares two objects and returns an object that describes the structural differences between the two objects. #### Arguments ###### string The string to decode. #### Examples ###### Compare two objects using named args: ```yaml _diff.deep: lhs: deleted: To be deleted edited: Edit me array: [1] rhs: new: New value edited: Edited array: [1, 2] ``` ```yaml _diff.deep: - deleted: To be deleted edited: Edit me array: [1] - new: New value edited: Edited array: [1, 2] ``` Both return: ```yaml - kind: D path: [deleted] lhs: To be deleted - kind: E path: [edited] lhs: Edit me rhs: Edited - kind: A path: ['array'] index: 1 item: kind: N rhs: 2 - kind: N path: [new] rhs: New value ``` --- url: https://docs.lowdefy.com/md/operators/_divide.md --- # _divide ``` ([numerator: number, denominator: number]): number ``` The `_divide` operator divides two numbers. It takes an array of two numbers as input and returns the first number divided by the second. Dividing by zero will throw an error. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### array An array of two numbers. #### Examples ###### Divide two numbers: ```yaml _divide: - 12 - 4 ``` Returns: `3` ###### Cannot divide by zero: ```yaml _divide: - 1 - 0 ``` Returns: `null` and throws a operator error. --- url: https://docs.lowdefy.com/md/operators/_eq.md --- # _eq ``` ([value1: any, value2: any]): boolean ``` The `_eq` operator tests if two values are equal. It takes an array of two values to test. > The `_eq` operator tests for strict equality, and won't do a deep comparison. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### array An array of two values to compare. #### Examples ###### Two strings: ```yaml _eq: - "Hello" - "Hello" ``` Returns: `true` ###### Two numbers: ```yaml _eq: - _sum: - 3 - 4 - 8 ``` Returns: `false` ###### Arrays are not compared deeply: ```yaml _eq: - [1,2,3] - [1,2,3] ``` Returns: `false` ###### Values from "getter" operators are copies and not equal: ```yaml _eq: - _state: my_object - _state: my_object ``` Returns: `false` --- url: https://docs.lowdefy.com/md/operators/_error.md --- # _error ``` (key: string): any (all: boolean): Error | null (arguments: { all?: boolean, key?: string, default?: any, }): any ``` The `_error` operator returns the error that is being handled: - Inside a server [`:catch`](/:try) routine, it returns the error that sent the routine to the `:catch`. - Inside a client [`catch`](/events-and-actions) action list, it returns the error that sent the event to the `catch` list. - Outside a catch, it returns `null`, the way [`_item`](/_item) does outside a `:for`. Reading a key outside a catch returns `null`, or the `default` when one is given. On the server, `_error` resolves lexically. It returns the error of the innermost enclosing `:catch`. After an inner `:try` finishes, the outer `:catch` reads its own error again. A `:finally` reads the error of the `:catch` that encloses its `:try`, or `null` if no `:catch` does. Each [`:parallel`](/:parallel) branch reads its own error. [`:reject`](/:reject) does not trigger `:catch`, so `_error` never holds a reject. Dot notation is supported for reading fields of the error, such as `_error: statusCode` or `_error: cause.message`. ##### The error on the server Inside a `:catch`, `_error` is an Error rebuilt from the caught error with only these fields: - `name`: The error class name, such as `RequestError`. - `message`: The real error message, with the values of known secrets (`_secret` values, `CRON_SECRET`, `BETTER_AUTH_SECRET` and values marked with [`_credential`](/_credential)) replaced by `[REDACTED]`. - `code`: The error's `code`, if it has one. - `statusCode`: The error's HTTP status, if it has one. - `handled`: `true` once the server has logged the error. - `retryAfter`: On a `ServiceError`, the wait the service asked for before a retry (seconds or an HTTP date), if it sent one. - `cause`: The error that caused this one, with the same fields, down the chain. A caught `UserError` (from a [`:throw`](/:throw) or a plugin) also keeps its `:cause` and `metaData`. The error carries no `received`, `source`, `config`, `configKey`, `location` or `stack`. ##### The error on the client Inside a client `catch` list, `_error` is the error the browser holds: - An error from the server (a failed `Request` or `CallAPI`) is the error as the browser received it: `name`, `message`, `code`, `statusCode` and `requestId`. The `message` is "Something went wrong." (the [`server.genericError`](/i18n) message) unless the author wrote it with `:throw` or `:reject`, it comes from a failed `ValidateSchema` step, or it is a plugin's `UserError`. - An error raised in the browser (an action or operator that failed on the page) keeps its real message, and carries `actionId`, the id of the action that failed. ##### Sending the error on Inside the routine, `_error: message` and `_error: statusCode` read the real values. When a routine sends the whole error to the client, the client receives it with the generic message, keeping `name`, `code` and `statusCode`. This applies when the whole `_error` is used as a `:cause`, as a [`:throw`](/:throw) message, or inside a [`:return`](/:return) value. To show the user the real message, send it by name: `:throw: { _error: message }`. Sent to a plugin as data, such as `body: { error: { _error: true } }` in an `AxiosHttp` request or a document in a MongoDB insert, the error records no message. Name the fields to send them: ```yaml error: name: _error: name message: _error: message statusCode: _error: statusCode ``` ##### `code` and `statusCode` `code` and `statusCode` follow one rule across connection libraries. `code` is the error's own `code`, such as `ERR_BAD_REQUEST` from AxiosHttp or `11000` from MongoDB. `statusCode` is the first number among the error's `statusCode`, `status` and `response.status`. A request error takes both from the error the connection threw, so `_error: statusCode` reads the HTTP status of a failed `AxiosHttp` request without reading the cause. ##### Service errors and rate limits A request that fails because of the service it calls, not its config, is a `ServiceError`: a network failure, a timeout, a 5xx response, or a rate limit (HTTP 429, including a provider's throttling error). Every other request failure is a `RequestError`. A routine can tell a rate limit from a fault and try again later: ```yaml - :try: - id: summarize type: GenerateText connectionId: claude properties: model: claude-haiku-4-5 prompt: _payload: text :catch: - :if: _eq: [{ _error: statusCode }, 429] :then: - :return: status: busy - :throw: Summary failed ``` #### Arguments ###### string If the `_error` operator is called with a string argument, the value of that field of the error is returned. If the field is not found, or there is no error, `null` is returned. Dot notation is supported. ###### boolean If the `_error` operator is called with boolean argument `true`, the whole error is returned, or `null` outside a catch. ###### object - `all: boolean`: If `all` is set to `true`, the whole error is returned, or `null` outside a catch. One of `all` or `key` are required. - `key: string`: The value of that field of the error is returned. If the field is not found, or there is no error, `null` or the specified default value is returned. Dot notation is supported. One of `all` or `key` are required. - `default: any`: A value to return if the `key` is not found. By default, `null` is returned. #### Examples ###### Branch on the HTTP status in a server `:catch`: ```yaml - :try: - id: get_customer type: AxiosHttp connectionId: crm properties: url: /customers params: id: _payload: id :catch: - :if: _eq: [{ _error: statusCode }, 404] :then: - :reject: Customer not found - :throw: Customer lookup failed ``` A `404` from the CRM rejects with a message for the user. Any other failure throws a new error, which the user sees as "Customer lookup failed". ###### Rethrow with the real message: ```yaml - :try: - id: charge type: AxiosHttp connectionId: payments properties: url: /charges method: post data: amount: _payload: amount :catch: - :throw: _string.concat: - 'Payment failed: ' - _error: message ``` The user sees the real error message after `Payment failed: `. Without naming `message`, `:throw: { _error: true }` would show "Something went wrong.". ###### Log the error with a default: ```yaml - :try: - id: sync_orders type: MongoDBInsertMany connectionId: orders properties: docs: _payload: orders :catch: - :log: message: Order sync failed code: _error: key: code default: unknown reason: _error: message :level: warn - :return: synced: false ``` ###### Branch on the HTTP status in a client `catch` list: ```yaml - id: save_button type: Button properties: title: Save events: onClick: try: - id: save type: Request params: save_customer messages: error: false catch: - :if: _eq: - _error: statusCode - 409 :then: - id: conflict_message type: DisplayMessage params: status: warning content: Someone else changed this customer. Reload the page and try again. :else: - id: error_message type: DisplayMessage params: status: error content: Could not save the customer. ``` `messages.error: false` turns off the request's own error message, so the `catch` list decides what the user sees. --- url: https://docs.lowdefy.com/md/operators/_event.md --- # _event ``` (key: string): any (all: boolean): any (arguments: { all?: boolean, key?: string, default?: any }): any ``` The `_event` operator gets a value from the `event` object. The `event` object is a data object provided to an [`action`](/events-and-actions) by an [`event`](/events-and-actions). This object is also available to a [`request or connection`](/connections-and-requests) called by the [`Request`](/Request) action. #### Arguments ###### string If the `_event` operator is called with a string argument, the value of the key in the `event` object is returned. If the value is not found, `null` is returned. Dot notation and [block list indexes](/lists) are supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. ###### boolean If the `_event` operator is called with boolean argument `true`, the entire `event` object is returned. ###### object - `all: boolean`: If `all` is set to `true`, the entire `event` object is returned. One of `all` or `key` are required. - `key: string`: The value of the key in the `event` object is returned. If the value is not found, `null`, or the specified default value is returned. Dot notation and [block list indexes](/lists) are supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. One of `all` or `key` are required. - `default: any`: A value to return if the `key` is not found in `event`. By default, `null` is returned if a value is not found. #### Examples ###### Get the value of `my_key` from `event`: ```yaml _event: my_key ``` ```yaml _event: key: my_key ``` Returns: The value of `my_key` in `event`. ###### Get the entire `event` object: ```yaml _event: true ``` ```yaml _event: all: true ``` Returns: The entire `event` object. ###### Dot notation: Assuming args: ```yaml my_object: subfield: 'Value' ``` then: ```yaml _event: my_object.subfield ``` ```yaml _event: key: my_object.subfield ``` Returns: `"Value"`. ###### Return a default value if the value is not found: ```yaml _event: key: might_not_exist default: Default value ``` Returns: The value of `might_not_exist`, or `"Default value"`. ###### Block list indices: Assuming `event`: ```yaml my_array: - value: 0 - value: 1 - value: 2 ``` then: ```yaml _event: my_array.$.value ``` Returns: `0` when used from the first block (0th index) in a list. --- url: https://docs.lowdefy.com/md/operators/_function.md --- # _function ``` (definition: any): function ``` The `_function` operator returns a function that evaluates the given operator definition when called. The resulting function can then be used by other operators like `_array.map`, or by blocks (for example as a formatter function). To use a operator in a function definition, the operator name should start with a double underscore, `__`, instead of a single underscore. Operators that start with a single underscore will be evaluated when the function is created, and those that start with a double underscore are evaluated when the function is executed. The arguments passed to the function when it is executed can be accessed with the [`_args`](/_args) operator. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### any The function definition. To use operators here, the operator names should start with a double underscore. #### Examples ###### Map over an array: ```yaml _array.map: on: - firstName: Ted lastName: Mosby - firstName: Robin lastName: Scherbatsky - firstName: Marshall lastName: Eriksen - firstName: Lily lastName: Aldrin - firstName: Barney lastName: Stinson callback: _function: __string.concat: - __args: 0.firstName - ' ' - __args: 0.lastName ``` Returns: ```yaml - Ted Mosby - Robin Scherbatsky - Marshall Eriksen - Lily Aldrin - Barney Stinson ``` --- url: https://docs.lowdefy.com/md/operators/_get.md --- # _get ``` (arguments: { from: any[] | object, key: string | integer, default?: any, }): any ``` The `_get` operator gets a value from the object or array specified in `from`. If the `key` is not found, the provided `default`, or `null` if not specified, are returned. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### object - `from: any[] | object`: __Required__ - The object to get the value from. - `key: string`: __Required__ - The value of the key or array index to get from the `from` object or array. If the value is not found, `null`, or the specified default value is returned. Dot notation and [block list indexes](/lists) are supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. - `default: any`: A value to return if the `key` is not found in `from`. By default, `null` is returned if a value is not found. #### Examples ###### Get the value of a key from an object: ```yaml _get: from: name: George age: 22 key: name ``` Returns: `"George"`. ###### Use _get to as a switch statement to choose an Icon name: ```yaml _get: key: _state: status from: new: add-circle escalated: CircleAlert investigation_started: Wrench client_contacted: Volume2 awaiting_confirmation: ThumbsUp closed: Ban ``` Returns: The icon corresponding to the status in state. ###### Get from an array (arrays are `0` indexed): ```yaml _get: from: - id: 1 name: Joe - id: 2 Name: Dave key: 0.name ``` Returns: `1`. ###### Dot notation: ```yaml _get: from: my_object: subfield: 'Value' key: my_object.subfield ``` Returns: `"Value"`. ###### Return a default value if the value is not found: ```yaml _get: from: value1: 1 key: value2 default: 99 ``` Returns: `99`. ###### Block list indices: Assuming `state`: ```yaml _get: from: my_array: - value: 0 - value: 1 - value: 2 key: my_array.$.value ``` Returns: `0` when used from the first block (0th index) in a list. --- url: https://docs.lowdefy.com/md/operators/_global.md --- # _global ``` (key: string): any (all: boolean): any (arguments: { all?: boolean, key?: string, default?: any, }): any ``` The `_global` operator gets a value from the [`global`](/page-and-app-state) object. The `global` object is a shared data object that can be accessed from any page in the app. #### Arguments ###### string If the `_global` operator is called with a string argument, the value of the key in the `global` object is returned. If the value is not found, `null` is returned. Dot notation and [block list indexes](/lists) are supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. ###### boolean If the `_global` operator is called with boolean argument `true`, the entire `global` object is returned. ###### object - `all: boolean`: If `all` is set to `true`, the entire `global` object is returned. One of `all` or `key` are required. - `key: string`: The value of the key in the `global` object is returned. If the value is not found, `null`, or the specified default value is returned. Dot notation and [block list indexes](/lists) are supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. One of `all` or `key` are required. - `default: any`: A value to return if the `key` is not found in `global`. By default, `null` is returned if a value is not found. #### Examples ###### Get the value of `my_key` from `global`: ```yaml _global: my_key ``` ```yaml _global: key: my_key ``` Returns: The value of `my_key` in `global`. ###### Get the entire `global` object: ```yaml _global: true ``` ```yaml _global: all: true ``` Returns: The entire `global` object. ###### Dot notation: Assuming global: ```yaml my_object: subfield: 'Value' ``` then: ```yaml _global: my_object.subfield ``` ```yaml _global: key: my_object.subfield ``` Returns: `"Value"`. ###### Return a default value if the value is not found: ```yaml _global: key: might_not_exist default: Default value ``` Returns: The value of `might_not_exist`, or `"Default value"`. ###### Block list indices: Assuming `global`: ```yaml my_array: - value: 0 - value: 1 - value: 2 ``` then: ```yaml _global: my_array.$.value ``` Returns: `0` when used from the first block (0th index) in a list. --- url: https://docs.lowdefy.com/md/operators/_gt.md --- # _gt ``` ([value1: any, value2: any]): boolean ``` The `_gt` operator tests if the first value is greater than the second equal. It takes an array of two values to test. > The `_gt` operator tests using the javascript greater than operator. You can find a description of the algorithm used to compare two values [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Less_than). > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### array An array of two values to compare. #### Examples ###### Two numbers: ```yaml _gt: - 4 - 3 ``` Returns: `true` ```yaml _gt: - 1 - 1 ``` Returns: `false` ```yaml _gt: - _sum: - 3 - 4 - 8 ``` Returns: `false` ###### Two strings: ```yaml _gt: - "a" - "b" ``` Returns: `false` --- url: https://docs.lowdefy.com/md/operators/_gte.md --- # _gte ``` ([value1: any, value2: any]): boolean ``` The `_gte` operator tests if the first value is greater than or equal to the second equal. It takes an array of two values to test. > The `_gte` operator tests using the javascript greater than or equal operator. You can find a description of the algorithm used to compare two values [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Less_than). > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### array An array of two values to compare. #### Examples ###### Two numbers: ```yaml _gte: - 4 - 3 ``` Returns: `true` ```yaml _gte: - 1 - 1 ``` Returns: `true` ```yaml _gte: - _sum: - 3 - 4 - 8 ``` Returns: `false` ###### Two strings: ```yaml _gte: - "a" - "b" ``` Returns: `false` --- url: https://docs.lowdefy.com/md/operators/_hash.md --- # _hash The `_hash` operator generates hashes using various algorithms. > This operator can be used as a [`_build`](/_build) operator method. # Operator methods: ## _hash.md5 ``` (value: string): string ``` The `_hash.md5` method generates the [MD5](https://en.wikipedia.org/wiki/MD5) hash of the input value. #### Arguments ###### string The string to hash. #### Examples ###### Hash a string: ```yaml _hash.md5: Hello World! ``` Returns: `"ed076287532e86365e841e92bfc50d8c"`. ## _hash.sha1 ``` (value: string): string ``` The `_hash.sha1` method generates the [SHA1](https://en.wikipedia.org/wiki/SHA1) hash of the input value. #### Arguments ###### string The string to hash. #### Examples ###### Hash a string: ```yaml _hash.sha1: Hello World! ``` Returns: `"2ef7bde608ce5404e97d5f042f95f89f1c232871"`. ## _hash.sha256 ``` (value: string): string ``` The `_hash.sha256` method generates the [SHA256](https://en.wikipedia.org/wiki/SHA256) hash of the input value. #### Arguments ###### string The string to hash. #### Examples ###### Hash a string: ```yaml _hash.sha256: Hello World! ``` Returns: `"7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069"`. ## _hash.sha512 ``` (value: string): string ``` The `_hash.sha512` method generates the [SHA512](https://en.wikipedia.org/wiki/SHA512) hash of the input value. #### Arguments ###### string The string to hash. #### Examples ###### Hash a string: ```yaml _hash.sha512: Hello World! ``` Returns: `"861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8"`. ## _hash.ripemd160 ``` (value: string): string ``` The `_hash.ripemd160` method generates the [RIPEMD-160](https://en.wikipedia.org/wiki/RIPEMD160) hash of the input value. #### Arguments ###### string The string to hash. #### Examples ###### Hash a string: ```yaml _hash.ripemd160: Hello World! ``` Returns: `"8476ee4631b9b30ac2754b0ee0c47e161d3f724c"`. --- url: https://docs.lowdefy.com/md/operators/_hmac.md --- # _hmac The `_hmac` operator computes an [HMAC](https://en.wikipedia.org/wiki/HMAC) of a string under a key, returned as lower-case hex. Use it to sign what a routine sends (a webhook signature header, for example) or to derive a secret from a root secret. The key is usually a [`_secret`](/_secret), so `_hmac` runs only on the server and never reaches the browser. The output of one call works as the `key` of the next, so one root secret can derive a secret per app, and that secret can sign a message. To check a signature a sender sent, do not compare it to an `_hmac` output with `_eq`: a plain comparison stops at the first character that differs, so its timing tells a caller how much of a forged signature is right. Check it in a webhook endpoint's `verify` plugin with `crypto.timingSafeEqual` instead (see [Webhook Endpoints](/lowdefy-api)). > This operator can be used as a [`_build`](/_build) operator method. # Operator methods: ## _hmac.sha256 ``` ({ key: string, data: string }): string ``` The `_hmac.sha256` method computes the HMAC-SHA256 of `data` under `key`. Both are read as UTF-8 strings. #### Arguments ###### object - `key: string`: __Required__ - The secret key. - `data: string`: __Required__ - The string to sign. #### Examples ###### Sign a string: ```yaml _hmac.sha256: key: my-secret data: Hello World! ``` Returns: `"ce334046c71c58d87a4c3201d1d14a3e7cdcba95041624dbcb84976a38d1c505"`. ###### Derive a per-app secret and sign a webhook body with it: ```yaml _hmac.sha256: key: _hmac.sha256: key: _secret: SUPPORT_ROOT_SECRET data: _string.concat: - 'app:' - _payload: app_id data: _string.concat: - _payload: timestamp - . - _payload: body ``` Returns the hex signature of `.` under the app's derived secret. ## _hmac.sha512 ``` ({ key: string, data: string }): string ``` The `_hmac.sha512` method computes the HMAC-SHA512 of `data` under `key`. Both are read as UTF-8 strings. #### Arguments ###### object - `key: string`: __Required__ - The secret key. - `data: string`: __Required__ - The string to sign. #### Examples ###### Sign a string: ```yaml _hmac.sha512: key: my-secret data: Hello World! ``` Returns: `"498943335e4bb157626950ce4f305568a54816fe13ad5d594902f125cbd9a844f52ba9ad21105f4d8cd78907dbe5315c6be2c439688213054e04e065d3582b51"`. --- url: https://docs.lowdefy.com/md/operators/_if.md --- # _if ``` (arguments: {test: boolean, then: any, else: any}): any ``` The `_if` operator returns the `then` argument if it's `test` argument is `true`, and it's `else` argument if it is `false`. Generally other operators are used to evaluate the `test` argument. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### object - `test: boolean`: The boolean result of a test. - `then: any`: The value to return if the test is `true`. - `else: any`: The value to return if the test is `false`. #### Examples ###### Return a value based on a user input: ```yaml _if: test: _eq: - _state: text_input - The password then: The user entered the password else: Access denied ``` Returns: `"The user entered the password"` if the text input's value is `"The password"`, else `"Access denied"` --- url: https://docs.lowdefy.com/md/operators/_if_none.md --- # _if_none ``` ([value: any, replacement: any]): any ``` The `_if_none` operator replaces the input value with an alternative if the value is of a "none-type" like `null` or `undefined`. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### array - First value: The value to test. - Second value: The replacement. #### Examples ###### The value is not replaced if it is not of a none-type: ```yaml _if_none: - Value - Replacement ``` Returns: `Value` ###### The value is replaced if it is of a none-type: ```yaml _if_none: - null - "Replacement" ``` Returns: `"Replacement"` ```yaml _if_none: - _state: does_not_exist # Value in state that does not exist - "Replacement" ``` Returns: `"Replacement"` ```yaml _if_none: - _request: still_loading # _request returns null if the request is loading - [] ``` Returns: `[]` --- url: https://docs.lowdefy.com/md/operators/_index.md --- # _index ``` (key: string): any (key: integer): any (all: boolean): any (arguments: { all?: boolean, key?: string | integer, default?: any }): any ``` The `_index` operator gets a value from the `list indices` array of a block. The `list indices` array is an array of the indices of all [`list`](/lists) block areas which the block is a part of. #### Arguments ###### integer If the `_index` operator is called with a integer argument, the value at that index in the `indices` array is returned. If the value is not found, `null` is returned.. ###### string If the `_args` operator is called with a string argument, the value at that index in the `indices` array is returned. If the value is not found, `null` is returned. ###### boolean If the `_index` operator is called with boolean argument `true`, the entire `indices` array is returned. ###### object - `all: boolean`: If `all` is set to `true`, the entire `indices` array is returned. One of `all` or `key` are required. - `key: string | integer`: The value of the index in the `indices` array is returned. If the value is not found, `null`, or the specified default value is returned. One of `all` or `key` are required. - `default: any`: A value to return if the `key` is not found in `arguments`. By default, `null` is returned if a value is not found. #### Examples ###### Get a index: Assuming the block is in the fourth content area of a `list` block. ```yaml _index: 0 ``` Returns: `3` ###### Get all indices: Assuming two nested list blocks, with the block in the fourth content area of the first `list` block, and the first content area of the nested `list` block. ```yaml _index: true ``` Returns: `[3, 0]` --- url: https://docs.lowdefy.com/md/operators/_input.md --- # _input ``` (key: string): any (all: boolean): any (arguments: { all?: boolean, key?: string, default?: any, }): any ``` The `_input` operator gets a value from the [`input`](/page-and-app-state) object. The `input` is a data object that can be set when linking to a new page using the [`Link`](/link) action, and can be used to set data like a `id` when switching to a new page. Unlike `urlQuery`, the `input` is not visible, and cannot be changed by the user, but if the page is reloaded, the `input` is lost. #### Arguments ###### string If the `_input` operator is called with a string argument, the value of the key in the `input` object is returned. If the value is not found, `null` is returned. Dot notation and [block list indexes](/lists) are supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. ###### boolean If the `_input` operator is called with boolean argument `true`, the entire `input` object is returned. ###### object - `all: boolean`: If `all` is set to `true`, the entire `input` object is returned. One of `all` or `key` are required. - `key: string`: The value of the key in the `input` object is returned. If the value is not found, `null`, or the specified default value is returned. Dot notation and [block list indexes](/lists) are supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. One of `all` or `key` are required. - `default: any`: A value to return if the `key` is not found in `input`. By default, `null` is returned if a value is not found. #### Examples ###### Get the value of `my_key` from `input`: ```yaml _input: my_key ``` ```yaml _input: key: my_key ``` Returns: The value of `my_key` in `input`. ###### Get the entire `input` object: ```yaml _input: true ``` ```yaml _input: all: true ``` Returns: The entire `input` object. ###### Dot notation: Assuming input: ```yaml my_object: subfield: 'Value' ``` then: ```yaml _input: my_object.subfield ``` ```yaml _input: key: my_object.subfield ``` Returns: `"Value"`. ###### Return a default value if the value is not found: ```yaml _input: key: might_not_exist default: Default value ``` Returns: The value of `might_not_exist`, or `"Default value"`. ###### Block list indices: Assuming `input`: ```yaml my_array: - value: 0 - value: 1 - value: 2 ``` then: ```yaml _input: my_array.$.value ``` Returns: `0` when used from the first block (0th index) in a list. --- url: https://docs.lowdefy.com/md/operators/_intl.md --- # _intl The `_intl` operator converts date objects to strings, using a specified format. > This operator can be used as a [`_build`](/_build) operator method. # Operator methods: ## _intl.dateTimeFormat ``` (arguments: { on: date, locale?: string, options?: object }) ``` The `_intl.dateTimeFormat` provides language-sensitive date and time formatting, based on [`Intl.DateTimeFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat). If no locale is provide, the users default locale will be used. #### Arguments ###### object - `on: date`: The date object to format. - `locale: string`: A string with a BCP 47 language tag, or an array of such strings. - `options: object`: [`Intl.DateTimeFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat) options. #### Examples ###### Format a date: ```yaml _intl.dateTimeFormat: on: _date: 2019-06-13 locale: en options: weekday: long year: numeric month: long day: numeric ``` Returns: `"Thursday, June 13, 2019"`. ## _intl.listFormat ``` (arguments: { on: any[], locale?: string, options?: object }) ``` The `_intl.listFormat` provides language-sensitive list formatting, based on [`Intl.ListFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat/ListFormat). If no locale is provide, the users default locale will be used. #### Arguments ###### object - `on: any[]`: The array to format. - `locale: string`: A string with a BCP 47 language tag, or an array of such strings. - `options: object`: [`Intl.ListFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat/ListFormat) options. #### Examples ###### Format a list: ```yaml _intl.listFormat: on: - Motorcycle - Bus - Car locale: fr ``` Returns: `"Motorcycle, Bus et Car"`. ## _intl.numberFormat ``` (arguments: { on: number, locale?: string, options?: object }) ``` The `_intl.numberFormat` provides language-sensitive number formatting, based on [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat). If no locale is provide, the users default locale will be used. #### Arguments ###### object - `on: number`: The number to format. - `locale: string`: A string with a BCP 47 language tag, or an array of such strings. - `options: object`: [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat) options. #### Examples ###### Format a number: ```yaml _intl.numberFormat: on: 13182375813.47422 locale: de ``` Returns: `"13.182.375.813,474"`. ## _intl.relativeTimeFormat ``` (arguments: { on: any, locale?: string, unit: enum, options?: object }) ``` The `_intl.relativeTimeFormat` provides language-sensitive relative time formatting, based on [`Intl.RelativeTimeFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat/RelativeTimeFormat). If no locale is provide, the users default locale will be used. #### Arguments ###### object - `on: number`: The number to format. - `locale: string`: A string with a BCP 47 language tag, or an array of such strings. - `unit: enum`: Unit to use in the relative time internationalized message. Possible values are: `year`, `quarter`, `month`, `week`, `day`, `hour`, `minute`, `second`. Plural forms are also permitted. - `options: object`: [`Intl.RelativeTimeFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat/RelativeTimeFormat) options. #### Examples ###### Format a number: ```yaml _intl.relativeTimeFormat: on: 4 unit: 'days' locale: fr ``` Returns: `"dans 4 jours"`. --- url: https://docs.lowdefy.com/md/operators/_item.md --- # _item ``` (key: string): any ``` The `_item` operator returns the current item value during iteration in [`:for`](/:for) and [`:parallel_for`](/:parallel_for) loop control structures within API routines. The key used with `_item` must match the key defined in the relevant loop control declaration because it also supports nested loops. Dot notation is supported for accessing nested properties. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. #### Arguments ###### string The key name defined in the relevant loop control structure. #### Examples ###### Using in a :for loop: ```yaml routine: - :for: user_id :in: _payload: user_ids :do: - id: update_user type: MongoDBUpdateOne connectionId: users properties: filter: _id: _item: user_id # Access the current user_id update: $set: last_seen: _date: now ``` Returns: The current value from the array being iterated over. ###### Using dot notation to access nested properties: ```yaml routine: - :for: product :in: _payload: products # Array of product objects :do: - id: update_product type: MongoDBUpdateOne connectionId: products properties: filter: _id: _item: product._id # Access the _id field of current product update: $set: name: _item: product.name price: _item: product.price category: _item: product.category last_updated: _date: now - :log: message: 'Processing product' product_name: _item: product.name product_sku: _item: product.sku ``` ###### Using in :parallel_for loop for concurrent processing: ```yaml routine: - id: get_orders type: MongoDBFind connectionId: orders properties: query: status: 'pending' - :parallel_for: order :in: _step: get_orders :do: - id: send_notification type: SendGridMail connectionId: email-service properties: to: _item: order.customer_email template_id: 'order_confirmation' dynamic_template_data: order_id: _item: order._id total: _item: order.total items: _item: order.items ``` ###### Nested loops with multiple _item references: ```yaml routine: - :for: category :in: _payload: categories :do: - :for: product :in: _item: category.products # Access products array from outer loop item :do: - id: update_product type: MongoDBUpdateOne connectionId: products properties: filter: _id: _item: product._id update: $set: category_name: _item: category.name # Access outer loop item category_id: _item: category._id product_rank: _item: product.rank # Access inner loop item ``` ###### Accumulating values with _item: ```yaml routine: - :set_state: total: 0 processed_count: 0 - :for: item :in: _payload: order_items :do: - :set_state: total: _sum: - _state: total - _product: - _item: item.price - _item: item.quantity processed_count: _sum: - _state: processed_count - 1 - :log: processing_item: _item: item.name item_total: _product: - _item: item.price - _item: item.quantity - :return: total: _state: total items_processed: _state: processed_count ``` --- url: https://docs.lowdefy.com/md/operators/_js.md --- # _js > This operator is experimental and may change in future versions. ``` (function: string): any (function: { fn: string, args?: object }): any ``` The `_js` operator enables the use of custom JavaScript logic within Lowdefy configuration where operators are evaluated. The purpose of this operator is to facilitate quick implementation of fast, synchronous functions. Like other operators, these functions are evaluated during page render, thus slow functions can impact app performance. For more advanced logic, or when the use of external dependencies is necessary, instead develop a [custom plugin](/plugins-introduction). #### Using Lowdefy operators in JavaScript Certain Lowdefy operators can be used inside of the JavaScript function block. These operators are available as functions and will take their standard arguments. ###### Client JavaScript function prototype: _Function parameters passed to the operator method._ ```js function ({ actions, args, event, input, location, lowdefyApp, lowdefyGlobal, pathParams, request, state, urlQuery, user }) { // Your JavaScript code here }; ``` The function arguments available to the JavaScript function are: - `actions: function`: Implements the [_actions](/_actions) operator. - `args: object`: Pre-resolved values passed via the object form of `_js` (see below). `undefined` when the string form is used. - `event: function`: Implements the [_event](/_event) operator. - `input: function`: Implements the [_input](/_input) operator. - `location: function`: Implements the [_location](/_location) operator. - `lowdefyApp: function`: Implements the [_app](/_app) operator. - `lowdefyGlobal: function`: Implements the [_global](/_global) operator. - `pathParams: function`: Implements the [_path_params](/_path_params) operator. - `request: function`: Implements the [_request](/_request) operator. - `state: function`: Implements the [_state](/_state) operator. - `urlQuery: function`: Implements the [_url_query](/_url_query) operator. - `user: function`: Implements the [_user](/_user) operator. ###### Server JavaScript function prototype: _Function parameters passed to the operator method._ ```js function ({ args, item, lowdefyApp, payload, secret, state, step, user }) { // Your JavaScript code here }; ``` The function arguments available to the JavaScript function are - `args: object`: Pre-resolved values passed via the object form of `_js` (see below). `undefined` when the string form is used. - `item: function`: Implements the [_item](/_item) operator. - `lowdefyApp: function`: Implements the [_app](/_app) operator. - `payload: function`: Implements the [_payload](/_payload) operator. - `secret: function`: Implements the [_secret](/_secret) operator. - `state: function`: Implements the [_state](/_state) operator. - `step: function`: Implements the [_step](/_step) operator. - `user: function`: Implements the [_user](/_user) operator. #### Passing pre-computed values with `args` The object form of `_js` accepts an `args` object whose values are resolved by the parser **before** the JavaScript function runs. This lets you compute values using any Lowdefy operator (`_state`, `_request`, `_user`, etc.) and consume them inside the function without calling operator methods from JavaScript. #### Arguments ###### string The JavaScript function body, including the function return statement, excluding the function prototype. ###### object An object with the following properties: - `fn: string`: The JavaScript function body (same as the string form). - `args: object`: (optional) Values to inject into the function. Each value is evaluated as a normal Lowdefy expression and made available inside `fn` on the `args` object. #### Examples ###### Perform a calculation: ```js _js: | let x = state('input_1'); let y = state('input_2'); return x + y; ``` ###### Create custom logic based on data from a request: ```js _js: | const products = request('get_products').data?.products ?? []; const laptopsWithRatingGreaterThan4 = products.filter(product => product.category === "laptops" && product.rating > 4 ); if (laptopsWithRatingGreaterThan4.length > 3) { return true; } return false; ``` ###### Chain array methods on request data: ```js _js: | const products = request('get_products').data?.products ?? []; const totalPriceOfPhones = products .filter(product => product.category === "smartphones") .reduce((acc, product) => acc + product.price, 0); return totalPriceOfPhones; ``` ###### Pass pre-computed values with `args`: ```yaml _js: fn: | const { products, target } = args; return products .filter(p => p.category === target) .reduce((a, p) => a + p.price, 0); args: products: _request: get_products.data.products target: smartphones ``` --- url: https://docs.lowdefy.com/md/operators/_json.md --- # _json The `_json` parses and writes JSON strings. > This operator can be used as a [`_build`](/_build) operator method. # Operator methods: ## _json.parse ``` (value: string): any ``` The `_json.parse` method parses a JSON string into an object. #### Arguments ###### string The string to parse. #### Examples ###### Parse a JSON string: ```yaml _json.parse: '{"key": "Value", "boolean": true, "array": [1, 2]}' ``` Returns: ``` key: Value boolean: true array: - 1 - 2 ``` ## _json.stringify ``` ({on: any, options?: object}): string ([on: any, options?: object]): string ``` The `_json.stringify` method creates a JSON string from an object. #### Arguments ###### object - `on: any`: The object to stringify. - `options: object`: Optional settings. - `stable: boolean`: If set to true, equal objects will be stringified to the same string. Object keys are sorted. - `space: number | string`: Used to insert whitespace to increase legibility. If a number, it is the number of space character to use. If a string, the string is used as whitespace. The default is `2`. #### Examples ###### Stringify an object as JSON: ```yaml _json.stringify: on: key: Value boolean: true array: - 1 - 2 ``` ```yaml _json.stringify: - key: Value boolean: true array: - 1 - 2 ``` Returns (as a string): ```text '{ "key": "Value", "boolean": true, "array": [ 1, 2 ] }' ``` ###### Stable option: ```yaml _json.stringify: on: key: Value boolean: true array: - 1 - 2 options: stable: true ``` Returns (as a string): ```text '{ "array": [ 1, 2 ], "boolean": true, "key": "Value" }'' ``` ###### Space option: ```yaml _json.stringify: on: key: Value boolean: true array: - 1 - 2 options: space: 0 ``` Returns `'{"key":"Value","boolean":true,"array":[1,2]}'` --- url: https://docs.lowdefy.com/md/operators/_jsonata.md --- # _jsonata ``` (arguments: {on: any, expr: string, bindings?: object}): any ``` The `_jsonata` operator provides a powerful query and transformation language for JSON data using the [JSONata](https://jsonata.org) library. JSONata is a lightweight query and transformation language that allows you to navigate, query, and transform JSON structures with simple and intuitive expressions. #### Arguments ###### object - `on: any`: The input data to evaluate the expression against. - `expr: string`: A JSONata expression string. - `bindings?: object`: Optional bindings object to provide additional variables to the expression. #### Examples ###### Basic arithmetic: ```yaml _jsonata: on: a: 5 b: 3 expr: a + b ``` Returns: `8` ###### String concatenation: ```yaml _jsonata: on: firstName: John lastName: Doe expr: firstName & " " & lastName ``` Returns: `"John Doe"` ###### Array filtering: ```yaml _jsonata: on: items: [1, 2, 3, 4, 5] expr: items[$ > 3] ``` Returns: `[4, 5]` ###### Array mapping: ```yaml _jsonata: on: users: - name: Alice age: 30 - name: Bob age: 25 expr: users.name ``` Returns: `["Alice", "Bob"]` ###### Using bindings for external variables: ```yaml _jsonata: on: price: 100 expr: price * taxRate bindings: taxRate: 1.2 ``` Returns: `120` ###### Aggregation with built-in functions: ```yaml _jsonata: on: items: [1, 2, 3, 4, 5] expr: $sum(items) ``` Returns: `15` ###### Conditional expression: ```yaml _jsonata: on: temperature: 25 expr: temperature > 20 ? "warm" : "cold" ``` Returns: `"warm"` ###### Object transformation: ```yaml _jsonata: on: user: firstName: Jane lastName: Smith email: jane@example.com expr: | { "fullName": user.firstName & " " & user.lastName, "contact": user.email } ``` Returns: ```json { "fullName": "Jane Smith", "contact": "jane@example.com" } ``` ###### Array transformation: ```yaml _jsonata: on: orders: - id: 1 amount: 100 - id: 2 amount: 200 expr: orders.{ "orderId": id, "total": amount * 1.1 } ``` Returns: ```json [ { "orderId": 1, "total": 110 }, { "orderId": 2, "total": 220 } ] ``` ###### Complex transformation with aggregation: ```yaml _jsonata: on: user: firstName: Jane lastName: Smith orders: - total: 100 - total: 200 expr: | { "customer": user.firstName & " " & user.lastName, "totalSpent": $sum(orders.total) } ``` Returns: ```json { "customer": "Jane Smith", "totalSpent": 300 } ``` --- url: https://docs.lowdefy.com/md/operators/_locale.md --- # _locale ``` (key: 'active' | 'default' | 'fallback' | 'supported'): string | object[] ``` The `_locale` operator reads the current locale state. Use it to render language pickers, conditional content per locale, or to display the active locale. The view exposes: - `active: string` — the resolved active locale (e.g. `'de-DE'`). - `default: string` — the configured `config.i18n.defaultLocale`. - `fallback: string` — always `'en-US'`. Lowdefy uses `en-US` as a fixed fallback when a key is missing in the active locale, so plugin and module authors should always ship `en-US` translations. - `supported: object[]` — the list of locales declared in `config.i18n.locales`. Each item has `{ code, label, antd, dayjs }`. Pair with the [`SetLocale`](/SetLocale) action to build a language picker. #### Arguments ###### string One of `active`, `default`, `fallback`, or `supported`. #### Examples ###### Read the active locale: ```yaml _locale: active ``` Returns: e.g. `"de-DE"`. ###### Render a language picker: `_locale: supported` returns `[{ code, label, antd, dayjs }, ...]`. Selector options must be an array of `{ value, label }` pairs — project each descriptor onto that shape with `_array.map`: ```yaml - id: lang_picker type: Selector properties: options: _array.map: on: _locale: supported callback: _function: value: __args: 0.code label: __args: 0.label value: _locale: active events: onChange: - id: set_locale type: SetLocale params: locale: _state: lang_picker ``` ###### Show only when a specific locale is active: ```yaml visible: _eq: - _locale: active - de-DE ``` --- url: https://docs.lowdefy.com/md/operators/_location.md --- # _location ``` (key: string): any ``` The `_location` operator gets a value from the browser [Location](https://developer.mozilla.org/en-US/docs/Web/API/Location) interface. The Location interface represents the location (URL) of the window object it is linked to, thus can return the URL elements of the current window. It can only be used on the web-client (Not in `requests` or `connections`). The following location properties are available. - `basePath: string`: The application base path setting. - `hash: string`: A string containing a '#' followed by the fragment identifier of the URL. - `host: string`: A string containing the host, that is the hostname, a `:`, and the port of the URL. - `hostname: string`: The domain of the URL. - `homePageId: string`: The home page id. - `href: string`: The entire URL string. - `origin: string`: The canonical form of the origin of the specific location. - `pageId: string`: The current page id. On a page with a [`path`](/page-paths) this is still the page's `id`; read its placeholder values with [`_path_params`](/_path_params). - `pathname: string`: A string containing an initial `/`` followed by the path of the URL, not including the query string or fragment. - `port: string`: The port number of the URL - `protocol: string`: The protocol scheme of the URL, mostly `http:` or `https:`. - `search: string`: A string containing a '?' followed by the parameters or "querystring" of the URL. #### Arguments ###### string If called with a string argument, the value of the key in the `location` object is returned. If the value is defined, `null` is returned. #### Examples ###### Get the `origin` from `location`: ```yaml _location: origin ``` Returns: The current `window.location.origin`, in this case 'https://docs.lowdefy.com'. ###### Get the `pathname` from `location`: ```yaml _location: pathname ``` Returns: The current `window.location.pathname`, in this case '/_location'. --- url: https://docs.lowdefy.com/md/operators/_log.md --- # _log ``` (value: any): any ``` The `_log` operator logs its input and returns the value it received. Since it returns the value it received, it can be used to debug without affecting the rest of the configuration. In the browser it logs to the console. On the server it writes an info line to the server log, where secrets and values marked with `_credential` are removed like on every other log line. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### any #### Examples ###### Log the results of a request to the console: ```yaml _log: _request: my_request ``` Returns: The value of the request --- url: https://docs.lowdefy.com/md/operators/_lt.md --- # _lt ``` ([value1: any, value2: any]): boolean ``` The `_lt` operator tests if the first value is less than the second equal. It takes an array of two values to test. > The `_lt` operator tests using the javascript less than operator. You can find a description of the algorithm used to compare two values [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Less_than). > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### array An array of two values to compare. #### Examples ###### Two numbers: ```yaml _lt: - 4 - 3 ``` Returns: `false` ```yaml _lt: - 1 - 1 ``` Returns: `false` ```yaml _lt: - _sum: - 3 - 4 - 8 ``` Returns: `true` #### Two strings: ```yaml _lt: - "a" - "b" ``` Returns: `true` --- url: https://docs.lowdefy.com/md/operators/_lte.md --- # _lte ``` ([value1: any, value2: any]): boolean ``` The `_lte` operator tests if the first value is less than or equal to the second equal. It takes an array of two values to test. > The `_lte` operator tests using the javascript less than or equal to operator. You can find a description of the algorithm used to compare two values [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Less_than). > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### array An array of two values to compare. #### Examples ###### Two numbers: ```yaml _lte: - 4 - 3 ``` Returns: `false` ```yaml _lte: - 1 - 1 ``` Returns: `true` ```yaml _lte: - _sum: - 3 - 4 - 8 ``` Returns: `true` ###### Two strings: ```yaml _lte: - "a" - "b" ``` Returns: `true` --- url: https://docs.lowdefy.com/md/operators/_math.md --- # _math The `_math` operator can be used to run javascript [`Math`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math) methods. The `_math` operator can take arguments in the following forms: ###### No Arguments ```text (void): number ``` Some methods like `_math.PI` take no arguments: ```yaml _math.PI: null ``` Returns: `pi` ###### Single argument ```text (x: number): number ``` Some methods like `_math.round` take a single argument: ```yaml _math.round: 3.14 ``` Returns: `3` ###### Named arguments ```text ({x: number, y: number}): number ([x: number, y: number]): number ``` Some methods like `_math.pow` take an object with named arguments: ```yaml _math.pow: base: 2 exponent: 3 ``` Returns: `8` These methods also accept their arguments as an array: ```yaml _math.pow: - 2 - 3 ``` Returns: `8` ###### Array arguments ```text (values: number[]): number ``` Some methods like `_math.max` take an array of values as arguments: ``` _math.max: - 42 - 99 - 0 ``` Returns: `99` > This operator can be used as a [`_build`](/_build) operator method. # Operator methods: ## _math.abs ``` (x: number): number ``` The `_math.abs` method returns the [absolute value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/abs) of a number. ## _math.acos ``` (x: number): number ``` The `_math.acos` method returns the [arccosine](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acos) (in radians) of a number. ## _math.acosh ``` (x: number): number ``` The `_math.acosh` method returns the [hyperbolic arc-cosine](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acosh) of a number. ## _math.asin ``` (x: number): number ``` The `_math.asin` method returns the [arcsine](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asin) (in radians) of a number. ## _math.atan ``` (x: number): number ``` The `_math.atan` method returns the [arctangent](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan) (in radians) of a number. ## _math.atan2 ``` ({x: number, y: number}): number ([x: number, y: number]): number ``` The `_math.atan2` method returns the [angle in the plane (in radians) between the positive x-axis and the ray from (0,0) to the point (x,y)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2). ## _math.atanh ``` (x: number): number ``` The `_math.atanh` method returns the [hyperbolic arctangent](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atanh) of a number. ## _math.cbrt ``` (x: number): number ``` The `_math.cbrt` method returns the returns the [cube root](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cbrt) of a number. ## _math.ceil ``` (x: number): number ``` The `_math.ceil` method [rounds a number up](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil) to the next largest integer. ## _math.clz32 ``` (x: number): number ``` The `_math.clz32` method returns the [number of leading zero bits in the 32-bit binary representation of a number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32). ## _math.cos ``` (x: number): number ``` The `_math.cos` method returns the [cosine](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cos) of the specified angle, which must be specified in radians ## _math.cosh ``` (x: number): number ``` The `_math.cosh` method returns the [hyperbolic cosine](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cosh) of a number. ## _math.exp ``` (x: number): number ``` The `_math.exp` method returns [`e` (Euler's number) to the power `x`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/exp). ## _math.expm1 ``` (x: number): number ``` The `_math.expm1` method returns [`e` (Euler's number) to the power `x` minus `1`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/expm1). ## _math.floor ``` (x: number): number ``` The `_math.floor` method returns the [largest integer less than or equal to a given number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor). ## _math.fround ``` (x: number): number ``` The `_math.fround` method returns the [nearest 32-bit single precision float representation of a number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/fround). ## _math.hypot ``` (values: number[]): number ``` The `_math.hypot` method returns the [square root of the sum of squares of its arguments](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/hypot). ## _math.imul ``` ({a: number, b: number}): number ([a: number, b: number]): number ``` The `_math.imul` method returns the [result of the C-like 32-bit multiplication of the two parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul). ## _math.log ``` (x: number): number ``` The `_math.log` method returns the [natural logarithm](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log) (base `e`) of a number. ## _math.log10 ``` (x: number): number ``` The `_math.log10` method returns the [base `10` logarithm](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log10) of a number. ## _math.log1p ``` (x: number): number ``` The `_math.log1p` method returns the [natural logarithm (base e) of `1 + a number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log1p). ## _math.log2 ``` (x: number): number ``` The `_math.log2` method returns the [base `2` logarithm](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log2) of a number. ## _math.max ``` (values: number[]): number ``` The `_math.max` method returns the [largest of the numbers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max) given as input parameters. ## _math.min ``` (values: number[]): number ``` The `_math.min` method returns the [smallest of the numbers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max) given as input parameters. ## _math.mod ``` ({dividend: number, divisor: number}): number ([dividend: number, divisor: number]): number ``` The `_math.mod` method returns the remainder of the `dividend` divided by the `divisor` (the [modulo operation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder)). ## _math.pow ``` ({base: number, exponent: number}): number ([base: number, exponent: number]): number ``` The `_math.pow` method returns the [`base` to the `exponent` power](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow). ## _math.random ``` (void): number ``` The `_math.random` method returns a floating-point, [pseudo-random number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) in the range `0` to less than `1`. ## _math.round ``` (x: number): number ``` The `_math.round` method returns the value of a number [rounded to the nearest integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round). ## _math.sign ``` (x: number): number ``` The `_math.sign` method returns either a [positive or negative 1 (`+/- 1`)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign), indicating the sign of a number passed into the argument. ## _math.sin ``` (x: number): number ``` The `_math.sin` method returns the [sine](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sin) of a number. ## _math.sinh ``` (x: number): number ``` The `_math.sinh` method returns the [hyperbolic sine](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sinh) of a number. ## _math.sqrt ``` (x: number): number ``` The `_math.sqrt` method returns the [square root](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sqrt) of a number. ## _math.tan ``` (x: number): number ``` The `_math.tan` method returns the [tangent](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tan) of a number. ## _math.trunc ``` (x: number): number ``` The `_math.trunc` method returns the [integer part](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc) of a number by removing any fractional digits. ## _math.E ``` (void): number ``` The `_math.E` method returns [Euler's number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/E), the base of natural logarithms, `e`, which is approximately 2.718. ## _math.LN10 ``` (void): number ``` The `_math.LN10` method returns the [natural logarithm of `10`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/LN10), which is approximately 2.302. ## _math.LN2 ``` (void): number ``` The `_math.LN2` method returns the [natural logarithm of `2`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/LN2), which is approximately 0.693. ## _math.LOG10E ``` (void): number ``` The `_math.LOG10E` method returns the [base `10` logarithm of `e`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/LOG10E), which is approximately 0.434. ## _math.LOG2E ``` (void): number ``` The `_math.LOG2E` method returns the [base `2` logarithm of `e`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/LOG2E), which is approximately 1.442. ## _math.PI ``` (void): number ``` The `_math.PI` method returns the constant [`pi`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/PI), the ratio of the circumference of a circle to its diameter, which is approximately 3.14159. ## _math.SQRT1_2 ``` (void): number ``` The `_math.SQRT1_2` method returns the [square root of `1/2`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/SQRT1_2), which is approximately 0.707. ## _math.SQRT2 ``` (void): number ``` The `_math.SQRT2` method returns the [square root of `2`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/SQRT2), which is approximately 1.414. --- url: https://docs.lowdefy.com/md/operators/_media.md --- # _media ``` (key: string): any (all: boolean): any (arguments: { all?: boolean, key?: string, default?: any, }): any ``` The `_media` operator gets a value from the [`media`](/page-and-app-state) object. It can only be used on the web-client (Not in `requests` or `connections`). `media` is a data object that contains information about the current screen size of a users browser window. It contains the following data: - `width: number`: The width of the window in pixels. - `height: number`: The height of the window in pixels. - `darkMode: boolean`: Whether dark mode is currently active. Resolves from the user's preference (`'dark'` → true, `'light'` → false, `'system'` → follows OS `prefers-color-scheme`). - `darkModePreference: 'system' | 'light' | 'dark'`: The user's dark mode preference. `'system'` means the OS preference is being followed. - `size: enum`: One of `xs`, `sm`, `md`, `lg`, `xl`, `2xl`. The sizes are determined by comparing the window width to the following breakpoints (in pixels): - `xs`: `width < 640px` - `sm`: `640px <= width < 768px` - `md`: `768px <= width < 1024px` - `lg`: `1024px <= width < 1280px` - `xl`: `1280px <= width < 1536px` - `2xl`: `1536px <= width` `media` stays current while the page is open. When the browser window is resized, Lowdefy waits until the resize settles (150ms without a further resize event) and then re-evaluates the blocks that read a `media` value that changed. Only the values a block reads count: a block that reads `_media: size` is re-evaluated when the window crosses a breakpoint, not on every pixel of a resize, while a block that reads `_media: width` or the whole `media` object is re-evaluated whenever the width changes. Blocks that do not use `_media` are not re-evaluated at all. This makes `_media` suitable for responsive `visible` conditions and properties: ```yaml id: mobile_menu type: Box visible: _eq: - _media: size - xs ``` Prefer `_media: size` over `_media: width` where a breakpoint is enough, so that blocks only re-render when the layout actually changes. `darkMode` and `darkModePreference` update when dark mode is toggled. For purely visual responsive changes, CSS breakpoints in `style`, `class` and `layout` do not need `_media` at all. > **Note:** In v4, the largest breakpoint was `xxl` (1600px). It has been renamed to `2xl` (1536px) to align with Tailwind CSS breakpoints. See the [Migration Guide](/v4-to-v5) for details. #### Arguments ###### string If the `_media` operator is called with a string argument, the value of the key in the `media` object is returned. If the value is not found, `null` is returned. ###### boolean If the `_media` operator is called with boolean argument `true`, the entire `media` object is returned. ###### object - `all: boolean`: If `all` is set to `true`, the entire `media` object is returned. One of `all` or `key` are required. - `key: string`: The value of the key in the `media` object is returned. If the value is not found, `null`, or the specified default value is returned. One of `all` or `key` are required. - `default: any`: A value to return if the `key` is not found in `media`. By default, `null` is returned if a value is not found. #### Examples ###### Get the value of `size` from `media`: ```yaml _media: size ``` ```yaml _media: key: size ``` Returns: The value of `size`. ###### Get the entire `media` object: ```yaml _media: true ``` ```yaml _media: all: true ``` Returns: The entire `media` object. ###### Check if dark mode is active: ```yaml _media: darkMode ``` Returns: `true` or `false`. ###### Get the current dark mode preference: ```yaml _media: darkModePreference ``` Returns: `'system'`, `'light'`, or `'dark'`. ###### Return a default value if the value is not found: ```yaml _media: key: does_not_exist default: Not there ``` Returns: `"Not there"`. --- url: https://docs.lowdefy.com/md/operators/_menu.md --- # _menu ``` (menuId: string): object (menuIndex: number): object (all: boolean): object[] (arguments: { value?: string, index?: number all?: boolean, }): object | object[] ``` The `_menu` operator can be used to access `menu` objects defined in the [`menus`](/lowdefy-schema) section of the Lowdefy configuration. #### Arguments ###### string The `menuId` of the `menu` to return. ###### number The index of the `menu` to return. ###### boolean If the `_menu` operator is called with boolean argument `true`, the entire `menus` object is returned. ###### object - `value: string`: The `menuId` of the `menu` to return. - `index: number`: The index of the `menu` to return. - `all: boolean`: If the `_menu` operator is called with boolean argument `true`, the entire `menus` object is returned. #### Examples ###### Get the `menus` object: ```yaml _menu: true ``` ```yaml _menu: all: true ``` Returns: An array of `menu` objects. ###### Get a `menu` by `id`: ```yaml _menu: default ``` ```yaml _menu: value: default ``` Returns: A `menu` object. ###### Get a `menu` by `index`: ```yaml _menu: 0 ``` ```yaml _menu: value: 0 ``` Returns: A `menu` object. --- url: https://docs.lowdefy.com/md/operators/_module.md --- # _module ``` _module.var: (key: string): any _module.pageId: (pageId: string): string _module.pageId: (arguments: { id: string, module: string }): string _module.connectionId: (connectionId: string): string _module.connectionId: (arguments: { id: string, module: string }): string _module.endpointId: (endpointId: string): string _module.endpointId: (arguments: { id: string, module: string }): string _module.notificationId: (notificationId: string): string _module.notificationId: (arguments: { id: string, module: string }): string _module.websocketId: (websocketId: string): string _module.websocketId: (arguments: { id: string, module: string }): string _module.id: (true): string _module.id: (arguments: { module: string }): string ``` The `_module` operator family provides access to module-scoped values during the build. `_module.var` and the string forms of the ID operators only make sense inside module config files — the build throws a clear error if you use them at the app level. The object form of `_module.pageId`, `_module.connectionId`, `_module.endpointId`, `_module.notificationId`, `_module.websocketId`, and `_module.id` also works at the app level, resolving by entry ID instead of a dependency name — useful for linking, calling, or labelling a specific module instance from app-level config without hardcoding the scoped ID. See each method below for an app-level example. All `_module` operators resolve during the walker pass — the same phase as `_var` and `_ref`. Inside module files, they work at any depth. See [Modules](/modules) for an overview of the module system. # Operator methods: ## _module.var ``` (key: string): any ``` The `_module.var` operator reads a value from the module entry's `vars`. It returns the consumer value passed in `lowdefy.yaml` if one was provided, otherwise it resolves the manifest default on read. If neither is provided (no consumer value, no declared default), the operator returns `null`. Unlike `_var`, which requires threading variables through each `_ref`, `_module.var` works at any depth within module files. It accesses the same vars regardless of `_ref` nesting. Defaults are declared in `module.lowdefy.yaml` and resolve on read. Defaults are expressions — they can be literals, `_ref`s, `_module.var` references to other vars, or any `_build.*` tree. See [Writing Modules → Dynamic Defaults](/module-authoring#dynamic-defaults) for patterns. Vars with `properties` merge at the property level — only consumer-provided keys override the declared defaults, and missing properties resolve their own defaults independently. A declared property with no consumer value and no default is left out of the object rather than set to `null`, so a block that receives the object applies its own default for that property. A property with no default that the consumer sets to `null` stays `null`. #### Arguments ###### string The key in the module entry's `vars` object. Dot notation is supported for nested access (e.g., `theme.colors.primary`). #### Examples ###### Simple string access: ```yaml title: _module.var: title ``` If the app provides `vars: { title: "Welcome" }`, returns `"Welcome"`. ###### Nested path access: ```yaml primary_color: _module.var: theme.colors.primary ``` Accesses nested values using dot notation. If the module manifest declares defaults via `properties`, unset values resolve to their defaults. ## _module.pageId ``` (pageId: string): string (arguments: { id: string, module: string }): string ``` Resolves a page ID to its fully scoped ID. The string form references the current module's own pages and only works inside module content. The object form references a target by entry ID. Inside module content, `module` is the abstract dependency name declared in `module.lowdefy.yaml`. At the app level, `module` is the concrete module entry ID from `lowdefy.yaml` — useful when an app-level page needs to link to a specific module instance. #### Arguments ###### string The module-local page ID (as declared in the module's page definition). ###### object - `id: string`: The page ID in the target module. - `module: string`: The abstract dependency name (in module content) or the module entry ID (in app-level config). #### Examples ###### Link to a sibling page: ```yaml - id: view_details type: Link params: pageId: _module.pageId: user-edit ``` In a module entry with `id: team-users`, resolves to `team-users/user-edit`. ###### Link to a page in another module: ```yaml - id: view_contact type: Link params: pageId: _module.pageId: id: contact-detail module: contacts ``` If the `contacts` dependency is wired to an entry with `id: contacts`, resolves to `contacts/contact-detail`. ###### Link from an app page to a module page: ```yaml # lowdefy.yaml (app level) pages: - id: home blocks: - id: view_team type: Link params: pageId: _module.pageId: id: users-list module: team-users ``` At the app level, `module` is the module entry ID. The build resolves this to `team-users/users-list` regardless of where the entry lives in your config. ## _module.connectionId ``` (connectionId: string): string (arguments: { id: string, module: string }): string ``` Resolves a connection ID to its scoped or remapped ID. The string form references the current module's connections and only works inside module content. The object form references a target by entry ID. Inside module content, `module` is the abstract dependency name declared in `module.lowdefy.yaml`. At the app level, `module` is the concrete module entry ID from `lowdefy.yaml` — useful when an app-level request needs to reach a specific module instance's connection. #### Arguments ###### string The module-local connection ID (as declared in the module's connection definition). ###### object - `id: string`: The connection ID in the target module. - `module: string`: The abstract dependency name (in module content) or the module entry ID (in app-level config). #### Examples ###### Reference a module connection: ```yaml connectionId: _module.connectionId: users-db ``` In a module entry with `id: team-users`, resolves to `team-users/users-db`. If the app remaps the connection (`connections: { users-db: my-app-mongodb }`), resolves to `my-app-mongodb` instead. ###### Reference a connection in another module: ```yaml connectionId: _module.connectionId: id: contacts-db module: contacts ``` Resolves to the target module's scoped or remapped connection ID. ###### Reference a module connection from an app request: ```yaml # lowdefy.yaml (app level) api: - id: get_team_users type: Request connectionId: _module.connectionId: id: users-db module: team-users ``` At the app level, `module` is the module entry ID. The build resolves this to the target module's scoped or remapped connection ID. ## _module.endpointId ``` (endpointId: string): string (arguments: { id: string, module: string }): string ``` Resolves an API endpoint ID to its fully scoped ID. The string form references the current module's endpoints and only works inside module content. The object form references a target by entry ID. Inside module content, `module` is the abstract dependency name declared in `module.lowdefy.yaml`. At the app level, `module` is the concrete module entry ID from `lowdefy.yaml` — useful when an app-level page needs to call a specific module instance's API. #### Arguments ###### string The module-local endpoint ID (as declared in the module's API definition). ###### object - `id: string`: The endpoint ID in the target module. - `module: string`: The abstract dependency name (in module content) or the module entry ID (in app-level config). #### Examples ###### Call a module API endpoint: ```yaml - id: call_invite type: CallAPI params: endpointId: _module.endpointId: invite-user ``` In a module entry with `id: team-users`, resolves to `team-users/invite-user`. ###### Call an API in another module: ```yaml - id: log_event type: CallAPI params: endpointId: _module.endpointId: id: log-event module: events ``` Resolves to the target module's scoped endpoint ID. ###### Call a module API endpoint from an app page: ```yaml # lowdefy.yaml (app level) pages: - id: dashboard events: onClick: - id: log_event type: CallAPI params: endpointId: _module.endpointId: id: log-event module: events ``` At the app level, `module` is the module entry ID. The build resolves this to `events/log-event`. ## _module.notificationId ``` (notificationId: string): string (arguments: { id: string, module: string }): string ``` Resolves a notification ID to its fully scoped ID. The string form references the current module's notifications and only works inside module content. The object form references a target by entry ID. Inside module content, `module` is the abstract dependency name declared in `module.lowdefy.yaml`. At the app level, `module` is the concrete module entry ID from `lowdefy.yaml` — useful when app-level config dispatches a notification whose template a module ships. #### Arguments ###### string The module-local notification ID (as declared in the module's `notifications:` section). ###### object - `id: string`: The notification ID in the target module. - `module: string`: The abstract dependency name (in module content) or the module entry ID (in app-level config). #### Examples ###### Render the module's own notification: ```yaml - id: render type: RenderNotification properties: notificationId: _module.notificationId: invite-user data: contact: _payload: contact ``` In a module entry with `id: user-admin`, resolves to `user-admin/invite-user`. ###### Dispatch with a scoped notification id from an app page: ```yaml # lowdefy.yaml (app level) params: endpointId: _module.endpointId: id: dispatch-notifications module: notifications payload: notification_id: _module.notificationId: id: invite-user module: user-admin ``` At the app level, `module` is the module entry ID. The build resolves this to `user-admin/invite-user`. ## _module.websocketId ``` (websocketId: string): string (arguments: { id: string, module: string }): string ``` Resolves a websocket ID to its fully scoped ID, for a page's `subscriptions[].websocketId` and the `Subscribe`, `Unsubscribe` and `Publish` actions. The string form references the current module's websockets and only works inside module content. The object form references a target by entry ID. Inside module content, `module` is the abstract dependency name declared in `module.lowdefy.yaml`. At the app level, `module` is the concrete module entry ID from `lowdefy.yaml`, for an app page that subscribes to a websocket a module ships. #### Arguments ###### string The module-local websocket ID (as declared in the module's `websockets:` section). ###### object - `id: string`: The websocket ID in the target module. - `module: string`: The abstract dependency name (in module content) or the module entry ID (in app-level config). #### Examples ###### Subscribe a module page to the module's own websocket: ```yaml subscriptions: - websocketId: _module.websocketId: thread-messages payload: ticket_id: _state: ticket_id ``` In a module entry with `id: support`, resolves to `support/thread-messages`. ###### Read the channel state of a module websocket: ```yaml _websocket: _string.concat: - _module.websocketId: thread-messages - .messages ``` ###### Subscribe an app page to a module's websocket: ```yaml # lowdefy.yaml (app level) subscriptions: - websocketId: _module.websocketId: id: thread-messages module: support ``` At the app level, `module` is the module entry ID. The build resolves this to `support/thread-messages`. ## _module.id ``` (true): string (arguments: { module: string }): string ``` Returns the raw module entry ID. The `true` form returns the current module's entry ID and only works inside module content. The object form returns the entry ID of a referenced module. Inside module content, `module` is the abstract dependency name declared in `module.lowdefy.yaml`. At the app level, `module` is the concrete module entry ID from `lowdefy.yaml` — useful for templates that need a module entry ID without hardcoding it. #### Arguments ###### boolean Pass `true` to return the current module entry ID. ###### object - `module: string`: The abstract dependency name (in module content) or the module entry ID (in app-level config). #### Examples ###### Use in a label: ```yaml label: _string.concat: - _module.id: true - " - User Admin" ``` In a module entry with `id: team-users`, returns `"team-users - User Admin"`. ###### Get a dependency's entry ID: ```yaml contactsEntry: _module.id: module: contacts ``` Returns the concrete entry ID that the `contacts` dependency is wired to. ###### Reference a module entry ID at the app level: ```yaml # lowdefy.yaml (app level) pages: - id: home blocks: - id: status_label type: Title properties: content: _string.concat: - 'Active module: ' - _module.id: module: team-users ``` At the app level, `module` is the module entry ID. Returns the entry ID verbatim — useful inside `_build.*` expressions or templates that need the entry name. --- url: https://docs.lowdefy.com/md/operators/_dayjs.md --- # _dayjs The `_dayjs` operator provides date manipulation and formatting using the [Day.js](https://day.js.org/) library. It supports two modes: - **Method mode**: Call a specific method like `_dayjs.format` or `_dayjs.humanizeDuration`. - **Chain mode**: Chain multiple Day.js operations together using an array. #### Chain mode Pass an array where the first element is the input date (or `"now"`), followed by method calls: ```yaml _dayjs: - "now" - subtract: - 3 - days - fromNow ``` Returns: `"3 days ago"` Each step is either a string (method called with no arguments) or an object (key is the method name, value is the arguments). ```yaml # Format a specific date _dayjs: - "2024-03-15" - format: "YYYY-MM-DD" ``` Returns: `"2024-03-15"` ```yaml # Add 1 month and format _dayjs: - "2024-01-01" - add: - 1 - month - format: "D MMM YYYY" ``` Returns: `"1 Feb 2024"` ```yaml # Get start of month _dayjs: - "2024-03-15" - startOf: month - format: "YYYY-MM-DD" ``` Returns: `"2024-03-01"` ```yaml # Calculate difference in days _dayjs: - "2024-03-15" - diff: - "2024-03-10" - days ``` Returns: `5` **Supported chain methods:** `add`, `subtract`, `startOf`, `endOf`, `set`, `utc`, `local`, `locale`, `isBefore`, `isAfter`, `isSame`, `format`, `fromNow`, `from`, `toNow`, `to`, `diff`, `valueOf`, `unix`, `toISOString`, `toJSON`, `toString`, `daysInMonth`, `year`, `month`, `date`, `day`, `hour`, `minute`, `second`, `millisecond`, `week`. > Migrating from v4? The `_moment` operator was renamed to `_dayjs` in v5. See the [v4 to v5 migration guide](/v4-to-v5) for details. # Operator methods: ## _dayjs.format ``` (arguments: { on: date | string, locale?: string, format?: string }) ``` The `_dayjs.format` formats dates using the [Day.js](https://day.js.org/docs/en/display/format) library. #### Arguments ###### object - `on: date | string`: The date to format. - `locale: string`: A string with a locale name. - `format: string`: A date [format string](https://day.js.org/docs/en/display/format). #### Examples ###### Format a date: ```yaml _dayjs.format: on: _date: 2019-06-04 format: 'D MMM YYYY' ``` Returns: `"4 Jun 2019"`. ## _dayjs.humanizeDuration ``` (arguments: { on: number, locale?: string, withSuffix?: boolean }) ``` The `_dayjs.humanizeDuration` formats durations in milliseconds using [Day.js](https://day.js.org/docs/en/plugin/duration). #### Arguments ###### object - `on: number`: The duration in milliseconds to format. - `locale: string`: A string with a locale name. Locale names are auto-normalized (e.g. `en-US` becomes `en`). - `withSuffix: boolean`: By default, the return string is describing a duration `a month` (suffix-less). If you want an oriented duration `in a month`, `a month ago` (with suffix), pass in true. > **Note:** The `thresholds` parameter from v4 is accepted but ignored in v5. Day.js uses fixed thresholds internally. #### Examples ###### Format a duration: ```yaml _dayjs.humanizeDuration: on: 245923000 withSuffix: true ``` Returns: `"in 3 days"`. --- url: https://docs.lowdefy.com/md/operators/_mql.md --- # _mql The `_mql` operator uses the [`mingo`](https://www.npmjs.com/package/mingo) package to evaluate MongoDB query language statements as an operator. # Operator methods: ## _mql.aggregate ``` ({on: object[], pipeline: object[]}): object[] ([on: object[], pipeline: object[]]): object[] ``` The `_mql.aggregate` method runs a MongoDB aggregation pipeline on an input array. #### Arguments ###### object - `on: object[]`: The data array on which to run the pipeline. It should be an array of objects. - `pipeline: object[]`: A MongoDB aggregation pipeline definition. #### Examples ###### Transform request results: ```yaml _mql.aggregate: pipeline: - $match: age: $gte: 18 - $addFields: averageScore: $avg: - $score1 - $score2 on: _request: my_request ``` ## _mql.expr ``` ({on: object, expr: any}): any ([on: object, expr: any]): any ``` The `_mql.expr` method evaluates a MongoDB aggregation pipeline operator expression. This is any statement that could be written in a `$project` stage. #### Arguments ###### object - `on: object`: An object to take as input for the expression. - `expr: object`: A MongoDB aggregation expression. #### Examples ###### Calculate an average of three inputs: ```yaml _mql.expr: expr: $avg: - $input1 - $input2 - $input3 on: _state: true ``` ## _mql.test ``` ({on: object, test: object}): boolean ([on: object, test: object]): boolean ``` The `_mql.test` method tests if a object matches a MongoDB filter/query expression. #### Arguments ###### object - `on: object`: The object to be tested. - `test: object`: A MongoDB filter/query expression. #### Examples ###### Test if a number input is greater than 100: ```yaml _mql.test: test: number_input: $gte: 18 on: number_input: _state: number_input ``` --- url: https://docs.lowdefy.com/md/operators/_ne.md --- # _ne ``` ([value1: any, value2: any]): boolean ``` The `_ne` operator tests if two values are not equal. It takes an array of two values to test. > The `_ne` won't do a deep comparison. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### array An array of two values to compare. #### Examples ###### Two non-equal strings: ```yaml _ne: - "Hello" - "Hello you" ``` Returns: `true` ###### Two equal strings: ```yaml _ne: - "Hello" - "Hello" ``` Returns: `false` ###### Two numbers: ```yaml _ne: - _sum: - 3 - 4 - 8 ``` Returns: `true` ###### Arrays are not compared deeply: ```yaml _ne: - [1,2,3] - [1,2,3] ``` Returns: `true` ###### Values from "getter" operators are copies and not equal: ```yaml _ne: - _state: my_object - _state: my_object ``` Returns: `true` --- url: https://docs.lowdefy.com/md/operators/_not.md --- # _not ``` (value: any): boolean ``` The `_not` operator returns the logical negation of the input. If the value is not a boolean, it will be converted to a boolean using javascript [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) and [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) rules. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### any #### Examples ###### Not `true` is `false`: ```yaml _not: true ``` Returns: `false` ###### Return `true` for a falsy value: ```yaml _not: null ``` Returns: `true` ###### Return `false` for a truthy value: ```yaml _not: 100 ``` Returns: `false` --- url: https://docs.lowdefy.com/md/operators/_number.md --- # _number The `_number` operator can be used to run javascript [`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) methods. > This operator can be used as a [`_build`](/_build) operator method. # Operator methods: ## _number.EPSILON ``` (void): number ``` The `_number.EPSILON` property represents the smallest interval between two representable numbers.. [See MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON) for more details and examples. ## _number.MAX_SAFE_INTEGER ``` (void): number ``` The `_number.MAX_SAFE_INTEGER` property represents the maximum safe integer in JavaScript (2^53 - 1). [See MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER) for more details and examples. ## _number.MAX_VALUE ``` (void): number ``` The `_number.MAX_VALUE` property represents the largest positive representable number. [See MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_VALUE) for more details and examples. ## _number.MIN_SAFE_INTEGER ``` (void): number ``` The `_number.MIN_SAFE_INTEGER` property represents the minimum safe integer in JavaScript (-(2^53 - 1)). [See MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER) for more details and examples. ## _number.MIN_VALUE ``` (void): number ``` The `_number.MIN_VALUE` property represents the smallest positive representable number — that is, the positive number closest to zero (without actually being zero). [See MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_VALUE) for more details and examples. ## _number.NaN ``` (void): number ``` The `_number.NaN` property represents the special "Not a Number" value. [See MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/NaN) for more details and examples. ## _number.NEGATIVE_INFINITY ``` (void): number ``` The `_number.NEGATIVE_INFINITY` property represents negative infinity. Returned on overflow. [See MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/NEGATIVE_INFINITY) for more details and examples. ## _number.POSITIVE_INFINITY ``` (void): number ``` The `_number.POSITIVE_INFINITY` property represents positive infinity. [See MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/POSITIVE_INFINITY) for more details and examples. ## _number.isFinite ``` (void): number ``` The `_number.isFinite` method is used to determine whether the passed value is a finite number. [See MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite) for more details and examples. ## _number.isInteger ``` (void): number ``` The `_number.isInteger` method is used to determine whether the passed value is an integer. [See MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger) for more details and examples. ## _number.isNaN ``` (void): number ``` The `_number.isNaN` method is used to determine whether the passed value is NaN. [See MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) for more details and examples. ## _number.isSafeInteger ``` (void): number ``` The `_number.isSafeInteger` method is used to determine whether the passed value is a safe integer (number between -(2^53 - 1) and 2^53 - 1). [See MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger) for more details and examples. ## _number.parseFloat ``` (void): number ``` The `_number.parseFloat` method parses an argument and returns a floating point number. If a number cannot be parsed from the argument, it returns NaN. [See MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseFloat) for more details and examples. ## _number.parseInt ``` (void): number ``` The `_number.parseInt` method parses a string argument and returns an integer of the specified radix or base. If a number cannot be parsed from the argument, it returns NaN. [See MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseInt) for more details and examples. ## _number.toExponential ``` (void): number ``` The `_number.toExponential` method returns a string representing the Number object in exponential notation. [See MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toExponential) for more details and examples. ## _number.toFixed ``` (void): number ``` The `_number.toFixed` method formats a number using fixed-point notation. [See MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) for more details and examples. ## _number.toLocaleString ``` (void): number ``` The `_number.toLocaleString` method returns a string with a language-sensitive representation of this number. [See MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString) for more details and examples. ## _number.toPrecision ``` (void): number ``` The `_number.toPrecision` method returns a string representing the Number object to the specified precision. [See MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toPrecision) for more details and examples. ## _number.toString ``` (void): number ``` The `_number.toString` method returns a string representing the specified Number object. [See MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toString) for more details and examples. --- url: https://docs.lowdefy.com/md/operators/_nunjucks.md --- # _nunjucks ``` (template: string): string (arguments: {template: string, on: object}): string ``` The `_nunjucks` hydrates a [Nunjucks](https://mozilla.github.io/nunjucks/) template. If called with a string argument, the template variables are the values in `state`. Otherwise template variables can be specified using the `on` argument. #### Arguments ###### string The template to hydrate. The template variables used are the values in state ###### object - `template: string`: The template to hydrate. - `on: object`: The template variables to use when hydrating the template. #### Examples ###### Populate a template from values in `state`: ```yaml _nunjucks: Hello {{ name }} ``` Returns: `"Hello Steven"` if the value of name in state is `"Steven"`. ###### Populate a markdown template with different values: Assuming `get_items` returns: ```yaml - name: Coca Cola description: The original. - name: Pepsi description: The same but different. ``` ```yaml _nunjucks: template: | ### {{ title }} {% for item in item_list %} - {{ item.name }}: {{ item.description }} {% endfor %} on: title: Soft drinks items: _request: get_items ``` Returns: ```markdown ### Soft drinks - Coca Cola: The original. - Pepsi: The same but different. ``` ###### Make use of the _nunjucks date filter: ```yaml _nunjucks: template: {{ date | date('D MMM YYYY') }} on: date: _date: 2022-08-01 ``` Returns: `"1 Aug 2022"` The `_nunjucks` date filter formats dates using the [Day.js](https://day.js.org/docs/en/display/format) library. ###### Use the _nunjucks unique filter: ```yaml _nunjucks: template: |
{% set uniqueArray = array | unique %} {% for item in uniqueArray %}

{{ item }}

{% endfor %}
on: array: - South Africa - New Zealand - Australia - South Africa - Pakistan - India - India ``` Returns: ```html

South Africa

New Zealand

Australia

Pakistan

India

``` ###### Make use of the _nunjucks urlQuery filter: ```yaml _nunjucks: template: {{ url | urlQuery(params) | safe }} on: url: www.lowdefy.com params: id: 1234 type: example ``` Returns: `"www.lowdefy.com?id=1234&type=example"` --- url: https://docs.lowdefy.com/md/operators/_object.md --- # _object The `_object` operator can be used to run javascript [`Object`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) methods. > This operator can be used as a [`_build`](/_build) operator method. # Operator methods: ## _object.assign ``` (objs: obj[]): object ``` The `_object.assign` method copies the values of one ore more source objects to a target objects. The first object in the arguments array is the target object, and the rest are source objects. Keys in the target object are overwritten. The result of `_object.assign` can be seen as a "shallow-merge" of all the objects in the array, with the values in later objects taken preferentially. #### Arguments ###### array An array of objects. #### Examples ###### Merge three objects: ```yaml _object.assign: - firstName: Rachel lastName: Green series: Friends - firstName: Monica lastName: Geller address: street: 90 Bedford St city: New York zipCode: '10014' country: US - friends: - Ross Geller - Rachel Green - Chandler Bing - Phoebe Buffay - Joey Tribbiani ``` Returns: ```yaml firstName: Monica lastName: Geller series: Friends address: street: 90 Bedford St city: New York zipCode: '10014' country: US friends: - Ross Geller - Rachel Green - Chandler Bing - Phoebe Buffay - Joey Tribbiani ``` ## _object.defineProperty ``` (arguments: { on: object, key: string, descriptor: { value: any, configurable?: boolean, enumerable?: boolean, writable?: boolean } }): any (arguments: [ on: object, key: string, descriptor: { value: any, configurable?: boolean, enumerable?: boolean, writable?: boolean } ]): any ``` The `_object.defineProperty` method defines a new property directly on an object, or modifies an existing property on an object, and returns the object. The first object in the arguments array, or the `on` parameter, is the target object to which the property will be applied. The second argument, or the `key` parameter, is the property name or key which will be applied to the target object. The third and last object argument, or the `descriptor` parameter, has serval options to consider, however, for the most common application set the `value` object key to the value that should be applied to the target object key. See [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) for a more detailed description. > The `_object.defineProperty` method is handy when the `key` to set is variable. #### Examples ###### Set a value to a object key: ```yaml _object.defineProperty: on: firstName: Rachel series: Friends key: lastName descriptor: value: Green ``` Returns: ```yaml firstName: Rachel lastName: Green series: Friends ``` ## _object.entries ``` (obj: object): any[] ``` The `_object.entries` method returns an array with an array of key-values pairs of all the fields in an object. #### Arguments ###### object The object to get entries from. #### Examples ###### Get the entries of an object: ```yaml _object.entries: firstName: Monica lastName: Geller ``` Returns: ```yaml - - firstName - Monica - - lastName - Geller ``` ## _object.fromEntries ``` (array: any[][]): object ``` The `_object.fromEntries` method creates an object from an array of key-value pairs. #### Arguments ###### array The array of key-value pairs. #### Examples ###### Get the entries of an object: ```yaml _object.fromEntries: - - firstName - Monica - - lastName - Geller ``` Returns: ```yaml firstName: Monica lastName: Geller ``` ## _object.keys ``` (obj: object): string[] ``` The `_object.keys` method returns an array with the objects keys. #### Arguments ###### object The object to get keys from. #### Examples ###### Get the keys of an object: ```yaml _object.keys: firstName: Monica lastName: Geller address: street: 90 Bedford St city: New York zipCode: '10014' country: US friends: - Ross Geller - Rachel Green - Chandler Bing - Phoebe Buffay - Joey Tribbiani ``` Returns: ```yaml - firstName - lastName - address - friends ``` ## _object.values ``` (obj: object): any[] ``` The `_object.values` method returns an array with the values of all the fields in an object. #### Arguments ###### object The object to get values from. #### Examples ###### Get the values of an object: ```yaml _object.values: firstName: Monica lastName: Geller address: street: 90 Bedford St city: New York zipCode: '10014' country: US friends: - Ross Geller - Rachel Green - Chandler Bing - Phoebe Buffay - Joey Tribbiani ``` Returns: ```yaml - Monica - Geller - street: 90 Bedford St city: New York zipCode: '10014' country: US - - Ross Geller - Rachel Green - Chandler Bing - Phoebe Buffay - Joey Tribbiani ``` --- url: https://docs.lowdefy.com/md/operators/_operator.md --- # _operator ``` (arguments: {name: string, params: any, operators?: string[]}): any ``` The `_operator` operator evaluates an operator with the given params. This is useful if the operator needs to be chosen dynamically. The `_operator` cannot evaluate itself. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### object - `name: string`: The name of the operator to evaluate, such as `_sum` or `_number.round`. Names written in `name`, including the names an `_if` returns from `then` and `else` and a `_switch` returns from its branches, are loaded with the page. - `params: any`: The params to give to the operator. - `operators: string[]`: The operators the call may evaluate. Required when `name` is read at runtime, from state, a request or a payload: the build fails without it, loads exactly these operators with the page (or the server), and `_operator` refuses any other name. A listed operator (`_number`) allows all its methods; a listed method (`_number.round`) allows only that method. #### Examples ###### Get a value from `urlQuery` if specified, else use the value in `state`: ```yaml _operator: name: _if: test: _eq: - _state: location_selector - url_query then: _url_query else: _state params: key: field_to_get ``` Returns: Value from `urlQuery` if `location_selector == url_query`, else the value from `state`. ###### Apply an aggregation chosen by the user: ```yaml _operator: name: _state: aggregation # _sum or _product operators: - _sum - _product params: _state: values ``` Returns: The sum or product of `values`. Any other name in `aggregation` is refused. --- url: https://docs.lowdefy.com/md/operators/_or.md --- # _or ``` (values: any[]): boolean ``` The `_or` operator performs a logical `or` over an array of inputs, using javascript [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) and [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) rules. It returns true if any of the values in the array are truthy (not `false`, `0`, `null`, `undefined`, or the empty string `""`). If all the values are falsy, it returns `false`. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### array An array of values over which to perform a logical or. #### Examples ###### `true` and `false` values: ```yaml _or: - true - false ``` Returns: `true` ###### Array of `true` and `false` values: ```yaml _or: - true - false - false - false ``` Returns: `true` ###### Falsy values values: ```yaml _or: - null - 0 - '' ``` Returns: `false` ###### Truthy values: ```yaml _or: - false - "Hello" ``` Returns: `true` ```yaml _or: - false - 99 ``` Returns: `true` ```yaml _or: - false - [1,2,3] ``` Returns: `true` --- url: https://docs.lowdefy.com/md/operators/_path_params.md --- # _path_params ``` (key: string): string (all: boolean): object (arguments: { all?: boolean, key?: string, default?: any, }): any ``` The `_path_params` operator gets a value from the page's path parameters: the values of the placeholders in the page's [`path`](/page-paths). On a page with `path: tickets/{space}/{ticket_id}`, opened at `/tickets/support/1234`, the path parameters are `{ space: 'support', ticket_id: '1234' }`. Path parameters are set when linking to the page with `pathParams`, for example with the [`Link`](/Link) action, or by typing the URL. Unlike `input`, they are visible to the user and can be changed by the user. > __DO NOT__ set any private or personal information in path parameters; all values in the URL are accessible publicly. Using a value that can be guessed, like an incremental `id`, can lead to security issues, since users can easily guess and access data for other `ids`. Values are always strings, the text from the URL after decoding: `1234` is `"1234"`, and nothing is deserialized. Convert a value when you need another type, for example with [`_number.parseInt`](/_number). A page without placeholders has no path parameters, so `_path_params: true` returns `{}`. Path parameters are kept apart from the [`urlQuery`](/_url_query): a page can have a `space` placeholder and a `space` query value, and `_path_params` only reads the placeholder. Within one [page instance](/page-paths#page-instances) the values never change, since different values open a different instance. `_path_params` is only available in the browser. To use a value in a request, pass it in the request `payload`. #### Arguments ###### string If the `_path_params` operator is called with a string argument, the value of that placeholder is returned. If the page has no such placeholder, `null` is returned. ###### boolean If the `_path_params` operator is called with boolean argument `true`, the object of all path parameters is returned. ###### object - `all: boolean`: If `all` is set to `true`, the object of all path parameters is returned. One of `all` or `key` are required. - `key: string`: The name of the placeholder whose value is returned. If the page has no such placeholder, `null`, or the specified default value is returned. One of `all` or `key` are required. - `default: any`: A value to return if the page has no placeholder named `key`. By default, `null` is returned. #### Examples ###### Get the value of the `ticket_id` placeholder: On a page with `path: tickets/{space}/{ticket_id}`, opened at `/tickets/support/1234`: ```yaml _path_params: ticket_id ``` ```yaml _path_params: key: ticket_id ``` Returns: `"1234"`. ###### Get all path parameters: ```yaml _path_params: true ``` ```yaml _path_params: all: true ``` Returns: `{ space: 'support', ticket_id: '1234' }`. ###### Return a default value if the page has no such placeholder: ```yaml _path_params: key: space default: general ``` Returns: The value of `space`, or `"general"` on a page whose path has no `{space}` placeholder. ###### Pass a value to a request: ```yaml requests: - id: get_ticket type: MongoDBFindOne connectionId: tickets payload: ticket_id: _path_params: ticket_id properties: query: _id: _payload: ticket_id ``` --- url: https://docs.lowdefy.com/md/operators/_product.md --- # _product ``` (values: any[]): number ``` The `_product` operator takes the product of the values given as input. If a value is not a number, the value is skipped. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### array An array of values to multiply. #### Examples ###### Two numbers: ```yaml _product: - 3 - 4 ``` Returns: `12` ###### Array of numbers: ```yaml _product: - 1 - 2 - 3 - 4 ``` Returns: `24` ###### Non-numbers are skipped: ```yaml _product: - 1 - null - 3 - "four" - 5 ``` Returns: `15` --- url: https://docs.lowdefy.com/md/operators/_random.md --- # _random ``` (type: string): string | number (arguments: { type: string, length?: number, max?: number, min?: number }): string | number ``` The `_random` operator returns a random string or number. The types it accepts are `string`, `integer`, or `float`. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### string The type to generate. One of `string`, `integer`, or `float`. ###### object - `type: string`: __Required__ - The type to generate. One of `string`, `integer`, or `float`. - `length: string`: The length of the string to generate if type is `string`. Default is `8`. - `max: number`: The maximum possible number if type is one of `integer` or `float`. Default is `100` if `integer` or `1` if `float`. - `min: number`: The minimum possible number if type is one of `integer` or `float`. Default is `0`. #### Examples ###### Random string: ```yaml _random: string ``` Returns: Random string of length 8. ```yaml _random: type: string ``` Returns: Random string of length 8. ###### Random integer: ```yaml _random: integer ``` Returns: Random integer between 0 and 100 inclusive. ```yaml _random: type: integer ``` Returns: Random integer between 0 and 100 inclusive. ###### Random float: ```yaml _random: float ``` Returns: Random float between 0 and 1 inclusive. ```yaml _random: type: float ``` Returns: Random float between 0 and 1 inclusive. ###### Random string of length 12: ```yaml _random: type: string length: 12 ``` Returns: Random string of length 12. ###### Random integer between 1 and 6 (Dice roll): ```yaml _random: type: integer min: 1 max: 6 ``` Returns: Random integer between 1 and 6 inclusive. ###### Random float between 34.2 and 42.89: ```yaml _random: type: float min: 34.2 max: 42.89 ``` Returns: Random float between 34.2 and 42.89 inclusive. --- url: https://docs.lowdefy.com/md/operators/_ref.md --- # _ref ``` (path: string): any (arguments: { path?: string, key?: string, resolver?: string, transformer?: string, vars?: object, }): any (arguments: { module: string, component: string, vars?: object, key?: string, }): any (arguments: { module: string, menu: string, }): object[] ``` The `_ref` operator can be used to reference a configuration file, in order to split the Lowdefy configuration into multiple files. More information on references and the Lowdefy configuration schema can be found [here](/lowdefy-schema). > The `_ref` operator is a build time operator: it is evaluated when the app configuration is being built. This means it is not evaluated dynamically as the app is running, and can be used anywhere in the configuration as long as the resulting configuration files are valid YAML. The `_ref` operator requires a file path to the file to be referenced, relative to the root directory of the project. If this file is a YAML or JSON file, and has file extension `.yaml`, `.yml`, or `.json`, the file is parsed as YAML/JSON, and the parsed result is included in the configuration. If this file is a Nunjucks template file, with file extension `.njk`, the file is parsed as a Nunjucks template, using any variables provided in the `vars` argument. If the file extension is `.yaml.njk`, `.yml.njk` or `.json.njk`, the template output is also parsed as YAML/JSON. If the file is not parsed (for example has an extension like `.txt`, `.md`, or `.html`), the file contents are included in the configuration as a string. If the referenced file is a YAML or JSON file, the `key` argument can be used to reference a specific key inside that file, instead of the whole file. ###### Variables Variables defined in the `vars` argument can be accessed in the referenced file using the [`_var`](/_var), and as template variables in Nunjucks files. ###### Resolver A resolver is a JavaScript function that overwrites the default way configuration files are read from the filesystem. It does not need to read from the filesystem, it could generate a value to return programmatically, or it could fetch the configuration using HTTP (from Github for example). The `resolver` argument should be the file path (relative to the root of the project) to a JavaScript file that exports a resolver function. The resolver function receives the `path`, `vars`, and a `context` object as arguments. If a resolver function is specified, the `path` argument to the `_ref` operator is optional, and does not need to correspond to a path to a file. If `path` ends with `.yaml`, `.yml`, `.json`, `.njk`, `.yaml.njk`, `.yml.njk`, or `.json.njk`, the returned result will be parsed as YAML/JSON/Nunjucks template. A default `_ref` resolver can be specified in the `lowdefy.yaml` `cli` section (as `refResolver`), or as a command-line option when running the CLI (as `--ref-resolver`). This resolver will then be used for all references in the app, unless another resolver is specified. ###### Transformer A transformer is a JavaScript function that receives the result of the `_ref` operator, and its `vars` as arguments. The value returned by this function will be included in the configuration as the final result of the `_ref` operator. The `transformer` argument should be the file path (relative to the root of the project) to a JavaScript file that exports a transformer function. ###### CommonJS and ES Modules Both [CommonJS](https://nodejs.org/api/modules.html) and [ES Modules](https://nodejs.org/api/esm.html) are supported for in resolver and transformer functions. By default the functions are imported as CommonJs modules, and the function should be exported as a default export (`module.exports = transformer`). Files can also use the `.cjs` file extension to indicate they are CommonJs modules. To use ES Modules, either use files with `.mjs` file extension, or add a `package.json` in the project directory file with `"type": "module"` set. When using ES Modules, the functions should also be a default export (`export default transformer;`) #### Arguments ###### string The file path to the referenced file, from the root of the project directory. ###### object - `path: string`: The file path to the referenced file, from the root of the project directory. If no `resolver` is specified, `path` is required. - `key: string`: Only include the content at the specified key, instead of the entire file content. Dot notation is supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. Reserved key names such as `__proto__` and `constructor` are rejected as a config error, failing the build. This can only be used with YAML or JSON files. - `resolver: string`: The file path to a JavaScript file, from the root of the project directory, that exports a resolver function. - `transformer: string`: The file path to a JavaScript file, from the root of the project directory, that exports a transformer function. - `vars: object`: An object to be used as variables for the `_var` operator in the referenced file, and as template variables in Nunjucks template files. - `module: string`: The module entry ID (as defined in `lowdefy.yaml` `modules` array). When `module` is specified, `path` is not used — the reference resolves from the module's exported components or menus. See [Modules](/modules). - `component: string`: The component name exported by the module (defined in `module.lowdefy.yaml` `components` array). Used with `module`. - `menu: string`: The menu ID exported by the module (defined in `module.lowdefy.yaml` `menus` array). Used with `module`. Returns the menu's `links` array. - `key: string`: Dot-notation path to extract from the resolved content. Works for both file refs and module component refs. > When referencing a module component, `vars` are accessed via `_var` inside the component (same as standard `_ref`). Module-level configuration is accessed via [`_module.var`](/_module). Both are available simultaneously. > Within module content, the `module` field can reference a declared dependency name (from the module's `dependencies` in `module.lowdefy.yaml`). The build resolves abstract names to concrete entry IDs via the dependency wiring. From app-level config, `module` is always a concrete entry ID. #### Examples ###### Reference pages: ```yaml # lowdefy.yaml lowdefy: 7.1.0 pages: - _ref: pages/page1.yaml - _ref: pages/page2.yaml ``` ```yaml # pages/page1.yaml id: page1 type: PageHeaderMenu blocks: # ... ``` ```yaml # pages/page2.yaml id: page2 type: PageHeaderMenu blocks: # ... ``` Returns: ``` lowdefy: 7.1.0 pages: - id: page1 type: PageHeaderMenu blocks: # ... - id: page2 type: PageHeaderMenu blocks: # ... ``` ###### Using a standardized input label template: ```yaml blocks: - id: name type: TextInput properties: label: _ref: path: label.yaml vars: title: Name description: Your name and surname. - id: age type: NumberInput properties: label: _ref: path: label.yaml vars: title: Age description: Your age. ``` ```yaml # label.yaml title: _var: title extra: _var: description span: 8 colon: false extraStyle: color: '#546358' ``` Returns: ```yaml blocks: - id: name type: TextInput properties: label: title: Name extra: Your name and surname. span: 8 colon: false extraStyle: color: '#546358' - id: age type: NumberInput properties: label: title: Age extra: Your age. span: 8 colon: false extraStyle: color: '#546358' ``` ###### Use key: ```yaml # lowdefy.yaml lowdefy: 7.1.0 version: _ref: path: package.json field: version ``` ```json // package.json { "version": "1.0.0" } ``` Returns: ``` lowdefy: 7.1.0 version: 1.0.0 ``` ###### Local or shared resolver: This resolver function will first look for the configuration file in the current working directory, but if the file is not found it will be read from an adjacent "shared" directory. This pattern can be used to build apps that mostly use a shared configuration, with a few components that are customised per app. ```js // resolvers/useLocalOrSharedConfig.js const fs = require('fs'); const path = require('path'); const { promisify } = require('util'); const readFilePromise = promisify(fs.readFile); async function useLocalOrSharedConfig(refPath, vars, context) { let fileContent try { fileContent = await readFilePromise(path.resolve(refPath), 'utf8'); return fileContent; } catch (error) { if (error.code === 'ENOENT') { fileContent = readFilePromise(path.resolve('../shared', refPath), 'utf8'); return fileContent; } throw error; } } module.exports = useLocalOrSharedConfig; ``` ```yaml // lowdefy.yaml lowdefy: 7.1.0 cli: refResolver: resolvers/useLocalOrSharedConfig.js pages: - _ref: pages/local-page.yaml - _ref: pages/shared-page.yaml ``` ###### This transformer adds a standard footer to each page: ```js // transformers/addFooter.js function addFooter(page, vars) { const footer = { // ... }; page.areas.footer = footer; return page; } module.exports = addFooter; ``` ```yaml // lowdefy.yaml lowdefy: 7.1.0 pages: - _ref: path: pages/page1.yaml transformer: transformers/addFooter.js ``` ###### Reference a module component: ```yaml blocks: - _ref: module: notifications component: notification-badge vars: position: top-right ``` ###### Data export with key extraction: ```yaml icon: _ref: module: events component: event_types key: login.icon ``` ###### Cross-module component ref within a module: ```yaml # contacts module embedding companies' selector blocks: - _ref: module: companies # abstract dependency name component: company-selector vars: field_id: company_id ``` ###### Module menu inside a MenuGroup: ```yaml menus: - id: main links: - id: home type: MenuLink pageId: home - id: team-admin type: MenuGroup properties: title: Team Admin links: _ref: module: team-users menu: default ``` ###### Using ES Modules with `.mjs` file extension: ```js // resolvers/useLocalOrSharedConfig.mjs import fs from 'fs'; import path from 'path'; import { promisify } from 'util'; const readFilePromise = promisify(fs.readFile); async function useLocalOrSharedConfig(refPath, vars, context) { let fileContent try { fileContent = await readFilePromise(path.resolve(refPath), 'utf8'); return fileContent; } catch (error) { if (error.code === 'ENOENT') { fileContent = readFilePromise(path.resolve('../shared', refPath), 'utf8'); return fileContent; } throw error; } } export default useLocalOrSharedConfig; ``` --- url: https://docs.lowdefy.com/md/operators/_regex.md --- # _regex ``` (pattern: string): boolean (arguments: { pattern: string, on?: string, key?: string, flags?: string }): boolean ``` The `_regex` operator performs a regex test on a string, and returns `true` if there is a match. The regex operator has shorthand argument definitions that can be used on web client. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### object - `pattern: string`: __Required__ - The regular expression pattern to test. - `on: string`: The string to test the value on. One of `on` or `key` must be specified unless the operator is used in an input block. - `key: string`: The key of a value in `state` to test. One of `on` or `key` must be specified unless the operator is used in an input block. - `flags: string`: The regex flags to use. The default value is `"gm"`. ###### string The regular expression pattern to test. The string shorthand can only be used in an input block, and the tested value will be the block's value. #### Examples ###### Check if a username is valid (Alphanumeric string that may include _ and – having a length of 3 to 16 characters): ```yaml _regex: pattern: ^[a-z0-9_-]{3,16}$ on: _state: username_input ``` Returns: `true` if matched else `false`. ###### Using the key of the value in `state`: ```yaml _regex: pattern: ^[a-z0-9_-]{3,16}$ key: username_input ``` Returns: `true` if matched else `false`. ###### Using the value of the block in which the operator is evaluated: ```yaml id: username_input type: TextInput validate: - message: Invalid username. status: error pass: _regex: ^[a-z0-9_-]{3,16}$ ``` Returns: `true` if matched else `false`. ###### Case insensitive match: ```yaml _regex: pattern: ^[a-z0-9_-]{3,16}$ on: _state: username_input flags: 'gmi' ``` Returns: `true` if matched else `false`. --- url: https://docs.lowdefy.com/md/operators/_request.md --- # _request ``` (requestId: string): any ``` The `_request` operator returns the response value of a request. If the request has not yet been called, or is still executing, the returned value is `null`. Dot notation and [block list indexes](/lists) are supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. The leading segment of a `_request` path is the request id and is matched exactly, so the rule above applies to the part of the path after it. For more detailed information about a request, the [_request_details](/_request_details) operator can be used. If the request was called via the [`Request`](/Request) action with `holdValue: true`, `_request` returns the previous response while the new request is loading instead of returning `null`. Use `_request_details: .0.loading` to detect that a new call is still in-flight, and `_request_details: .0.error` to detect a failure (`0` is the latest call). #### Arguments ###### string The id of the request. #### Examples ###### Using a request response: ```yaml _request: my_request ``` Returns: The response returned by the request. ###### Using dot notation to get the data object from the response: ```yaml _request: my_request.data ``` ###### Using dot notation to get the first element of an array response: ```yaml _request: array_request.0 ``` ###### Using dot notation and block list indexes to get the name field from the element corresponding to the block index of an array response: ```yaml _request: array_request.$.name ``` --- url: https://docs.lowdefy.com/md/operators/_request_details.md --- # _request_details ``` (requestId: string): any ``` The `_request_details` operator returns detailed information of a request. If the request has not yet been called, the returned value is `null`. The response includes the following fields: - `blockId: string`: The id of the block from which the request was initiated. - `loading: boolean`: When `true`, the request is awaiting a response. - `payload: object`: The payload sent with the request. - `requestId: string`: The id of the request. - `response: object`: The response returned by the request. `null` while `loading` is true. - `responseTime: number`: The time taken to get the response in milliseconds. #### Arguments ###### string The id of the request. #### Examples ###### Using a request id: ```yaml _request_details: my_request ``` Returns: The details of the specified request. ###### Whether the latest call is still loading: ```yaml _request_details: my_request.0.loading ``` Returns: `true` while the latest call of the request awaits its response. The details are a list of the request's calls, latest first, so `0` is the latest call. Use it for a block's `loading`, for example a table's `loading` property. --- url: https://docs.lowdefy.com/md/operators/_secret.md --- # _secret ``` (key: string): any (all: boolean): any (arguments: { all?: boolean, key?: string, default?: any }): any ``` The `_secret` operator gets a value from the [`secret`](/context-and-secret) object. The `secrets` is a data object that contains sensitive information like passwords or API keys. The `_secret` operator can only be used in `connections` and `requests`. Secrets are read from environment variables on the server that start with `LOWDEFY_SECRET_`, (i.e. `LOWDEFY_SECRET_SECRET_NAME`). The name of the secret is the part after `LOWDEFY_SECRET_`. Since environment variables can only be strings, secrets can be JSON encoded, and parsed using the [`_json.parse`](/_json) method. #### Arguments ###### string If the `_secret` operator is called with a string argument, the value of the key in the `secrets` object is returned. If the value is not found, `null` is returned. ###### boolean If the `_secret` operator is called with boolean argument `true`, the entire `secrets` object is returned. ###### object - `all: boolean`: If `all` is set to `true`, the entire `secrets` object is returned. One of `all` or `key` are required. - `key: string`: The value of the key in the `secrets` object is returned. If the value is not found, `null`, or the specified default value is returned. One of `all` or `key` are required. - `default: any`: A value to return if the `key` is not found in `secrets`. By default, `null` is returned if a value is not found. #### Examples ###### Get the value of `MY_SECRET` from `secrets`: ```yaml _secret: MY_SECRET ``` ```yaml _secret: key: MY_SECRET ``` Returns: The value of `MY_SECRET` in `secrets`. ###### Get the entire `secret` object: ```yaml _secret: true ``` ```yaml _secret: all: true ``` Returns: The entire `secrets` object. ###### Return a default value if the value is not found: ```yaml _secret: key: MY_SECRET default: Not so secret ``` Returns: The value of `MY_SECRET`, or `"Not so secret"`. --- url: https://docs.lowdefy.com/md/operators/_state.md --- # _state ``` (key: string): any (all: boolean): any (arguments: { all?: boolean, key?: string, default?: any, }): any ``` If used in a block, the `_state` operator gets a value from the [`state`](/page-and-app-state) object. The `state` is a data object specific to the page it is in. The value of `input` blocks are available in `state`, with their `blockId` as key. If used in an API endpoint, the `_state` operator gets a value from the API endpoints state. The `state` is a data object specific to that execution of the API endpoint. #### Arguments ###### string If the `_state` operator is called with a string argument, the value of the key in the `state` object is returned. If the value is not found, `null` is returned. Dot notation and [block list indexes](/lists) are supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. ###### boolean If the `_state` operator is called with boolean argument `true`, the entire `state` object is returned. ###### object - `all: boolean`: If `all` is set to `true`, the entire `state` object is returned. One of `all` or `key` are required. - `key: string`: The value of the key in the `state` object is returned. If the value is not found, `null`, or the specified default value is returned. Dot notation and [block list indexes](/lists) are supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. One of `all` or `key` are required. - `default: any`: A value to return if the `key` is not found in `state`. By default, `null` is returned if a value is not found. #### Examples ###### Get the value of `my_key` from `state`: ```yaml _state: my_key ``` ```yaml _state: key: my_key ``` Returns: The value of `my_key` in `state`. ###### Get the entire `state` object: ```yaml _state: true ``` ```yaml _state: all: true ``` Returns: The entire `state` object. ###### Dot notation: Assuming state: ```yaml my_object: subfield: 'Value' ``` then: ```yaml _state: my_object.subfield ``` ```yaml _state: key: my_object.subfield ``` Returns: `"Value"`. ###### Return a default value if the value is not found: ```yaml _state: key: might_not_exist default: Default value ``` Returns: The value of `might_not_exist`, or `"Default value"`. ###### Block list indices: Assuming `state`: ```yaml my_array: - value: 0 - value: 1 - value: 2 ``` then: ```yaml _state: my_array.$.value ``` Returns: `0` when used from the first block (0th index) in a list. ###### Using `_state` with `:set_state` in an API endpoint ```yaml - :set_state: product_id: _uuid: true - id: insert_product type: MongoDBInsertOne connectionId: tickets properties: doc: _id: _state: product_id name: _payload: product_name created_at: _date: now - id: insert_new_product_event type: MongoDBInsertOne connectionId: events properties: doc: _id: _uuid: true type: new-product description: _nunjucks: template: New product {{ product_name }} created by {{ user_name }}. on: product_name: _payload: product_name user_name: _user: name product_id: _state: product_id user: id: _user: id name: _user: name timestamp: _date: now ``` --- url: https://docs.lowdefy.com/md/operators/_step.md --- # _step ``` (stepId: string): any ``` The `_step` operator returns the response value from a previously executed step in an API routine. This operator is only available within API routines and allows later steps to access data from earlier steps. If the step has not yet been executed or is still executing, the returned value is `null`. Dot notation and array indexes are supported for accessing nested properties. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. #### Arguments ###### string The id of a step that has already been executed in the current API routine. #### Examples ###### Using a step response: ```yaml routine: - id: get_user type: MongoDBFindOne connectionId: users properties: query: email: _payload: email - :return: _step: get_user ``` Returns: The complete response object returned by the `get_user` step. ###### Using dot notation to access nested properties: ```yaml routine: - id: get_user type: MongoDBFindOne connectionId: users properties: query: email: _payload: email - id: get_orders type: MongoDBFind connectionId: orders properties: query: userId: _step: get_user._id # Access the _id field from get_user ``` ###### Using array indexes: ```yaml routine: - id: get_products type: MongoDBFind connectionId: products properties: query: category: 'electronics' - :return: first_product: _step: get_products.0 # Access the first element of the array first_product_name: _step: get_products.0.name # Access a property of the first element ``` ###### Chaining multiple steps: ```yaml routine: - id: create_order type: MongoDBInsertOne connectionId: orders properties: doc: items: _payload: items total: _payload: total - id: send_confirmation type: SendGridMail connectionId: email-service properties: to: _payload: customer_email template_data: order_id: _step: create_order.insertedId # Use the inserted document's ID - :return: success: true order_id: _step: create_order.insertedId email_sent: _step: send_confirmation.success ``` --- url: https://docs.lowdefy.com/md/operators/_string.md --- # _string The `_string` operator can be used to run javascript [`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) methods. > This operator can be used as a [`_build`](/_build) operator method. # Operator methods: ## _string.charAt ``` (arguments: {on: string, index: number}): string (arguments: [on: string, index: number]): string ``` The `_string.charAt` method returns a string consisting of [the single UTF-16 code (character) unit located at the specified offset](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charAt) into the string. ## _string.concat ``` (strings: string[]): string ``` The `_string.concat` method [concatenates](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/concat) strings. For label-style interpolation (`"Updates (5)"`), prefer [`_string.format`](#format) — it's more concise and renders `null`/`undefined` values as empty strings. ## _string.format ``` (template: string, ...values: any[]): string (arguments: { template: string, on?: object }): string ``` The `_string.format` method substitutes placeholders in a template string with values. Two input forms are supported: **Positional (array form)** — `{0}`, `{1}`, ... refer to elements after the template: ```yaml _string.format: - 'Updates ({0})' - _request: get_counts.0.update ``` **Named (object form)** — `{name}` placeholders are looked up in `on`: ```yaml _string.format: template: 'Updates ({count}) since {date}' on: count: _request: get_counts.0.update date: _date.format: - YYYY-MM-DD - _state: lastSync ``` **Behavior**: - `null` and `undefined` values render as an empty string. This often makes `_if_none` unnecessary — `_string.format: ['Updates ({0})', null]` produces `'Updates ()'` directly. - Missing positional indexes or named keys render as an empty string. - Non-string values are coerced via `String(v)`. For object/array values, use [`_json.stringify`](/_json) first. - To include a literal `{` or `}`, use `{{` or `}}`. - Use [`_nunjucks`](/_nunjucks) when you need conditionals, loops, or filters. ## _string.endsWith ``` (arguments: { on: string, searchString: string, length?: number }): boolean (arguments: [ on: string, searchString: string, length?: number ]): boolean ``` The `_string.endsWith` method determines whether a string [ends with the characters of a specified string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith), returning `true` or `false` as appropriate. ## _string.includes ``` (arguments: { on: string, searchString: string, position?: number }): boolean (arguments: [ on: string, searchString: string, position?: number ]): boolean ``` The `_string.includes` method determines whether [one string may be found within another string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes), returning `true` or `false` as appropriate. ## _string.indexOf ``` (arguments: { on: string, searchValue: string, fromIndex?: number }): number (arguments: [ on: string, searchValue: string, fromIndex?: number ]): number ``` The `_string.indexOf` method returns the index within string of the [first occurrence of the specified value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf), starting the search at `fromIndex`. Returns `-1` if the value is not found. ## _string.length ``` (string: string): number ``` The `_string.length` method returns the [length](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length) of a string. ## _string.lastIndexOf ``` (arguments: { on: string, searchValue: string, fromIndex?: number }): number (arguments: [ on: string, searchValue: string, fromIndex?: number ]): number ``` The `_string.lastIndexOf` method returns the index within string of the [last occurrence of the specified value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf), searching backwards from `fromIndex`. Returns `-1` if the value is not found. ## _string.match ``` (arguments: { on: string, regex: string, regexFlags?: string }): string[] ``` The `_string.match` method returns the [result of matching a string against a regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match). ## _string.normalize ``` (arguments: {on: string, form?: enum}): string (arguments: [on: string, form?: enum]): string ``` The `_string.normalize` method returns the [Unicode Normalization Form](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize) of the string. ## _string.padEnd ``` (arguments: { on: string, targetLength: number, padString?: string }): string (arguments: [ on: string, targetLength: number, padString?: string ]): string ``` The `_string.padEnd` method [pads the string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd) with a given string (repeated, if needed) so that the resulting string reaches a given length. The padding is applied from the end of the string. ## _string.padStart ``` (arguments: { on: string, targetLength: number, padString?: string }): string (arguments: [ on: string, targetLength: number, padString?: string ]): string ``` The `_string.padStart` method [pads the string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd) with a given string (repeated, if needed) so that the resulting string reaches a given length. The padding is applied from the start of the string. ## _string.repeat ``` (arguments: {on: string, count: number}): string (arguments: [on: string, count: number]): string ``` The `_string.repeat` method returns a string which contains [the specified number of copies of the string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat) on which it was called, concatenated together. ## _string.replace ``` (arguments: { on: string, regex: string, newSubstr: string, regexFlags?: string }): string ``` The `_string.replace` method returns a string with [some or all matches of a pattern replaced by a replacement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace). ## _string.search ``` (arguments: { on: string, regex: string, regexFlags?: string }): number ``` The `_string.search` method executes a [search for a match between a regular expression and a string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/search). It returns the index of the first match between the regular expression and the given string, or `-1` if no match was found. ## _string.slice ``` (arguments: { on: string, start: number, end?: number }): string (arguments: [ on: string, start: number, end?: number ]): string ``` The `_string.slice` method [extracts a section](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) of a string. ## _string.split ``` (arguments: {on: string, separator?: string}): string[] (arguments: [on: string, separator?: string]): string[] ``` The `_string.split` method [divides a string into an array of substrings](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split), splitting on the provided separator. ## _string.startsWith ``` (arguments: { on: string, searchString: string, position?: number }): boolean (arguments: [ on: string, searchString: string, position?: number ]): boolean ``` The `_string.startsWith` method determines whether a string [starts with the characters of a specified string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith), returning `true` or `false` as appropriate. ## _string.substring ``` (arguments: { on: string, start: number, end?: number }): string (arguments: [ on: string, start: number, end?: number ]): string ``` The `_string.startsWith` method returns [the part of the string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substring) between the `start` and `end` indexes, or to the end of the string. ## _string.toLowerCase ``` (string: string): string ``` The `_string.toLowerCase` method converts the string to [lower case](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase). ## _string.toUpperCase ``` (string: string): string ``` The `_string.toUpperCase` method converts the string to [upper case](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase). ## _string.trim ``` (string: string): string ``` The `_string.trim` method [removes whitespace from both ends](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim) of a string. ## _string.trimEnd ``` (string: string): string ``` The `_string.trimEnd` method [removes whitespace from the end](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimEnd) of a string. ## _string.trimStart ``` (string: string): string ``` The `_string.trimStart` method [removes whitespace from the start](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimStart) of a string. --- url: https://docs.lowdefy.com/md/operators/_subtract.md --- # _subtract ``` ([minuend: number, subtrahend: number]): number ``` The `_subtract` operator takes an array of two numbers as input and returns the second number subtracted from the first. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### array An array of two numbers. #### Examples ###### Subtract a number: ```yaml _subtract: - 12 - 4 ``` Returns: `8` --- url: https://docs.lowdefy.com/md/operators/_sum.md --- # _sum ``` (values: any[]): number ``` The `_sum` operator takes the sum of the values given as input. If a value is not a number, the value is skipped. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments #### array An array of values to add. #### Examples ###### Two numbers: ```yaml _sum: - 3 - 4 ``` Returns: `7` ###### Array of numbers: ```yaml _sum: - 1 - 2 - 3 - 4 ``` Returns: `10` ###### Non-numbers are skipped: ```yaml _sum: - 1 - null - 3 - "four" - 5 ``` Returns: `9` --- url: https://docs.lowdefy.com/md/operators/_switch.md --- # _switch ``` (arguments: {branches: {if: boolean, then: any}[], default: any}): any ``` The `_switch` operator evaluates an array of conditions and returns the `then` argument of the first item for which the `if` argument evaluates to `true`. If no condition evaluates to `true`, the value of the `default` argument is returned. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### object - `branches:` `if: boolean`: The boolean result of a test. `then: any`: The value to return if the test is `true`. - `default: any`: The value to return if all the `if` tests are `false`. #### Examples ###### Return a value based on a series of conditions: ```yaml _switch: branches: - if: _eq: - x - y then: A - if: _eq: - x - z then: B default: C ``` Returns: `"C"` since both of the `if` tests are `false`. --- url: https://docs.lowdefy.com/md/operators/_t.md --- # _t ``` (key: string): string (arguments: { key: string, values?: object, locale?: string, }): string ``` The `_t` operator translates a string key against the app's `config.i18n` message catalogs, with [ICU MessageFormat](https://formatjs.io/docs/core-concepts/icu-syntax/) interpolation. It's the primary way app authors localize their own strings. Resolution order: the active locale's messages → `en-US` messages → built-in framework messages → the key itself. The active locale is resolved from the user's preference (`SetLocale`/`localStorage`), then the browser's `navigator.languages` best-fit, then `config.i18n.defaultLocale`. Lowdefy always uses `en-US` as the missing-key fallback, so plugin and module authors should ship `en-US` translations as a baseline. Configure messages on `config.i18n.messages.{locale}`: ```yaml config: i18n: defaultLocale: en-US locales: - { code: en-US, label: English, antd: en_US, dayjs: en } - { code: de-DE, label: Deutsch, antd: de_DE, dayjs: de } messages: en-US: greeting: 'Hello, {name}!' cart.items: '{count, plural, one {# item} other {# items}}' de-DE: greeting: 'Hallo, {name}!' cart.items: '{count, plural, one {# Artikel} other {# Artikel}}' ``` `_t` is also used by the engine for built-in framework strings (e.g. `engine.action.loading`, `engine.validation.fieldRequired`). Override any built-in by adding the same key under `config.i18n.messages.{locale}`. **Inside plugin code:** the runtime equivalent for custom block and action plugins is `methods.translate(key, values)`. Same lookup chain, same overrides — see the [i18n concept page](/i18n) for plugin authoring patterns and the `./messages` manifest format. #### Arguments ###### string The translation key to look up. ###### object - `key: string`: The translation key. - `values: object`: Values to interpolate into the message via ICU MessageFormat. - `locale: string`: BCP 47 locale code to force this lookup into a specific locale, regardless of the user's active locale. #### Examples ###### Translate a simple key: ```yaml _t: greeting ``` Returns: the active-locale message at `messages.{active}.greeting`, or the `en-US` message, or the key. ###### Interpolate values: ```yaml _t: key: greeting values: name: _user: name ``` Returns: e.g. `"Hallo, Ada!"` when active is `de-DE`. ###### Pluralize via ICU MessageFormat: ```yaml _t: key: cart.items values: count: _state: itemCount ``` Returns: `"3 items"` / `"3 Artikel"` etc., respecting the locale's plural rules. ###### Force a specific locale: ```yaml _t: key: greeting locale: en-US ``` Returns: the en-US message regardless of the active locale. ###### Override a built-in framework string: ```yaml config: i18n: defaultLocale: de-DE locales: - { code: de-DE, label: Deutsch, antd: de_DE, dayjs: de } messages: de-DE: engine.validation.fieldRequired: 'Pflichtfeld' engine.action.loading: 'Laden' ``` The engine's built-in messages (used by `Validate`, `Request`, etc.) now render in German. --- url: https://docs.lowdefy.com/md/operators/_theme.md --- # _theme ``` (key: string): any (all: boolean): any (arguments: { all?: boolean, key?: string, default?: any, }): any ``` The `_theme` operator gets a value from the app's antd theme seed tokens, as configured in `theme.antd.token` in `lowdefy.yaml`. This is useful for accessing theme values in expressions, such as setting colors or sizes dynamically based on the app's theme. > The `_theme` operator is **client-side only**. It accesses seed tokens only — for derived tokens (like hover/active color variants), use CSS variables instead: `var(--ant-color-primary-hover)`. See the [Theming](/theming) concept page for details on configuring your app's theme. #### Arguments ###### string If the `_theme` operator is called with a string argument, the value of that token key from `theme.antd.token` is returned. If the token is not found, `null` is returned. ###### boolean If the `_theme` operator is called with boolean argument `true`, the entire seed token object is returned. ###### object - `all: boolean`: If `all` is set to `true`, the entire seed token object is returned. One of `all` or `key` are required. - `key: string`: The value of the specified token is returned. If not found, `null` or the specified default value is returned. One of `all` or `key` are required. - `default: any`: A value to return if the token key is not found. By default, `null` is returned. #### Examples ###### Get the primary color token: ```yaml _theme: colorPrimary ``` Returns: `"#1677ff"` (or whatever `colorPrimary` is set to in your theme config). ###### Get a token with a default value: ```yaml _theme: key: colorBgContainer default: '#ffffff' ``` Returns the value of `colorBgContainer` from the theme, or `"#ffffff"` if not set. ###### Get all seed tokens: ```yaml _theme: all: true ``` Returns the entire `theme.antd.token` object. ###### Use in a block property: ```yaml - id: my_paragraph type: Paragraph properties: content: The theme's primary color is set to... style: .element: color: _theme: colorPrimary ``` --- url: https://docs.lowdefy.com/md/operators/_type.md --- # _type ``` (type: enum): boolean (arguments: { type: string, on?: any, key?: string }): boolean ``` The `_type` operator performs a type test on an object, and returns true if the object is of the specified type. The regex operator has shorthand argument definitions that can be used on web client. > This operator can be used as a [`_build`](/_build) operator method. #### Arguments ###### object - `type: enum`: __Required__ - The type to test. Can be one of: - `string` - `array` - `date` - `object` - `boolean` - `number` - `integer` - `null` - `undefined` - `none` (`null` or `undefined`) - `empty` (`null`, `undefined`, `''`, or `[]` - note that `0`, `false` and `{}` are not empty) - `primitive` (`undefined`, `null`, `string`, `number`, `boolean`, or `date`) - `on: any`: The value to test. One of `on` or `key` must be specified unless the operator is used in an input block. - `key: string`: The key of a value in `state` to test. One of `on` or `key` must be specified unless the operator is used in an input block. ###### string The type to test. The string shorthand can only be used in an input block, and the tested value will be the block's value. #### Examples ###### Check if a value is a number: ```yaml _type: type: number on: _state: input ``` Returns: `true` if a number. ###### Using the key of the value in `state`: ```yaml _type: type: number key: input ``` Returns: `true` if a number. ###### Using the value of the block in which the operator is evaluated: ```yaml id: input type: TextInput validate: - message: This field is required. status: error pass: _not: _type: empty ``` Returns: `true` if the input has a value. This is the test the `required` block field applies. ###### Check if a value is empty: ```yaml _type: type: empty on: _state: selected_tags ``` Returns: `true` if `selected_tags` is `null`, `undefined`, `''` or `[]`, and `false` for any other value, including `0`, `false` and `{}`. ###### Test if an id in the `urlQuery` is undefined or null: ```yaml _type: type: none on: _url_query: id ``` Returns: `true` if the id is none, --- url: https://docs.lowdefy.com/md/operators/_uri.md --- # _uri The `_uri` operator [encodes and decodes](https://en.wikipedia.org/wiki/Percent-encoding) Uniform Resource Identifiers (URI). It encodes characters that are not in the limited US-ASCII characters legal within a URI. > This operator can be used as a [`_build`](/_build) operator method. # Operator methods: ## _uri.decode ``` (value: string): string ``` The `_uri.decode` method decodes a string that has been uri-encoded. It uses [`decodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent). #### Arguments ###### string The string to decode. #### Examples ###### Decode a base64 string: ```yaml _uri.decode: http%3A%2F%2Fusername%3Apassword%40www.example.com%3A80%2Fpath%2Fto%2Ffile.php%3Ffoo%3D316%26bar%3Dthis%2Bhas%2Bspaces%23anchor ``` Returns: `"http://username:password@www.example.com:80/path/to/file.php?foo=316&bar=this+has+spaces#anchor"`. ## _uri.encode ``` (value: string): string ``` The `_uri.encode` uri-encodes a string. It uses [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent). #### Arguments ###### string The string to encode. #### Examples ###### Encode a string as base64: ```yaml _uri.encode: http://username:password@www.example.com:80/path/to/file.php?foo=316&bar=this+has+spaces#anchor ``` Returns: `"http%3A%2F%2Fusername%3Apassword%40www.example.com%3A80%2Fpath%2Fto%2Ffile.php%3Ffoo%3D316%26bar%3Dthis%2Bhas%2Bspaces%23anchor"`. --- url: https://docs.lowdefy.com/md/operators/_url_query.md --- # _url_query ``` (key: string): any (all: boolean): any (arguments: { all?: boolean, key?: string, default?: any, }): any ``` The `_url_query` operator gets a value from the [`urlQuery`](/page-and-app-state) object. The `urlQuery` is a data object that is set as the [`https://en.wikipedia.org/wiki/Query_string`] of the app URL. It can be set when linking to a new page using the [`Link`](/link) action, and can be used to set data like a `id` when switching to a new page. Unlike `input`, the `urlQuery` is visible to the user, and can be modified by the user. > __DO NOT__ set any private or personal information to `urlQuery`; all data set to `urlQuery` are accessible publicly. Setting a `id` that can be guessed, like an incremental `id`, can lead to security issues, since users can easily guess and access data for other `ids`. If the page is reloaded, the `urlQuery` is not lost. By using `urlQuery`, you can make links containing data that can be shared by users. By default, `_url_query` accesses the `url_query` object from the [page](/page-and-app-state) the operator is used in. `urlQuery` objects are serialized to JSON, allowing nested objects or arrays to be specified. To put a value like a record id in the URL path instead, as in `/tickets/1234`, give the page a [`path`](/page-paths) with placeholders and read them with [`_path_params`](/_path_params). Path parameters never merge into `urlQuery`. Because the query string is user controlled, a key named `__proto__`, `constructor`, `prototype`, `__defineGetter__`, `__defineSetter__`, `__lookupGetter__` or `__lookupSetter__` is skipped while the rest of the query is parsed as usual — a URL of `?__proto__=1&my_key=2` gives a `urlQuery` of `{ my_key: 2 }`. A value that cannot be deserialized is kept as the raw string. #### Arguments ###### string If the `_url_query` operator is called with a string argument, the value of the key in the `urlQuery` object is returned. If the value is not found, `null` is returned. Dot notation and [block list indexes](/lists) are supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. ###### boolean If the `_url_query` operator is called with boolean argument `true`, the entire `urlQuery` object is returned. ###### object - `all: boolean`: If `all` is set to `true`, the entire `urlQuery` object is returned. One of `all` or `key` are required. - `key: string`: The value of the key in the `urlQuery` object is returned. If the value is not found, `null`, or the specified default value is returned. Dot notation and [block list indexes](/lists) are supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. One of `all` or `key` are required. - `default: any`: A value to return if the `key` is not found in `urlQuery`. By default, `null` is returned if a value is not found. #### Examples ###### Get the value of `my_key` from `urlQuery`: ```yaml _url_query: my_key ``` ```yaml _url_query: key: my_key ``` Returns: The value of `my_key` in `urlQuery`. ###### Get the entire `urlQuery` object: ```yaml _url_query: true ``` ```yaml _url_query: all: true ``` Returns: The entire `urlQuery` object. ###### Dot notation: Assuming urlQuery: ```yaml my_object: subfield: 'Value' ``` then: ```yaml _url_query: my_object.subfield ``` ```yaml _url_query: key: my_object.subfield ``` Returns: `"Value"`. ###### Return a default value if the value is not found: ```yaml _url_query: key: might_not_exist default: Default value ``` Returns: The value of `might_not_exist`, or `"Default value"`. ###### Block list indices: Assuming `urlQuery`: ```yaml my_array: - value: 0 - value: 1 - value: 2 ``` then: ```yaml _url_query: my_array.$.value ``` Returns: `0` when used from the first block (0th index) in a list. --- url: https://docs.lowdefy.com/md/operators/_user.md --- # _user ``` (key: string): any (all: boolean): any (arguments: { all?: boolean, key?: string, default?: any, }): any ``` The `_user` operator gets a value from the [`user`](/user-object) object — the caller Lowdefy resolves for the signed-in user in their active organization. It has a [fixed shape](/user-object); see that page for the available fields. #### Arguments ###### string If the `_user` operator is called with a string argument, the value of the key in the `user` object is returned. If the value is not found, `null` is returned. Dot notation and [block list indexes](/lists) are supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. ###### boolean If the `_user` operator is called with boolean argument `true`, the entire `user` object is returned. ###### object - `all: boolean`: If `all` is set to `true`, the entire `user` object is returned. One of `all` or `key` are required. - `key: string`: The value of the key in the `user` object is returned. If the value is not found, `null`, or the specified default value is returned. Dot notation and [block list indexes](/lists) are supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. One of `all` or `key` are required. - `default: any`: A value to return if the `key` is not found in `user`. By default, `null` is returned if a value is not found. #### Examples ###### Get the value of `name` from `user`: ```yaml _user: name ``` ```yaml _user: key: name ``` Returns: The value of `name` in `user`. ###### Get the entire `user` object: ```yaml _user: true ``` ```yaml _user: all: true ``` Returns: The entire `user` object. ###### Dot notation: Assuming user: ```yaml id: abc123 name: User Name my_object: subfield: 'Value' ``` then: ```yaml _user: my_object.subfield ``` ```yaml _user: key: my_object.subfield ``` Returns: `"Value"`. ###### Return a default value if the value is not found: ```yaml _user: key: might_not_exist default: Default value ``` Returns: The value of `might_not_exist`, or `"Default value"`. ###### Block list indices: Assuming `user`: ```yaml id: abc123 name: User Name my_array: - value: 0 - value: 1 - value: 2 ``` then: ```yaml _user: my_array.$.value ``` Returns: `0` when used from the first block (0th index) in a list. # Operator methods: ## _user.hasRole ``` (role: string): boolean ``` The `_user.hasRole` method returns `true` if the `user.roles` array contains the given role. It returns `false` when the user is not logged in, when `user.roles` is missing, or when the role is not present. The `user.roles` value is read directly from the `user` object and must be an array of strings. An error is thrown if `user.roles` is defined but is not an array, or if the argument is not a string. #### Examples ###### Check for a single role: ```yaml _user.hasRole: admin ``` Returns: `true` if `user.roles` includes `"admin"`. ## _user.hasSomeRoles ``` (roles: string[]): boolean ``` The `_user.hasSomeRoles` method returns `true` if the `user.roles` array contains at least one of the given roles. It returns `false` when the user is not logged in, when `user.roles` is missing, or when none of the roles match. The `user.roles` value is read directly from the `user` object and must be an array of strings. An error is thrown if `user.roles` is defined but is not an array, or if the argument is not an array of strings. For checking a single role, use [`_user.hasRole`](#hasRole). #### Examples ###### Check for any of several roles: ```yaml _user.hasSomeRoles: - admin - support ``` Returns: `true` if `user.roles` includes `"admin"` or `"support"`. ## _user.hasAllRoles ``` (roles: string[]): boolean ``` The `_user.hasAllRoles` method returns `true` if the `user.roles` array contains every one of the given roles. It returns `false` when the user is not logged in, when `user.roles` is missing, or when any required role is not present. The `user.roles` value is read directly from the `user` object and must be an array of strings. An error is thrown if `user.roles` is defined but is not an array, or if the argument is not an array of strings. For checking a single role, use [`_user.hasRole`](#hasRole). #### Examples ###### Require all of several roles: ```yaml _user.hasAllRoles: - admin - support ``` Returns: `true` only if `user.roles` includes both `"admin"` and `"support"`. --- url: https://docs.lowdefy.com/md/operators/_uuid.md --- # _uuid ``` (void): string ``` The `_uuid` operator creates [UUIDs](https://en.wikipedia.org/wiki/Universally_unique_identifier). A UUID is a random identifier that is, for all practical purposes, unique. It looks like: ``` 123e4567-e89b-12d3-a456-426614174000 ``` #### Arguments ###### default: `_uuid: true`, `_uuid: null` or `_uuid: undefined` returns a version 4 UUID. #### Examples ###### Generate a v4 uuid: ```yaml _uuid: null ``` Returns: A version 4 UUID. # Operator methods: ## _uuid.v1 ``` (void): string ``` Create a version 1 (timestamp) UUID. #### Arguments The `_uuid.v1` method does not take any arguments. #### Examples ###### Generate a version 1 UUID: ```yaml _uuid.v1: null ``` Returns: A version 1 UUID. ## _uuid.v3 ``` ({name: string | string[], namespace: string | string[]}): string ([name: string | string[], namespace: string | string[]]): string ``` Create a version 3 (namespace w/ MD5) UUID. #### Arguments ###### object: If the `_uuid` operator is called with arguments, it can be one of the following: - `name: string | string[]`: A string or an array. - `namespace: string | string[]`: A string or an Array[16] - Namespace UUID. #### Examples ###### Generate a version 3 UUID: ```yaml _uuid.v3: name: hello namespace: world ``` Returns: A version 3 UUID. ## _uuid.v4 ``` (void): string ``` Create a version 4 (random) UUID. #### Arguments The `_uuid.v4` method does not take any arguments. #### Examples ###### Generate a version 4 UUID: ```yaml _uuid.v4: null ``` Returns: A version 4 UUID. ## _uuid.v5 ``` ({name: string | string[], namespace: string | string[]}): string ([name: string | string[], namespace: string | string[]]): string ``` Create a version 5 (namespace w/ SHA-1) UUID. #### Arguments ###### object: If the `_uuid` operator is called with arguments, it can be one of the following: - `name: string | string[]`: A string or an array. - `namespace: string | string[]`: A string or an Array[16] - Namespace UUID. #### Examples ###### Generate a version 5 UUID: ```yaml _uuid.v5: name: hello namespace: world ``` Returns: A version 5 UUID. --- url: https://docs.lowdefy.com/md/operators/_var.md --- # _var ``` (key: string): any (arguments: { key: string, default?: any, }): any ``` The `_var` operator gets a value from the `vars` object, specified by a [`_ref`](/ref) operator when referencing a file. > The `_var` operator is a build time operator: it is evaluated when the app configuration is being built. This means it is not evaluated dynamically as the app is running, and can be used anywhere in the configuration as long as the resulting configuration files are valid YAML. > For module-level variables (passed by the app developer in `lowdefy.yaml` `modules[].vars`), use [`_module.var`](/_module) instead. `_module.var` works at any depth within module files without threading vars through each `_ref`. #### Arguments ###### string If the `_var` operator is called with a string argument, the value of the key in the `vars` object is returned. If the value is not found, `null` is returned. Dot notation is supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. Reserved key names such as `__proto__` and `constructor` are rejected as a config error, failing the build. ###### object - `key: string`: The value of the key in the `vars` object is returned. If the value is not found, `null`, or the specified default value is returned. Dot notation is supported. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` reads the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key first, and a plain key that is present always wins: a nested `a.b.c` shadows a literal `a.b` key, and a present `a` blocks the literal key even when it holds a string or number rather than an object. A literal dotted key is only reached where the plain key is absent, so escape it to address one reliably. Reserved key names such as `__proto__` and `constructor` are rejected as a config error, failing the build. - `default: any`: A value to return if the `key` is not found in `vars`. By default, `null` is returned if a value is not found. #### Examples ###### Using a standardized input label template: ```yaml blocks: - id: name type: TextInput properties: label: _ref: path: label.yaml vars: title: Name description: Your name and surname. descriptionTextColor: '#546358' - id: age type: NumberInput properties: label: _ref: path: label.yaml vars: title: Age description: Your age. ``` ```yaml # label.yaml title: _var: title extra: _var: description span: 8 colon: false extraStyle: color: _var: key: descriptionTextColor default: '#333333' ``` Returns: ```yaml blocks: - id: name type: TextInput properties: label: title: Name extra: Your name and surname. span: 8 colon: false extraStyle: color: '#546358' - id: age type: NumberInput properties: label: title: Age extra: Your age. span: 8 colon: false extraStyle: color: '#333333' ``` --- url: https://docs.lowdefy.com/md/operators/_yaml.md --- # _yaml The `_yaml` parses and writes YAML strings. # Operator methods: ## _yaml.parse ``` ({on: string, options?: object}): object ([on: string, options?: object]): object ``` The `_yaml.parse` method parses a YAML string into an object. #### Arguments ###### object - `on: string`: String to parse. - `options?: object`: Optional settings. See the [YAML.parse: method here](https://eemeli.org/yaml/#parse-options) for supported settings. #### Examples ###### Parse a YAML string: ```yaml _yaml.parse: on: | key: Value boolean: true array: - 1 - 2 ``` or: ```yaml _yaml.parse: - | key: Value boolean: true array: - 1 - 2 ``` Returns: ``` key: Value boolean: true array: - 1 - 2 ``` ## _yaml.stringify ``` ({on: any, options?: object}): string ([on: any, options?: object]): string ``` The `_yaml.stringify` method creates a YAML string from an object. #### Arguments ###### object - `on: any`: The object to stringify. - `options?: object`: Optional settings. See the [YAML.stringify: method here](https://eemeli.org/yaml/#tostring-options) for supported settings. #### Examples ###### Stringify an object as YAML: ```yaml _yaml.stringify: on: key: Value boolean: true array: - 1 - 2 ``` or: ```yaml _yaml.stringify: - key: Value boolean: true array: - 1 - 2 ``` Returns (as a string): ```text key: Value boolean: true array: - 1 - 2 ``` --- url: https://docs.lowdefy.com/md/controls/for.md --- # :for ``` ({:for: string, :in: any[], :do: routine}): void ``` The `:for` control iterates over an array, executing a routine for each item sequentially. The routine defined by the `:do` key is executed for each item within the array defined by the `:in` key. This routine is executed sequentially for each item in the array. The value of the current array item can be accessed using the [`_item`](/_item) operator with the item key set by the `:for` key. This operator is only available within the `:for` and [`:parallel_for`](/:parallel_for) controls. #### Keys - `:for: string`: __Required__ - Used to define the key that can be used to access the value of the array item of the current iteration. - `:in: array`: __Required__ - Used to define the array of data to iterate over. - `:do: routine`: __Required__ - Used to define the routine that will be executed for each array item. #### Examples ###### Simple Example ```yaml - :for: count :in: - 1 - 2 - 3 :do: :log: _item: count ``` Logs: ``` 1 2 3 ``` ###### Calculate Order Total ```yaml - :set_state: order_total: 0 - :for: item :in: _payload: order_items :do: - :set_state: item_total: _math.multiply: - _item: item.price - _item: item.quantity - :set_state: order_total: _math.add: - _state: order_total - _state: item_total - :log: _string.concat: - _item: item.name - ' x ' - _item: item.quantity - ' = $' - _state: item_total - :return: total: _state: order_total ``` ###### Setting Active Users in MongoDB ```yaml - id: get_active_users type: MongoDBAggregation connectionId: activity-log properties: pipeline: - $match: $expr: $gte: - $timestamp - $dateTrunc: date: $$NOW unit: day - $group: _id: $user_id - :for: user :in: _step: get_active_users :do: - id: update_user_status type: MongoDBUpdateOne connectionId: users properties: filter: _id: _item: user._id update: $set: last_processed: _date: now status: 'active' ``` --- url: https://docs.lowdefy.com/md/controls/if.md --- # :if ``` ({:if: boolean, :then: routine, :else: routine}): void ``` The `:if` control executes different routines based on a boolean condition. It evaluates the condition and runs the `:then` branch if true, or the optional `:else` branch if false. Generally other operators are used to evaluate the test. The `:if` control also works in client [event action lists](/events-and-actions) — there each branch is a list of actions, and the same syntax and semantics apply. The condition uses JS truthiness — any truthy value takes the `:then` branch. This differs from an action's `skip` field, which only skips when it evaluates to exactly `true`. #### Keys - `:if: boolean`: __Required__ - The boolean result of a test or value. - `:then: routine`: __Required__ - The routine to execute if the test result is true. - `:else: routine`: The routine to execute if the test result is false. #### Examples ###### Simple Example ```yaml routine: - :if: true :then: :log: 'Test was true' :else: :log: 'Test was false' ``` Logs: `Test was true` ###### Set User Features ```yaml routine: - :if: _eq: - _payload: user_type - 'premium' :then: - :set_state: discount: 0.2 features: ['unlimited', 'priority_support'] :else: - :set_state: discount: 0 features: ['basic'] ``` ###### Optional Fraud Check ```yaml routines: - :if: _gte: - _payload: order_amount - 1000 :then: - :log: 'High-value order detected, applying fraud checks' - id: fraud_check type: FraudDetectionAPI connectionId: fraud-service properties: amount: _payload: order_amount customer_id: _payload: customer_id - :if: _gt: - _step: fraud_check.risk_score - 0.7 :then: :reject: 'Order flagged for manual review' - id: create_payment_intent type: StripeRequest connectionId: stripe properties: paymentIntents: create: - amount: 2000 currency: eur payment_method_types: ['card'] ``` ###### In a client event action list ```yaml events: onClick: - :if: _not: _state: skip_reopen :then: - id: reopen_ticket type: Request params: reopen_ticket - id: redirect type: Link params: pageId: ticket-view :else: - id: show_blocked type: DisplayMessage params: content: Ticket cannot be reopened. status: warning ``` --- url: https://docs.lowdefy.com/md/controls/log.md --- # :log ``` ({:log: any, :level: string}): void ``` The `:log` control outputs messages to the server console at specified log levels (debug, info, warn, error). #### Keys - `:log: any`: __Required__ - The value to be log to the console. - `:level: string`: Level of log. One of debug, info, warn, error. The default is info. #### Examples ###### String Message ```yaml :log: 'New Log Message' ``` ###### Log a JSON Object ```yaml :log: message: Log Message user_id: _user: id timestamp: _date: now ``` ###### Set log levels ```yaml - :log: payload: _payload: true :level: debug - :log: 'Processing started' :level: 'info' - :log: message: Multiple failed attempts attempts: _payload: attempt_count user: _user: id :level: warn - :log: message: An error occurred. :level: error ``` --- url: https://docs.lowdefy.com/md/controls/parallel.md --- # :parallel ``` ({:parallel: routine[]}): void ``` The `:parallel` control executes multiple independent routines simultaneously. All routines within the `:parallel` array are started at the same time and the control waits for all of them to complete before continuing. If any routine returns with an error, rejection, or explicit return, the parallel control will exit early with that status (prioritizing errors first, then rejections, then returns). #### Keys - `:parallel: routine[]`: __Required__ - An array of routines that will be executed in parallel. #### Examples ###### Fetch from multiple sources in parallel ```yaml routine: - :parallel: - id: fetch_user_stats type: MongoDBAggregation connectionId: users properties: pipeline: - $match: account_id: _payload: account_id - $group: _id: null total_users: $sum: 1 active_users: $sum: $cond: - $eq: ['$status', 'active'] - 1 - 0 - id: fetch_revenue type: KnexRaw connectionId: analytics_db properties: query: | SELECT SUM(amount) as total_revenue, COUNT(*) as transaction_count FROM transactions WHERE account_id = :account_id AND created_at >= NOW() - INTERVAL '30 days' parameters: account_id: _payload: account_id - id: fetch_tickets type: AxiosHttp connectionId: tickets-api properties: url: '/tickets' method: get params: organization_id: _payload: account_id status: 'open' - :return: user_stats: _step: fetch_user_stats revenue: _step: revenue tickets: _step: fetch_tickets ``` --- url: https://docs.lowdefy.com/md/controls/parallel_for.md --- # :parallel_for ``` ({:parallel_for: string, :in: any[], :do: routine, :concurrency?: integer}): void ``` The `:parallel_for` control executes a routine for each item in an array simultaneously. Each iteration has access to its array item through the [`_item`](/_item) operator using the key specified in `:parallel_for`. Unlike sequential [`:for`](/:for), all iterations start at the same time, making it ideal for processing independent items where order doesn't matter. Set `:concurrency` to run at most that many iterations at once: the next item starts when one finishes. Use it when every iteration calls a rate-limited service (an AI or enrichment API), so a large array does not send hundreds of calls at the same moment. The control waits for all iterations to complete before continuing. Every item runs, with or without `:concurrency`; then the first error, else the first rejection, else the first return (in item order) ends the control with that result. #### Keys - `:parallel_for: string`: __Required__ - Used to define the key that can be used to access the value of the array item of the executing iteration. - `:in: any`: __Required__ - Used to define the array of data to iterate over. - `:do: routine`: __Required__ - Used to define the routine that will be executed for each array item in parallel. - `:concurrency: integer`: The maximum number of iterations running at once, a positive integer (operators allowed, for example `_payload`). Without it every iteration starts at once. #### Examples ###### Update Multiple Users ```yaml - :parallel_for: user_id :in: _payload: user_ids :do: id: update_last_seen type: MongoDBUpdateOne connectionId: users properties: filter: _id: _item: user_id update: $set: last_seen: _date: now ``` ###### Call a rate-limited API, five domains at a time ```yaml - :parallel_for: domain :in: _payload: domains :concurrency: 5 :do: id: lookup type: AxiosHttp connectionId: company_api properties: url: /lookup params: domain: _item: domain ``` --- url: https://docs.lowdefy.com/md/controls/reject.md --- # :reject ``` ({:reject: string | Error, :cause: any, :status: integer, :body: any}): void ``` The `:reject` control is used to return a user-friendly error to the client when validation fails or business rules are violated. Unlike [`:throw`](/:throw), which indicates a system error, `:reject` represents an expected failure condition that should be communicated to the user. The control immediately stops routine execution and returns with a `"reject"` status. Importantly, `:reject` does not trigger `:catch` blocks in [`:try`](/:try) statements: the reject flows past every enclosing `:try`, so a `:catch` in an outer `:try` never runs either, while `:finally` still runs. This makes `:reject` ideal for handling validation and business logic errors separately from system errors. Choose [`:throw`](/:throw) when a step failed and the routine may recover; choose `:reject` when the routine decided the request cannot be fulfilled. In a [webhook endpoint](/api#webhook-endpoints), `:status` and `:body` set the HTTP answer: the endpoint answers `:status` (an integer from 400 to 499, default 400) with `:body` sent as is. Without `:body`, the body is `{ "error": { "code": "rejected", "message": } }`. A `:reject` in an `InternalApi` endpoint the webhook calls with `CallApi` sets the answer the same way. In a [link endpoint](/api#link-endpoints), `:status` sets the status the link answers with, and `:body` is a build error, since a link answers with no body. On any other endpoint, `:status` and `:body` are a build error. `:reject` also takes an Error as its message, such as the error a `:catch` caught: `:reject: { _error: true }`. The reject message is then "Something went wrong.", unless the error was a `UserError` or an auth refusal (`AuthenticationError`, `AuthorizationError` or `TwoFactorEnrolmentRequiredError`), whose message is kept. To reject with the caught error's real message, name it: `:reject: { _error: message }`. See [`_error`](/_error). #### Keys - `:reject: string | Error`: __Required__ - The error message that will be returned in the response object of the API call result, or an Error whose message to use. - `:cause: any`: Additional metadata that will be returned with the error message. - `:status: integer`: In a webhook or link endpoint, or an `InternalApi` endpoint one calls, the HTTP status the endpoint answers with, from 400 to 499. Defaults to 400. - `:body: any`: In a webhook endpoint, or an `InternalApi` endpoint a webhook calls, the body the webhook answers with, sent as is. #### Examples ###### Example ```yaml - id: check_inventory type: MongoDBFindOne connectionId: products properties: query: product_id: _payload: product_id - :if: _lt: - _step: check_inventory.quantity - _payload: requested_quantity :then: :reject: _string.concat: - "Insufficient inventory. Only " - _step: check_inventory.quantity - " items available" ``` ###### Answer a webhook with a status ```yaml - :if: _eq: - _step: find_ticket - null :then: :reject: Ticket not found. :status: 404 :body: error: code: not_found ``` ###### Reject with Additional Context ```yaml - :if: _not: _step: get_user.email_verified :then: :reject: "Please verify your email before proceeding" :cause: user_id: _step: get_user._id email: _step: get_user.email account_created_at: _step: get_user.created_at ``` --- url: https://docs.lowdefy.com/md/controls/return.md --- # :return ``` ({:return: any, :content_type: string}): void ``` The `:return` control immediately ends the execution of an API endpoint routine and returns a successful response with the specified data. Any routine steps after a `:return` are not executed. The control accepts any value type (objects, arrays, strings, numbers, null) and marks the API call as successful. When used within conditional controls like [`:if`](/:if) or [`:switch`](/:switch), it provides a way to exit early with a success status and return data to the client. In a [webhook endpoint](/api#answering-with-plain-text), `:content_type: text/plain` sends the returned string as a plain-text body instead of JSON, for a handshake that wants a text reply. The value must be a string. `text/plain` is the only accepted type, and `:content_type` on any other endpoint is a build error. The `:return` control also works in client [event action lists](/events-and-actions) — it ends the whole event successfully, the event's `catch` actions do not run, and any actions after it are recorded as skipped. Inside a `catch` action list, `:return` ends the remaining catch actions the same way, but the event keeps its error result — `success` stays `false`. #### Keys - `:return: any`: __Required__ - The value that will be returned in the response object of the API call result. - `:content_type: string`: In a webhook endpoint, `text/plain` answers with the returned string as a plain-text body. #### Examples ###### Return Step Result ```yaml - id: get_company type: MongoDBInsertOne connectionId: companies properties: doc: _id: _payload: company_id - :return: _step: get_company ``` ###### Answer a webhook handshake with plain text ```yaml - :if: _ne: - _payload: query.validationToken - null :then: :return: _payload: query.validationToken :content_type: text/plain ``` ###### Switch with multiple returns ```yaml - id: get_user_subscription type: MongoDBFindOne connectionId: subscriptions properties: query: user_id: _user: id - :switch: - :case: _not: _step: get_user_subscription :then: :return: plan: "free" features: ["basic_access"] limit: 10 - :case: _eq: - _step: get_user_subscription.type - "premium" :then: :return: plan: "premium" features: ["full_access", "priority_support", "api_access"] limit: -1 :default: :return: plan: "standard" features: ["standard_access", "email_support"] limit: 100 ``` --- url: https://docs.lowdefy.com/md/controls/set_state.md --- # :set_state ``` ({:set_state: object}): void ``` The `:set_state` control sets values in the server-side state object during API endpoint execution. It accepts an object where each key-value pair is saved to the state, making data available throughout the routine via the `_state` operator. This server state is isolated to the current API call and does not persist between calls or affect client state. The control supports nested paths using dot notation and can store any data type. A dot is always a separator unless it is escaped with `\.`, which makes it a literal character in the key — `a\.b` sets the key `a.b`; inside a double-quoted YAML string the escape must be written `\\.`. On an unescaped path each segment is tried as a plain key of the state first, and a plain key that is present always wins: with `a` already in state, `a.b` writes `b` inside `a`, and it does so even when `a` holds a string or number, replacing that value with an object. A literal dotted key is only written where the plain key is absent, and once a key matches there is no retry with a longer join — the value is written inside the key that matched, with missing intermediate objects created as needed. Reserved key names such as `__proto__` and `constructor` are rejected as a config error. Values are evaluated before being set, allowing dynamic state updates based on previous operations. #### Keys - `:set_state: object`: __Required__ - Object to be saved to server state. #### Examples ###### Accumulating Values ```yaml - :set_state: total: 0 count: 0 - :for: product :in: _payload: products :do: - :set_state: total: _sum: - _state: total - _item: product.price count: _sum: - _state: processed_count - 1 - :return: total: _state: total count: _state: count ``` ###### Dot Notation ```yaml - :set_state: user: _step: get_user - :set_state: user.profile.name: _string.concat: - _state: user.profile.first_name - ' ' - _state: user.profile.last_name ``` ###### Shared Value ```yaml - :set_state: product_id: _uuid: true - id: insert_product type: MongoDBInsertOne connectionId: tickets properties: doc: _id: _state: product_id name: _payload: product_name created_at: _date: now - id: insert_new_product_event type: MongoDBInsertOne connectionId: events properties: doc: _id: _uuid: true type: new-product description: _nunjucks: template: New product {{ product_name }} created by {{ user_name }}. on: product_name: _payload: product_name user_name: _user: name product_id: _state: product_id user: id: _user: id name: _user: name timestamp: _date: now ``` --- url: https://docs.lowdefy.com/md/controls/switch.md --- # :switch ``` ({:switch: [{:case: boolean, :then: routine], :default: routine}): void ``` The `:switch` control evaluates multiple conditions sequentially and executes the routine for the first matching case. Each case in the `:switch` array contains a `:case` condition and a `:then` routine. The control evaluates conditions from top to bottom, executing only the first matching case's `:then` block. If no cases match, the optional `:default` routine is executed. This control is ideal for handling multiple mutually exclusive conditions more elegantly than nested [`:if`](/:if) statements. The `:switch` control also works in client [event action lists](/events-and-actions) — there each `:then` and `:default` is a list of actions, and the same syntax and semantics apply. #### Keys - `:switch: array`: __Required__ - `:case: boolean`: __Required__ - The boolean result of a test. - `:then: routine`: __Required__ - The routine to execute if the test is `true`. - `:default: routine`: The routine to execute if all the `:case` tests are `false`. #### Examples ###### Validation ```yaml - :switch: - :case: _not: _payload: email :then: :reject: "Email is required" - :case: _not: _regex: on: _payload: email pattern: ^[^@]+@[^@]+\\.[^@]+$ :then: :reject: "Invalid email format" - :case: _lt: - _string.length: _payload: password - 8 :then: :reject: "Password must be at least 8 characters" - :case: _not: _eq: - _payload: password - _payload: confirm_password :then: :reject: "Passwords do not match" :default: [] ``` ###### Switch with multiple returns ```yaml - id: get_user_subscription type: MongoDBFindOne connectionId: subscriptions properties: filter: user_id: _user: id - :switch: - :case: _not: _step: get_user_subscription :then: :return: plan: "free" features: ["basic_access"] limit: 10 - :case: _eq: - _step: get_user_subscription.type - "premium" :then: :return: plan: "premium" features: ["full_access", "priority_support", "api_access"] limit: -1 :default: :return: plan: "standard" features: ["standard_access", "email_support"] limit: 100 ``` --- url: https://docs.lowdefy.com/md/controls/throw.md --- # :throw ``` ({:throw: string | Error, :cause: any}): void ``` The `:throw` control creates a system error that immediately stops routine execution and returns with an "error" status. Unlike [`:reject`](/:reject), which handles expected user errors, `:throw` is designed for unexpected system failures, programming errors, or critical issues that require attention. The key difference is that `:throw` can be caught and handled by [`:try`](/:try)/`:catch` blocks, while [`:reject`](/:reject) cannot. This makes `:throw` ideal for recoverable system errors where you might want to implement fallback logic. Choose `:throw` when a step failed and the routine may recover; choose [`:reject`](/:reject) when the routine decided the request cannot be fulfilled. `:throw` also takes an Error as its message, such as the error a `:catch` caught: `:throw: { _error: true }`. The error is rethrown as it is, with its own class and cause, and `:cause` is ignored. The client receives it with the message "Something went wrong.", keeping its `code` and `statusCode`, unless it was a `UserError`, whose message is kept. To show the user the caught error's real message, name it: `:throw: { _error: message }`. See [`_error`](/_error). #### Keys - `:throw: string | Error`: __Required__ - The error message that will be returned in the response object of the API call result, or an Error to rethrow. - `:cause: any`: Additional metadata that will be returned with the error message. #### Examples ###### Rethrow a caught error ```yaml - :try: - id: update_stock type: MongoDBUpdateOne connectionId: inventory properties: filter: sku: _payload: sku update: $inc: quantity: _payload: change :catch: - :log: message: Stock update failed reason: _error: message :level: error - :throw: _error: true ``` The `:catch` logs the real message, then rethrows the error. The client receives it with the message "Something went wrong.", with the error's `code` and `statusCode`. ###### Throw on invalid status ```yaml - :switch: - :case: _eq: - _payload: status - accept :then: - :return: message: Accepted - :case: _eq: - _payload: status - deny :then: - :return: message: Denied - :case: _eq: - _payload: status - more_info_required :then: - :return: message: More info required :default: - :throw: 'Invalid status type' :cause: status: _payload: status ``` ###### Throw on invalid configuration ```yaml - id: get_system_config type: MongoDBFindOne connectionId: config properties: query: name: 'max_file_size' - :if: _not: _step: get_system_config :then: :throw: 'System configuration missing: max_file_size not found' - :if: _gt: - _payload: file_size - _step: get_system_config.value :then: :throw: 'File size exceeds system limit' :cause: maxAllowed: _step: get_system_config.value received: _payload: file_size - :return: message: 'File upload validated' ``` --- url: https://docs.lowdefy.com/md/controls/try.md --- # :try ``` ({:try: routine, :catch: routine, :finally: routine}): void ``` If one control in the routine chain defined in the `:try` key fails by throwing an error, the steps in the list following the failed control will not be executed. To handle any errors thrown by a routine element, a routine can be provided using `catch` key. The routine defined using the `:finally` key will be run regardless of whether or not the routine throws an error. A [`:reject`](/:reject) inside the `:try` routine is __not__ caught. `:reject` is the routine's own reply — it stops the routine, returns a `"reject"` status to the caller, and flows past every enclosing `:catch`. `:finally` still runs. Use [`:throw`](/:throw) for failures a `:catch` should handle. An error a `:catch` handles is an outcome, not a fault, when it is a request or service error (a provider's 404, a timeout) or a [`:throw`](/:throw): it is logged at debug level only. A config, operator or internal error in the `:try` is still logged as an error, with its config location, and the `:catch` runs as well. Inside the `:catch` routine, the [`_error`](/_error) operator returns the error that was caught, so the routine can branch on its `statusCode` or `code`, or read its `message`. `_error` returns the error of the innermost `:catch` that encloses it, and `null` outside a `:catch`. #### Keys - `:try: routine`: __Required__ - The routine to be run. - `:catch: routine`: The routine to execute if any step in the routine defined errors. Runs on a thrown error only — a `:reject` is not caught. - `:finally: routine`: The routine to be run regardless of the out come of the initial routine. Runs on success, error and reject. #### Examples ###### Branch on the caught error ```yaml - :try: - id: get_customer type: AxiosHttp connectionId: crm properties: url: /customers params: id: _payload: id :catch: - :if: _eq: [{ _error: statusCode }, 404] :then: - :reject: Customer not found - :throw: Customer lookup failed ``` ###### Try external API with fallback ```yaml - :try: - id: get_weather type: AxiosHttp connectionId: weather-api properties: url: /current params: city: _payload: city :catch: - id: get_default_weather type: MongoDBFindOne connectionId: weather-cache properties: query: city: _payload: city - :return: weather: _if: test: _step: get_weather then: _step: get_weather.data else: _step: get_default_weather ``` ###### Try database operation with fallback ```yaml - :try: - id: update_user type: MongoDBUpdateOne connectionId: database properties: filter: _id: _payload: user._id update: $set: last_login: _date: now :catch: - :log: message: 'Database update failed, creating new user record' user_id: _payload: user._id :level: warn - id: create_user type: MongoDBInsertOne connectionId: database properties: doc: _id: _payload: user._id last_login: _date: now created_at: _date: now :finally: - :log: message: 'User login process completed' :level: info - :return: success: true user_id: _payload: user._id ``` --- url: https://docs.lowdefy.com/md/controls/while.md --- # :while ``` ({:while: boolean, :do: routine}): void ``` The `:while` control implements a while loop within a routine. The routine defined in the `:do` key will be repeated as long as the test defined using the `:while` key is true. #### Keys - `:while: boolean`: __Required__ - The test that will determine whether or not the defined routine should execute again. - `:do: routine`: __Required__ - The routine to run while the test condition holds. The `:while` condition is evaluated fresh before every iteration, against the routine's current `state` and `steps`. This is how the loop terminates - the routine in `:do` changes the state or step results that the condition reads. Unlike `:for`, a `:while` loop does not index the step results of its body. A `:while` has no item to index by, so each iteration overwrites the previous iteration's step results, and `_step: my_step` inside or after the loop reads the value from the latest iteration. There is no iteration cap and no timeout. A condition that the routine in `:do` never falsifies loops forever, exactly as it would in JavaScript. #### Examples ###### Simple Example ``` - :set_state: count: 0 - :while: _lt: - _state: count - 5 :do: - :log: _state: count - :set_state: count: _sum: - _state: count - 1 ``` Logs: ``` 0 1 2 3 4 ``` --- url: https://docs.lowdefy.com/md/migration/upgrade-guide.md --- # Upgrade Guide This guide explains how the Lowdefy upgrade system works — independent of any specific version. For version-specific migration details, see: - [Version 6 to Version 7](/v6-to-v7) - [Version 5 to Version 6](/v5-to-v6) - [Version 4 to Version 5](/v4-to-v5) - [Version 3 to Version 4](/v3-to-v4) ## Quick start ```bash npx lowdefy upgrade ``` That's it. The command reads your current version from `lowdefy.yaml`, fetches the latest codemods, resolves the upgrade chain, and walks you through each step. ## How Lowdefy versioning works Lowdefy follows [semantic versioning](https://semver.org/). Major versions (4.0 → 5.0) contain breaking changes. Minor and patch versions are backwards-compatible. When breaking changes ship, the `@lowdefy/codemods` package is updated with migration prompts for the new version. The `lowdefy upgrade` command uses this package to guide the migration. ## The `lowdefy upgrade` command The upgrade command: 1. Reads your current version from `lowdefy.yaml`. 2. Queries npm for the latest stable Lowdefy version (or uses `--to`). 3. Fetches `@lowdefy/codemods@latest` — a single package containing all migration prompts for all versions. 4. Resolves the upgrade chain — which intermediate versions need codemods. 5. Presents migration prompts phase by phase. 6. Updates `lowdefy.yaml` after each phase and suggests a git commit. ### Options | Option | Description | Default | |---|---|---| | `--to ` | Target version to upgrade to | Latest stable | | `--plan` | Show the upgrade plan without executing | | | `--resume` | Resume a previously interrupted upgrade | | | `--config-directory` | Change config directory | `.` | | `--log-level` | Log level (debug, info, warn, error) | `info` | See the [CLI reference](/cli) for the full command documentation. ## Multi-version upgrades The upgrade command handles jumps across multiple versions. If you're on 4.5.0 and the latest is 6.0.0, the command resolves the full chain: ``` 4.5.0 → 5.0.0 → 6.0.0 ``` Each step in the chain is a **phase**. The command walks you through all prompts for phase 1, updates `lowdefy.yaml`, suggests a commit, then moves to phase 2. This gives you clean git history per version jump. The version chain is computed from `registry.json` in the `@lowdefy/codemods` package. Each entry specifies a target version and a `from` semver range. The resolver walks the entries in order until the current version reaches the target. ## How prompts work Every codemod is a self-contained markdown prompt. For each codemod, the CLI presents three options: 1. **Copy prompt to clipboard** — paste into Claude Code, ChatGPT, Cursor, or any AI coding tool. 2. **View migration guide** — read the step-by-step instructions and apply changes manually. 3. **Skip for now** — mark as pending. Return later with `--resume`. Each prompt includes: - What changed and why - Before/after YAML examples - How to find affected files (glob patterns, grep patterns) - Edge cases to watch for - Verification steps This format works with any AI coding tool. Copy the prompt, paste it into your tool of choice, and let it scan your project and apply changes. Prompts can also be followed manually without any AI tool. ## Resuming interrupted upgrades If you stop mid-upgrade (skip a codemod, close the terminal, or hit an error), run: ```bash npx lowdefy upgrade --resume ``` The command reads `.lowdefy/upgrade-state.json` — a file that tracks which phases and codemods have completed — and picks up where you left off. If you skip codemods, the build warns about pending migrations: ``` ⚠ Warning: 2 codemod(s) were skipped during upgrade. Run "npx lowdefy upgrade --resume" to complete them. Or add --skip-codemod-check to suppress this warning. ``` Suppress this with `--skip-codemod-check` or `cli.skipCodemodCheck: true` in `lowdefy.yaml`. ## Manual upgrades If you prefer to upgrade without the command: 1. Read the version-specific migration guide (e.g. [V5 to V6](/v5-to-v6)). 2. Apply each breaking change manually using the before/after examples. 3. Update the `lowdefy:` version in `lowdefy.yaml`. 4. Run `lowdefy dev` and fix any remaining issues. The upgrade command automates this process, but every change is documented in the migration guides for manual reference. --- url: https://docs.lowdefy.com/md/migration/auth-upgrade.md --- # Auth Upgrade (BetterAuth) This guide covers upgrading an app from the previous NextAuth-shaped `auth:` configuration to the BetterAuth-based auth system. The `auth:` block is restructured, sessions are database-backed, the session user shape changes, and roles move onto organization memberships. Every app with auth is affected. API auth strategies (`auth.strategies`) are purely additive — an app with no strategies configured behaves exactly as before. ## Summary of breaking changes | Change | Old | New | |---|---|---| | Database required | Optional (JWT-only possible) | `auth.database` required for any login method | | Provider type names | `GoogleProvider`, `GitHubProvider`, ... | `Google`, `GitHub`, ... (`OpenIDConnectProvider` → `GenericOAuth`) | | Callbacks / events | `auth.callbacks`, `auth.events` (JS plugins) | `auth.hooks` — bindings to `InternalApi` endpoints | | Session strategy | `session.strategy: jwt` supported | Database sessions only; `maxAge` → `expiresIn` | | Collection names | `users`, `accounts`, `sessions` | `user-*` convention, fixed by the adapter | | `userFields` | Mapped provider claims onto the session | Removed — each mapped key has a new home (see below) | | Secret | `NEXTAUTH_SECRET` env var | `auth.secret` with the `_secret` operator | | Auth UI | NextAuth built-in pages, `auth.theme` | Removed — the app provides its own pages | | Magic link | `EmailProvider` in `providers` | `auth.magicLink` + `auth.email` | | `authPages` | `signIn`, `signOut`, `error`, `verifyRequest`, `newUser` | `signIn`, `signUp`, `error`, `forgotPassword`, `resetPassword`, `verifyEmail` | | OIDC profile claims | ~20 claims copied onto the session user | Only `id`, `name`, `email`, `image`, `email_verified` | | `sub` | `_user.sub` = provider's OIDC subject | Removed — `_user.id` is the internal id (a different value) | | Roles | On the user record via `userFields` | App roles on `member.appRoles` (`_user.roles`); the organization's `owner`/`admin`/`member` tier on `member.role` (`_user.org_roles`) | | Attributes | `_user.app_attributes`, `_user.global_attributes` | One merged `_user.attributes` bag | | Signed-out request to any URL | `/404` if the page is missing, sign-in if protected | Sign-in for every URL, in apps whose unlisted ids default to protected | | Missing request / endpoint id, signed out (auth'd app) | `500` (missing) vs `401` (protected) | `401` for both | | 404 page | Had to serve signed-out visitors refused a protected page | Only ever serves signed-in visitors, in a protected app | ## Upgrade steps ### 1. Restructure the `auth:` block Rename provider types (drop the `Provider` suffix; `OpenIDConnectProvider` becomes `GenericOAuth` with `discoveryUrl` and a `scopes` array). Convert the session block — `strategy` is gone, `maxAge` becomes `expiresIn`: ```yaml auth: session: expiresIn: 604800 # was maxAge updateAge: 86400 ``` Remove `theme`, `debug`, `advanced`, and `userFields` — they have no successor keys. `theme` styled NextAuth's built-in pages, which no longer exist. Every `userFields` mapping has a new home — see [the session user shape](#the-session-user-shape) below. ### 2. Add `auth.secret` The `NEXTAUTH_SECRET` / `AUTH_SECRET` environment variables are no longer read. Configure the secret explicitly: ```yaml auth: secret: _secret: BETTER_AUTH_SECRET ``` Set `LOWDEFY_SECRET_BETTER_AUTH_SECRET` to a secure random string (`openssl rand -base64 32`). Reusing your old secret value is fine — sessions are invalidated by the upgrade anyway. ### 3. Pin the canonical URL with `BETTER_AUTH_URL` Set the `BETTER_AUTH_URL` environment variable to your app's canonical origin (e.g. `https://app.example.com`), or declare it once as the current environment's `url` in [`config.environments`](/lowdefy-schema). Auth builds password-reset, magic-link and email-verification links — and the origins it trusts for the CSRF check and for `callbackURL` / `redirectTo` targets — from this value. When it is pinned, those links and origins are fixed. When it is unset, both come from each incoming request, so the host in an emailed link would come from the request too. A production server therefore refuses to start when it is unset and auth sends emails with links: any app with `auth.email` configured, since it sends verification, password-reset, magic-link and invitation emails. The error names the flows. Without `auth.email` the server starts and logs a warning, and auth trusts only the origin each request arrived on. The dev server (`lowdefy dev`) still uses the request origin. Pinning it is also needed behind a proxy that terminates TLS and forwards plain `http` (see [Deploy with Docker](/docker)). This replaces the old `NEXTAUTH_URL` variable. ### 3b. Declare trusted proxies Auth rate limits (for example 3 sign-in attempts per 10 seconds) count attempts per client address, and sessions record it. A self-hosted server takes the client address from the connection and does not read `X-Forwarded-For`, which any client can set. Behind a reverse proxy or load balancer, every request then comes from the proxy, so list its addresses in [`config.trustedProxies`](/lowdefy-schema) and the server reads the client address the proxy forwarded: ```yaml config: trustedProxies: - 10.0.0.0/8 ``` On Vercel nothing is needed: the platform supplies the client address. See [Deploy with Docker](/docker#behind-a-reverse-proxy). ### 4. Add `auth.database` Sessions are database-backed, so every app with a login method needs an adapter — including apps that previously ran JWT-only with no adapter: ```yaml auth: database: id: auth_db type: MongoDBAuthAdapter properties: uri: _secret: AUTH_DATABASE_URI ``` If you used the old `MongoDBAdapter`: the type is renamed, `databaseUri` becomes `uri`, and `options.databaseName` folds into the URI path. There is no `options.collections` mapping — collection names are fixed by the adapter. ### 5. Migrate MongoDB collections The adapter uses the `user-*` naming convention, and the names are fixed — there is no rename escape hatch: - `users` keeps its name. - Rename `accounts` to `user-accounts`. - Drop `sessions` and `verification_tokens` — the session format changes and users sign in again. If you configured custom collection names via the old `options.collections`, rename those collections to the fixed names instead. ### 6. Migrate `EmailProvider` to `magicLink` Magic-link login is a plugin with its own key, not a provider entry. `auth.email` no longer carries an inline SMTP transport — it references an `SMTP` connection by id. That one connection owns `from`, `replyTo`, the transport, and the delivery `filter`, and is shared by every email flow (magic links, verification, password reset, invitations): ```yaml connections: - id: email type: SMTP properties: from: noreply@example.com host: _secret: SMTP_HOST port: 587 auth: user: _secret: SMTP_USER pass: _secret: SMTP_PASS auth: magicLink: enabled: true expiresIn: 300 email: connectionId: email # an SMTP connection in connections[] ``` The client signs in with the `Login` action: `params: { magicLink: true, email: ... }`. **Auth emails obey the connection's delivery filter.** Because auth email now sends through the same `SMTP` connection your app uses for notification and routine mail, that connection's `filter` (dev catch-all, sandbox allowlist/regex, none in production) governs auth email too. This closes the old footgun where auth emails bypassed the filter and shipped to real inboxes while everything else was caught in development. **Branded HTML by default.** The four auth emails — verification, password reset, magic link, and invitation — render branded HTML from your `app.email` theme (logo, colour, layout), not the old plain-text link. There is no per-flow theme knob; one `app.email` brand covers them all. **Overriding a flow.** Point any flow at one of your own `notifications:` entries to replace its email with a bespoke notification template (its own copy, subject, and per-notification `theme` merged over `app.email`): ```yaml auth: email: connectionId: email templates: # all optional; unset → branded stock template verifyEmail: verify-email-notification resetPassword: reset-password-notification magicLink: magic-link-notification invitation: invite-notification ``` Each value is a notification id from `notifications:`. These `templates` keys are an email-flow namespace, separate from the `authPages` roles despite the shared `resetPassword` / `verifyEmail` spelling. An override template **must surface the framework `url` behind its call-to-action** — the engine injects `url` (and, for invitations, the accept-page link). A verify email with no verify link is a dead end, so a template that ignores `url` is a mistake. **`invitation.send` is removed.** The invitation email is now symmetric with the other three: branded stock `InvitationEmail` by default, an override notification when configured. The old `invitation.send` auth hook — whose only purpose was branding the invite before overrides existed — is gone. If you bound it, point `auth.email.templates.invitation` at a notification instead. ### 7. Rewrite callbacks and events as hooks `auth.callbacks` and `auth.events` are replaced by `auth.hooks`. A hook is not a plugin — it binds an auth lifecycle point to an `InternalApi` endpoint, and the endpoint's routine does the work, reading the hook payload with the `_payload` operator: ```yaml auth: hooks: - id: audit-login point: session.create.after endpointId: audit-login ``` Where old slots land: | Old | New point | |---|---| | `createUser` event | `user.create.after` | | `signIn` event | `session.create.after` | | `signOut` event | `session.delete.after` | | `linkAccount` event | `account.create.after` | | `updateUser` event | `user.update.after` | | `signIn` callback (allow / deny) | `session.create.before` — `:reject` vetoes the sign-in | | `jwt` callback | None — database sessions carry no JWT; persist data via a create hook instead | | `redirect` callback | None — post-login navigation is the `Login` action's `callbackUrl` | | `session` callback | None — the session shape is fixed; store extra data in attributes or app collections | Custom callback/event plugin logic moves into the endpoint's routine; anything a routine can't express goes in a custom request plugin called from the routine. ### 8. Build your auth pages BetterAuth ships no UI. The app owns its sign-in, sign-up, and error pages, and `auth.authPages` points at them: ```yaml auth: authPages: signIn: /login signUp: /signup error: /auth-error ``` The old `signOut`, `verifyRequest`, and `newUser` keys are removed. New optional keys for email flows: `signUp`, `forgotPassword`, `resetPassword`, `verifyEmail`. `twoFactor` is a further key, and unlike those it is **required** when `auth.twoFactor.enabled` is true — the build fails without it, because the engine routes the two-factor challenge to that page itself. See [Two-Factor Authentication](/two-factor). `acceptInvitation` is a further key, naming the page that consumes an `?invitationId=` link. **Write its copy for the unauthenticated case.** Where one app sends another organization's invitations, the invitee accepts on the *sending* app, and `AcceptInvitation` sets the active organization inside its own transaction — so the accept page itself renders unauthenticated the moment the accept succeeds. That page must tell an already-signed-in invitee to sign in again **at the app they are on**, not at the app that sent the invitation: the active-organization policy runs at every session create and re-pins them here, while the production cookie prefix is shared across apps on one host, so signing in at the other app flips the shared session back the other way. Sign-in uses the `Login` action, which dispatches by parameter — `providerId` for OAuth, `magicLink: true`, or `email` + `password`. Email/password sign-up pages use the new `SignUp` action; social and magic-link "sign-up" pages use `Login`, since those methods create the account on first sign-in. Sign-in errors now surface inline (the `Login` action returns `error.code`) rather than as a `?error=` query parameter on the error page. ### 9. Migrate user records to the new model Roles move off the user record onto organization memberships, and the fused user-contact record splits into an app-owned `contact` and auth-owned `user` and `member` records. Neither the core upgrade nor any module ships a data migration for this: each app writes its own, once, against its auth database. Apps that never stored roles or app data on the user record only need the collection renames from step 5. For apps built on the [`modules-mongodb`](https://github.com/lowdefy/modules-mongodb) `user_contacts` convention — one combined record per person, with `is_user`, an `apps.{app_name}` map and `global_attributes` — the mapping is: - **One `users` row per contact with `is_user: true` in any app.** Keep the contact's `_id` as the user's `_id`, so `_user.id` and every record that stores it stay valid. The adapter stores ids as strings, so a contact keyed by an `ObjectId` needs its hex string here. Fill the user fields `name`, `email`, `email_verified`, `image`, `created_at` and `updated_at` from the contact. - **`global_attributes` becomes `users.attributes`.** - **One `user-members` row per enabled `apps.{app_name}` entry**, with `user_id` set to the user's `_id`, `organization_id` set to the app's pinned slug (`auth.organizations.org` — under `pinned` the slug is the organization's id), `app_roles` from the entry's `roles`, `attributes` from its `app_attributes`, `role: member` and a `created_at`. A disabled entry gets no row. - **The contact is reshaped in place**, without the `apps` map and without the `is_user`, `invite` and `disabled` flags. It stays the app's record of the person. - **At least one member per organization holds `role: owner`**, so someone can send invitations. Without one, the organization is the closed loop described under [Bootstrapping the first administrator](#bootstrapping-the-first-administrator). Write these rows with the stored field names, which are snake_case (`user_id`, `organization_id`, `app_roles`, `created_at`): the adapter maps BetterAuth's camelCase names, such as `appRoles` in the auth steps, to snake_case on the way in. The module side of the upgrade — vars, role catalog and column paths — is covered by the `user-admin` module's migration how-to (`docs/user-admin/how-to/migration.md` in `modules-mongodb`). ### 10. Sign in again Existing sessions are invalidated — the session format changed. There is nothing to migrate; users log in once after the upgrade. ## The session user shape The session user carries `id`, `name`, `email`, `image`, `email_verified`, `active_organization_id`, and the user record's own `profile` bag, plus three fields Lowdefy resolves from the caller's row in the active organization: - **`roles`** — the app's own role names, read from the active membership's `appRoles` array. These are what page and endpoint `auth.roles` gates match, and the only thing they match. - **`org_roles`** — the organization tier, from `member.role`: `owner`, `admin` or `member`. This is an administrative fact about the active organization, and it is **not a gate source** — `createAuthorize` reads only `roles`. An app that wants a page for organization administrators gates that page on one of its own app roles, and lets the write authority answer the administration question separately, at the step. - **`attributes`** — the one merged bag of authorization inputs: `user.attributes` (global) under the active `member.attributes` (per-organization), merged per key, member wins. **`_user.role` (singular) carries nothing.** It rides the session-user spread off the user record, and Lowdefy never writes it. It sits one character away from `roles`, so an app that gates on `_user.role` is gating on a constant — the gate either always passes or always fails, and never for the reason the author intended. Read `roles`. There is no `impersonatedBy` field on the session user. Impersonation is not part of the platform. Where old `_user` reads land: | Old read | New home | |---|---| | `_user.sub` | `_user.id` — a **different value**: the internal id, not the provider subject. The provider subject lives on the `user-accounts` collection (`accountId`). | | `_user.picture` | `_user.image` | | `_user.email_verified` | `_user.email_verified` — unchanged; the pre-upgrade OIDC claim and the resolved caller field already share this name. | | `_user.roles` | Unchanged read — but roles now resolve from the active organization membership's `appRoles`, granted via invitations and auth steps, not provider claims. | | `_user.orgRoles` | `_user.org_roles` — the organization tier from `member.role` (`owner`, `admin`, `member`). | | `_user.twoFactorEnrolled` | `_user.two_factor_enrolled` — whether the caller holds a factor satisfying `auth.twoFactor.required`. | | `_user.authMethod` | `_user.auth_method` — the strategy that authenticated the caller, on strategy-resolved callers only. | | `_user.app_attributes.*` | `_user.attributes.*` — per-app values live on the membership (`member.attributes`). | | `_user.global_attributes.*` | `_user.attributes.*` — global values live on the user (`user.attributes`); merged per key, member wins. | | Other OIDC claims (`given_name`, `phone_number`, `address`, ...) | Not in the session. Persist what you need at signup via an endpoint hook — profile data onto your contact collection, authorization inputs into attributes — and read it from there. | ## Page and API existence is no longer observable before you sign in This is a behaviour change every app inherits, not a two-factor feature. It removes an enumeration asymmetry — a logged-out caller used to be able to tell a real-but-protected id apart from an absent one by the response they got back. That is an information leak the engine design had accepted as a documented cost of following web convention, not an access-control failure: no protected page or request was ever reachable without a session. Nothing about who can *reach* a surface changes here — only what a signed-out caller can *learn* about which surfaces exist. **The rule.** When the caller is not authenticated, existence is never consulted. A protected app answers the sign-in page for any URL a logged-out visitor asks for — whether that URL is a real protected page or a typo that matches nothing. Existence is only resolved once there is a session to resolve it against. **Which apps change, which don't.** Only apps whose *unlisted* page ids default to protected are affected. The `auth.pages.protected: [...]` shape (an explicit protected list, everything else public) is **untouched** — a logged-out visitor typing a wrong URL still gets `/404`, because unlisted ids there are public. Every other shape treats an unlisted id as protected, so a logged-out typo now lands on the sign-in page, then resolves to `/404` once the visitor authenticates — one extra hop for a wrong URL. | Config | An unlisted page id is | |---|---| | `auth.pages.public: [...]` | protected | | `auth.pages.protected: true` | protected | | `auth.pages.protected: [...]` | public | | neither | public | **A known imprecision, so nobody files it as a bug.** The default the runtime reads for an unlisted id is a single build-resolved boolean, deliberately coarser than the glob-list config that produced it. In an app configured `auth.pages.public: ['docs/**']` (so unlisted ids default to protected), a signed-out visitor typing `docs/typo` gets the sign-in page rather than `/404`, even though the `docs/**` namespace was declared public. Every id that **is** in the build carries its own resolved decision, so no real page changes behaviour — only a URL matching nothing reads the coarse default. Shipping the pattern lists into the build artifact and re-matching globs per request was considered and rejected: the artifact exists precisely so the runtime reads a resolved decision and never re-derives access from patterns. **What you can delete.** In a protected app the 404 page now answers one question — "does a page with this id exist for this signed-in caller?" — instead of three. The auth logic some apps hand-write onto their 404 page (a session check, a conditional "sign in" link for the logged-out case) is now dead code: a logged-out visitor never reaches the 404 page, so it only ever renders for someone already signed in. Delete it. The build still forces the `404` page id public, unchanged, so the page itself is always reachable. **The API surface.** In an authenticated app a logged-out caller gets `401 Authentication required` for **every** request and endpoint id, present or absent. Two consequences for upgrading apps. A missing id used to surface as a `500`, so any error handling keyed on the status code will now see `401` where it previously saw `500`. And a typo'd `requestId` called from a logged-out browser now reads "Authentication required" rather than a not-found error — expected, given existence is no longer disclosed pre-auth. The **authenticated** case is unchanged: for a signed-in caller a missing id and a role-refused id both still surface as the same opaque "does not exist". A no-auth app is unchanged, and so is a system or routine context — the `401` rewrite is for a real session-less human on a protected surface, not for "no user" in general. **Websockets are unchanged.** The websocket surface was already symmetric — a logged-out caller could never distinguish a present subscription id from an absent one — so it needed no change and got none. It is called out here only so a reader does not conclude the surface was overlooked. ## Administering organizations Administering an organization means holding `admin` (or `owner`) in that organization — the `member.role` tier, on the caller's own member row there. There is no app-wide administering role, and no config key that grants administration across organizations: nothing to configure. Every auth step declares the authority it requires, and the step floor authorizes the caller against their member row in the organization the step names, so a caller who administers the team organization holds nothing in the customer organization. The steps that write the deployment-wide `user` row — `UpdateUserProfile`, `UpdateUserAttributes`, `BanUser`, `UnbanUser`, `DeleteUser`, `RevokeUserSessions` — additionally require the **target** to hold a member row in that organization, so an administrator can only reach people who are members of an organization they administer. ## Bootstrapping the first administrator Nothing grants organization authority implicitly. On a fresh `policy: pinned` + `signup: invite-only` deployment that is a closed loop: nobody can be invited, because inviting needs `invitation: ['create']` authority that nobody holds, and nobody can sign up, because the admission gate admits only members and pending invitees. Breaking the loop is one document inserted into the `user-invitations` collection, by hand, outside the platform: ```js { _id: , organization_id: 'team', email: 'admin@example.com', role: 'owner', app_roles: [], status: 'pending', inviter_id: 'bootstrap', expires_at: , created_at: new Date() } ``` **The field names are snake_case.** The adapter stores BetterAuth's camelCase fields (`organizationId`, `appRoles`, `inviterId`, `expiresAt`, `createdAt`) under snake_case names, as for the user and member rows above. A document written with the camelCase names matches no pending invitation for the organization, so the admission gate refuses the sign-up and the bootstrap never starts. **`organization_id` is the configured slug.** Under `pinned` the organization's id *is* `auth.organizations.org`, so there is no id to look up and the same document works in every environment. **The key is `_id`, not `id`.** The adapter maps the logical `id` to the physical `_id` inbound and back outbound, so every invitation the platform writes stores its id at `_id`. A document inserted literally as `{ id: … }` gets a driver-generated `ObjectId` `_id` plus a stray `id` field, after which the accept flow's invitation lookup never finds it — while the admission gate, which looks the invitation up by email, organization and status rather than by id, happily admits the person to sign up. The result is a user who can log in, an invitation that cannot be accepted, and no error pointing at either. Use `_id`. **The role is `owner`, not `admin`, and that is not cosmetic.** With no owner in the organization every membership guard is inert: `removeMember`'s creator protection engages only when the target holds the creator role, and `updateMemberRole`'s last-owner count only when the updater is the creator editing themselves. So a sole `admin` could demote themselves to `member`, or remove the last other `admin`, after which nobody anywhere holds `member: ['update']` or `invitation: ['create']` — and under `signup: invite-only` nobody can sign up to fix it. Seeding `owner` makes all three guards live, and costs nothing reachable: `owner`'s only statement beyond `admin`'s is `organization: ['delete']`, whose endpoint is disabled under `pinned` and has no step. **Deliver the accept URL by hand.** The accept URL is built only inside the invitation email sender, which only the `InviteMember` step reaches, so a hand-inserted invitation sends no email. Give the nominated person this link: ``` {baseUrlOrigin}{basePath}{authPages.acceptInvitation}?invitationId= ``` **The pre-accept state looks like a failure and is not.** Under `invite-only`, an invitee holding only a pending invitation gets a session with **no** active organization, and a session with no active organization resolves unauthenticated. So the nominated person signs up successfully and lands logged out everywhere. That is the carve-out working: it admits them just far enough to accept. The link is the only way forward — the front door alone does not finish the bootstrap. **Email verification is required before accepting.** Lowdefy sets a function-form `advanced.database.generateId`, which makes the organization plugin require a verified email for the accept-by-invitation-id action. Normal flow, not a workaround: verify the address, then open the link. **Under `signup: open` this route does not grant the role.** The auto-join hooks mint a `role: 'member'` member row before the accept, and accepting an invitation into an organization you already belong to only marks the invitation accepted — it never changes an existing membership. There the single write is the other one: the person signs up normally, and the operator sets `member.role: 'owner'` on the row they already have. That one field is the whole grant — no second write and no denormalized copy to leave stale, because nothing writes `user.role`. **Bootstrap is per-organization, not per-deployment.** An `admin` of the team organization holds no authority in the customer organization, so that organization's first `admin` needs its own inserted invitation. After the first one, each organization is self-sustaining: its owner can invite and promote from inside the app. ## Retired client actions Seven client actions are gone. An app that authors one fails at build with the unknown-action-type error; each has a named replacement, except the two that do not: | Retired action | Replacement | |---|---| | `ImpersonateUser` | **None.** The capability is retired. | | `StopImpersonating` | **None.** The capability is retired. | | `InviteMember` | The `InviteMember` step | | `CancelInvitation` | The `CancelInvitation` step | | `UpdateMemberRole` | The `UpdateMemberOrgRole` step (the `owner`/`admin`/`member` tier). App roles are the `UpdateMemberRoles` step. | | `RemoveMember` | The `RemoveMember` step | | `UpdateOrganization` | The `UpdateOrganization` step | The rule that produced the list also tells you which actions will keep existing: **a client action is an operation that mutates the caller's own session, and nothing else.** So `SetActiveOrganization`, `AcceptInvitation` and `LeaveOrganization` remain — each acts on the caller. An app administering **other** people's memberships authors a routine instead, and that is what earns it the per-step authority floor, an explicitly named target organization, and both role tiers (`appRoles` and `orgRole`) rather than one fused `role`. Two consequences. A `pinned` app never had these actions working in the first place — every organization route is disabled under `pinned` — so this reads as a removal only for `tenant` apps. And the step path is what makes cross-organization administration possible at all: a client action forwards no organization id, so it could only ever act on the session's active organization. ## Exposing MCP tools to assistants New in this release: a Lowdefy app can be an **OAuth 2.1 authorization server** and expose its API endpoints as authenticated tools to MCP clients (Claude, ChatGPT, IDE assistants). There is one MCP resource at `/api/mcp`; the organization a call acts in is a token claim chosen at authorization, not a URL segment. Enable it with `auth.oauthProvider` and tag endpoints in the `mcp` block. This is entirely additive — an app with no `mcp` endpoints and no `auth.oauthProvider` is unaffected. **One rule to internalise before you ship it: never list an MCP endpoint id in `auth.api.public`.** A single public tool suppresses the OAuth challenge for the whole route, so a newly connected assistant reports "connected" and never authenticates. The full model — the flow, the `organization_id` claim, `mcp:read` / `mcp:write` scopes, the consent and organization-picker pages, and switching or disconnecting with `RevokeMcpGrant` — is on the [MCP Server & OAuth](/mcp-oauth) page. The `mcp.agents` config key is removed; MCP agent tools are not supported. ## Where the details now live This guide is the migration path; the current behaviour is documented on the dedicated pages: - [Organizations & Multi-Tenancy](/organizations) — the `pinned` / `tenant` policies and the tenant wall. - [Auth Steps](/auth-steps) — the per-step authority model and every step's scope and permission. - [MCP Server & OAuth](/mcp-oauth) — the authenticated MCP surface. - [User Object](/user-object) — the fixed `_user` shape. ## What you don't need to change - **Page and API authorization** — `auth.pages` and `auth.api` (`protected` / `public` / `roles`) keep their shape and semantics. - **Page role lists** — existing role names keep working; roles are matched as strings exactly as before. - **Provider ids** — OAuth callback URLs are still `/api/auth/callback/`; keep your provider `id` values. - **`dev.mockUser`** — still available in the dev server. --- url: https://docs.lowdefy.com/md/migration/v6-to-v7.md --- # Version 6 to Version 7 This guide covers every breaking change between Lowdefy v6 and v7 that an app or plugin author has to act on. Most apps need three things: - **Node.js 24.** The CLI and servers no longer run on Node.js 22. - **The upgrade command.** `npx lowdefy upgrade` runs the v7 codemods: `react-icons-to-lucide` rewrites icon names, `clickable-html-data-events` adds `dataEvents` to `ClickableHtml` blocks, and `nested-event-bubbling` adds `bubble: true` where nested events depend on each other. The rest of this guide covers what the codemods don't change. - **The auth migration, if the app has `auth`.** NextAuth is replaced by BetterAuth, which has its own guide: [Auth Upgrade](/auth-upgrade). This guide covers only the auth changes outside that one. ```bash npx lowdefy upgrade ``` ## Summary of breaking changes | Change | Impact | Action | | -------------------------------------------------------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------- | | Node.js 24 is the minimum | All apps | Upgrade Node.js, and change `FROM node:22-slim` to `node:24-slim` in Dockerfiles | | NextAuth is replaced by BetterAuth | Apps with `auth` | Follow [Auth Upgrade](/auth-upgrade) | | With `auth.email`, production needs a pinned public URL | Apps that send auth emails | Set `BETTER_AUTH_URL` or the environment's `url` | | The client address comes from the connection | Self-hosted apps behind a proxy | List the proxy in `config.trustedProxies` | | react-icons names are not built in | Almost every app | Run `lowdefy upgrade`, or install the compatibility set | | An unknown icon name fails the build | Apps with old or misspelled names | Fix the name the error names | | Block and Ant Design icons are Lucide | All apps | None; check screens that depend on the old look | | `theme.icons.aliases` targets are Lucide names | Apps with aliases | Drop the `Lu` prefix: `LuReceipt` becomes `Receipt` | | Icon hover titles come from the new names | Apps that show Icon block hover titles | Set `title` where the text matters | | Events no longer bubble to outer blocks | Apps with clickable blocks inside clickable blocks or AgGrid rows | Run `lowdefy upgrade`, or set `bubble: true` on the inner event | | A declared `payloadSchema` is enforced on every caller | Apps whose endpoints declare `payloadSchema` | Fix the callers or the schema | | Webhook endpoints must be listed in `auth.api.public` | Apps with webhook endpoints | Add each webhook endpoint id to `auth.api.public` | | Webhooks run untrusted until a verifier passes | Webhooks that call protected endpoints | Add a `webhook.verify` request | | Webhooks answer plain JSON with set statuses | Webhook senders that read the response | Check dates, errors and `:reject` answers | | A detached `CallApi` runs as the user who started it | Detached runs of protected endpoints started by users | Make sure the user may call the target endpoint | | Module endpoints and agents need a signed-in caller | Apps with auth that call them signed out | List them in `auth.api.public` to keep them public | | Auth refusals keep their error class in the browser | `catch` actions that check `_error.name` | Branch on the refusal's class, not `ActionError` | | A refused signed-in caller gets `403`, a wrong method `405` | Outside callers that check status codes | Handle `403` and `405` | | HTTP 429 is a `ServiceError` | `catch` and `:catch` that check `RequestError` | Check `ServiceError`, or `statusCode` `429` | | MongoDB driver errors are `ServiceError`s | Code that reads the driver's error message | Read `code` and `hint` instead | | `_if` without `test` fails the build | Apps with a malformed `_if` | Add `test` | | A `CallMethod` to a missing block fails production builds | Apps with stale `CallMethod` targets | Fix the `blockId` | | An invalid `payloadSchema` or schedule payload fails the build | Apps with endpoint schemas or `schedules` | Fix the schema or the schedule's `payload` | | `_operator` names read at runtime need a list | Apps with a runtime `_operator` name | List the operators it may call in `operators` | | Dynamic blocks render only written blocks | Apps returning stored blocks as data | Use a policy with `ValidateDynamic` | | Escaped keys in Dynamic data keep their underscores | Apps that read `_typename`-style keys from Dynamic data | Read the key with both underscores | | `ClickableHtml` fires only listed data events | Apps that use `data-event` | Run `lowdefy upgrade`, or list the events in `dataEvents` | | `RenderNotification` `landingPage` is a page id | Apps with a notification landing page | Pass the landing page's id, not its path | | A `system` message in AI `messages` is refused | AI requests that build system messages into `messages` | Move it to `system`, or set `allowSystemInMessages: true` | | Files the server downloads for AI calls are limited | AI calls with `http:`, private or large file links | Use public `https:` links, or raise `fileDownload` | | AI calls are cancelled at the request timeout | Synchronous routines with long AI calls | Use `async: true` | | Agent hook payloads follow AI SDK 7 | Agent hooks that read the event payload | Check the fields the hook reads | | Cross-origin websocket upgrades are refused | Apps behind a proxy or CDN that rewrites `Host` | Forward the original `Host`, or set `X-Forwarded-Host` | | Websockets close with code `4401` when the session changes | Apps with websocket subscriptions and auth | None; check subscriptions after sign-out or an organization switch | | Change stream subscriptions run the `read` check | Connections with `read: false` used for `MongoDBChangeStream` | Remove `read: false` from the connection | | Block links accept only safe schemes | Apps with `href`s on other schemes | Use an allowed scheme or a relative URL | | `Pagination` events report the correct `skip` | Apps that corrected `_event: skip` | Remove the correction | | Block properties that were ignored now apply | Apps that set them | Check the pages that set them | | `ColorSelector` clears to `null` | Apps that read a cleared colour | Handle `null` | | `PhoneNumberInput` renders as a compact group | Apps with CSS for its addon, or `addonBg` | Update the CSS, and use `selectorBg` | | `TitleInput` and `ParagraphInput` show no edit icon | Apps that rely on the pencil icon | Set `editable.icon` | | Vercel functions check environment guards | Vercel apps with `guards` | Make the runtime variables match the build's | | Server-side Sentry runs on Vercel | Vercel apps with `SENTRY_DSN` | None; set `logger.sentry.server: false` to turn it off | | The agent MCP server is renamed `lowdefy` | Projects set up with `lowdefy agent-setup` | Rerun `lowdefy agent-setup` | | `lowdefy dev` uses the port you set or fails | Scripts that set a port that may be taken | Free the port, or set another | | `@lowdefy/e2e-utils` doesn't reuse a running server | e2e runs against `pnpm e2e:server` | Set `LOWDEFY_E2E_REUSE_SERVER=true` | | Generated servers are their own pnpm workspace | Apps inside a pnpm workspace | Remove `.lowdefy/*` from the parent's `pnpm-workspace.yaml` | | `react-icons` is not a server dependency | Plugins that import `react-icons` | Render `components.Icon` with a name, or add `react-icons` to the plugin | | Block plugins should pin `antd@6.6.5` | Block plugins that import `antd` | Pin `antd@6.6.5` | | Blocks must declare the types they run themselves | Block plugins that register their own actions or operators | List them in `meta.actions` and `meta.operators` | | `createIcon` takes an object | Code that calls `createIcon` from `@lowdefy/client` | Pass `{ icons, loadAllIcons }` | ## Node.js 24 Lowdefy v7 needs Node.js 24 or later. The CLI refuses to start on an older version, and the CLI and servers declare `"node": ">=24"` in `engines`. - **Docker.** `lowdefy init-docker` now writes a Dockerfile on `node:24-slim`. In a Dockerfile written by an earlier version, change every `FROM node:22-slim` line to `FROM node:24-slim`. - **Vercel.** The build output declares the `nodejs24.x` function runtime. Set the project's Node.js version to 24 so the install and build steps run on it too. - **CI.** Run builds and tests on Node.js 24. ## Auth Apps with an `auth` block move from NextAuth to BetterAuth. `@lowdefy/plugin-next-auth` and `@lowdefy/plugin-auth0` are removed. Follow [Auth Upgrade](/auth-upgrade) for providers, sessions, hooks, auth pages and the user object. The next section covers the auth changes that apply whichever login methods the app uses: the pinned auth URL, the client address and the email link pages. ## Auth links and client addresses These changes affect apps with auth. See [Auth Upgrade](/auth-upgrade), [Auth configuration](/auth-configuration) and [Deploy with Docker](/docker#behind-a-reverse-proxy). - **Pinned auth URL.** With `auth.email` configured, the production server refuses to start unless `BETTER_AUTH_URL` or the current environment's `url` in `config.environments` is set, so emailed auth links cannot take their host from a request. The dev server is unchanged. - **Client address.** The server takes the client address for auth rate limits, sessions and request logs from the connection, and reads `X-Forwarded-For` only from the proxies listed in `config.trustedProxies`. Behind a reverse proxy or load balancer, list it there; otherwise every client shares the proxy's rate limits. On Vercel nothing changes. - **Email link pages.** `SignUp` and `SendVerificationEmail` without a `callbackUrl` send the verification link to `auth.authPages.verifyEmail` (default `/verify-email`), not the home page. `RequestPasswordReset` without `redirectTo` sends the reset link to `auth.authPages.resetPassword` (default `/reset-password`). The build warns when the app sends these emails and the page does not exist. ## Icon names An icon name has one of three forms: - **Semantic:** `edit`, `delete`, `chevron-down`. Built-in names for common jobs. [Theming](/theming) lists them all. - **Lucide:** `Pencil`, `CreditCard`. The PascalCase name from [lucide.dev/icons](https://lucide.dev/icons). - **Qualified:** `lucide:Pencil`. A name in a named [icon set](/plugins-icon-sets). ```yaml # v6 - id: delete_button type: Button properties: title: Delete icon: AiOutlineDelete - id: card_icon type: Icon properties: name: AiOutlineCreditCard # v7 - id: delete_button type: Button properties: title: Delete icon: delete - id: card_icon type: Icon properties: name: CreditCard ``` ## Run the codemod ```bash npx lowdefy upgrade ``` The v7 phase includes the `react-icons-to-lucide` codemod. It rewrites icon names as text, so it reaches: - YAML, Nunjucks (`.njk`) and JSON config - single-quoted names in `_js` code - string literals and `meta.icons` in local plugin code - `theme.icons.aliases` targets Each old name maps to a semantic name where one fits, otherwise to a Lucide name. `Lu*` names drop the prefix (`LuPencil` becomes `Pencil`). The table lives in `@lowdefy/codemods` at `v7-0-0/react-icons-to-lucide.json`, and the build uses the same table for its error suggestions. The codemod reports what it cannot decide for you: - **Judgement calls**, where the closest Lucide glyph differs from the old one. - **Known losses**: filled variants (`AiFillStar`), brand icons such as WhatsApp, and Word and PDF file glyphs. Lucide has no equivalents. - **Runtime icon names**: icon properties set from `_state`, `_payload` or `_request`. The codemod does not change data. Use the compatibility set for names stored in a database. ## Build errors for old names A literal icon name at an icon property that resolves to nothing fails the build. An old react-icons name gets a migration message: ``` Icon "AiOutlineDelete" is a react-icons name. Lowdefy 7 uses Lucide icons. Use "delete" (or "Trash"). To keep react-icons names, install @lowdefy/icons-react-icons and set theme.icons.set: react-icons. Run `lowdefy upgrade` to migrate names automatically. ``` Old Ant Design names such as `HomeOutlined` get the same message, pointing at their semantic name. In v6 these names drew nothing, so the error may show icons that were missing all along. An icon property is a key ending in `icon` (`icon`, `prefixIcon`, `suffixIcon`, ...), its `name` field, the Icon block's `name`, a `data-icon` attribute in HTML, and `theme.icons.aliases` and `theme.icons.include`. Names inside an operator are bundled but not checked. An unknown name that reaches the page at runtime draws a red `icon-missing` icon. ## Keep old names: the compatibility set `@lowdefy/icons-react-icons` holds every react-icons 5.6.0 icon under its old name. Install it to keep old names working without edits, or for names stored in data: ```yaml plugins: - name: '@lowdefy/icons-react-icons' version: '^7' theme: icons: set: react-icons ``` Old names then resolve in the compatibility set, and semantic and Lucide names still work. Block and Ant Design icons stay Lucide. Without `set: react-icons`, reach an old name with its qualified name: `react-icons:FaWhatsapp`. See [Icon Sets](/plugins-icon-sets). ## Visual changes - **Block icons are Lucide.** Date picker calendars, input validation marks, selector arrows, the loading spinner, menu toggles and the dark mode toggle all use semantic names and draw Lucide icons. - **Ant Design icons are Lucide.** Close buttons on modals, drawers, tags and alerts, alert and message status icons, collapse and menu arrows, pagination arrows, and button spinners use the same semantic names. Spin, Progress, QRCode, the Empty and Result illustrations, and the date picker panel arrows stay as Ant Design draws them. - **Stroke width is a setting.** Lucide draws a 2px line on a 24px grid. Set `theme.icons.strokeWidth: 1.5` for a lighter look closer to Ant Design outline icons, or set `strokeWidth` on one icon. - **Size is unchanged.** Icons default to `1em`, as before. Change it with `theme.icons.size`. - **`color` and `rotate` go through CSS.** `color` colours stroke and fill icons alike, and `rotate` now turns the icon. - **Hover titles change.** The Icon block's title comes from the name: `Pencil` gives "Pencil", and `more-vertical` gives "More vertical". Old names in the compatibility set give titles such as "Ai outline user". Set `title` where the text matters, or `title: ''` for none. - **Icons without a title are hidden from screen readers** (`aria-hidden`). ## `theme.icons` `theme.icons.aliases` targets are now Lucide or qualified names. The codemod rewrites them: ```yaml # v6 theme: icons: aliases: invoice: LuReceipt include: - LuFlag # v7 theme: icons: aliases: invoice: Receipt include: - Flag ``` `theme.icons` also has new keys: `set`, `size`, `strokeWidth` and `nonScalingStroke`. See [Theming](/theming). ## Events no longer bubble In v6, a click on a block inside another clickable block ran the events of both: a `Button` in a clickable `Card` also opened the card. In v7 a browser event runs only the innermost block event that handles it. Once a block with actions for the event fires, the blocks around it skip that same browser event. A block without an event for the interaction, like a `Paragraph` in the card, still passes the click on to the card. Set `bubble: true` on the inner event to also run the events of the blocks around it: ```yaml # v6: clicking the button ran the button's and the card's onClick - id: order_card type: Card events: onClick: - id: open_order type: Link params: order-detail blocks: - id: select_button type: Button events: onClick: - id: select_order type: SetState params: selected: true # v7: bubble: true keeps the card's onClick running after the button's - id: order_card type: Card events: onClick: - id: open_order type: Link params: order-detail blocks: - id: select_button type: Button events: onClick: bubble: true try: - id: select_order type: SetState params: selected: true ``` Inside an `AgGrid` block, clicks on a cell's buttons, links, menus, inputs, selectors, switches or selection checkbox fire only that control's event, never `onRowClick` or `onCellClick`. The `nested-event-bubbling` codemod in `lowdefy upgrade` adds `bubble: true` only where the outer event reads a value the inner event sets, moves AgGrid row-click state setup into the cell control events that relied on it, and lists the other nested events for you to review. Most nested pairs were bugs the app worked around, like a delete button that also opened its card, and need no change. See [Events and Actions](/events-and-actions). ## Endpoints and webhooks ### `payloadSchema` is enforced In v6 an endpoint's `payloadSchema` was only advertised to MCP clients and agents. In v7 every call checks the payload against it before the routine runs, and refuses a payload that doesn't match. There is no opt-out: an endpoint that should not validate must not declare a `payloadSchema`. Who built the payload decides how the refusal is reported: - **An outside caller's payload** is a `UserError`, logged as a warning. A REST call to `POST /api/endpoints/` answers `400` with `{ name: 'UserError', message }`, an MCP `tools/call` answers with `isError: true`, and an agent tool call gets a tool error the model can retry. - **A payload the app's own config built** is a `ConfigError` with its config location, logged as a server error. That covers a page's `CallAPI`, a nested or detached `CallApi` step, a Dynamic block's endpoint, an auth or agent hook and a scheduled run's `payload`. A page's `CallAPI` fails with `_error.name: ConfigError`, a nested `CallApi` reaches the routine's `:catch`, and a detached or scheduled run fails. The message names the endpoint and the failing location: ``` Payload for endpoint "create_order" does not match its payloadSchema at /quantity: must be number. ``` A common cause is a number or boolean that a page sends as a string, for example straight from a text input. Fix the caller, or loosen the schema. An endpoint can't declare both `webhook` and `payloadSchema`, because a webhook routine receives `{ body, query, headers }`, not the schema's shape. The build fails when it finds both. ### Webhooks In v6 a webhook endpoint ran as a trusted system context: nested `CallApi` steps to protected endpoints always passed. In v7 a webhook starts untrusted and has to earn trust. - **List the endpoint in `auth.api.public`.** The build fails for a webhook endpoint that is not listed there, including one that is public only because no rule names it. This applies with or without app auth. - **Add `webhook.verify` to call protected endpoints.** `verify` is a request that checks the sender before the routine runs. When it returns `true` or `{ verified: true }`, the routine runs as a trusted system context. When it returns anything else or throws, the endpoint answers `401` and the routine never runs. A webhook without `verify` runs untrusted throughout, so its `CallApi` steps to protected endpoints fail, as do those to module endpoints, which are protected by default. ```yaml # v6 api: - id: github-webhook type: Api webhook: true routine: # ... # v7 auth: api: public: - github-webhook api: - id: github-webhook type: Api webhook: verify: connectionId: github_signature type: GitHubSignature properties: rawBody: _payload: rawBody signature: _payload: headers.x-hub-signature-256 secret: _secret: GITHUB_WEBHOOK_SECRET routine: # ... ``` The verifier's `_payload` is `{ body, rawBody, query, headers }`. Use `rawBody` for signature checks. Webhook responses change too: - **Plain JSON.** A date in the `:return` value arrives as an ISO string, not `{ "~d": 1767323045000 }`, and an error as its message fields, not `{ "~e": ... }`. - **Statuses.** A `:reject` without `:status` answers `400` with `{ error: { code: 'rejected', message } }`. A failed `ValidateSchema` step answers `400` (`invalid_request`), a failed verifier `401` (`unauthorized`), and any other error `500` (`internal_error`) with nothing from the error. A routine that succeeds still answers `200` with its return value. Set `:status` (400 to 499) and `:body` on a `:reject` to choose the answer. See [Lowdefy APIs](/lowdefy-api). ### Detached `CallApi` runs as the user who started it In v6 a `CallApi` step with `detached: true` always ran its target as system. In v7 the detached run carries the identity of the run that started it, and checks the target endpoint's `auth` as a synchronous call would. A detached run that a user started fails when that user may not call the target endpoint. Runs started by a schedule, an auth hook or a verified webhook still run as system. ### Module endpoints and agents are protected by default In an app with auth, an API endpoint or agent that a module ships needs a signed-in caller, unless the module lists it as public in its manifest (`auth.api.public` for endpoints, `auth.agents.public` for agents) or the app makes it public with `auth.api.public: true` or a `public` list that names it. An app endpoint with no `auth.api` rule stays public, but a module endpoint does not. This covers `CallApi` steps too: a public app endpoint, or a webhook without `verify`, that calls a module endpoint is refused when the caller is signed out. To keep a module endpoint or agent public, list its id in the app's `auth.api.public`. Apps without auth are unchanged. ## Error classes A few failures now have a different error class or HTTP status. Update `catch` actions and routine `:catch` blocks that check `_error.name`, and outside callers that check status codes. - **Auth refusals keep their class in the browser.** When a `Request` or `CallAPI` is refused, `_error.name` (and `_actions..error.name`) is the refusal's class: `AuthenticationError` (signed out, `401`), `AuthorizationError` (missing role, `403`), `TwoFactorEnrolmentRequiredError` (`403`), or `UserError` for a `400`. In v6 all of these were `ActionError`. The message still shows and `catch` actions still run, but the error logs to the browser console only. - **A refused signed-in caller gets `403`.** A request, endpoint, agent or websocket that refuses a signed-in caller answers `403` with an `AuthorizationError`. In v6 an endpoint refused for a missing role answered `500` with a "does not exist" error. - **A wrong HTTP method gets `405`**, not `500`. - **HTTP 429 is a `ServiceError` on every connection**, not a `RequestError`. Its `statusCode` is `429` and `retryAfter` holds the service's `Retry-After` value. A check on `_error.statusCode` keeps working. - **MongoDB driver errors are `ServiceError`s.** Every `MongoDBCollection` request maps a driver failure to a `ServiceError` whose message names the collection and the operation, whose `code` is the MongoDB error code, and whose `hint` says what to do. The driver's own message no longer reaches the browser, because it can quote values from the document. It stays in the server log. ## Build checks These checks fail builds that v6 accepted. Each points at the config location. - **`_if` without `test`.** An `_if` whose params are an object without a `test` key, such as `_if: { _eq: [1, 1], then: a, else: b }`, fails the build. In v6 it failed at runtime with `_if takes a boolean type for parameter test`. Params that are one operator, such as `_if: { _state: if_params }`, still pass. Silence the check on a node with `~ignoreBuildChecks: [if-test]`. - **`CallMethod` to a missing block.** A `CallMethod` whose literal `blockId` names no block on the page warns in dev and fails production builds. Pages with Dynamic blocks are not checked. Silence it with `~ignoreBuildChecks: [callmethod-refs]`. - **Endpoint `payloadSchema`.** A `payloadSchema` that is not valid JSON Schema fails the build, as does a `schedules` entry whose `payload` doesn't match its endpoint's schema. - **Webhooks.** A webhook endpoint not listed in `auth.api.public`, or one that also declares `payloadSchema`, fails the build. See Webhooks above. ## `_operator` names read at runtime An `_operator` whose `name` is read at runtime (from state, a request, a payload or `__args`) must list the operators it may call in `operators`, or the build fails. The build loads exactly those operators with the page, so the call works on a direct page load, and `_operator` refuses any other name. A literal `name`, and the literal branches an `_if` or `_switch` returns, need no list. ```yaml # v6 _operator: name: _state: aggregation params: _state: values # v7 _operator: name: _state: aggregation operators: - _sum - _product params: _state: values ``` ## Blocks returned as data by a Dynamic endpoint A `Dynamic` block without a dynamic blocks policy renders only the blocks and actions its endpoint writes in its `:return` config. Block config the endpoint reads as data (a database record, routine state, a nested endpoint's result) and returns as it is now renders the block's fallback, even when it holds no operators. Data can still fill values in written blocks, for example by mapping stored fields into blocks with `_array.map`. To render stored or generated block config, declare a [dynamic blocks policy](/dynamic-page-content#dynamic-blocks-policies) that lists what the content may use, put it on the `Dynamic` block, and return the content through a `ValidateDynamic` step with the same policy: ```yaml - id: check type: ValidateDynamic properties: policy: stored_content blocks: _step: get_record.blocks - :return: blocks: _step: check.blocks ``` ### Escaped keys in Dynamic data A Dynamic endpoint writes client operators with an extra underscore (`__state`), so the server leaves them for the page to run. In 6.1 the server stripped one underscore from every key that started with `__`, data included, so GraphQL's `__typename` reached the page as `_typename`. In v7 only operator keys lose the underscore. Data keys pass through unchanged: ```yaml - :return: blocks: - id: orders type: Table properties: data: _step: get_orders # rows like { __typename: Order, id: 42 } # v6: the rows reached the block with a _typename key # v7: the rows reach the block with a __typename key ``` Config that read such a key by its 6.1 name, `_typename`, has to use the original name, `__typename`. Data read into a Dynamic `:return` may also nest at most 200 levels deep. Deeper data fails resolution with an error that says so. ## `ClickableHtml` data events A `ClickableHtml` block now fires only the events its `dataEvents` property lists. A `data-event` whose name is not listed does nothing and logs a console warning, and a block without `dataEvents` fires no events from its HTML. HTML is often built from request or user data, and sanitising keeps `data-*` attributes, so in v6 markup inside that data could fire any event the block declared. ```yaml # v6 - id: rows type: ClickableHtml properties: html: '' # v7 - id: rows type: ClickableHtml properties: dataEvents: - name: onDelete confirm: true html: '' ``` An entry is an event name, or `{ name, confirm }`: with `confirm`, every click on the event asks first, whatever the markup says. The `clickable-html-data-events` codemod in `lowdefy upgrade` adds `dataEvents` to each block and reports HTML built from data that inserts values without escaping them. It only reaches the app's files: add `dataEvents` yourself to `ClickableHtml` blocks that [dynamic page content](/dynamic-page-content) builds at page load, for example from config stored in a database. See [HTML attributes](/html-attributes). ## `RenderNotification` landing page `properties.landingPage` on a `RenderNotification` step names the landing page by its id, or as `{ pageId, pathParams }` when the page's `path` has placeholders. The step builds the landing URL from the page's path like any other notification link. A value that names no page, such as the v6 path string, fails the step with an error that says to pass the page id. ```yaml # v6 - id: render type: RenderNotification properties: landingPage: /notifications/link # v7 - id: render type: RenderNotification properties: landingPage: notifications-link ``` See [Lowdefy APIs](/lowdefy-api). ## AI requests and agents The AI plugins move to AI SDK 7. Lowdefy's own config is unchanged apart from the points below. ### System messages in `messages` `GenerateText` and `GenerateObject` refuse a `role: system` message in `messages` unless the request sets `allowSystemInMessages: true`. A message list built from user input (`_payload`, `_state`) could otherwise carry a system turn the model treats as the app's own instructions. Put the system prompt in `system`: ```yaml # v6 - id: summarise type: GenerateText connectionId: claude properties: messages: - role: system content: Summarise the ticket in one line. - role: user content: _payload: ticket # v7 - id: summarise type: GenerateText connectionId: claude properties: system: Summarise the ticket in one line. messages: - role: user content: _payload: ticket ``` If the system message is written in your own config and has to stay in `messages`, set `allowSystemInMessages: true`. Never set it for messages that come from a user. ### Files the server downloads When the model doesn't take a file in `messages` as a link (a CSV or text file on most providers, most files on Google), the server downloads it. `GenerateText`, `GenerateObject` and agents now limit that download: - Only an `https:` link to a public address is downloaded. The address is checked after DNS resolution and on every redirect, so `http:` links and links to private or internal addresses fail. - A file is capped at 20 MB and 30 seconds by default. Raise the limits with `fileDownload: { maxBytes, timeout }` (bytes and milliseconds) on the request or agent. ### Calls are cancelled at the request timeout An AI call is cancelled when the request that started it closes: when `config.requestTimeout` answers first, or, on the Node server, when the client disconnects. In v6 a synchronous routine kept running, and billing, after its request timed out. A call stopped by the timeout fails with a `ServiceError`, and one stopped because the client left fails with a `UserError`. Endpoints with `async: true`, detached runs and agent chat turns are not cancelled with their request, so move work that takes longer than `config.requestTimeout` into an `async: true` endpoint. ### Agent hook payloads Agent hooks keep their names, but the event payloads passed to `onStart`, `onStepStart`, `onToolCallStart`, `onToolCallFinish` and `onStepFinish` now follow AI SDK 7's event shapes. Check any hook endpoint that reads fields from them. ## Websockets - **Cross-origin upgrades are refused.** `/api/websocket` accepts an upgrade only when its `Origin` matches the `Host` it arrived on, the first `X-Forwarded-Host`, or the app's public URL (`BETTER_AUTH_URL`, the current environment's `url`, or an auth trusted origin). A request with no `Origin`, such as a server or script, is accepted. Any other upgrade gets a `403`. Behind a proxy or CDN, forward the original `Host` or set `X-Forwarded-Host`. - **Connections follow the session.** The server checks the caller again before every subscribe and at least once a minute. When the user signs out, the session ends, the membership is removed, the roles change or the user switches organization, the server closes the connection with code `4401`. The client reconnects at once as the current caller and resubscribes. A subscription the new caller may not hold fails, as it would on a freshly loaded page. - **Change streams run the connection `read` check.** A connection with `read: false` refuses a `MongoDBChangeStream` subscription, as it refuses read requests. Remove `read: false` from a connection used for change streams. ## Blocks ### Link schemes A block `href` is used only when it is a relative path, a query, a fragment, or uses one of these schemes: `http`, `https`, `ftp`, `ftps`, `mailto`, `tel`, `callto`, `sms`, `cid`, `xmpp` or `matrix`. Any other value, including one with leading whitespace or control characters, renders no link. This covers `Button`, `Tag` and `FloatButton`, table and AgGrid link cells, `AgentChat` links, Tiptap mention links, and every `href` given to Lowdefy's `Link` component. A `Link` with a refused `href` doesn't fall back to its `pageId` or `url`, and the `Link` action throws "Invalid Link: no target resolved." ### `Pagination` `skip` The `onChange` and `onSizeChange` events of `Pagination` passed a `skip` one page too high (`current * pageSize`). They now pass `(current - 1) * pageSize`, the same as the block value in state. Remove any correction the app applied: ```yaml # v6 skip: _subtract: - _event: skip - _event: pageSize # v7 skip: _event: skip ``` Apps that read `skip` from the block value in state are not affected. ### Properties that now apply These properties were accepted but had no effect in v6. They now work, so pages that set them may look or behave differently: - **Page blocks:** `class`, `loading` and `skeleton` set on the page block. - **Layout:** `layout.align` sets the vertical alignment of a block's content area. In v6 it was dropped, with a console warning, unless `layout.selfAlign` was also set. - **`theme`:** on `Layout`, `Sider`, `PageHeaderMenu` and `PageSiderMenu`, and on `AutoComplete`, `ColorSelector`, `Search`, `Label`, `ConfirmModal` and `ConfigProvider`. `TreeSelector` and `TreeMultipleSelector` now apply the selector tokens. - **`Menu`:** an inline `Menu` with `collapsed: true` outside a `Sider` collapses, and the `item` style applies. - **`Sider`:** the collapse trigger toggles the sider. - **CSS keys:** `Tabs` `tabBar`, `tabPane` and `inkBar`; the `menu` key of `DropdownButton` and `DropdownMenu`; the `profileMenu` and `localeSelectorMenu` keys of `Header` and the page layouts; `Modal` `header`, `footer` and `content`; and the `Popover` keys. - **Other:** `Pagination` `showTotal: false` hides the total, `Tour` `gap: { x, y }` applies, a `Notification` `button` closes the notification, and `ConfirmModal` fires `onClose`. ### `ColorSelector` Clearing the colour sets the value to `null`. In v6 it was the last colour with zero alpha, such as `#1677ff00`. A cleared `ColorSelector` with `required: true` now fails validation. In gradient mode the value is a `linear-gradient(...)` CSS string. ### `PhoneNumberInput` The region selector and the input are siblings in an `.ant-space-compact` group, not an `.ant-input-group-addon`. The `ldf-phone-number-input` class and `element` style stay on the outer wrapper, but CSS that targets `.ant-input-group-addon` or `.ant-input-group-wrapper` needs updating. The `addonBg` theme token no longer applies; use `selectorBg`. ### `TitleInput` and `ParagraphInput` Both now edit in place and show no edit icon by default. Set `editable.icon` to bring one back: ```yaml - id: title type: TitleInput properties: editable: icon: edit ``` Expanding a `ParagraphInput` ellipsis fires `onExpand`, not `onCopy`. ## Vercel The function entry that `lowdefy vercel-output` writes now starts the same way as the Node server. Redeploy to pick it up. - **Environment guards are checked.** `config.environments..guards` run when a function starts. A deployment whose runtime variables differ from the build's, for example one built with `vercel build` and deployed with `--prebuilt` or `--env`, now fails every request with the guard error instead of serving with the wrong variables. See [Deployment Environments](/deployment-environments). - **Server-side Sentry runs.** With `SENTRY_DSN` set, server errors now reach Sentry from Vercel functions, where v6 only logged them. It reports errors only; set `logger.sentry.serverTracing: true` to trace requests, or `logger.sentry.server: false` to turn server reporting off. ## Dev tools and coding agents - **The MCP server is renamed `lowdefy`.** It was `lowdefy-docs`, so its tools are now `mcp__lowdefy__*`. Rerun `lowdefy agent-setup`: it replaces the `lowdefy-docs` entry in `.mcp.json` with a `lowdefy mcp` entry that names no port, and renames the server in `.claude/settings.json` and `.claude/settings.local.json`, including tool permission rules. Update any other place that names `mcp__lowdefy-docs__` tools, such as hooks. The `--port` option of `agent-setup` is removed. The dev server's `/lowdefy-docs` routes are unchanged. - **`lowdefy dev` uses the port you set.** A port set with `--port`, `PORT` or `cli.port` is used, or the command fails when it is taken. In v6 it moved to the next free port. Only the default port 3000 still moves. - **`@lowdefy/e2e-utils` no longer reuses a running server by default.** A server left on the port could serve an old build. Set `LOWDEFY_E2E_REUSE_SERVER=true` to reuse one, for example after `pnpm e2e:server`, and `LOWDEFY_E2E_PORT` to run on another port. A reused server must be this app's own e2e build, or the run stops with an error. - **Generated servers are their own pnpm workspace.** `lowdefy dev`, `build`, `emails` and `test` give the server in `.lowdefy/` its own `pnpm-workspace.yaml` and lockfile, carrying over the parent workspace's settings. If the app sits inside a pnpm workspace, remove the `.lowdefy/*` globs from the parent's `pnpm-workspace.yaml` `packages`. If the parent's patches only apply to the servers, also set `allowUnusedPatches: true` there. ## Plugins Blocks no longer import icons from `react-icons` or `@ant-design/icons`, and the Lowdefy server no longer installs `react-icons`. A local block plugin should render icons by name with `components.Icon`, and list the names it uses in its `meta.icons`, so apps can restyle them: ```js // v6 import { AiOutlineDelete } from 'react-icons/ai'; ; // v7 ; ``` A plugin that must keep importing `react-icons` adds it to its own `dependencies`. ### antd version Lowdefy v7 uses antd 6.6.5, Ant Design X 2.9.0 and `@ant-design/icons` 6.3.4. A block plugin that imports from `antd` directly should pin `antd@6.6.5`, so the app keeps one copy of antd. ### Declare the actions and operators a block runs Production pages now load only the blocks, actions, operators and icons they use. A block that runs actions or operators for events it registers itself, the way `PageHeaderMenu` runs `SetLocale` from its locale selector, must list them in its `meta`. Otherwise, on a page that doesn't use those types anywhere else, they are not loaded, and the action fails with `Invalid action type`: ```js MyHeader.meta = { category: 'container', actions: ['SetDarkMode', 'SetLocale'], operators: ['_event'], icons: [], }; ``` The dev server still loads every type, so this shows up only in production builds. ### `createIcon` `createIcon` in `@lowdefy/client` takes `{ icons, loadAllIcons }` instead of the icon map on its own. --- url: https://docs.lowdefy.com/md/migration/v3-to-v4.md --- # Version 3 to Version 4 ## Payload on Requests In version 3, the `state`, `global`, `input`, `urlQuery` and `event` data objects were sent with the request. This allowed the use of the [`_state`](/_state), [`_global`](/_global), [`_input`](/_input), [`_url_query`](/_url_query) and [`_event`](/_event) operators inside requests and connections. If the state became too large, this would result in a payload size exceeded error on request calls. In version 4, these operators can no longer be used in requests, and request payloads are introduced. Payloads can be used to specify the data that should be sent with the request. The data specified in the payload can be accessed using the `_payload` operator. Operators are evaluated in the payload before it is sent, so operators such as [`_state`](/_state) and [`_global`](_global) can be used in the payload. If only the [`_state`](/_state) operator was used, the entire state object can be set as the payload: ###### Version 3: ```yaml id: api_call type: AxiosHttp connectionId: my_api properties: url: /products/new method: post data: name: _state: name product_category: _state: name ``` ###### Version 4: ```yaml id: api_call type: AxiosHttp connectionId: my_api payload: _state: true properties: url: /products/new method: post data: name: _payload: name product_category: _payload: product_category ``` The payload can also be created more explicitly using more than one operator type: ###### Version 3: ```yaml id: api_call type: AxiosHttp connectionId: my_api properties: url: /products/update method: post data: id: _url_query: id name: _state: name ``` ###### Version 4: ```yaml id: api_call type: AxiosHttp connectionId: my_api payload: id: _url_query: id name: _state: name properties: url: /products/new method: post data: id: _payload: id name: _payload: name ``` The `user` data object is still available in requests, and the [`_user`](/_user) operator should be used in the request properties not in they payload as malicious users can modify the data sent in payload. ###### Don't do: ```yaml id: api_call type: AxiosHttp connectionId: my_api payload: # ... created_by: _user: id properties: url: /products/new method: post data: # ... created_by: _payload: created_by ``` ###### Do: ```yaml id: api_call type: AxiosHttp connectionId: my_api payload: # ... properties: url: /products/new method: post data: # ... created_by: _user: id ``` > If you see a warning message like `Operator type "_state" was used but is not defined.`, you are using the `_state` operator in a request's properties and should move it to payload. ## Icons The Ant Design icons used in version 3 have been replaced by [React Icons](https://react-icons.github.io/react-icons/), which has a much larger library of icons to choose from, including the Ant Design icons. To use a React Icon, set the `icon` or `icon.name` property to the React Icon name. To convert existing Ant Design icons to React Icons, the icon names should be changed to the new format: ###### Version 3: ```yaml id: save_button type: Button properties: icon: SaveOutlined ``` ###### Version 4: ```yaml # Using the same Ant Design icon id: save_button type: Button properties: icon: AiOutlineSave ``` ```yaml # Using the Font Awesome icon id: save_button type: Button properties: icon: FaRegSave ``` ## Renames and Minor changes The `Message` action has been renamed to [`DisplayMessage`](/DisplayMessage). Change the type name of `Message` actions to [`DisplayMessage`](/DisplayMessage). The `onEnter` and `onEnterAsync` events have been removed as their functionality is duplicated by the `onMount` and `onMountAsync` events. Rename all `onEnter` and `onEnterAsync` events to `onMount` and `onMountAsync` respectively. Duplicate action ids in the same action chain were allowed in version 3, but are not allowed in version 4. If you get a build error about duplicate action ids, give the actions unique ids. The `Notification` action has been removed. Switch to the [`DisplayMessage`](/DisplayMessage) action, or use a [`Notification`](/Notification) block and the `CallMethod` action to open the notification. The 404 page is now always a public page. The [`Anchor`](/Anchor) block properties schema has changed. The `href` property has been removed and it now takes the same props as the [`Link`](/Link) action. The [`_yaml.parse`](/_yaml) operator now takes an object with a `on` property or an array where the first argument is the string to parse, instead of just a string, to allow it to take additional options. The `ChromeColorSelector`, `CircleColorSelector`, `CompactColorSelector`, `GithubColorSelector`, `SliderColorSelector`, `SwatchesColorSelector`, and `TwitterColorSelector` have been replaced by the [`ColorSelector`](/ColorSelector) block. The `color` properties in blocks like [`MobileMenu`](/MobileMenu), [`PageHeaderMenu`](/PageHeaderMenu), [`PageSiderMenu`](/PageSiderMenu) have been removed. Use Ant Design theme less variables instead. The [`_var`](/_var) operator `name` param has been renamed to `key` to be more consistent with other getter operators. The `_format` operator has been replaced with the [`_intl`](/_intl) and [`_moment`](/_moment) operator, with some minor schema changes. Note: `_moment` was renamed to [`_dayjs`](/_dayjs) in v5. The format of dates in the output of the [`_json.stringify`](/_json) and [`_yaml.stringify`](/_yaml) operators, as well as in URL query parameters has changed from `_date: {iso_date_string}` to `~d: {iso_date_string}`. ## Block Loading States The page loading states in version 4 have been improved. In general apps should load a lot faster. Blocks will now be a lot less likely to show a loading state and rather render as normal, and render their children. Input blocks will be disabled while in a loading state. This contributes to users seeing useful content sooner, and to less layout shifts once the app finishes loading. In version 3, blocks had a `loading` property to specify the loading skeleton that should be used while the block is in a loading state. This functionality has been replaced by the `skeleton` property. The `loading` property is now used to control the loading state of the block. The block will be in a loading state if an `onInit` or `onMount` event is busy, if a parent block is loading or if the `loading` property evaluates to true. See more about loading [here](/blocks). The convert from version 3 to version 4, replace the `loading` property on blocks with the `skeleton` property. ###### Version 3: ```yaml id: paragraph_one type: Markdown loading: type: SkeletonParagraph properties: lines: 5 properties: content: _request: get_text.body_text ``` ###### Version 4: ```yaml id: paragraph_one type: Markdown skeleton: type: SkeletonParagraph properties: lines: 5 properties: content: _request: get_text.body_text ``` ```yaml # Keep the block in loading while the request is loading id: paragraph_one type: Markdown loading: _eq: - _request: get_text - null skeleton: type: SkeletonParagraph properties: lines: 5 properties: content: _request: get_text.body_text ``` ## Static files Static files placed in the `public` folder of your project were always served from the public url path - for example `https://example.com/public/filename.jpeg`. These files are now served from the root path of your app. The example above will now be served from `https://example.com/filename.jpeg`. This allows you to serve files like `robots.txt` and `sitemap.xml` from the root path. Change all references in your config to the new paths. If you still want to serve assets under the `/public` path, you can add an addition `public` folder inside the existing `public` folder: ```txt . ├─ public │ └─ public │ └─ my-logo.jpeg └─ lowdefy.yaml ``` ## Authentication Changes In version 4, authentication has been reworked to use [NextAuth.js](https://next-auth.js.org), which offers a lot more flexibility and additional features - read more [here](/users-introduction). To migrate a version 3 OpenID Connect provider, at least the following needs to be done: - The `config.auth` property has moved to the `auth` root level property. - The `config.auth.openId` properties have been removed, replace them with configuration for your provider: ```yaml auth: providers: - id: auth0 type: Auth0Provider properties: clientId: _secret: AUTH0_CLIENT_ID clientSecret: _secret: AUTH0_CLIENT_SECRET issuer: _secret: AUTH0_ISSUER ``` - The `NEXTAUTH_SECRET` environment variable needs to be set to a secure secret value (replacing `LOWDEFY_SECRET_JWT_SECRET`). - The callback URLs configured with the provider change from `{{ protocol }}{{ host }}/auth/openid-callback` to `{{ protocol }}{{ host }}/api/auth/callback/{{ providerId }}`. - The `Login` and `Logout` action params have changed between V3 and V4. ## Custom Blocks, Actions and Operators Custom blocks, the `JsAction` action and the `_js` operator have been replaced by the new Lowdefy plugin system which is an easier, more powerful and standardized way to extend actions, blocks, connections, operators and the authentication system with custom JavaScript. See the [plugin documentation](/plugins-introduction) for more information on how to use and develop plugins. The [AgGrid](https://github.com/lowdefy/blocks-aggrid) custom blocks have been added as default Lowdefy blocks, so these blocks can now be used without any additional configuration. ## Context has been removed In version 3, the first block on a page needed to be a `context` category block. This was because in version 3 you could have multiple context blocks on the same page, each with it's own separate state. This feature was found to be unnecessary, created additional complexity and was difficult to understand, so it was removed. The `Context` type block was removed, as it was a `Box` block that also created a context. The `Context` block can be replaced by a `Box`. If the `Context` block is no longer necessary it can also be removed: ###### Version 3: ```yaml id: '404' type: Context style: minHeight: 100vh blocks: - id: 404_result type: Result properties: status: 404 title: '404' subTitle: Sorry, the page you are visiting does not exist. ``` ###### Version 4: ```yaml id: '404' type: Result style: minHeight: 100vh properties: status: 404 title: '404' subTitle: Sorry, the page you are visiting does not exist. ``` ## Other removed blocks In version 4 the `PageHCF`, `PageHCSF`, `PageHSCF`, and `PageSHCF` blocks have been removed. Instead you can now use the `Layout`, `Header`, `Content`, `Sider` and `Footer` blocks. See more about this [here](https://4x.ant.design/components/layout/). ###### Layout, Header-Content-Footer example: ```yaml id: layout type: Layout style: textAlign: center blocks: - id: header type: Header blocks: - id: Title type: Title properties: content: Header style: backgroundColor: red - id: content type: Content blocks: - id: Title type: Title style: backgroundColor: green properties: content: Content - id: footer type: Footer blocks: - id: Title type: Title style: backgroundColor: blue properties: content: Footer ``` ## Server Configuration The `LOWDEFY_SERVER_BASE_PATH` environment variable configuration option has been removed. To configure a base path, use the `config.basePath` configuration option. ``yaml lowdefy: 7.1.0 config: basePath: /foo ``` The `_build.env` operator can be used to set the base path using an environment variable: ```yaml lowdefy: 7.1.0 config: basePath: _build.env: LOWDEFY_BASE_PATH ``` The `LOWDEFY_SERVER_PUBLIC_DIRECTORY` and `LOWDEFY_SERVER_BUILD_DIRECTORY` are no longer supported because configuration is bundled with the server at build time. The `LOWDEFY_SERVER_PORT` environment variable has been replaced by the `PORT` environment variable. ## _change_case The options splitRegex and stripRegex are no longer supported in v4 and the following have been renamed: - paramCase to kebabCase - headerCase to trainCase ###### Version 3: ```yaml _change_case.paramCase: on: example string ``` ###### Version 4: ```yaml _change_case.kebabCase: on: example string ``` ###### Version 3: ```yaml _change_case.headerCase: on: example string ``` ###### Version 4: ```yaml _change_case.trainCase: on: example string ``` --- url: https://docs.lowdefy.com/md/migration/v5-to-v6.md --- # Version 5 to Version 6 This guide covers all breaking changes when upgrading from Lowdefy v5 to v6. Lowdefy v6 replaces the Next.js framework with a [Hono](https://hono.dev) server and a [Vite](https://vite.dev)-built React client, and moves authentication from NextAuth v4 to [Auth.js](https://authjs.dev). **Your YAML config does not change** — `lowdefy build`, `lowdefy dev` and `lowdefy start` work as before, and plugin changes in dev now hot-reload in well under a second instead of triggering a full rebuild. ## Summary of breaking changes | Change | Impact | Action | |---|---|---| | Server runs on Hono + Vite (not Next.js) | All apps | No config change | | Custom `next.config.js` no longer applies | Apps with bundler customizations | Move to `vite.config.js` | | Auth engine is Auth.js (not NextAuth v4) | Apps using auth | Users sign in again once; config unchanged | | `NEXTAUTH_SECRET` removed, `NEXTAUTH_URL` deprecated | Apps using auth | Rename `NEXTAUTH_SECRET` to `AUTH_SECRET`; prefer `AUTH_URL` | | `LOWDEFY_BUILD_OUTPUT_STANDALONE` removed | Docker / standalone deploys | Deploy the `.lowdefy/server` folder | | `NEXT_PUBLIC_SENTRY_DSN` removed | Apps using Sentry | Set `SENTRY_DSN` only | | CLI `--no-next-build` renamed | Deployment scripts | Use `--no-client-build` (old flag is a deprecated alias) | | Page navigation is client-side (SPA) | All apps | No action; full reloads no longer happen on navigation | ## Server runs on Hono + Vite The production server is a [Hono](https://hono.dev) app serving a [Vite](https://vite.dev)-built React client, and the dev server hot-reloads plugin changes through Vite instead of rebuilding the whole app. Things to check when upgrading: - **Custom `next.config.js` files no longer apply.** If you customized the client bundler, move the customization to a `vite.config.js` in the server directory. - **`LOWDEFY_BUILD_OUTPUT_STANDALONE` is removed.** `lowdefy build` writes a complete runnable server to `/.lowdefy/server` — copy that folder and run `node src/index.js` (or `pnpm start`) inside it. See the updated [Docker](/docker) and [node server](/node-server) guides. - **`NEXT_PUBLIC_SENTRY_DSN` is removed.** Set `SENTRY_DSN` on the server — it is passed to the browser client at runtime, so rotating the DSN no longer requires a rebuild. - **The CLI `--no-next-build` flag is now `--no-client-build`.** The old flag still works as a deprecated alias. - **Page navigation is client-side.** The first page load embeds config in the HTML; navigating between pages fetches page config without a full browser reload. ## Auth engine is Auth.js Authentication now runs on [Auth.js](https://authjs.dev) instead of NextAuth v4. The `auth:` configuration schema — providers, adapters, callbacks, events, session options and protected pages — is **unchanged**. Things to know when upgrading: - **Users sign in again once.** The session cookie prefix changes from `next-auth.*` to `authjs.*`, so existing sessions are invalidated on upgrade. - **`NEXTAUTH_SECRET` is no longer read — rename it to `AUTH_SECRET`.** The build fails with a config error when auth providers are configured and `AUTH_SECRET` is not set. `NEXTAUTH_URL` still works as an Auth.js fallback, but `AUTH_URL` is the preferred name. - **Email sign-in flows expect `nodemailer@^7`.** Auth.js declares a `nodemailer@^7` peer dependency. Custom email providers (or plugins) that pin `nodemailer@6` keep working but log a peer-dependency warning during install — upgrade the `nodemailer` dependency in your app or plugin to `^7` to clear it. --- url: https://docs.lowdefy.com/md/migration/v4-to-v5.md --- # Version 4 to Version 5 This guide covers all breaking changes and new features when upgrading from Lowdefy v4 to v5. ## Summary of breaking changes | Change | Impact | Action | |---|---|---| | `_moment` → `_dayjs` | All apps using `_moment` | Find-and-replace | | `areas` → `slots` | All container blocks | Find-and-replace (backwards compatible) | | `styles.less` → `styles.css` | Apps with custom Less styles | Rewrite in CSS | | Less variables → theme tokens | Apps using `@primary-color` etc. | Move to `theme.antd.token` | | `style.sm` responsive → Tailwind | Apps using responsive style breakpoints | Use Tailwind classes | | `bordered` → `variant` | 16 input/display blocks | Update property | | `visible` → `open` | Modal | Update property | | Button `type`/`danger` → `color`/`variant` | Button blocks | Update properties | | Inline style props → `style: { .key }` | Card, Modal, Drawer, etc. | Update properties | | Comment block removed | Apps using Comment | Use Card + Flex + Avatar | | `_menu` returns links directly | Apps using `_menu` with `_get` | Simplify to `_menu: menuId` | | Property renames | Various blocks | See table below | ## `_menu` returns links array directly The `_menu` operator now returns the `links` array directly when selecting a single menu by ID. It also supports dot-path access into the result. ### Before (v4) ```yaml # Get links required _get wrapper _get: key: links from: _menu: profile_menu # Access through links _get: key: links.0.pageId from: _menu: search_links ``` ### After (v5) ```yaml # Links returned directly _menu: profile_menu # Dot-path access _menu: search_links.0.pageId ``` `_menu: true` and `_menu: { all: true }` still return the full menus array. A codemod is available to automate the conversion. ## `_moment` renamed to [`_dayjs`](/_dayjs) The `_moment` operator has been renamed to `_dayjs`. The underlying library has changed from [Moment.js](https://momentjs.com/) to [Day.js](https://day.js.org/), which is significantly smaller (~2KB vs ~70KB). A codemod is available to automate the rename — run `lowdefy upgrade` in your project directory. Alternatively, this is a simple find-and-replace in your YAML config files: ### Before (v4) ```yaml _moment.format: on: _date: 2019-06-04 format: 'D MMM YYYY' ``` ### After (v5) ```yaml _dayjs.format: on: _date: 2019-06-04 format: 'D MMM YYYY' ``` ### humanizeDuration #### Before (v4) ```yaml _moment.humanizeDuration: on: 245923000 withSuffix: true ``` #### After (v5) ```yaml _dayjs.humanizeDuration: on: 245923000 withSuffix: true ``` ### `thresholds` parameter dropped The `thresholds` parameter on `humanizeDuration` is accepted but ignored in v5. Day.js uses fixed thresholds internally. If you relied on custom thresholds, the output may differ slightly. ### Locale names The `_dayjs` operator auto-normalizes locale names (e.g. `en-US` becomes `en`, `zh-CN` becomes `zh-cn`). In nunjucks date filters, locale names must be lowercase. 22 locales are bundled: `af`, `de`, `en`, `es`, `fr`, `hi`, `it`, `ja`, `ko`, `nl`, `pl`, `pt`, `ru`, `sv`, `th`, `tr`, `uk`, `vi`, `zh-cn`, `zh-tw`, `ar`, `he`. Uncommon locales fall back to English silently. ### AgGrid cell renderers If you use `_moment` inside `_function` blocks (e.g. AgGrid cell renderers), update the operator name: #### Before (v4) ```yaml cellRenderer: _function: __moment.format: on: __args: 0.data.date format: 'D MMM YYYY' ``` #### After (v5) ```yaml cellRenderer: _function: __dayjs.format: on: __args: 0.data.date format: 'D MMM YYYY' ``` ## [Layout Grid System](/layout-overview) The layout grid system has been rewritten from the Ant Design Row/Col grid to a standalone CSS custom properties grid using Tailwind CSS breakpoints. ### Breakpoint changes Breakpoint values have shifted to match Tailwind CSS v4 defaults, and `xxl` has been renamed to `2xl`: | Breakpoint | v4 | v5 | |---|---|---| | `xs` | < 576px | < 640px | | `sm` | >= 576px | >= 640px | | `md` | >= 768px | >= 768px | | `lg` | >= 992px | >= 1024px | | `xl` | >= 1200px | >= 1280px | | `xxl` / `2xl` | >= 1600px | >= 1536px | Update any `xxl` responsive keys to `2xl`: ```yaml # Before layout: span: 8 xxl: span: 4 # After layout: span: 8 2xl: span: 4 ``` ### `gutter` renamed to `gap` The `gutter` property on area/slot config has been renamed to `gap`: ```yaml # Before slots: content: gutter: 16 # After slots: content: gap: 16 ``` ### `content*` prefix removed from layout properties Layout properties that controlled the content area no longer use the `content` prefix. These properties are set on the parent block's `layout` and flow to the content area as defaults: | v4 | v5 | |---|---| | `contentGutter` / `contentGap` | `gap` | | `contentAlign` | `align` | | `contentJustify` | `justify` | | `contentDirection` | `direction` | | `contentWrap` | `wrap` | | `contentOverflow` | `overflow` | ```yaml # Before layout: span: 12 contentGutter: 16 contentAlign: middle contentJustify: center contentDirection: column # After layout: span: 12 gap: 16 align: middle justify: center direction: column ``` ### `layout.align` renamed to `layout.selfAlign` The `layout.align` property (which controlled the block's own vertical alignment within its parent row) has been renamed to `layout.selfAlign`. The `layout.align` property now controls content area children alignment: ```yaml # Before — block self-alignment layout: span: 6 align: top # After — renamed to selfAlign layout: span: 6 selfAlign: top ``` ### `span: 0` behavior change Setting `span: 0` now hides the block (`display: none`) instead of making it full-width. ### [`_media`](/_media) operator The `_media` operator now returns `"2xl"` instead of `"xxl"` for the largest breakpoint. Update any comparisons: ```yaml # Before _if: condition: _eq: - _media: size - xxl then: ... # After _if: condition: _eq: - _media: size - 2xl then: ... ``` ### Responsive `style` breakpoints removed Responsive breakpoint settings on the `style` property have been removed. Only `layout` supports responsive breakpoints. Use Tailwind CSS responsive classes via `class` instead: ```yaml # Before style: padding: 64 sm: padding: 32 # After class: "p-16 sm:p-8" ``` A codemod is available to automate many of these changes — run `lowdefy upgrade` in your project directory. ## `areas` renamed to `slots` The `areas` key on container and list blocks has been renamed to `slots`. This change is **backwards compatible** — `areas` will still work but is deprecated. We recommend updating to `slots` for consistency with the new documentation. ### Before (v4) ```yaml - id: my_card type: Card areas: content: blocks: - id: my_block type: Box ``` ### After (v5) ```yaml - id: my_card type: Card slots: content: blocks: - id: my_block type: Box ``` ## Style system migration The entire styling system has changed from Less/emotion to CSS-in-JS with Tailwind CSS. ### `styles.less` → `styles.css` Rename your `public/styles.less` file to `public/styles.css` and convert any Less syntax to plain CSS. #### Before (v4) ```less @primary-color: #2ac8bb; @new-color: #2ac8bb; h1 { font-size: 32px; color: @new-color; } ``` #### After (v5) ```css h1 { font-size: 32px; color: #2ac8bb; } ``` For theme variable overrides, use `theme.antd.token` in `lowdefy.yaml` instead: ```yaml theme: antd: token: colorPrimary: '#2ac8bb' ``` ### Responsive `style` breakpoints → Tailwind classes The responsive `style.sm`, `style.md` etc. syntax has been removed. Use Tailwind CSS responsive classes via the `class` property instead: #### Before (v4) ```yaml - id: my_card type: Card style: padding: 64 sm: padding: 32 ``` #### After (v5) ```yaml - id: my_card type: Card class: "p-16 sm:p-8" ``` ### Inline style props → `style: { .key }` Block-specific inline style properties have been replaced with the unified `style` property using `.`-prefixed CSS slot keys: | Block | Remove | Use instead | |---|---|---| | [Card](/Card) | `headerStyle`, `bodyStyle` | `style: { .header }`, `style: { .body }` | | [Modal](/Modal) | `bodyStyle`, `maskStyle` | `style: { .body }`, `style: { .mask }` | | [Drawer](/Drawer) | `drawerStyle`, `headerStyle`, `bodyStyle`, `maskStyle`, `contentWrapperStyle` | `style: { .wrapper }`, `style: { .header }`, `style: { .body }`, `style: { .mask }`, `style: { .content }` | | [Tabs](/Tabs) | `tabBarStyle` | `style: { .tabBar }` | | ConfirmModal | `bodyStyle` | `style: { .body }` | | [Statistic](/Statistic) | `valueStyle` | `style: { .value }` | | [Descriptions](/Descriptions) | `contentStyle`, `labelStyle` | `style: { .content }`, `style: { .label }` | | [Tooltip](/Tooltip) | `overlayStyle` | `style: { .inner }` | #### Before (v4) ```yaml - id: my_card type: Card properties: bodyStyle: backgroundColor: "#f0f0f0" headerStyle: fontWeight: bold ``` #### After (v5) ```yaml - id: my_card type: Card style: .body: backgroundColor: "#f0f0f0" .header: fontWeight: bold ``` ## Theme migration Less variables are replaced by antd design tokens. Configure tokens in `lowdefy.yaml`: | Less variable (v4) | Theme token (v5) | |---|---| | `@primary-color` | `colorPrimary` | | `@success-color` | `colorSuccess` | | `@warning-color` | `colorWarning` | | `@error-color` | `colorError` | | `@font-size-base` | `fontSize` | | `@border-radius-base` | `borderRadius` | | `@text-color` | `colorTextBase` | | `@heading-color` | `colorTextHeading` | | `@disabled-color` | `colorTextDisabled` | | `@border-color-base` | `colorBorder` | | `@body-background` | `colorBgBase` | | `@component-background` | `colorBgContainer` | #### Before (v4) ```less @primary-color: #6366f1; @border-radius-base: 8px; ``` #### After (v5) ```yaml # lowdefy.yaml theme: antd: token: colorPrimary: '#6366f1' borderRadius: 8 ``` Dark and compact modes: ```yaml # Before: @import 'antd/dist/antd.dark.less'; # After: theme: antd: algorithm: dark ``` See the [Theming](/theming) concept page for full details. ## Property renames | Block | Old property | New property | |---|---|---| | [Modal](/Modal) | `visible` | `open` | | [Tooltip](/Tooltip) | `defaultVisible` | `defaultOpen` | | Tooltip | event `onVisibleChange` | event `onOpenChange` | | [Progress](/Progress) | `gapPosition` | `gapPlacement` | | [Carousel](/Carousel) | `dotPosition` | `dotPlacement` | | [Collapse](/Collapse) | `expandIconPosition` | `expandIconPlacement` | | Collapse | `bordered` | `variant` (`outlined` / `borderless`) | | [Notification](/Notification) | `message` | `title` | | [Tabs](/Tabs) | `tabPosition` | `tabPlacement` (`left` → `start`, `right` → `end`) | | [Divider](/Divider) | `type` | `orientation` | | Divider | `orientation` | `titlePlacement` (`start` / `center` / `end`) | ## `bordered` → `variant` 16 blocks have replaced the `bordered` boolean with a `variant` enum for more control: | Old | New | |---|---| | `bordered: true` (default) | `variant: outlined` (default) | | `bordered: false` | `variant: borderless` | | — | `variant: filled` (new option) | Affected blocks: [AutoComplete](/AutoComplete), [Card](/Card), [Collapse](/Collapse), [Descriptions](/Descriptions), [DateSelector](/DateSelector), [DateRangeSelector](/DateRangeSelector), [DateTimeSelector](/DateTimeSelector), [MonthSelector](/MonthSelector), [WeekSelector](/WeekSelector), [MultipleSelector](/MultipleSelector), [NumberInput](/NumberInput), [PasswordInput](/PasswordInput), [PhoneNumberInput](/PhoneNumberInput), [Selector](/Selector), [TextArea](/TextArea), [TextInput](/TextInput). ## [Button](/Button) `type`/`danger` → `color`/`variant` The Button block has a new property API: | Old (v4) | New (v5) | |---|---| | `type: primary` | `color: primary`, `variant: solid` | | `type: dashed` | `variant: dashed` | | `type: text` | `variant: text` | | `type: link` | `variant: link` | | `danger: true` | `color: danger` | #### Before (v4) ```yaml - id: my_button type: Button properties: title: Delete type: primary danger: true ``` #### After (v5) ```yaml - id: my_button type: Button properties: title: Delete color: danger variant: solid ``` ## Date blocks The internal date library has changed from Moment.js to Day.js. Standard date format strings (`YYYY-MM-DD`, `YYYY-MM-DD HH:mm:ss`) work identically. If you use custom format strings, verify they produce the expected output — some moment.js-specific tokens may differ in dayjs. The date blocks — [DateSelector](/DateSelector), [DateRangeSelector](/DateRangeSelector), [DateTimeSelector](/DateTimeSelector), [MonthSelector](/MonthSelector), and [WeekSelector](/WeekSelector) — also have the `bordered` → `variant` change described above. ## Removed blocks ### Comment The Comment block has been removed (it was removed from antd v5). Use a composition pattern with [Card](/Card), [Flex](/Flex), [Avatar](/Avatar), and [Paragraph](/Paragraph) instead: ```yaml - id: comment type: Card properties: size: small blocks: - id: comment_header type: Flex properties: align: center gap: small blocks: - id: avatar type: Avatar properties: color: '#402B18' content: TD - id: author type: Paragraph properties: content: "**The Dude** — 18 January, 1998" - id: comment_content type: Paragraph properties: content: "Yeah, well, you know, that's just, like, your opinion, man." ``` ## New blocks The following blocks are new in v5: | Block | Category | Description | |---|---|---| | [FloatButton](/FloatButton) | display | Floating action button | | [Tour](/Tour) | display | Guided tour component | | [QRCode](/QRCode) | display | QR code generator | | [ColorSelector](/ColorSelector) | input | Color picker input (renamed from ColorPicker) | | [SegmentedSelector](/SegmentedSelector) | input | Segmented control selector (renamed from Segmented) | | [Flex](/Flex) | container | Flexbox layout container | | [Splitter](/Splitter) | container | Resizable split-pane container | | [Masonry](/Masonry) | container | Masonry (Pinterest-style) layout | | [MasonryList](/MasonryList) | list | Data-driven masonry layout | | [DropdownMenu](/DropdownMenu) | container | Dropdown navigation menu | | [DropdownButton](/DropdownButton) | input | Button with dropdown menu | | [Search](/Search) | input | Command palette search | | [Watermark](/Watermark) | container | Watermark overlay container | | [ConfigProvider](/ConfigProvider) | container | Scoped theme provider | ## Removed packages ### `@lowdefy/blocks-color-selectors` The `@lowdefy/blocks-color-selectors` package has been removed. The `ColorPicker` block has been renamed to `ColorSelector` and moved to `@lowdefy/blocks-antd`. Update your config: ```yaml # Before (v4) - id: color type: ColorPicker # After (v5) - id: color type: ColorSelector ``` ### `@lowdefy/blocks-algolia` The `@lowdefy/blocks-algolia` package has been removed. Use the new `Search` block in `@lowdefy/blocks-antd` instead, which provides a MiniSearch-powered command palette search with pre-built index support, grouped results, and keyboard navigation. See [Search](/Search). ## New features - **`class` property** — Apply Tailwind CSS classes to any block. See [Custom Styling](/custom-styling). - **`style` with `.` keys** — Apply inline styles to specific parts of a block using CSS slot keys (e.g. `style: { .header: {...} }`). - **`properties.theme`** — Override antd design tokens per block. - **`_theme` operator** — Access theme seed tokens in expressions. See [`_theme`](/_theme). - **Theming system** — App-level, component-level, and block-level theme customization. See [Theming](/theming). - **Keyboard shortcuts** — Add `events.onClick.shortcut: 'mod+k'` to any event for platform-aware keyboard shortcuts. Blocks like [Button](/Button), [Anchor](/Anchor), [Tag](/Tag), and [Menu](/Menu) render a `ShortcutBadge` automatically. - **`lowdefy upgrade` CLI** — Run `lowdefy upgrade` to automatically migrate your config from v4 to v5. Includes 20 codemods covering antd prop renames, layout grid migration, dayjs migration, and more. Supports `--plan`, `--dry-run`, and `--resume`. - **ECharts v6** — Upgraded to echarts 6.0.0 with improved flex container sizing. ## Common pitfalls 1. **`styles.less` not loading** — Rename to `styles.css`. Less is no longer supported. 2. **`style.sm` not working** — Use `class: "... sm:..."` with Tailwind responsive classes instead. 3. **`bordered: false` not working** — Use `variant: borderless`. 4. **`@primary-color` not working** — Use `theme.antd.token.colorPrimary` in `lowdefy.yaml`. 5. **Button looks different** — Update `type` to `color`/`variant`. See the mapping table above. 6. **`areas` deprecated warning** — Rename `areas` to `slots` in your config. --- url: https://docs.lowdefy.com/md/reference/reference.md --- # Reference Every Lowdefy primitive, by the book. New to Lowdefy? Start with [The Four Primitives](/four-primitives) under Learn. 114 UI components — inputs, tables, charts, containers, and lists. 70 functions that express logic and shape data anywhere in config. 24 event handlers that make pages interactive. 19 integrations — databases, storage, HTTP services, and AI providers. Flow control for API routines — loops, branches, and error handling. The shape of app, page, block, request, and auth config. --- url: https://docs.lowdefy.com/md/reference/blocks-reference.md --- # Blocks Blocks are the visual building parts of every page. Browse by what you are building — the sidebar groups the same blocks by technical type (Input / Display / Container / List). Forms Capture input with validated form controls. Data display Tables, charts, and stats. Layout Arrange content on the page. Feedback Alerts, modals, and loading states. Navigation Menus, steps, and wayfinding. Media Images, maps, and files. Text Titles, prose, and rich content. AI Agent chat interfaces, documented under Learn › AI Agents. --- url: https://docs.lowdefy.com/md/reference/operators-reference.md --- # Operators Operators are functions written directly in config — they evaluate wherever they appear. All 70 operators, grouped by what they do. See [Operators](/operators) for how evaluation works. Logic & control Branch, compare, and combine conditions. State & context Read page, user, and app context. Requests & APIs Read request responses and API results. Build & config Compose config at build time. App & i18n App metadata, menus, theme, and translations. Data manipulation Get, shape, and query data structures. Math Arithmetic and number utilities. String Transform and match strings. Date & time Create and format dates. Programming Escape hatches into JavaScript and expressions. Format & encode Serialize, encode, and hash values. --- url: https://docs.lowdefy.com/md/reference/actions-reference.md --- # Actions Actions run in response to events — a button click, a page load, an input change. All 24 actions, grouped by purpose. See [Events and Actions](/events-and-actions) for how event chains work. State & validation Write state and validate user input. Navigation & focus Move users around the app. Data Call requests and APIs. UI & preferences Messages and user preferences. Files & device Files, clipboard, and device APIs. Flow Control the action chain. Auth & session Sign users in and out. --- url: https://docs.lowdefy.com/md/reference/connections-reference.md --- # Connections Connections integrate your app with databases, APIs, and services; requests execute against them server-side. All 19 connections, grouped by type. See [Connections and Requests](/connections-and-requests) for the model. SQL Relational databases. NoSQL & search Document stores, search, and caching. AI LLM providers and MCP tools. Storage & sheets Object storage and spreadsheets. HTTP & services HTTP APIs and hosted services. --- url: https://docs.lowdefy.com/md/reference/schema-reference.md --- # Schema Reference Every Lowdefy app is one config object, assembled from your YAML files at build time. The schema material lives with the concept that owns it — this hub points to each area. The lowdefy.yaml root object, pages, and how config is assembled. The block object — id, type, properties, layout, style, events, and slots. Connections, requests, and payloads. Providers, adapters, callbacks, and protected pages. --- url: https://docs.lowdefy.com/md/learn/quickstart.md --- # Quickstart A running Lowdefy app in five minutes. You need [Node.js](https://nodejs.org) v24 or newer and [pnpm](https://pnpm.io) installed. ## 1. Create an app Initialize a new app and start the dev server: $ pnpx lowdefy@5 init && pnpx lowdefy@5 dev This creates a `lowdefy.yaml` in the current directory and opens your app at [http://localhost:3000](http://localhost:3000). The dev server reloads on every save. ## 2. Add a block Pages are lists of blocks. Open `lowdefy.yaml` and add a `TextInput` and a `Title` to the first page's `blocks`: ```yaml blocks: - id: name type: TextInput properties: title: What is your name? - id: greeting type: Title properties: content: _nunjucks: template: 'Hello {{ name }}!' on: name: _state: name ``` Save, and type into the input — the title updates as you type. ## 3. What just happened You used three of the four primitives: - The **blocks** `TextInput` and `Title` render the UI. - The **operators** `_state` and `_nunjucks` read the input's value from page state and template the greeting. Operators re-evaluate whenever state changes. - Behind the scenes, typing fires **events** — the fourth primitive, **actions**, lets you respond to them (navigate, call APIs, set state). Add a **connection** and a request when you're ready to talk to a database or API. ## Next steps - Take the [full tutorial](/tutorial-start) — build a complete app with requests and data, in about an hour. - Read [The Four Primitives](/four-primitives) for the mental model. - Browse the [Reference](/reference) to see everything Lowdefy ships. --- url: https://docs.lowdefy.com/md/concepts/four-primitives.md --- # The Four Primitives Every Lowdefy app is composed from four primitives. Learn these four ideas and you can read any Lowdefy app. What the user sees. React components — inputs, tables, charts, containers — arranged into pages. How the app thinks. Functions written in config that read state and shape data, re-evaluated on every change. What happens next. Event handlers that run when users click, pages load, or values change. Where the data lives. Server-side integrations to databases, APIs, and services, called through requests. ## How they fit together A page is a tree of **blocks**. Blocks hold values in page **state** — a `TextInput` writes what the user types, a `Selector` writes the chosen option. **Operators** are functions evaluated in config. They read state and context — `_state`, `_user`, `_media` — and compute the values around them: a block's visibility, a title's text, a request's query. When state changes, operators re-evaluate and the page updates. User interaction fires **events** — a click, a page load, an input change. Each event runs a list of **actions**: `SetState` writes state, `Link` navigates, `Request` calls the server. **Connections** define the services your app talks to — a database, an HTTP API, an AI provider. **Requests** execute against them on the server, so credentials never reach the browser. Actions trigger requests; operators read their results. One loop, four primitives: blocks render state → events fire actions → actions set state and call requests → operators re-evaluate → blocks re-render. ## Where to go deeper - [Blocks](/blocks) — the block object and the page tree. - [Operators](/operators) — evaluation rules and where operators work. - [Events and Actions](/events-and-actions) — event chains, error handling. - [Connections and Requests](/connections-and-requests) — the server-side model. - Or see everything in the [Reference](/reference). --- url: https://docs.lowdefy.com/md/recipes/recipes.md --- # Recipes Recipes are end-to-end compositions — complete, copyable examples that show how blocks, operators, actions, and connections work together to solve a real problem. The first recipes are on their way: - Login flow with protected pages - CRUD table with edit modal - File upload with live preview - Multi-step form with validation - Dashboard with filters, KPIs, and a chart Until they land, the [tutorial](/tutorial-start) builds a complete app step by step, and the [example repositories](https://github.com/lowdefy?q=example) on GitHub cover common patterns like CRUD, surveys, and authentication. --- url: https://docs.lowdefy.com/md/plugins/plugins.md --- # Plugins Everything in Lowdefy is a plugin — every block, operator, action, and connection is an npm package, and your app can install more. A browsable catalog of official and community plugins is coming; until then, the [community plugins repository](https://github.com/lowdefy/community-plugins) lists what the community has published. ## Build a plugin How the plugin system works and what a plugin package contains. Set up a workspace and develop plugins against a local app. Write a custom action. Write a custom connection and requests. Write a custom operator.