API Documentation

Open File Viewer integration and API guide

Get Started Live Demo

Overview

Start file previews from one stable container

Open File Viewer combines a framework-agnostic core with UI adapters. The core handles file detection, plugin matching, container sizing, toolbar commands, fallbacks and lifecycle events. React, Vue and Svelte adapters expose the same capability as components.

4Vanilla JS / React / Vue / Svelte
15+Built-in preview plugins
1Unified plugin protocol

Frameworks

Supported frameworks

Vanilla JavaScript

Call createViewer() directly in any web page, low-code platform, micro-frontend container or framework-free project.

React

Use the FileViewer component from @open-file-viewer/react, including renderToolbar for complete toolbar replacement.

Vue

Use the OpenFileViewer component from @open-file-viewer/vue, with a #toolbar slot.

Svelte

Use the OpenFileViewer component from @open-file-viewer/svelte, with slot="toolbar" and renderToolbar.

Install

Installation

Install the core package, then add the adapter for your framework when needed.

terminal
npm install @open-file-viewer/core
npm install @open-file-viewer/react
npm install @open-file-viewer/vue
npm install @open-file-viewer/svelte
Import the stylesheet

The default UI, toolbar, error states and plugin containers depend on @open-file-viewer/core/style.css. Override CSS variables or classes after this import.

Optional Dependencies

Optional enhanced capabilities

The default core install contains common preview capabilities. A few proprietary or streaming formats need optional dependencies. Without them, plugins show metadata or a download fallback without affecting ordinary previews.

PDF

pdfPlugin() requires the host app to provide a pdfjs-dist worker URL so Vite, Webpack, Next.js and other build systems can handle worker assets correctly.

DWG

cadPlugin({ webglDwg }) uses a Worker-backed WebGL CAD scene for complex DWG files. The host copies the parser and MTEXT Workers into a public static directory. Without that option, the lightweight LibreDWG SVG path remains available.

FLV / M2TS

videoPlugin() needs no extra dependency for native formats such as MP4, WebM and MOV. FLV and MPEG-TS/M2TS require mpegts.js; otherwise a download fallback is shown.

terminal
npm install pdfjs-dist
npm install @mlightcad/cad-simple-viewer@1.5.9 @mlightcad/data-model@1.12.3 lodash-es@4.17.21
npm install @mlightcad/libredwg-web@0.7.4
npm install mpegts.js
Recommended DWG setup

Install the pinned optional dependencies, add the copy script below to the host application's scripts/ directory, and run it in predev/prebuild. The example targets the conventional public/ static directory. After startup, verify both CAD Workers and the lightweight SVG engine assets return 200.

scripts/copy-libredwg-assets.mjs
import { copyFile, cp, mkdir } from "node:fs/promises";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

const moduleEntry = fileURLToPath(import.meta.resolve("@mlightcad/libredwg-web"));
const packageRoot = dirname(dirname(moduleEntry));
const targetRoot = fileURLToPath(
  new URL("../public/vendor/libredwg-web/", import.meta.url)
);
const viewerEntry = fileURLToPath(import.meta.resolve("@mlightcad/cad-simple-viewer"));
const viewerRoot = dirname(dirname(viewerEntry));
const viewerTarget = fileURLToPath(
  new URL("../public/vendor/cad-engine/", import.meta.url)
);

await Promise.all([
  mkdir(targetRoot, { recursive: true }),
  mkdir(viewerTarget, { recursive: true })
]);
await Promise.all([
  cp(join(packageRoot, "dist"), join(targetRoot, "dist"), {
    recursive: true,
    force: true
  }),
  cp(join(packageRoot, "wasm"), join(targetRoot, "wasm"), {
    recursive: true,
    force: true
  }),
  copyFile(
    join(viewerRoot, "dist/libredwg-parser-worker.js"),
    join(viewerTarget, "libredwg-parser-worker.js")
  ),
  copyFile(
    join(viewerRoot, "dist/mtext-renderer-worker.js"),
    join(viewerTarget, "mtext-renderer-worker.js")
  )
]);
package.json
{
  "scripts": {
    "assets:dwg": "node scripts/copy-libredwg-assets.mjs",
    "predev": "npm run assets:dwg",
    "prebuild": "npm run assets:dwg"
  }
}
viewer.ts
cadPlugin({
  webglDwg: {
    workerBaseUrl: "/vendor/cad-engine"
  },
  libreDwg: {
    wasmBaseUrl: "/vendor/libredwg-web/wasm",
    workerModuleUrl: "/vendor/libredwg-web/dist/libredwg-web.js",
    workerTimeoutMs: 120_000
  }
});
DWG deployment check

The two files below /vendor/cad-engine/ and the lightweight SVG engine assets must be reachable. Add the application's public base path for subpath deployments, serve WASM as application/wasm, and allow Workers in CSP.

pnpm 11 / blockExoticSubdeps note

An upstream dependency of mpegts.js references webworkify-webpack through git, so Open File Viewer no longer makes it a required core dependency. Install it only for FLV/M2TS playback. If pnpm blocks git subdependencies, override it with the npm release or keep the built-in download fallback.

package.json
{
  "pnpm": {
    "overrides": {
      "webworkify-webpack": "2.1.5"
    }
  }
}
Umi / utoo PDF compatibility

If the PDF and worker are reachable but the preview still falls back with Cannot set properties of undefined (setting 'onPull'), enable useFetchData. The main thread will read the PDF bytes before passing them to pdf.js, avoiding worker network-stream compatibility issues.

viewer.ts
pdfPlugin({
  workerSrc,
  useFetchData: true
});

Quick Start

Integration examples

Vanilla JS

viewer.ts
import {
  createViewer,
  imagePlugin,
  officePlugin,
  pdfPlugin,
  textPlugin
} from "@open-file-viewer/core";
import "@open-file-viewer/core/style.css";
import workerSrc from "pdfjs-dist/build/pdf.worker.mjs?url";

const viewer = createViewer({
  container: "#viewer",
  file,
  fileName: file.name,
  height: "70vh",
  theme: "auto",
  toolbar: true,
  plugins: [
    imagePlugin(),
    pdfPlugin({ workerSrc }),
    officePlugin(),
    textPlugin()
  ]
});

React

AttachmentPreview.tsx
import { FileViewer } from "@open-file-viewer/react";
import { imagePlugin, officePlugin, pdfPlugin, textPlugin } from "@open-file-viewer/core";
import "@open-file-viewer/core/style.css";

const plugins = [imagePlugin(), pdfPlugin({ workerSrc }), officePlugin(), textPlugin()];

export function AttachmentPreview({ file }: { file: File }) {
  return (
    <FileViewer
      file={file}
      fileName={file.name}
      height="640px"
      toolbar
      plugins={plugins}
    />
  );
}

Vue

AttachmentPreview.vue
<script setup lang="ts">
import { OpenFileViewer } from "@open-file-viewer/vue";
import { imagePlugin, officePlugin, pdfPlugin, textPlugin } from "@open-file-viewer/core";
import "@open-file-viewer/core/style.css";

defineProps<{ file: File }>();
const plugins = [imagePlugin(), pdfPlugin({ workerSrc }), officePlugin(), textPlugin()];
</script>

<template>
  <OpenFileViewer
    :file="file"
    :file-name="file.name"
    height="640px"
    toolbar
    :plugins="plugins"
  />
</template>

Svelte

AttachmentPreview.svelte
<script lang="ts">
  import { OpenFileViewer } from "@open-file-viewer/svelte";
  import { imagePlugin, officePlugin, pdfPlugin, textPlugin } from "@open-file-viewer/core";
  import "@open-file-viewer/core/style.css";

  export let file: File;
  const plugins = [imagePlugin(), pdfPlugin({ workerSrc }), officePlugin(), textPlugin()];
</script>

<OpenFileViewer {file} fileName={file.name} height="640px" toolbar {plugins} />

Core API

createViewer(options)

containerHTMLElement | stringRequired. The viewer mount element; string values are resolved with document.querySelector.
fileFile | Blob | string | ArrayBufferSingle-file source. Remote URLs must allow cross-origin access.
files(PreviewSource | PreviewItem)[]Multi-file queue. The default toolbar can navigate to the previous and next item.
initialIndexnumberInitial queue index. Defaults to the first file.
fileNamestringRecommended when the source is not a File, because plugins use the extension for format detection.
mimeTypestringAdditional MIME type for Blob, ArrayBuffer or remote URLs with unreliable extensions.
width / heightnumber | stringViewer dimensions. Common values include 100%, 640px and 70vh.
fitcontain | cover | width | height | actual | scale-downContent fitting mode. Plugins follow the same semantics where possible.
pluginsPreviewPlugin[]Ordered plugin list. Put specific product plugins before general-purpose plugins.
fallbackinline | download | customFallback strategy for unsupported files. Use with renderFallback for complete customization.
toolbarboolean | PreviewToolbarOptionsDefault toolbar switch or configuration for actions, labels, icons, order and custom commands.
themelight | dark | autoViewer theme. auto follows the system or host environment.
classNamestringCustom class added to the root element for scoped style overrides.
onLoad(file) => voidRuns after the current file loads successfully.
onError(error, file?) => voidRuns when plugin rendering or file reading fails.
onUnsupported(file) => voidRuns when no plugin matches the current file.

Viewer Instance

Instance methods

reload(file?)Promise<void>Reload the current file or replace it with a new source.
next()Promise<void>Move to the next file in the queue.
previous()Promise<void>Move to the previous file in the queue.
goTo(index)Promise<void>Move to a specific queue index.
getCurrentIndex()numberReturn the current queue index.
resize()voidAsk the active plugin to recalculate after the container size changes.
destroy()voidDestroy the viewer and release event listeners and plugin resources.

Toolbar

Toolbar customization

Customize the toolbar at three levels: configure built-in actions, add product actions or replace the renderer. You can also override .ofv-toolbar, .ofv-toolbar button and .ofv-toolbar-search.

toolbar.ts
createViewer({
  container: "#viewer",
  file,
  toolbar: {
    labels: {
      download: "Download",
      fullscreen: "Fullscreen",
      search: "Search"
    },
    order: ["search", "download", "favorite", "approve", "share", "fullscreen"],
    actions: [
      { id: "favorite", label: "Favorite", onClick: (ctx) => favoriteFile(ctx.file) },
      { id: "approve", label: "Approve", onClick: (ctx) => openApproval(ctx.file) },
      { id: "share", label: "Share", onClick: (ctx) => shareFile(ctx.file) }
    ]
  },
  plugins
});
Custom labels

Use labels and titles to adapt actions to product terminology.

Custom order

order accepts both built-in and custom action IDs.

Custom icons

icons accepts SVG strings, HTMLElement or SVGElement values.

Product actions

actions adds approval, favorite, sharing and other actions with hidden and disabled functions.

Replace the toolbar in React, Vue or Svelte

React render prop

<FileViewer
  file={file}
  toolbar
  renderToolbar={(ctx) => (
    <div className="business-toolbar">
      <button onClick={ctx.previous} disabled={!ctx.canPrevious}>Previous</button>
      <span>{ctx.index + 1} / {ctx.length}</span>
      <button onClick={ctx.download}>Download</button>
      <button onClick={() => approve(ctx.file)}>Approve</button>
    </div>
  )}
/>

Vue slot

<OpenFileViewer :file="file" toolbar>
  <template #toolbar="ctx">
    <div class="business-toolbar">
      <button @click="ctx.previous()" :disabled="!ctx.canPrevious">Previous</button>
      <span>{{ ctx.index + 1 }} / {{ ctx.length }}</span>
      <button @click="ctx.download()">Download</button>
      <button @click="approve(ctx.file)">Approve</button>
    </div>
  </template>
</OpenFileViewer>

Svelte slot

<OpenFileViewer {file} toolbar>
  <svelte:fragment slot="toolbar" let:ctx>
    <div class="business-toolbar">
      <button on:click={ctx.previous} disabled={!ctx.canPrevious}>Previous</button>
      <span>{ctx.index + 1} / {ctx.length}</span>
      <button on:click={ctx.download}>Download</button>
      <button on:click={() => approve(ctx.file)}>Approve</button>
    </div>
  </svelte:fragment>
</OpenFileViewer>

Plugin Protocol

Plugin protocol

A plugin only needs to implement match(file) and render(ctx). The core selects the first matching plugin in array order and passes it a consistent rendering context.

custom-plugin.ts
import type { PreviewPlugin } from "@open-file-viewer/core";

export function customReportPlugin(): PreviewPlugin {
  return {
    name: "custom-report",
    match(file) {
      return file.extension === "report" || file.mimeType === "application/x-report";
    },
    render(ctx) {
      const element = document.createElement("div");
      element.className = "report-preview";
      element.textContent = ctx.file.name;
      ctx.viewport.append(element);

      return {
        resize(size) {
          element.style.setProperty("--viewer-width", `${size.width}px`);
        },
        command(command) {
          if (command === "zoom-in") {
            element.classList.add("zoomed");
            return true;
          }
          return false;
        },
        destroy() {
          element.remove();
        }
      };
    }
  };
}
Plugin order matters

Put product-specific formats or server conversion plugins before general-purpose plugins so textPlugin or a fallback does not match first.

Built-in Plugins

Built-in plugins

imagePlugin

Image formats including jpg, png, gif, webp, avif, svg, bmp, ico, heic and heif.

pdfPlugin

PDF rendering, zoom, search, print and high-resolution canvas output.

officePlugin

Office and OpenDocument formats including doc, docx, xls, xlsx, pptx, rtf, odt, ods and odp.

textPlugin

txt, md, json, yaml, toml, source code and syntax highlighting.

archivePlugin

Archive directories and file structures for zip, rar, 7z, tar, gz and more.

gisPlugin

Map data including geojson, kml, kmz, gpx, topojson and shp.

model3dPlugin

3D models including gltf, glb, obj, stl, fbx, dae, 3mf and usdz.

cadPlugin

Engineering drawings and chip layouts including dxf, dwg, step, ifc, gds and oas. Complex DWG files can use the Worker-backed WebGL CAD scene; the lightweight SVG and custom renderer paths remain available.

emailPlugin

Email bodies, headers and attachment structures for eml, msg and mbox.

FAQ

Frequently asked questions

Why does a remote URL preview fail?

The browser must be able to access the file directly and the origin must allow CORS. For private files, use a temporary URL from your backend or fetch the resource into a Blob before passing it to the viewer.

How do I avoid layout issues after the container width changes?

Give the outer container stable width and height values, then call resize() after layout changes. Built-in plugins keep scrolling inside the preview container where possible.

How do I connect a server-side conversion service?

Create a custom plugin, call the conversion service from render(ctx), then render the returned HTML, image, PDF or structured data into ctx.viewport.

Why do Office previews hang on "Loading" inside qiankun / micro-app?

Micro-frontend sandboxes tear down the window message listeners that jszip's setImmediate polyfill relies on, so JSZip.loadAsync never resolves and zip-based previews (docx, xlsx, pptx, epub, ofd) stay on the loading state. Newer versions detect sandbox flags such as __POWERED_BY_QIANKUN__ and switch to a MessageChannel-based scheduler automatically. On 0.1.27 and earlier, patch window.setImmediate = (fn, ...args) => setTimeout(fn, 0, ...args) in the sub-app entry before any imports.