---
title: useFilePicker composable
related:
  - title: useDropZone composable
    path: use-drop-zone.md
  - title: v-drop-zone directive
    path: ../vue-directives/drop-zone.md
  - title: useObjectUrl composable
    path: use-object-url.md
  - title: File Picker
    path: ../vue-components/file.md
  - title: Uploader
    path: ../vue-components/uploader.md
---
The `useFilePicker()` composable opens the browser's file dialog from your own code and hands you the picked `File` objects, without rendering a file input. You call `openFilePicker()` from a click handler (or any other user interaction) and get the files back as a Promise, through the `acceptedPickerFiles` reactive Array and through the `onChange` hook.

The picked files go through the same validation as [QFile](../vue-components/file.md) and [QUploader](../vue-components/uploader.md) (`accept`, `maxFileSize`, `maxTotalSize`, `maxFiles` and `filter`), and the files that do not pass are reported the same way those components emit `@rejected`.

Use it when you want a button, a menu entry, a keyboard shortcut or a card to pick files, and QFile's field design or QUploader's queue would get in the way. To accept dropped files as well, pair it with the [useDropZone](use-drop-zone.md) composable, which shares its validation options.

> [!NOTE]
> On the server-side of SSR or SSG modes, the file dialog cannot be opened: `openFilePicker()` resolves to `null` and `acceptedPickerFiles` stays empty until the client takes over.

> [!TIP]
> **Outside of a component**
>
> The composable can also be called outside of `setup()`: in a boot file, a store or a plain module. It works the same there, and there is nothing to release.

## Syntax

```js
import { useFilePicker } from 'quasar'

setup () {
  const {
    acceptedPickerFiles,
    rejectedPickerFiles,
    openFilePicker,
    resetFilePicker
  } = useFilePicker({
    // all optional:
    multiple: true,         // allow picking more than one file (default: false)
    accept: 'image/*,.pdf', // same format as the native "accept" attribute (default: any file)
    capture: 'environment', // 'user' or 'environment'; asks mobile devices for the camera (default: none)
    directory: true,        // pick a folder instead of files (default: false)

    // validation, same meaning as the QFile/QUploader props:
    maxFileSize: 1048576,   // bytes (default: no limit)
    maxTotalSize: 10485760, // bytes (default: no limit)
    maxFiles: 5,            // (default: no limit)
    filter (files) {        // keep only the files you return (default: keep them all)
      return files.filter(file => file.name.endsWith('.jpg'))
    },

    onChange (files) {},      // the accepted files of a selection
    onRejected (rejected) {}, // [{ failedPropValidation, file }, ...]
    onCancel () {}            // the dialog was dismissed
  })

  // ...
}
```

```ts
function useFilePicker(
  options?: MaybeRefOrGetter<{
    multiple?: boolean
    accept?: string
    capture?: 'user' | 'environment'
    directory?: boolean
    maxFileSize?: string | number
    maxTotalSize?: string | number
    maxFiles?: string | number
    filter?: (files: readonly File[]) => readonly File[]
    onChange?: (files: File[]) => void
    onRejected?: (rejected: QRejectedEntry[]) => void
    onCancel?: () => void
  }>
): {
  acceptedPickerFiles: ShallowRef<File[]>
  rejectedPickerFiles: ShallowRef<QRejectedEntry[]>
  openFilePicker: (overrides?: UseFilePickerOptions) => Promise<File[] | null>
  resetFilePicker: () => void
}

// UseFilePickerOptions is the type of the "options" parameter above;
// QRejectedEntry is the type of the entries of the QFile/QUploader "rejected" event
interface QRejectedEntry {
  failedPropValidation:
    | 'accept'
    | 'max-file-size'
    | 'max-total-size'
    | 'filter'
    | 'max-files'
    | 'duplicate'
  file: File
}
```

`openFilePicker()` must be called as a direct consequence of a user interaction (a click or keyup handler, for example). Browsers refuse to show the file dialog otherwise, including from an `async` callback that already awaited something.

The Promise returned by `openFilePicker()` resolves with the accepted files (an empty Array when every picked file got rejected) or with `null` when the user dismisses the dialog. It also resolves with `null` when `openFilePicker()` gets called again before the previous dialog reported, or when your component gets destroyed.

`acceptedPickerFiles` holds the accepted files of the latest selection. A selection where every file gets rejected leaves `acceptedPickerFiles` unchanged; `rejectedPickerFiles` always reflects the latest selection. `resetFilePicker()` empties both.

`onChange` is called only when at least one file got accepted, `onRejected` only when at least one file got rejected (both can be called for the same selection), and `onCancel` when the dialog was dismissed.

Setting `directory` picks a folder: the Array holds every file inside it (recursively), with each file's path relative to the picked folder available as `file.webkitRelativePath`. The `multiple` option does not matter in this case. Safari's dialog also lets the user pick individual files here; those come with an empty `webkitRelativePath`.

The `failedPropValidation` of a rejected entry is one of `accept`, `max-file-size`, `max-total-size`, `max-files` or `filter`, naming the option that the file did not pass (`duplicate` belongs to the same `QRejectedEntry` type, but only QFile and QUploader can report it, when appending to a list).

## Changing the options

The options can be a plain Object, a Ref or a getter Function, and they are read each time `openFilePicker()` is called. You can also hand over overrides to `openFilePicker()` itself, which take precedence for that one call:

```js
import { ref } from 'vue'
import { useFilePicker } from 'quasar'

setup () {
  const allowVideos = ref(false)

  const { openFilePicker } = useFilePicker(() => ({
    multiple: true,
    accept: allowVideos.value ? 'image/*,video/*' : 'image/*'
  }))

  function pickAvatar () {
    // only one image for the avatar, whatever the current options say
    return openFilePicker({ multiple: false, accept: 'image/*' })
  }

  // ...
}
```

## Example

A button that opens the file dialog for images, lists what was accepted, and reports the rejected files and a dismissed dialog through notifications:

Example "Picking files":

```vue
<template>
  <div class="q-gutter-md">
    <q-btn
      color="primary"
      push
      icon="attach_file"
      label="Pick images (up to 1MB each)"
      @click="attach"
    />

    <q-list
      v-if="acceptedPickerFiles.length !== 0"
      bordered
      separator
      class="rounded-borders"
    >
      <q-item v-for="file in acceptedPickerFiles" :key="file.name">
        <q-item-section>{{ file.name }}</q-item-section>
        <q-item-section side>{{
          format.humanStorageSize(file.size)
        }}</q-item-section>
      </q-item>
    </q-list>
  </div>
</template>

<script setup>
import { format, useFilePicker, useQuasar } from 'quasar'

const $q = useQuasar()

const { acceptedPickerFiles, openFilePicker } = useFilePicker({
  multiple: true,
  accept: 'image/*',
  maxFileSize: 1024 * 1024,
  onRejected(rejected) {
    $q.notify({
      type: 'negative',
      message: `${rejected.length} file(s) did not pass the validation`
    })
  }
})

async function attach() {
  const picked = await openFilePicker()

  if (picked === null) {
    $q.notify({ message: 'The dialog was dismissed' })
  }
}
</script>
```

The same picker can serve different needs by handing overrides to `openFilePicker()`. The second button picks a whole folder and lists the paths relative to it:

Example "Per-call overrides and folders":

```vue
<template>
  <div class="q-gutter-sm">
    <q-btn
      color="primary"
      push
      label="Pick a document"
      @click="pickDocument"
    />
    <q-btn color="secondary" push label="Pick a folder" @click="pickFolder" />
  </div>

  <div v-if="acceptedPickerFiles.length !== 0" class="q-gutter-sm q-mt-md">
    <q-badge :label="`${acceptedPickerFiles.length} file(s)`" />
    <div
      v-for="file in acceptedPickerFiles.slice(0, 10)"
      :key="file.webkitRelativePath || file.name"
    >
      {{ file.webkitRelativePath || file.name }}
    </div>
    <div v-if="acceptedPickerFiles.length > 10">...</div>
  </div>
</template>

<script setup>
import { useFilePicker } from 'quasar'

const { acceptedPickerFiles, openFilePicker } = useFilePicker({
  accept: '.pdf,.doc,.docx,.txt'
})

function pickDocument() {
  openFilePicker()
}

function pickFolder() {
  // the overrides apply to this one call only
  openFilePicker({ directory: true, accept: void 0 })
}
</script>
```

To preview a picked image (or hand any picked file to an `<img>`, a `<video>` or a download link), pair it with the [useObjectUrl](use-object-url.md) composable, which creates the object URL for the `File` and revokes it when it is no longer needed.
