Skip to content

File Preview & Download

Any model can expose an inline file preview pane inside the search modal. Implement HasGlobalSearchPreview and return a PreviewDto — Scoutify handles the rest.

use Matheusmarnt\Scoutify\Contracts\HasGlobalSearchPreview;
use Matheusmarnt\Scoutify\Support\PreviewDto;
class Document extends Model implements GloballySearchable, HasGlobalSearchPreview
{
use Searchable;
public function globalSearchPreview(): ?PreviewDto
{
return PreviewDto::fromDisk(
disk: 'documents',
path: $this->file_path,
);
}
}

Return null to suppress the preview pane for a specific record (e.g. when no file is attached).

Use PreviewDto::fromDisk() when the file lives on a Laravel filesystem disk:

PreviewDto::fromDisk(
disk: 'documents', // any disk defined in config/filesystems.php
path: $this->file_path, // path relative to the disk root
mime: 'application/pdf', // optional — auto-detected from disk if omitted
filename: $this->name, // optional — defaults to basename($path)
sizeBytes: $this->size, // optional
ttl: 3600, // optional — signed URL TTL in seconds (default 3600)
)

URL resolution order:

  1. If the disk supports temporary URLs (e.g. S3), Scoutify generates a pre-signed URL directly.
  2. Otherwise, Scoutify streams the file through a signed scoutify.preview.stream route — no extra config needed.

Scoutify picks the viewer automatically based on the resolved MIME type:

MIMEViewer
application/pdfNative PDF embed
image/*Inline image
video/*HTML5 video player
Anything elseFallback — external link and download button

MIME resolution order:

  1. Explicit mime on the PreviewDto
  2. Auto-detected from the disk (mimeType()) — storage-based files only
  3. Falls back to application/octet-stream

When the user clicks the download button, Scoutify dispatches a scoutify:download browser event. Add a listener to your root layout to trigger the browser’s native download:

window.addEventListener('scoutify:download', (e) => {
const a = document.createElement('a');
a.href = e.detail.url;
a.download = e.detail.filename ?? '';
document.body.appendChild(a);
a.click();
a.remove();
});

The scoutify:download event carries { url: string, filename: string } in its detail.

Preview and download reuse the same authorization rules as search results. A user who cannot see a record in search results cannot stream or download its file either — GlobalSearchAuthorizer is checked at the stream route level before serving any content.

No additional policy configuration is needed.

KeyAction
TabMoves focus from the search input to the Preview button on the active row. A second Tab moves to the Download button. A third Tab returns focus to the search input.
Shift+TabReverses the focus cycle: input ← Download ← Preview.
EnterWhen focus is on a Preview or Download button, activates the button — does not navigate to the record’s route.
/ Navigate between rows. If an action button is focused, focus returns to the input first.
KeyAction
EscCloses the preview pane and returns to the results list. A second Esc dismisses the modal entirely.
EnterWhen focus is on the Back button (auto-focused when the preview opens), closes the preview.
TabCycles through the Back button and the download link in the preview header.
PageDown PageUpNot intercepted — the browser forwards them to the embedded PDF or video viewer.

Override the viewer Blade view for a specific record by setting the view parameter:

PreviewDto::fromDisk(
disk: 'documents',
path: $this->file_path,
view: 'my-package::preview.custom',
)

The custom view receives $dto (PreviewDto) and $url (resolved stream/temporary URL) as variables.