Skip to content

Document picker

Let users attach a PDF, a spreadsheet or any other file from wherever it lives: local storage, iCloud, Google Drive. @symbiote-native/document-picker wraps expo-document-picker, which opens the system’s own document UI, so every SymbioteNative adapter can reach it, not just React. Like print, the single export is a free function with no per-instance state, so the React, Vue, Angular, Svelte, and Solid entry points are plain re-exports of the same core.

OS platform Support
iOS live
Android live
Framework adapter Support
React live
Vue live
Angular live
Svelte live
Solid live
Terminal window
npm install @symbiote-native/document-picker

Scaffolding or extending a SymbioteNative app? npx @symbiote-native/cli new --document-picker (or add --document-picker in an existing app) installs and wires this for you - see @symbiote-native/cli.

expo-document-picker and expo-modules-core come along as regular dependencies, pinned to exact versions. Never install either yourself, and never add the expo meta-package to your project (it bundles its own Metro/Babel pipeline, which conflicts with this project’s own).

No runtime permission or manifest edit is needed: the picker is the OS’s own UI and runs outside your app’s permission surface. Android 11+ package visibility is handled by a <queries> block the package ships.

All five adapters re-export the same function; there is no per-adapter hook, composable or service, since nothing here holds live state.

import { useState } from 'react';
import { getDocumentAsync } from '@symbiote-native/document-picker/react';
export default function PickPdf() {
const [name, setName] = useState<string | null>(null);
async function onPress() {
const result = await getDocumentAsync({ type: 'application/pdf' });
if (!result.canceled) setName(result.assets[0].name);
}
return (
<view>
<text>{name ?? 'nothing picked'}</text>
<button title="Pick a PDF" onPress={onPress} />
</view>
);
}
const result = await getDocumentAsync({ type: ['application/pdf', 'image/*'], multiple: true });
if (!result.canceled) {
for (const asset of result.assets) console.log(asset.name, asset.uri, asset.size);
}
Signature Description
getDocumentAsync(options?: IDocumentPickerOptions): Promise<IDocumentPickerResult> Opens the system document picker and resolves what the user chose or that they cancelled
Field Type Description
type string | string[] | undefined MIME types to show; wildcards such as image/* are allowed. Defaults to any type
copyToCacheDirectory boolean | undefined Copy each pick into the app’s cache so it is readable right away. Defaults to true
multiple boolean | undefined Allow picking more than one document. Defaults to false

IDocumentPickerResult and IDocumentPickerAsset

Section titled “IDocumentPickerResult and IDocumentPickerAsset”
Type Field Description
IDocumentPickerResult canceled true when the user dismissed the picker. Then assets is null
IDocumentPickerResult assets IDocumentPickerAsset[] when not cancelled, null when cancelled
IDocumentPickerAsset name File name, including extension
IDocumentPickerAsset uri Location of the file: the cache copy unless copyToCacheDirectory is false
IDocumentPickerAsset lastModified Last-modified time in milliseconds since the epoch
IDocumentPickerAsset size Size in bytes, when the provider reports it
IDocumentPickerAsset mimeType MIME type, when the provider reports it
  • Check canceled before reading assets. The result is a union: assets is null when the user backs out, so index into it only after !result.canceled.
  • The cache copy is on by default. With copyToCacheDirectory: true the file is copied into the app’s cache and uri points at the copy, which the file system package can read. Turn it off to skip the copy for large files, and read uri the way the provider allows.
  • size and mimeType can be missing. Not every provider reports them.
  • Web-only fields are dropped. Upstream’s base64, file and output exist only on the web platform; this package targets iOS and Android.
  • The picker can only be verified on a device or simulator. The headless tests fake the native module, so they prove option defaults and the type string-to-array coercion, not the picker.

The uri is a content:// URI on Android and I cannot read it. That happens with copyToCacheDirectory: false: the URI belongs to the document provider and is not a normal file path. Leave copyToCacheDirectory at its default of true so the file is copied into the app’s cache and uri is a readable path (for example for the file system package).

size is null or missing. Some Android document providers do not report a size. Read the file (after copying it) if you need the exact size.

How do I limit the picker to PDFs or images? Pass type, for example 'application/pdf' or 'image/*', or an array of them.

Do I need a permission? No. The picker is system UI and runs outside your app’s permission surface.

Can I pick several files? Yes, set multiple: true. result.assets is then an array of every file picked.

Sources: Expo docs: DocumentPicker, expo/expo#11075 handling content:// URIs on Android.

expo-document-picker’s JS is hand-ported into this package’s core/, resolving ExpoDocumentPicker through expo-modules-core rather than the expo meta-package. The five adapter entry points are plain re-exports of core (Angular stays a physical subpath for its separate ngc/AOT build). Upstream’s config plugin is build-time code this project does not run; the Android module entry lives in a static native-link.json. The native code is never vendored: expo-modules-autolinking resolves it from node_modules (see the native setup guide).