# Pi-Web File Viewer Supported File Types: How Preview Rendering Works

> Discover Pi Web file viewer supported file types, including images, code, and more. Learn how its preview rendering pipeline works with React components.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: how-to-guide
- Published: 2026-08-17

---

**The Pi-Web file viewer supports images (PNG, JPG, GIF, SVG), text/source code files (JavaScript, TypeScript, Python, Go, and 15+ languages), Markdown files, and binary files, with each type triggering a distinct rendering pipeline using React components like `HighlightedCode` and `MarkdownBody`.**

The `FileViewer` component in the **agegr/pi-web** repository handles file preview functionality through a type-safe discrimination system. Understanding what file types are supported and how the preview mechanism processes each category is essential for developers extending the codebase or debugging rendering issues.

## Supported File Types in the Viewer

The file viewer categorizes all content into four distinct types based on file extension and MIME type detection. The `viewFile` function in [`lib/file-access.ts`](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts) implements this classification logic at lines 82–105.

### Image Files

**Supported extensions:** `.png`, `.jpg`, `.jpeg`, `.gif`, `.svg`

When `viewFile` detects an image MIME type through the `mimeFromExtension` mapping, it returns an `ImageFileInfo` object containing a URL pointer to `/api/files/{fileId}` rather than the file content itself. In [`components/FileViewer.tsx`](https://github.com/agegr/pi-web/blob/main/components/FileViewer.tsx) (lines 68–70), the component renders a native `<img>` element:

```tsx
} else if (fileInfo.type === "image") {
  return <img src={fileInfo.url} alt={fileInfo.name} className="max-w-full h-auto" />;
}

```

This approach offloads image decoding to the browser while avoiding memory overhead from reading binary data into the React state.

### Text and Source Code Files

**Supported extensions:** `.js`, `.ts`, `.tsx`, `.jsx`, `.json`, `.py`, `.go`, `.java`, `.c`, `.cpp`, `.rs`, `.html`, `.css`, `.txt`

Text files trigger the `TextFileInfo` type path. The `languageFromExtension` function (lines 59–76 in [`lib/file-access.ts`](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts)) maps file extensions to Prism.js language identifiers. The `viewFile` function reads the file contents using `fs.readFile` with UTF-8 encoding and returns both the content string and the detected language.

In [`FileViewer.tsx`](https://github.com/agegr/pi-web/blob/main/FileViewer.tsx) (lines 64–66), text content flows into the `HighlightedCode` component:

```tsx
if (fileInfo.type === "text") {
  return <HighlightedCode code={content} language={fileInfo.language} />;
}

```

The `HighlightedCode` component (located in [`components/HighlightedCode.tsx`](https://github.com/agegr/pi-web/blob/main/components/HighlightedCode.tsx)) leverages `react-syntax-highlighter` with the `a11yLight` theme, line numbers enabled, and automatic line wrapping to provide syntax highlighting for the 15+ supported programming languages.

### Markdown Files

**Supported extension:** `.md`

Although Markdown files technically match `text/markdown` MIME types, Pi-Web treats them as a distinct `MarkdownFileInfo` type to enable rich rendering. When `viewFile` encounters a `.md` extension (lines 97–101 in [`lib/file-access.ts`](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts)), it reads the full text content and returns `type: "markdown"` instead of the generic text classification.

The viewer passes this content to `MarkdownBody` (lines 66–68 in [`FileViewer.tsx`](https://github.com/agegr/pi-web/blob/main/FileViewer.tsx)), which uses `react-markdown` with the `remark-gfm` plugin for GitHub Flavored Markdown support. This enables tables, strikethrough, and task lists beyond standard CommonMark.

### Binary and Unsupported Files

**Fallback behavior:** Any file without a recognized image or text MIME type

When `mimeFromExtension` returns `null` or the extension does not match known text patterns, `viewFile` returns a `BinaryFileInfo` object containing only the filename (lines 103–104). The viewer displays a placeholder message: `[Binary file cannot be displayed]`. In the render logic (lines 70–73), binary files appear within a `<pre>` block, though the architecture prevents the actual binary buffer from entering client-side memory.

## How the Preview Pipeline Works

Understanding the technical flow reveals why certain file types render differently and where to intercept the process for customization.

### File Type Detection Logic

The detection pipeline runs entirely within the `viewFile` async function in [`lib/file-access.ts`](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts):

1. **Extension extraction:** `path.extname(filePath)` isolates the file extension
2. **MIME mapping:** `mimeFromExtension` checks against a hardcoded Record object mapping extensions to MIME types (lines 40–53)
3. **Branching logic:** 
   - If `mime?.startsWith("image/")` → return ImageFileInfo
   - Else if `mime?.startsWith("text/")` → read UTF-8 content, detect language, return TextFileInfo
   - Else if `ext === ".md"` → read UTF-8 content, return MarkdownFileInfo
   - Else → return BinaryFileInfo

This discrimination happens server-side (or in the main process) before any content reaches the React component tree.

### Rendering Components

The `FileViewer` component orchestrates rendering through a `useMemo` hook (lines 62–76) that switches on `fileInfo.type`:

```typescript
const rendered = useMemo(() => {
  if (!fileInfo) return null;
  if (fileInfo.type === "text") {
    return <HighlightedCode code={content} language={fileInfo.language} />;
  } else if (fileInfo.type === "markdown") {
    return <MarkdownBody markdown={content} />;
  } else if (fileInfo.type === "image") {
    return <img src={fileInfo.url} alt={fileInfo.name} className="max-w-full h-auto" />;
  } else if (fileInfo.type === "binary") {
    return <pre>{content}</pre>;
  } else {
    return <div>Unsupported preview.</div>;
  }
}, [fileInfo, content]);

```

State management follows a strict loading pattern: `useEffect` triggers the async `viewFile` call, sets loading states, handles cancellation via a `cancelled` flag (lines 30–53), and persists view state (scroll position) through `fileViewState` on unmount (lines 56–60).

## Working with the File Viewer Programmatically

To integrate the viewer into a custom page or extend its supported types, use the `fileId` encoding pattern demonstrated in the source:

```tsx
import { FileViewer } from "../components/FileViewer";

// Render a TypeScript file with syntax highlighting
export const CodePreviewPage = () => {
  const fileId = encodeURIComponent("/home/user/project/src/utils.ts");
  return <FileViewer fileId={fileId} />;
};

// Render a Markdown README
export const DocsPage = () => {
  const fileId = encodeURIComponent("./docs/README.md");
  return <FileViewer fileId={fileId} />;
};

```

**Extending supported languages:** Add new entries to the `languageFromExtension` Record in [`lib/file-access.ts`](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts) (line 60) and ensure the corresponding Prism.js language is imported in [`HighlightedCode.tsx`](https://github.com/agegr/pi-web/blob/main/HighlightedCode.tsx).

**Adding new image formats:** Extend the `mimeFromExtension` map (lines 41–52) with entries like `.webp: "image/webp"`; the viewer will automatically route them through the image rendering path.

## Summary

- **Image files** (PNG, JPG, GIF, SVG) preview via direct URL references to the `/api/files/` endpoint, rendered with native `<img>` tags
- **Text files** receive automatic language detection and highlight via `react-syntax-highlighter` based on the `languageFromExtension` mapping in [`lib/file-access.ts`](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts)
- **Markdown files** bypass standard text highlighting to render through `react-markdown` with GitHub Flavored Markdown support
- **Binary files** display placeholder text without loading content into memory, improving security and performance
- The preview pipeline uses a discriminated union pattern (`FileInfo` types) determined server-side by `viewFile` before React rendering decisions occur in [`FileViewer.tsx`](https://github.com/agegr/pi-web/blob/main/FileViewer.tsx)

## Frequently Asked Questions

### How does Pi-Web determine which syntax highlighting language to use?

Pi-Web uses the `languageFromExtension` function in [`lib/file-access.ts`](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts) (lines 59–76) to map file extensions to Prism.js language identifiers. When `viewFile` processes a text file, it extracts the extension via `path.extname`, looks up the language in a hardcoded Record object, and passes that string to the `HighlightedCode` component. If no mapping exists, the language parameter becomes `null` and `react-syntax-highlighter` renders the code without specific highlighting.

### Why do binary files show `[Binary file cannot be displayed]` instead of downloading?

The `viewFile` function intentionally limits memory usage and security exposure by never reading binary files into JavaScript strings. At lines 40–46 in [`FileViewer.tsx`](https://github.com/agegr/pi-web/blob/main/FileViewer.tsx), the component checks `info.type` and sets a placeholder string when detecting binary content during the effect hook. To implement downloads, you would modify the binary case to return a download URL similar to the image file approach, then render an anchor tag or trigger a programmatic download in the UI.

### Can I add support for PDF files or other document types?

Currently, PDFs map to `application/pdf` in `mimeFromExtension` but fall through to the binary case since no specific renderer exists. To add PDF support, extend the `FileInfo` union type in [`lib/file-access.ts`](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts) with a new `PdfFileInfo` variant, add a conditional branch in `viewFile` to return this type, and implement a corresponding renderer in [`FileViewer.tsx`](https://github.com/agegr/pi-web/blob/main/FileViewer.tsx) (such as an `<iframe>` or PDF.js component) within the `useMemo` rendering logic.