Skip to content

Vanilla JS

Every example on this page uses plain DOM APIs and fetch - no library, no build step. Jump to just the one you need from the outline on the right, or read top to bottom for the full picture.

Basic submission

The plain HTML case - works with JavaScript disabled entirely:

html
<form action="https://f.bootform.com/{form_id}" method="POST">
  <input name="email" type="email" required />
  <textarea name="message"></textarea>
  <button type="submit">Send</button>
</form>

AJAX submission

Intercept the submit, post the same data as JSON, and update the page in place instead of navigating away:

html
<form id="contact-form" action="https://f.bootform.com/{form_id}" method="POST">
  <input name="email" type="email" required />
  <textarea name="message"></textarea>
  <button type="submit">Send</button>
</form>
<p id="status" role="status"></p>

<script>
  const form = document.getElementById('contact-form')
  const status = document.getElementById('status')

  form.addEventListener('submit', async (event) => {
    event.preventDefault()
    status.textContent = 'Sending…'

    const response = await fetch(form.action, {
      method: 'POST',
      headers: { Accept: 'application/json' },
      body: new FormData(form),
    })
    const result = await response.json()

    if (result.ok) {
      form.reset()
      status.textContent = 'Thanks - your message was sent.'
    } else {
      status.textContent = result.error
    }
  })
</script>

File upload

FormData handles files the same way as text fields - just append the file, don't set Content-Type yourself (the browser sets the multipart boundary):

html
<form id="upload-form" action="https://f.bootform.com/{form_id}" method="POST">
  <input name="email" type="email" required />
  <input name="resume" type="file" required />
  <button type="submit">Send</button>
</form>

<script>
  document.getElementById('upload-form').addEventListener('submit', async (event) => {
    event.preventDefault()
    const form = event.target

    const response = await fetch(form.action, {
      method: 'POST',
      headers: { Accept: 'application/json' },
      body: new FormData(form), // includes the selected file automatically
    })
    const result = await response.json()
    console.log(result.ok ? 'Uploaded' : result.error)
  })
</script>

A file upload requires the form to be claimed first - 422 FORM_NOT_CLAIMED otherwise. See Building your form.

Moderated content feed

Fetch a form's approved submissions and let visitors react, with no dashboard/API key involved - this endpoint is public and unauthenticated by design:

html
<ul id="feed"></ul>

<script>
  async function loadFeed() {
    const response = await fetch('https://f.bootform.com/forms/{form_id}/feed?per_page=20')
    const { items } = await response.json()

    const list = document.getElementById('feed')
    list.innerHTML = items
      .map(
        (item) => `
        <li data-id="${item.id}">
          <p>${item.fields.message}</p>
          <button onclick="react('${item.id}', 'up')">👍 ${item.reactions.up}</button>
          <button onclick="react('${item.id}', 'down')">👎 ${item.reactions.down}</button>
        </li>
      `,
      )
      .join('')
  }

  async function react(submissionId, type) {
    await fetch(`https://f.bootform.com/forms/{form_id}/feed/${submissionId}/react`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ type }),
    })
    loadFeed() // refetch to show the updated count
  }

  loadFeed()
</script>

See Moderated content feed for the report endpoint, pagination, and the hosted-widget alternative if you'd rather not write this yourself.