Chapter 4.6: Automatic Form Generation — Zero-Code Report Parameters
"You define your table structure once. The platform generates forms, validates input, and builds filter queries automatically. 20 templates means 20 parameter forms — all built for you."
The Problem Form Generation Solves
A Class Roster template needs to be filtered. Maybe the administrator wants only Grade 3 students. Maybe they want a specific co-op. Maybe they want a particular academic term. Maybe all three.
Without form generation, you would need to:
- Create an HTML form manually for each template
- Add input fields for each possible filter
- Write validation logic in JavaScript
- Handle form submission and parameter passing
- Parse parameters on the server
- Build WHERE clauses from the parsed data
Multiply that by 20 templates and you have 120 steps just to make filtering work. And when you add a new field to your table definition, you have to manually update every form that filters on that table.
The Data Publisher platform eliminates this entire category of work through automatic form generation. You define your table structure in domain-config.json. The platform generates the forms, validates the inputs, passes parameters correctly, and builds filter queries — all automatically.
This chapter shows you how to design your table definitions to enable rich, user-friendly report parameter forms with zero additional code.
How It Works: From Table Definition to Working Form
When a user clicks "Generate" on a Class Roster template card in the Reports tab, the platform:
- Looks up the template in
documentTypesto find itsprimaryTableandrelatedTables - Scans the table definitions to find all fields that could be useful filters
- Renders a dialog with dropdowns, checkboxs, date pickers, and text inputs based on field types
- Populates dropdown options by querying actual data from the CSV (or database if connected)
- Validates user selections client-side using the field validation rules from domain-config.json
- Passes parameters to the server as a structured object:
{filters: {CoopID: "1", TermID: "3"}, sort: {field: "ClassName", direction: "asc"}} - Builds SQL WHERE clauses (or CSV filter logic) from the parameters automatically
- Returns filtered data to the document merge engine
You wrote none of this code. You only defined your table structure correctly in domain-config.json.
The Magic is in Field Metadata
Every field in your table definition can include metadata that controls how it appears in generated forms:
{
"name": "CoopID",
"type": "lookup",
"required": true,
"references": "coops",
"displayField": "CoopName",
"description": "The co-op organization",
"filterable": true,
"defaultToFirst": true
}
Let's break down what each property does for form generation:
type — Determines Input Control
The field type maps directly to HTML input controls in generated forms:
| Type | Generated Control | Example Use Case |
|---|---|---|
text |
<input type="text"> |
Names, addresses, notes |
number |
<input type="number"> |
Ages, counts, quantities |
currency |
<input type="number" step="0.01"> |
Prices, fees, balances |
date |
<input type="date"> |
Birth dates, deadlines |
datetime |
<input type="datetime-local"> |
Timestamps, appointments |
select |
<select> with hardcoded options |
T-shirt sizes, states, grades |
lookup |
<select> with dynamic options |
Foreign keys to other tables |
boolean |
<input type="checkbox"> |
Yes/no flags |
email |
<input type="email"> |
Email addresses |
phone |
<input type="tel"> |
Phone numbers |
When the form generator encounters type: "date", it renders an HTML5 date picker with calendar popup. When it sees type: "lookup", it queries the referenced table and builds a dropdown.
references and displayField — Smart Dropdowns
Lookup fields create the most user-friendly form controls:
{
"name": "ClassID",
"type": "lookup",
"references": "classes",
"displayField": "ClassName"
}
This tells the platform:
- references: Query the
classestable to get options - displayField: Show users the
ClassNamefield, not the ID - Behind the scenes, pass the
ClassIDvalue when building filters
The generated dropdown looks like:
┌─────────────────────────────────┐
│ Select a class... │
│ ───────────────────────────── │
│ Math 101 │ ← User sees "Math 101"
│ Science Lab │ but system stores ClassID=5
│ Literature │
│ Art & Music │
└─────────────────────────────────┘
Users navigate by human-readable names. The system tracks by foreign key IDs. No manual option mapping required.
required — Validation Rules
{
"name": "CoopID",
"type": "lookup",
"required": true,
"references": "coops",
"displayField": "CoopName"
}
When required: true, the generated form:
- Shows a red asterisk next to the field label
- Prevents form submission if the field is empty
- Displays validation error: "Co-op is required" if user tries to submit
When required: false:
- Field is optional
- Renders with lighter label text
- Form submits even if field is blank
filterable — Inclusion in Parameter Forms
Not every field makes sense as a filter:
{
"name": "InternalNotes",
"type": "text",
"required": false,
"filterable": false,
"description": "Staff use only"
}
Setting filterable: false excludes this field from report parameter forms entirely. It still exists in the table, it's still available in document merges, but users won't see it as a filter option when generating reports.
By default, foreign keys (type: "lookup") and common filter fields (like dates, dropdowns) are assumed filterable. Text fields with names like "Notes" or "Comments" are assumed not filterable.
defaultToFirst — Smart Defaults
When you have a filter that most users will fill out the same way every time:
{
"name": "CoopID",
"type": "lookup",
"required": true,
"references": "coops",
"displayField": "CoopName",
"defaultToFirst": true
}
The form will pre-select the first option in the dropdown automatically. If there's only one co-op in the system, the field is already filled out when the dialog opens. Users just click Run.
Real-World Example: Class Roster Parameters
Here's how a Class Roster template's filter form is generated from table metadata:
Template Definition
{
"id": 1,
"name": "Class Roster",
"filename": "Template-01-Class-Roster.docx",
"category": "Academic",
"primaryTable": "classes",
"relatedTables": ["enrollments", "students", "families", "teachers", "academic_terms"]
}
Relevant Table Field Definitions
// From the students table
{
"name": "CoopID",
"type": "lookup",
"required": true,
"references": "coops",
"displayField": "CoopName",
"filterable": true,
"defaultToFirst": true
}
// From the classes table
{
"name": "TermID",
"type": "lookup",
"required": true,
"references": "academic_terms",
"displayField": "TermName",
"filterable": true
}
{
"name": "ClassID",
"type": "lookup",
"required": true,
"references": "classes",
"displayField": "ClassName",
"filterable": true
}
{
"name": "TeacherID",
"type": "lookup",
"required": false,
"references": "teachers",
"displayField": "FirstName,LastName",
"filterable": true
}
Generated Parameter Form
The platform automatically creates a dialog with:
┌──────────────────────────────────────────────┐
│ Class Roster Parameters │
├──────────────────────────────────────────────┤
│ │
│ Co-op Organization * │
│ ┌────────────────────────────────────────┐ │
│ │ Westside Homeschool Co-op ▾ │ │ ← Pre-selected (defaultToFirst)
│ └────────────────────────────────────────┘ │
│ │
│ Academic Term * │
│ ┌────────────────────────────────────────┐ │
│ │ Select a term... ▾ │ │
│ │ ───────────────────────────────────── │ │
│ │ Fall 2025 (2025-2026) │ │
│ │ Spring 2025 (2024-2025) │ │
│ └────────────────────────────────────────┘ │
│ │
│ Class │
│ ┌────────────────────────────────────────┐ │
│ │ All Classes ▾ │ │ ← Optional (required: false)
│ │ ───────────────────────────────────── │ │
│ │ Math 101 │ │
│ │ Science Lab │ │
│ │ Literature │ │
│ └────────────────────────────────────────┘ │
│ │
│ Teacher │
│ ┌────────────────────────────────────────┐ │
│ │ All Teachers ▾ │ │
│ │ ───────────────────────────────────── │ │
│ │ Sarah Johnson │ │
│ │ Michael Martinez │ │
│ └────────────────────────────────────────┘ │
│ │
│ Sort By │
│ ┌────────────────────────────────────────┐ │
│ │ Class Name ▾ │ │
│ │ ───────────────────────────────────── │ │
│ │ Student Last Name │ │
│ │ Student Grade │ │
│ └────────────────────────────────────────┘ │
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │ Cancel │ │Generate │ │
│ └─────────┘ └─────────┘ │
└──────────────────────────────────────────────┘
Every element in this form was generated automatically from the table metadata. No HTML written. No JavaScript validation coded. No dropdown population logic.
The user selects "Spring 2025", "Math 101", and "All Teachers", then clicks Run. The platform passes:
{
filters: {
CoopID: "1",
TermID: "3",
ClassID: "5",
TeacherID: null // "All Teachers" = no filter
},
sort: {
field: "ClassName",
direction: "asc"
}
}
The server receives this structured object and builds the appropriate query. The template merges with only the filtered records.
Advanced Field Configuration
Multi-Column Display Fields
When you want dropdowns to show first name + last name instead of just one field:
{
"name": "TeacherID",
"type": "lookup",
"references": "teachers",
"displayField": "FirstName,LastName",
"displayFormat": "{FirstName} {LastName}"
}
Generates dropdown options like:
Sarah Johnson
Michael Martinez
Emily Chen
Instead of:
Sarah
Michael
Emily
Conditional Options
Some dropdowns should only show relevant options based on other selections:
{
"name": "ClassID",
"type": "lookup",
"references": "classes",
"displayField": "ClassName",
"filterBy": "TermID",
"description": "Filters classes to only those in the selected term"
}
With filterBy, when a user selects "Spring 2025" for TermID, the ClassID dropdown automatically updates to show only classes from Spring 2025, not all classes in the database.
This is cascade filtering, handled automatically by the form generator.
Date Range Inputs
Financial reports often need date ranges:
{
"name": "StartDate",
"type": "date",
"required": true,
"defaultValue": "firstDayOfMonth",
"description": "Report start date"
},
{
"name": "EndDate",
"type": "date",
"required": true,
"defaultValue": "today",
"description": "Report end date"
}
The form generator recognizes fields named StartDate/EndDate or FromDate/ToDate and renders them as a date range:
Start Date * End Date *
┌──────────┐ ┌──────────┐
│ 02/01/26 │ │ 02/23/26 │
└──────────┘ └──────────┘
Checkbox Groups for Multiple Selection
Sometimes users need to select multiple values:
{
"name": "Grades",
"type": "multiselect",
"options": ["Pre-K", "K", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"],
"required": false,
"description": "Select one or more grade levels"
}
Renders as checkboxes:
Grades
□ Pre-K □ K □ 1 □ 2
□ 3 □ 4 □ 5 □ 6
□ 7 □ 8 □ 9 □ 10
□ 11 □ 12
User selects 3, 4, and 5. The filter becomes WHERE Grade IN ('3', '4', '5').
Designing for Form Generation
When designing your table fields, think about how they'll appear in parameter forms:
1. Foreign Keys Should Always Be Lookups
❌ Bad — stores ID as plain text:
{
"name": "ClassID",
"type": "text",
"required": true
}
✅ Good — enables smart dropdown:
{
"name": "ClassID",
"type": "lookup",
"required": true,
"references": "classes",
"displayField": "ClassName"
}
2. Use Descriptive Field Names
Field names become form labels automatically:
❌ Bad — unclear abbreviation:
{
"name": "TID",
"type": "lookup",
"references": "teachers"
}
Form label: "TID" ← Users confused
✅ Good — self-documenting:
{
"name": "TeacherID",
"type": "lookup",
"references": "teachers"
}
Form label: "Teacher" ← Users understand (ID suffix stripped automatically)
3. Provide Options for Select Fields
Select fields need defined options or they render as text inputs:
❌ Bad — no options defined:
{
"name": "ShirtSize",
"type": "select"
}
Renders as: <input type="text"> ← Users can type anything
✅ Good — explicit options:
{
"name": "ShirtSize",
"type": "select",
"options": ["YS", "YM", "YL", "S", "M", "L", "XL"]
}
Renders as: <select> with 7 valid options ← Constrained input
4. Make Required Fields Actually Required
If a filter is essential for the report to make sense, mark it required:
{
"name": "CoopID",
"type": "lookup",
"required": true, // ← User must select co-op
"references": "coops",
"displayField": "CoopName"
}
If it's an optional refinement, leave it optional:
{
"name": "TeacherID",
"type": "lookup",
"required": false, // ← "All Teachers" is valid
"references": "teachers",
"displayField": "FirstName,LastName"
}
The Reports Group and Template Cards
In Chapter 4.5, we introduced the Reports table group with an empty tables array:
{
"id": "reports",
"name": "Reports",
"icon": "📊",
"description": "Document templates and reports",
"tables": []
}
When users click the Reports group, the platform doesn't show tables — it shows document template cards. Each card displays:
- Template name
- Category badge
- Brief description
- Tables used (primaryTable + relatedTables)
- "Run" button
Clicking "Run" opens the automatically-generated parameter form we've been discussing.
This is the complete user journey:
- Browse: User sees Reports group (📊), clicks it
- Select: User sees 20 template cards in a grid, clicks "Class Roster"
- Preview: Modal shows full template metadata (category, description, tables, use case)
- Run: User clicks "Run" button
- Configure: Auto-generated form appears with filters pre-set to sensible defaults
- Refine: User adjusts filters (select specific term, class, teacher)
- Execute: User clicks "Run" — dialog closes, template loads into Word, data merges
Steps 4-7 all happen because you defined your table structure correctly in domain-config.json. No additional code.
Validation and Error Handling
The form generator includes built-in validation:
Client-Side Validation
Before submitting the form, the platform validates:
- Required fields: All fields with
required: truemust have values - Data types: Date fields must be valid dates, number fields must be numeric
- Range constraints: If field definition includes
min/max, values must fall within range - References: Lookup fields must select an option from the dropdown (not free text)
If validation fails, the form shows inline error messages:
Academic Term *
┌────────────────────────────────┐
│ Select a term... ▾ │
└────────────────────────────────┘
⚠️ Academic term is required
Server-Side Validation
After client submission, the server validates:
- Foreign key integrity: Selected IDs exist in referenced tables
- Data consistency: Cross-field validation (StartDate < EndDate)
- Authorization: User has permission to access requested data
If server validation fails, the dialog stays open and displays the server error message.
Testing Your Form Generation
Before publishing your domain, test the generated forms:
Test 1: All Templates Have Parameters
For each template in documentTypes, click "Run" and verify:
- [ ] Parameter form appears
- [ ] All expected filter fields present
- [ ] Required fields marked with asterisks
- [ ] Dropdown options populate correctly
- [ ] Default values make sense
- [ ] Validation works (try submitting empty required fields)
Test 2: Filter Logic Produces Correct Results
Generate the same template with different filter combinations and verify data changes appropriately:
- No filters: Should return all records
- Single filter: Only records matching that filter
- Multiple filters: AND logic (all conditions must match)
- Sort options: Order changes correctly
Test 3: Edge Cases
- What if a referenced table is empty? (e.g., no teachers in system yet)
- What if all filter fields are optional and user leaves them blank?
- What if lookup table has 100+ options? (pagination, search)
The platform handles all these cases automatically, but test them to ensure your table definitions support the edge cases gracefully.
Migration: Adding Form-Friendly Metadata to Existing Tables
If you have an existing domain without rich field metadata:
// Before — minimal metadata
{
"name": "ClassID",
"type": "text"
}
Enhance to enable form generation:
// After — rich metadata
{
"name": "ClassID",
"type": "lookup",
"required": false,
"references": "classes",
"displayField": "ClassName",
"filterable": true,
"description": "Filter to specific class or leave blank for all classes"
}
This is a non-breaking change as long as you don't change field names or make previously-optional fields required. Increment your domain version from 1.3.0 to 1.4.0 and republish.
Existing users will see enhanced parameter forms the next time they generate reports.
Summary
Automatic form generation is what makes the Data Publisher platform scalable for domain developers. Define your table structure once with rich metadata. The platform handles:
- ✅ Rendering appropriate input controls based on field types
- ✅ Populating dropdowns from related tables
- ✅ Validating user input client-side and server-side
- ✅ Passing structured parameters to the merge engine
- ✅ Building filter queries automatically
Your 20 templates get 20 parameter forms with zero additional code.
The key is comprehensive field metadata in domain-config.json:
- Use
type: "lookup"for all foreign keys - Specify
referencesanddisplayFieldfor smart dropdowns - Mark essential filters as
required: true - Set
filterable: falsefor fields that don't make sense as filters - Provide
optionsarrays for select fields - Use descriptive field names that become clear form labels
With table groups (Chapter 4.5) providing intuitive navigation and automatic form generation (this chapter) providing zero-code parameter collection, your domain is ready for template generation — that's Chapter 5.