This is one of the most confusing JavaScript errors you’ll encounter with HTML forms. The error appears when you try to programmatically submit a form, but the submit method has been mysteriously replaced by something else.

What makes this error particularly baffling is that forms clearly should have a submit() function, and MDN documentation confirms it exists. So why isn’t it working?

The answer reveals a quirky HTML behavior that catches even experienced developers off guard.

The Problem

“form.submit is not a function” occurs when an HTML form element has its submit method masked by a form control with the name “submit”. This is a blocking error that stops script execution when it occurs.

Here’s the classic scenario that triggers this error:


<!-- The problematic HTML -->
<form id="contact-form" action="/contact" method="POST">
  <input type="text" name="name" placeholder="Your Name" />
  <input type="email" name="email" placeholder="Your Email" />
  <input type="submit" name="submit" value="Submit" />
</form>

<script>
// The JavaScript that breaks
const form = document.getElementById('contact-form');
form.addEventListener('submit', function(e) {
  e.preventDefault();
  form.submit(); // TypeError: form.submit is not a function
});
</script>

Key insight: The input with name="submit" overwrites the form’s submit() method, replacing it with a reference to the input element itself.

Understanding the Root Cause

“form.submit is not a function” stems from a specific HTML behavior where named form controls become properties of the form element:

1. Form Control Name Masking

The core issue: Any form control with a name attribute becomes accessible as a property on the form element, potentially overriding built-in methods and properties. This can mask not only form-specific methods like submit() and reset(), but also standard HTML element properties like id, className, classList, and others.

How to identify: Check if your form has inputs named “submit”, “reset”, “action”, “method”, or other form property names. Also watch for inputs named after standard HTML properties like “id”, “className”, “style”, “classList”.

2. Multiple Submit Buttons

Modern forms often have multiple submit buttons with different names, and conflicts can arise when they’re named after form methods.

How to identify: Forms with buttons named “submit”, “reset”, or other method names cause this issue.

3. Dynamic Form Generation

JavaScript-generated forms or forms created by libraries might inadvertently create name conflicts.

How to identify: Errors occur in dynamically created forms or when using form generation libraries.

4. Framework Component Integration

React, Vue, or Angular components that manage form state might encounter this when integrating with traditional form submission.

How to identify: Errors happen in framework components that mix controlled and uncontrolled form patterns.

5. Form Validation Library Conflicts

Form validation libraries that add hidden inputs or modify form structure can introduce naming conflicts.

How to identify: Errors appear after integrating form validation or enhancement libraries.

How to Fix “form.submit is not a function”

Quick Troubleshooting Checklist

  • Verify form inputs don’t use HTML reserved names like “submit”, “reset”, “id”, “className”
  • Use HTMLFormElement.prototype.submit.call() for guaranteed access to submit method
  • Implement proper form naming conventions to avoid conflicts
  • Test form submission in both manual and programmatic scenarios

If basic naming fixes don’t resolve the issue, follow these systematic approaches:

Step 1: Identify Name Conflicts

Check your form for inputs that mask built-in form properties:


// Debug form property conflicts by checking if properties mask prototype members
function debugFormConflicts(form) {
  console.log('Form debug report:');
  const conflicts = [];

  // Get all enumerable properties on the form element
  for (const prop in form) {
    const value = form[prop];

    // Check if this property exists on the HTMLFormElement prototype
    const prototypeValue = HTMLFormElement.prototype[prop];

    // If it exists on prototype but current value is an HTML element, it's masked
    if (prototypeValue !== undefined && value instanceof HTMLElement) {
      conflicts.push({
        property: prop,
        element: value,
        originalType: typeof prototypeValue
      });

      console.warn(`⚠️  Property "${prop}" is masked by form control:`, value);
      console.log(`   Original ${prop} was:`, typeof prototypeValue, prototypeValue);
    }
  }

  if (conflicts.length === 0) {
    console.log('✅ No property conflicts detected');
  } else {
    console.log(`Found ${conflicts.length} property conflicts`);
  }

  return conflicts;
}

// Usage
const form = document.getElementById('my-form');
debugFormConflicts(form);

Step 2: Fix Name Conflicts

Rename conflicting form controls to avoid masking built-in methods:


<!-- Before: Problematic naming -->
<form id="contact-form">
  <input type="text" name="name" />
  <input type="submit" name="submit" value="Submit" />
  <input type="reset" name="reset" value="Reset" />
</form>

<!-- After: Safe naming -->
<form id="contact-form">
  <input type="text" name="name" />
  <input type="submit" name="submit-btn" value="Submit" />
  <input type="reset" name="reset-btn" value="Reset" />
</form>

<!-- Alternative: Use value-based identification -->
<form id="contact-form">
  <input type="text" name="name" />
  <input type="submit" value="Submit" />
  <input type="submit" name="action" value="save-draft" />
  <input type="submit" name="action" value="publish" />
</form>

Step 3: Use Prototype Methods for Guaranteed Access

When you can’t change the form HTML, access methods directly from the prototype:


// Guaranteed access to form methods
function safeFormSubmit(form) {
  // Call submit method directly from prototype
  HTMLFormElement.prototype.submit.call(form);
}

function safeFormReset(form) {
  // Call reset method directly from prototype
  HTMLFormElement.prototype.reset.call(form);
}

// Safe property access
function getFormAttribute(form, attributeName) {
  return form.getAttribute(attributeName);
}

// Usage in event handlers
document.getElementById('contact-form').addEventListener('submit', function(e) {
  e.preventDefault();

  // Perform validation
  if (validateForm(this)) {
    safeFormSubmit(this);
  }
});

// Alternative using bind
const form = document.getElementById('contact-form');
const safeSubmit = HTMLFormElement.prototype.submit.bind(form);

// Later...
safeSubmit(); // Always works regardless of naming conflicts

Step 4: Implement Modern Form Submission Patterns

Use modern approaches that avoid the submit method entirely:


// Modern form submission with fetch
async function handleFormSubmission(form) {
  const formData = new FormData(form);

  try {
    const response = await fetch(form.action || '/submit', {
      method: form.method || 'POST',
      body: formData
    });

    if (response.ok) {
      const result = await response.json();
      console.log('Form submitted successfully:', result);
      // Handle success (redirect, show message, etc.)
    } else {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
  } catch (error) {
    console.error('Form submission failed:', error);
    // Handle error (show message, etc.)
  }
}

// Event handler that avoids form.submit()
document.getElementById('contact-form').addEventListener('submit', async function(e) {
  e.preventDefault();

  // Validate form
  if (!this.checkValidity()) {
    this.reportValidity();
    return;
  }

  // Submit using modern approach
  await handleFormSubmission(this);
});

Step 5: Monitor Form Submission Errors

Track form-related errors to identify naming conflicts and submission issues. Error monitoring services like TrackJS can help identify when users encounter form submission problems, allowing you to proactively fix naming conflicts before they impact user experience.

Monitor for patterns like increased form submission errors after HTML changes, which might indicate new naming conflicts introduced during development.

When to Ignore This Error

“form.submit is not a function” should never be ignored as it directly impacts user ability to submit forms. However, consider the context:

  • Legacy code: Inherited systems with established naming conventions
  • Third-party widgets: External form components with their own naming schemes
  • CMS limitations: Content management systems that generate conflicting names

Always investigate when you see:

  • Form submission failures: Users unable to complete critical actions
  • Checkout process errors: E-commerce form submission problems
  • Contact form issues: Support and lead generation impact
  • Registration failures: User onboarding disruption

Summary

“form.submit is not a function” reveals a quirky HTML behavior where form controls can mask built-in form methods. The solution involves either renaming conflicting inputs or using prototype-based method access for guaranteed functionality.

The best prevention is establishing naming conventions that avoid reserved form property names like “submit”, “reset”, “action”, and “method”. For existing forms with conflicts, use HTMLFormElement.prototype.submit.call(form) for reliable submission.

Remember: This error showcases why understanding HTML’s implicit behaviors is crucial for robust web development. Form elements in HTML are more dynamic than they initially appear, and named controls become properties that can override built-in functionality.

TrackJS is the easy way to monitor your JavaScript applications and fix production errors. TrackJS is provides detailed error monitoring and alerting to developers around the world at companies like 3M, Tidal, IKEA, Venmo, Allbirds, and Frontend Masters. TrackJS catches millions of errors from users everyday. Let's start catching yours.

Protect your JavaScript