Creating dynamic forms that users can design at runtime—dragging fields, configuring validation, and previewing behaviour—gives product teams huge flexibility. Such a system lets business users create surveys, work orders, onboarding flows, or admin screens without developer changes. In this article you will learn a practical, production-ready approach to build a fully dynamic form builder using Angular (frontend), ASP.NET Core (backend), and SQL Server (metadata store). The guide covers architecture, data model, UX patterns, example code, and operational best practices.

Target audience: beginner → expert web developers (especially full-stack .NET + Angular engineers).

What is a Dynamic Form Builder?

A dynamic form builder provides:

High-level architecture

User (Designer)  <->  Angular Form Builder UI  <->  ASP.NET Core API  <->  SQL Server (Form + Field metadata)
                                                          |
                                                          v
                                                 Submission Store / Blob (files)

Key components

Flowchart

Designer opens Builder
       ↓
Drag & drop fields on canvas
       ↓
Configure field properties + validation
       ↓
Preview form (client-side)
       ↓
Publish → API saves form JSON + version
       ↓
Users submit runtime form → API validates & stores submission

Workflow (smaller header)

Design -> Preview -> Publish -> Render at runtime -> Submit -> Validate -> Store

ER diagram (smaller header)

+-------------------+     +----------------------+     +----------------------+
| Forms             | 1 - *| Fields               | 1 - *| ValidationRules      |
+-------------------+     +----------------------+     +----------------------+
| FormId (PK)       |     | FieldId (PK)        |     | RuleId (PK)          |
| Name              |     | FormId (FK)         |     | FieldId (FK)         |
| Version           |     | Type (text, select) |     | RuleType (regex,...) |
| IsPublished       |     | Label               |     | Expression           |
| CreatedOn         |     | Key (jsonKey)       |     | ErrorMessage         |
+-------------------+     | Options (JSON)      |     +----------------------+
                          | Order               |
                          | Conditional (JSON)  |
                          +----------------------+

+----------------------+
| Submissions          |
+----------------------+
| SubmissionId (PK)    |
| FormId (FK)          |
| Data (JSON)          |
| CreatedOn            |
+----------------------+

Architecture diagram (Visio-style, smaller header)

[ Angular Builder ]  <---HTTP--->  [ ASP.NET Core API ]  <---EF Core---> [ SQL Server ]
     |  (Drag/Drop)                        |                             |
     |  (Preview)                           |                             |
     v                                      v                             v
[ Angular Renderer ]                   [ Validation Engine ]         [ Blob Storage (files) ]
     | (runtime form)                        |                             |
     v                                      v                             v
[ User Submits ] --------------------> [ Submission API ] --------------> [ Submissions Table ]

Sequence diagram (smaller header)

Designer -> AngularBuilder: open form
AngularBuilder -> User: shows canvas
Designer -> AngularBuilder: add field, set props
AngularBuilder -> PreviewComponent: render preview
Designer -> AngularBuilder: Publish
AngularBuilder -> API: POST /forms
API -> SQLServer: save form + version
User -> AngularRenderer: GET /forms/{id}
AngularRenderer -> API: GET form definition
API -> SQLServer: fetch form JSON
AngularRenderer -> User: render form
User -> AngularRenderer: submit form
AngularRenderer -> API: POST /forms/{id}/submit
API -> ValidationEngine: validate submission
ValidationEngine -> API: returns result
API -> SQLServer: store submission
API -> AngularRenderer: return success/fail

Data model and JSON format

Store a form as JSON in DB (plus normalized fields if needed). Example simplified JSON:

{
  "formId": "guid",
  "name": "Work Order",
  "version": 3,
  "fields": [
    {
      "id": "f1",
      "key": "customerName",
      "type": "text",
      "label": "Customer Name",
      "placeholder": "Enter full name",
      "required": true,
      "order": 1
    },
    {
      "id": "f2",
      "key": "priority",
      "type": "select",
      "label": "Priority",
      "options": ["Low","Medium","High"],
      "required": true,
      "order": 2
    },
    {
      "id": "f3",
      "key": "dueDate",
      "type": "date",
      "label": "Due Date",
      "required": false,
      "conditional": {
         "showIf": { "priority": "High" }
      }
    }
  ]
}

Keep ValidationRules either embedded in field JSON or as a separate table for complex rules and versioning.

Frontend: Angular builder and renderer

Libraries & concepts

Builder: key parts

  1. Palette: list of field types to drag.

  2. Canvas: droppable container that maintains an ordered array of field metadata.

  3. Property Panel: edit selected field’s properties (label, placeholder, validations, options).

  4. Preview: client-side runtime that converts current metadata to a FormGroup and shows it.

Example: DragDrop + saving metadata (simplified)

// builder.component.ts (simplified)
fields: FormField[] = [];

drop(event: CdkDragDrop<FormField[]>) {
  moveItemInArray(this.fields, event.previousIndex, event.currentIndex);
}

addField(type: string) {
  const newField: FormField = {
    id: uuid(),
    key: 'field_' + Date.now(),
    type,
    label: 'New ' + type,
    required: false,
    order: this.fields.length + 1
  };
  this.fields.push(newField);
}

save() {
  const payload = { name: this.formName, fields: this.fields };
  this.http.post('/api/forms', payload).subscribe();
}

Renderer: build reactive form from metadata

buildForm(fields: FormField[]) {
  const group: { [k: string]: FormControl } = {};
  fields.forEach(f => {
    const validators = [];
    if (f.required) validators.push(Validators.required);
    if (f.pattern) validators.push(Validators.pattern(f.pattern));
    if (f.min != null) validators.push(Validators.min(f.min));
    group[f.key] = new FormControl(f.default || null, validators);
  });
  this.form = new FormGroup(group);
}

For conditional visibility, use valueChanges subscriptions to show/hide controls and to add/remove validators dynamically.

Backend: ASP.NET Core design

API endpoints (suggested)

Persistence

Server-side validation

Always validate submissions server-side using the same rules defined in metadata. Implement a ValidationEngine that:

Example validation pseudo

foreach (var field in form.Fields) {
    var value = submission.Data[field.Key];
    if (field.Required && value is null) AddError(field.Key, "Required");
    if (field.Pattern != null && !Regex.IsMatch(value, field.Pattern)) AddError(field.Key, "Invalid format");
    // cross-field: if A == X then B required...
}

Validation rules — simple to advanced

Versioning and publishing

File uploads & large data

Caching & performance

Security considerations

UX tips for builder and preview

Analytics and monitoring

Example: Minimal EF Core models

public class Form {
  public Guid FormId { get; set; }
  public string Name { get; set; }
  public int Version { get; set; }
  public bool IsPublished { get; set; }
  public string FormJson { get; set; } // JSON columns
  public DateTime CreatedOn { get; set; }
}

public class Submission {
  public Guid SubmissionId { get; set; }
  public Guid FormId { get; set; }
  public string DataJson { get; set; }
  public DateTime CreatedOn { get; set; }
}

Use System.Text.Json for fast JSON (or Newtonsoft.Json if needed).

Testing strategy

Roadmap: advanced features

Conclusion

A fully dynamic form builder is a very useful product feature for enterprise applications. With Angular CDK drag-drop, reactive forms, a robust backend validation engine, and a flexible JSON metadata model in SQL Server, you can deliver a builder that is both powerful and safe. Key points to remember: