Skip to content

Vue

Every example on this page is a self-contained Vue <script setup> component using fetch - no BootForm SDK to install. 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 unchanged inside a template - no reactive state needed at all:

vue
<template>
  <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>
</template>

AJAX submission

Intercept the submit, post as JSON, and track status with ref() instead of navigating away:

vue
<script setup>
import { ref } from 'vue'

const status = ref('idle') // 'idle' | 'sending' | 'sent' | 'error'
const error = ref(null)

async function handleSubmit(event) {
  status.value = 'sending'

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

  if (result.ok) {
    event.target.reset()
    status.value = 'sent'
  } else {
    error.value = result.error
    status.value = 'error'
  }
}
</script>

<template>
  <form action="https://f.bootform.com/{form_id}" method="POST" @submit.prevent="handleSubmit">
    <input name="email" type="email" required />
    <textarea name="message"></textarea>
    <button type="submit" :disabled="status === 'sending'">
      {{ status === 'sending' ? 'Sending…' : 'Send' }}
    </button>
    <p v-if="status === 'sent'">Thanks - your message was sent.</p>
    <p v-if="status === 'error'" role="alert">{{ error }}</p>
  </form>
</template>

File upload

FormData picks up a file input the same way as text fields - nothing Vue-specific needed beyond the state you're already tracking above:

vue
<script setup>
import { ref } from 'vue'

const status = ref('idle')

async function handleSubmit(event) {
  status.value = 'sending'

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

<template>
  <form action="https://f.bootform.com/{form_id}" method="POST" @submit.prevent="handleSubmit">
    <input name="email" type="email" required />
    <input name="resume" type="file" required />
    <button type="submit" :disabled="status === 'sending'">Send</button>
  </form>
</template>

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

Moderated content feed

Load a form's approved submissions on mount, and let visitors react - the feed endpoint is public and unauthenticated, no API key involved:

vue
<script setup>
import { onMounted, ref } from 'vue'

const props = defineProps<{ formId: string }>()
const items = ref([])

async function loadFeed() {
  const response = await fetch(`https://f.bootform.com/forms/${props.formId}/feed?per_page=20`)
  const { items: fetched } = await response.json()
  items.value = fetched
}

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

onMounted(loadFeed)
</script>

<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      <p>{{ item.fields.message }}</p>
      <button @click="react(item.id, 'up')">👍 {{ item.reactions.up }}</button>
      <button @click="react(item.id, 'down')">👎 {{ item.reactions.down }}</button>
    </li>
  </ul>
</template>

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