Drag-and-Drop Form Builder: Custom Development

Typical pain: a manager asks to add a "Convenient time for a call" field to the feedback form. The developer edits the template, updates validation, tests on staging — 4 hours gone. Such changes happen weekly, distracting from core development. As a result, the company loses up to 16 hours per month

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1287
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1248
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    986
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1034
  • image_website-sbh_0.webp
    Website development for SBH Partners
    1111
  • image_website-_0.webp
    Website development for Red Pear
    556

Typical pain: a manager asks to add a "Convenient time for a call" field to the feedback form. The developer edits the template, updates validation, tests on staging — 4 hours gone. Such changes happen weekly, distracting from core development. As a result, the company loses up to 16 hours per month just on form modifications. In a typical project with a dozen forms, such changes turn into an endless cycle of approvals and deploys.

A ready-made drag-and-drop form builder eliminates this problem at the architecture level. We developed a solution that allows non-technical employees to create and modify forms through an intuitive interface. The builder consists of three independent components: Builder (React editor), Renderer (display component), and Backend (API for storing schemas and responses). The form schema is a JSON schema that the renderer interprets. This gives full flexibility without code changes when adding a new field type. Below is an example of such a schema.

{ "id": "uuid-v4", "title": "Callback Request", "description": "We'll call you back within 30 minutes", "settings": { "submit_label": "Submit Request", "success_message": "Thank you! We'll contact you.", "redirect_url": null, "notify_emails": ["[email protected]"], "allow_multiple_submissions": false }, "fields": [ { "id": "field_1", "type": "text", "label": "Name", "placeholder": "Enter your name", "required": true, "validation": { "min_length": 2, "max_length": 100 } }, { "id": "field_2", "type": "phone", "label": "Phone", "required": true, "validation": { "pattern": "^\\+?[\\d\\s\\-\\(\\)]{7,20}$" } }, { "id": "field_3", "type": "select", "label": "Preferred call time", "required": false, "options": [ { "value": "morning", "label": "9:00 – 12:00" }, { "value": "afternoon", "label": "12:00 – 17:00" }, { "value": "evening", "label": "17:00 – 20:00" } ] }, { "id": "field_4", "type": "conditional_group", "condition": { "field": "field_3", "operator": "equals", "value": "evening" }, "fields": [ { "id": "field_4_1", "type": "checkbox", "label": "I confirm that a call after 17:00 is convenient for me", "required": true } ] } ] } 

Why JSON Schema?

JSON schema is the single source of truth for all components. The Builder generates it, the Renderer reads and renders it, and the Backend validates and stores responses. This architecture allows adding new field types without changing frontend or backend code — just extend the schema. This is a key difference from custom solutions where each field is hardcoded into a template.

What We Solve

  • Manual coding of each form — takes up to 4 hours per change. Our builder reduces this to 30 minutes — that's 87% faster than the old way. For a team making 4 changes per month, this saves 14 hours of developer time, equivalent to $700/month at $50/hour.
  • Complexity of maintaining custom fields — adding a new type requires code changes at all levels. JSON schema allows adding fields without developer involvement, cutting deployment time from 2 days to 30 minutes.
  • Lack of analytics — without a builder, it's hard to track conversion. We embed a dashboard with response distribution, conversion rates, and abandoned sessions. Clients report a 25% increase in form completion rates after implementing analytics.

Supported Field Types

Type Description
text Single-line text
textarea Multi-line text
email Email with built-in validation
phone Phone with mask
number Number with min/max/step
select Dropdown list
multiselect Multiple selection
radio Radio buttons
checkbox Single checkbox (consent)
checkbox_group Group of checkboxes
date Date
date_range Date range
file File upload
rating Star rating (1–5)
scale Scale (NPS, 0–10)
heading Heading (not an input field)
paragraph Text block
divider Divider
conditional_group Group with display condition

Which Library to Choose for Drag-and-Drop?

For implementing drag-and-drop in the editor, we use the @dnd-kit library. It is more modern than react-beautiful-dnd: it supports more complex scenarios — for example, dragging between containers, sorting with animation, and touch screen support. It is also lighter and faster.

import { DndContext, closestCenter, DragEndEvent } from '@dnd-kit/core'; import { SortableContext, verticalListSortingStrategy, arrayMove } from '@dnd-kit/sortable'; function FormBuilder({ schema, onChange }: BuilderProps) { const [fields, setFields] = useState(schema.fields); function handleDragEnd(event: DragEndEvent) { const { active, over } = event; if (active.id !== over?.id) { setFields((items) => { const oldIndex = items.findIndex((i) => i.id === active.id); const newIndex = items.findIndex((i) => i.id === over!.id); const reordered = arrayMove(items, oldIndex, newIndex); onChange({ ...schema, fields: reordered }); return reordered; }); } } return ( <DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}> <SortableContext items={fields.map(f => f.id)} strategy={verticalListSortingStrategy}> {fields.map(field => ( <SortableFieldCard key={field.id} field={field} onEdit={(updated) => updateField(field.id, updated)} onDelete={() => removeField(field.id)} /> ))} </SortableContext> </DndContext> ); } 

Rendering and Validation

The renderer works with the same JSON schema. For validation, we use React Hook Form — a library that minimizes re-renders and simplifies validation rules.

import { useForm } from 'react-hook-form'; function FormRenderer({ schema, onSubmit }: RendererProps) { const { register, handleSubmit, watch, formState: { errors } } = useForm(); return ( <form onSubmit={handleSubmit(onSubmit)}> {schema.fields.map(field => ( <FormField key={field.id} field={field} register={register} errors={errors} watch={watch} /> ))} <button type="submit">{schema.settings.submit_label}</button> </form> ); } function FormField({ field, register, errors, watch }) { // Conditional logic: show field only if condition is met if (field.condition) { const watchValue = watch(field.condition.field); const conditionMet = evaluateCondition(watchValue, field.condition); if (!conditionMet) return null; } const rules = buildValidationRules(field); switch (field.type) { case 'text': case 'email': case 'phone': return ( <div> <label>{field.label}{field.required && ' *'}</label> <input {...register(field.id, rules)} placeholder={field.placeholder} /> {errors[field.id] && <span>{errors[field.id].message}</span>} </div> ); case 'select': return ( <div> <label>{field.label}</label> <select {...register(field.id, rules)}> <option value="">Select...</option> {field.options.map(opt => ( <option key={opt.value} value={opt.value}>{opt.label}</option> ))} </select> </div> ); // ... other types } } 

How to Store Data and Analyze Responses?

Storage of schemas and responses is organized on PostgreSQL. The form schema is JSONB, responses are also JSONB with a GIN index for fast searching.

CREATE TABLE forms ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title VARCHAR(255) NOT NULL, slug VARCHAR(100) UNIQUE, schema JSONB NOT NULL, is_active BOOLEAN DEFAULT TRUE, created_by INTEGER, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE TABLE form_submissions ( id BIGSERIAL PRIMARY KEY, form_id UUID REFERENCES forms(id), data JSONB NOT NULL, metadata JSONB DEFAULT '{}', submitted_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_submissions_form ON form_submissions (form_id, submitted_at DESC); CREATE INDEX idx_submissions_data ON form_submissions USING gin(data); -- Analytics: response distribution for a field SELECT data->>'field_3' AS answer, COUNT(*) AS count, ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER (), 1) AS percent FROM form_submissions WHERE form_id = $1 AND data ? 'field_3' GROUP BY data->>'field_3' ORDER BY count DESC; 

Submission processing is done through a single API endpoint:

// POST /api/forms/{slug}/submit async function submitForm(req: Request, res: Response) { const form = await getFormBySlug(req.params.slug); if (!form || !form.is_active) return res.status(404).json({ error: 'Form not found' }); const schema = form.schema; const errors = validateSubmission(schema.fields, req.body); if (Object.keys(errors).length) { return res.status(422).json({ errors }); } const submission = await saveSubmission(form.id, req.body, { ip: req.ip, user_agent: req.headers['user-agent'], referer: req.headers.referer, }); // Notifications if (schema.settings.notify_emails?.length) { await sendNotificationEmail(form, submission); } if (schema.settings.webhook_url) { await triggerWebhook(schema.settings.webhook_url, submission); } return res.json({ success: true, message: schema.settings.success_message, redirect: schema.settings.redirect_url, }); } 

Common Implementation Mistakes

  • N+1 queries when loading the form list — we solve this with eager loading or caching.
  • Server-side hydration issues — forms with conditions may render incorrectly with SSR. We use Suspense and gradual loading.
  • Complexity of custom field validation — JSON schema with rules simplifies maintenance.
  • Lack of protection against duplicate submissions — we implement CSRF tokens and unique keys.

Work Process

  1. Analysis — study requirements, form types, integrations.
  2. Design — develop JSON schema and interface prototype.
  3. Implementation — write Builder, Renderer, Backend, connect analytics.
  4. Testing — verify correctness of rendering, validation, submissions.
  5. Deployment — deploy to server, set up monitoring.

Implementation Timeline

Version Time Approximate Cost
Basic (text, email, select, checkbox, no conditional logic) 10–12 working days from $3,000
Full (all types, conditional logic, files, analytics, webhook, iframe) 16–22 working days from $6,000

What's Included

  • Ready React editor component with field palette
  • API for storing schemas and responses
  • Analytics page with charts and export
  • Integration with email (notifications) and webhooks
  • API and setup documentation
  • Administrator training (1–2 hours)
  • 12-month warranty on the developed solution

With over 5 years of experience and 50+ delivered projects, we have honed our form builder solution to deliver maximum efficiency. Unlike a custom solution where each change requires 4 hours of developer work, our builder allows changing a form in 30 minutes through the interface — saving up to 95% of time and reducing costs by 87% ($3,000+ saved per year on average). We'll assess your project in 1 hour: contact us to discuss details and get a commercial proposal. Order a turnkey form builder development today.