npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

pdf-builder-core

v0.2.0

Published

Config-driven visual template builder for PDF and email

Readme

Template Builder

A config-driven, registry-driven, command-driven, and schema-driven visual template builder for PDF and email generation.

Built on GrapesJS as the internal visual engine, exposed through a clean, typed public API.

Quick Start

With Docker (recommended)

# Copy env file
cp .env.example .env

# Start everything
docker compose up

The playground will be available at http://localhost:3000.

Without Docker

Requirements: Node.js 20+ and pnpm 9+

# Install dependencies
pnpm install

# Build the builder package
pnpm build

# Start playground + builder in watch mode
pnpm dev:all

Project Structure

├── packages/
│   └── builder/              # @template-builder/core — the main package
│       ├── src/
│       │   ├── core/         # Types, models, config, registries
│       │   ├── adapters/     # Storage, exporters (PDF, email)
│       │   ├── presets/      # Default blocks, commands, templates
│       │   └── ui/           # React components (shell, panels, canvas)
│       ├── tsup.config.ts    # Build config (ESM + CJS + d.ts)
│       └── vitest.config.ts  # Test config
├── apps/
│   └── playground/           # Vite + React playground app
│       └── src/routes/       # Lab routes (editor, blocks, export)
├── docker-compose.yml
├── Dockerfile
└── docs/                     # Architecture & design docs

Architecture

The package follows a layered architecture:

| Layer | Purpose | |------------|----------------------------------------------| | Core | Types, contracts, config normalization, registries | | Adapters | GrapesJS bridge, storage, exporters | | Presets | Default blocks, commands, templates | | UI | React components for the editor interface |

Key Design Principles

  • Config-driven: Everything customizable via BuilderConfig
  • Registry-based: Blocks, commands, and templates via typed registries
  • Schema-driven properties: Block panels generated from propsSchema
  • Engine-agnostic API: GrapesJS is internal; the public API is the package
  • Replaceable services: Storage, assets, exporters — all swappable

Usage

Basic usage

import { TemplateBuilder } from '@template-builder/core';
import '@template-builder/core/styles.css';

function App() {
  return <TemplateBuilder />;
}

With configuration

import {
  TemplateBuilder,
  defineBuilderConfig,
  defaultPdfExporter,
  defaultEmailExporter,
} from '@template-builder/core';
import '@template-builder/core/styles.css';

const config = defineBuilderConfig({
  theme: {
    tokens: {
      colors: { primary: '#e11d48' },
    },
  },
  capabilities: {
    userLevel: 'advanced',
    allowExport: { pdf: true, email: true },
  },
  exporters: {
    pdf: defaultPdfExporter,
    email: defaultEmailExporter,
  },
  onChange: (data) => console.log('Changed:', data),
});

function App() {
  return <TemplateBuilder config={config} />;
}

Custom blocks

import { defineBlock, TemplateBuilder } from '@template-builder/core';

const myBlock = defineBlock({
  type: 'my-custom-block',
  label: 'My Block',
  category: 'Custom',
  defaults: { text: 'Hello!' },
  propsSchema: [
    { key: 'text', label: 'Text', type: 'text', tab: 'content' },
  ],
  renderers: {
    canvas: (props) => `<div>${props.text}</div>`,
  },
});

function App() {
  return <TemplateBuilder config={{ blocks: [myBlock] }} />;
}

Scripts

| Command | Description | |---------|-------------| | pnpm dev | Start playground | | pnpm dev:all | Start playground + builder watch | | pnpm build | Build the builder package | | pnpm test | Run tests | | pnpm lint | Lint all packages | | pnpm typecheck | Run TypeScript checks | | pnpm format | Format with Prettier |

Default Blocks

| Block | Type | Category | Semantic Tag | |-------|------|----------|-------------| | Section | section | Layout | <section> | | Container | container | Layout | <div> | | Hero Banner | hero-banner | Layout | <header> | | Section Title | section-title | Typography | <h2> | | Rich Text | rich-text | Typography | <div> | | Image | image-block | Media | <figure> | | Callout | callout | Content | <blockquote> | | Bullet List | bullet-list | Content | <ul> | | Numbered List | numbered-list | Content | <ol> | | CTA Button | cta-button | Actions | <a> | | Footer | footer | Layout | <footer> | | Divider | divider | Layout | <hr> | | Page Break | page-break | Layout | - | | KPI Card | kpi-card | Data | <div> | | Data Table | data-table | Data | <table> | | Timeline | timeline | Data | <div> | | Signature | signature | Content | <div> | | Chart Placeholder | chart-placeholder | Data | <figure> |

All blocks include semantic descriptors (semantics field) defining HTML tag, editable strategy, and content model.

PDF Export Engines

The builder supports multiple PDF engines via a pluggable PdfEngine contract:

| Engine | ID | Status | Selectable Text | Links | Method | |--------|----|--------|:-:|:-:|--------| | pdfmake | pdfmake | Official | ✅ | ✅ | Config-driven DDO | | Browser Print | browser-print | Complementary | ✅ | ✅ | window.print() | | Legacy (jsPDF) | legacy-jspdf | Fallback | ❌ | ❌ | html-to-image raster |

Using the Semantic PDF Engine

import { TemplateBuilder, pdfmakeEngine } from '@template-builder/core';

// pdfmake is registered by default.
// Export via the ExportMenu "PDF Semântico" option,
// or programmatically:
const builderRef = useRef(null);
// ...
const result = await builderRef.current.exportPdfViaEngine({ engineId: 'pdfmake' });

Custom PDF Engine

import { PdfEngine } from '@template-builder/core';

const myEngine: PdfEngine = {
  id: 'my-engine',
  label: 'My Custom Engine',
  status: 'experimental',
  capabilities: { selectableText: true, externalLinks: true, /* ... */ },
  async render(input) {
    // Convert input.document (TemplateDocument) to PDF blob
    return { blob: myBlob };
  },
};

Plugin API

import { BuilderPlugin, PluginManager } from '@template-builder/core';

const myPlugin: BuilderPlugin = {
  id: 'my-plugin',
  name: 'My Plugin',
  setup(ctx) {
    ctx.registerBlock(myCustomBlock);
    ctx.registerCommand(myCommand);
    ctx.registerPdfEngine(myEngine);
  },
};

Data Binding

The builder supports data-driven content via BindingRegistry:

import { BindingRegistryService } from '@template-builder/core';

const bindings = new BindingRegistryService();

// Variable binding: replace a block prop with data
bindings.addVariable('block-1', {
  sourcePath: 'user.name',
  targetProp: 'content',
});

// Loop binding: repeat a block for each item in an array
bindings.addLoop('row-template', {
  sourcePath: 'items',
  itemAlias: 'item',
  limit: 10,
  emptyState: 'No items found',
});

// Set data sources
bindings.setDataSource('user', { name: 'Alice' });
bindings.setDataSource('items', [{ title: 'A' }, { title: 'B' }]);

Export

  • PDF Semântico: pdfmake engine — text selecionável, links clicáveis
  • PDF Legacy: html2pdf.js — imagem rasterizada (fallback)
  • PDF Alta Fidelidade: Backend Chromium (se configurado)
  • Browser Print: Diálogo nativo do navegador
  • Email HTML: HTML table-based seguro para clientes de email

All exporters are replaceable via config.exporters or the PdfEngineRegistry.

License

Private — All rights reserved.