Form Submission and Action Attributes

1

Form Submission and Action Attributes

▶

Every HTML form exists for one fundamental purpose: to collect information from a user and deliver that information somewhere useful. The mechanics of that delivery are controlled by a small set of attributes placed directly on the <form> element itself. Understanding these attributes — action, method, and enctype — is essential because they determine where the data goes, how it travels, and in what format it is packaged before it leaves the browser. Getting any one of these wrong can mean data that never arrives, data that is corrupted, or — most seriously — sensitive information exposed to the world.

The action attribute tells the browser the destination URL for the submitted form data. Its value must be either a fully qualified URL (such as https://example.com/process.php) or a relative path (such as /submit or ../handler) that resolves to a server-side script or API endpoint capable of receiving and processing the incoming data. When the form is submitted, the browser navigates to that address and delivers the form's contents according to the rules set by method and enctype.

A subtle but important behavior: if you omit the action attribute entirely, the browser resubmits the form to the current page's URL. This is sometimes intentional — for example, in a simple PHP page that both renders and processes its own form, or in a single-page application where JavaScript intercepts the submission before it reaches the network. In those cases the omission is deliberate, but in most production contexts you should always provide an explicit action value to make the intended destination unambiguous.

<!-- Submits to an absolute URL -->
<form action="https://api.example.com/register" method="post">

<!-- Submits to a relative path on the same server -->
<form action="/contact/send" method="post">

<!-- Omitting action reloads the current page -->
<form method="post">

The method attribute specifies the HTTP verb the browser uses when it delivers the data. HTML forms natively support only two values: get and post. Other HTTP verbs such as PUT, PATCH, or DELETE are not natively supported by HTML forms and require JavaScript (for example, using the fetch API or XMLHttpRequest) or a server-side convention such as a hidden field named _method. When method is omitted, virtually all browsers default to GET, so the default is rarely desirable for forms that change data on the server.

The GET method appends the form's name-value pairs directly onto the URL as a query string. When you submit a search form, for instance, the browser constructs a URL like https://example.com/search?query=html+forms&page=1 and navigates there. Because the data lives in the URL, GET submissions are bookmarkable and shareable — a major advantage for search results or filtered lists. They are also idempotent, meaning that making the same request multiple times has no additional side effects beyond the first request. Searching for something twice does not create two records; it simply returns the same results.

However, GET carries serious constraints. Because data is encoded directly in the URL, it is visible in the browser's address bar, stored in browser history, captured in server access logs, and potentially leaked via the HTTP Referer header. This makes GET wholly inappropriate for passwords, credit card numbers, social security numbers, or any other sensitive information. Additionally, URLs have length limits — browsers and web servers impose practical ceilings, historically around 2,000 characters for maximum compatibility, though modern browsers allow more. This limits how much data a GET form can realistically carry.

<!-- GET appends data to the URL -->
<form action="/search" method="get">
  <input type="text" name="query" value="HTML forms">
  <input type="text" name="page"  value="1">
  <button type="submit">Search</button>
</form>

<!-- Resulting URL after submission: -->
<!-- /search?query=HTML+forms&page=1 -->

The POST method sends the form data in the HTTP request body rather than in the URL. The address bar shows only the action URL — none of the form values appear there. This has several important implications. First, POST is far more appropriate for sensitive data: passwords and personal details are not stored in browser history or server access logs in the same way. Second, because the request body is not subject to URL length limits, POST can carry arbitrarily large amounts of data, including entire file uploads. Third, POST requests are not idempotent — submitting the same form twice may create two database records, process two payments, or send two emails. This is why browsers show a warning dialog ("Resend form data?") when a user tries to refresh a page that was reached via a POST submission.

<!-- POST sends data in the request body -->
<form action="/login" method="post">
  <input type="text"     name="username">
  <input type="password" name="password">
  <button type="submit">Log In</button>
</form>

<!-- The URL after submission remains simply: /login -->
<!-- Credentials travel invisibly in the HTTP body   -->

A useful side-by-side comparison of the two methods:

Characteristic GET POST
Data location URL query string HTTP request body
Visibility Visible in address bar, history, logs Not visible in URL
Bookmarkable Yes No
Data size limit Limited by URL length (~2,000 chars practical) No practical limit
Idempotent Yes No
Suitable for sensitive data No Yes (with HTTPS)
File uploads No Yes (with multipart/form-data)
Typical use cases Search, filtering, navigation Login, registration, data creation

Before the browser can send data anywhere, it must first decide which form controls to include. Two rules govern this. First, only controls that have a name attribute are serialized; a field without a name is silently omitted from the submission, no matter what value it holds. Second, any field with the disabled attribute is also excluded from the submitted data. This second rule is particularly important to remember: disabling a field does not just prevent the user from changing it — it actively prevents the value from being submitted at all. If you want to display a read-only value that does get submitted, use the readonly attribute instead.

<form action="/profile" method="post">

  <!-- Submitted: name="username" is present -->
  <input type="text" name="username" value="alice">

  <!-- NOT submitted: no name attribute -->
  <input type="text" value="this is ignored">

  <!-- NOT submitted: field is disabled -->
  <input type="text" name="role" value="admin" disabled>

  <!-- Submitted and uneditable: readonly keeps the value -->
  <input type="text" name="account_id" value="42" readonly>

  <button type="submit">Save</button>
</form>

After determining which fields to include, the browser serializes the data — converts it into a specific text format — using rules determined by the enctype attribute. The enctype attribute on the <form> element specifies that encoding format, and it accepts three values.

The default value, application/x-www-form-urlencoded, is appropriate for virtually all text-based forms. In this encoding, field names and values are URL-encoded: spaces become + signs, and special characters become percent-encoded sequences (for example, & becomes %26). Each pair is joined with =, and pairs are separated by &. This is the same format you see in a GET query string, but when used with POST, it travels in the request body instead.

<!-- Default enctype — suitable for text fields -->
<form action="/register" method="post"
      enctype="application/x-www-form-urlencoded">
  <input type="text"  name="first_name" value="Jane">
  <input type="email" name="email"      value="jane@example.com">
  <button type="submit">Register</button>
</form>

<!-- Request body sent to server: -->
<!-- first_name=Jane&email=jane%40example.com -->

The second value, multipart/form-data, is mandatory whenever your form includes a file input (<input type="file">). This encoding divides the request body into separate "parts," each with its own headers, separated by a randomly generated boundary string. Binary file data is transmitted within its own part without any URL encoding that would corrupt it. If you use a file input but forget to set enctype="multipart/form-data", the browser will submit only the filename as a string — the actual file content will never reach the server.

<!-- multipart/form-data required for file uploads -->
<form action="/upload" method="post"
      enctype="multipart/form-data">
  <input type="text" name="title">
  <input type="file" name="avatar">
  <button type="submit">Upload</button>
</form>

<!-- Simplified view of the HTTP request body: -->
<!--
--boundary123
Content-Disposition: form-data; name="title"

My Profile Photo
--boundary123
Content-Disposition: form-data; name="avatar"; filename="photo.jpg"
Content-Type: image/jpeg

[binary file data here]
--boundary123--
-->

The third value, text/plain, sends data as plain, unencoded text with each name-value pair on its own line and no special escaping of characters. It was introduced as a debugging aid and is essentially never used in real applications. Because no encoding is applied, the server cannot reliably parse the data — a value containing a newline character could break the format entirely. Avoid it in any production context.

<!-- text/plain — avoid in production -->
<form action="/debug" method="post" enctype="text/plain">
  <input type="text" name="note" value="line1
line2">
  <button type="submit">Send</button>
</form>

<!-- Request body (unencoded, unpredictable with special chars): -->
<!-- note=line1                                                   -->
<!-- line2                                                        -->

Putting all three attributes together, a well-constructed form for a user registration page with a profile picture upload would look like this:

<form action="/api/users/create"
      method="post"
      enctype="multipart/form-data">

  <input type="text"     name="username"   required>
  <input type="email"    name="email"      required>
  <input type="password" name="password"   required>
  <input type="file"     name="avatar"     accept="image/*">

  <!-- This field has no name — it will NOT be submitted -->
  <input type="text" placeholder="Search (UI only)">

  <!-- This field is disabled — it will NOT be submitted -->
  <input type="text" name="referral_code" value="PROMO50" disabled>

  <button type="submit">Create Account</button>
</form>

The relationship between these three attributes can be summarized simply: action answers where to send the data, method answers how to send it (appended to the URL vs. in the body), and enctype answers in what format to package it before sending. Choosing them correctly — POST with HTTPS for anything sensitive, multipart/form-data whenever files are involved, and GET only for idempotent read-only queries — is one of the most fundamental skills in building reliable, secure web forms.

NotesConsider pairing this topic with a live demonstration using browser DevTools (Network tab) to show students the actual HTTP request headers and body for both GET and POST submissions, with each enctype. Seeing the raw network traffic makes the abstract descriptions of URL encoding and multipart boundaries concrete and memorable.