Docs

Plugins

API

Generate invoices using your preferred framework.

Features

Preview invoices

Generate PDF previews of invoices using your configured templates.

Generate invoices

Produce PDF/A-3b compliant invoices from your templates using Puppeteer.

Authorization

Control access to API endpoints with customizable request-based authorization.

And more...

Trusted origins, RPC client, and other advanced options.

Installation

Install the package

Let's start by adding the API Plugin to your project.

npm install @node-zugferd/api
If you're using a separate client and server setup, make sure to install the plugin in both parts of your project.

Setup API instance

The API instance is the entry point for handling requests to generate and preview invoices. Start by importing api and passing your zugferd (invoicer) instance.

invoicer.ts
import { api } from "@node-zugferd/api";

export const zugferdApi = api({
  invoicer,
  // ...
});

At this stage, the call will proce a TypeScript error. That's expected as api still requires additional configuration before it can run:

  • renderer: Tells the API how to render invoice templates (React, Vue, Svelte, etc.).
  • templates: One or more layouts that control the content of generated PDF invocies.

We'll configure these in the next steps.

Create a template

Templates define the layout and content of generated PDF invoices. The plugin comes with a renderer for all popular web frameworks, including vanilla html.

Templates are rendered server-side during PDF generation. They can be asynchronous, but streaming output (e.g React Suspense) is not supported.
invoicer.tsx
import { reactRenderer } from "@node-zugferd/api/react/renderer";

api({
  invoicer,
  renderer: reactRenderer(),
  templates: {
    myTemplate: (data) => {
      return {
        body: (
          <h1>Invoice {data.number}</h1>
        ),
      };
    },
  },
});
  1. Create a file called template.vue somewhere in your project
template.vue
<template>
  <h1>Invoice {{ number }}</h1>
</template>

<script setup lang="ts">
import type { invoicer } from "./your/path/invoicer";

defineProps<typeof invoicer.$Infer.Schema>>();
</script>
  1. Add the component to the config
invoicer.ts
import { vueRenderer } from "@node-zugferd/api/vue/renderer";
import Template from "./your/path/template.vue";

api({
  invoicer,
  renderer: vueRenderer(),
  templates: {
    myTemplate: (data) => {
      return {
        body: {
          component: Template,
          props: data,
        },
      };
    },
  },
});
  1. Create a file called template.svelte somewhere in your project
template.svelte
<script>
import type { invoicer } from "./your/path/invoicer";

export let data: typeof invoicer.$Infer.Schema;
</script>

<h1>Invoice {data.number}</h1>
  1. Add the component to the config
invoicer.ts
import { svelteRenderer } from "@node-zugferd/api/svelte-kit/renderer";
import Template from "./your/path/template.svelte";

api({
  invoicer,
  renderer: svelteRenderer(),
  templates: {
    myTemplate: (data) => {
      return {
        body: {
          component: Template,
          props: data,
        },
      };
    },
  },
});
invoicer.ts
import { solidRenderer } from "@node-zugferd/api/solid-start/renderer";
api({
  invoicer,
  renderer: solidRenderer(),
  templates: {
    myTemplate: (data) => {
      return {
        body: (
          <h1>Invoice {data.number}</h1>
        ),
      };
    },
  },
});
invoicer.ts
import { vanillaRenderer } from "@node-zugferd/api/vanilla/renderer";

api({
  invoicer,
  renderer: vanillaRenderer(),
  templates: {
    myTemplate: (data) => {
      return {
        body: `<h1>${data.number}</h1>`,
      };
    },
  },
});

Mount Handler

To handle api requests, you need to set up a route handler on your server.

Create a new file or route in your framework's designated catch-all route handler. This route should handle requests for the path /api/zugferd/* (unless you've configured a different base path).

node-zugferd supports any backend framework with standard Request and Response objects and offers helper functions for popular frameworks.
/app/api/zugferd/[...all]/route.ts
import { zugferdApi } from "./your/path/invoicer"; // path to your api instance
import { toNextJsHandler } from "@node-zugferd/api/next-js";

export const { POST, GET } = toNextJsHandler(zugferdApi);
/server/api/zugferd/[...all].ts
import { zugferdApi } from "./your/path/invoicer"; // path to your api instance

export default defineEventHandler((event) => {
    return zugferdApi.handler(toWebRequest(event));
});
hooks.server.ts
import { zugferdApi } from "./your/path/invoicer"; // path to your api instance
import { svelteKitHandler } from "node-zugferd/svelte-kit";

export async function handle({ event, resolve }) {
    return svelteKitHandler({ event, resolve, api: zugferdApi });
}
/app/routes/api.zugferd.$.ts
import { zugferdApi } from "./your/path/invoicer"; // path to your api instance
import type { LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node";

export async function loader({ request }: LoaderFunctionArgs) {
    return zugferdApi.handler(request);
}

export async function action({ request }: ActionFunctionArgs) {
    return zugferdApi.handler(request);
}
/routes/api/zugferd/*.all.ts
import { zugferdApi } from "./your/path/invoicer"; // path to your api instance
import { toSolidStartHandler } from "@node-zugferd/api/solid-start";

export const { GET, POST } = toSolidStartHandler(zugferdApi);
src/index.ts
import { Hono } from "hono";
import { zugferdApi } from "./your/path/invoicer"; // path to your api instance
import { serve } from "@hono/node-server";
import { cors } from "hono/cors";

const app = new Hono();

app.on(["GET", "POST"], "/api/zugferd/\*\*", (c) => zugferdApi.handler(c.req.raw));

serve(app);
server.ts
import express from "express";
import { toNodeHandler } from "@node-zugferd/api/node";
import { zugferdApi } from "./your/path/invoicer"; // path to your api instance

const app = express();
const port = 8000;

app.all("/api/zugferd/*", toNodeHandler(zugferdApi)); // For ExpressJS v4
// app.all("/api/zugferd/*splat", toNodeHandler(zugferdApi)); For ExpressJS v5

// Mount express json middleware after node-zugferd handler
// or only apply it to routes that don't interact with node-zugferd
app.use(express.json());

app.listen(port, () => {
    console.log(`node-zugferd app listening on port ${port}`);
});

This also works for any other node server framework like express, fastify, hapi, etc.

server.ts
import { Elysia, type Context } from "elysia";
import { zugferdApi } from "./your/path/invoicer"; // path to your api instance

const nodeZugferdView = (ctx: Context) => {
  const NODE_ZUGFERD_ACCEPT_METHODS = ["GET", "POST"];
  // validate request method
  if (NODE_ZUGFERD_ACCEPT_METHODS.includes(ctx.request.method)) {
    return zugferdApi.handler(ctx.request);
  } else {
    ctx.error(405);
  }
}

const app = new Elysia().all("/api/zugferd/\*", nodeZugferdView).listen(3000);

console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);
/routes/api/zugferd/$.ts
import { zugferdApi } from "./your/path/invoicer"; // path to your api instance
import { createAPIFileRoute } from "@tanstack/react-start/api";

export const APIRoute = createAPIFileRoute("/api/zugferd/$")({
    GET: ({ request }) => {
        return zugferdApi.handler(request);
    },
    POST: ({ request }) => {
        return zugferdApi.handler(request);
    },
});

Create Client Instance

The client-side library helps you interact with the zugferd api server.

lib/zugferd-client.ts
import { createClient } from "@node-zugferd/api/client";
import type { zugferdApi } from "./your/path/invoicer"; // Import as type

export const zugferdClient = createClient<typeof zugferdApi>({
  /** the base url of the server (optional if you're using the same domain) */
  baseURL: "http://localhost:3000", 
});

🎉 That's it!

That's it! You're now ready to use @node-zugferd/api in your application.

Continue to basic usage to learn how to use the API Plugin to preview or generate invoices.

Basic Usage

The API Plugin provides endpoints for:

  • Previewing PDF invoices
  • Generating PDF/A-3b compliant invoices

Creating an invoice

To create an invoice we first need to define the data.

const data: typeof invoicer.$Infer.Schema = {
  // ...
};

Then we need to call the endpoint with the data as body. This can be done client or server-side.

Server Side

import { zugferdApi } from "./your/path/invoicer"; // path to your api instance

const result = await zugferdApi.api.create({
  body: {
    template: "myTemplate",
    data, // pass the data defined previously
  },
});

Client Side

import { zugferdClient } from "./lib/zugferd-client"; // path to your zugferd client

const { data, error } = await zugferdClient("@post/create", {
  body: {
    template: "myTemplate",
    data, // pass the data defined previously
  },
});

You can also use it with client-side data-fetching libraries like TanStack Query.

Previewing an invoice

To preview an invoice we first need to define the data.

const data: typeof invoicer.$Infer.Schema = {
  // ...
};

Now we can finally call the endpoint.

import { zugferdApi } from "./your/path/invoicer"; // path to your api instance

const result = await zugferdApi.api.preview({
  body: {
    template: "myTemplate",
    data, // pass the data defined previously
  },
});

Options

invoicer

The zugferd instance used to generate the invoice.

import { invoicer } from "./your/path/invoicer";

api({
  invoicer,
});

renderer

Tells the API how to render invoice templates (React, Vue, Svelte, etc.).

See Create a template.

templates

One or more layouts that control the content of generated PDF invocies.

See Create a template.

authorize?

Restrict access to endpoints.

api({
  authorize: async (ctx) => {
    const user = await getUser(ctx.request);

    if (user.role === "admin") {
      return true;
    }

    return false;
  },
});

baseURL?

Base URL for the node-zugferd api. This is typically the root URL where your application is hosted.

api({
  baseURL: "https://example.com",
});

basePath?

Base path for the node-zugferd api. This is typically the path where the node-zugferd api routes are mounted

api({
  basePath: "/api/zugferd",
});

Default: /api/zugferd

trustedOrigins?

List of trusted origins.

api({
  trustedOrigins: ["https://example.com", "*.trusted.com"],
});

The baseURL is always included.

advanced?

Advanced configuration options.

api({
  advanced: {
    disableCSRFCheck: false,
    puppeteer: {
      lauch: {
        headless: true,
        args: ["--no-sandbox"],
      },
    },
  },
});
  • disableCSRFCheck?: Disable trusted origins check (âš  security risk)
  • puppeteer.launch?: Launch options for puppeteer, See LaunchOptions

onAPIError?

API error handling configuration.

api({
  onAPIError: {
    throw: false,
    onError: (error, ctx) => {
      console.error("API Error:", error);
    },
  },
});
  • throw: Throw an error on API error (default: true)
  • onError: Custom error handler

disabledPaths?

Disable specific api paths.

api({
  disabledPaths: ["/preview"],
});