How to Build an AI-Powered Form Builder with DHTMLX

Modern web applications commonly come with the capabilities to collect, validate, and process user data on-the-fly. This functional arsenal is implemented via dynamic forms that serve as comminitation channels between users and apps. Despite the widespread use of forms, it is often a tricky task to create this UI element and tailor it to a specific workflow. That’s where DHTMLX JavaScript Form usually comes to the rescue. This widget from our Suite library is equipped with a range of controls for configuring forms of any scale and complexity. But there is always a room for improvement. What if you could simply describe the form in plain text and see it generated instantly, ready for use and further editing?

In this tutorial, you will learn how to build an AI-powered application that transforms natural language descriptions into fully functional web forms by combining the DHTMLX Form widget with the OpenAI API.

Understanding the App’s Architecture and UI

Before we dive into coding, let’s take a quick look at how the app is structured. This will help you understand how the frontend, backend, and AI service interact to generate forms dynamically. We’ll also walk through the user interface layout so you know what the final result should look like and how each part contributes to the workflow.

The app’s architecture includes 3 main parts:

  1. Frontend – relies on DHTMLX widgets to build the interface, Socket.IO to communicate with the backend, and the Monaco Editor to provide a code editor for viewing and modifying the AI-generated JSON configuration.
  2. Backend – based on the Node.js server with Express and Socket.IO that brokers communication between the frontend and the AI service.
  3. AI Service – employs an OpenAI-compatible API that processes user prompts and returns structured JSON describing the form layout.

To follow along, you’ll need Node.js installed and an API key from OpenAI (or another API-compatible provider).

Our objective is to build an application with the following UI sections:

  • Control Panel (left side): contains the Prompt Interface (a textarea for describing the desired form, along with suggestions and a “Generate Form” button).
  • Output Panel (right side): displays the Live Preview at the top, showing the generated form, and the Code View underneath, where the JSON configuration is shown. From here, you can update the code, re-render the form, or copy the configuration.

Here’s a quick preview of what the final app looks like:
generating forms with AI and DHTMLXOpen live demo >

Please note: This tutorial focuses on the core logic of connecting the DHTMLX Form widget with an AI service. For simplicity, the HTML structure and CSS styling have been kept minimal. The complete, fully styled source code for the final application is available on GitHub.

Let’s dive into how it’s built.

Step 1: Setting Up the Frontend with DHTMLX

Let’s start by putting together the web page. You need an HTML file (index.html) to define the structure and a JavaScript file (app.js) for the logic.

The HTML file needs containers for four main UI sections: the control panel, prompt suggestions, the form preview, and the JSON editor.

<!DOCTYPE html>
<html>
<head>
    <title>DHTMLX Form + AI Generation</title>
    <link rel="stylesheet" href="https://cdn.dhtmlx.com/suite/edge/suite.css">
    <script src="https://cdn.dhtmlx.com/suite/edge/suite.js"></script>
    <script src="/socket.io/socket.io.js"></script>
    <!-- Monaco Editor for the JSON view -->
    <script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.34.1/min/vs/loader.js"></script>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <h1>AI Form Generator</h1>
    <div id="prompt-container"></div>
    <div id="suggestions-pills" class="suggestions-container"></div>
    <div id="form-preview-container"></div>
    <div id="json-editor-container"></div>
    <script src="app.js"></script>
</body>
</html>

Next, in app.js, you initialize the control panel, the prompt suggestions, and the Monaco Editor. This gets the entire UI ready before you start adding the AI logic.

document.addEventListener("DOMContentLoaded", function () {
    // --- Global variables ---
    let generatedForm = null;
    let jsonEditor = null;

    // --- DHTMLX Widgets Initialization ---
    const controlForm = new dhx.Form("prompt-container", {
        rows: [
            { type: "textarea", name: "prompt", placeholder: "Describe your form..." },
            { type: "button", name: "generate", text: "✨ Generate Form" }
        ]
    });

    // --- Prompt Suggestions Logic ---
    const promptSuggestions = [
        "row: input, selectbox, selectbox",
        "name, email, message textarea, submit button",
        "row 1: input, selectbox\nrow 2: datepicker, colorpicker"
    ];
    const suggestionsContainer = document.getElementById("suggestions-pills");
    promptSuggestions.forEach(promptText => {
        const pill = document.createElement('button');
        pill.className = 'prompt-pill';
        pill.textContent = promptText;
        pill.addEventListener('click', () => {
            controlForm.getItem("prompt").setValue(promptText);
        });
        suggestionsContainer.appendChild(pill);
    });

    // --- Monaco Editor Initialization ---
    require.config({ paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.34.1/min/vs' } });
    require(['vs/editor/editor.main'], function () {
        jsonEditor = monaco.editor.create(document.getElementById("json-editor-container"), {
            value: JSON.stringify({ message: "Your generated form config will appear here..." }, null, 4), language: 'json',
            theme: 'vs-dark',
            automaticLayout: true,
            readOnly: false
        });
    });

    // More logic will follow in the next steps
});

At this point, the complete UI is rendered, but it doesn’t do anything yet. The form preview and JSON editor are empty placeholders. In the next step, you’ll bring it to life by connecting it to our AI backend.

Step 2: Interfacing with the AI

Now that the UI is ready, it is time to make it work. When a user clicks the “Generate Form” button, you need to send their text description to the backend. The backend will forward it to the AI, and upon receiving the response, the form preview and the JSON editor will be populated with data.

The Socket.IO library is perfect for this, as it keeps a persistent connection open for real-time communication.

Here’s the logic to add to app.js inside the DOMContentLoaded event listener:

// Connect to the backend
const socket = io();
const generateButton = controlForm.getItem("generate");

// Handle the Generate button click
generateButton.events.on('click', () => {
    const userRequest = controlForm.getValue().prompt.trim();
    if (!userRequest) { /* ... error handling ... */ return; }

    // ... show loader and disable button ...

    // Clean up any previously generated form
    if (generatedForm) {
        generatedForm.destructor();
        generatedForm = null;
    }

    // Send the prompt to the backend
    socket.emit('generate_form', userRequest, (response) => {
        // ... hide loader and enable button ...

        if (response.success) {
            try {
                const formConfig = JSON.parse(response.json);
                const prettyJson = JSON.stringify(formConfig, null, 4);

                // Update the JSON editor
                if (jsonEditor) jsonEditor.setValue(prettyJson);

                // Render the generated form in the preview panel
                generatedForm = new dhx.Form("form-preview-container", formConfig);

            } catch (e) { /* ... error handling ... */ }
        } else { /* ... error handling ... */ }
    });
});

What’s happening in this piece of code:

  • socket.emit() sends the user’s prompt to the backend.
  • The callback function waits for the server to respond.
  • Once the response arrives, we parse the JSON and use it to update both the Monaco Editor (with jsonEditor.setValue()) and the form preview (with new dhx.Form()).

Step 3: Adding Manual Interactivity

To give users more control, you can add “Copy” and “Update” buttons. This will allow them to copy the generated JSON or manually edit it and see their changes reflected in the form preview instantly.

First, add the buttons to your index.html file, right after the JSON editor container:

<!-- index.html -->
...
    <div id="json-editor-container"></div>
    <div class="editor-controls">
        <button id="update-btn">Update</button>
        <button id="copy-btn">Copy</button>
    </div>
...

The next step is to add the following JavaScript to your app.js file to make these buttons work:

// --- UI elements ---
const copyButton = document.getElementById("copy-btn");
const updateButton = document.getElementById("update-btn");

// --- Update from Editor Logic ---
updateButton.addEventListener('click', () => {
    if (!jsonEditor) return;
    const editedCode = jsonEditor.getValue();
    try {
        const formConfig = JSON.parse(editedCode);
        if (generatedForm) generatedForm.destructor();
        generatedForm = new dhx.Form("form-preview-container", formConfig);
    } catch (e) { /* ... error handling ... */ }
});

// --- Copy-to-Clipboard Logic ---
copyButton.addEventListener('click', () => {
    if (!jsonEditor) return;
    const codeToCopy = jsonEditor.getValue();
    navigator.clipboard.writeText(codeToCopy).then(() => {
        copyButton.textContent = "Copied!";
        setTimeout(() => { copyButton.textContent = "Copy"; }, 2000);
    }).catch(err => { /* ... error handling ... */ });
});

Now, users can manually fine-tune the AI’s output and update the form preview, or copy the configuration to use elsewhere.

Step 4: Instructing the AI (Backend)

The backend’s main responsibility is to receive a user’s text prompt from the frontend, combine it with a set of master instructions, and send it to the AI for processing.

Crafting the System Prompt

The most critical piece of this entire application is the system prompt, a detailed set of instructions that tells the AI how to behave. It’s stored in backend/formBuilderPrompt.js. Without a strong system prompt, the AI would return inconsistent or invalid JSON.

Our prompt engineering strategy involves several key areas:

  1. Core Rules: Enforce the fundamental output format (e.g., JSON only, required properties).
  2. Layout Logic: Translate simple user keywords like “row” into the correct JSON structure for horizontal or vertical layouts.
  3. UI Best Practices: Handle special cases, like how to arrange a “Submit” and “Cancel” button pair.
  4. Examples: Provide clear, concrete examples of user requests and the desired JSON output, which is one of the most effective ways to guide the model.

Here is a snippet of the most important rules from formBuilderPrompt.js:

// --- CORE RULES ---
1.  **JSON ONLY:** You MUST return ONLY a valid JSON object. No extra text.
2.  **TYPE & UNIQUE NAME:** Every control object MUST have a \`type\` and a unique \`name\`.
3.  **DEFAULT LABELS:** Provide sensible default English labels.
4.  **LABEL POSITION:** All controls with labels MUST have \`labelPosition: "top"\`.
//....other rules

Connecting to the AI Service

Now, let’s look at how this system prompt is used in the backend/server.js file.

// backend/server.js
io.on('connection', (socket) => {
    socket.on('generate_form', async (userRequest, callback) => {
        // 1. Retrieve the AI instructions (system prompt)
        const systemPrompt = generateFormBuilderPrompt();

        // 2. Prepare the full request for the AI
        const messages = [
            { role: "system", content: systemPrompt },
            { role: "user", content: userRequest }
        ];

        try {
            // 3. Call the AI with a low temperature for consistent results
            const res = await openai.chat.completions.create({
                model: 'gpt-4.1-nano',
                messages: messages,
                temperature: 0.2,
                response_format: { "type": "json_object" }
            });

            // 4. Send the AI's response back to the client
            callback({ success: true, json: res.choices[0].message.content });

        } catch (e) {
            console.error("AI request failed:", e);
            callback({ success: false, error: e.message || "Failed to generate form from AI." });
        }
    });
});

How it works:

  • You import the generateFormBuilderPrompt function and call it to get the master instructions.
  • You create a messages array, placing our instructions in the “system” role and the user’s request in the “user” role.
  • You set temperature: 0.2 to make the AI’s output more predictable and less creative, which is crucial for generating valid configuration files.
  • You use response_format: { “type”: “json_object” } to explicitly request JSON output, a feature supported by newer OpenAI models.

By following this approach, your integration with the AI becomes reliable and predictable, providing a seamless experience for users when generating dynamic DHTMLX-based forms.

Wrapping Up

By combining DHTMLX Form with the OpenAI API, you can turn plain text into ready-to-use web forms in just a few clicks. This tutorial showed how to set up the UI, connect it to an AI backend, and even refine the generated JSON with live editing. This pattern of structured prompting is a powerful technique for integrating AI into other applications that require reliable, machine-readable output. Learn more about DHTMLX Form and other Suite widgets in the documentation and download a free 30-day trial version to try any of them in your use-case scenarios.

Advance your web development with DHTMLX

Gantt chart
Event calendar
Diagram library
30+ other JS components