All Products
Search
Document Center

ApsaraVideo VOD:Upload files using the JavaScript SDK

Last Updated:Jul 13, 2026

The ApsaraVideo VOD JavaScript SDK (aliyun-vod-upload-sdk) uploads video and image files from a browser to ApsaraVideo VOD storage. The SDK handles credential retrieval, multipart upload, resumable upload, and progress tracking.

Browser requirements

BrowserMinimum version
Chrome60+
Microsoft Edge79+ (Chromium)
Firefox55+
Safari11+
Android default browser60+
iOS default browser11+

Quick start

You can download the Demo source code for a complete working example.

Installation

Install the SDK by using npm:

npm install aliyun-vod-upload-sdk

The SDK provides ESM, CJS, and UMD builds and supports all modern bundlers (Vite, webpack, Rollup, and esbuild).

CDN import

To use the SDK without a bundler, import it from a CDN:

<script src="https://g.alicdn.com/apsara-media-box/imp-web-vod-upload/2.0.0/vod-upload.umd.js"></script>
<script>
  const { createUploader, UploadError } = window.VodUpload;
</script>

Backend API requirements

The SDK does not directly call Alibaba Cloud OpenAPI. Instead, it delegates to your backend through the getAuth callback to obtain upload credentials. Your backend must implement the following APIs, which correspond to VOD OpenAPI operations:

Backend APICorresponding OpenAPI operationResponse fields
Create video upload credentialCreateUploadVideo{ UploadAuth, UploadAddress, VideoId }
Refresh video upload credentialRefreshUploadVideo{ UploadAuth, UploadAddress, VideoId }
Create image upload credentialCreateUploadImage{ UploadAuth, UploadAddress, ImageId }

The backend response JSON is passed as-is to the SDK. The SDK automatically handles base64 decoding and field mapping.

Upload a file in five lines of code

The following example shows the minimal code to upload a file:

import { createUploader } from 'aliyun-vod-upload-sdk';

const uploader = createUploader({
  getAuth: () => fetch('/api/vod/auth').then(r => r.json()),
});

// file is a File object, for example from an <input type="file"> element
const { videoId } = await uploader.upload(file);

Basic features

Upload videos

The following example creates an uploader with a getAuth callback that handles both initial credential creation and credential refresh:

import { createUploader } from 'aliyun-vod-upload-sdk';

const uploader = createUploader({
  getAuth: async (ctx) => {
    switch (ctx.kind) {
      case 'create-video':
        return fetch('/api/vod/create-auth', {
          method: 'POST',
          body: JSON.stringify({ fileName: ctx.file.name, ...ctx.meta }),
        }).then(r => r.json());
      case 'refresh-video':
        return fetch(`/api/vod/refresh-auth?videoId=${ctx.videoId}`)
          .then(r => r.json());
    }
  },
});

const result = await uploader.upload(file, {
  meta: { title: 'My Video', cateId: 1000, tags: 'tutorial,example' },
});
console.log('Upload successful, videoId:', result.videoId);

The getAuth callback receives a ctx parameter. The SDK automatically determines ctx.kind based on the scenario:

  • create-video — First upload. The backend must call CreateUploadVideo.

  • refresh-video — Credential refresh during resumable upload. The backend must call RefreshUploadVideo.

  • create-image — Image upload. The backend must call CreateUploadImage.

Upload images

Image upload uses the same upload() method. The SDK automatically detects the file type based on file.type (image/* types use the image upload path):

const uploader = createUploader({
  getAuth: async (ctx) => {
    if (ctx.kind === 'create-image') {
      return fetch('/api/vod/image-auth', {
        method: 'POST',
        body: JSON.stringify({ imageType: 'default' }),
      }).then(r => r.json());
    }
  },
});

const result = await uploader.upload(imageFile);
console.log('Image upload successful, imageId:', result.imageId);

Track upload progress

Use the onProgress callback to track upload progress:

const result = await uploader.upload(file, {
  onProgress: (percent, loaded, total) => {
    // percent: 0..1 (e.g. 0.5 means 50%)
    // loaded:  bytes uploaded
    // total:   total file size in bytes
    progressBar.value = percent;
    console.log(`${(percent * 100).toFixed(1)}% - ${loaded} / ${total}`);
  },
});

The SDK guarantees that percent equals 1 when the upload is complete.

Cancel an upload

You can cancel an upload by using either the task.abort() method or an AbortSignal.

  • Method 1: task.abort() — Simplest approach

const task = uploader.upload(file);

cancelBtn.onclick = () => task.abort();

try {
  const result = await task;
} catch (e) {
  if (e.name === 'AbortError') {
    console.log('Upload canceled by user');
  }
}
  • Method 2: AbortSignal — Suitable for React/Vue component lifecycles

const ctrl = new AbortController();
cancelBtn.onclick = () => ctrl.abort();

try {
  const result = await uploader.upload(file, { signal: ctrl.signal });
} catch (e) {
  if (e.name === 'AbortError') {
    console.log('Upload canceled');
  }
}

After cancellation, the checkpoint is preserved. The next call to upload(file) automatically resumes from the breakpoint.

Resumable upload

Resumable upload is enabled by default and requires no additional configuration. This feature is supported only for video uploads. Image uploads do not use resumable upload. The SDK automatically:

  • Stores the progress of completed parts in localStorage.

  • Resumes from where the upload was interrupted when upload(file) is called again after a page refresh.

  • Matches checkpoints by using the rule: same file.name + file.size + file.lastModified.

// First upload (user closes the page at 40% progress)
await uploader.upload(file);

// User reopens the page and uploads the same file again
// The SDK automatically resumes from 40% without re-uploading
const result = await uploader.upload(file);

To disable resumable upload:

const uploader = createUploader({
  getAuth: myGetAuth,
  checkpoint: false,  // Disable resumable upload
});

Advanced features

Batch upload

The SDK provides a single-file upload() API. For batch uploads, use standard JavaScript async primitives.

Serial upload (simplest)

for (const file of files) {
  const result = await uploader.upload(file, {
    onProgress: p => updateProgress(file.name, p),
  });
  console.log(`${file.name} complete, videoId: ${result.videoId}`);
}

Concurrent upload (high bandwidth and small files)

const results = await Promise.all(
  files.map(f => uploader.upload(f)),
);

(Recommended) Concurrency-limited upload (large files)

import pLimit from 'p-limit';

const limit = pLimit(2); // Upload at most 2 files simultaneously
const results = await Promise.all(
  files.map(f => limit(() => uploader.upload(f, {
    onProgress: p => updateItemProgress(f, p),
  }))),
);

Pause and resume

The SDK does not have an explicit pause API. Achieve the pause effect through cancel + resume:

// Pause: cancel the current upload
task.abort();

// Resume: re-upload the same file, automatically resumes from checkpoint
const result = await uploader.upload(file);

Timeout and multi-source cancellation

Use AbortSignal.timeout() and AbortSignal.any() to implement timeout-based and combined cancellation:

// Auto-cancel after 60 seconds
await uploader.upload(file, {
  signal: AbortSignal.timeout(60_000),
});

// Multi-source cancellation: user manual cancel OR 60-second timeout
const userCtrl = new AbortController();
await uploader.upload(file, {
  signal: AbortSignal.any([
    userCtrl.signal,
    AbortSignal.timeout(60_000),
  ]),
});

React and Vue integration

React

React Hook example

import { useEffect, useState, useRef } from 'react';
import { createUploader, UploadError } from 'aliyun-vod-upload-sdk';

function useUploader(getAuth) {
  const uploaderRef = useRef(createUploader({ getAuth }));

  useEffect(() => {
    return () => uploaderRef.current.dispose(); // Release on unmount
  }, []);

  return uploaderRef.current;
}

function UploadButton({ file }) {
  const uploader = useUploader(myGetAuth);
  const [progress, setProgress] = useState(0);

  const handleUpload = () => {
    const ctrl = new AbortController();

    uploader.upload(file, {
      signal: ctrl.signal,
      onProgress: p => setProgress(p),
    }).then(result => {
      console.log('Success', result.videoId);
    }).catch(e => {
      if (e.name !== 'AbortError') {
        console.error('Failed', e);
      }
    });
  };

  return <button onClick={handleUpload}>Upload ({(progress * 100).toFixed(0)}%)</button>;
}

Vue 3

Vue 3 Composable example

import { onUnmounted, ref } from 'vue';
import { createUploader } from 'aliyun-vod-upload-sdk';

export function useUploader(getAuth) {
  const uploader = createUploader({ getAuth });
  const progress = ref(0);

  onUnmounted(() => uploader.dispose());

  async function upload(file) {
    return uploader.upload(file, {
      onProgress: p => { progress.value = p; },
    });
  }

  return { upload, progress };
}

API reference

createUploader(config)

Creates an Uploader instance.

import { createUploader } from 'aliyun-vod-upload-sdk';

const uploader = createUploader(config);

UploaderConfig

ParameterTypeRequiredDefaultDescription
getAuthGetAuthYesCredential retrieval callback. For more information, see getAuth callback.
retryRetryPolicyNo{ count: 3 }Automatic retry policy for OSS multipart upload failures.
checkpointfalse | { store?: CheckpointStore }NolocalStorageResumable upload configuration. Set to false to disable. Set to { store } to inject a custom store.
parallelnumberNo4Number of concurrent OSS parts.
partSizenumberNo1048576 (1 MB)OSS part size in bytes.
timeoutnumberNo60000Network request timeout in milliseconds.
cnamestringNoCustom OSS domain name.
refreshSTSTokenIntervalnumberNo300000 (5 min)STS credential refresh check interval in milliseconds.

RetryPolicy

FieldTypeDefaultDescription
countnumber3Maximum number of retries.

uploader.upload(file, options?)

Uploads a single file. Returns an UploadTask, which is both a Promise<UploadResult> and an object with an .abort() method.

const task = uploader.upload(file, options);

UploadOptions

ParameterTypeDescription
metaVodVideoMeta | VodImageMetaMedia metadata (title, category, tags, and more).
signalAbortSignalWeb standard cancellation signal.
onProgress(percent, loaded, total) => voidProgress callback. The percent value ranges from 0 to 1.
partSizenumberOverrides the global partSize.
parallelnumberOverrides the global parallel.

VodVideoMeta (video metadata)

FieldTypeDescription
titlestringVideo title.
descriptionstringVideo description.
cateIdnumberCategory ID.
tagsstringTags, separated by commas.
templateGroupIdstringTranscoding template group ID.
storageLocationstringStorage address.
coverUrlstringCover image URL.
workflowIdstringWorkflow ID.
appIdstringApplication ID.
userDataRecordCustom data.

VodImageMeta (image metadata)

FieldTypeDescription
titlestringImage title.
descriptionstringImage description.
imageType'default' | 'cover' | 'watermark'Image type.
imageExtstringImage file extension.
tagsstringTags.
cateIdnumberCategory ID.
storageLocationstringStorage address.

UploadResult

FieldTypeDescription
videoIdstring?Video ID. Returned for video uploads.
imageIdstring?Image ID. Returned for image uploads.
etagstringOSS ETag.
requestIdstringOSS request ID.
durationMsnumberUpload duration in milliseconds.
uploadTaskIdstringUnique upload task ID. Can be used for troubleshooting with technical support.

UploadTask

The object returned by upload() is both a Promise<UploadResult> and an object with an .abort() method.

type UploadTask = Promise<UploadResult> & {
  abort(): void;
};

The .abort() method is lost after chaining. task.then(fn) returns a regular Promise. To retain the ability to cancel after chaining, use options.signal.

uploader.dispose()

Releases resources: cancels all in-progress uploads and clears internal state.

uploader.dispose();

After this call, the uploader instance cannot be used again. Call this method when a React or Vue component unmounts to prevent memory leaks.

getAuth callback

type GetAuth = (ctx: AuthContext) => Promise<AuthResult>;

AuthContext

The SDK passes different ctx values based on the upload scenario:

ctx.kindTrigger scenarioAdditional ctx fields
'create-video'First video uploadfile, meta?
'refresh-video'Credential refresh during resumable uploadfile, videoId
'create-image'Image uploadfile, meta?

AuthResult

The raw JSON returned by the backend from the OpenAPI response. The SDK automatically decodes it:

FieldTypeRequiredDescription
UploadAuthstringYesBase64-encoded STS credential.
UploadAddressstringYesBase64-encoded OSS upload address.
VideoIdstringRequired for create-videoVideo ID.
ImageIdstringRequired for create-imageImage ID.
ImageURLstringNoImage URL (passed through).

Simplest getAuth implementation:

// Unified backend route
createUploader({
  getAuth: ctx => fetch('/api/vod/auth', {
    method: 'POST',
    body: JSON.stringify(ctx),
  }).then(r => r.json()),
});

Automatic fallback on refresh-video failure: When credential refresh during resumable upload fails, the SDK automatically falls back to create-video to re-upload the file. This is fully transparent to the caller.

Error handling

UploadError structure

All upload errors are UploadError instances (extends Error):

import { UploadError } from 'aliyun-vod-upload-sdk';

class UploadError extends Error {
  readonly code: string;           // Structured error code, e.g. 'UPLOAD.OSS.ACCESS_DENIED'
  readonly message: string;        // Error description
  readonly suggestion: string;     // Fix suggestion
  readonly cause?: unknown;        // Original underlying error
  readonly uploadTaskId?: string;  // Upload task ID (for troubleshooting)
}

User-initiated upload cancellation is not an UploadError. It is a standard DOMException (name === 'AbortError').

Error codes

Error code format: UPLOAD.{layer}.{type}

Error codeDescriptionSuggested fix
UPLOAD.AUTH.GET_AUTH_FAILEDThe getAuth callback threw an error or returned an invalid structure.Check whether the getAuth callback correctly returns JSON that contains the UploadAuth and UploadAddress fields. Make sure the backend service is available and the response format is correct.
UPLOAD.OSS.ACCESS_DENIEDOSS permission error (403).Check whether the upload credential (STS Token) is valid and has not expired, and whether the RAM policy grants OSS write permissions.
UPLOAD.OSS.NO_SUCH_BUCKETThe bucket does not exist.Check whether the bucket name in the upload address is correct, and make sure the bucket has been created in the corresponding region.
UPLOAD.OSS.NO_SUCH_UPLOADThe multipart upload session has expired.The multipart upload session has expired (possibly after more than 24 hours). The SDK automatically re-uploads the file. No manual action is required.
UPLOAD.OSS.UNKNOWNUnknown OSS error.Check the browser console for detailed error information, or contact technical support.
UPLOAD.NETWORK.TIMEOUTRequest timed out.Open the Network panel in the browser developer tools to view the details of the failed request. Check whether the network connection is stable, or try increasing the timeout configuration value.
UPLOAD.NETWORK.ERRORNetwork connection error.Open the Network panel in the browser developer tools to view the status code and response of the failed request. Make sure the browser can access the OSS service address. Check whether a proxy or firewall is blocking the request.
UPLOAD.NETWORK.OFFLINEThe browser is offline.Check the network status and retry the upload after reconnecting.
UPLOAD.FILE.EMPTYThe file size is 0.The file size is 0. Empty files cannot be uploaded. Check whether the correct file is selected.
UPLOAD.INTERNAL.DISPOSEDThe Uploader instance has been disposed.Call createUploader() again to create a new instance.

Use error code constants

(Recommended) Use ErrorCode constants instead of string literals:

import { ErrorCode, UploadError } from 'aliyun-vod-upload-sdk';

try {
  await uploader.upload(file);
} catch (e) {
  if (e instanceof UploadError) {
    switch (e.code) {
      case ErrorCode.AUTH_GET_AUTH_FAILED:
        showToast('Failed to obtain credentials. Refresh the page and try again.');
        break;
      case ErrorCode.NETWORK_TIMEOUT:
      case ErrorCode.NETWORK_ERROR:
        showToast('Network error. Check your network and try again.');
        break;
      case ErrorCode.OSS_ACCESS_DENIED:
        showToast('Insufficient upload permissions. Contact the administrator.');
        break;
      default:
        showToast(`Upload failed: ${e.suggestion || e.message}`);
    }
  }
}

Troubleshooting

Each UploadError contains an uploadTaskId, which is a global trace ID for the entire upload. You can provide this ID to Alibaba Cloud technical support for quick issue identification:

catch (e) {
  if (e instanceof UploadError) {
    // Send to your technical support team
    const diagnostic = {
      code: e.code,
      message: e.message,
      uploadTaskId: e.uploadTaskId,
      suggestion: e.suggestion,
    };
    reportToSupport(diagnostic);
  }
}

The UploadResult.uploadTaskId can also be used for log correlation after a successful upload:

const result = await uploader.upload(file);
myLogger.info('Upload successful', { videoId: result.videoId, uploadTaskId: result.uploadTaskId });