Skip to content
Core Plugins

Import/Export

Move files out of the page and back in — downloads and uploads of documents and data.


Overview

One plugin covers both directions of file transfer, and everything happens in the visitor's browser — no server involved:

  • Exportx-export turns any element into a download button. The whole page, a chosen section, or an $x data source can be saved as PDF, PNG, JPEG, WebP, CSV, or JSON.
  • Importx-import turns any element into an "open file" button. A JSON or CSV file from the visitor's device is read and handed back as usable data.

Together they close the loop: whatever a visitor exports, they (or anyone else) can import back. Save files, backups, and user-made content packs all work without a single line of server code.


Setup

Import/Export is included in manifest.js with all core plugins, or can be selectively loaded. The plugin name is export; import also works and loads the same file.

<script src="https://cdn.jsdelivr.net/npm/mnfst@latest/lib/manifest.min.js"></script>

Export

The x-export directive and $export magic save the page, a section of it, or a data source as a file. PDFs go through the browser's own print dialog — the familiar "Save as PDF" flow — so text stays selectable and copy-pasteable, long content flows across pages properly, and the page's own @media print styles apply. Exporting the whole page prints the whole page; exporting a target section temporarily narrows the print to just that section. Image formats (PNG, JPEG, WebP) use html2canvas-pro, fetched from the jsDelivr CDN only when someone first clicks.

Triggers

Buttons

Add x-export to anything clickable. With no options it saves the whole page as a PDF; a modifier picks another format.


<!-- PDF -->
<button x-export>PDF</button>

<!-- Image formats -->
<button x-export.png>PNG</button>
<button x-export.jpg>JPG</button>
<button x-export.webp>WEBP</button>

<!-- Data formats -->
<div x-data="{ rows: [
    { id: 1, name: 'Acme Co.', plan: 'Pro', mrr: 240 },
    { id: 2, name: 'Globex', plan: 'Starter', mrr: 49 }
    ] }">
    <button x-export="{ format: 'csv', data: rows, filename: 'rows.csv' }">CSV</button>
    <button x-export="{ format: 'json', data: rows, filename: 'rows.json' }">JSON</button>
</div>

The visual formats (PDF, PNG, JPEG, WebP) take a picture of the page or a chosen element. The data formats (CSV, JSON) write out a $x source or an inline value — covered in Data Sources below.

Anything that shouldn't appear in the saved file (headers, sidebars, navigation, the export button itself) can be marked with data-no-export. The filter applies to every visual export on the page.

The downloaded filename can be set without any other options: a data-filename attribute works on any element, and on a link the standard HTML download attribute works too.

<button x-export.png data-filename="report.png">Download report</button>
<a x-export.pdf href="#chart" download="chart.pdf">Save chart</a>

To control the sharpness or size of a saved image, use the options form. resolution multiplies the pixel density — it defaults to the visitor's screen density, so the saved image looks like what they see; set it to 2 or 3 for a consistently sharp result on any hardware. width and height set exact output dimensions.

<button x-export="{ format: 'png', resolution: 2 }">Retina PNG</button>
<button x-export="{ format: 'jpeg', width: 1200 }">1200 px wide JPG</button>
<button x-export="{ format: 'webp', width: 1200, height: 800 }">Sized WEBP</button>

For more control, pass an options object. target is a CSS selector pointing at the element to save; everything outside it is left out.


<div id="report">
    <h2>Quarterly Report</h2>
    <p>...</p>
</div>

<button x-export="{ format: 'pdf', target: '#report', filename: 'q3-report.pdf' }">Download PDF report</button>

When x-export sits on a link whose href starts with #, that fragment becomes the target. Clicking downloads the matched element instead of scrolling to it.


<a x-export.png href="#chart">Save chart as PNG</a>

This pairs well with a normal in-page anchor as a "download this section" companion.


When x-export sits on a link to another page, the link quietly gains ?export=<format>. The browser navigates as usual, and the destination page notices the signal in its URL and exports itself once it has loaded.

<!-- The link becomes /reports/q3?export=pdf -->
<a x-export.pdf href="/reports/q3">Download Q3 report</a>

The link says what the visitor wants; the destination page knows what it can hand over. Ordinary visitors never trigger downloads — only someone arriving through an export link, or a pasted URL carrying the parameter, will.


Data Sources

For tables and structured data, point at a $x source by name. The plugin writes the source out as CSV or JSON and downloads it.


<!-- Full source as CSV -->
<button x-export="{ format: 'csv', source: 'customers', filename: 'customers.csv' }">
    Export customers
</button>

<!-- Filtered subset using existing query helpers -->
<button x-export="{ format: 'csv', data: $x.customers.$search(term, 'name') }">
    Export search results
</button>

<!-- Full JSON tree -->
<button x-export="{ format: 'json', source: 'settings' }">
    Export settings
</button>

CSV quoting is handled automatically to the standard rules (RFC 4180): values containing commas, quotes, or line breaks come out intact. The header row collects every column that appears in any row, so rows don't all need the same fields.


Export Magic

$export does everything x-export does, from inside an expression — useful when the download should happen on your own conditions: after validation, behind a sign-in check, or partway through a multi-step flow.


<!-- Validate, then export -->
<form @submit.prevent="$refs.form.checkValidity() && await $export({ format: 'csv', data: rows })">
    ...
</form>

<!-- Require sign-in before export -->
<button @click="$auth.isAuthenticated && $export({ format: 'pdf', target: '#dashboard' })">
    Download (signed-in only)
</button>

All of $export's options are identical to those of x-export.


Export Options

Property Type Default Description
format String 'pdf' One of pdf, png, jpeg, webp, csv, json
target String / Element <body> CSS selector or element to save. Visual formats only.
source String Name of a $x data source to export. csv / json only.
data Array / Object Inline data to export instead of a $x source. csv / json only.
filename String export-<timestamp>.<ext> Suggested download name. Falls back to a download attribute on links, then a data-filename attribute, then a timestamped default.
resolution Number screen density Sharpness multiplier for saved images. The default matches the visitor's screen — 1 on standard monitors, 2 on retina — so the file looks like what they see. Set it explicitly (e.g. 2 or 3) for the same sharpness on any hardware.
width Number natural width Output width in pixels for saved images. Overrides the element's natural width.
height Number natural height Output height in pixels for saved images. Overrides the element's natural height.
quality Number (0–1) 0.95 JPEG / WebP compression quality
backgroundColor String page background Solid background for saved images (PNG, JPEG, WebP). Defaults to the page's own background. Pass 'transparent' for no fill (useful for icon / logo exports). PDFs use the page's CSS directly and ignore this option.
pageSize String 'a4' PDF page size — a4, a3, letter, legal, etc. Visitors can still change it in the browser's print dialog.
trigger String 'click' 'click' (default) or 'url'. With 'url' the export runs on page load if the URL carries the export parameter.
urlParam String 'export' Name of the URL parameter to watch when trigger: 'url'
delay Number 0 Milliseconds to wait after a url trigger before saving. Useful when charts or animations need a moment to settle.

Batch and CI Exports

The mnfst-export command-line tool runs the same exports with nobody clicking — the right tool for build pipelines, scheduled jobs, and bulk runs. It supports the same six formats, plus an rss format for blog feeds.

# Snapshot a single route as PDF
npx mnfst-export --pdf --path /reports/q3 --target "#report"

# Whole project at once (reads manifest.export.routes from manifest.json)
npx mnfst-export

# Data source as CSV
npx mnfst-export --csv --path /admin/customers --source customers

Routes can be listed in manifest.json so the same npx mnfst-export runs anywhere.

manifest.json
{ "export": { "output": "exports", "routes": [ { "path": "/reports/q3", "format": "pdf", "target": "#report" }, { "path": "/customers", "format": "csv", "source": "customers" }, { "path": "/blog", "format": "rss", "source": "posts", "map": { "link": "slug" } } ] } }
Option Type Default Description
output string "exports" Output folder relative to the project root
routes object[] [] Per-route export entries. Each takes the same fields as the directive's options (path, format, target, source, filename, pageSize, etc.)
rss object inherited Channel defaults: { title, link, description }. Falls back to manifest.name, manifest.live_url, and manifest.description.

The tool starts a temporary local server, opens each page in an invisible browser, waits for the page to finish rendering (the manifest:render-ready signal), then saves whatever that page exposes. PDFs and images use the browser engine's own capture, which is more reliable there than the in-page libraries.

Run npx mnfst-export --help for the full list of flags. Puppeteer (the invisible browser) is required — install it once in the project.

npm i -D puppeteer

Import

The x-import directive and $import magic bring files back in. The visitor picks a JSON or CSV file, the plugin reads it right there in the browser (the file never leaves their device), and the content arrives as an event, a promise, or straight into an $x data source. Restoring an exported backup, loading a saved document, or accepting user-made content packs is one attribute.

With no options the picker accepts JSON or CSV and tells them apart by the file's extension; a modifier pins one format.

<button x-import>Open file</button>
<button x-import.json>Open JSON</button>
<button x-import.csv>Open CSV</button>

The result arrives as a manifest:import event on the trigger element, so it can be handled right where the button is. The event's detail carries data (the parsed content), format, source (when one was targeted), and file (name, size, type). A file that can't be read fires manifest:import-error instead, with format and error.

Here's the whole loop in one place. Download the table as a CSV, then import the file back — a second table appears showing exactly what the file contains, parsed fresh from disk. Change a name or a number in a spreadsheet app first if you'd like proof it isn't a trick.


<div x-data="{ team: [
    { name: 'June',  role: 'Design',      hours: 12 },
    { name: 'Marco', role: 'Engineering', hours: 9 },
    { name: 'Priya', role: 'Research',    hours: 14 }
], imported: null, filename: '' }">
    <table>
        <thead><tr><th>Name</th><th>Role</th><th>Hours</th></tr></thead>
        <tbody>
            <template x-for="(m, i) in team" :key="i">
                <tr>
                    <td x-text="m.name"></td>
                    <td x-text="m.role"></td>
                    <td x-text="m.hours"></td>
                </tr>
            </template>
        </tbody>
    </table>
    <button x-export="{ format: 'csv', data: team, filename: 'team.csv' }">Download CSV</button>
    <button x-import.csv
        @manifest:import="imported = $event.detail.data; filename = $event.detail.file.name">
        Import a CSV
    </button>

    <template x-if="imported">
        <div>
            <small x-text="'From ' + filename + ':'"></small>
            <table>
                <thead><tr><th>Name</th><th>Role</th><th>Hours</th></tr></thead>
                <tbody>
                    <template x-for="(m, i) in imported" :key="i">
                        <tr>
                            <td x-text="m.name"></td>
                            <td x-text="m.role"></td>
                            <td x-text="m.hours"></td>
                        </tr>
                    </template>
                </tbody>
            </table>
        </div>
    </template>
</div>

Replacing a Data Source

Pass a source to pour the file's content into a $x data source, replacing what's there. Anything on the page bound to that source updates on its own. A bare string value is shorthand for the same thing.

<button x-import="{ source: 'products' }">Load products</button>
<button x-import="'products'">Load products</button>

Together with export, backup and restore is a few lines — no server, no account:

<button x-export="{ format: 'json', source: 'saves', filename: 'backup.json' }">Back up</button>
<button x-import="{ format: 'json', source: 'saves' }">Restore</button>

Import Magic

$import(opts) opens the picker from inside an expression and hands back the parsed data — or null when the visitor closes the dialog without choosing. Same options as the directive.

<button @click="save = await $import({ format: 'json' })">Load save</button>

CSV Parsing

A CSV file comes back as a list of objects, one per row, named by the header row. Quotes, and commas or line breaks inside values, follow the standard CSV rules, and the separator — comma, semicolon, or tab — is detected automatically from the first line. Values arrive typed: numbers become numbers, true/false/null become real values, cells that look like JSON ({…} or […]) are parsed, and everything else stays text. A file produced by the CSV export imports back exactly.

Import Options

Property Type Default Description
format String inferred 'json' or 'csv'. Left out, it's inferred from the file extension, defaulting to JSON.
source String $x data source to replace with the file's content.
accept String by format Override which file types the picker dialog offers.