Diagram - Export

Export rendered Diagram templates to SVG, PNG, JPEG, and browser print, or create detached portable output.

#Export rendered templates

Loading Demo...

Choose SVG or PNG and a color scheme to export the review cards. The captured content includes their HTML, image, icon and nested SVG. Create SVG prepares a result, Download SVG requests a file download, and Download PNG creates and downloads an image. A browser download request does not confirm that a file was saved.

Choose SVG or PNG and an export color scheme to try the rendered cards above. Create SVG prepares the output; Download SVG saves it. Download PNG creates and saves the image in one action. The avatar is a bundled same-origin PNG image.

<script setup lang="ts">
import { Diagram, type DiagramModel } from '@primeui/vue-diagram';
import { DiagramOutputUI } from './components/primeui/diagram/output';

const props = defineProps<{ model: DiagramModel; pageId: string }>();
</script>

<template>
    <Diagram.Root :value="props.model" :page-id="pageId">
        <Diagram.NodeDef node-type="employee" v-slot="{ node }">
            <article>{{ node.data?.name }}</article>
        </Diagram.NodeDef>
        <Diagram.Viewport label="Directory" auto-fit style="height: 28rem" />
        <DiagramOutputUI filename="directory" />
    </Diagram.Root>
</template>

The root's value is a one-way reload input. The output panel reads the current root model, page and selection, including accepted edits. Do not bind v-model:value or replace the input after every edit.

A rooted output control selects its root's unique directly owned viewport. Nested roots do not contribute candidates. Zero or multiple viewports fail when output starts. The root supplies the current model, page, and selection. Use First Diagram for store creation, styles, and disposal.

The copied DiagramOutputUI provides the complete panel. This component accepts a valid model containing employee nodes and its page ID. Follow Setup for styles, licensing and copied UI.

#Select a viewport explicitly

DiagramViewportHandle.exportSource() returns a stable DiagramRenderedExportSource bound to that viewport and root. Reading the handle starts no capture. Capture requires a mounted viewport, and the handle expires when its viewport is destroyed. Pass source to choose among several views or connect an independent transport. Explicit source takes precedence over rooted discovery.

<script setup lang="ts">
import { computed, shallowRef } from 'vue';
import { Diagram, type DiagramStore, type DiagramViewportHandle } from '@primeui/vue-diagram';
import { DiagramSvgExport } from '@primeui/vue-diagram/export';

const props = defineProps<{ store: DiagramStore }>();
const view = shallowRef<DiagramViewportHandle | null>(null);
const svg = shallowRef<InstanceType<typeof DiagramSvgExport> | null>(null);
const source = computed(() => view.value?.exportSource());
</script>

<template>
    <Diagram.Root :store="props.store" page-id="page">
        <Diagram.Viewport ref="view" label="Workflow" style="height: 24rem" />
        <DiagramSvgExport ref="svg" :source="source" :options="{ pageIds: ['page'] }" />
        <button type="button" :disabled="!source" @click="svg?.download()">Download SVG</button>
    </Diagram.Root>
</template>

The supplied store must contain page page. Keep node definitions under the same root. The same optional source prop is available on DiagramRasterExport, DiagramPrint, DiagramOutputHost and both copied DiagramOutputUI variants. The output model passes that selection to its SVG, raster and print transports.

For SVG data, call the Vue function after the viewport mounts:

import { type DiagramStore, type DiagramViewportHandle } from '@primeui/vue-diagram';
import { exportDiagramSvg } from '@primeui/vue-diagram/export';

async function createSvg(store: DiagramStore, view: DiagramViewportHandle) {
    return exportDiagramSvg(store.getSnapshot(), { pageIds: ['page'] }, view.exportSource());
}

The supplied model must equal the selected root's current validated model, including its ID. Equivalent copies are accepted. Historical or different content fails. Capture freezes committed geometry, templates, CSS, theme and options from one source revision. Changes during preparation fail the job.

#Choose the export color scheme

Rooted output controls accept the setting through baseOptions:

<DiagramOutputUI :base-options="{ colorScheme: 'dark' }" />

Remove colorScheme to follow the source again. Only 'light', 'dark', and omission are valid. A detached explicit mode fails with render-source-required. The existing theme?: Partial<DiagramExportTheme> remains a portable paint object. A source-backed paint object, including {}, fails with render-unsupported-theme, even when colorScheme is supplied.

CSS mode selection retains the current preset, palettes, and source-local tokens. It supports accessible custom properties, light-dark(), generated content, nested SVG, and registered system, media, class, and attribute theme conditions. Registered compound conditions require a lossless mapping that preserves their structure and non-theme predicates. An unresolved selector, condition, or required stylesheet fails with render-theme-unavailable and a specific reason. The exporter does not infer arbitrary unregistered application theme switches.

Application bindings such as isDark() keep their captured values and branches, including text, conditional components, image URLs, SVG attributes, and inline paint literals. Use CSS tokens for template colors that must change with the export color scheme. CSS selection does not rerun application state.

#Capture mounted Vue templates

Vue source capture uses mounted in-tree content and its existing application context. It does not mount a second Vue tree or run unmounted node definitions for export. If required framework content is offscreen or unavailable under virtualization, capture fails with diagnostics. Make the needed content available in the mounted view, or provide an explicit portable representation through detached export.

Capture freezes committed geometry, template DOM, CSS, theme and options from one source revision. An active editing preview or a source change during preparation invalidates the result. Editor chrome and temporary selection elevation are excluded. Keep the source mounted and stable until the job settles.

#Wait for async template data

Loading Demo...

Choose Load data and create SVG. The example loads the reviewer data, updates the card, waits for Vue's DOM update, and calls DiagramSvgExport.export(). A request or export failure leaves download unavailable and displays feedback.

Await application requests, lazy descendants and any child readiness promises before capture. After changing refs, await Vue's nextTick(). That tick confirms the pending Vue DOM update; it does not settle future requests, arbitrary timers or asynchronous child work. Export then checks its acquired image/font resources and decoding. Fixed delays and network idleness do not establish content readiness.

Handle ok: false and display diagnostics before saving output. Missing fonts, inaccessible images, unavailable framework content, cancellation and resource limits can still fail after application data is ready. These requirements apply to source-backed SVG, raster and print preparation.

Export waits for these resources after it acquires the template content:

  • Images with captured sources, including image bytes and decoding.
  • Used fonts with accessible embeddable faces, their loading and coverage checks, and the capture document's document.fonts.ready.
  • Async resource providers and adapters used by the export job.

These rules apply to source-backed SVG, PNG, and shared browser print preparation, including Save as PDF in the browser print dialog. Diagram does not expose a direct PDF file export. Missing fonts, inaccessible images, and failed decoding still fail output after your data is ready. Handle ok: false and show the diagnostics instead of saving partial output.

#Backgrounds, resources, and limits

Leave background unset to use the export render's canvas after theme selection. An explicit white, transparent, or supported custom color replaces that backdrop without changing content colors. PNG precedence is raster.background, then options.background, then the themed canvas. SVG and print use options.background before the canvas. Print paper and margins remain separate. JPEG still requires an explicit opaque hexadecimal raster.background.

Capture embeds the selected render's used fonts, images, CSS resources, and SVG definitions. Explicit fonts and resourceProvider keep their keyed precedence. Every supplied entry is validated. Missing or inaccessible bytes, fallback fonts, failed decode, cancellation, and exceeded limits fail output. A broken explicit resource never falls back to automatic lookup.

Closed shadow roots, canvas, video, native widgets, and unsupported effects have no fidelity guarantee. Identified unsupported content fails. Standalone SVG contains captured HTML through foreignObject; arbitrary SVG editor compatibility is not guaranteed.

Source-backed calls reject supplied geometry, measureText, styles, and symbols, including empty overrides, with render-unsupported-option. Root-level symbol registration remains valid because the source renders it. Use existing template definitions without export-specific adapters. Explicit keyed adapters may replace a node body deliberately; their literal graphics retain portable paints, and their failure fails output.

Membership, bounds, scale, selection, cancellation, and resource budgets follow the contracts below. Explicit scale is honored without automatic downscaling. Source destruction, input changes, or cancellation release owned preparation. Prepared print output becomes invalid when the source, theme, or options change.

#Save files with a headless browser

Use the headless recipe's HeadlessExportPage.vue, diagram-headless-model.json and export.mjs together in a Vue application and Node.js runner. The complete source files appear below. Add your own client-rendered route for the page, copy its model to public/diagram-headless-model.json, and retain a mounted viewport with nonzero dimensions. Load the Diagram styles from Setup.

With the application running and Playwright available in the runner's project, the recipe command is:

node export.mjs http://127.0.0.1:4100/diagram-export ./diagram-output chrome

/diagram-export is the application route you create. It is not an extra documentation route. The runner saves diagram.svg and diagram.png; confirm both First loaded card and Last loaded card appear. PDF export is planned for V2; browser print remains available separately through Print. Firefox and WebKit runs also save SVG and PNG.

The page's window.diagramHeadless.run(format, options?) bridge belongs to the recipe, not the Diagram API. It loads and validates the model, replaces the one-way value, awaits nextTick(), then acquires current transport handles. Add application requests and child readiness before this render wait.

Run jobs sequentially. The optional final timeout defaults to 45,000 ms and bounds launch and then the export job separately. Diagram's own limits still apply. Browser and print resources close in cleanup; earlier format files can remain after a later failure. Publish the output set only after the process succeeds, and own partial-file cleanup. Node.js alone cannot capture Vue templates or encode browser raster output.

#Mount the export page

Mount HeadlessExportPage.vue on a dedicated application route. Load the global Diagram styles and license before mounting. Put the JSON fixture at public/diagram-headless-model.json; its URL is /diagram-headless-model.json.

The page exposes an application-owned window.diagramHeadless.run bridge. It is not a Diagram API. Wait for application data and Vue rendering before starting output, keep the source mounted, and run one job at a time. The recipe releases its bridge and cancels work on unmount.

#HeadlessExportPage.vue

<script setup lang="ts">
import { deserializeDiagramModel, type DiagramModel, Diagram } from '@primeui/vue-diagram';
import { DiagramPrint, DiagramRasterExport, DiagramSvgExport, prepareDiagramPrintDocument, type DiagramExportOptions } from '@primeui/vue-diagram/export';
import { shallowRef, onBeforeUnmount, nextTick } from 'vue';

const emptyModel: DiagramModel = {
    schemaVersion: 1,
    id: 'headless',
    pages: [{ id: 'page', name: 'Export' }],
    layers: [{ id: 'layer', pageId: 'page', name: 'Cards' }],
    nodes: [],
    connectors: []
};

const svg = shallowRef<InstanceType<typeof DiagramSvgExport> | null>(null);
const raster = shallowRef<InstanceType<typeof DiagramRasterExport> | null>(null);
const print = shallowRef<InstanceType<typeof DiagramPrint> | null>(null);
const value = shallowRef<DiagramModel>(emptyModel);
const options = shallowRef<DiagramExportOptions>({ pageIds: ['page'], padding: 0 });
const controllerValue = new AbortController();
const loaded = shallowRef(false);
const busy = shallowRef(false);
const bridge = { run: (format: 'svg' | 'png' | 'print', currentOptions: DiagramExportOptions = {}) => runDemo(format, currentOptions) };
const host = (typeof window === 'undefined' ? undefined : window) as
    | (Window & {
          diagramHeadless?: typeof bridge;
      })
    | undefined;

if (host) host.diagramHeadless = bridge;

onBeforeUnmount(() => {
    controllerValue.abort();
    if (host?.diagramHeadless === bridge) delete host.diagramHeadless;
});

async function runDemo(format: 'svg' | 'png' | 'print', currentOptions: DiagramExportOptions) {
    if (busy.value) throw new Error('Run one export at a time.');

    if (!['svg', 'png', 'print'].includes(format)) throw new Error('Invalid export request.');

    busy.value = true;

    try {
        if (!loaded.value) {
            const response = await fetch('/diagram-headless-model.json', { signal: controllerValue.signal });

            if (!response.ok) throw new Error(`Model request returned HTTP ${response.status}.`);

            const checked = deserializeDiagramModel(await response.text());

            if (!checked.ok) throw new Error(JSON.stringify(checked.diagnostics));

            value.value = checked.value;
            loaded.value = true;
        }

        options.value = { pageIds: ['page'], padding: 0, ...currentOptions };
        await nextTick();
        await nextTick();
        if (controllerValue.signal.aborted) throw new Error('Export page was destroyed.');

        if (format === 'svg') {
            const currentSvg = svg.value;

            if (!currentSvg) throw Error('SVG output is unavailable.');

            const result = await currentSvg.export();

            if (!result.ok) throw new Error(JSON.stringify(result.diagnostics));

            if (result.value.length !== 1) throw new Error('This recipe requires one SVG page.');

            return { svg: result.value[0].svg };
        }

        if (format === 'png') {
            const currentRaster = raster.value;

            if (!currentRaster) throw Error('PNG output is unavailable.');

            let bytes: number[] | undefined;
            const result = await currentRaster.export(async ({ blob }) => {
                if (bytes) throw new Error('This recipe requires one PNG chunk.');

                bytes = Array.from(new Uint8Array(await blob.arrayBuffer()));
            });

            if (!result.ok) throw new Error(JSON.stringify(result.diagnostics));

            if (!bytes) throw new Error('PNG export returned no chunk.');

            return { png: bytes };
        }

        const printer = print.value;

        if (!printer) throw Error('Print output is unavailable.');

        const result = await printer.prepare();

        if (!result.ok) throw new Error(JSON.stringify(result.diagnostics));

        const prepared = await prepareDiagramPrintDocument(result.value, { resources: [], fonts: [] }, document, controllerValue.signal);

        try {
            const html = prepared.frame.contentDocument?.documentElement.outerHTML;

            if (!html) throw new Error('Prepared print document is unavailable.');

            return { html: `<!doctype html>\n${html}`, pageCount: prepared.pageCount };
        } finally {
            prepared.close();
            printer.cancel();
        }
    } finally {
        busy.value = false;
    }
}
</script>

<template>
    <div class="diagram-demo-host headless-export-page">
        <Diagram.Root page-id="page" :value="value" :editing-options="{ readOnly: true }">
            <Diagram.NodeDef v-slot="{ node }" :node-type="'export-card'"
                ><article class="card">{{ node.data?.['text'] }}</article></Diagram.NodeDef
            >
            <Diagram.Viewport></Diagram.Viewport>
            <DiagramSvgExport ref="svg" :value="value" :options="options"></DiagramSvgExport>
            <DiagramRasterExport ref="raster" :value="value" :options="options" :raster="{ format: 'png', background: '#ffffff' }"></DiagramRasterExport>
            <DiagramPrint ref="print" :value="value" :options="options"></DiagramPrint>
        </Diagram.Root>
    </div>
</template>

<style scoped>
.diagram-demo-host {
    display: block;
}
.p-diagram-viewport {
    display: block;
    width: 960px;
    height: 600px;
}
.card {
    box-sizing: border-box;
    width: 100%;
    height: 100%;
    padding: 8px;
    border: 1px solid #1e293b;
    background: #e0f2fe;
    color: #0f172a;
    font: 14px Geist;
}
</style>

#public/diagram-headless-model.json

{
    "schemaVersion": 1,
    "id": "headless",
    "pages": [{ "id": "page", "name": "Export" }],
    "layers": [{ "id": "layer", "pageId": "page", "name": "Cards" }],
    "nodes": [
        { "id": "first", "type": "export-card", "pageId": "page", "layerId": "layer", "bounds": { "x": 0, "y": 0, "width": 160, "height": 80 }, "rotation": 0, "ports": [], "labels": [], "data": { "text": "First loaded card" } },
        { "id": "last", "type": "export-card", "pageId": "page", "layerId": "layer", "bounds": { "x": 200, "y": 120, "width": 160, "height": 80 }, "rotation": 0, "ports": [], "labels": [], "data": { "text": "Last loaded card" } }
    ],
    "connectors": []
}

#Run the Playwright script

Use a Node.js project with playwright and matching browser binaries available. The chrome choice requires Google Chrome installed on the host. Save the runner below as export.mjs in that project and point it at your export route:

The optional fourth argument is a positive integer timeout in milliseconds, defaulting to 45000. Browser launch has that timeout. After launch, a Node.js job timer bounds the whole export, including unresolved page.evaluate() calls and application readiness. Expiry rejects the process with a timeout error and closes the browser in finally. This timer does not change Diagram's export limits.

#export.mjs

import { chromium, firefox, webkit } from 'playwright';
import { mkdir, writeFile } from 'node:fs/promises';
import { resolve } from 'node:path';

const [url, directory = 'diagram-output', browserName = 'chrome', timeout = '45000'] = process.argv.slice(2);
const engines = { chrome: chromium, firefox, webkit };
const timeoutMs = Number(timeout);

if (!url || !Object.hasOwn(engines, browserName) || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1) throw new Error('Usage: node export.mjs <page-url> [output-directory] [chrome|firefox|webkit] [timeout-ms]');

const browser = await engines[browserName].launch({ headless: true, timeout: timeoutMs, ...(browserName === 'chrome' ? { channel: 'chrome' } : {}) });
let timer;

try {
    await Promise.race([
        saveFiles(),
        new Promise((_, reject) => {
            timer = setTimeout(() => reject(new Error(`Headless Diagram export timed out after ${timeoutMs} ms.`)), timeoutMs);
        })
    ]);
} finally {
    clearTimeout(timer);
    await browser.close();
}

async function saveFiles() {
    const page = await browser.newPage({ viewport: { width: 1280, height: 800 }, deviceScaleFactor: 1 });

    page.setDefaultTimeout(timeoutMs);
    await page.goto(url, { waitUntil: 'domcontentloaded' });
    await page.waitForFunction(() => typeof window.diagramHeadless?.run === 'function');
    await mkdir(directory, { recursive: true });

    for (const format of ['svg', 'png']) {
        const result = await page.evaluate((kind) => window.diagramHeadless.run(kind), format);

        await writeFile(resolve(directory, `diagram.${format}`), format === 'svg' ? result.svg : Buffer.from(result.png));
    }
}

Use firefox or webkit as the third argument to save SVG and PNG in those engines. Playwright WebKit is a patched engine build, not Safari. The Chrome run uses channel: 'chrome', not bundled Chromium. See Playwright's browser distinctions.

#Tested node-count boundaries

The following measurements describe the Angular reference recipe; Vue results require the same workload, browser and export conditions before comparison.

These are largest-tested successes and observed failures, not universal maximums. The reference runs used Playwright 1.63.0, Angular 22.2.2 and Node.js 22.22.3 on macOS 26.6.2, Apple silicon, with a 1,280 × 800 browser viewport and device scale factor 1. Browsers ran headlessly and sequentially on 28 October 2026.

The node-count fixture uses unconnected 160 × 80 HTML cards with a border, a background, and a short Geist text label. Cards occupy a grid with 200-unit spacing. An explicit square page frame and scene scale keep SVG and PNG output at 1,024 × 1,024 pixels as the node count changes. Print fits the same page onto one A4 sheet. Dense output reduces text legibility even when all cards are present.

BrowserSVG serializationPNG with verified card paint
Google Chrome 156.0.8078.731,024 nodes saved; 2,048 rejected by the aggregate resource budget1,024 nodes saved; 2,048 rejected by the same budget
Playwright Firefox 155.01,024 nodes serialized with all card content; 2,048 rejected by the aggregate resource budget256 nodes saved; 512 and 1,024 returned success but saved fully transparent PNGs
Playwright WebKit 26.6, build 23591,024 nodes saved; 2,048 rejected by the aggregate resource budget1,024 nodes saved; 2,048 rejected by the same budget

Resource rejections report render-resource-limit. The Firefox transparent images have valid PNG dimensions and no exporter error. Opening their saved SVG counterparts directly in Firefox still painted the cards. The observed loss occurs in raster output; the underlying engine cause was not isolated. Do not treat ok: true or a nonempty file as proof that the expected paint survived. Validate saved files in the intended viewer. SVG serialization alone does not establish viewer parity for embedded HTML.

#Tested PNG dimension boundaries

The following measurements describe the Angular reference recipe; Vue results require the same workload, browser and export conditions before comparison.

These trials keep two cards and the same template, move the second card to the far corner, and vary an explicit page frame at scene scale 1. They vary output dimensions separately from node count. The saved PNG dimensions and card paint were checked after writing the files.

BrowserLargest tested square successObserved square failureTested axis boundary
Google Chrome 156.0.8078.732,816 × 2,8163,072 × 3,072, render-resource-limit during SVG image decoding admission8,192 × 204 saved; 8,193 × 204 rejected with raster-plan-failed
Playwright Firefox 155.02,816 × 2,8163,072 × 3,072, same admission failure8,192 × 204 saved; 8,193 × 204 rejected with raster-plan-failed
Playwright WebKit 26.6, build 23592,816 × 2,8163,072 × 3,072, same admission failure8,192 × 204 saved; 8,193 × 204 rejected with raster-plan-failed

The square failures hit Diagram's existing estimated live-memory checks. The axis failure hits its existing 8,192-pixel drawing-surface cap. These trials did not establish native browser canvas maxima or a safe size for arbitrary content. The raster planner also accounts for resources and intermediate surfaces, so the nominal surface-pixel ceiling is not a promise that every image below it fits.

Nested HTML, more CSS, connectors, labels, images, fonts, multiple pages, and different browser or operating-system versions change these boundaries. The fixture has no external images, connectors, or nested application components. Keep existing limits in place, handle diagnostics, and use bounded tile streaming when a single raster surface does not fit. Tiling still has aggregate limits and requires partial-output cleanup. It is not an automatic large-image stitcher.

#Arrange the copied output controls

Loading Demo...

The example imports DiagramOutputHost from @primeui/vue-diagram/export and five presentation parts from local @ui/diagram/output. One host owns the SVG, raster, and print transports; its children share DiagramOutputModel. Format/scope and actions sit together, relevant settings form the next row, and status has its own position. The host belongs inside the destination Diagram root, which supplies the current model, page and selection.

For a complete starter panel, use DiagramOutputUI from the same copied entry. It composes the same parts and transport scope with optional settings/diagnostics disclosures. Do not nest an additional output host around a complete OutputUI. The example selects the review shape through the public Session so Selection has an explicit source.

The panel offers SVG, PNG, JPEG and fit-mode browser print. SVG and print can include all pages; single-image raster output uses the current page or selection. SVG page downloads and browser print requests are separate user actions. baseOptions supplies supported export settings, such as padding and colorScheme. The model owns scope and scale. Background starts unset. Canvas default preserves baseOptions.background when supplied, or uses the themed canvas. A chosen background overrides the base background.

Both presets offer Follow app, Light, and Dark under Color scheme. Follow app stores undefined and omits colorScheme, including an earlier explicit base value. Light and Dark set their corresponding values. Initial and later baseOptions.colorScheme inputs synchronize the model and picker, including removal. Selection changes cancel preparation and invalidate prepared output. The picker never modifies the portable theme object. Choose an opaque background for JPEG. PrimeOne and Tailwind provide their own copied UI; importing the panel does not change export authority or add formats.

Use the focused APIs below when your application needs fixed bounds, explicit JPEG quality, tile streaming or actual-scale print pagination beyond the panel's controls.

#SVG scope and per-page downloads

The remaining demos intentionally export detached portable scenes. Their transports sit outside the Diagram root and omit source. Their rectangle adapters match empty node templates, and native captions export separately. Detached functions remain usable without viewport DOM, including SSR workflows.

Loading Demo...

Create Page content output to include the shape at x=1,000, even while the viewport shows Review and Approve. The hidden layer is omitted. Review + approve includes those two shapes and their between-selected connector. Fixed page bounds clips output to an explicit 640 × 360 frame plus padding. All pages produces two independent vector documents. Choose a page and download one SVG per action; no combined multipage SVG file is implied.

createDiagramExportScene(model, options) returns { ok: true, value: scene, diagnostics } or { ok: false, diagnostics }. serializeDiagramSvg(scene, limits) serializes each scene page as a DiagramSvgDocument with pageId, svg, bytes, width and height. exportDiagramSvg(model, options) combines scene construction and serialization. Core projection/serialization need no viewport DOM and do not fetch resources themselves.

DiagramSvgExport accepts a snapshot through value, stable options and a filename. export() exposes the result; download(pageId?) exports current inputs again and downloads one page, defaulting to the first exported page. An unavailable requested page sets downloadError. Filenames have path/control characters removed and are bounded; the application should choose distinct names for pages. The transport creates/revokes object URLs and emits downloaded after requesting the browser download. That event does not confirm the user saved the file.

#Choose membership and bounds

OptionContract
pageIds / layerIdsOmit to include all eligible pages/layers; requested IDs must exist. They filter membership independently of viewport visibility.
selection.idsExplicit saved node/connector IDs, not an automatic read of current UI selection. Unknown IDs reject.
selection.descendantsOptional container descendants; disabled unless requested.
selection.edges'selected' includes explicitly selected eligible connectors; 'between-selected' also includes edges between included nodes. A selected connector whose node endpoint is excluded is omitted with a diagnostic. Point endpoints remain explicit coordinates.
boundsDefault 'content', framing paint visible through clips at scene scale. Empty content uses a 1 × 1 frame. 'page' requires pageBounds for each selected page and sets an explicit crop.
padding / scaleDefault 8 world units / 1. Padding expands the frame; scale sets output units/pixels per world unit. Scale is finite 0.001–1,000; padding is finite 0–65,536.
backgroundSource-backed default is the themed canvas; detached default is '#ffffff'; use 'transparent' for transparent output. Explicit background replaces the backdrop without changing content paint.
portsDefault off. Physical ports are opt-in; editing/focus/resize handles remain excluded.

Hidden layers/owners and children hidden by collapsed structure are excluded according to scene visibility. Container clips and centered-inside label clips are preserved as world polygons; viewport membership never replaces those clips. Export does not reveal hidden content merely because it exists in the Store.

DiagramExportPage.bounds describes the output frame and its whitespace. clipBounds is the explicit intentional page crop. Content exports omit clipBounds; bounds: 'page' supplies it. If you construct a cropped scene manually, set clipBounds. A frame alone does not imply a crop. SVG and raster output preserve the explicit crop and ancestor clips.

A portable scene retains compiled viewport-stroked primitives, their transforms, and their clips even when no paint reaches an ancestor clip at the current output scale. Their presence does not mean that they paint at every scale. Print reevaluates clipped paint at its final scale and can expand an uncropped content frame while preserving whitespace. Scene data remains portable through JSON serialization; print does not depend on hidden object identity.

#Capture measured geometry and routes together

For detached export, omit geometry to derive geometry from the saved model. Source-backed export acquires committed source geometry and rejects this override. DiagramExportGeometry can supply measurements, nested portMeasurements, labelMeasurements, rotationOrigins, shapes, connectorKinds, resolved routes and synchronous routing options. The host must capture them against the same model snapshot and pass its modelId. A different model ID, unknown owner/label/port, invalid route or unsupported geometry field rejects.

Do not pass an active drag/connection preview as saved output. Wait for intended edits and deferred routes to settle before capturing their approved geometry. The engine does not infer missing offscreen DOM measurements. Mounted measurements can be incomplete under virtualization; supply approved measurements for the content you require or use authored sizes. measureText can supply awaited whole-label width/height/baseline metrics for included visible labels before projection. Font loading or content replacement may require a new capture.

#Connector widths in saved output

For detached export, DiagramExportOptions.connectorStrokeScaling defaults to 'viewport'. Connector paths, terminal decoration outlines, and midpoint outlines use a 2px width in output CSS pixels. A per-connector styles[id].strokeWidth supplies a numeric override. Use 'world' when widths must scale with output geometry.

import { type DiagramExportOptions } from '@primeui/vue-diagram/export';

const options: DiagramExportOptions = {
    connectorStrokeScaling: 'viewport',
    styles: { 'review-flow': { strokeWidth: 1.5 } }
};

Detached export does not capture live CSS, including --p-diagram-connector-stroke-width. Source-backed export captures the rendered widths. Pass the custom width through export options. SVG serialization and PNG or JPEG rasterization preserve the selected scaling policy. Raster resolution controls pixel density without changing the output CSS width. Decoration sizes, fills, and dash lengths retain their geometry.

Custom portable graphics can set DiagramExportPaint.strokeScaling to 'world' or 'viewport'. Native connector primitives receive the selected connector policy; other primitives retain their own paint. Browser print uses the same export options and preserves readable outlines when fitting to a sheet, including browser Save as PDF.

#Transparent PNG

Loading Demo...

PNG is lossless and can preserve transparency. The example supplies 'transparent' in both scene and raster settings, with a solid shape body. Increasing scene scale increases the image dimensions; it does not use devicePixelRatio. Image dimensions are the ceiling of frame width/height multiplied by scale. A PNG quality option is invalid.

DiagramRasterExport accepts the model, scene options, raster options and filename stem. download() supports one selected page in single-image mode; it normalizes .png/.jpg and rejects a multipage result or tile mode. export(consumer) supports explicit awaited chunk handling. result, busy, downloadError, exported and downloaded make output/error ownership visible.

#JPEG with an opaque background

Loading Demo...

JPEG requires an explicit opaque hexadecimal background in DiagramRasterOptions, even when the scene has a background. Quality is 0–1, default 0.92; encoder results and file sizes depend on the browser. Quality changes compression, while scene scale changes pixel dimensions. Transparent JPEG requests reject. The example uses '#ffffff' and separate 92%/50% quality choices.

The browser renderer decodes embedded resources, draws the scene and encodes chunks through Canvas 2D. Fonts, glyph coverage, shaping and platform encoders can affect output. A successful scene does not guarantee a resource will decode or a Canvas context will remain available; display transport diagnostics and errors before reporting success.

#Represent custom HTML content explicitly

Loading Demo...

The order card uses a Vue HTML template. Its adapter supplies a rectangle and two text primitives from the same application data. Disable Supply adapter to see the missing-adapter error. This detached call does not read HTML or consumer CSS. Use a rendered source to capture the template automatically.

Declare every custom template/component type in customTypes, even when its name matches a builtin. Provide a keyed adapters function that returns 1–4,096 inert vector/text/image graphics for that node body. Each adapter receives the immutable model, node, projected geometry, export theme and cancellation signal. Coordinates are local to the body; transforms and clips are applied by the scene. Authored ordinary labels still follow the owner, so avoid duplicating them inside the adapter.

Adapters may be asynchronous and must cooperate with cancellation. A missing adapter, invalid primitive, unresolved symbol or exhausted resource budget fails scene creation instead of silently dropping custom content. Use symbols for validated custom symbol definitions, or an adapter for a deliberate portable representation.

For detached export, theme overrides nodeFill, nodeStroke, connector, text, surface, muted, fontFamily and fontSize; styles applies validated authored paint by entity/label ID. Core does not resolve the current CSS theme. Paint accepts supported hexadecimal colors, none or transparent, not CSS variables or external paint URLs. Font family is one literal name, not a CSS family stack.

The detached default export family is 'sans-serif'. The editor's bundled Geist font and --p-diagram-font-family setting are browser styles; they do not change this default or embed a font in output. Set theme.fontFamily and supply matching font resources when your output needs a specific face.

For detached export, only resourceProvider({ key, kind, signal, maxBytes }) may acquire opaque image/font resources. It returns approved PNG or WOFF2 MIME/data content; The exporter never fetches a URL by itself. The application owns URL access, provider cancellation and errors. The scene validates and embeds bounded resource data. fonts maps literal family/weight/italic faces to resource keys. Missing matching faces produce font-fallback warnings. embeddedFontsOnly indicates matching resources were supplied; it does not certify decoding, glyph coverage or identical shaping. Browser transports verify decoding and may fail after portable projection succeeds.

Links default to omission with diagnostics where supplied. links: 'safe' includes validated explicit HTTP(S) or simple mailto navigation destinations. SVG links are inert navigation, not script execution; raster output cannot retain clickable navigation. Resource embedding and safe links do not promise visual parity in every viewer.

#Bounded tile streaming

Loading Demo...

This example renders a fixed 640 × 360 page in 128-pixel tiles. The application awaits each consumer drain, releases the old object URL, and retains only the final preview. It deliberately does not save a whole image. Try the 1 KiB memory budget to inspect admission failure. Cancel discards the local preview and reports received partial output.

createDiagramRasterPlan(scene, options) produces the portable single/tile plan. Raster planning and rendering preserve each page's explicit clipBounds crop, including tiled output. getDiagramRasterTile(plan.pages[pageIndex], tileIndex) gives exact pixels and corresponding world rectangles. renderDiagramRaster(scene, options, consumer, signal, document) renders in a browser and awaits consumer({ tile, blob }) for backpressure. Place tiles at their destination pixel rectangles without resizing, overlap or added seams. Edge tiles may be smaller; their world rectangle can extend to the final pixel beyond fractional bounds.

mode defaults to 'single'; 'tiles' uses tileSize, default 1,024 output pixels. Tiling is a stream of separate images, not an automatic large-image stitcher or file. Your consumer owns persistence, Blob retention and a final commit marker. A later cancellation, consumer rejection, encoding/resource error or deadline can occur after earlier chunks were delivered. Commit persisted output only after ok: true; discard or explicitly mark partial output on failure. Built-in download() cannot save a tiled stream.

Raster ceilingDefault / hard maximum
surfaceAxis / surfacePixels8,192 per drawing-surface axis / 16,777,216 surface pixels
pageAxis / totalPixels / tiles1,000,000 per page axis / 1,000,000,000 aggregate pixels / 4,096 tiles
liveBytes268,435,456 estimated working bytes; 24 bytes per surface pixel plus encoded/decoded resource and expanded font estimates
outputBytes / timeoutMs64,000,000 aggregate encoded bytes / 30,000 ms

Scene limits separately default to 100,000 records, 500,000 primitives, 8,000,000 text units, 16,000,000 resource bytes, 256 resources, 64,000,000 serialized bytes and 30,000 ms. Options may lower ceilings. These are admission bounds, not universal browser Canvas/RAM capacity or real-time guarantees. Smaller tiles reduce surface working memory; they do not remove total-pixel, output-byte, tile-count or resource limits. Native encoding itself may not yield promptly to cancellation.

The Vue transports cancel on input/option changes or destruction and reject stale completions. Direct API callers own AbortController, provider/consumer cancellation, timers, object URLs and temporary storage. Keep a snapshot and stable settings for each run, show errors separately from download feedback, and never report partial chunks as a completed image.

Browser print · BPMN XML

#Connector paint order

DiagramExportOptions.edgeLayer?: DiagramEdgeLayer accepts 'below' or 'above'. With a rendered source, omission inherits that viewport's current setting. An explicit matching value is accepted; a conflicting value fails with render-invalid-options. Select an explicit viewport.exportSource() when a root owns multiple viewports. An export option does not override the source viewport.

Detached output defaults to 'below' and accepts either explicit value. 'below' places each connector immediately below its highest endpoint body. 'above' paints strokes and attached decorations above node bodies inside each authored model layer. Owner captions remain adjacent to their connector; default/layer captions and ports stay above strokes. Layer isolation and clipping remain effective.

import { type DiagramExportOptions } from '@primeui/vue-diagram/export';

const options: DiagramExportOptions = { edgeLayer: 'above' };

SVG, PNG and print use committed order. They exclude temporary selection/drag elevation and editor chrome. An active model preview still fails capture. A viewport policy change invalidates pending capture and prepared print; prepare again with the new setting. Capture uses the acquired viewport policy. Mounted-content, font, image, theme and colorScheme validation still applies; required unavailable Vue content fails capture.