Files
awesome-copilot/skills/create-web-form/references/form-data-handling.md
jhauga 27149859a4 Add skill to create web forms
Add skill to create web forms
2026-02-09 19:22:54 -05:00

17 KiB

Form Data Handling Reference


Section 1: Sending and Retrieving Form Data

Source: https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Forms/Sending_and_retrieving_form_data

Overview

Once form data is validated on the client-side, it is ready to submit. This section covers what happens when a user submits a form, where the data goes, and how to handle it on the server.

Client/Server Architecture

The web uses a basic client/server architecture:

  • Client (web browser) sends an HTTP request to a server.
  • Server (Apache, Nginx, IIS, Tomcat) responds using the same protocol.
  • HTML forms are a user-friendly way to configure HTTP requests for sending data.

On the Client Side: Defining How to Send Data

The <form> element controls how data is sent. Two critical attributes are action and method.

The action Attribute

The action attribute defines where form data gets sent. It must be a valid relative or absolute URL.

Absolute URL:

<form action="https://www.example.com">...</form>

Relative URL (same origin):

<form action="/somewhere_else">...</form>

Same page (no attributes or empty action):

<form>...</form>

Security Note: Use HTTPS (secure HTTP) to encrypt data. If a secure form posts to an insecure HTTP URL, browsers display a security warning.

The method Attribute

Two main HTTP methods transmit form data: GET and POST.

GET Method

  • Used by browsers to ask the server to send back a resource.
  • Data is appended to the URL as query parameters.
  • Browser sends an empty body.

Example Form:

<form action="https://www.example.com/greet" method="GET">
  <div>
    <label for="say">What greeting do you want to say?</label>
    <input name="say" id="say" value="Hi" />
  </div>
  <div>
    <label for="to">Who do you want to say it to?</label>
    <input name="to" id="to" value="Mom" />
  </div>
  <div>
    <button>Send my greetings</button>
  </div>
</form>

Result URL: https://www.example.com/greet?say=Hi&to=Mom

HTTP Request:

GET /?say=Hi&to=Mom HTTP/2.0
Host: example.com

When to use: Reading data, non-sensitive information.

POST Method

  • Used to send data that the server should process.
  • Data is included in the HTTP request body, not the URL.
  • More secure for sensitive data (passwords, etc.).

Example Form:

<form action="https://www.example.com/greet" method="POST">
  <div>
    <label for="say">What greeting do you want to say?</label>
    <input name="say" id="say" value="Hi" />
  </div>
  <div>
    <label for="to">Who do you want to say it to?</label>
    <input name="to" id="to" value="Mom" />
  </div>
  <div>
    <button>Send my greetings</button>
  </div>
</form>

HTTP Request:

POST / HTTP/2.0
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 13

say=Hi&to=Mom

When to use: Sensitive data, large amounts of data, modifying server state.

Viewing HTTP Requests in Browser DevTools

  1. Open Developer Tools (F12).
  2. Select the "Network" tab.
  3. Select "All" to see all requests.
  4. Click the request in the "Name" tab.
  5. View "Request" (Firefox) or "Payload" (Chrome/Edge).

On the Server Side: Retrieving the Data

The server receives data as a string parsed into key/value pairs. Access method depends on the server platform.

Example: Raw PHP

<?php
  // Access POST data
  $say = htmlspecialchars($_POST["say"]);
  $to  = htmlspecialchars($_POST["to"]);

  // Access GET data
  // $say = htmlspecialchars($_GET["say"]);

  echo $say, " ", $to;
?>

Output: Hi Mom

Example: Python with Flask

from flask import Flask, render_template, request

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def form():
    return render_template('form.html')

@app.route('/hello', methods=['GET', 'POST'])
def hello():
    return render_template('greeting.html',
                         say=request.form['say'],
                         to=request.form['to'])

if __name__ == "__main__":
    app.run()

Other Server-Side Frameworks

Language Frameworks
Python Django, Flask, web2py, py4web
Node.js Express, Next.js, Nuxt, Remix
PHP Laravel, Laminas, Symfony
Ruby Ruby On Rails
Java Spring Boot

A Special Case: Sending Files

Files are binary data and require special handling. Three steps are needed:

The enctype Attribute

This specifies the Content-Type HTTP header.

  • Default: application/x-www-form-urlencoded
  • For files: multipart/form-data

File Upload Example:

<form
  method="post"
  action="https://example.com/upload"
  enctype="multipart/form-data">
  <div>
    <label for="file">Choose a file</label>
    <input type="file" id="file" name="myFile" />
  </div>
  <div>
    <button>Send the file</button>
  </div>
</form>

Requirements:

  • Set method to POST (file content cannot go in a URL).
  • Set enctype to multipart/form-data.
  • Include one or more <input type="file"> controls.

Note: Servers can limit file and request sizes to prevent abuse.

Security Considerations

Be Paranoid: Never Trust Your Users

All incoming data must be checked and sanitized:

  1. Escape Dangerous Characters -- Watch for executable code patterns (JavaScript, SQL commands). Use server-side escaping functions. Different contexts require different escaping.
  2. Limit Incoming Data -- Only accept what is necessary. Set maximum sizes for requests.
  3. Sandbox Uploaded Files -- Store on a different server. Serve through a different subdomain or domain. Never execute uploaded files directly.

Key Rule: Never trust client-side validation alone -- always validate on the server. Client-side validation can be bypassed; the server has no way to verify what truly happened on the client.

Quick Reference: GET vs POST

Aspect GET POST
Data location Visible in URL as query parameters Hidden in request body
Data size Limited by URL length No inherent limit
Security Not suitable for sensitive data Better for sensitive/large data
Caching Can be cached Not cached
Use case Reading/retrieving data Modifying server state, sending files

Important Notes

  • Form data format: Name/value pairs joined with ampersands (name=value&name2=value2).
  • URL encoding: Special characters are URL-encoded in query parameters.
  • Default form target: Without action, data submits to the current page.
  • Secure protocol: Always use HTTPS for sensitive data.

Section 2: Form Validation

Source: https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Forms/Form_validation

Overview

Client-side form validation helps ensure data entered matches requirements set by form controls. While important for user experience, it must always be paired with server-side validation since client-side validation is easily bypassed by malicious users.

Built-In HTML Validation Attributes

required

Specifies that a form field must be filled before submission.

<input id="choose" name="i-like" required />
  • Input matches :required and :invalid pseudo-classes when empty.
  • For radio buttons, one button in a same-named group must be checked.

minlength and maxlength

Constrains character length for text fields and textareas.

<input type="text" minlength="6" maxlength="6" />
<textarea maxlength="140"></textarea>

min, max, and step

Constrains numeric values and their increments.

<input type="number" min="1" max="10" step="1" />
<input type="date" min="2024-01-01" max="2024-12-31" />

type

Validates against specific formats (email, URL, number, date, etc.).

<input type="email" />
<input type="url" />
<input type="number" />

pattern

Validates against a regular expression.

<input
  type="text"
  pattern="[Bb]anana|[Cc]herry"
  required
/>

Pattern Examples:

Pattern Matches
a Single character 'a'
abc 'a' followed by 'b' followed by 'c'
ab?c 'ac' or 'abc'
ab*c 'ac', 'abc', 'abbbbbc', etc.
a|b 'a' or 'b'

CSS Pseudo-Classes for Validation States

Valid States

input:valid {
  border: 2px solid black;
}

input:user-valid {
  /* Matches after user interaction */
}

input:in-range {
  /* For inputs with min/max */
}

Invalid States

input:invalid {
  border: 2px dashed red;
}

input:user-invalid {
  /* Matches after user interaction */
}

input:out-of-range {
  /* For inputs with min/max */
}

input:required {
  /* Matches required fields */
}

Complete Built-In Validation Example

<form>
  <p>Please complete all required (*) fields.</p>

  <fieldset>
    <legend>Do you have a driver's license? *</legend>
    <input type="radio" required name="driver" id="r1" value="yes" />
    <label for="r1">Yes</label>
    <input type="radio" required name="driver" id="r2" value="no" />
    <label for="r2">No</label>
  </fieldset>

  <p>
    <label for="age">How old are you?</label>
    <input type="number" min="12" max="120" step="1" id="age" name="age" />
  </p>

  <p>
    <label for="fruit">What's your favorite fruit? *</label>
    <input
      type="text"
      id="fruit"
      name="fruit"
      list="fruits"
      required
      pattern="[Bb]anana|[Cc]herry|[Aa]pple"
    />
    <datalist id="fruits">
      <option>Banana</option>
      <option>Cherry</option>
      <option>Apple</option>
    </datalist>
  </p>

  <p>
    <label for="email">Email address:</label>
    <input type="email" id="email" name="email" />
  </p>

  <button>Submit</button>
</form>

Constraint Validation API

The Constraint Validation API provides methods and properties for custom validation logic.

Key Properties

validationMessage -- Returns localized validation error message.

validity -- Returns a ValidityState object with these properties:

Property Description
valid true if element meets all constraints
valueMissing true if required but empty
typeMismatch true if value does not match type (e.g., email)
patternMismatch true if pattern does not match
tooLong true if exceeds maxlength
tooShort true if below minlength
rangeOverflow true if exceeds max
rangeUnderflow true if below min
customError true if custom error set via setCustomValidity()

willValidate -- Boolean, true if element will be validated on form submission.

Key Methods

// Check validity without submitting
element.checkValidity()    // Returns boolean

// Report validity to user
element.reportValidity()   // Shows browser's error message

// Set custom error message
element.setCustomValidity("Custom error text")

// Clear custom error
element.setCustomValidity("")

JavaScript Custom Validation Examples

Basic Custom Error Message

const email = document.getElementById("mail");

email.addEventListener("input", (event) => {
  if (email.validity.typeMismatch) {
    email.setCustomValidity("I am expecting an email address!");
  } else {
    email.setCustomValidity("");
  }
});

Extending Built-In Validation

const email = document.getElementById("mail");

email.addEventListener("input", (event) => {
  // Reset custom validity
  email.setCustomValidity("");

  // Check built-in constraints first
  if (!email.validity.valid) {
    return;
  }

  // Add custom constraint
  if (!email.value.endsWith("@example.com")) {
    email.setCustomValidity("Please enter an email with @example.com domain");
  }
});

Complex Form Validation with Custom Messages

const form = document.querySelector("form");
const email = document.getElementById("mail");
const emailError = document.querySelector("#mail + span.error");

email.addEventListener("input", (event) => {
  if (email.validity.valid) {
    emailError.textContent = "";
    emailError.className = "error";
  } else {
    showError();
  }
});

form.addEventListener("submit", (event) => {
  if (!email.validity.valid) {
    showError();
    event.preventDefault();
  }
});

function showError() {
  if (email.validity.valueMissing) {
    emailError.textContent = "You need to enter an email address.";
  } else if (email.validity.typeMismatch) {
    emailError.textContent = "Entered value needs to be an email address.";
  } else if (email.validity.tooShort) {
    emailError.textContent =
      `Email should be at least ${email.minLength} characters; you entered ${email.value.length}.`;
  }
  emailError.className = "error active";
}

Disabling Built-In Validation with novalidate

Use novalidate on the form to disable the browser's automatic validation while retaining CSS pseudo-classes:

<form novalidate>
  <input type="email" id="mail" required minlength="8" />
  <span class="error" aria-live="polite"></span>
</form>

Manual Validation Without the Constraint API

For custom form controls or complete control over validation:

const form = document.querySelector("form");
const email = document.getElementById("mail");
const error = document.getElementById("error");

const emailRegExp = /^[\w.!#$%&'*+/=?^`{|}~-]+@[a-z\d-]+(?:\.[a-z\d-]+)*$/i;

const isValidEmail = () => {
  return email.value.length !== 0 && emailRegExp.test(email.value);
};

const setEmailClass = (isValid) => {
  email.className = isValid ? "valid" : "invalid";
};

const updateError = (isValid) => {
  if (isValid) {
    error.textContent = "";
    error.removeAttribute("class");
  } else {
    error.textContent = "Please enter a valid email address.";
    error.setAttribute("class", "active");
  }
};

email.addEventListener("input", () => {
  const validity = isValidEmail();
  setEmailClass(validity);
  updateError(validity);
});

form.addEventListener("submit", (event) => {
  event.preventDefault();
  const validity = isValidEmail();
  setEmailClass(validity);
  updateError(validity);
});

Styling Error Messages

/* Invalid field styling */
input:invalid {
  border-color: #990000;
  background-color: #ffdddd;
}

input:focus:invalid {
  outline: none;
}

/* Error message container */
.error {
  width: 100%;
  padding: 0;
  font-size: 80%;
  color: white;
  background-color: #990000;
  border-radius: 0 0 5px 5px;
}

.error.active {
  padding: 0.3em;
}

Accessibility Best Practices

  1. Mark required fields with an asterisk in the label:
    <label for="name">Name *</label>
    
  2. Use aria-live for dynamic error messages:
    <span class="error" aria-live="polite"></span>
    
  3. Provide clear, helpful messages that explain what is expected and how to fix errors.
  4. Avoid relying on color alone to indicate errors.

Validation Summary

Approach Pros Cons
HTML built-in No JavaScript needed, fast Limited customization
Constraint Validation API Modern, integrates with built-in features Requires JavaScript
Fully manual (JS) Complete control over UI and logic More code, must handle everything
  • HTML validation is faster and does not require JavaScript.
  • JavaScript validation provides more customization and control.
  • Always validate server-side -- client-side validation is not secure.
  • Use the Constraint Validation API for modern, built-in functionality.
  • Provide clear error messages and guidance to users.
  • Style validation states with :valid and :invalid pseudo-classes.