# Welcome to Directual

Hello, Creators! 🙌

[Directual](http://directual.com/) is a **full-stack no-code/low-code platform**. You can build the whole stack here — backend, frontend, automations, APIs, and AI features — without drowning in boilerplate code.

***

### Why Directual?

Other no-code tools are fine for prototypes, but they quickly hit a ceiling. Directual is built for growth:

* **Full-stack** — backend + frontend in one platform.
* **Scalable** — from weekend MVPs to enterprise-level systems.
* **AI-ready** — built-in vector DB, embeddings, RAG pipelines, and agents.

You don’t have to switch platforms when your app grows. Directual is designed to scale with you.

***

### Who is it for?

* **Business teams** — automate workflows, launch dashboards, and run internal apps without waiting on IT.
* **Studios & freelancers** — our favorite partners. Build apps for clients, run agency-style businesses, and earn through our affiliate program while delivering real value.
* **Developers** — skip the boring parts like infrastructure, DevOps, API setup, and security — all of that comes out of the box. Focus on building real logic and value instead of plumbing.
* **Solopreneurs** — ship products fast, from Telegram Mini Apps to full SaaS.
* **Enterprises** — handle serious workloads with our **NoSQL database** that can store millions of records, plus a **vector database** for semantic search and AI features. Deploy on SaaS, in a private cloud, or fully on-premise — whatever fits your infrastructure needs.

***

### Core concepts

Directual is built around four key elements:

* **Database** — flexible NoSQL storage for millions of records, with a built-in **vector DB** for semantic queries and AI-driven features.
* **API** — a powerful builder with REST, GraphQL, and webhooks to connect your app with any system. Comes with advanced role-based access, rich filtering and sorting, even synchronous logic on any endpoint (a rare feature), plus Swagger docs out of the box.
* **Scenarios** — the heart of the platform. A universal, abstract workflow engine — not clunky BPMN. Run thousands of events in real time or schedule logic to fire automatically. Visual, powerful, and flexible for any business process or integration.
* **UI builder** — not a dumb drag-and-drop, but a sharp tool tailored for internal apps. Work with big components like forms, tables, and cards that configure quickly, with role-based access models easy to apply. Build web apps, dashboards, and Telegram Mini Apps fast and clean.

***

### What can you build?

* **Internal business apps** — CRMs, dashboards, employee portals, learning management systems, service desks, and more — all integrated with your IT stack.
* **Telegram Mini Apps** — lightweight apps inside Telegram, reaching hundreds of millions of users. Supports payments, deep integrations, and of course bots are first-class citizens too.
* **AI assistants & RAG systems** — from knowledge-based chatbots to automation agents.
* **Enterprise solutions** — robust apps with authentication, role-based access, APIs, and integrations.

***

{% hint style="success" %}
**In short:** Directual is where **no-code meets full-stack power**.
{% endhint %}


# Platform Features

Take a closer look at the essential components of an app 🏗

### Modules of the Platform&#x20;

* **Database**. Scalable and flexible, based on NoSQL storage.&#x20;
* **Web-app.** Mobile friendly web-page builder. Also works as Telegram Mini App.
* **Backend-logic**. Real-time and scheduled [scenarios](/scenarios/principles-of-scenarios) (streaming processing) and reports (batch jobs)
* **API layer**. Including [API endpoints](/api-integrations/api-endpoints-security-layer) and [Webhooks](/api-integrations/webhooks)
* **Plugins**. Additional scenario blocks, auth methods and web-components. Plus blockchains&#x20;


# Signing up & Logging in

Welcome! Here is your 🗝 to endless possibilities!

We are glad to see you among the Creators!

Go to [my.directual.com](https://my.directual.com) to create and login into your Directual account.

![Login page](/files/-M5BU6-qgVSa7htOg_an)

### Signing up with email&#x20;

Follow these simple steps to sign in with an email:

1. Fill in the [signup form](https://my.directual.com/platform/login/register)
2. Your password will be sent to your email address
3. [Sign in](https://my.directual.com/platform/login/) using the received password
4. We recommend [changing the password](/getting-started/login-and-signup/profile-settings) after login

### Signing up & logging in with Google account

You can sign up with your Google account by clicking both "Sign in with Google" and "Sign up with Google". Your Google account will be automatically connected to your Directual account.

If you signed up with **an email**, you still can connect **your Google account** in profile settings and then log in using **both** your email/password and Google account.

If you signed up with an email/password, you can log in with the same email address via Google.

### Password recovery

Use the following steps to recover your password:

1. Reset the password, using the [Forgot password](https://my.directual.com/platform/login/restore/) form
2. You will receive a validation code if there is a registered Directual account associated with this email
3. Enter the validation code
4. Your new password for your Directual account will be sent to your email
5. We recommend [changing the password](/getting-started/login-and-signup/profile-settings) after login


# Profile Settings

Nice to meet you! 👨‍💻

Manage your account by clicking "Profile settings" in the bottom left corner of the screen:

![Profile settings page](/files/quuTfzcLr5Jv1i46I18g)

You can customize the following:

* Your name
* Password
* Link/unlink your Google account


# App Management

Let's get this show on the road! 🚀

An **application** is a distinct functional unit that includes data structures, scenarios, API endpoints, web pages, etc.

## Creating new applications

Click the \[**+ New app ]** button to create and customize your application:

To [create an app with a paid plan](/pricing-and-billing/pricing-plans#app-plans), you need to add a basic payment method or add funds to your account balance in advance.

![New app page](/files/BD0yZV9KsFguCv0vY5z8)

{% hint style="info" %}
Choose the system name wisely — it is the address of your app (for example, ***yourapp**.directual.app*), it cannot be changed in the future. But, certainly, you can use your [custom domain address](/web-pages/web-app-settings/custom-domain).
{% endhint %}

### Using templates

You can create a blank app and build it from scratch, or use one of our free templates. This is a great way to quickly learn the Platform. Some templates require integrations (with email or Telegram) which can be configured right here.

### Application settings

Click the "App settings" button on an app card to edit the details:

![App settings page](/files/0DCeXvHiGZcnTTFJXvKk)

#### **General settings**

* System name (can't be changed later!)
* Displayed name
* Description
* You can also delete your app from this menu

#### **Adding developers**

To add developers to work on an app, you need to create a [Team](/teams/teams).

#### Billing

There are three different [app plans](/pricing-and-billing/pricing-plans) to cater to your specific needs:

1\) **Startup** plan (monthly/yearly)

2\) **Pro** plan (monthly/yearly)

3\) **Business** plan (monthly/yearly)

#### **Hosting region**

The option to choose a hosting region is only available with the [Pro and Business plans](#hosting-region).

![Hosting region page](/files/5vWV2QQjOfxclxq0G7oA)


# Templates to Start With

Get the ball rolling! 🏀

Use one of the free templates to get up and running quickly with your app development!

* [Basic Template (Blank app)](/getting-started/templates-to-start-with/basic-template-blank-app);
* [CRM Template](/getting-started/templates-to-start-with/crm-template);
* ... coming more soon...


# Basic Template (Blank app)


# CRM Template


# Learning Directual

📚 Study, learn more, learn forever! (V. I. Lenin)

## 101-course

Discover dozens of comprehensive video tutorials on the [101-crash course page](https://www.directual.com/101-crash-course).

## Lesson library

Explore lessons and tutorials on specific topics in the [No-code Lesson Library](https://www.directual.com/lesson-library).

## What's New section

Find new feature descriptions and a link to the [public roadmap](https://dev.directual.app/open-pipeline) in the "What's new" section.

![](/files/cTvxQJQFMTK9CQtWZlFW)


# Webinars

### No-code and Security

April 11, 2024

{% embed url="<https://youtu.be/cp-eGxV8L3s?si=2gZgoUDrRl5HnNIq>" %}

### Update overview

January 12, 2024

{% embed url="<https://youtu.be/SmACCw5eW9s?si=iPTpW8MjMqM-OlRG>" %}

### Building no-code apps with AI

August 31, 2023

{% embed url="<https://youtu.be/YRIlMwNAs8U?si=-wh_KP5EhXJ13KoT>" %}

### Webinar with NoCodeDevs

October 17, 2022

{% embed url="<https://youtu.be/S7bfML4P05s?si=_JZOVhmuTscj7mKp>" %}

### Key Differences from Other No-code Platforms

November 15, 2021

{% embed url="<https://youtu.be/9jvdFUaHN30?si=2Eup6G_G7fLmeMC8>" %}


# Data Structures

This is the fundamental part of any app 🏰

Data structures are a database for your app.

Objects from data structures:

* are processed in [scenarios](/scenarios/principles-of-scenarios)
* are processed in reports
* can be accessed via [API-endpoints](/api-integrations/api-endpoints-security-layer) and [Webhooks](/api-integrations/webhooks)

By default, the following object properties exist:

* **ID**. This is a unique key to the object. If you change the ID, a new object will be created
* **Who**. Who changed/created an object. Can't be changed from UI, API, or scenario
* **dateCreated**. The date and time when the object was created. Can't be changed from UI, API, or scenario
* **dateChanged**. The date and time when the object was changed. Can't be changed from UI, API, or scenario

{% hint style="warning" %}
The maximum **ID** length is 36 characters!
{% endhint %}

You can add custom fields (properties) and groups for your data structure. Each field has to be a [type from the list](/data/data-types).

{% embed url="<https://youtu.be/-14a_hoVUw4>" %}

### Structure visible name

**Y**ou can set up a visible name of a structure by configuring the fields. The name will be displayed in the objects near links to that structure, in [cards](/web-pages/components/legacy-components/cards), and in selections [form](/web-pages/components/legacy-components/form#link-and-arraylink-quick-search-option).


# System Structures

Each app starts with several **system structures**:

![](/files/-M5HQveTWoxtgTKFaXiV)

### Frequently used system structures:

| Folder              | Structure name           | Structure system name | What is it for?                                                                                                                      |
| ------------------- | ------------------------ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| Root/               | **App users**            | `WebUser`             | Objects are your app users. [RBAC is based](broken://pages/-M4ORivK1hGzFJTL3NfZ) on the properties of the objects from **App users** |
| Root/               | **Files**                | `FileUpload`          | [File storage](/data/file-storage)                                                                                                   |
| Root/ System/       | **App global constants** | `GlobalVariables`     | Data structure for storing[ global constants](/scenarios/using-variables/global-constants)                                           |
| Root/ Integrations/ | —                        | —                     | Contains data structures for your [integrations](https://www.directual.com/integrations)                                             |

### Less frequently used system structures:

| Folder              | Structure name       | Structure system name | What is it for?                                                                |
| ------------------- | -------------------- | --------------------- | ------------------------------------------------------------------------------ |
| Root/ System/       | **User sessions**    | `WebUserSession`      | Store sessions for **App users**                                               |
| Root/ System/       | **Social sessions**  | `SocialUser`          | Store connection to social accounts (Facebook, Google, etc.) for **App users** |
| Root/ System/ Logs/ | —                    | —                     | Store app logs, which you can see on the Dashboard                             |
| Root/ System/ Logs/ | **Import data logs** | `ImportInformation`   | Data import operation status for objects                                       |


# Data Types

### Available data types

There are general types like '**string**,' subtypes such as '**email**,' and formatting options for some data types.

| Type          | Subtype        | Formatting options                                                                | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| ------------- | -------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Type          | Subtype        | Formatting options                                                                | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| **string**    | —              | —                                                                                 | <p>A sequence of characters used to represent text.</p><p><code>// Example:</code> </p><p><code>Hello world!</code></p>                                                                                                                                                                                                                                                                                                                                                                     |
| **string**    | markdown       | —                                                                                 | Formatted text (see [markdown cheatsheet](/data/data-types/markdown-cheatsheet))                                                                                                                                                                                                                                                                                                                                                                                                            |
| **string**    | html           | —                                                                                 | Formatted HTML text                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| **string**    | email          | —                                                                                 | <p>Formatted email address</p><p><code>// Example:</code></p><p><code><support@directual.com></code></p>                                                                                                                                                                                                                                                                                                                                                                                    |
| **string**    | phone          | —                                                                                 | <p>String of numbers</p><p><code>// Example:</code></p><p><code>79201231212</code></p>                                                                                                                                                                                                                                                                                                                                                                                                      |
| **string**    | color          | —                                                                                 | <p> HEX color</p><p><code>// Example:</code></p><p><code>#AAA012</code></p>                                                                                                                                                                                                                                                                                                                                                                                                                 |
| **string**    | webLink        | —                                                                                 | <p>A URL</p><p><code>// Example:</code></p><p><code><http://directual.com></code></p>                                                                                                                                                                                                                                                                                                                                                                                                       |
| **string**    | youTube        | —                                                                                 | <p>Youtube URL</p><p><code>// Example: <https://www.youtube.com/watch?v=m4U232MuTG4></code></p>                                                                                                                                                                                                                                                                                                                                                                                             |
| **number**    | —              | —                                                                                 | <p>A numeric data type from –9223372036854775808 to 9223372036854775807.</p><p><code>// Examples:</code></p><p><code>42</code></p><p><code>-102</code></p><p><code>0</code></p>                                                                                                                                                                                                                                                                                                             |
| **number**    | positiveNum    | —                                                                                 | A positive number                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| **decimal**   | —              | —                                                                                 | <p>An exact numeric data type. Directual interface displays up to 15 digits.</p><p><code>// Examples:</code></p><p><code>0.12</code></p><p><code>-99999999.9999999999</code></p><p><code>234.23123454</code></p><p>Supports up to 34 digits without any loss and exponent range from –6143 to +6144</p>                                                                                                                                                                                     |
| **Array**     | —              | —                                                                                 | <p>An array is an ordered collection of elements. Each element has its own type.</p><p><code>// Examples:</code></p><p><code>1,a,hello world,4,true</code></p><p><code>42.2,{"key1":true,"key2":"hello"},abc</code></p>                                                                                                                                                                                                                                                                     |
| **Date**      | —              | [Formatting date format](/data/data-types/formatting-date-time-data)              | <p>Date and time. By default has <em>date-time</em> format: YYYY-MM-DDTHH:mm:ss.sssZ)</p><p>Also can be processed as <em>timestamp</em>, which represents the time, in milliseconds since 00:00:00 UTC on 1 January 1970.</p><p><code>// Examples:</code></p><p><code>2019-10-01T10:00:00.000Z = 01 October 2019 10:00:00 in date-time format;</code></p><p><code>1569924000000 = 01 October 2019 10:00:00 in timestamp format;</code></p>                                                  |
| **Boolean**   | —              | Options names (`true` and `false` by default)                                     | The type for *yes or no*. There are three options for its value: `true, false, null`                                                                                                                                                                                                                                                                                                                                                                                                        |
| **JSON**      | —              | —                                                                                 | <p>JavaScript object notation. Often used in integrations. MDN Documentation.</p><p><code>// Example:</code></p><p><code>{</code></p><p>   <code>"title": "War and peace",</code></p><p>   <code>"year": 1865,</code><br>   <code>"Chapters": \[1,2,3,4,5,6],</code></p><p>   <code>author: {</code></p><p>      <code>"name: "Leo",</code></p><p>      <code>"last\_name": "Tolstoy"</code></p><p>   <code>},</code></p><p>   <code>"is\_favourite": false</code></p><p><code>}</code></p> |
| **JSON**      | checkboxes     | Checkbox options + custom option                                                  | <p><code>// JSON example:</code></p><p><code>{ "option2": true, "customOption": "2021-01-06T00:00:00.000Z" }</code></p>                                                                                                                                                                                                                                                                                                                                                                     |
| **JSON**      | radioOptions   | Radio options + custom option                                                     | <p><code>// JSON example:</code></p><p><code>{ "value": "hello" }</code></p><p><code>or</code></p><p><code>{ "customOption": "hello world" }</code></p>                                                                                                                                                                                                                                                                                                                                     |
| **JSON**      | slider         | Unit name + step + min, max values                                                | <p><code>// JSON example:</code></p><p><code>{ "firstValue": 3 }</code></p>                                                                                                                                                                                                                                                                                                                                                                                                                 |
| **JSON**      | rangeSlider    | Unit name + step + min, max values                                                | <p><code>// JSON example:</code></p><p><code>{ "secondValue": 6, "firstValue": 3 }</code></p>                                                                                                                                                                                                                                                                                                                                                                                               |
| **JSON**      | geo-data       | Array of objects, that include coordinates, title, image (link) and a description | `// JSON example`                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| **Link**      | —              | —                                                                                 | The **ID** of an object from the linked structure.                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| **arrayLink** | —              | —                                                                                 | **Array** of **Links**                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| **File**      | —              | —                                                                                 | **= String**, the URL of the file. Here the [fileUpload](/data/file-storage) structure is often used.                                                                                                                                                                                                                                                                                                                                                                                       |
| **File**      | image          | —                                                                                 | **= String**, the URL of the image (PNG, SVG, JPG, BMP).                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| **File**      | multipleImages | —                                                                                 | **= String**, the URLs of images, comma separated.                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| **File**      | multipleFiles  | —                                                                                 | **= String**, the URLs of files, comma separated.                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| **Operator**  | —              |                                                                                   | <p>Comparison operator from <a href="/pages/-M5H7dzZwlk9M_H20dnV">the list</a>.</p><p><code>// Examples:</code></p><p><code>></code></p><p><code><=</code></p><p><code>like</code></p><p><code>isEmpty</code></p>                                                                                                                                                                                                                                                                           |


# Markdown Cheat Sheet

**Markdown** is a straightforward method to format text for a consistent appearance on any device. It focuses on essentials, like using familiar keyboard symbols, without fancy font changes like size, color, or type.

{% hint style="info" %}
Telegram has its own version of markdown. Check it out in [Telegram step documentation.](/scenarios/editing-scenarios/integration-steps/telegram-step)
{% endhint %}

| Type...                                                                                                | Or...                                                                             | ...to Get                                        |          |                      |            |            |            |                      |      |       |        |             |   |       |
| ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------- | ------------------------------------------------ | -------- | -------------------- | ---------- | ---------- | ---------- | -------------------- | ---- | ----- | ------ | ----------- | - | ----- |
| `*Italic*`                                                                                             | `_Italic_`                                                                        | *Italic*                                         |          |                      |            |            |            |                      |      |       |        |             |   |       |
| `**Bold**`                                                                                             | `__Bold__`                                                                        | **Bold**                                         |          |                      |            |            |            |                      |      |       |        |             |   |       |
| `# Heading 1`                                                                                          | <p><code>Heading 1</code></p><p><code>=========</code></p>                        | Heading 1                                        |          |                      |            |            |            |                      |      |       |        |             |   |       |
| `## Heading 2`                                                                                         | <p><code>Heading 2</code></p><p><code>---------</code></p>                        | Heading 2                                        |          |                      |            |            |            |                      |      |       |        |             |   |       |
| `[Link](https://directual.com)`                                                                        |                                                                                   | [Link](https://directual.com)                    |          |                      |            |            |            |                      |      |       |        |             |   |       |
| `![Image](https://directual.com/logo.png)`                                                             |                                                                                   | Image                                            |          |                      |            |            |            |                      |      |       |        |             |   |       |
| `> Blockquote`                                                                                         |                                                                                   | Blockquote                                       |          |                      |            |            |            |                      |      |       |        |             |   |       |
| <p><code>\* List</code></p><p><code>\* List</code></p><p><code>\* List</code></p>                      | <p><code>- List</code></p><p><code>- List</code></p><p><code>- List</code></p>    | <ul><li>List</li><li>List</li><li>List</li></ul> |          |                      |            |            |            |                      |      |       |        |             |   |       |
| <p><code>1. List</code></p><p><code>2. List</code></p><p><code>3. List</code></p>                      | <p><code>1) List</code></p><p><code>2) List</code></p><p><code>3) List</code></p> | <ol><li>List</li><li>List</li><li>List</li></ol> |          |                      |            |            |            |                      |      |       |        |             |   |       |
| <p><code>Horizontal Rule</code></p><p><code>---</code></p>                                             |                                                                                   | Horizontal Rule———————                           |          |                      |            |            |            |                      |      |       |        |             |   |       |
| `` `Inline code` with backticks ``                                                                     |                                                                                   | `Inline code` with backticks                     |          |                      |            |            |            |                      |      |       |        |             |   |       |
| <p><code>`</code> </p><p><code>multiple</code></p><p><code>line code</code></p><p><code>`</code>  </p> |                                                                                   | Multiple line code block                         |          |                      |            |            |            |                      |      |       |        |             |   |       |
| <p><code>                                                                                              | Header 1                                                                          | Header 2                                         | Header 3 | </code></p><p><code> | :--------- | ---------: | :--------: | </code></p><p><code> | Left | Right | Center | </code></p> |   | Table |


# Indexing Fields

The following cases require you to set **indexing** manually:

* Filters on *linked fields* in [API-endpoints](/api-integrations/api-endpoints-security-layer)
* Setting filters *on linked objects* in [scheduled scenarios](/scenarios/schedule-triggers)
* Searching *by linked objects* in the [Search step](/scenarios/editing-scenarios/action-steps/search-objects-step)
* Composing [Reports](broken://pages/-M4O2KQYdd875MCIR7Fe), which includes *conditions on fields of linked objects*

### Configuring indexing&#x20;

Navigate to Data structure configuration, select Show advanced settings, and specify the linked object fields for indexing, separated by commas.

![](/files/-MU3BWUo3f3iBJ7nzX64)

{% hint style="info" %}
After setting the fields for indexing you should refresh indexing. Go to Data structure → Other operations → Refresh all indexes for search
{% endhint %}

### Reindexing from scenarios

Use `$D.system.refreshIndex("servers")` [SDK function](/javascript-sdk/internal-usdd-methods)


# Formatting Date/Time

Dates in the Platform are stored as strings in the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format, which has the following structure:

`YYYY-MM-ddTHH:mm.ss.SSS`

Examples:

* `2024` — year 2024
* `2023-02` — February 2023
* `2022-04-12` — 12th of April, 2022
* `1987-03-31T04:30` — 31st of March 1987, 4:30 a.m. UTC

Formatting dates has three use cases:

* Setting standard formatting for data structure field
* [Import](/data/import-and-export)
* [Template formatting](/template-system/basics-of-template-system#formatting-date)

#### Examples

|         |            |
| ------- | ---------- |
| DD MMM  | 24 June    |
| DD.MM.Y | 24.06.2023 |

Formatting date/time details:&#x20;

| Symbol | Meaning           | Type                     | Mask                           | Example                             |
| ------ | ----------------- | ------------------------ | ------------------------------ | ----------------------------------- |
| C      | Century, AD       | number                   | CC.dd.MM.yyyy                  | 20.31.12.1909                       |
| Y      | Year, AD          | number                   | Y                              | 1996                                |
| w      | Week in a year    | number                   | ww                             | 27                                  |
| e      | Day of a week     | number                   | e                              | 2                                   |
| E      | Day of a week     | text                     | <p>EEEE</p><p>E</p>            | <p>Tuesday</p><p>Tue</p>            |
| y      | Year              | number                   | y                              | 1996                                |
| D      | Day in a year     | number                   | D                              | 189                                 |
| M      | Month in a year   | <p>text</p><p>number</p> | <p>MMMM</p><p>MMM</p><p>MM</p> | <p>July</p><p>Jul</p><p>07</p>      |
| d      | Day in a month    | number                   | dd                             | 31                                  |
| a      | AM/PM             | text                     | <p>a </p><p>a</p>              | <p>AM </p><p>PM</p>                 |
| H      | Hour (0\~23)      | number                   | H                              | 0                                   |
| m      | Minute            | number                   | mm                             | 36                                  |
| s      | Second            | number                   | ss                             | 25                                  |
| S      | millisecond       | number                   | SSS                            | 783                                 |
| z      | Time zone         | text                     | z                              | Pacific Standard Time; PST          |
| Z      | Time zone / shift | text                     | Z                              | -0800; -08:00; America/Los\_Angeles |

### Formatting *date* field type

![](/files/-M_5NZQQVQ8kNWLBte0E)

Time constraints field:

```javascript
{
   "hours": {
      "min": 1,
      "max": 15,
      "step": 3
   },
   "minutes": {
      "min": 10,
      "max": 30,
      "step": 20
   }
}
```

{% hint style="info" %}
Remember, these constraints only affect web page components and user input in the platform. Scenarios and API calls are **not limited.**
{% endhint %}

### Time zones

Dates are stored in the UTC format. If you select 'use user's local time zone' in settings, the platform's interface and web-page builder-based apps will automatically use the user's browser time zone. However, when working with dates in scenarios, you must manage the time zone manually using the `moment({{your_date}}).zone(Z).format();`function, where `Z` represents the timezone offset, such as `-3` or `+10` .


# Directual Query Language (DQL)

Use advanced search and filtering 🔎

Data structure can contain millions of objects. You can use Directual Query Language (DQL) for searching and filtering them.&#x20;

![Example of applying DQL](/files/-M5HZO6ac2g_viBICsY-)

Here are some examples of DQL-requests:

Request `42` is a synonym to `id = "42"` — it finds the object with **ID** = *42.*

`(title like "sun" AND year < 1950) OR is_good = "true"`— finds objects of two groups. First: with **titles** similar to *The Sun also rises* or *Under the Blood-Red Sun,* and with **year** less than 1950. Plus second: with **is\_good** equals *true*.

`email like "@directual.com" AND name != ""`— finds objects with **email** field similar to *<team@directual.com>*, or *<hello@directual.com>*, and with not empty **name** field.

`birth_date <= "2000-04-09T00:00:00"`— finds objects with **birth\_date** before *9th April 2000.*

{% hint style="info" %}
Note that all values for comparison must be enclosed in quotes. Numbers can be compared with or without quotes.
{% endhint %}

{% embed url="<https://youtu.be/5DuE2t7jzEM>" %}


# Import and Export

From Directual to XLS and back

## Export data from Directual

Exporting objects to XLS or CSV files.

{% hint style="info" %}
XLS has certain limits, so if you export significant data structure, use CSV.
{% endhint %}

![](/files/-MESeW74KJ2H1OvF2UXI)

## Import data to Directual

Uploading objects into Directual.

{% hint style="info" %}
Pro tip: First, **export** the file, check its format, and then compose the file for importing!
{% endhint %}

![](/files/-MESeOOszLQ1dhfopaeD)


# Import API

### Import <a href="#restapi-import" id="restapi-import"></a>

API method accepts POST-request with CSV / XLS (**multipart/form-data**)

<mark style="color:green;">`POST`</mark> `https://api.directual.com/good/api/v3/struct/structureName/import?appID=appID&appSecret=appSecret`

#### Path Parameters

| Name          | Type   | Description                                  |
| ------------- | ------ | -------------------------------------------- |
| structureName | string | Structure system name                        |
| appID         | string | App ID (go to API section, API keys)         |
| appSecret     | string | API key secret (go to API section, API keys) |

{% tabs %}
{% tab title="200 " %}

```
{
  "result": {
    "insert": 10,
    "update": 1,
    "lastObjectID": "3d00ccc3-eb6a-403e-8846-961e38341b49"
  },
  "status": "OK"
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Tip: Export the file, check its format, then prepare it for import.
{% endhint %}


# File Storage

Store all your files 📦

There is a system data structure Files (`FileUpload`), where you can store your files (documents, pictures, etc.)

### Uploading files in the platform interface

You can create objects in `FileUpload` structure by clicking **Upload files**. The result URL of the file will be in `urlLink` field.

![](/files/-MU2kpFoRKMSKv6Mxs-D)


# API for File Storage

## Upload file with API Endpoints

### Step 1

Create a field type of `link` to `FileUpload` data structure

![Example structure with linked field.](/files/-M__sCOWYWUraS5gyQdB)

Create an API Endpoint which includes that field for POST requesting. Also, include `urlLink` field for reading to get url of the uploaded file.

![](/files/-M__sbkmdTaxKX4Ff-4z)

And now, you could send  `multipart/form-data` request with file (you could use any library for uploading a file to Directual)

```javascript
curl -X "POST" "https://api.directual.com/good/api/v5/data/test/testfileuploaded?appID=?&sessionID" \
     -H 'Content-Type: multipart/form-data; charset=utf-8' \
     -F "file="

```

And these API will return url to your file (if you include urlLink for GET-requesting):

```json
{
    "result":[
        {
            "file":
            {
                "urlLink":"https://api.directual.com/fileUploaded/evp/faec0620-8a6c-48a8-983b-2bb079536dc0.png"
            }
        }
    ],
    "status":"OK"
}
```

### Sample code for React JS

Explore Directual [boilerplate for ReactJS](/directual-react-js/boilerplate-for-react-js)

```javascript
// "file" — field name (link to FileUpload data structure)

const uploadHandler = (e) => {
        const file = e.target.files[0];
        const formData = new FormData();
        formData.append(
            "file", file
        );
        api
            .structure(fileStorage)
            .setData(fileEndpoint, formData,
                {
                    appID: APP_ID,
                    sessionID: auth.sessionID,
                })
            .then((response) => {                
                setResponse(response.result)
                setStatus(response.status)
                setLoading(false)
            })
            .catch((e) => {
                setLoading(false)
                console.log(e.response)
                setBadRequest({
                    httpCode: e.response.status,
                    msg: e.response.data.msg
                })
            })
    }
```

```html
<label htmlFor="upload_files">Upload Files</label>
<input type="file" id="upload_files" name="upload_files" onChange={uploadHandler}/>
```

### Legacy file-upload API

POST to <https://api.directual.com/good/api/v3/file/upload?source=others&appID=appID&appSecret=appSecret>


# API-Endpoints

{% embed url="<https://youtu.be/t8pNin1daBA>" %}


# Advanced techniques for GET and POST requesting

{% embed url="<https://youtu.be/EycsWJe7IIU>" %}


# Paging for GET-requests


# Dynamic sorting for GET-requests


# Custom filtering parameters for GET-requests


# Advanced filtering for GET-requests


# Formatting response for GET-request

## Get Cakes

<mark style="color:blue;">`GET`</mark> `https://api.directual.com/good/api/v5/data/:_TABLE_NAME_/:_METHOD_NAME`

Not required parameter **format**, if you want to get answer as object, you must set this parameter as "head". \
Note: if API endpoint returns collection, and parameter format set as head, platform will return the first element.

#### Query Parameters

| Name      | Type   | Description                                                    |
| --------- | ------ | -------------------------------------------------------------- |
| format    | string | Wrapper for result answer (default: "", options: "" or "head") |
| pageSize  | string | Count of objects in response (default: 30)                     |
| page      | string | Number of page (start with 0, default: 1)                      |
| appID     | string | You app id                                                     |
| sessionID | string |                                                                |

{% tabs %}
{% tab title="200 Cake successfully retrieved." %}

```
{    "name": "Cake's name",    "recipe": "Cake's recipe name",    "cake": "Binary cake"}
```

{% endtab %}

{% tab title="404 Could not find a cake matching this query." %}

```
{    "message": "Ain't no cake like that."}
```

{% endtab %}
{% endtabs %}


# Fields validation for POST-requests


# Synchronic scenarios for POST-requests

See [synch scenarios ](/scenarios/synchronic-scenarios-1#running-synch-scenarios-in-post-requests)


# Cross-Origin Resource Sharing CORS


# API testing and debugging

We recommend to use such software as [Paw](https://paw.cloud/) or [Postman](https://www.postman.com/) to test your API-endpoints.


# Coding mode (raw mode) in filters

You can compose complex filters in API

`"exp": "all" == AND`

`"exp": "any" == OR`

```
[
	{
		"exp": "all",
		"filters": [
        {
            "exp": "any",
            "filters": [
                {
                    "exp": "isEmptyValue",
                    "field": "category",
                    "value": "{{HttpRequest.category}}",
                    "isExp": false
                },
                {
                    "exp": "in",
                    "field": "category",
                    "value": "{{HttpRequest.category}}",
                    "isExp": false
                }
            ]
        },
        {
            "exp": "any",
            "filters": [
                {
                    "exp": "isEmptyValue",
                    "field": "portalID",
                    "value": "{{HttpRequest.portalid}}",
                    "isExp": false
                },
                {
                    "exp": "==",
                    "field": "colour",
                    "value": "{{HttpRequest.colour}}",
                    "isExp": false
                }
            ]
        }
		]
	}
]
```


# Swagger specification

[Swagger](https://swagger.io/) is the OpenAPI Specification was donated to the Linux Foundation under the OpenAPI Initiative in 2015. The specification creates a RESTful interface for easily developing and consuming an API by effectively mapping all the resources and operations associated with it.

You can compose swagger specification for your app including a few (or all) endpoints to the specification.

If you want endpoint to be displayed in swagger specification, turn on that option in the endpoint settings:

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


# Webhooks

Integrate a third-party service with just a few simple clicks 🪝

**Webhooks** represent automated notifications dispatched by applications in response to specific events. These notifications consist of a message or ***payload*** in JSON format and are directed to a distinct URL. In Directual, you have the capability to generate these unique URLs for receiving such notifications.

### Creating webhooks

Go to **API** section → **Webhooks** → **+ New webhook** and enter system name of a new webhook.  In 99% of cases you will need a scenario for parsing incoming objects. Such a scenario can be created automatically right here.

![](/files/-MYf2I2AkdZd-DCUXRpp)

Copy the Webhook URL and use it with your external service to send messages to Directual.

### Storing incoming messages

After setting up a webhook, Directual automatically generates the corresponding [data structure](/data/data-structures) within the ***Integrations*** → ***Webhooks*** folder, and you'll also find a convenient link to it in the webhooks table. The first object in this folder is created by Directual for testing purposes.

![](/files/-MYf4SfXOndQFrMTo-ZY)

There are the following fields in the webhook data structure:

* `id` — usual unique field
* `body, headers, urlData` — fields in JSON format. One or a few of them contain data (depending on a specific third party service)

### Dealing with incoming messages

Objects coming via Webhooks contain data in JSON-format. The best way to deal with them is to [apply JSON-step](/scenarios/editing-scenarios/action-steps/json-step) or to apply [templating techniques for parsing JSON](/template-system/basics-of-template-system/advanced-templating-techniques#handling-json).

### Changing API-response

The default response is:

```
{
  "result": null,
  "status": "OK"
}
```

Occasionally, a third-party system may necessitate a specific response in JSON or even XML format. Fortunately, this is not a limitation, as you have the flexibility to construct any API response you require. Here's how:

#### 1. Add a synchronous scenario for your webhook

![](/files/-MYf86iyYxMVdC0F8qSn)

Please take note that the scenario needs to be published and run.

<br>

#### 2. Setup an API-response step in the scenario

Have a look at the [API-response step documentation](/scenarios/editing-scenarios/integration-steps/api-response).&#x20;

### Practical tip

Take a look at the tutorial on how to update an existing data table using incoming JSON data:

{% embed url="<https://youtu.be/qnc7sBiGkFg>" %}
уы
{% endembed %}


# Authentication API


# Login/password

## Authentication using login-password pair

<mark style="color:green;">`POST`</mark> `https://api.directual.com/good/api/v5/auth?appID=?`

#### Query Parameters

| Name                                    | Type   | Description |
| --------------------------------------- | ------ | ----------- |
| appID<mark style="color:red;">\*</mark> | String | API-key     |

#### Request Body

| Name                                       | Type   | Description |
| ------------------------------------------ | ------ | ----------- |
| provider<mark style="color:red;">\*</mark> | string | rest        |
| username<mark style="color:red;">\*</mark> | string | username    |
| password<mark style="color:red;">\*</mark> | string | password    |

{% tabs %}
{% tab title="200 " %}

```
{
	"result": {
	"token": "8ba4aee8-f509-41d0-9c59-232e828a41a6",
	"username": "test",
	"role": ""
},
	"status": "ok"
}
```

{% endtab %}

{% tab title="404 " %}

```
{
    "msg": "User or password incorrect",
    "code": 108,
    "httpCode": 400,
    "typeError": "ERROR",
    "errorVariables": {},
    "payload": null,
    "status": "ERROR"
}
```

{% endtab %}
{% endtabs %}

Get the `appID` from the API section in your Directual project under API-keys.

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

The login is the ID of the object from the WebUser data structure, and the password is stored encrypted.

You can use the following cURL command to make a POST request to the Directual API:

```json
{
    "provider": "rest",
    "username": "regressUser@directual.com",
    "password": "regress"
}
```

```
curl --location --request POST "https://api.directual.com/good/api/v5/auth?appID=?" \
  --header "Content-Type: application/json" \
  --data "{
\"provider\":\"rest\",
\"username\":\"regressUser@directual.com\",
\"password\":\"regress\"
}"
```

{% hint style="warning" %}
In the response, the token represents the sessionID of the user, and it is stored in the WebUserSession system data structure. You can use this token for subsequent authenticated requests.
{% endhint %}


# OpenID connect

## Auth with openID

<mark style="color:green;">`POST`</mark> `https://api.directual.com/good/v4/auth`

#### Request Body

| Name          | Type   | Description                                                                              |
| ------------- | ------ | ---------------------------------------------------------------------------------------- |
| redirect\_uri | string | URL of the redirect page                                                                 |
| clientID      | string | openID client ID                                                                         |
| appID         | string | id of your app (go to API → API keys, create key (if there is none) and copy APP\_ID) ID |
| provider      | string | openid                                                                                   |
| code          | string | auth code after successful authentication                                                |

{% tabs %}
{% tab title="200 " %}

```javascript
{
  "result":{
    "token": "8ba4aee8-f509-41d0-9c59-232e828a41a6",
    "username": "test",
    "role": ""
  },
  "status": "ok"
}
```

{% endtab %}

{% tab title="404 " %}

```javascript
{
  "msg":"user or password incorrect",
  "status":"error"
}
```

{% endtab %}
{% endtabs %}

```
Copy
curl -X PUT -H "Content-Type: application/json" \ https://api.directual.com/good/v4/auth \
-d '{
  "appID":"de87a6f7-a1e5-4b31-9d13-37c842b259a",
  "provider": "openid"
  "code": "37c842b259a...",
  "clientID": "de87a6f7....",
  "redirect_uri": "http://mywebsite.app/login"
}'
```


# Facebook oAuth

### Install Facebook Auth plugin

&#x20;[How to install a plugin](/plugins/using-plugins/user-authentication-plugins-not-web3/facebook-oauth-plugin)

### Example for React app

Please, install the node dependency

```
npm install react-facebook-login --save
```

```
import FacebookLogin from 'react-facebook-login';

<FacebookLogin
          appId={you_app_id_in_fb}
          autoLoad={true}
          fields="name,email"
          onClick={(d)=>{
            /** **/
          }}
          callback={(d)=>{
           let url = `http://api.directual.com/good/api/v4/auth/`
              let body = { provider: "fb", token: d.accessToken, clientID: you_app_id_in_fb  }
              fetch(url, {
                method: 'POST',
                body: JSON.stringify(req.body),
                headers: {
                  'Content-Type': 'application/json'
                }
              }).then(res2=>{
                res2.json().then(result=>{
                  res.end(JSON.stringify(result))
                }).catch((data)=>{
                  res.end('error')
                })
            
              })
          }} />
```


# Google oAuth

### How get clientID and secret key

[How to install a plugin](/plugins/using-plugins/user-authentication-plugins-not-web3/google-oauth-plugin)

### Auth by native app

## Auth with Google oAuth

<mark style="color:green;">`POST`</mark> `https://api.directual.com/good/v4/auth`

#### Request Body

| Name          | Type   | Description                                                                              |
| ------------- | ------ | ---------------------------------------------------------------------------------------- |
| appID         | string | id of your app (go to API → API keys, create key (if there is none) and copy APP\_ID) ID |
| provider      | string | google                                                                                   |
| code          | string | auth code after successful authentication                                                |
| clientID      | string | clientID google oAuth                                                                    |
| redirect\_uri | string | redirect\_uri                                                                            |

{% tabs %}
{% tab title="200 " %}

```javascript
{
  "result":{
    "token": "8ba4aee8-f509-41d0-9c59-232e828a41a6",
    "username": "test",
    "role": ""
  },
  "status": "ok"
}
```

{% endtab %}

{% tab title="404 " %}

```javascript
{
  "msg":"user or password incorrect",
  "status":"error"
}
```

{% endtab %}
{% endtabs %}

```
curl -X PUT -H "Content-Type: application/json" \ https://api.directual.com/good/v4/auth \
-d '{
  "appID":"de87a6f7-a1e5-4b31-9d13-37c842b259a",
  "provider": "google"
  "code": "37c842b259a...",
  "clientID": "de87a6f7....",
  "redirect_uri": "http://mywebsite.app/login"
}'
```

### Example auth for React js

Install google-login plugin

```
npm install react-google-login --save
```

Insert login button to your LoginPage template, example:

```
import { GoogleLogin } from 'react-google-login';

<GoogleLogin
          clientId={you_client_id}
          buttonText="Login"
          accessType={"offline"}
          responseType={"code"}
          onSuccess={(d)=>{
              let url = `http://api.directual.com/good/api/v4/auth/`
              let body = { provider: "google", token: d.code, clientID: you_client_id  }
              fetch(url, {
                method: 'POST',
                body: JSON.stringify(req.body),
                headers: {
                  'Content-Type': 'application/json'
                }
              }).then(res2=>{
                res2.json().then(result=>{
                  res.end(JSON.stringify(result))
                }).catch((data)=>{
                  res.end('error')
                })
            
              })
          }}
          onFailure={(d)=>{

          }}
          cookiePolicy={'single_host_origin'}
      />
```


# Security Features

API → Security settings

![](/files/-MW4ch8zuZULIiv9DoCW)

## CORS settings

Turn on CORS settings for authentication (if needed).

Enter all your addresses from which your are going to send requests (don't forget to add all the addresses, including `https://mywebsite.app/login`, `https://mywebsite.app/signin`, etc&#x20;

## Password encryption

Select the appropriate encryption method for your passwords. Keep in mind that existing user accounts will lose access, and a password reset will be required.


# Other Integrations


# OpenAI

### Step 1. Sign up&#x20;

Go to <https://chat.openai.com/> and create an account (EU/US telephone number is needed)

### Step 2. Get API key

<https://platform.openai.com/account/api-keys>&#x20;

### Step 3. Install plugin

{% hint style="info" %}
OpenAI plugin is available for all paid plans
{% endhint %}

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

### Completion text

#### Example

**prompt**:\
`Bob: Hello, guys!`\
`Alice: Hello, chaps!`\
`AI: Nice to meet you!`\
`Aliсe: Who are you?!`\
`AI:`

**stopwords**:\
`Bob,Alice,AI`

### Text to image

#### Example

**prompt**:

`Draw me a cat`


# Integration Hubs


# Zapier

## What is Zapier

[Zapier](https://zapier.com/) is a popular integration hub. "The hub" means that you are able to use it as intermediary between Directual and [2000+](https://zapier.com/apps)  apps. Among others there are such popular apps as: Google Sheets, Airtable, Twitter, Facebook, Trello, Mailchimp, and many others.

#### There are two types of Directual–Zapier integration:

* **Directual→Zapier→Your app.** Directual triggers (sends an object) to Zapier, and the object goes to the selected app
* **Your app→Zapier→Directual.** Some app triggers Zapier, sends the piece data, and Zapier in turn, saves this data as Directual object

## Add Directual app in your Zapier account

After creating a Zapier account (if you haven't got one yet) you can add Directual into your integrations catalogue by [accepting the invite](https://zapier.com/developer/public-invite/99220/cb296059f9252fdfabb27d7d5e76eb2e/).

## Sending data from Directual

You can send objects from Directual scenario, using Zapier step. Remember that you'll deal with objects from the target structure, which you've chosen setting up a Start step of your scenario.

![Zapier step for Directual scenario](/files/-MQf6u60vy2RgDUjfIIW)

In the step you add parameters to send and its values. You can compose values of parameters using [Template system](https://www.directual.com/academy/template-system).

![](/files/-MQf71gsKDZLf5elIbvn)

**Important**: if you turn on the option "Add editing-mode integration to Zapier", there will be two options at *Scenario object* step in Zapier: running mode and editing mode. Running mode is an integration with current *published* version of the scenario, and Editing mode as an integration with current *draft*. If you turn off t "Add editing-mode integration to Zapier", there will be only one option—running mode, current published scenario step.

And we can go to Zapier and continue to setting up the integration (remember, it can take a few minutes after publishing your scenario to let Zapier synchronise with Directual).

You go to Zapier, click 'New zap' and choose Directual (to have Directual in the list you shall [accept the invite](https://zapier.com/developer/public-invite/99220/cb296059f9252fdfabb27d7d5e76eb2e/), if you haven't yet):

![](/files/-MQf799vsRHrpmDpqRFL)

There is one Trigger type for Directual—Read Scenario Object. OK, Continue.

![](/files/-MQf7Dvb8kVzUfCGzX7y)

The next step is setting up the access to your Directual account. Choose 'Add new account', and you'll see the following window:

![](/files/-MQf7JIHO3jk_AsTz87R)

These are the unique keys for accessing your app. Go to your Directual account, API tab, then API-keys section and create new API-key. Copy the **appID** (this long string) to Zapier. Then, click **\*\*\*** to see the secret key. You'll be asked to enter your account password, for security reasons (the secret key gives access to you app). If you don't remember the password (probably, even don't know it, having logged in with Google account only), use the [password recovery ](https://my.directual.com/platform/login/restore/)service. Copy the given secret key as **appSecret** to Zapier.

![](/files/-MQf7WdikNUtAp-kklai)

The next step is choosing the scenario for this zap. Remember, if you turn on the option "Add editing-mode integration to Zapier" setting up the Zapier step on Directual, there will be two options at this step: editing and running for the same scenario step.

![](/files/-MQf7aT0buGl29rz71ds)

That is it! Then, you can choose the app to connect with and action. The parameters from Directual scenario step (remember, we've set up the list at the beginning!) are mapped to the properties of the action, which you choose.

For example, here it is the screenshot of adding the new subscriber in MailChimp:

![](/files/-MQf7eeto8bxoB1l0xZp)

## Receiving data from Zapier

First, you have to set up the first step of a zap—choose an app and its trigger (for example, it could be new card on Trello or a new Salesforce lead). Then, you configuring the second step of a zap. There is only one Action Event on Directual—Create/Change Object. Pick it!

![](/files/-MQf7knqR1xoH5ET3CkB)

Here you also have to connect your Directual account—the appID+appSecret process, which we've described above.

Then, we choose the API-endpoint to write the objects into the Directual database. [Learn how to configure API-endpoint](https://www.directual.com/academy/api-builder-basics) from the tutorial. Note that further we'll be able to choose the object properties, which we added as 'Fields for POST requesting'. Note that you may have to click 'Refresh fields' to get the fields from chosen API-endpoint.

![](/files/-MQf7psMEjJedWUDHDyc)

Here we are! Click 'Done editing' and turn on your zap!


# Telegram

The best messenger

## What is Telegram

[Telegram](https://telegram.org/) is a messenger that allows for the creation of advanced chatbots. Let's explore how you can create a Telegram bot using Directual.

## &#x20;Directual–Telegram basics

### Step 1. Create a bot

Open the messenger and go to the [BotFather](https://t.me/BotFather) and type `/newbot`

![Secret token is red. Here on the screenshot it is masked partially :)](/files/-MQfKxScX0tY2HQAQu-z)

The BotFather will give you an HPPT API **secret token** (the red one).

### Step 2. Connect your bot to the app

Go to Directual app, **Plugins** section → **Telegram** , insert the **secret token** you copied and click **Install**.

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

### Step 3. Find Telegram structures in Directual

Go to Database section, `Integrations/Telegram` folder (it appears automatically). Here you will find five data structures:

![](/files/-MQfP8SBK2E_ZXT8Ze4M)

* **Incoming Telegram messages** (system name `TMessageIn`). Stores all messages that users have sent to the bot.
* **Outcoming Telegram messages** (system name `TMessageOut`). Stores all messages that the bot have sent to users.
* **Users Telegram** (system name `TUser`). Stores Users who have sent messages to the bot.
* **Chats Telegram** (system name `TChat`). Stores Chat-objects. That is the object which is used by scenario Telegram step as a 'destination'.
* **Keyboards Telegram (Legacy)** (system name `TKeyboard`).

Also, a [webhook](/api-integrations/webhooks) for receiving Telegram messages has been added, and a new [System scenario](/scenarios/system-scenarios) `Parse incoming telegram messages` has appeared (do not edit them!).

### Step 4. Test the integration and investigate `TMessageIn` structure

Type something to the bot! Send a picture, a file, or location details. Then check the `TMessageIn` structure.

![Different messages are being sent to the bot](/files/-MQfShYO0FcjHKZYIgUv)

Let's have a look at new objects which appeared in `TMessageIn`!

![](/files/-MQfUPWN16IxlJJ_0b4t)

The clue field for text messages is `text` — it contains the message. Also there is information referring to images, files and locations.&#x20;

{% hint style="info" %}
Information about files and images are stored as `Telegram file IDs`, further in this manual we'll figure out how to download and process them.&#x20;
{% endhint %}

### Step 5. Creating a simple scenario

Create a new scenario, which triggers new objects in `TMessageIn` structure. (See [scenarios documentation](/scenarios/principles-of-scenarios)):

![](/files/-MQfXVDsKUkWHC3H2aUv)

Put the [Telegram step](/scenarios/editing-scenarios/integration-steps/telegram-step) in scenario and configure it as follows (don't forget to publish and run the scenario):

![](/files/-MQfYJ3is7HuQGMnvHmg)

The bot will behave as follows:

![](/files/-MQf_ZOszMDh6rEbP2tZ)

Check out some [useful techniques for building Telegram bots with Directual](/api-integrations/other-integrations/telegram/telegram-advanced-techniques)


# Telegram: Advanced Techniques

### Using the templating system

Feel free to use all the features of the [Templating system](/template-system/basics-of-template-system) in the [Telegram step](/scenarios/editing-scenarios/integration-steps/telegram-step).

### Processing system commands

Telegram bots have `/commands`, so add them in the BotFather (using `/setcommands`) and create **one** scenario which triggers new objects from `TMessageIn`, and if there is no system commands in `text` field, the scenario sends an object to the **Router** **scenario**.

![](/files/-MQfdvp-ASQJAEuSwUeH)

{% hint style="info" %}
Important! The best bot architecture includes **only one** scenario which triggers new `TMessageIn` objects.
{% endhint %}

### Context-based architecture

**The Router** scenario (which is not triggered by events but invoked) is routing messages, based on the value of the field `context` in `TChat` object.

![](/files/-MQfgEylh62bus4hpR9K)

Condition step (checking `context`) looks like:

![](/files/-MQfggwZed1T6j8IdoUU)

{% hint style="info" %}
**Multi-context architecture**

You can add as many fields in `TChat` structure (e.g. `subcontext`) and compose conditions on them. That is a way to create multi-context architecture.
{% endhint %}

### Dealing with files, images and userpics

#### Step 1. Getting temporary file path

Call Telegram API-method `getFile` using Telegram step this way:

![](/files/-MQfihLjK1Skat4-Loyh)

Then, save to a field temporary URL: `https://api.telegram.org/file/bot_BOT_TOKEN_/{{API_response.file_path}}`, where `_BOT_TOKEN_` is token of your bot.

#### Step 2. Saving file to Directual

Use internal SDK method [$D.fs.download](/javascript-sdk/internal-usdd-methods#save-file-to-the-internal-file-storage-usdd-fs-download) (it can be applied in both [SDK step](/scenarios/editing-scenarios/action-steps/js-sdk-step) and in [Edit object step](/scenarios/editing-scenarios/action-steps/edit-object-step))


# Email

There are following options to integrate with email:

* [SMPT](/api-integrations/other-integrations/email/smtp)
* [Gmail](/api-integrations/other-integrations/email/gmail)
* [Mandrill](broken://pages/-MQf67VSDBOOoYSVGolN)


# SMTP

Go to **Integrations** section → **Email**. Choose SMTP option and enter your credentials:

![](/files/-M_Wj9ZvXSY8SJiEvqcm)

Click **Check the gate** at the end. If your credentials aren't correct, the check will not succeed.

### Data structure

There is a system data structure with all the information about emails sent: `Root/Integrations/Email/Sent emails(MailLog)`


# Gmail

You can connect your Gmail account to your app and send emails through it.

### Installing Gmail plugin

Select an app you wish to add your Gmail account to. Go to the **Plugins** section. Choose the Gmail plugin and type in your email address and password.

![](/files/7YBv5CggljSf68xHoTBe)

{% hint style="warning" %}
**NOTE!**

You have to generate **an app password** within your **Google account!**
{% endhint %}

To do this, just follow these simple steps:

1. Login into your Google account
2. Click on Manage your Google Account&#x20;
3. Go to the Security tab - Signing in to Google
4. Turn ON 2-step verification
5. Click **App passwords**
6. Generate a new password

![](/files/3CY0vgHuGpRVEiVuNkCI)

![](/files/KKLskXvPhoDK2QcXrazq)

7\. Use the **GENERATED PASSWORD** and your email address when installing the Plugin.

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

### Sending email from scenarios

Since you installed Gmail plugin, you can use Email step in your scenarios:

![](/files/w6LNMkOFwf82D37xLoMO)

### Data Structure

You can find a system data structure with all the information about emails sent: `Root/Integrations/Email/Sent emails(MailLog)`


# Twilio SMS

## What is Twilio?

[Twilio ](https://www.twilio.com/en-us)is a communication platform that enables you to send text messages (SMS) from Directual. To get started, you'll need to create a Twilio account, purchase a phone number, and add funds to your account.

Once your Twilio account is set up, go to your Twilio Console Dashboard and find: ACCOUNT SID, AUTH TOKEN and the Friendly name of the number which you bought.

## Sending SMS from Directual through Twilio

In Directual, go to the **Integrations tab** → **Other systems** and click **+New integration. Choose** Integration type — **Twilio**. Next, fill in the form in the popup window (insert the parameters from your Twilio Console Dashboard):

![](/files/-MQkI48TcIsl79mVcPPR)

Finally, you can send SMS from Directual scenarios, using SMS step. Remember, that you can use the [Templating system](https://www.directual.com/academy/template-system) here.

![](/files/-MQkHkudyHE-Pzq4f9S5)

## Data structure

There is a system data structure with all the information about SMS sent: `Root/Integrations/SMS/SMS sent (SmsLog)`


# Airtable

Excel on steroids

## What is Airtable?

> [Airtable](https://airtable.com/) is a is a spreadsheet-database hybrid, with the features of a database but applied to a spreadsheet. The fields in an Airtable table are similar to cells in a spreadsheet, but have types such as 'checkbox', 'phone number', and 'drop-down list', and can reference file attachments like images (source: [Wikipedia](https://en.wikipedia.org/wiki/Airtable))

![Airtable example](/files/-MYaCYOqJU-iA-MKqtY1)

There are two options for connecting Directual with Airtable:

* **Option 1**. [Connect using HTTP-request step and Airtable Automations](/api-integrations/other-integrations/airtable#option-1-connect-with-http-request-step-and-airtable-automations);
* **Option 2**. [Connect using Zapier](/api-integrations/other-integrations/airtable#option-2-connect-with-zapier).

## Connect with Zapier

Directual–Airtable integration is powered by [Zapier.](https://www.directual.com/integrations/zapier)

### Directua&#x6C;**→Airtable**

1. [Accept invite](https://zapier.com/developer/public-invite/99220/cb296059f9252fdfabb27d7d5e76eb2e/) for adding Directual integration in your Zapier account;
2. Create scenario with Zapier step (find the details in [Directual-Zapier documentation](https://www.directual.com/integrations/zapier));
3. Create Zap on Zapier, triggered by Directual;

### **Airtable→Directual**

1. [Accept invite](https://zapier.com/developer/public-invite/99220/cb296059f9252fdfabb27d7d5e76eb2e/) for adding Directual integration in your Zapier account;
2. Create[ API-endpoint](https://readme.directual.com/api-integrations/api-endpoints-security-layer) for receiving data from Airtable;
3. Create Zap on Zapier triggered by Airtable;


# Coupler.io

An easy way to gather your data in Google sheets

## What is Coupler.io?

[Coupler.io](https://www.coupler.io/) is an add-on for Google Sheets that allows to pull data from various apps to Google Sheets on a set schedule with no coding required.

## How to pull data from Directual to Google Sheets using Coupler.io?

### Step 1. Configure API-endpoint in Directual

Begin by setting up an API endpoint on Directual. Any data exchange with third-party applications in Directual is organized through [API endpoints](/api-integrations/api-endpoints-security-layer). In the API builder, create a new endpoint where you can select the fields that will be accessible for reading. Additionally, you can set up filtering and sorting options.

### Step 2. Install Coupler add-on

Visit the [G Suite Marketplace ](https://workspace.google.com/marketplace)and install the [Coupler.io ](https://workspace.google.com/marketplace/app/couplerio/532272210531)add-on for your Google account. After installation, open Google Spreadsheet, click **Add-ons** – **Coupler.io** – **Open dashboard.**

### Step 3. Connect your API-endpoint

**‍**In the Coupler.io dashboard, select ***JSON client*** as your data source:

![](/files/-MQkDqC01Lmrg2P3gYbP)

Then, paste your API-endpoint URL to the **JSON URL**. Click 'Show advanced'. You'll find a field 'URL query string' there. Type to that field **pageSize: N**, where **N** is a number of objects which Spreadsheet will receive (by default pageSize = 30).

![](/files/-MQkEAgFCl3J9ShhDIUd)

### Step 4. Save and run

Coupler.io will automatically grab data from Directual API-endpoint (with a certain schedule which you can setup).

![](/files/-MQkEHjybSHjAVhx0Bf-)

If your API-endpoint contains **links** or **arrayLinks**, Coupler.io will map the objects to flat table.


# Other No-Code Tools


# Bubble.io

Integrating with bubble.io can provide you with the capability to harness the strengths of both platforms: building a robust web/UI app with Bubble.io and implementing complex logic with Directual.


# Authorization

## 1. Prepare Bubble.com

Create a login page on Bubble.com with 2 fields: one for the login and another for the password, along with the Login button.

it may look something like this:

![](/files/-Mah_wSWzK2xbRX40nLn)

Next, click the Plugin button and add ***API connector*** to you project. You plugin page will look like this:

![Plugin page](/files/-MahaMbtFcx5VmeotVJL)

Click on ***API Connector,*** then click ***Add another API***.

1. Set API name to ***Directual auth***&#x20;
2. Name it ***API auth v5***
3. Set use as ***Action***
4. Set POST address: <https://api.directual.com/good/api/v5/auth?appID=XXX>, when XXX you API token, which you could find in you project' API section under r API KEYS
5. Add 3 parameters: **provider** with the value "rest", **username** with the value "test", and remove the flag "private"

Now you have an API integration with Directual.com.

![](/files/-MahcDZRmFhRX6eRLceX)

## Prepare Directual

For testing the integration, open you project and navigate to **Database** to find the **App User** structure

![](/files/-Mahdjps0M3h79JMPPNc)

Press **New object** to create the first user

![](/files/-MahdqO_Z_ZGrazNsZPH)

And fill in the ID with value "test" and **password** with value "test".

After filling the "password" field, please, click to convert to hash, because, since your application doesn't store user passwords; it only stores hash.

![](/files/-MahddM8frQURI0TubxY)

The form with data should look like this:

![](/files/-MahdNCb-WrR0CuZ-MkB)

Saving you first user

## Creating flow on Bubble for user authorization

Go to the integration on Bubble.com and expand your "Directual auth" integration.

Please, click to "initialization call" to get a result on Directual.com. You will see the following popup:

&#x20;&#x20;

![](/files/-MaheXUh6p9jQwb_C53j)

Click to save, and then remove the default value "test" the from parameters.

![Parameters on integration without default values.](/files/-MahemNrr-1hQyJkenvd)

Design section

In the Design section, add an alert component. Click the "Login" button, and then click to start/edit the workflow.

![](/files/-MahkJoQs9E72szHqXSk)

### Step 1. Validation

![](/files/-MahsFVedwxtuPhgPQND)

Add "Show message in alert box". Fill the message text with: "Please, fill valid login and password". Then add the condition: "Input login's value is empty"

![](/files/-MahsXCYW_WDXKfS0IVj)

### Step 2. Integration with Directual

Add a new step to the workflow.

![](/files/-Maht4LF09r-q6XDQBKm)

![](/files/-Maht8QOCfAG8LcdSC3R)

#### Step 3. Alert with message&#x20;

Add an alert message like similar to step 1, providing information that user has been logged in **if the token is not empty.**

![](/files/-MahtTDQT9qbsKpkxftY)

### Step 4. Reset login and password after login

Add a step with the category "Element actions" -> "Reset inputs"

### Step 5. Save you token for use as session id with request on Bubble pages

Go to the plugin page, and install "session and local storage"

![](/files/-MahuT6O_uP5VVK7O-zy)

Add "Web Storage" to your page

![](/files/-Mai3Dyq9pI2mPyIW4Da)

Go to the flow editor and add a new step:&#x20;

![](/files/-Mai3SW3FXq_S78XWVAM)

Set key = sessionID

Set value = result of step2' token


# Displaying Data from Directual on Bubble

You can display data from Directual on your Bubble project. Let's have a look at how to do it for both anonymous and authenticated users.

## Prepare Directual (structure and API endpoints)

For example, let's create a structure called "tasks" with the following fields: id, name, and task.

Go to the database and click "New Data Structure". Insert the name and sysname as "tasks" and then click "Save and go".

On the page for this structure, click "Configure to field" and create two fields: "name" and "task".

![](/files/-Maid_8L45YXUtAFBtIk)

Next, go to the **API** section and create a new endpoint, with the ability to read **id, name, task.**&#x20;

{% hint style="info" %}
Note: Set up a new security layer, and remove all conditions in the layer. The default condition, "not empty," means that the API layer requires only authenticated users.
{% endhint %}

![Example API Endpoint for reading tasks.](/files/-Malvv1MB4F7fdNU-hzC)

Click on "Endpoint respond preview" and, after saving, copy the API Endpoint URL for use in Bubble.

![](/files/-MalwFN8BDiLvK2SUGfS)

![Respond preview: copy URL to buffer](/files/-MalwQtoFgosmpxjxE31)

## Prepare Bubble

Go to "Plugins" -> "API Connector" and click "Add another call". Create a new API endpoint proxy like this:

![](/files/-MalwqAJEs5lvKa1-p4q)

## Creating view on Bubble

Add a button called "Load data"

Add "Repeating Group"

&#x20;

![](/files/-Malxb4kQwW6NHgxiaWt)

![](/files/-MalxpBjJRzF3B4f-TWU)

Move "Visual Element"->Text to "Repeating group"

Setting Repeating group

![](/files/-Maly_sPu8s2u399g_Cd)

![](/files/-MalyhAtV1Ygy-aPCtCj)

![](/files/-Malyjl2_L1XxgweUHlB)

![](/files/-MalypyCnJwuGZaRr9AW)

![](/files/-MalysbFvi9pl5Fsu_q1)


# Adalo

### Step 1. Enable developer mode in Adalo

![](/files/ToUU3Xdd0wwTN8uPQCxn)

### Step 2. Create an app with External Users Base

![](/files/Qg0P6P0RUDJxLVib5fFS)

### Step 3. Set up Login

Use Directual [Authentication API](/api-integrations/authentication-api/login-password#authentication-using-login-password-pair). Get appID from section **API → API Keys**

![](/files/xr2zjz48rkurYK4Avsgv)

![](/files/AoxOOtkEqTXamWzAm0p3)

### Step 4. Add a structure and an API endpoint for Signing Up

New structure:

![](/files/QzmbqSAOaHZVokQc7gxM)

API-endpoint:

![](/files/075rZqnN3MM63MYZzqph)

Scenario for Signing Up:

![](/files/YgacE8WB6jGoSz8WD4MV)

In that scenario we need to check that there is no user with the given email, create it and return the response [synchronously](/scenarios/synchronic-scenarios-1).

### Step 5. Proceed with the Adalo wizard

![](/files/oToc1l1STifiVAuOL3n2)


# UI bakery


# Tilda


# AppGyver


# Web-App Builder basics

Create marvelous, mobile-friendly web apps 🦄

The Directual web page builder allows you to create beautiful, mobile-friendly web apps in just a few minutes.

Take a look at [an app](https://library.directual.app/) that is 100% built on Directual

There are two ways to apply Directual web pages:

* Using the Directual Web portal (as shown the example above)
* Embedding Directual web pages into your application, whether it's developed traditionally or with other no-code tools

## Web Pages Home Screen

![You can view all the pages of your app In the Web Pages section ](/files/-MBcVKrhu7oVZhLeW6SW)

### Portal Settings

![](/files/-MXaIELoQiYTJnCwnIrg)

#### General settings

* **Portal Title.** Your site name to be displayed in the browser
* **Link to Logo Picture**. Insert the link to your logo. You can upload it using the [Directual file storage](/data/file-storage) structure and copy the link from there
* **Site Locale.** Choose the language for the messages in the interface:
  * English
  * Russian
  * Spanish
  * German
  * French
  * Japanese

#### Appearance

* **Menu** appearance. Place at the top or on the left
* **Color scheme**. [Colors of your app](broken://pages/u0owb1qncaofOFG4ZUx7)
* **Border radius**. Affects text inputs, buttons and cards in your app
* **Fonts**. Choose the font-face for headers and texts in your app. You can find an example of applying fonts below
* [**White-label**](broken://pages/-MjYSgdQgXWgiMH4wsZu) option

#### Security

* **Turning on authentication for the site**. This will require your app users to authorize before they get access to the portal (you can also choose to open certain pages for non-authorized users)
* **Turning on sign-up for new users**. This will allow new users to sign up (often unnecessary for different admin portals)

![Sign-up is ON (on the left) and OFF (on the right)](/files/-MXaL8k6KGoIrRwbHdYR)

* **Login format**. `email`/`phone`/`string`. This is just a visual check of the user's login.

{% hint style="info" %}
If you want to provide your clients with a sign-in link, it is '`yourapp.directual.app/signin' and`, the sign-up link is: '`yourapp.directual.app/signin?signup=true'`
{% endhint %}

{% hint style="info" %}
Remember, that the user's login is *always* stored in the App users (`WebUser`) data structure, field `ID`, despite the chosen format
{% endhint %}

{% hint style="info" %}
You can use authentication plugins for [Facebook](/plugins/using-plugins/user-authentication-plugins-not-web3/facebook-oauth-plugin) and [Google](/plugins/using-plugins/user-authentication-plugins-not-web3/google-oauth-plugin) oAuth
{% endhint %}

#### Custom domain

Connect custom domain to your app. See the [custom domain documentation section](/web-pages/web-app-settings/custom-domain) further.

### Page settings

Click on **Edit page settings** icon on the page card.

![](/files/-MXaROVFNURWTYW5Ij0e)

#### General

* **Page title**
* **Page route**. The path of the page
* **Icon**. The icon in the portal menu. Choose icon name [form the list](https://design.directual.app/system-icons)
* **Order**. Position in the app menu
* **Group**. Grouping in the app menu (e.g. it may be `Admin pages` group)
* **Include to the portal**. If you want to use the page as a part of the portal, turn this setting ON. If you only want to embed the page, turn it OFF

#### Access & Security

* **Overriding portal security settings**. If this setting is OFF, the page inherits portal security settings. If it is ON, you can set up security for the page independently, including the limit access for *certain roles* (list the roles, comma separated) — users (objects from `WebUser` data structure) should have the roles in the `role` field (comma separated as well)

![](/files/gYi6srzcmrvge7KaSYtW)

If you want to enable authentication for the whole app but keep some pages public, use the security overriding feature for those pages.

#### 🚫 Delete page

Be careful, the action is irreversible.

## Profile page

`profile` is a system page. You can edit it as you want, but you cannot delete it or change its path. The link to the page is at the bottom of the menu.

![](/files/-MXabtr2hJVrHMAsrLWR)


# Web-App Settings


# General web-app settings


# Main menu


# Creating a logotype


# Web-App color scheme


# Web-app typography


# White Labeling


# Web-App Icon


# Custom Domain

Give your app a unique web address 🕸

Besides deploying your project to `yourapp.directual.app,` you can also publish your project to any custom domain you own once you have [a suitable plan](/pricing-and-billing/pricing-plans).

## Step 1. Register your domain

If you own your domain already you can add it, go to [Step 2](/web-pages/web-app-settings/custom-domain#step-2-add-a-domain-in-directual). You also can search and purchase a new domain, for example, through [GoDaddy](https://godaddy.com/) or [Google Domains](https://domains.google.com/) (or any other service).

## Step 2. Add a domain in Directual

Go to **Web-pages** section, then to Web-portal settings → Custom Domain.

![](/files/-MSXv4qq0LwGHIxL80xR)

### Adding a First-level Domain

For example, `mysite.com` or `superapp.io`

![](/files/-MSXu0uQCesCSX-3LDSY)

If you add a first-level domain, you'll see A-records to copy.

### Adding a Subdomain

For example, `app.mysite.com` or `www.superapp.io`&#x20;

![](/files/-MSXueaodAL-cVDf3Fgt)

## Step 3. Updating DNS-records

Access your domain's DNS management in your domain registrar's or DNS host's dashboard.

### In case of a first-level domain

* Add an A record
* Set the **host** to @
* Add the first `ip`, that you've copied in Directual
* Save the record
* Add one more A record
* Set the host to @
* Add the second `ip`, that you've copied in Directual
* Save the record

### In case of a subdomain

* Add a CNAME record
* Set the **host** to your subdomain (for example if you add a subdomain `hello.myapp.com`, host will be `hello`)
* Set the **link** to `proxy.directual.app`
* Save the record

{% hint style="info" %}
Don't forget to edit records for `www` subdomain, if you want to use it. Usually there are some default settings for `www`.
{% endhint %}

## Step 4. Check DNS status in Directual

{% hint style="info" %}
Domains can take anywhere from 24–48 hours to populate and sometimes up to 72 hours depending on your provider. Some providers like GoDaddy and Google Domains can populate their DNS as quickly as 15 min to 1 hour.
{% endhint %}

![](/files/-MSXx8rv35xi0RGRGjN_)

## Bonus. Free SSL

Directual automatically provides SSL (**"secure socket layer"**) to encrypt your app. SSL is the standard method for establishing an encrypted link between a web server and a browser. It ensures that all data passed between the web server and browsers remain private and integral, so you and your website's visitors can rest assured that your information is safe.

{% hint style="info" %}
Issuing an SSL certificate usually takes 10–20 minutes.
{% endhint %}


# Custom code


# Setting Up Page Layout

### Page Header

![](/files/-MXbTfe3cGN1DGev8B_v)

You can enable the page header if you want it to be visible. By default, the header is the page title, but you can edit it as needed.

### Tabs, Sections and Columns

Everything on the page can be dragged and dropped!&#x20;

![](/files/-MXbW633iOS1c9NEZK9N)

### Copying Sections with Components

To copy a section with components, select the section you want to duplicate and click the Copy Section button

![](/files/-MjpGGO_AtPmZ4viElg2)

### Adding and Configuring Components

Drop components into columns and configure them according to your preferences.

![](/files/-MXbX6Qdt85xFLwbZ1dC)

Click Preview to see the resulting page.

### Desktop, Tablet and Mobile Views

Each section can contain one or several columns, which serve as containers for components. If a section has more than two columns, you can configure how they are displayed in three different views:

* **Mobile:** width from 0 to 478 px
* **Tablet:** width from 479 px to 768 px
* **Desktop:** width from 768 px and above

Choose the layout direction in the section settings.

![](/files/-MXbZIwOKM6g8Vc6o60L)

{% hint style="info" %}
Each component can have its own layout for mobile, tablet, and desktop views. An icon in the bottom-right corner indicates which option is applied for the specific column width.
{% endhint %}

### Conditional Tabs Visibility

Each tab can be set to be visible for specific groups of users:

* All authorized users
* List of roles only (similar to [security settings for the page](/web-pages/portal#page-settings)).

![](/files/-MXbaCOz4RqgUIEMda5C)

### Conditional Sections Visibility

Similarly, each section can be configured to be visible for specific groups of users.

{% hint style="warning" %}
Remember to consider security settings when configuring API endpoints! Configuring conditional visibility alone may not be sufficient.
{% endhint %}


# Subpages and URL Parameters

### Page URL Parameters

You can add URL parameters to the page:

![URL parameters configuration](/files/pwZnNO3hN3zHRs1PR4GL)

If you visit the page with the following address (for example):

![](/files/RS9KCiMZIcQ2UpDGAomG)

You will have values for parameters `category == main`, `id == 0005`. You can use these values by applying `{{HttpRequest.category}}` and `{{HttpRequest.id}}` in:

* [Components](/template-system/basics-of-template-system/templating-techniques-for-web-pages#url-parameters) on the page
* [API-endpoint](/api-integrations/api-endpoints-security-layer) filters

Additionally, you can set up a custom layout for each of these subpages.

### Setting Up Custom Layout for Subpages

![](/files/Vk5a99sw0Xv5wkZG4JWD)

You can configure a custom layout for each subpage or use the default one. To navigate between pages and subpages, simply use the [Link Button component](/web-pages/components/link-button).

[Cards](/web-pages/components/legacy-components/cards#purposes-of-the-component) and [Table](/web-pages/components/table#purposes-of-the-component) components have an option to open objects in subpages:

![](/files/oZVzb2o2DMYUSQSbBWJY)


# Components

Components are the fundamental building blocks of your app's interface 🧱


# Multistep Form

🪜 Build complex dynamic personalised forms in no time!

Multistep form is a new generation of the [Form](/web-pages/components/legacy-components/form) component (for now we keep them both available). It allows users to create complex multistep dynamic forms which are personalised for app users.

### Learning the Basic Component Concepts

* **Form step** (section). A block that includes **Form elements**.
* **Form element**. Could be one of the following:
  * Text paragraph;
  * Inputs (up to 12);
  * Action buttons (up to 12);
  * Submit button;
  * Hint;
  * Sub-header;
  * Redirect.
* **Object model**. JSON-object that includes the current fields' values (default values and filled by the user).
* **Form state**. JSON-object that includes one ore several parameters of the current form state. There are two default properties: `step` defines which form step is displayed now) and `popup` shows the popup and the relevant step in it.

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

### Building a Simple Form

Let's start from creating a simple form where all users (both authorised and unauthorised) would be able to add new book, filling it's title. First of all, we are creating a data structure and then follow the steps.

#### Step 1. Add a Multistep form to your page

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

#### Step 2. Configure endpoint&#x20;

We need to have the endpoint for creating/editing  objects. For the beginning we may make it public.

<figure><img src="/files/LRxlUXRllvoXONmIKpkw" alt=""><figcaption><p>Creating new public endpoint</p></figcaption></figure>

#### Step 3.  Add step elements

By default, we have 2 form steps: `default step` and `submitted`. Let's configure them!

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

Add new element to `default step`:

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

Choose **Inputs** element. Here we can configure inputs for filling fields available for reading (as well as state properties):

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

The second element will be "Submit". It'll send the POST request to the endpoint creating new object. Also, submit changes Form state `step` current value → `submitted`

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

#### Step 4.  Set up the first form step

The last thing that we need to configure the default value of the state – define the first step in the Form:

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

#### Step 5.  Test the form!

Push "Save" and – *voilà*:

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

### Applying the Templating engine

You can use templating engine in the form title, description, in step elements, etc. The available fields include:

* **Form State** properties
* Fields from **Object model**
* **WebUser**'s fields like `firstName`, `lastName`, `role`, `userpic`, `id`

<div align="left"><figure><img src="/files/MqF5ex26TyB8wp2FRCbH" alt="" width="375"><figcaption><p>Paragraph element allows to insert HTML and to use the Templating engine</p></figcaption></figure></div>

If the element has a description `<HTML /> is allowed here`, you can apply HTML and CSS.

You can use the templating engine in different elements like paragraph, sub-header, input descriptions, etc.

### Navigating between steps

As we pinpointed, the current step is defined by the form state, `FormState.step`.

For example, if you want to start from `my step`, you need to define it as a default value:

#### Setting default step

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

#### Changing state

There are the following ways to change the state (and, the `step` property in particular):

* Add **Inputs** element and define it as an Input for changing the state. You can configure it as a plain text input, as a dropdown select or a button line. For select and button line you need to add  options. Those options can be set manually or pulled from the object's field (type of json, an array of key-value pairs like `[{"key" : "option1", "value" : "option one"}, {"key" : "option2", "value" : "option two"}]`).

<figure><img src="/files/jYpKqrFNOQHTvUiqSgvX" alt=""><figcaption><p>A dropdown for changing the state property — options are set manually here</p></figcaption></figure>

* Apply [Action button](#using-action-buttons) for changing the state;
* Apply [Sync request processing](#processing-requests-synchronically).

#### Steps (sections) visibility

By default the step (section) is visible if its **name** == **FormState.step**. If you want to make it visible in other cases, use Advanced step settings:

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

The step may be visible always or when the FormState.step value is in the list (comma separated)

### Debugging

You can turn on debug for displaying Form Model (and system messages on developer console about [Conditions](#configuring-conditional-visibility-of-elements)) and Form State:

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

Here is how debug mode looks like:

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

{% hint style="info" %}
Don't forget to turn off the debug when you go public with your app!
{% endhint %}

### Setting up Dynamic Inputs

One may need an input that dynamically provides user with options. It may be a dropdown select, radio buttons, tags. etc. The options may depend on the other fields' values, on the user's role, and on other parameters. Let's figure out how to set up the dynamic input!

{% hint style="warning" %}
First and foremost — the dynamic input is one for `link` or `arrayLink` fields. The options are defined by the request to the additional endpoint.
{% endhint %}

Example of a **dynamic input**. first input is a radio button: "guys or girls". The dropdown below provides us with the options according to the first choice:

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

There are the following options for rendering dynamic input:

* Dropdown select (for `link`)
* Dropdown multi-select (for `arrayLink`)
* Radio-buttons (for `link`)
* Checkboxes (for `arrayLink`)
* Tags (both for `link` and `arrayLink`)
* Images-radio-buttons (for `link`)
* Images-checkboxes  (for `arrayLink`)

Dropdowns support any number of options thanks to **dynamic filtering**.

#### Configuring endpoint for Dynamic inputs

Any dynamic input requires an endpoint. You can configure filters and sorting in this endpoint as you wish. Moreover, don't forget to set up [structure visible name](/data/data-structures#structure-visible-name) and make it available for reading in that endpoint.

<div><figure><img src="/files/0c8xXDW5oD6gsdA8qUPY" alt=""><figcaption></figcaption></figure> <figure><img src="/files/QlitpagAMjyvemb5Iz63" alt=""><figcaption></figcaption></figure></div>

**Important**: if you use dropdown, there have to be system filters for `_value` and `_filer`. if you create an endpoint right from the Multiform component, that filter is added automatically. But pay attention, that if you want filtering (quick search in the dropdown) work with more fields, add them in the filter (`field like _filter`)

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

#### Dynamic filtering

If you want to connect filtering on endpoint with the values that user fills, use custom request parameters and pass them those values.

<div><figure><img src="/files/5yvbK0pE65jFUEZuq3RU" alt=""><figcaption></figcaption></figure> <figure><img src="/files/Qew2Q2UiRlzoAozrFBLv" alt=""><figcaption></figcaption></figure></div>

#### Saving options quantity

If you like, you can save the options quantity to a state field. It works for hidden elements as well.

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

### Configuring Conditional Visibility of Elements

You can set up the conditional visibility for each step element and for each action separately.

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

The conditions can be combined either with `AND` or `OR` operators. You can compare current model fields and state properties with expressions (using the [Templating engine](#applying-the-templating-engine)). There are the following operators for comparison:

* equal, not equal
* contains/dos not contain (for Directual arrays – *comma separated strings*)
* in/not in (for Directual arrays – *comma separated strings*)
* empty/not empty (no second value)
* model is changed/not changed since last submit (no any values at all)

### Using Actions

Action is an element that can do the following:

* Send POST-request to API-endpoint
* Edit object model or state (including resetting the model and discarding changes in it)

Action can be either button or auto-action (performed on a certain step or when a certain field is changed)

One configures an action (tab **Actions**) first and then add it to the form as an element

<div><figure><img src="/files/uEeHdvtKp5z42S9RHvwB" alt=""><figcaption></figcaption></figure> <figure><img src="/files/1UjKcXtQfea7f1fDWf56" alt=""><figcaption></figcaption></figure></div>

There can be a few action buttons within a single form element, in a row.

Each action button has its own visibility conditions, [similar to element ones](#configuring-conditional-visibility-of-elements). One can hide or disable the button conditionally.

Actions support [sync API response processing](#processing-requests-synchronically).

### Submitting the model

To save the whole model to the object you can use **Submit** element

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

By default the submit button resets the model and sets the `FormState.step` to `submitted`.

You can change those settings: keep the model and choose different step.

Also, you can set up conditional visibility. Common case: hide the submit button if the model is not changed.

On the State tab you can turn on saving state to a certain field on submit.

Submit supports [sync API response processing](#processing-requests-synchronically).

### Editing Existing Objects

Turn on switch "Edit the 1-st object in API-response"

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

{% hint style="warning" %}
Editing an object pay attention to the following:

* Object's fields including ID have to be available both for reading and writing
* Form takes the first object from the endpoint. Configure filters or sorting accordingly.

Tip: usually endpoint filters depend on [parameters from URL](/web-pages/setting-up-pages-layout/subpages-and-url-parameters)
{% endhint %}

If you want to proceed editing object after submitting, turn on "Keep the model" on [Submit element](#submitting-the-model).

### Storing State in the Object

If you **edit an object**, you can restore the state from it. There are two options.

#### 1. Getting state properties from object fields

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

#### 2. Getting the whole state from a single field type of JSON

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

### Using Popups

You can place any Step (section) to the popup:

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

Similar to `step` [configuration](#steps-sections-visibility) you can set up `popup` specifying which section should be displayed in the popup (for example, [using Action button](#using-actions)):

<div><figure><img src="/files/4Gg56TWZpkP3QzWO5mJU" alt=""><figcaption></figcaption></figure> <figure><img src="/files/EW1VDVojl1S3WatAi763" alt=""><figcaption></figcaption></figure></div>

If you like you can even configure a chain of popups!

### Processing Requests Synchronically

{% hint style="warning" %}
This feature turns Multistep-form into a super-powerful component!
{% endhint %}

[Submit](#submitting-the-model) and [Action](#using-actions) can send a POST-request. As we know, the [POST-request can be synchronous](/api-integrations/api-endpoints-security-layer/advanced-techniques-for-get-requesting/synchronic-scenarios-for-post-requests). That means the API response may contain the result of the object processing within the scenario.

Use [API-response step](/scenarios/editing-scenarios/integration-steps/api-response) in the scenario and compose a response in the following format:

```json
//if you want to edit model:
{
    "object": {
        "field1" : "value",
        ...
    }
}

//if you want to edit form state:
{
    "state": {
        "step" : "value",
        "popup" : null
        ...
    }
}

//if you want to edit both:
{
    "state": {
        "step" : "value",
        "popup" : null
        ...
    },
    "object": {
        "field1" : "value",
        ...
    }
}

// if you want to redirect user, you can do it as well!
{
    "redirect": {
        "target" : "./{{myField}}", // similar to LINK BUTTON component
        "delay" : 500 // in millisectods. 0 – by default
        ...
    }
}

// if you want all dynamic fields refresh their options
{
    "refresh": true
}
```

### Setting up Progress Bar

You can add a progress bar to the form:

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

First—better to put progress bar in a step that is [visible everywhere](#steps-sections-visibility) (or at leas on the steps that are included into the progress).

Configure the progress bar, adding steps into it, arranging the order, and filling steps' names, descriptions, etc.

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

### Using Redirect Element

**Redirect** element (surprise!) redirects the user. It works like [Link button](/web-pages/components/link-button), but automatically.

Bear in mind that you can redirect to the page or a subpage using object fields or form state properties in target URL.

The common case: the form creates the object, [scenario synchronically](#processing-requests-synchronically) returns its ID, and the redirect element leads the user to the page where he proceeds editing the object.

### Online Refreshing with Socket.io

If you [edit an object](#editing-existing-objects) using the Multiform, you can update it using [plugin Socket.io](/plugins/using-plugins/websockets-socket.io).

#### Step 1. Configure the form

The updated fields have to be **available for reading**

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

The form settings includes "Edit object" option

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

Here is how the form look like (be sure that your endpoint provides you with the required object. If not — use filters and sorting)

<figure><img src="/files/bJ0iIcs7FtAS3lHpLRHe" alt=""><figcaption><p>Object on the platform (left) and the form (right)</p></figcaption></figure>

#### Step 2. Install Socket plugin and configure it

Install the plugin from the Marketplace

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

Create a scenario, that triggers when object is Changed. It calls the Socket plugin for all users (`*` in the first field), for a certain event (in our example event name = `refresh`)

<div><figure><img src="/files/mlYUKIeave60udPUARmv" alt=""><figcaption><p>Start step</p></figcaption></figure> <figure><img src="/files/86KM2kdl3HfX2bYppY1Y" alt=""><figcaption><p>The whole scenario</p></figcaption></figure> <figure><img src="/files/7Rkblz7EYxBK1MpcSoFm" alt=""><figcaption><p>Socket (push) step</p></figcaption></figure></div>

{% hint style="info" %}
Don't forget to publish and run the scenario!
{% endhint %}

Next — add a refresher component to the page where the form is located (use the same event name. In our case — `refresh`)

<figure><img src="/files/uV3Bhky7ptDZ0UBxo1VO" alt=""><figcaption><p>Web-page with the form and socket refresher</p></figcaption></figure>

That it is! Result:

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

### Using data-actions

You can now make any HTML element inside a Multistep Form interactive using data-action-type attributes. This allows buttons, links, or spans to trigger actions, navigate, or open modals — directly from HTML content (e.g. inside **Paragraph** element).

#### Supported types

| Type   | Description                                | Example                                                                             |
| ------ | ------------------------------------------ | ----------------------------------------------------------------------------------- |
| action | Run an action defined in the form settings | `<button data-action-type="action" data-action-data="like_action">👍 Like</button>` |
| route  | Navigate to a page (supports templating)   | `<a data-action-type="route" data-action-data="/profile/{{id}}">View Profile</a>`   |
| modal  | Open a modal window                        | `<span data-action-type="modal" data-action-data="/edit/{{id}}">✏️ Edit</span>`     |

#### Key points

* Templating support – use {{field}} in data-action-data.
* Action integration – call existing actions by name or ID.
* Event delegation – works seamlessly with other click handlers.
* Compatible with ElementText and any HTML-enabled content.

#### Example

```html
<!-- Execute an action -->
<button data-action-type="action" 
        data-action-data="like_action">
    👍 Like
</button>

<!-- Navigate to a page -->
<a data-action-type="route" 
   data-action-data="/profile/{{id}}">
    View Profile
</a>

<!-- Open modal -->
<span data-action-type="modal" 
      data-action-data="/edit/{{id}}">
    ✏️ Edit
</span>
```

This feature lets you build rich, interactive forms without extra JavaScript — just simple HTML attributes.

### Using Custom API

Multistep Form provides a public API for programmatic form control from external JavaScript code. This allows you to integrate the form with custom logic and dynamically modify form data and state.

[Explore custom API](/web-pages/components/multistep-form/multiform-custom-api)


# MultiForm Custom API

Control your form like a boss — programmatic API to read/write data, manage steps, submit forms, and trigger actions from your JavaScript.

[Multistep Form](/web-pages/components/multistep-form) provides a public API for programmatic form control from external JavaScript code. This allows you to integrate the form with custom logic and dynamically modify form data and state.

### API Access

The API is available through the global object `window.FpsForm2_API[formId]`, where `formId` is the short component ID from form settings (`data.params.comp_ID`).

```javascript
// Get form API by ID
const formAPI = window.FpsForm2_API['myFormId'];
```

### Mounting and Timing Issues

**Important:** The form API is registered during component mount. If your script runs before the form is mounted, the API will be `undefined`.

#### Recommended Solution: `waitForFormAPI` Helper

Use this helper function to wait for the API to become available:

```javascript
function waitForFormAPI(formId, callback, timeout = 5000) {
  const start = Date.now();
  const check = () => {
    if (window.FpsForm2_API && window.FpsForm2_API[formId]) {
      callback(window.FpsForm2_API[formId]);
    } else if (Date.now() - start < timeout) {
      setTimeout(check, 100);
    } else {
      console.error('Form API not available after ' + timeout + 'ms. Check form ID.');
    }
  };
  check();
}

// Usage
waitForFormAPI('myFormId', (formAPI) => {
  console.log('✅ API ready!');
  // Your code here
  formAPI.editModel('firstName', 'John');
});
```

**Alternative:** Use `setTimeout` with 500-1000ms delay if you know the form will mount quickly.

***

### API Methods

#### Getting Data

**`getModel()`**

Returns the current form model (flat object with fields).

```javascript
const model = formAPI.getModel();
console.log(model.firstName); // get field value
```

**`getExtendedModel()`**

Returns the extended form model (with nested objects for links/arrayLinks).

```javascript
const extModel = formAPI.getExtendedModel();
console.log(extModel.company); // may be an object with structure
```

**`getState()`**

Returns the current form state (step, popup, custom fields).

```javascript
const state = formAPI.getState();
console.log(state.step); // current step
console.log(state.popup); // opened popup
```

**`getOriginalModel()`**

Returns the original model (before user changes).

```javascript
const original = formAPI.getOriginalModel();
```

***

#### Modifying Model

**`editModel(field, value)`**

Changes a single field in the model. Supports nested paths with dot notation.

```javascript
// Simple field
formAPI.editModel('firstName', 'John');

// Nested field
formAPI.editModel('address.city', 'Moscow');
```

**Important:** Automatically updates both `model` and `extendedModel`.

**`setModel(newModelData)`**

Merges the provided object with the current model (merge, not replace).

```javascript
formAPI.setModel({
  firstName: 'John',
  lastName: 'Doe',
  age: 30
});
```

**`replaceModel(newModel)`**

Completely replaces the model (replace, not merge).

```javascript
formAPI.replaceModel({
  firstName: 'Jane',
  lastName: 'Smith'
});
```

***

#### Modifying State

**`editState(field, value)`**

Changes a single field in state. Supports nested paths.

```javascript
// Change step
formAPI.editState('step', 'step2');

// Open popup
formAPI.editState('popup', 'confirmPopup');

// Close popup
formAPI.editState('popup', '');

// Custom field
formAPI.editState('myCustomFlag', true);
```

**`setState(newStateData)`**

Merges the provided object with the current state.

```javascript
formAPI.setState({
  step: 'step3',
  customData: { foo: 'bar' }
});
```

**`replaceState(newState)`**

Completely replaces the state (use with caution, may break form logic).

```javascript
formAPI.replaceState({
  step: 'newStep',
  popup: ''
});
```

***

#### Programmatic Submit

**`submit(options)`**

Programmatically triggers form submission.

```javascript
// Simple submit
formAPI.submit();

// With options
formAPI.submit({
  submitKeepModel: true,  // don't reset model after submit
  targetStep: 'step3',    // go to step3 after success
  resetModel: false,      // don't reset model
  finish: (result) => {   // callback after completion
    console.log('Submit completed', result);
  }
});
```

**Options:**

* `finish` — callback after completion
* `submitKeepModel` — keep model after submit (default: `true`)
* `targetStep` — step to navigate to after success
* `autoSubmit` — autosubmit flag (default: `false`)
* `submitMapping` — field mapping for submit
* `newData` — additional data `{ model: {...}, state: {...} }`
* `resetModel` — reset model after submit

***

#### Calling Actions

**`callAction(actionIdOrName, callback)`**

Programmatically triggers a form action by ID or name.

```javascript
// Call action by ID
formAPI.callAction('action_id_123', (success) => {
  if (success) {
    console.log('Action completed!');
  }
});

// Call action by name
formAPI.callAction('Submit Order', (success) => {
  console.log('Done:', success);
});
```

**Supported action types:**

* `state` — updates state/model via stateMapping
* `endpoint` — calls API endpoint with mapping

**Limitations:**

* Does not support `link`, `modal`, or complex action types
* For those, use direct API methods (`editState`, `submit`, etc.)

**Note:** For simpler cases, consider using the `data-action` mechanism in HTML elements. It's easier to implement and doesn't require JavaScript. See the `data-action` documentation for details.

***

#### Utilities

**`refreshOptions()`**

Refreshes dynamic options in form fields (for select/autocomplete with endpoints).

```javascript
formAPI.refreshOptions();
```

***

### Usage Examples

#### 1. Validation and Data Modification

```javascript
waitForFormAPI('registrationForm', (formAPI) => {
  // Get current data
  const model = formAPI.getModel();
  
  // Validation
  if (!model.email.includes('@')) {
    alert('Invalid email');
    formAPI.editModel('email', '');
  }
  
  // Auto-fill
  if (model.country === 'Russia') {
    formAPI.editModel('currency', 'RUB');
  }
});
```

#### 2. Step Management

```javascript
waitForFormAPI('wizardForm', (formAPI) => {
  
  // Next step
  function nextStep() {
    const state = formAPI.getState();
    const steps = ['step1', 'step2', 'step3', 'final'];
    const currentIndex = steps.indexOf(state.step);
    
    if (currentIndex < steps.length - 1) {
      formAPI.editState('step', steps[currentIndex + 1]);
    }
  }
  
  // Previous step
  function prevStep() {
    const state = formAPI.getState();
    const steps = ['step1', 'step2', 'step3', 'final'];
    const currentIndex = steps.indexOf(state.step);
    
    if (currentIndex > 0) {
      formAPI.editState('step', steps[currentIndex - 1]);
    }
  }
  
  // Attach to buttons
  document.getElementById('nextBtn').addEventListener('click', nextStep);
  document.getElementById('prevBtn').addEventListener('click', prevStep);
});
```

#### 3. Syncing with External Data

```javascript
waitForFormAPI('profileForm', (formAPI) => {
  
  // Load data from API
  async function loadUserData(userId) {
    const response = await fetch(`/api/users/${userId}`);
    const userData = await response.json();
    
    // Update form
    formAPI.setModel({
      firstName: userData.first_name,
      lastName: userData.last_name,
      email: userData.email
    });
  }
  
  loadUserData('123');
  
  // Subscribe to changes (polling)
  setInterval(() => {
    const model = formAPI.getModel();
    console.log('Current data:', model);
  }, 5000);
});
```

#### 4. Custom Triggers and Calculations

```javascript
waitForFormAPI('orderForm', (formAPI) => {
  
  // Recalculate total when quantity changes
  function recalculateTotal() {
    const model = formAPI.getModel();
    const quantity = parseInt(model.quantity) || 0;
    const price = parseFloat(model.price) || 0;
    const total = quantity * price;
    
    formAPI.editModel('total', total);
  }
  
  // Monitor changes (you can also use input events)
  document.getElementById('quantityInput').addEventListener('change', recalculateTotal);
  document.getElementById('priceInput').addEventListener('change', recalculateTotal);
});
```

#### 5. Programmatically Opening Popups

```javascript
waitForFormAPI('mainForm', (formAPI) => {
  
  // Open confirmation popup
  function showConfirmation() {
    formAPI.editState('popup', 'confirmationPopup');
  }
  
  // Close all popups
  function closePopups() {
    formAPI.editState('popup', '');
  }
  
  document.getElementById('confirmBtn').addEventListener('click', showConfirmation);
  document.getElementById('closeBtn').addEventListener('click', closePopups);
});
```

#### 6. Calling Actions Programmatically

```javascript
waitForFormAPI('checkoutForm', (formAPI) => {
  
  // Trigger an action
  document.getElementById('processBtn').addEventListener('click', () => {
    formAPI.callAction('Process Payment', (success) => {
      if (success) {
        alert('Payment processed!');
      } else {
        alert('Payment failed');
      }
    });
  });
});
```

***

### Important Notes

1. **Form ID is required** — API is only available if the FpsForm2 component has a short component ID in `data.params.comp_ID`.
2. **Timing is critical** — Always use `waitForFormAPI` helper or `setTimeout` to ensure the form is mounted before accessing the API.
3. **Race conditions** — Use `setModel()` for updating multiple fields at once, NOT multiple `editModel()` calls. Form inputs can override changes between sequential updates, causing data loss. Add 100-150ms `setTimeout` delay if inputs still override your changes.
4. **Model vs ExtendedModel** — When modifying through the API, both objects are automatically synchronized.
5. **Autosubmit** — If autosubmit on model change is enabled, programmatic changes via API will also trigger it.
6. **State persistence** — If state saving to field (saveStateTo) is enabled, programmatic state changes will also be saved on submit.
7. **Refs** — API uses refs to get current values, so it always returns fresh data.
8. **Cleanup** — API is automatically removed from `window.FpsForm2_API` when component unmounts.
9. **Actions vs data-action** — For simple button actions, consider using the `data-action` HTML attribute mechanism instead of programmatic `callAction`. It's simpler and requires no JavaScript code.

***

### Complete Working Example

```html
<!-- Simple control with two inputs and submit button -->
<div style="padding: 20px; border: 1px solid #ccc; border-radius: 8px; max-width: 400px;">
  
  <!-- Model field -->
  <div style="margin-bottom: 15px;">
    <label style="display: block; margin-bottom: 5px; font-weight: bold;">
      Model Field:
    </label>
    <input 
      type="text" 
      id="modelField" 
      placeholder="Enter value for model"
      style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;"
    />
  </div>
  
  <!-- State field -->
  <div style="margin-bottom: 15px;">
    <label style="display: block; margin-bottom: 5px; font-weight: bold;">
      State Field:
    </label>
    <input 
      type="text" 
      id="stateField" 
      placeholder="Enter value for state"
      style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;"
    />
  </div>
  
  <!-- Submit button -->
  <button 
    id="submitBtn"
    style="width: 100%; padding: 10px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px;"
  >
    Submit Form
  </button>
  
</div>

<script>
// ============= SETTINGS - CHANGE THESE =============
const FORM_ID = 'myFormId';              // Form component ID
const MODEL_FIELD_NAME = 'firstName';    // Model field name
const STATE_FIELD_NAME = 'customState';  // State field name
// ===================================================

// Helper to wait for API
function waitForFormAPI(formId, callback, timeout = 5000) {
  const start = Date.now();
  const check = () => {
    if (window.FpsForm2_API && window.FpsForm2_API[formId]) {
      callback(window.FpsForm2_API[formId]);
    } else if (Date.now() - start < timeout) {
      setTimeout(check, 100);
    } else {
      console.error('Form API not available after ' + timeout + 'ms');
    }
  };
  check();
}

// Wait for API to be ready
waitForFormAPI(FORM_ID, (formAPI) => {
  console.log('✅ Form API ready!', formAPI);
  
  // Model field - update model on input
  document.getElementById('modelField').addEventListener('input', (e) => {
    const value = e.target.value;
    formAPI.editModel(MODEL_FIELD_NAME, value);
    console.log(`Model updated: ${MODEL_FIELD_NAME} = ${value}`);
  });
  
  // State field - update state on input
  document.getElementById('stateField').addEventListener('input', (e) => {
    const value = e.target.value;
    formAPI.editState(STATE_FIELD_NAME, value);
    console.log(`State updated: ${STATE_FIELD_NAME} = ${value}`);
  });
  
  // Submit button
  document.getElementById('submitBtn').addEventListener('click', () => {
    console.log('Submitting form...');
    formAPI.submit({
      finish: (result) => {
        console.log('Submit completed!', result);
        alert('Form submitted successfully!');
      }
    });
  });
});
</script>
```

***

### AI Prompt Template

Use this prompt template with any AI assistant (ChatGPT, Claude, etc.) to generate custom code for your form:

{% code overflow="wrap" %}

```
I'm working with Multistep Form component that has a public JavaScript API available at window.FpsForm2_API[formId].

Available API methods:
- getModel() - returns current form model (flat object)
- getExtendedModel() - returns extended model (with nested objects)
- getState() - returns current state (step, popup, custom fields)
- getOriginalModel() - returns original model before changes
- editModel(field, value) - change single field in model
- setModel(newModelData) - merge object with current model (PREFERRED for multiple fields)
- replaceModel(newModel) - completely replace model
- editState(field, value) - change single field in state
- setState(newStateData) - merge object with current state
- replaceState(newState) - completely replace state
- submit(options) - programmatically submit form
- callAction(actionIdOrName, callback) - trigger form action
- refreshOptions() - refresh dynamic field options

Important: The form API becomes available after component mount. Always use this helper:

function waitForFormAPI(formId, callback, timeout = 5000) {
  const start = Date.now();
  const check = () => {
    if (window.FpsForm2_API && window.FpsForm2_API[formId]) {
      callback(window.FpsForm2_API[formId]);
    } else if (Date.now() - start < timeout) {
      setTimeout(check, 100);
    } else {
      console.error('Form API not available after ' + timeout + 'ms');
    }
  };
  check();
}

CRITICAL: When updating multiple fields at once, ALWAYS use setModel() instead of multiple editModel() calls to avoid race conditions with form inputs:

// ❌ BAD - Can lose data due to race conditions:
formAPI.editModel('firstName', 'John');
formAPI.editModel('lastName', 'Doe');
formAPI.editModel('email', 'john@example.com');

// ✅ GOOD - Atomic update, no race conditions:
formAPI.setModel({
  firstName: 'John',
  lastName: 'Doe',
  email: 'john@example.com'
});

// Optional: Add 100-150ms delay if inputs still override your changes:
setTimeout(() => {
  formAPI.setModel({ /* your data */ });
}, 150);

**TASK**: [DESCRIBE YOUR TASK HERE]

Example tasks:
- Calculate total price when quantity or price fields change
- Validate email format and show error
- Auto-fill city based on selected country
- Navigate to next step when button is clicked
- Open popup when certain condition is met
- Load data from external API and populate form
- Clear specific fields when user clicks a button
- Call a form action programmatically
- Parse address from geocoding API and populate multiple address fields

Please generate JavaScript code that:
1. Uses waitForFormAPI helper to wait for the API
2. Gets the form API using the form ID
3. Uses setModel() for updating multiple fields (NOT multiple editModel calls)
4. Includes error handling
5. Has clear comments explaining what the code does
6. Can be easily integrated into HTML via <script> tag
```

{% endcode %}

#### Prompt Usage Examples:

**Example 1: Calculate total**

{% code overflow="wrap" %}

```
**TASK**: I have a form with fields 'quantity', 'price', and 'total'. When quantity or price changes, automatically calculate and update the total field.Form ID is 'orderForm'.
```

{% endcode %}

**Example 2: Conditional field population**

{% code overflow="wrap" %}

```
**TASK**: When user selects country='USA' in the dropdown, automatically set currency='USD' and timezone='America/New_York'.When country='UK', set currency='GBP' and timezone='Europe/London'.Form ID is 'registrationForm'.
```

{% endcode %}

**Example 3: Custom validation**

{% code overflow="wrap" %}

```
**TASK**: Before allowing user to proceed to 'step2', check that:
- email contains '@' symbol
- phone number is exactly 10 digits
- age is between 18 and 100
If validation fails, show alert and prevent step change.
Form ID is 'wizardForm'.
```

{% endcode %}

**Example 4: External data integration**

{% code overflow="wrap" %}

```
**TASK**: When form loads, fetch user data from '/api/users/123' and populate firstName, lastName, and email fields.Show loading indicator while fetching.Form ID is 'profileForm'.
```

{% endcode %}

**Example 5: Action trigger**

{% code overflow="wrap" %}

```
**TASK**: Add a button that triggers the form action named 'Send Email'when clicked, and shows an alert when the action completes.Form ID is 'contactForm'.
```

{% endcode %}

***

**Pro tip:** Just copy the prompt template, replace \[DESCRIBE YOUR TASK HERE] with your specific requirement, and paste it into any AI chat. You'll get ready-to-use code! 🚀


# Cards


# Custom HTML filters

🎠 Create any filtering experience you like

Custom HTML Filters are a powerful feature that allows developers to create completely customized filtering interfaces for Directual Web Components (Tables, Cards, etc.). Instead of being limited to the built-in filter components, you can write your own HTML, CSS, and JavaScript to create any filtering experience you need.

#### What are HTML Filters?

HTML Filters are custom user interfaces that replace the standard Directual filtering system. They consist of:

* **Custom HTML**: Your own form elements, layouts, and styling
* **Interactive JavaScript**: Logic to handle user interactions and generate filters
* **DQL Integration**: Seamless connection to Directual's query language
* **Real-time Updates**: Instant filtering as users interact with your interface

#### Key Benefits

✅ **Complete Design Freedom**: Create filters that match your exact design requirements\
✅ **Advanced Interactions**: Build complex filtering logic with multiple conditions\
✅ **Custom Components**: Use any UI library or custom components you prefer\
✅ **Business Logic**: Implement domain-specific filtering rules and validations\
✅ **User Experience**: Design intuitive interfaces tailored to your users' needs

#### How It Works

1. **Enable Custom Filters**: Set `customHTMLfilters: true` in your component props
2. **Provide HTML Content**: Pass your custom HTML/CSS/JS in `customHTMLfiltersContent`
3. **Use the API**: Access `window.DirectualFilter` to interact with the filtering system
4. **Generate DQL**: Create Directual Query Language strings to filter data
5. **Apply Filters**: Call `window.DirectualFilter.emit(dql)` to update the view

#### Use Cases

* **E-commerce**: Product search with price ranges, categories, ratings, and availability
* **Real Estate**: Property filters with location maps, price ranges, and feature checkboxes
* **CRM**: Contact filtering with tags, date ranges, and custom field combinations
* **Analytics**: Data filtering with date pickers, metric selectors, and comparison tools
* **Content Management**: Article filtering with categories, authors, and publication dates

### AI Prompt for Custom Filter Development

````
You are an expert web developer creating custom HTML filters for Directual Web Components. Your task is to create interactive filter interfaces that integrate with the Directual filtering system.

**CRITICAL: Date Formatting Requirements**
- All dates in DQL queries MUST be in ISO 8601 format
- Use formats like: `'2000-04-09T'`, `'2000-04-09T14:30:00Z'`, or `'2000-04-09T14:30:00.000Z'`
- Convert user input dates to ISO format before generating DQL
- Example: User selects "April 9, 2000" → convert to `'2000-04-09T'` in DQL

### Technical Requirements

**API Contract:**
- Use `window.DirectualFilter.props` to access current state and available fields
- Call `window.DirectualFilter.emit(dqlString, sortOptions)` to apply filters and sorting
- DQL (Directual Query Language) format: `'fieldName' operator 'value'`
- Sort options: `{field: 'fieldName', direction: 'asc'|'desc'}` or `'fieldName:asc'`

**Available Data:**
```javascript
window.DirectualFilter.props = {
    currentFilter: string,    // Current DQL filter
    currentSort: object|string|null, // Current sorting (object: {field, direction} or string: "field:direction")
    fields: Array<{          // Available fields for filtering
        key: string,         // Field system name
        value: string        // Field display name
    }>,
    lang: string,           // Current language
    dict: object           // Localization dictionary
}
```

**DQL Operators:**
- `like` - partial text match
- `=` - exact match  
- `>=`, `<=`, `>`, `<` - numeric/date comparisons
- `AND`, `OR` - logical operators

**DQL Examples:**
- `'title' like 'sun' AND 'year' < 1950`
- `('title' like 'sun' AND 'year' < 1950) OR 'is_good' = 'true'`
- `'birth_date' <= '2000-04-09T'`
- `'price' >= 100 AND 'category' = 'electronics'`

**Important Notes:**
- **Dates must be in ISO format**: Use ISO 8601 format like `'2000-04-09T'` or `'2000-04-09T14:30:00Z'`
- **Numbers are NOT quoted**: Use bare numbers like `100`, `1950` (no quotes)
- **Strings and dates ARE quoted**: Text and dates must be in single quotes `'text'`, `'2000-04-09T'`
- **Field names must be quoted**: Always use single quotes around field names `'fieldName'`

### Filter Design Guidelines

**[DESCRIBE YOUR FILTER REQUIREMENTS HERE]**
*Example: Create a modern, responsive filter interface with search inputs, dropdown selectors, and date pickers. Use Bootstrap-style components with blue (#007bff) primary color and clean typography.*

### Implementation Pattern

1. **HTML Structure**: Create form elements with unique IDs
2. **JavaScript Functions**: Implement filter logic and DQL generation
3. **Event Handlers**: Connect UI interactions to `window.DirectualFilter.emit()`
4. **State Management**: Handle filter clearing and validation

### Example Code Template

```html
<div style="padding: 20px; border: 1px solid #ddd; border-radius: 8px;">
    <h4>Custom Filters</h4>
    
    <!-- Text Search -->
    <div style="margin-bottom: 15px;">
        <label>Search Text:</label>
        <input type="text" id="textSearch" placeholder="Enter search term...">
        <button onclick="applyTextFilter()">Apply</button>
    </div>
    
    <!-- Numeric Range -->
    <div style="margin-bottom: 15px;">
        <label>Number Range:</label>
        <input type="number" id="minNumber" placeholder="Min">
        <input type="number" id="maxNumber" placeholder="Max">
        <button onclick="applyNumberFilter()">Filter</button>
    </div>
    
    <!-- Sorting -->
    <div style="margin-bottom: 15px;">
        <label>Sort by:</label>
        <select id="sortField">
            <option value="">No sorting</option>
            <option value="fieldName">Field Name</option>
            <option value="numberField">Number Field</option>
        </select>
        <select id="sortDirection">
            <option value="asc">Ascending</option>
            <option value="desc">Descending</option>
        </select>
        <button onclick="applySorting()">Sort</button>
    </div>
    
    <!-- Actions -->
    <button onclick="clearFilters()">Clear All</button>
</div>

<script>
function applyTextFilter() {
    const value = document.getElementById('textSearch').value;
    if (value) {
        const dql = "'fieldName' like '" + value + "'";
        const currentSort = getCurrentSort();
        window.DirectualFilter.emit(dql, currentSort);
    }
}

function applyNumberFilter() {
    const min = document.getElementById('minNumber').value;
    const max = document.getElementById('maxNumber').value;
    
    let conditions = [];
    if (min) conditions.push("'numberField' >= '" + min + "'");
    if (max) conditions.push("'numberField' <= '" + max + "'");
    
    const dql = conditions.join(' AND ');
    const currentSort = getCurrentSort();
    window.DirectualFilter.emit(dql, currentSort);
}

function applySorting() {
    const field = document.getElementById('sortField').value;
    const direction = document.getElementById('sortDirection').value;
    
    if (field) {
        const sortOptions = { field: field, direction: direction };
        const currentFilter = getCurrentFilter();
        window.DirectualFilter.emit(currentFilter, sortOptions);
    }
}

function getCurrentSort() {
    const field = document.getElementById('sortField').value;
    const direction = document.getElementById('sortDirection').value;
    return field ? { field: field, direction: direction } : null;
}

function getCurrentFilter() {
    const text = document.getElementById('textSearch').value;
    const min = document.getElementById('minNumber').value;
    const max = document.getElementById('maxNumber').value;
    
    let conditions = [];
    if (text) conditions.push("'fieldName' like '" + text + "'");
    if (min) conditions.push("'numberField' >= '" + min + "'");
    if (max) conditions.push("'numberField' <= '" + max + "'");
    
    return conditions.join(' AND ');
}

function clearFilters() {
    document.getElementById('textSearch').value = '';
    document.getElementById('minNumber').value = '';
    document.getElementById('maxNumber').value = '';
    document.getElementById('sortField').value = '';
    window.DirectualFilter.emit('', null);
}

// Initialize on load
setTimeout(() => {
    console.log('Available fields:', window.DirectualFilter.props.fields);
}, 100);
</script>
```

### Best Practices

1. **Field Validation**: Check if fields exist before filtering
2. **Error Handling**: Validate user input and DQL syntax
3. **Performance**: Debounce rapid filter changes
4. **UX**: Provide clear feedback and loading states
5. **Accessibility**: Use proper labels and keyboard navigation

### Advanced Features

- **Multi-field filtering**: Combine multiple conditions with AND/OR
- **Dynamic field selection**: Let users choose which fields to filter
- **Preset filters**: Provide common filter combinations
- **Filter history**: Save and restore previous filter states

---

## Simple Example: Product Search Filter

```html
<div class="custom-filter" style="padding: 20px; background: #f8f9fa; border-radius: 8px; font-family: Arial, sans-serif;">
    <h3 style="margin-top: 0; color: #333;">🔍 Product Search</h3>
    
    <!-- Product Name Search -->
    <div style="margin-bottom: 15px;">
        <label style="display: block; font-weight: bold; margin-bottom: 5px;">Product Name:</label>
        <input type="text" id="productName" 
               style="width: 250px; padding: 8px; border: 1px solid #ccc; border-radius: 4px;"
               placeholder="Enter product name...">
    </div>
    
    <!-- Price Range -->
    <div style="margin-bottom: 15px;">
        <label style="display: block; font-weight: bold; margin-bottom: 5px;">Price Range:</label>
        <input type="number" id="minPrice" placeholder="Min $" 
               style="width: 100px; padding: 8px; border: 1px solid #ccc; border-radius: 4px; margin-right: 10px;">
        <input type="number" id="maxPrice" placeholder="Max $"
               style="width: 100px; padding: 8px; border: 1px solid #ccc; border-radius: 4px;">
    </div>
    
    <!-- Category Dropdown -->
    <div style="margin-bottom: 15px;">
        <label style="display: block; font-weight: bold; margin-bottom: 5px;">Category:</label>
        <select id="category" style="width: 200px; padding: 8px; border: 1px solid #ccc; border-radius: 4px;">
            <option value="">All Categories</option>
            <option value="electronics">Electronics</option>
            <option value="clothing">Clothing</option>
            <option value="books">Books</option>
        </select>
    </div>
    
    <!-- Sorting Options -->
    <div style="margin-bottom: 15px;">
        <label style="display: block; font-weight: bold; margin-bottom: 5px;">Sort by:</label>
        <select id="sortBy" style="width: 150px; padding: 8px; border: 1px solid #ccc; border-radius: 4px; margin-right: 10px;">
            <option value="">Default</option>
            <option value="productName">Name</option>
            <option value="price">Price</option>
            <option value="category">Category</option>
        </select>
        <select id="sortOrder" style="width: 120px; padding: 8px; border: 1px solid #ccc; border-radius: 4px;">
            <option value="asc">A → Z / Low → High</option>
            <option value="desc">Z → A / High → Low</option>
        </select>
    </div>
    
    <!-- Action Buttons -->
    <div>
        <button onclick="searchProducts()" 
                style="padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 10px;">
            Search Products
        </button>
        <button onclick="clearSearch()"
                style="padding: 10px 20px; background: #6c757d; color: white; border: none; border-radius: 4px; cursor: pointer;">
            Clear
        </button>
    </div>
    
    <!-- Status Display -->
    <div id="filterStatus" style="margin-top: 15px; padding: 10px; background: #e9ecef; border-radius: 4px; display: none;">
        <strong>Active Filter:</strong> <span id="currentFilter"></span>
    </div>
</div>

<script>
function searchProducts() {
    const name = document.getElementById('productName').value;
    const minPrice = document.getElementById('minPrice').value;
    const maxPrice = document.getElementById('maxPrice').value;
    const category = document.getElementById('category').value;
    
    let conditions = [];
    
    // Add name filter
    if (name) {
        conditions.push("'productName' like '" + name + "'");
    }
    
    // Add price range filters
    if (minPrice) {
        conditions.push("'price' >= '" + minPrice + "'");
    }
    if (maxPrice) {
        conditions.push("'price' <= '" + maxPrice + "'");
    }
    
    // Add category filter
    if (category) {
        conditions.push("'category' = '" + category + "'");
    }
    
    // Combine all conditions
    const dql = conditions.join(' AND ');
    
    // Get current sorting
    const sortBy = document.getElementById('sortBy').value;
    const sortOrder = document.getElementById('sortOrder').value;
    const sortOptions = sortBy ? { field: sortBy, direction: sortOrder } : null;
    
    // Show current filter
    updateFilterStatus(dql, sortOptions);
    
    // Apply filter with sorting
    window.DirectualFilter.emit(dql, sortOptions);
    
    console.log('Applied filter:', dql, 'with sorting:', sortOptions);
}

function clearSearch() {
    // Clear all inputs
    document.getElementById('productName').value = '';
    document.getElementById('minPrice').value = '';
    document.getElementById('maxPrice').value = '';
    document.getElementById('category').value = '';
    document.getElementById('sortBy').value = '';
    
    // Hide status
    document.getElementById('filterStatus').style.display = 'none';
    
    // Clear filter and sorting
    window.DirectualFilter.emit('', null);
    
    console.log('Filters and sorting cleared');
}

function updateFilterStatus(dql, sortOptions) {
    const status = document.getElementById('filterStatus');
    const current = document.getElementById('currentFilter');
    
    if (dql || sortOptions) {
        let statusText = '';
        if (dql) statusText += 'Filter: ' + dql;
        if (sortOptions) {
            if (statusText) statusText += ' | ';
            statusText += 'Sort: ' + sortOptions.field + ' ' + sortOptions.direction;
        }
        current.textContent = statusText;
        status.style.display = 'block';
    } else {
        status.style.display = 'none';
    }
}

// Auto-search on Enter key
document.addEventListener('keypress', function(e) {
    if (e.key === 'Enter') {
        searchProducts();
    }
});

// Initialize when API is ready
setTimeout(() => {
    if (window.DirectualFilter) {
        console.log('🚀 Custom filter ready!');
        console.log('Available fields:', window.DirectualFilter.props.fields);
        
        // Show current filter if any
        const current = window.DirectualFilter.props.currentFilter;
        if (current) {
            updateFilterStatus(current);
        }
    }
}, 100);
</script>
```

This example creates a comprehensive product search interface with:
- Text search for product names
- Numeric range filtering for prices  
- Dropdown category selection
- **Sorting by multiple fields with direction control**
- Combined filter logic with AND operators
- Clear visual feedback with filter/sort status
- Keyboard shortcuts (Enter to search)

The filter generates DQL like: `'productName' like 'laptop' AND 'price' >= '100' AND 'price' <= '500' AND 'category' = 'electronics'`

With sorting like: `{field: 'price', direction: 'desc'}` for highest price first.

### API Usage Examples:

```javascript
// Filter only
window.DirectualFilter.emit("'name' like 'search'");

// Filter with sorting
window.DirectualFilter.emit("'name' like 'search'", {field: 'price', direction: 'asc'});

// Sorting only (keep current filter)
window.DirectualFilter.emit(window.DirectualFilter.props.currentFilter, {field: 'name', direction: 'desc'});

// Clear everything
window.DirectualFilter.emit('', null);
```
````


# Table

Work with a lot of data

![](/files/-M_uUvWDovJ0i6OBgZo4)

## Component Overview

The Table component serves several purposes:

* Display data
* Edit data (posting objects via multiple API-endpoints)
* Support role-based access control.

&#x20;

## Component Settings Sections

![](/files/-M_uXO-iuEShj5WSRS49)

* **General:** Settings like API-endpoint, title, page size
* **Table:** Settings of the Table view
* **Object:** Settings of the object view
* **Actions:** Settings of the component actions.


# Kanban

[Live-demo](https://kanban.directual.app/)

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

## Component Overview

The Kanban component serves several purposes:

* Display data
* Edit data (including drag and drop between stages-columns)
* Support role-based access control.

### Step 1. Configuring API-endpoint

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

Choose the endpoint, which includes the following fields:

* **Stage (column)** — a `link` field, linked with a directory structure
* **Weight** — a `decimal` field. Sorting within a column from small to large ↓. As soon as the user dragged an item the weight is saved from 0 to N for the whole column.

{% hint style="info" %}
If you want to drag and drop objects, `ID`, `weight` and `stage` fields have to be available both for reading and writing.&#x20;
{% endhint %}

### Step 2. Configuring Kanban

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

1. Choose the stage (column field)
2. Choose weight field
3. Choose enable or disable drag and drop
4. Click "Reset the list" to get the stages from the linked data structure (that is a kind of directory)

<figure><img src="/files/IE4Ao5VYzCxocdFAjcQw" alt=""><figcaption><p>Stages directory data structure</p></figcaption></figure>

### Step 3. Configure Cards View and Actions

Follow the same principles as in the [Cards component](/web-pages/components/legacy-components/cards).


# Markdown text

By using the Markdown text component, you can create rich text using [Markdown](/data/data-types/markdown-cheatsheet).

You can also add data from your objects using [Templating for web-pages](/template-system/basics-of-template-system/templating-techniques-for-web-pages). That is a great technique with [subpages](/web-pages/setting-up-pages-layout/subpages-and-url-parameters).


# HTML code

By using the HTML code component, you can insert HTML-code onto the page.

You can add data from your objects using [Templating for web-pages](/template-system/basics-of-template-system/templating-techniques-for-web-pages). That is a great technique with [subpages](/web-pages/setting-up-pages-layout/subpages-and-url-parameters).


# Custom Browser API

### Overview

The FPS platform provides a global browser API for custom HTML components. This API handles authentication automatically using secure HTTP-only cookies, eliminating the need to manage session tokens in your code.

### Available APIs

#### `window.callEndpoint()`

Makes authenticated API requests to your backend endpoints.

**Signature:**

```javascript
window.callEndpoint(endpoint, method, body, params, callback, options)
```

**Parameters:**

* `endpoint` (string) - The API endpoint name (e.g., `'getFeatures'`, `'updateUser'`)
* `method` (string) - HTTP method: `'GET'`, `'POST'`, `'PUT'`, `'DELETE'`
* `body` (object) - Request body for POST/PUT/DELETE requests (ignored for GET)
* `params` (object) - Query parameters (e.g., `{ page: 1, limit: 10 }`)
* `callback` (function) - Callback function: `(status, data, settings, fullResponse) => {}`
  * `status` (string) - `'ok'` or `'error'`
  * `data` (array|object) - Response data
  * `settings` (object) - View settings (optional)
  * `fullResponse` (object) - Full API response
* `options` (object) - Optional settings (e.g., `{ signal: abortController.signal }`)

**Returns:** Nothing (results passed to callback)

### Examples

#### Basic GET Request

```javascript
// Fetch user list
window.callEndpoint(
  'getUsers',           // endpoint
  'GET',                // method
  undefined,            // body (not used for GET)
  { limit: 10 },        // params
  (status, data) => {   // callback
    if (status === 'ok') {
      console.log('Users:', data);
      // Render your data here
    } else {
      console.error('Error:', data);
    }
  }
);
```

#### POST Request with Body

```javascript
// Create new item
window.callEndpoint(
  'createItem',
  'POST',
  { 
    title: 'New Item',
    description: 'Item description',
    price: 99.99
  },
  {},
  (status, data) => {
    if (status === 'ok') {
      console.log('Created:', data);
    }
  }
);
```

#### Update Request

```javascript
// Update existing record
window.callEndpoint(
  'updateItem',
  'POST',
  { 
    id: '123',
    title: 'Updated Title'
  },
  {},
  (status, data, settings, fullResponse) => {
    if (status === 'ok') {
      console.log('Updated successfully');
      console.log('Full response:', fullResponse);
    }
  }
);
```

#### Search with Filters

```javascript
// Search with query parameters
window.callEndpoint(
  'searchItems',
  'GET',
  undefined,
  {
    _value: 'search term',
    category: 'electronics',
    minPrice: 50
  },
  (status, data) => {
    if (status === 'ok') {
      document.getElementById('results').innerHTML = 
        data.map(item => `<div>${item.title}</div>`).join('');
    }
  }
);
```

#### With Abort Controller

```javascript
const abortController = new AbortController();

window.callEndpoint(
  'getLargeData',
  'GET',
  undefined,
  {},
  (status, data) => {
    console.log('Data received:', data);
  },
  { signal: abortController.signal }
);

// Cancel request after 5 seconds
setTimeout(() => abortController.abort(), 5000);
```

### Authentication

**All requests are automatically authenticated.** The session token is stored in a secure HTTP-only cookie and is automatically included with every request. You don't need to:

* Store tokens in localStorage/sessionStorage
* Manually add authorization headers
* Handle token refresh

The platform manages authentication for you.

### Error Handling

```javascript
window.callEndpoint(
  'riskyOperation',
  'POST',
  { data: 'value' },
  {},
  (status, data) => {
    if (status === 'error') {
      // API returned an error
      console.error('API Error:', data.msg || data);
      // Show error to user
      alert('Operation failed: ' + (data.msg || 'Unknown error'));
    } else {
      // Success
      console.log('Success:', data);
    }
  }
);
```

### Migration Guide

If you have existing code using `fetch()` directly, you need to migrate to `window.callEndpoint()` for authentication to work properly.

#### Before (Old Code - Will Not Work)

```javascript
// ❌ This will NOT work - no access to session token
fetch('https://api.example.com/endpoint?sessionID=TOKEN', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ key: 'value' })
})
.then(res => res.json())
.then(data => console.log(data));
```

#### After (New Code - Works)

```javascript
// ✅ This works - automatic authentication
window.callEndpoint(
  'endpoint',
  'POST',
  { key: 'value' },
  {},
  (status, data) => {
    if (status === 'ok') {
      console.log(data);
    }
  }
);
```

### File Upload with FormData

```javascript
// Upload file to backend
const fileInput = document.getElementById('fileInput');

fileInput.addEventListener('change', (e) => {
  const file = e.target.files[0];
  
  if (!file) return;
  
  // Create FormData and append file
  const formData = new FormData();
  formData.append('file', file);
  
  // You can add additional fields
  formData.append('description', 'My uploaded file');
  formData.append('category', 'documents');
  
  window.callEndpoint(
    'uploadFile',         // endpoint name
    'POST',               // method
    formData,             // FormData body (not JSON!)
    { 
      fileStorage: 'my-storage',
      maxSize: 10485760  // optional params
    },
    (status, data) => {
      if (status === 'ok') {
        console.log('File uploaded:', data);
        // data might contain file URL, ID, etc.
        alert('File uploaded successfully!');
      } else {
        console.error('Upload failed:', data);
        alert('Upload error: ' + (data.msg || 'Unknown error'));
      }
    }
  );
});
```

Important for file uploads:

* Use FormData instead of plain object for body parameter
* The browser automatically sets correct Content-Type with boundary for multipart/form-data
* Field name 'file' must match your backend FileUpload data structure field
* You can append multiple files or additional text fields to FormData

### AI Migration Prompt

Use this prompt with an AI assistant to automatically convert your old code:

***

**PROMPT:**

```
I need to migrate my fetch() API calls to use window.callEndpoint() for the FPS platform.

OLD PATTERN (fetch):
- Direct fetch() calls to API endpoints
- Manual sessionID/token in URL or headers
- Promise-based (.then/.catch)

NEW PATTERN (window.callEndpoint):
- window.callEndpoint(endpoint, method, body, params, callback)
- Automatic authentication (no token needed)
- Callback-based

CONVERSION RULES:
1. Extract endpoint name from URL (last segment)
2. Convert HTTP method (GET/POST/PUT/DELETE)
3. Move body from fetch options to 3rd parameter
4. Move URL query params to 4th parameter (as object)
5. Convert .then(res => res.json()).then(data => ...) to callback: (status, data) => { if (status === 'ok') { ... } }
6. Remove all sessionID, token, Authorization headers
7. Keep only 'signal' in options if using AbortController

Please convert this code:

[PASTE YOUR CODE HERE]
```

***

**Example Conversion:**

Input:

```javascript
fetch('/api/sl?sl=getFeatures&limit=10', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ category: 'electronics' })
})
.then(res => res.json())
.then(data => {
  console.log(data);
  renderItems(data);
})
.catch(err => console.error(err));
```

AI Output:

```javascript
window.callEndpoint(
  'getFeatures',
  'POST',
  { category: 'electronics' },
  { limit: 10 },
  (status, data) => {
    if (status === 'ok') {
      console.log(data);
      renderItems(data);
    } else {
      console.error(data);
    }
  }
);
```

### Best Practices

1. **Always check status** in the callback before using data
2. **Don't store tokens** - authentication is handled automatically
3. **Use meaningful endpoint names** that match your backend structure
4. **Handle errors gracefully** with user-friendly messages
5. **Use AbortController** for long-running requests that can be cancelled
6. **Test in browser console** before integrating into your component

### Troubleshooting

#### Request not authenticated

* Make sure you're logged in to the platform
* Check that the user has valid session (check browser cookies for `token_new_*`)

#### Endpoint not found

* Verify the endpoint name matches your backend configuration
* Check for typos in the endpoint string

#### Data not received

* Verify the callback function is being called
* Check browser console for errors
* Inspect network tab in DevTools to see the actual request/response

### Additional Resources

* See `SESSION_AUTH.md` for details on session management
* Check your backend API documentation for available endpoints
* Use browser DevTools Network tab to debug requests


# HTML component custom API

### Overview

Every **HTML code** component on a page exposes a JavaScript API via `window.FpsHtml_API`. This lets you programmatically control any HTML component from custom scripts — update content, show/hide, re-run embedded scripts, or call backend endpoints — all without a page reload.

### Setup: Component ID

For the API to work, the component must have a **Component ID** set in its settings (`comp_ID` field). Without it the component won't register itself in `window.FpsHtml_API`.

Set a short, unique string like `hero-block` or `promo-banner`.

### Accessing the API

The API is registered after the component mounts. Use the helper below to wait for it:

```javascript
function waitForHtmlAPI(compId, callback, timeout) {
  timeout = timeout || 5000;
  var start = Date.now();
  var check = function() {
    if (window.FpsHtml_API && window.FpsHtml_API[compId]) {
      callback(window.FpsHtml_API[compId]);
    } else if (Date.now() - start < timeout) {
      setTimeout(check, 100);
    } else {
      console.error('FpsHtml_API: component "' + compId + '" not found after ' + timeout + 'ms');
    }
  };
  check();
}
```

Then use it:

```javascript
waitForHtmlAPI('my-block', function(api) {
  api.setHtml('<p>Hello from API!</p>');
});
```

### API Reference

#### `getHtml()`

Returns the current HTML string of the component.

```javascript
var html = api.getHtml();
console.log(html); // '<p>Hello from API!</p>'
```

***

#### `setHtml(newHtml)`

Replaces the component's HTML content.

```javascript
api.setHtml('<h2>Updated content</h2><p>Fresh text here.</p>');
```

***

#### `rerender()`

Forces the component to re-execute all embedded `<script>` tags. Useful when you need scripts inside the HTML to run again without changing the content.

```javascript
api.rerender();
```

***

#### `show()`

Shows the component if it was hidden via `api.hide()`.

```javascript
api.show();
```

***

#### `hide()`

Hides the component programmatically. Does not affect the `isHidden` setting — just toggles visibility at runtime.

```javascript
api.hide();
```

***

#### `getData()`

Returns the full data object the component was initialized with (including `html`, `comp_ID`, `isHidden`, etc.).

```javascript
var data = api.getData();
console.log(data.comp_ID); // 'my-block'
```

***

#### `getElement()`

Returns the root DOM element wrapping the component. Useful for direct DOM manipulation.

```javascript
var el = api.getElement();
el.style.opacity = '0.5';
```

***

#### `callEndpoint(endpoint, method, body, params, callback)`

Makes an authenticated request to a backend endpoint. Works the same as `window.callEndpoint()` but scoped to this component's context.

**Parameters:**

| Parameter  | Type             | Description                                              |
| ---------- | ---------------- | -------------------------------------------------------- |
| `endpoint` | string           | API endpoint name (e.g. `'getItems'`)                    |
| `method`   | string           | HTTP method: `'GET'`, `'POST'`                           |
| `body`     | object\|FormData | Request body (ignored for GET)                           |
| `params`   | object           | URL query parameters                                     |
| `callback` | function         | `(status, data) => {}` — `status` is `'ok'` or `'error'` |

```javascript
api.callEndpoint('getStats', 'GET', null, { period: '30d' }, function(status, data) {
  if (status === 'ok') {
    api.setHtml('<p>Total: ' + data[0].total + '</p>');
  }
});
```

### Examples

#### Load data and render HTML on page load

```javascript
waitForHtmlAPI('stats-widget', function(api) {
  api.callEndpoint('getDashboardStats', 'GET', null, {}, function(status, data) {
    if (status === 'ok') {
      var item = data[0] || {};
      api.setHtml(
        '<div class="stats">' +
          '<span>Users: ' + item.users + '</span>' +
          '<span>Orders: ' + item.orders + '</span>' +
        '</div>'
      );
    } else {
      api.hide();
    }
  });
});
```

***

#### Control visibility from another HTML component

```javascript
// This script lives inside a different HTML component on the same page
document.getElementById('toggle-btn').addEventListener('click', function() {
  var api = window.FpsHtml_API && window.FpsHtml_API['promo-banner'];
  if (api) {
    api.hide();
  }
});
```

***

#### Update content on a button click

```javascript
waitForHtmlAPI('result-block', function(api) {
  document.getElementById('load-btn').addEventListener('click', function() {
    api.setHtml('<p>Loading...</p>');

    api.callEndpoint('fetchResult', 'POST', { id: '123' }, {}, function(status, data) {
      if (status === 'ok') {
        api.setHtml('<p>' + data[0].text + '</p>');
      } else {
        api.setHtml('<p>Error loading data.</p>');
      }
    });
  });
});
```

***

#### Re-run scripts after socket update

If you have scripts inside the HTML component that should re-execute when data refreshes via WebSocket, call `rerender()` after updating content:

```javascript
waitForHtmlAPI('chart-block', function(api) {
  api.callEndpoint('getChartData', 'GET', null, {}, function(status, data) {
    if (status === 'ok') {
      api.setHtml('<canvas id="myChart"></canvas><script>/* init chart with ' + JSON.stringify(data) + ' */<\/script>');
      api.rerender();
    }
  });
});
```

### Notes

* The API is registered after component mount. Always use `waitForHtmlAPI` (or a similar polling approach) rather than accessing `window.FpsHtml_API` directly.
* `hide()` / `show()` is runtime-only. The `isHidden` setting in the component config is a separate mechanism and takes precedence at initial render.
* If the component is removed from the page, its entry in `window.FpsHtml_API` is automatically deleted.
* All requests via `api.callEndpoint()` are authenticated the same way as `window.callEndpoint()`. See `CUSTOM_BROWSER_API.md` for details.


# Hint

Add a hint to your page:

![](/files/x92YTtgdmNIB76Wl9bRz)

The component has the following settings:

* Hint color: red, green, orange (you can change those colors in the Portal appearance settings);
* Hint title: string
* Hint text: string or HTML
* Margins: top and bottom
* Endpoint and option to use [templating](/template-system/basics-of-template-system/templating-techniques-for-web-pages#connecting-api-endpoint-and-turning-on-the-templating-engine)


# Link Button

Link Button is made to let your users easily navigate between pages and subpages.

You may configure the button: label, icon, size, color.

There are the following common use cases for the **Link** setting:

* `/page` — go to another web-page with route `/page`
* `./parm` — add parameter to URL. e.g. You are clicking that button on `yourapp.directual.app/catalogue/category`. You'll get to `yourapp.directual.app/catalogue/category/param`
* `../` — go back to one level. `../../` — go back to two levels, etc. You may use such combinations as `../../anotherpage`

You can apply [Templating engine](/template-system/basics-of-template-system/templating-techniques-for-web-pages) for the fields **Link** and **Label.**


# Video

Copy and paste a **YouTube** video URL (check that it is not private)

Set up max width and max height of your video.


# Plugins


# Messenger


# Chart


# Legacy components


# Cards (legacy)

Role-based access to your data 🃏

![](/files/-MHLC_1UsdlTtcFaCmJZ)

Here, take a look at this short [Demo](https://library.directual.app/books) of the component.

## Component Overview

The Cards component serves several purposes:

* Display data
* Edit data (posting objects via multiple API-endpoints)
* Support role-based access control.

## Component Settings Sections

![](/files/-M_uXTFJXB0LLrWxtAh4)

* [**General**](/web-pages/components/legacy-components/cards#general-cards-view-settings)**:** Settings like API-endpoint, title, page size
* [**List**](/web-pages/components/legacy-components/cards#list-appearance-settings)**:** Settings of the list appearance view
* [**Object**](/web-pages/components/legacy-components/cards#object-view-settings)**:** Settings of the object view
* [**Actions**](/web-pages/components/legacy-components/cards#actions)**:** Settings of the component actions

## General Cards View Settings

* [API-endpoint](/api-integrations/api-endpoints-security-layer) (the main one). It defines the objects to display in the component. There can be additional API-endpoints for posting objects via [Actions](/web-pages/components/legacy-components/cards#actions)
* List title
* Page size (10 by default)

## List Appearance Settings

![Example of a grid view cards with picture (on the left option) and counter](/files/-MN3lHShaUyZPuAxgRde)

* **Card Header:** You can't choose the header from here, because throughout the system, the [structure visible name](/data/data-structures#structure-visible-name) plays role of the object title. You can configure it from the data structure configuration
* **Card Header** C**omment:** This is the subheader in the card. It can by any field (available for reading in the API-endpoint) like *string, number, email, date, link, arrayLink*
* **Card Text:** This is the text in a card. It can be any field (available for reading in the API-endpoint) like *string, number, email*
* **Card Color:** Allows you to select the color of the card. There are following options of coloring the card:
  * No coloring
  * Stripe on the left side
  * Border
  * Fill the whole card
* **Show Counter** on the card (`OFF` by default)
  * Counter field (type of *number*)
  * Counter text (e.g. *5 requests*)
* **Layout**: `grid` view or `list` view (`grid` by default)
* **Include Picture** to the card (`OFF` by default)
  * Picture URL (if you turn `ON` the option to include picture to the card). It can be any field type available for reading.&#x20;
  * Place to display the picture in the card
    * No picture
    * On the left (the option picture size is available)
    * On the top (the option picture size is available)
    * On the left, circled (the option picture size is available)
    * As a background
  * Picture size (`100px` by default)

## Object View Settings

![Example of a card with two tabs and an action (form)](/files/-MN3zaRWngTlK5MxIGPL)

In this section, you can organize the fields in an object card using Tabs (Sections), adjust the field order and configure fields settings.

A field can have one of the following  settings:

* Read-only
* Write-only
* Read and write

These options depend on the details of your API endpoint. If there are fields available for writing, users will see them as inputs and will be able to edit and save them.

![Example of a Card with fields available for writing](/files/-MN4RWLC4eCiSEgwxUP7)

{% hint style="warning" %}
It's important to remember that you must include the ID field, which should be available for both reading and writing, to make the object editable.
{% endhint %}

## Actions

Using Actions you can:

* Update current object
* Update linked objects
* Create new objects

There are two types of actions:

* Button (if it uses the main API-endpoint, it can be included into *dropdown menu*)&#x20;
* Form

You also can turn on closing a popup when performing an action.

{% hint style="info" %}
Hint: If you want your data to be updated online, consider using [synchronous scenarios](/scenarios/synchronic-scenarios-1) and the option 'Close popup at clicking.'
{% endhint %}

Next, you need to select an API endpoint for writing data. It can be the main one or another relevant endpoint. You will choose fields from that API endpoint that are available for writing to set up the form and automatic mapping.

You can also decide whether to call the action from the main object card or from a linked object card.

### Automatic Fields Mapping

On the left side, choose fields to be filled (from the API-endpoint).

On the right side, choose one of the following options:

* Constant. (e.g. `true` or `12`)
* Object card. Fields from object we've opened.
* User ID. If user is authorized. Remember, you can [make users log in](/web-pages/portal#portal-settings) for accessing the page.
* Linked card. Fields from linked object. Available only if we put action in the linked card (*link* or *arrayLink* field)

### Conditionals

Actions can be conditionally displayed depending on user roles or current field values.&#x20;

The following options are available for conditionals:

* User's `ID` equals a constant value
* User's `ID` equals to object field value (or to linked object field value, if an action is called from it)
* User `role` contains some value (e.g. `role = admin,manager` contains `admin`). You can list several roles (e.g. `role1,role2`), and if the user has at least one of them, conditional returns true
* Object `field` equals to a particular `constant`&#x20;
* Linked object `field` equals to a particular `constant` (for actions called from linked cards)

  &#x20;


# Form (legacy)

An interactive web form ⌨️

{% hint style="warning" %}
This component is outdated. Use advanced [multi-step form](/web-pages/components/multistep-form) instead
{% endhint %}

## Component **Overview**

The Form component serves several purposes:

* Collecting data
* [Editing objects](/web-pages/components/legacy-components/form#editing-existing-objects)
* Interacting with the user through online [result processing](/web-pages/components/legacy-components/form#online-result-processing).

## Component Settings Sections

* [**General**](/web-pages/components/legacy-components/form#general-form-settings)**:** Settings like the header, description, button text
* [**Fields**](/web-pages/components/legacy-components/form#fields-settings)**:** Inputs for each form field
* [**Result**](/web-pages/components/legacy-components/form#online-result-processing)**:** On-line submission processing.

## General Form Settings

* [API-endpoint](/api-integrations/api-endpoints-security-layer). Defines the fields available for writing in the form
* Form Title
* Form Description (you can use [Markdown](/data/data-types/markdown-cheatsheet) here)
* Submit Button Text (`Submit` by default)
* Labels or Placeholders. Switches the view of inputs: field names can be displayed *above the field* or as a *placeholder.*
* Form max width in pixels
* Default http-request parameters.

## Fields Settings

Each field, which is available for writing (POST-requesting) or reading (GET-requesting) in an [API-endpoint](/api-integrations/api-endpoints-security-layer), has a specific [data type](/data/data-types). You can configure the form input field and set the default value according to the field type.

### Sections

You can display sections as visible sections or as invisible groups, which can be particularly useful for conditional sections.

### Conditional Sections

The visibility of a section can depend on the user's choice of field values above (or on existing objects fields in the case of [object editing](#editing-existing-objects)). If you add more than one conditional, you can choose either the AND or OR operator to join your conditionals.

If you want to check if a field is empty, enter the `null` value. Conversely, use `isNotNull`, if you want the field to have any value other than empty.

![In this example, Section 2 is visible only if the user choses value "bug" for the field {{feature\_type}}](/files/-M_uE8CSoLM2rCG0mXMy)

### Link and ArrayLink Quick Search Option

When the dropdown for `link`/`arrayLInk` field is ON (it is OFF by default), objects from the linked structure can be displayed as options in dropdown selects. The [visible name](/data/data-structures#structure-visible-name) of the linked will be displayed, and if there is no such, name, the `id` field will be displayed instead.&#x20;

To make this feature work, you need to create an API endpoint for it. You can also add [different filters ](/api-integrations/api-endpoints-security-layer)to that endpoint, such as "objects connected with the current user."

{% hint style="info" %}
The maximum quantity of options in the dropdown is limited to 1000.
{% endhint %}

### Hidden Fields

If you marked a field as hidden (which is possible for *string* and *link* fields), their values will be obtained from the URL query parameters.&#x20;

It should look like this:

`yourapp.directual.app/FormPage?fieldOne=value&fieldTwo=1984`

{% hint style="warning" %}
Query parameters must start with a '`?'` and be separated by '`&'.`
{% endhint %}

In the submitted object, you will receive:

fieldOne = `value`

fieldTwo = `1984`

## Personalization

You can include a hidden field for User ID, which will be filled automatically if the user is logged in.

First, you need to have a field type of *link* to `WebUser` data structure available for posting in your API-endpoint.

Find the section **Personalization** in the **Fields settings**:

The user's ID will be posted in this field automatically If the user is logged in. If the user isn't logged in, the field will remain empty.

{% hint style="warning" %}
Remember, that you can configure your API-endpoint in a way that unauthorized user will not be able to submit the form.
{% endhint %}

## Editing Existing Objects

If you want to enable the option to edit existing objects in addition to creating new ones, you can turn on that option in the **Fields settings** section.

{% hint style="info" %}
Don't forget to include the `id` field in API-endpoint, making  it available both for writing and reading. Additionally, ensure all the fields you want to be fetched available for reading in API-endpoint settings. By default, the `id` field is included in the form, but it's hidden.
{% endhint %}

There are three ways to get the`id` of the object to edit:

1. From the query parameter: `@editObject=_id_` form the URL, where`_id_` represents the id of the object you would like to edit.
2. From URL parameters (this option matches with [subpages](/web-pages/setting-up-pages-layout/subpages-and-url-parameters))
3. Use the user's `id` as the `id` of the object to edit. This allows you to edit the App user (WebUser) object or any other object with such an ID. This option is commonly applied for profile settings.

## Result Resubmission

By default, after submitting the form, the user will see a success message and a button "Submit again". You can remove the button and disable resubmission, hide the button and redirect the user back to the form automatically after a few seconds. Also, you can edit the message (by using HTML).

## Online Result Processing

Submitted object can be processed in a [synchronous scenario](/scenarios/synchronic-scenarios-1), and then the result is to be displayed to the user. There three fields of the object for interpreting the result of object processing:

* **Result: success/fail**. A *Boolean* field. If the field is `true`, the Success block is displayed. If that field is `false`, the error block is displayed.
* **Result message title**. A String field. The title of the result block.
* **Result message text**. A String field (HTML usage is available here). The text of the result block.

![](/files/-MBdOyLB-fSRJnDMVRof)

{% hint style="warning" %}
Remember, that object is saved regardless of the result of synchronous processing. However, if the object does not meet the [validation rules](/api-integrations/api-endpoints-security-layer/advanced-techniques-for-get-requesting), it will not be saved. Validations are crucial to ensure that only data meeting specified criteria is stored.
{% endhint %}

{% hint style="info" %}
**Hint**: you can build an online calculator for your app using that form with a synchronous result processing.
{% endhint %}


# Embedding Pages

Each page can be:

* used as a single page (e.g. survey form)
* embedded into other web-site.

Click embed page and copy the URL or the whole `iframe` code:

![](/files/-MN4kuC4khIb5lNLx0Li)


# Understanding Directual Scenarios

Unleash the power of back-end logic 🚀

{% hint style="info" %}
101-course: [The basics of Directual scenarios](https://www.directual.com/101-crash-course/scenarios-the-basics)
{% endhint %}

Directual scenarios are a crucial component of your application's back-end logic. Scenarios manage and process data based on specified criteria.

{% embed url="<https://youtu.be/MdfWP8NhMpQ>" %}

Directual scenarios process objects in a **target data structure**. You can select the **target data structure** when configuring the **START step:**

![Choosing target structure for scenario](/files/-M6iF-WWjFIOGbrtioct)

## Types of triggers

Directual scenarios offer two primary types of triggers:

* [Real-time, event-](/scenarios/event-driven-triggers)[based](/scenarios/event-driven-triggers) (as a reaction to an [event](/scenarios/principles-of-scenarios/directual-event-model)): These scenarios respond to specific events, such as user actions or changes in the application's data. For instance, a scenario can be triggered when a user submits a form.
* [Schedule](/scenarios/schedule-triggers)[d](/scenarios/schedule-triggers): Scheduled scenarios run at predefined times or intervals, providing you with the ability to automate tasks. For example, you can schedule a scenario to execute every Monday at 4:40 p.m. GMT.

Additional trigger options:

* [Calling scenarios from within scenarios](/scenarios/editing-scenarios/system-steps/link-scenario-step): You can execute one scenario from another using the Link scenario step, creating complex workflows by chaining scenarios together
* [Synchronous scenarios](/scenarios/synchronic-scenarios-1): These scenarios respond to synchronous POST requests and process incoming data, returning it as an API response

{% hint style="info" %}
Note that a single scenario has the capability to incorporate all available trigger types!
{% endhint %}

![An example of a scenario which combines different trigger types](/files/-MBcj43lX0YFMwPiehBr)


# Directual Event Model

In Directual, events are categorized into two types:

* **Object Creation Event:** This event occurs when a new object is created within the platform
* **Object Modification Event:** This event takes place when an existing object is altered or modified

{% hint style="info" %}
Note that object creation and editing can sometimes occur without triggering an event!
{% endhint %}

**Methods of Event Generation:**

You can generate events in the following ways:

## Manual event generation via platform UI:

When creating or editing an object, you have two options:

* **Save** (which generates an event)
* **Save without an event** (which saves the changes without generating an event, maintaining silence)

![](/files/-M6iH-7W5W1z_b_krK53)

## **Event generation via scenarios**

Within scenarios, there are steps that can create or edit objects. You have the option to enable or disable event generation using the "Generate an event" toggle. By default, this option is turned off.

![](/files/-M6iIJFRDmwDjGPLv1jr)

## **Event generation via API and Integrations**

All the integrations [from the catalog](https://www.directual.com/integrations) create/edit objects and always generate an event.

POST-requests via [API](/api-integrations/api-endpoints-security-layer) generate an event by default. You can turn off event generation using the following query parameter: `changeEventForRealChangedData=false`, which is `true` by default.


# Event-Based Triggers

## Real-time: New Objects

* This scenario reacts to events associated with new objects being created
* When you choose this trigger, the scenario will activate in response to the creation of new objects within your data structure

![](/files/-M6iPbOV6QpYhX7e8YYJ)

## **Real-time: Changed Objects**

* This scenario responds to [events ](/scenarios/principles-of-scenarios/directual-event-model)related to objects that have been modified or edited
* You have the flexibility to select specific fields that must change to trigger the scenario. If no fields are selected, the scenario will trigger in response to any change made to the object

![](/files/-M6iPhLKX4W90VEG5vXg)

![](/files/-M6iO6kc6Hn_rCJLziSW)

###


# Scheduled Triggers

In Directual, scheduled scenarios can be configured with two main settings:

**Autorun**:

* No autorun
* Regular (for example, every hour, or every 15 minutes)
* Daily (every day in a certain time; for example every day at 9:00 a.m.)
* Weekly (for example, on Mondays and on Saturdays at 1 p. m.)
* Monthly (for example on 10th and 20th)
* [Raw CRON configuration](/scenarios/schedule-triggers/cron-format)

**Filter for objects:**

{% hint style="info" %}
**Note**: The choice of the time zone in the advanced section of app settings affects the autorun settings.
{% endhint %}

![](/files/-M6iTKNVddD74QTaph9J)

## Filter for objects

Standard [comparison component](/template-system/comparison-operators). Here you can filter the objects from the target structure before they are processed by the scenario. You can use [Global constants](/scenarios/using-variables/global-constants) here as well.

![](/files/-M6iWxRd26XUIDKBbZfC)

## Manual scenario run

If your scenario uses the 'By CRON (scheduled)' trigger and is not stopped, you can also run it manually by clicking **Other actions → Push all objects in scenario**

![](/files/-MGch5d-GfKu79g9YNnx)


# Cron Format

Manage regular scenario runs professionally

The Cron format is a simple yet powerful and flexible way to define the time and frequency of various actions. It consists of five fields, each specifying a different time unit.

The following graph explains it in detail:

```
* * * * * 
| | | | | 
| | | | |
| | | | +---- Day of the Week   (range: 1-7, 1 standing for Monday)
| | | +------ Month of the Year (range: 1-12)
| | +-------- Day of the Month  (range: 1-31)
| +---------- Hour              (range: 0-23)
+------------ Minute            (range: 0-59)
```

Any of these 5 fields may be an asterisk `*`. This would mean the entire range of possible values, i.e. each minute, each hour, etc.

Any field may contain a list of values separated by commas, (e.g. `1,3,7`) or a range of values (two integers separated by a hyphen, e.g. `1-5`).

After an asterisk `*` or a range of values, you can use character `/` to specify that values are repeated over and over with a certain interval between them. For example, you can write `0-23/2` in Hour field to specify that some action should be performed every two hours (it will have the same effect as `0,2,4,6,8,10,12,14,16,18,20,22`); value `*/4` in Minute field means that the action should be performed every 4 minutes, `1-30/3` means the same as `1,4,7,10,13,16,19,22,25,28`.




---

[Next Page](/llms-full.txt/1)

