npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hubspire/react-native-upload

v1.0.8

Published

A generalized React Native package for chunked file uploads with progress tracking

Readme

react-native-upload

A generalized React Native package for file uploads with automatic chunking. This package automatically switches between chunked multipart uploads (for large files) and simple uploads (for smaller files) based on file size, supports concurrent uploads, and provides detailed progress callbacks.

Features

  • ✅ Automatic upload method selection - Automatically uses chunked upload for large files and simple upload for smaller files
  • ✅ Concurrent file and chunk uploads with progress tracking
  • ✅ Real-time progress tracking per file and overall progress
  • ✅ Support for photos and videos
  • ✅ Automatic video thumbnail generation using expo-video-thumbnails - Generate thumbnails on selection for preview or during upload
  • ✅ TypeScript support with full type definitions
  • ✅ Expo compatible
  • ✅ Error handling with detailed failure reasons

Installation

npm install @hubspire/react-native-upload
# or
yarn add @hubspire/react-native-upload
# or
bun add @hubspire/react-native-upload

Peer Dependencies

This package requires the following peer dependencies:

  • expo-file-system - For file system operations
npm install expo-file-system
# or
yarn add expo-file-system
# or
bun add expo-file-system

For automatic video thumbnail generation (optional - only needed if you plan to upload videos):

npm install expo-video-thumbnails
# or
yarn add expo-video-thumbnails
# or
bun add expo-video-thumbnails

Note: The package will automatically generate thumbnails for videos if expo-video-thumbnails is installed. If not installed, video uploads will proceed without thumbnails.

Usage

The package provides a single unified uploadFiles function that automatically selects the best upload method based on file size. Files larger than the threshold use chunked multipart upload, while smaller files use simple upload.

Basic Example

import { uploadFiles, UploadConfig, File } from "@hubspire/react-native-upload";

// Configure your upload
const uploadConfig: UploadConfig = {
  // File size threshold in bytes (default: 5MB)
  // Files >= this size will use chunked upload, files < this size will use simple upload
  chunkThresholdBytes: 5 * 1024 * 1024, // 5MB

  // Required: Unified function to get signed URLs for chunked, simple, and thumbnail uploads
  getUploadUrl: async ({
    uploadType,
    mediaType,
    contentType,
    extension,
    totalParts,
  }) => {
    const response = await fetch("/api/upload/url", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        uploadType, // "chunked", "simple", or "thumbnail"
        mediaType, // Not required for thumbnails
        contentType,
        extension,
        totalParts, // Only used when uploadType is "chunked"
      }),
    });
    if (!response.ok) throw new Error("Failed to get upload URL");
    return response.json();
    // Returns { urls, key, uploadId } for chunked or { url, key } for simple/thumbnail
  },

  // Required: Function to mark chunked upload as complete
  markUploadComplete: async ({ eTags, key, uploadId }) => {
    const response = await fetch("/api/upload/complete", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ eTags, key, uploadId }),
    });
    if (!response.ok) throw new Error("Failed to complete upload");
    return response.json();
  },

  // Optional: Progress callback (includes both per-file and overall progress)
  onProgress: (progress) => {
    console.log(`File ${progress.fileIndex}: ${progress.percentComplete}%`);
    console.log(`Overall: ${progress.overallPercentComplete}%`);
    // progress includes: fileIndex, status, percentComplete, uploadedBytes, totalBytes, error
    // Also includes: overallPercentComplete, totalUploadedBytes (overall progress across all files)
    // status: "uploading" | "completed" | "failed"
    // error: string | Error (only present if status === "failed")
  },

  // Optional: Configuration
  chunkSize: 5 * 1024 * 1024, // 5MB chunks (default)
  concurrentFileUploadLimit: 3, // Max 3 files at once (default)
  concurrentChunkUploadLimit: 6, // Max 6 chunks at once (default)
  maxFileSizeMB: 4096, // Max file size in MB (default: 4096MB = 4GB)
};

// Upload a single file (always pass as array)
// fileIndex is automatically assigned by the package (not part of File)
const files: File[] = [
  {
    filePath: "/path/to/file.jpg",
    fileSize: 1024 * 1024 * 10, // 10MB - will use chunked upload (>= 5MB threshold)
    mediaType: "photo",
    contentType: "image/jpeg",
    extension: "jpg",
  },
];

const results = await uploadFiles(files, uploadConfig);
console.log("Upload result:", results[0]);

// Upload multiple files (mixed sizes)
// fileIndex is automatically assigned by the package
const multipleFiles: File[] = [
  {
    filePath: "/path/to/small-image.jpg",
    fileSize: 1024 * 1024 * 2, // 2MB - will use simple upload (< 5MB threshold)
    mediaType: "photo",
    contentType: "image/jpeg",
    extension: "jpg",
  },
  {
    filePath: "/path/to/large-video.mp4",
    fileSize: 1024 * 1024 * 50, // 50MB - will use chunked upload (>= 5MB threshold)
    mediaType: "video",
    // thumbnailPath is optional - will be auto-generated if expo-video-thumbnails is installed
    contentType: "video/mp4",
    extension: "mp4",
  },
];

const uploadResults = await uploadFiles(multipleFiles, uploadConfig);
console.log("Upload results:", uploadResults);

Automatic Video Thumbnail Generation

The package automatically generates thumbnails for videos if expo-video-thumbnails is installed. You can generate thumbnails on selection for preview purposes, or let the package generate them during upload.

Option 1: Generate Thumbnails on Selection (Recommended for UI Preview)

Generate thumbnails immediately when videos are selected to use them for preview in your media list:

import { generateVideoThumbnail, File } from "@hubspire/react-native-upload";

// When selecting videos
const videoFile: File = {
  filePath: videoUri,
  fileSize: videoSize,
  mediaType: "video",
  contentType: "video/mp4",
  extension: "mp4",
};

// Generate thumbnail on selection for preview
try {
  const thumbnailPath = await generateVideoThumbnail(videoUri);
  if (thumbnailPath) {
    videoFile.thumbnailPath = thumbnailPath; // Use this for preview in your UI
  }
} catch (error) {
  console.warn("Failed to generate thumbnail:", error);
  // Continue without thumbnail - will be generated during upload if needed
}

// Use thumbnailPath in your UI for preview
// When uploading, if thumbnailPath is provided, it will be reused (no regeneration needed)
const results = await uploadFiles([videoFile], uploadConfig);

Option 2: Let Package Generate During Upload

If you don't provide thumbnailPath, the package will automatically generate it during upload:

// Thumbnail will be auto-generated during upload if expo-video-thumbnails is installed
// fileIndex is automatically assigned by the package
const videoFile: File = {
  filePath: videoUri,
  fileSize: videoSize,
  mediaType: "video",
  contentType: "video/mp4",
  extension: "mp4",
  // thumbnailPath is optional - will be auto-generated during upload if not provided
};

const results = await uploadFiles([videoFile], uploadConfig);

Note:

  • If thumbnailPath is provided, it will be used during upload (no regeneration)
  • If thumbnailPath is not provided, thumbnails are automatically generated during upload if expo-video-thumbnails is installed
  • Generating thumbnails on selection allows you to use them for preview in your UI before upload

API Reference

Functions

uploadFiles(files, config)

Uploads one or more files, automatically selecting chunked or simple upload based on file size.

Parameters:

  • files (required): Array of File objects. Always pass an array, even for a single file.
  • config (required): UploadConfig object with all required callbacks and optional settings.

Returns: Promise<UploadFileResult[]> - Array of upload results, one per file, in the same order as input.

Behavior:

  • Files with fileSize >= chunkThresholdBytes use chunked multipart upload
  • Files with fileSize < chunkThresholdBytes use simple upload
  • All files are uploaded concurrently up to concurrentFileUploadLimit
  • Progress callbacks are called for both chunked and simple uploads

generateVideoThumbnail(videoUri, options?)

Generates a thumbnail from a video file using expo-video-thumbnails. This function is useful for generating thumbnails on selection to use for preview in your UI.

Parameters:

  • videoUri (required): URI or path to the video file
  • options (optional): Configuration object
    • time (optional): Time in milliseconds to capture thumbnail (default: 1000)
    • quality (optional): Quality of thumbnail 0-1 (default: 0.8)

Returns: Promise<string | null> - Promise resolving to the thumbnail URI, or null if expo-video-thumbnails is not available or generation fails.

Example:

import { generateVideoThumbnail } from "@hubspire/react-native-upload";

// Generate thumbnail when video is selected
const thumbnailPath = await generateVideoThumbnail(videoUri, {
  time: 1000, // Capture at 1 second
  quality: 0.8, // 80% quality
});

if (thumbnailPath) {
  // Use thumbnailPath for preview in your UI
  // Also set it in File.thumbnailPath to reuse during upload
}

Note: This function requires expo-video-thumbnails to be installed. If not installed, it returns null without throwing an error.

Types

UploadConfig

Configuration object for unified uploads.

  • chunkThresholdBytes (optional): File size threshold in bytes. Files >= this size use chunked upload, files < this size use simple upload. Default: 5MB (5 _ 1024 _ 1024)
  • getUploadUrl (required): Unified function to get signed URLs for chunked, simple, and thumbnail uploads. The library calls this with uploadType: "chunked", uploadType: "simple", or uploadType: "thumbnail" as needed.
  • markUploadComplete (required): Function to complete chunked multipart uploads
  • onProgress (optional): Callback for per-file progress updates. Receives UploadProgress object that includes both per-file progress (fileIndex, status, percentComplete, etc.) and overall progress (overallPercentComplete, totalUploadedBytes) across all files
  • chunkSize (optional): Size of each chunk in bytes (default: 5MB)
  • concurrentFileUploadLimit (optional): Max concurrent file uploads (default: 3)
  • concurrentChunkUploadLimit (optional): Max concurrent chunk uploads (default: 6)
  • maxFileSizeMB (optional): Maximum file size in MB (default: 4096)

File

Configuration for a single file upload.

  • filePath: Local file path to upload
  • fileSize: File size in bytes
  • mediaType: 'photo' or 'video'
  • thumbnailPath (optional): Path to thumbnail image. If not provided for videos, will be auto-generated if expo-video-thumbnails is installed
  • contentType (optional): MIME content type (e.g., 'image/jpeg', 'video/mp4')
  • extension (optional): File extension (e.g., 'jpg', 'mp4')

UploadFileResult

Result of a file upload.

  • fileIndex: File index that was uploaded
  • mediaType: 'photo' or 'video'
  • key: S3 key where the file is stored
  • height (optional): Image/video height in pixels
  • width (optional): Image/video width in pixels
  • thumbnailKey (optional): S3 key of thumbnail (for videos)
  • status (optional): Upload status ("completed" | "failed") - only present if upload finished
  • error (optional): Error message or Error object if upload failed

UploadProgress

Progress information for a file upload.

  • fileIndex: File index that this progress update is for
  • status: Upload status ("uploading" | "completed" | "failed")
  • totalParts (optional): Total number of chunks (for chunked uploads)
  • uploadedParts (optional): Number of uploaded chunks (for chunked uploads)
  • percentComplete (optional): Upload percentage (0-100)
  • uploadedBytes (optional): Bytes uploaded so far
  • totalBytes (optional): Total file size
  • error (optional): Error message or Error object if upload failed
  • overallPercentComplete (optional): Overall progress percentage across all files (0-100)
  • totalUploadedBytes (optional): Total bytes uploaded across all files

Example App

An example React Native app demonstrating the package usage is available in the example/ directory. The example includes:

  • Full React Native Expo app with file selection
  • Backend server with AWS S3 integration
  • LocalStack support for local development
  • Video thumbnail generation
  • Progress tracking UI
  • Error handling with detailed messages
  • Automatic upload method selection

To run the example:

cd example
bun install  # or npm install
npm start    # or bun start

See the example README for detailed setup instructions.

Backend Requirements

Your backend needs to provide the following endpoints:

Required:

  • POST /api/upload/url - Unified endpoint that handles both chunked and simple uploads. Receives uploadType parameter ("chunked" or "simple") and returns appropriate response:
    • For uploadType: "chunked": Returns { urls: string[], key: string, uploadId: string }
    • For uploadType: "simple": Returns { url: string, key: string }
  • POST /api/upload/complete - Complete multipart upload (required for chunked uploads)

Optional:

See the example backend in example/backend/ for a complete implementation using AWS S3 and LocalStack.

LocalStack Support

The package works seamlessly with LocalStack for local development and testing. The example backend includes:

  • LocalStack S3 setup
  • Docker Compose configuration
  • Scripts for managing LocalStack
  • File viewing and management interface

See example/backend/README.md for LocalStack setup instructions.

License

MIT