Call createViewer() directly in any web page, low-code platform, micro-frontend container or framework-free project.
API Documentation
Open File Viewer integration and API guide
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.
Frameworks
Supported frameworks
Use the FileViewer component from @open-file-viewer/react, including renderToolbar for complete toolbar replacement.
Use the OpenFileViewer component from @open-file-viewer/vue, with a #toolbar slot.
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.
npm install @open-file-viewer/core
npm install @open-file-viewer/react
npm install @open-file-viewer/vue
npm install @open-file-viewer/svelte
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.
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.
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.
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.
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
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.
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")
)
]);
{
"scripts": {
"assets:dwg": "node scripts/copy-libredwg-assets.mjs",
"predev": "npm run assets:dwg",
"prebuild": "npm run assets:dwg"
}
}
cadPlugin({
webglDwg: {
workerBaseUrl: "/vendor/cad-engine"
},
libreDwg: {
wasmBaseUrl: "/vendor/libredwg-web/wasm",
workerModuleUrl: "/vendor/libredwg-web/dist/libredwg-web.js",
workerTimeoutMs: 120_000
}
});
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.
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.
{
"pnpm": {
"overrides": {
"webworkify-webpack": "2.1.5"
}
}
}
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.
pdfPlugin({
workerSrc,
useFetchData: true
});
Quick Start
Integration examples
Vanilla JS
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
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
<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
<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)
HTMLElement | stringRequired. The viewer mount element; string values are resolved with document.querySelector.File | Blob | string | ArrayBufferSingle-file source. Remote URLs must allow cross-origin access.(PreviewSource | PreviewItem)[]Multi-file queue. The default toolbar can navigate to the previous and next item.numberInitial queue index. Defaults to the first file.stringRecommended when the source is not a File, because plugins use the extension for format detection.stringAdditional MIME type for Blob, ArrayBuffer or remote URLs with unreliable extensions.number | stringViewer dimensions. Common values include 100%, 640px and 70vh.contain | cover | width | height | actual | scale-downContent fitting mode. Plugins follow the same semantics where possible.PreviewPlugin[]Ordered plugin list. Put specific product plugins before general-purpose plugins.inline | download | customFallback strategy for unsupported files. Use with renderFallback for complete customization.boolean | PreviewToolbarOptionsDefault toolbar switch or configuration for actions, labels, icons, order and custom commands.light | dark | autoViewer theme. auto follows the system or host environment.stringCustom class added to the root element for scoped style overrides.(file) => voidRuns after the current file loads successfully.(error, file?) => voidRuns when plugin rendering or file reading fails.(file) => voidRuns when no plugin matches the current file.Viewer Instance
Instance methods
Promise<void>Reload the current file or replace it with a new source.Promise<void>Move to the next file in the queue.Promise<void>Move to the previous file in the queue.Promise<void>Move to a specific queue index.numberReturn the current queue index.voidAsk the active plugin to recalculate after the container size changes.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.
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
});
Use labels and titles to adapt actions to product terminology.
order accepts both built-in and custom action IDs.
icons accepts SVG strings, HTMLElement or SVGElement values.
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.
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();
}
};
}
};
}
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
Image formats including jpg, png, gif, webp, avif, svg, bmp, ico, heic and heif.
PDF rendering, zoom, search, print and high-resolution canvas output.
Office and OpenDocument formats including doc, docx, xls, xlsx, pptx, rtf, odt, ods and odp.
txt, md, json, yaml, toml, source code and syntax highlighting.
Archive directories and file structures for zip, rar, 7z, tar, gz and more.
Map data including geojson, kml, kmz, gpx, topojson and shp.
3D models including gltf, glb, obj, stl, fbx, dae, 3mf and usdz.
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.
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.