The data model
Internally the explorer works with one canonical shape:
interface DocumentItem {
id: string;
name: string;
type: 'file' | 'folder';
parentId?: string | null;
mimeType?: string;
extension?: string;
size?: number;
createdAt?: string | number | Date;
updatedAt?: string | number | Date;
viewUrl?: string;
downloadUrl?: string;
/** Anything you want to carry through and display. */
metadata?: Record<string, unknown>;
/** The untouched source object this was mapped from. */
raw?: unknown;
}If your data already looks like this, you need no mapper at all:
<DocumentExplorer data={documents} />Otherwise, see Mapping your data.
Why flat, not nested
The internal truth is flat rows plus parentId, not a nested children tree. Flat suits how document data actually behaves: it paginates, it virtualizes, it lazy-loads, and you can update a single row without rebuilding a subtree.
Nested input is still accepted at the boundary and flattened for you, so you can pass whichever shape your API returns.
// Both of these work.
const flat = [
{ id: '1', name: 'Engineering', type: 'folder' },
{ id: '2', name: 'spec.pdf', type: 'file', parentId: '1' },
];
const nested = [
{ id: '1', name: 'Engineering', type: 'folder',
children: [{ id: '2', name: 'spec.pdf', type: 'file' }] },
];Metadata
Anything that is not part of the canonical shape belongs in metadata, and can then be sorted, searched and displayed:
metadata: { owner: 'Nitesh', status: 'approved', department: 'Engineering' }<DocumentExplorer
columns={['name', { key: 'metadata.owner', label: 'Owner' }, 'size']}
search={{ searchableFields: ['metadata.owner'] }}
/>Messy data is repaired, not fatal
Real payloads contain bad rows. The normalizer never throws; it repairs and reports:
| Problem | What happens |
|---|---|
Duplicate id | The later row is dropped. |
Missing id | A synthetic id is assigned. |
parentId points at a row that does not exist | Treated as a root. |
| A row is its own parent | The link is cut. |
| A cycle between rows | The chain is broken. |
| A file is used as a parent | It is promoted to a folder. |
Each produces a NormalizeWarning, which you can surface if you want to know about upstream data problems:
import { normalize } from '@document-explorer/core';
const { warnings } = normalize(apiResponse, { mapper });
warnings.forEach((w) => console.warn(w.code, w.message));One bad record must not take down the explorer.