Documentation

Data & assets

Reactive data

The whole point of authoring in Vue is that your data drives the document. v-for, v-if, computed and slots all work - the renderer walks the same tree Vue builds, so anything that produces a tree produces a PDF.

<script setup lang="ts">
import { computed } from "vue";
import { Document, Page, Row, Text, Divider } from "@jasy/vue";

const props = defineProps<{ lines: { id: number; name: string; price: number }[] }>();
const total = computed(() => props.lines.reduce((sum, l) => sum + l.price, 0));
</script>

<template>
  <Document :size="11">
    <Page :size="'A4'" :margin="48" :gap="8">
      <Row v-for="line in lines" :key="line.id" :justify="'between'">
        <Text>{{ line.name }}</Text>
        <Text>{{ line.price }} EUR</Text>
      </Row>

      <Divider />
      <Row :justify="'between'">
        <Text bold>Total</Text>
        <Text bold>{{ total }} EUR</Text>
      </Row>
    </Page>
  </Document>
</template>

Custom fonts

The standard PDF fonts (Helvetica, Times, Courier) need nothing. To use your own, register the font bytes on <Document :fonts> under a name, then reference that name as a font:

<script setup lang="ts">
import { ref, onMounted } from "vue";

const font = ref<Uint8Array>();
onMounted(async () => {
  font.value = new Uint8Array(await (await fetch("/fonts/Inter.ttf")).arrayBuffer());
});
</script>

<template>
  <Document v-if="font" :fonts="{ Inter: font }" font="Inter">
    <Page :size="'A4'" :margin="48">
      <Text :size="24" bold>Set in Inter</Text>
    </Page>
  </Document>
</template>

A .ttf is loaded with full Unicode (the font is subsetted on the way into the PDF, so the file stays small). For weights, register one file per style as { Inter: { normal, bold, italic } }.

Images

<Image :src> takes the image bytes as a Uint8Array. JPEG and PNG both work, and PNG transparency is honoured (it composites over whatever sits behind it, not a white box). Fetch the bytes the same way as a font:

<script setup lang="ts">
import { ref, onMounted } from "vue";

const logo = ref<Uint8Array>();
onMounted(async () => {
  logo.value = new Uint8Array(await (await fetch("/logo.png")).arrayBuffer());
});
</script>

<template>
  <Image v-if="logo" :src="logo" :width="120" :height="40" :fit="'contain'" />
</template>

In Node, read the file into a Uint8Array (for example with fs.readFileSync) and pass it the same way.

Render options

renderToPdf (and renderToPdfString) take a third argument for engine options. The most useful is onOverflow, which decides what happens when a single unbreakable element is taller than the page:

const bytes = await renderToPdf(Invoice, props, { onOverflow: "warn" });
  • "error" (default) - throw, so an impossible layout never slips out silently.
  • "warn" - place it anyway (clipped) and log.
  • "ignore" - place it anyway, no log.
jasypdf

Declarative PDFs in pure TypeScript. ZUGFeRD & XRechnung compliant, with no headless browser and no Java.

Resources

© 2026 Florian Heuberger · MIT License

Built with Nuxt · self-hosted fonts · no trackers