Upload fields
Two families: auto fields upload immediately and store what the API returns; the plain dropzone keeps the File for you to send. This playground uploads to a mock endpoint unless NUXT_PUBLIC_UPLOAD_API is set.
Source in the kit: components/Form/InputUpload*/ · helpers/uploadCore.ts
What the upload API must return
The auto fields POST multipart/form-data with the file under file ( bodyKey ) and read these keys from the JSON answer. Each key can be renamed, or be a path such as data.url , with responseURL , responsePath , responseName , responseSize , responseID .
{
"url": "https://cdn.example.com/files/contract.pdf",
"path": "/files/contract.pdf",
"name": "contract.pdf",
"size": 48213,
"id": "f_01HZX"
}One file, uploaded on pick
examples/fields/Upload.vueคลิกเพื่อเลือกไฟล์
หรือ ลากและวางที่นี่
PDF or image, up to 5 MB
<template>
<FormFields :options="fields" />
</template>
<script lang="ts" setup>
const form = useForm()
// Where uploads go: baseURL + headers. In an app this usually adds the Authorization header.
const requestOptions = useRequestOptions().getFile()
const fields = createFormFields(() => [
{
type: INPUT_TYPES.UPLOAD_DROPZONE_AUTO,
props: {
name: 'contract',
label: 'UPLOAD_DROPZONE_AUTO',
placeholder: 'PDF or image, up to 5 MB',
requestOptions,
uploadPathURL: '/uploads',
// Extensions, MIME types or wildcards
accept: '.pdf,image/*',
// In KB
maxSize: 5120,
},
on: {
success: (file: IFileValue) => console.log('uploaded', file),
},
},
])
// The file is uploaded as soon as it is picked; the form value is the API's answer, not the File:
// { url, path, name, size, id }
defineExpose({
debug: computed(() => form.values),
})
</script>composables/useRequestOptions.ts
import type { AxiosRequestConfig } from 'axios'
export const useRequestOptions = () => {
const config = useRuntimeConfig()
// The mock API is served by this same app, so its address is wherever the page came from.
// A fixed http://localhost:3000 only works on the machine running the dev server.
const origin = useRequestURL().origin
const getMock = (): Omit<AxiosRequestConfig, 'baseURL'> & {
baseURL: string
} => {
return {
baseURL: String(config.public.baseAPIMock || `${origin}/api/mock`),
}
}
const getFile = (): Omit<AxiosRequestConfig, 'baseURL'> & {
baseURL: string
} => {
// Set NUXT_PUBLIC_UPLOAD_API and NUXT_PUBLIC_UPLOAD_TOKEN to upload to a real API; falls back to the local mock
const uploadAPI = config.public.uploadAPI as string
const uploadToken = config.public.uploadToken as string
if (!uploadAPI) return getMock()
return {
baseURL: uploadAPI,
headers: uploadToken
? {
Authorization: `Bearer ${uploadToken}`,
}
: {},
}
}
const getDefault = (): Omit<AxiosRequestConfig, 'baseURL'> & {
baseURL: string
} => {
return {
baseURL: String(config.public.baseAPI),
}
}
return {
getDefault,
getMock,
getFile,
}
}Several files
examples/fields/UploadMultiple.vueคลิกเพื่อเลือกไฟล์
หรือ ลากและวางที่นี่
Up to 3 files, 5 MB each
existing-photo.png
20.48 KB
<template>
<FormFields :options="fields" />
</template>
<script lang="ts" setup>
const form = useForm({
initialValues: {
// Existing files (when editing a record) are the same shape the API returned
attachments: [
{
url: 'https://placehold.co/600x400.png',
name: 'existing-photo.png',
size: 20480,
},
],
},
})
const requestOptions = useRequestOptions().getFile()
const fields = createFormFields(() => [
{
type: INPUT_TYPES.UPLOAD_DROPZONE_AUTO_MULTIPLE,
props: {
name: 'attachments',
label: 'UPLOAD_DROPZONE_AUTO_MULTIPLE',
placeholder: 'Up to 3 files, 5 MB each',
requestOptions,
uploadPathURL: '/uploads',
maxSize: 5120,
maxFiles: 3,
},
},
])
// Files upload one after another; each finished file is added to the front of the array.
// A failed file stays in the list with a retry button and does not stop the others.
defineExpose({
debug: computed(() => form.values),
})
</script>Images, with dimension rules
examples/fields/UploadImage.vueAny image up to 2 MB
คลิกเพื่อเลือกรูปภาพ
หรือ ลากและวางที่นี่
At least 800px wide, 16:9
คลิกเพื่อเลือกรูปภาพ
หรือ ลากและวางที่นี่
<template>
<FormFields
:options="fields"
class="grid gap-x-4 md:grid-cols-2"
/>
</template>
<script lang="ts" setup>
const form = useForm()
const requestOptions = useRequestOptions().getFile()
const fields = createFormFields(() => [
{
type: INPUT_TYPES.UPLOAD_IMAGE_AUTO,
props: {
name: 'avatar',
label: 'UPLOAD_IMAGE_AUTO',
description: 'Any image up to 2 MB',
requestOptions,
uploadPathURL: '/uploads',
maxSize: 2048,
},
},
{
type: INPUT_TYPES.UPLOAD_IMAGE_AUTO,
props: {
name: 'banner',
label: 'With dimension rules',
description: 'At least 800px wide, 16:9',
requestOptions,
uploadPathURL: '/uploads',
// Checked in the browser before anything is uploaded:
// width, height, minWidth, minHeight, maxWidth, maxHeight, aspectRatio, aspectRatioTolerance
dimensions: {
minWidth: 800,
aspectRatio: 16 / 9,
aspectRatioTolerance: 0.05,
},
},
},
])
defineExpose({
debug: computed(() => form.values),
})
</script>Keep the File and send it yourself
examples/fields/UploadFile.vue<template>
<Form @submit="onSubmit">
<FormFields :options="fields" />
<Button
type="submit"
class="mt-4"
>
Build FormData
</Button>
</Form>
</template>
<script lang="ts" setup>
const form = useForm()
// UPLOAD_DROPZONE uploads nothing: the form value is the File itself.
// Use it when the file must travel with the rest of the form in one multipart request.
const fields = createFormFields(() => [
{
type: INPUT_TYPES.TEXT,
props: {
name: 'title',
label: 'Title',
},
},
{
type: INPUT_TYPES.UPLOAD_DROPZONE,
props: {
name: 'document',
label: 'UPLOAD_DROPZONE',
accept: '.pdf',
maxSize: 5120,
},
},
])
const sent = ref<string[]>([])
const onSubmit = form.handleSubmit((values) => {
const body = new FormData()
body.append('title', values.title ?? '')
if (values.document) body.append('document', values.document)
// loader.run({ data: body }) would send it; here we only list what is inside
sent.value = Array.from(body.entries()).map(
([key, value]) => `${key}: ${value instanceof File ? `File(${value.name})` : value}`
)
})
defineExpose({
debug: computed(() => ({
values: form.values,
formData: sent.value,
})),
})
</script>