Dialog File Preview for Quasar
🇪🇸 Versión en español | 🇧🇷 Versão em português
Quasar extension to preview files in a modal dialog in Vue 3 + Quasar apps, with an imperative composable-based API.
Table of contents
- In 30 seconds
- What it is and what it includes
- Requirements and compatibility
- Install in a Quasar app
- What the extension adds to the host
- Global defaults (boot)
- Language (i18n)
- Quick start
- TypeScript in your IDE
- Public API
- Session options (
PreviewSessionOptions) - Preview size limits
- Accepted file sources
- Supported file types
- Exported TypeScript types
- MIME utilities
- Related optional extensions
- Public entrypoints
- Troubleshooting
In 30 seconds
Recommended base flow:
- Install the extension in your Quasar app (
quasar ext add). - Ensure the host Pinia boot runs before this extension's boot.
- Import
useDialogFilePreview()wherever you need to open the preview. - Call
void preview.show(file)— the dialog mounts globally; no need to add components to your templates.
The extension registers boots and CSS in the host project automatically after quasar ext add.
What it is and what it includes
Global modal dialog controlled by useDialogFilePreview() — not an embeddable component.
show()/hide()/canPreview();File,Blob, URL or descriptor object; multi-file gallery.- Viewers: image (zoom), adaptive PDF, video, audio, text; fallback + download.
- Session options in
show(input, options); global defaults in boot. - Built-in i18n
en,es,pt— follows Quasar$q.lang; boot overrides (locale,messages). canPreview()is synchronous (MIME + local size);show()may probe remote URLs.- Optional Form Builder integration (
InputFile/showPreview).
Requirements and compatibility
| Item | Value |
|---|---|
| Node | >= 20.0.0 |
| Quasar | ^2.6.0 |
| Vue | ^3.4.18 |
| Pinia | ^2.0.11 | ^3.0.0 |
| Package format | ES modules |
| Recommended CLI | @quasar/app-vite ^2.x or ^3.x |
| PDF in the package | vue-pdf-embed / pdf.js included (no extra host dependency) |
The host Pinia boot must run before this extension's boot.
Install in a Quasar app
Add the extension in your Quasar app:
quasar ext add @benjaminor-dev/dialog-file-preview
Remove:
quasar ext remove @benjaminor-dev/dialog-file-preview
After quasar ext add, the extension registers boots and CSS in your app automatically. No additional steps are needed for a normal npm install.
What the extension adds to the host
Resources installed or registered by the extension:
| Resource | npm path |
|---|---|
| Dialog boot | ~@benjaminor-dev/quasar-app-extension-dialog-file-preview/boot/dialog |
| Styles | ~@benjaminor-dev/quasar-app-extension-dialog-file-preview/main.css |
| Boot defaults (host) | src/boot/bor/bor-dialog-file-preview-defaults.ts — created on install; Skip/Overwrite if it already exists |
Install (quasar ext add / invoke): creates src/boot/bor/bor-dialog-file-preview-defaults.ts (or .js) and registers it in quasar.config → boot: [] automatically. No manual editing required for a normal install. The package boot (boot/dialog) and main.css are injected by the extension on every dev/build (they do not appear as short entries in quasar.config).
Remove (quasar ext remove): removes the defaults boot and its entry in quasar.config.
Boot order (this extension): Pinia → DFP dialog boot → bor/bor-dialog-file-preview-defaults → rest. In quasar.config you will see bor/bor-dialog-file-preview-defaults; the boot/dialog boot is added by the runner at compile time.
The boot mounts the dialog in the document and connects the host Pinia store. You do not need to import DialogFilePreview in App.vue.
Global defaults (boot)
Avoid repeating showPrint / restrictInteraction on every show(). Priority: show(input, options) → boot (configureDialogFilePreviewDefaults) → built-in.
After quasar ext add, review src/boot/bor/bor-dialog-file-preview-defaults.ts — template with all boot options (// recommended: …). The boot does not support title or forcePreview — those options belong only in show(input, options).
// src/boot/bor/bor-dialog-file-preview-defaults.ts
import { configureDialogFilePreviewDefaults } from "@benjaminor-dev/quasar-app-extension-dialog-file-preview";
configureDialogFilePreviewDefaults({
showPrint: false,
restrictInteraction: true,
});
configureDialogFilePreview is an alias of configureDialogFilePreviewDefaults.
Language (i18n)
Built-in en, es, and pt. By default toolbar labels, tooltips, PDF controls, and fallback messages follow Quasar language ($q.lang.isoName); if Quasar has no language, fallback is en.
Locale resolution: boot locale → $q.lang.isoName prefix (es* → es, pt* → pt) → en.
| Option | When to use |
|---|---|
locale: 'es' in boot |
Pin a specific language (en | es | pt); ignores $q.lang changes |
messages: { download: '…' } |
Change one string without setting locale |
Quasar config example (Spanish for the whole app):
// quasar.config.ts — framework.lang
framework: {
lang: "es",
},
Boot template (after install):
configureDialogFilePreviewDefaults({
// locale: "es", // uncomment to pin Dialog File Preview language only
showPrint: true,
});
Override a single string:
configureDialogFilePreviewDefaults({
messages: { loadingFile: "Opening file…" },
});
Quick start
<script setup lang="ts">
import { useDialogFilePreview } from "@benjaminor-dev/quasar-app-extension-dialog-file-preview";
const preview = useDialogFilePreview();
function onViewPdf(file: File) {
if (preview.canPreview(file)) {
void preview.show(file);
}
}
</script>
<template>
<q-btn label="View PDF" @click="onViewPdf(selectedFile)" />
</template>
Multiple files (gallery or attachments):
void preview.show([imageFile, pdfFile, anotherImage]);
Remote URL with explicit name:
void preview.show({
source:
"https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf",
name: "tracemonkey-pldi-09.pdf",
mimeType: "application/pdf",
});
TypeScript in your IDE
The composable exposes a typed API: use autocomplete instead of guessing options.
useDialogFilePreview()returnsDialogFilePreviewApi— methods and refs appear when you typepreview..- In
show(file, options), the second argument isPreviewSessionOptions: Ctrl+Space (Windows/Linux) or Cmd+Space (macOS) listsshowPrint,showDownload,restrictInteraction,forcePreview, etc. - Typed descriptors:
PreviewItemInput,PreviewSourceInputin the main entrypoint or/types. titleandforcePreviewbelong only inshow()— not inconfigureDialogFilePreviewDefaults(the boot uses the narrowerDialogFilePreviewDefaults).- In boot,
localeaccepts"en" \| "es" \| "pt";messagesaccepts partialDialogFilePreviewMessages(toolbar, PDF controls, fallback text). - Hover over a method: the tooltip shows parameters and JSDoc.
To check whether a file can be previewed before opening the modal, canPreview() is also typed according to the source you pass.
Public API
Entry point: useDialogFilePreview().
import { useDialogFilePreview } from "@benjaminor-dev/quasar-app-extension-dialog-file-preview";
const preview = useDialogFilePreview();
Methods
| Method | Description |
|---|---|
show(input, options?) |
Opens the dialog immediately and returns a Promise that resolves when MIME normalization/probing of sources is complete. The second argument options configures the session (toolbar, title, restrictions, forcePreview). By default, local files that exceed the size limit open in fallback mode (message + download); see forcePreview to force the embedded viewer. Meanwhile, and until the viewer emits ready state, the viewport shows "Loading file...". |
hide() |
Closes the dialog and revokes blob: URLs created by the extension. |
next() |
Next file (when multiple). |
previous() |
Previous file. |
downloadCurrent() |
Downloads the visible file (from File/Blob or link when only a URL is available). |
canPreview(source) |
true if the MIME has a viewer in the dialog and the local file does not exceed the size limit. Synchronous evaluation (no remote probing). |
canPreviewAll(sources) |
true if all items in the array are previewable (also synchronous). |
Reactive state (read-only)
| Ref | Description |
|---|---|
visible |
Whether the dialog is open. |
current |
Metadata of the current item (PreviewItem) or null. |
hasPrevious / hasNext |
Navigation in multi-file lists. |
hasMultiple |
More than one file in the current session. |
Use canPreview() to show or hide "View" buttons in your UI without opening the dialog. If the URL has no extension or known MIME, pass mimeType in the descriptor or call show() directly (remote probing happens on open, not in canPreview). If the local file exceeds the size limit, canPreview() returns false even when the MIME is valid; show() shows fallback unless you pass forcePreview: true (at your own risk).
Open behavior (show)
- The dialog becomes visible instantly.
- A single loading overlay covers the viewport (dark background, white spinner, "Loading file...") while:
- sources are enriched (
File/Blob/URL → items with MIME and display URL), and - the current item's viewer finishes its visible load (for PDF: first canvas render, not just document metadata).
- sources are enriched (
- Print and download toolbar buttons appear only when the viewer emits ready state and the corresponding session option is active (
showPrint/showDownload). - Print is offered only for PDF, images, and text; video and audio do not show the button even when
showPrintistrue. - When switching files in a gallery (
next/previous), the loader returns until the new viewer is ready. - When printing (button or Ctrl/Cmd+P), if iframe/PDF preparation takes time, the same overlay shows "Preparing print..." after ~250 ms and hides the instant the system dialog appears (it does not wait for you to confirm or cancel).
Internal preparation state is not exposed in useDialogFilePreview(); visible and the overlay UX are enough.
Session options (PreviewSessionOptions)
Optional second argument of show(). Applies to the entire dialog session (including multi-file gallery).
import type { PreviewSessionOptions } from "@benjaminor-dev/quasar-app-extension-dialog-file-preview";
void preview.show(confidentialPdf, {
showPrint: false,
showDownload: true,
restrictInteraction: true,
title: "Confidential contract",
});
void preview.show([scan1, scan2], {
showGalleryNav: true,
showPdfZoom: false,
});
// Force embedded viewer even when size limit is exceeded (performance / crash risk)
void preview.show(heavyLocalPdf, { forcePreview: true });
| Option | Default | Description |
|---|---|---|
showPrint |
true |
Shows the print button when the type allows it (PDF, image, text). |
showDownload |
true |
Shows the download button in the toolbar. |
showGalleryNav |
true |
Shows previous/next navigation when there are multiple files. |
showPdfZoom |
true |
Shows "Zoom: N%" in the toolbar for PDF. |
title |
file name | Centered title in the toolbar. |
restrictInteraction |
false |
Blocks copy, cut, selection, context menu, and common shortcuts in the viewer. Does not prevent screenshots or advanced browser tools. |
forcePreview |
false |
Skips the size limit and opens the embedded viewer even when canPreview() is false due to size. Developer responsibility: very large files may slow the UI, freeze the tab, or close the browser. Does not change canPreview() result. |
If you omit options, the table defaults are used.
Preview size limits
For File / Blob with known .size, the extension applies a cap before using the embedded viewer. The same limits apply to canPreview() and show() by default.
| Viewer | MIME / category | Limit |
|---|---|---|
| Image | image/* |
50 MB |
application/pdf |
100 MB | |
| Video | video/mp4, video/webm, video/ogg |
150 MB |
| Audio | audio/mpeg, audio/wav, audio/ogg, audio/webm |
50 MB |
| Text | text/plain, text/csv, application/json, application/xml, text/xml |
10 MB |
Exported constant: PREVIEW_MAX_BYTES_BY_KIND (bytes per viewer kind).
Behavior:
canPreview(file)→falseif the MIME is valid but size exceeds the limit.show(file)(default) → dialog with file-too-large message, formatted size, and download button.show(file, { forcePreview: true })→ attempts the embedded viewer anyway. Use only if you accept performance or browser-close risk; does not altercanPreview().- Remote URLs without known size: not blocked by this synchronous size check (MIME probing on open does not reliably include
Content-Lengthin all cases).
Accepted file sources
Unified type PreviewSourceInput:
| Form | Example |
|---|---|
File |
File from <input type="file"> or new File(...) |
Blob |
API or canvas Blob |
string |
https://... or blob:... URL |
| Descriptor object | See below |
// File or Blob directly
void preview.show(file);
void preview.show([imageFile, pdfFile]);
// URL with explicit metadata (recommended when extension is unclear)
void preview.show({
source: "https://example.com/document",
name: "contract.pdf",
mimeType: "application/pdf",
});
// Descriptor with File
void preview.show({
source: selectedFile,
name: "renamed-attachment.pdf",
});
| Situation | What to do |
|---|---|
File / Blob |
Name and MIME are inferred from the object |
| URL without extension | Pass name and mimeType in the descriptor |
| Remote URL on open | May be probed (HEAD/partial GET) to infer MIME |
canPreview() returns false (MIME) |
Evaluated synchronously; with explicit mimeType or after probing, show() may still open (or fallback if the type has no viewer) |
canPreview() returns false (size) |
The File/Blob exceeds the size limit. show() opens fallback with download; use { forcePreview: true } only if you accept performance risk |
External blob: URLs |
The extension does not revoke them; only those created from File/Blob are released |
Supported file types
| Category | MIME / prefixes | Viewer in the dialog | Printable |
|---|---|---|---|
| Images | image/* |
Image centered and contained in the viewport; click for ×2 zoom with pan; no scroll in the viewer area | Yes |
application/pdf |
pdf.js (vue-pdf-embed): adaptive mode (see below); page bar with direct jump, zoom, and text/annotation layers |
Yes | |
| Video | video/mp4, video/webm, video/ogg |
<video controls> |
No |
| Audio | audio/mpeg, audio/wav, audio/ogg, audio/webm |
<audio controls> with central icon and equalizer while playing |
No |
| Text | text/plain, text/csv, application/json, application/xml, text/xml |
Monospaced text with scroll | Yes |
| Other | — | Message + download button | No |
If the MIME has no viewer, show() can still open the dialog in fallback mode to allow download.
Image viewer
- The image scales to fit the visible area without cropping.
- Click on the image: ×2 zoom anchored to the cursor point; move the mouse to pan the enlarged view.
- Click again (or outside the image in zoom mode): returns to the initial fit.
- The cursor shows
zoom-in/zoom-outonly over the image.
PDF viewer (adaptive mode)
The viewer automatically chooses between two presentations based on document size:
| Mode | When | Experience |
|---|---|---|
| Continuous scroll | Light file (typically ≤ 25 MB, ≤ 40 pages) | All pages in a column with spacing; vertical scroll; bottom bar tracks the visible page |
| Paginated | Heavy file or many pages | One page at a time (suitable for very large PDFs) |
In both modes:
- Bottom bar: first / previous / numeric field / total / next / last.
- The page field accepts digits only, limits value to range
1…total, selects all on focus, and confirms with Enter or on blur. - Buttons and direct jump position the view (smooth scroll in continuous mode).
- Zoom with ± buttons (40–100%) or Ctrl/Cmd + wheel / Ctrl/Cmd + +/-; percentage appears in the toolbar when
showPdfZoomis notfalse. - Internal PDF links navigate to the corresponding page.
There is no public option to force a mode: the heuristic protects the browser from huge documents (e.g. hundreds of MB or thousands of pages).
Exported TypeScript types
From the main entrypoint:
import type {
DialogFilePreviewApi,
DialogFilePreviewDefaults,
PreviewBlockReason,
PreviewSourceInput,
PreviewItemInput,
PreviewItem,
PreviewKind,
PreviewSessionOptions,
} from "@benjaminor-dev/quasar-app-extension-dialog-file-preview";
| Type | Use |
|---|---|
DialogFilePreviewApi |
Return type of useDialogFilePreview() |
DialogFilePreviewDefaults |
Options accepted in configureDialogFilePreviewDefaults (boot) |
PreviewBlockReason |
Size block reason ("size", etc.) |
PreviewSourceInput |
First argument of show() and argument of canPreview() |
PreviewSessionOptions |
Optional second argument of show() |
PreviewItemInput |
Descriptor with optional metadata |
PreviewItem |
Normalized item in reactive state (current); includes blockedReason: PreviewBlockReason | null when applicable |
PreviewKind |
"image" | "pdf" | "video" | "audio" | "text" | "unknown" |
type PreviewItemInput = {
source: File | Blob | string;
name?: string;
mimeType?: string;
};
MIME utilities
Also exported to validate in your UI without opening the dialog:
import {
isPreviewableMime,
PREVIEWABLE_MIME_PREFIXES,
} from "@benjaminor-dev/quasar-app-extension-dialog-file-preview";
if (isPreviewableMime(file.type)) {
// show preview button
}
Values of PREVIEWABLE_MIME_PREFIXES (prefix with / = full family; rest = exact MIME):
| Entry | Category |
|---|---|
image/ |
Images |
application/pdf |
|
video/mp4, video/webm, video/ogg |
Video |
audio/mpeg, audio/wav, audio/ogg, audio/webm |
Audio |
text/plain, text/csv, application/json, application/xml, text/xml |
Text |
Related optional extensions
| Extension | Purpose | Relationship with Dialog File Preview |
|---|---|---|
| @benjaminor-dev/form-builder | Declarative forms with defineForm and FormBuilder |
InputFile and InputFileMultiple detect this extension when showPreview is not false (default true): view/download buttons in the append or management table. Not a dependency of Dialog File Preview |
If you use both in the host:
quasar ext add @benjaminor-dev/form-builder
quasar ext add @benjaminor-dev/dialog-file-preview
Full stack boot order:
1. Pinia (host)
2. Form Builder — boot/store
3. bor/bor-form-builder-defaults (host)
4. Table Builder — boot/store
5. bor/bor-table-builder-defaults (host)
6. Dialog File Preview — boot/dialog
7. bor/bor-dialog-file-preview-defaults (host)
8. Dialog Loader — boot/dialog
9. bor/bor-dialog-loader-defaults (host)
10. Dialog Message — boot/dialog
11. bor/bor-dialog-message-defaults (host)
In quasar.config you will see each extension's defaults boot (e.g. bor/bor-dialog-file-preview-defaults); package boots are injected by the extension at compile time — they do not appear as short entries in your config.
Manual use from a callback or custom button (without depending on Form Builder):
<script setup lang="ts">
import { useDialogFilePreview } from "@benjaminor-dev/quasar-app-extension-dialog-file-preview";
const preview = useDialogFilePreview();
function onPreviewFile(file: File) {
void preview.show(file);
}
</script>
The boot also exposes the API via provide (DIALOG_FILE_PREVIEW_API_KEY) for sibling library integrations — same instance as useDialogFilePreview().
Public entrypoints
| Entrypoint | Purpose |
|---|---|
@benjaminor-dev/quasar-app-extension-dialog-file-preview |
useDialogFilePreview, configureDialogFilePreviewDefaults, i18n helpers (resolveDialogFilePreviewMessages, resolveExtensionLocale), MIME utilities (isPreviewableMime, PREVIEW_MAX_BYTES_BY_KIND, …) — IDE types (PreviewSessionOptions, DialogFilePreviewDefaults, DialogFilePreviewMessages, …) |
@benjaminor-dev/quasar-app-extension-dialog-file-preview/boot/dialog |
Quasar boot (registered automatically by the extension) |
@benjaminor-dev/quasar-app-extension-dialog-file-preview/main.css |
Dialog styles (registered automatically) |
Troubleshooting
UI strings stay in English
- Set
framework.langinquasar.config(e.g."es") or call$q.lang.set()at runtime. - Or pin the extension:
configureDialogFilePreviewDefaults({ locale: "es" })insrc/boot/bor/bor-dialog-file-preview-defaults.ts.
Boots or styles missing from quasar.config
Under normal conditions install registers the defaults boot in boot: [] and the runner injects boot/dialog + CSS at compile time. If the defaults boot is missing (older project):
npx quasar ext invoke @benjaminor-dev/dialog-file-preview
Error: Pinia not initialized / store unavailable
- Verify a Pinia boot exists in the host (
app.use(pinia)or equivalent). - In
quasar.config, the Pinia boot must come before this extension'sdialogboot.
useDialogFilePreview() outside setup
Call it inside setup, another composable, or <script setup>. If you need it outside the component context, ensure the extension boot has already run (app mounted).
The "View" button should not be shown
Use preview.canPreview(file) before rendering the action. Returns false if the MIME has no viewer or if the local file exceeds the size limit. isPreviewableMime() only evaluates MIME, not size.
canPreview() returns false due to size
The local file exceeds its type limit (e.g. image > 50 MB). By default show(file) opens fallback with download. To force the embedded viewer: show(file, { forcePreview: true }) — at your own risk (performance, frozen tab, or browser close). canPreview() will still be false.
canPreview() returns false for a URL without extension
canPreview() does not perform remote probing. If the URL has no extension or known heuristic, pass mimeType in the descriptor or call show() directly.
Very large PDF: I only see one page at a time
Expected behavior in paginated mode: files above ~25 MB, with more than 40 pages, or long documents without known size (> 15 pages) render one at a time to avoid saturating browser memory. Use the page bar to navigate.
PDF page counter does not change when scrolling
In continuous mode with high zoom (≥ 90%), wait a moment after stopping scroll; the counter tracks the page with the largest visible area. If the document entered paginated mode, viewport scroll does not apply — use the page buttons.
PDF or text from external URL does not load
- CORS: the server must allow
fetchfrom your origin (remote PDF is downloaded as blob before rendering). - Pass explicit
mimeTypeif the URL has no extension. - Verify the URL responds 200 (broken links show the error in the viewer).
- For domains without CORS, download on your backend and pass a local
File/Blob.
Example public URL that works in tests:
preview.show({
source:
"https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf",
name: "tracemonkey-pldi-09.pdf",
mimeType: "application/pdf",
});
Download button does not appear on open
Expected at first: toolbar download is enabled only when the viewer emits ready state and showDownload is not false. While preparing sources or loading PDF/image/text you will see "Loading file..." in the viewport. If the loader does not disappear, check network/CORS errors in the console.
Print button does not appear
- Check that
showPrintis notfalsein session options. - Only PDF, images, and text are printable; video/audio do not show the button.
- Like download, it requires ready state from the viewer.
Print loader is slow or does not appear
- Cached text and image usually open the dialog instantly without overlay (expected behavior).
- Remote PDF or image may show "Preparing print..." while the iframe is prepared; the overlay is removed as soon as the browser print window opens, not when you click Print or Cancel in that dialog.
restrictInteraction does not block everything
The option reduces copy, selection, and context menu in the viewer, but does not replace DRM or prevent screenshots. A technically savvy user can still access the content.
Dialog opens empty briefly or takes time to show the file
show() opens the modal immediately and normalizes sources in the background. With remote URLs or multiple files, the overlay may remain for a few seconds; that alone does not indicate failure.
Image/PDF preview works locally but not in production
Verify the extension is installed/invoked in the production build and that the extension's main.css is in the generated config.
Multiple files: navigation does not appear
Pass an array to show([...]). The previous/next bar only shows when there is more than one item and showGalleryNav is not false.
MIT © Benjamín Olvera R.