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

@jyothiswarup/pdf-template-builder

v0.1.0

Published

React components for visually mapping PDF template fields

Downloads

25

Readme

@jyothiswarup/pdf-template-builder

A React component library for visually mapping PDF template fields. Works seamlessly with @jyothiswarup/pdf-template-engine to create a complete PDF template solution.

📦 Installation

npm install @jyothiswarup/pdf-template-builder

✨ Features

  • 🎨 Visual Field Mapping - Click on PDF to add fields, drag to reposition
  • 📄 Multi-page Support - Map fields across multiple PDF pages
  • 🎯 Precise Positioning - Accurate coordinate mapping with automatic conversion
  • 🔧 Field Customization - Adjust font sizes, rename fields, set sample data
  • 📤 Schema Export - Export field definitions as JSON compatible with backend package
  • 🚀 PDF Generation - Generate filled PDFs directly from the UI
  • 💪 TypeScript Support - Full TypeScript definitions included
  • 🎨 Customizable - Use individual components or the all-in-one builder

🚀 Quick Start

Basic Usage

import { PDFTemplateBuilder } from '@jyothiswarup/pdf-template-builder';
import '@jyothiswarup/pdf-template-builder/styles';

function App() {
  return (
    <PDFTemplateBuilder
      apiEndpoint="http://localhost:3000/pdf"
    />
  );
}

With Custom Handlers

import { PDFTemplateBuilder } from '@jyothiswarup/pdf-template-builder';
import '@jyothiswarup/pdf-template-builder/styles';

function App() {
  const handleSchemaExport = (schema) => {
    // Save schema to your database
    fetch('/api/templates', {
      method: 'POST',
      body: JSON.stringify({ schema }),
      headers: { 'Content-Type': 'application/json' }
    });
  };

  const handleGenerate = async (schema, data, pdfFile) => {
    const formData = new FormData();
    formData.append('pdf', pdfFile);
    formData.append('schema', JSON.stringify(schema));
    formData.append('data', JSON.stringify(data));

    const response = await fetch('/api/generate-pdf', {
      method: 'POST',
      body: formData,
    });

    const blob = await response.blob();
    // Handle the generated PDF
  };

  return (
    <PDFTemplateBuilder
      onSchemaExport={handleSchemaExport}
      onGenerate={handleGenerate}
    />
  );
}

📚 API Reference

Components

PDFTemplateBuilder

Main all-in-one component that combines all functionality.

Props:

| Prop | Type | Default | Required | Description | |------|------|---------|----------|-------------| | pdfFile | File \| null | null | No | Initial PDF file to load | | initialSchema | FieldSchema[] | undefined | No | Initial field schema (for editing existing templates) | | onSchemaChange | (schema: FieldSchema[]) => void | undefined | No | Called whenever schema changes (for auto-save) | | onSchemaExport | (schema: FieldSchema[]) => void | undefined | No | Called when "Export Schema" button is clicked | | onGenerate | (schema, data, file) => Promise<void> | undefined | No | Custom PDF generation handler | | apiEndpoint | string | undefined | No | API endpoint URL for PDF generation (alternative to onGenerate) | | mode | 'builder' \| 'viewer' | 'builder' | No | 'builder' for field mapping, 'viewer' for read-only | | className | string | '' | No | Custom CSS class name |

Example:

<PDFTemplateBuilder
  apiEndpoint="http://localhost:3000/pdf"
  onSchemaExport={(schema) => console.log('Schema:', schema)}
  mode="builder"
/>

PDFUploader

Component for uploading PDF files.

Props:

| Prop | Type | Default | Required | Description | |------|------|---------|----------|-------------| | onFileSelect | (file: File) => void | - | Yes | Callback when PDF file is selected | | loading | boolean | false | No | Show loading state |

Example:

import { PDFUploader } from '@jyothiswarup/pdf-template-builder';

function MyComponent() {
  const handleFileSelect = (file: File) => {
    console.log('Selected PDF:', file.name);
    // Process the file
  };

  return <PDFUploader onFileSelect={handleFileSelect} loading={false} />;
}

PDFViewer

Component for displaying PDF pages with field overlays.

Props:

| Prop | Type | Default | Required | Description | |------|------|---------|----------|-------------| | pages | PDFPage[] | - | Yes | Array of PDF pages from usePDF hook | | fields | PDFField[] | - | Yes | Array of field definitions | | selectedFieldId | string \| null | - | Yes | ID of currently selected field | | onFieldSelect | (fieldId: string \| null) => void | - | Yes | Called when field is selected | | onFieldUpdate | (fieldId: string, updates: Partial<PDFField>) => void | - | Yes | Called when field is updated (drag/resize) | | onFieldDelete | (fieldId: string) => void | - | Yes | Called when field is deleted | | onFieldAdd | (pageNumber: number, x: number, y: number) => void | undefined | No | Called when clicking on PDF to add field | | getCanvasRef | (pageNumber: number) => (element: HTMLCanvasElement \| null) => void | - | Yes | Function to get canvas ref (from usePDF hook) |

Example:

import { PDFViewer, usePDF } from '@jyothiswarup/pdf-template-builder';

function MyComponent() {
  const { pages, loadPDF, getCanvasRef } = usePDF();
  const [fields, setFields] = useState([]);
  const [selectedFieldId, setSelectedFieldId] = useState(null);

  return (
    <PDFViewer
      pages={pages}
      fields={fields}
      selectedFieldId={selectedFieldId}
      onFieldSelect={setSelectedFieldId}
      onFieldUpdate={(id, updates) => {
        setFields(prev => prev.map(f => f.id === id ? { ...f, ...updates } : f));
      }}
      onFieldDelete={(id) => {
        setFields(prev => prev.filter(f => f.id !== id));
      }}
      onFieldAdd={(page, x, y) => {
        // Add new field
      }}
      getCanvasRef={getCanvasRef}
    />
  );
}

FieldMapper

Component for managing and editing fields.

Props:

| Prop | Type | Default | Required | Description | |------|------|---------|----------|-------------| | fields | PDFField[] | - | Yes | Array of field definitions | | selectedFieldId | string \| null | - | Yes | ID of currently selected field | | onFieldAdd | () => void | - | Yes | Called when "Add Field" button is clicked | | onFieldUpdate | (fieldId: string, updates: Partial<PDFField>) => void | - | Yes | Called when field properties are updated | | onFieldDelete | (fieldId: string) => void | - | Yes | Called when field is deleted | | onFieldSelect | (fieldId: string \| null) => void | - | Yes | Called when field is selected | | currentPage | number | - | Yes | Currently selected page (0-indexed) | | onPageChange | (page: number) => void | - | Yes | Called when page selection changes | | pages | Array<{ pageNumber: number }> | - | Yes | Array of available pages | | fieldData | FieldData | - | Yes | Sample data for fields | | onFieldDataChange | (key: string, value: string) => void | - | Yes | Called when field sample data changes |

Example:

import { FieldMapper } from '@jyothiswarup/pdf-template-builder';

<FieldMapper
  fields={fields}
  selectedFieldId={selectedFieldId}
  onFieldAdd={() => addNewField()}
  onFieldUpdate={(id, updates) => updateField(id, updates)}
  onFieldDelete={(id) => deleteField(id)}
  onFieldSelect={setSelectedFieldId}
  currentPage={currentPage}
  onPageChange={setCurrentPage}
  pages={pages}
  fieldData={fieldData}
  onFieldDataChange={(key, value) => updateFieldData(key, value)}
/>

Toolbar

Component with export and generate buttons.

Props:

| Prop | Type | Default | Required | Description | |------|------|---------|----------|-------------| | fields | PDFField[] | - | Yes | Array of field definitions | | pages | PDFPage[] | - | Yes | Array of PDF pages | | fieldData | FieldData | - | Yes | Sample data for fields | | pdfFile | File \| null | - | Yes | Current PDF file | | onGeneratePDF | (schema, data, file) => Promise<void> | - | Yes | Called when "Generate PDF" is clicked | | generating | boolean | false | No | Show generating state | | onSchemaExport | (schema: FieldSchema[]) => void | undefined | No | Called when "Export Schema" is clicked |

Example:

import { Toolbar, exportSchema } from '@jyothiswarup/pdf-template-builder';

<Toolbar
  fields={fields}
  pages={pages}
  fieldData={fieldData}
  pdfFile={pdfFile}
  onGeneratePDF={async (schema, data, file) => {
    // Generate PDF
  }}
  generating={false}
  onSchemaExport={(schema) => {
    // Export schema
  }}
/>

Hooks

usePDF()

Hook for loading and managing PDF files.

Returns:

{
  pdfDoc: any;                    // PDF.js document object
  pages: PDFPage[];               // Array of rendered PDF pages
  loading: boolean;               // Loading state
  error: string | null;            // Error message if any
  loadPDF: (file: File) => Promise<void>;  // Function to load PDF
  getCanvasRef: (pageNumber: number) => (element: HTMLCanvasElement | null) => void;  // Get canvas ref
}

Example:

import { usePDF } from '@jyothiswarup/pdf-template-builder';

function MyComponent() {
  const { pages, loading, error, loadPDF, getCanvasRef } = usePDF();

  const handleFileSelect = async (file: File) => {
    await loadPDF(file);
  };

  if (loading) return <div>Loading PDF...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div>
      {pages.map(page => (
        <div key={page.pageNumber}>
          {/* Render page */}
        </div>
      ))}
    </div>
  );
}

Utilities

exportSchema(fields: PDFField[], pages: PDFPage[]): FieldSchema[]

Converts internal field format to schema format compatible with @jyothiswarup/pdf-template-engine.

Parameters:

  • fields: Array of PDFField objects
  • pages: Array of PDFPage objects from usePDF hook

Returns: Array of field schema objects

Example:

import { exportSchema } from '@jyothiswarup/pdf-template-builder';

const schema = exportSchema(fields, pages);
// [
//   {
//     key: 'customerName',
//     x: 100,
//     y: 700,
//     fontSize: 14,
//     page: 1
//   }
// ]

downloadSchema(fields: PDFField[], pages: PDFPage[]): void

Exports schema and triggers browser download as JSON file.

Parameters:

  • fields: Array of PDFField objects
  • pages: Array of PDFPage objects

Example:

import { downloadSchema } from '@jyothiswarup/pdf-template-builder';

downloadSchema(fields, pages);
// Downloads 'pdf-template-schema.json'

Types

PDFField

Internal field representation used by components.

interface PDFField {
  id: string;           // Unique identifier
  key: string;         // Field key (used in data mapping)
  x: number;           // X coordinate in pixels (canvas)
  y: number;           // Y coordinate in pixels (canvas)
  width: number;       // Field width in pixels
  height: number;      // Field height in pixels
  page: number;        // Page number (0-indexed)
  fontSize?: number;   // Font size in points (default: 12)
}

PDFPage

PDF page representation from usePDF hook.

interface PDFPage {
  pageNumber: number;        // Page number (0-indexed)
  canvas: HTMLCanvasElement; // Canvas element with rendered PDF
  width: number;            // Canvas width in pixels (scaled)
  height: number;           // Canvas height in pixels (scaled)
  pdfWidth?: number;        // Actual PDF width in points
  pdfHeight?: number;       // Actual PDF height in points
}

FieldData

Sample data for fields.

interface FieldData {
  [key: string]: string | number;  // Key-value pairs matching field keys
}

FieldSchema

Schema format compatible with backend package (exported format).

interface FieldSchema {
  key: string;         // Field key
  x: number;          // X coordinate in points (PDF coordinates)
  y: number;          // Y coordinate in points (from top of page)
  fontSize?: number;  // Font size in points
  page: number;       // Page number (1-indexed)
}

📖 Usage Examples

Example 1: Simple Template Builder

import { PDFTemplateBuilder } from '@jyothiswarup/pdf-template-builder';
import '@jyothiswarup/pdf-template-builder/styles';

function TemplateBuilder() {
  return (
    <PDFTemplateBuilder
      apiEndpoint="http://localhost:3000/pdf"
    />
  );
}

Example 2: Save Schema to Database

import { PDFTemplateBuilder } from '@jyothiswarup/pdf-template-builder';
import '@jyothiswarup/pdf-template-builder/styles';

function TemplateBuilder() {
  const handleSchemaExport = async (schema) => {
    await fetch('/api/templates', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        name: 'Invoice Template',
        schema: schema
      })
    });
    alert('Template saved!');
  };

  return (
    <PDFTemplateBuilder
      onSchemaExport={handleSchemaExport}
    />
  );
}

Example 3: Load and Edit Existing Template

import { PDFTemplateBuilder } from '@jyothiswarup/pdf-template-builder';
import '@jyothiswarup/pdf-template-builder/styles';
import { useState, useEffect } from 'react';

function EditTemplate({ templateId }) {
  const [template, setTemplate] = useState(null);
  const [pdfFile, setPdfFile] = useState(null);

  useEffect(() => {
    fetch(`/api/templates/${templateId}`)
      .then(res => res.json())
      .then(data => {
        setTemplate(data);
        // Load PDF file if you have URL
        fetch(data.pdfUrl)
          .then(res => res.blob())
          .then(blob => {
            const file = new File([blob], 'template.pdf', { type: 'application/pdf' });
            setPdfFile(file);
          });
      });
  }, [templateId]);

  if (!template || !pdfFile) return <div>Loading...</div>;

  return (
    <PDFTemplateBuilder
      pdfFile={pdfFile}
      initialSchema={template.schema}
      onSchemaChange={(schema) => {
        // Auto-save on change
        fetch(`/api/templates/${templateId}`, {
          method: 'PUT',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ schema })
        });
      }}
    />
  );
}

Example 4: Custom Layout with Individual Components

import { 
  PDFUploader, 
  PDFViewer, 
  FieldMapper, 
  Toolbar,
  usePDF,
  exportSchema
} from '@jyothiswarup/pdf-template-builder';
import '@jyothiswarup/pdf-template-builder/styles';
import { useState } from 'react';

function CustomBuilder() {
  const { pages, loading, error, loadPDF, getCanvasRef } = usePDF();
  const [fields, setFields] = useState([]);
  const [selectedFieldId, setSelectedFieldId] = useState(null);
  const [currentPage, setCurrentPage] = useState(0);
  const [fieldData, setFieldData] = useState({});
  const [pdfFile, setPdfFile] = useState(null);

  const handleFileSelect = (file: File) => {
    setPdfFile(file);
    loadPDF(file);
  };

  const handleFieldAdd = () => {
    const newField = {
      id: `field-${Date.now()}`,
      key: `field${fields.length + 1}`,
      x: 50,
      y: 50,
      width: 200,
      height: 30,
      page: currentPage,
      fontSize: 12,
    };
    setFields([...fields, newField]);
  };

  const handleGenerate = async () => {
    const schema = exportSchema(fields, pages);
    // Generate PDF...
  };

  return (
    <div className="custom-builder">
      <div className="sidebar">
        <PDFUploader onFileSelect={handleFileSelect} loading={loading} />
        {error && <div className="error">{error}</div>}
        
        {pages.length > 0 && (
          <>
            <FieldMapper
              fields={fields}
              selectedFieldId={selectedFieldId}
              onFieldAdd={handleFieldAdd}
              onFieldUpdate={(id, updates) => {
                setFields(fields.map(f => f.id === id ? { ...f, ...updates } : f));
              }}
              onFieldDelete={(id) => {
                setFields(fields.filter(f => f.id !== id));
              }}
              onFieldSelect={setSelectedFieldId}
              currentPage={currentPage}
              onPageChange={setCurrentPage}
              pages={pages}
              fieldData={fieldData}
              onFieldDataChange={(key, value) => {
                setFieldData({ ...fieldData, [key]: value });
              }}
            />
            <Toolbar
              fields={fields}
              pages={pages}
              fieldData={fieldData}
              pdfFile={pdfFile}
              onGeneratePDF={handleGenerate}
            />
          </>
        )}
      </div>
      
      <div className="main">
        {pages.length > 0 ? (
          <PDFViewer
            pages={pages}
            fields={fields}
            selectedFieldId={selectedFieldId}
            onFieldSelect={setSelectedFieldId}
            onFieldUpdate={(id, updates) => {
              setFields(fields.map(f => f.id === id ? { ...f, ...updates } : f));
            }}
            onFieldDelete={(id) => {
              setFields(fields.filter(f => f.id !== id));
            }}
            onFieldAdd={(page, x, y) => {
              const newField = {
                id: `field-${Date.now()}`,
                key: `field${fields.length + 1}`,
                x, y,
                width: 200,
                height: 30,
                page,
                fontSize: 12,
              };
              setFields([...fields, newField]);
            }}
            getCanvasRef={getCanvasRef}
          />
        ) : (
          <div>Upload a PDF to get started</div>
        )}
      </div>
    </div>
  );
}

🎨 Styling

Default Styles

Import the default stylesheet:

import '@jyothiswarup/pdf-template-builder/styles';

Custom Styling

You can override styles by targeting the component classes:

/* Main container */
.pdf-template-builder {
  /* Your styles */
}

/* PDF viewer area */
.pdf-viewer {
  /* Your styles */
}

.pdf-page-container {
  background: white;
  padding: 2rem;
  border-radius: 8px;
}

.canvas-wrapper {
  background: white;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

/* Field boxes */
.field-box {
  border: 2px dashed #007bff;
  background: rgba(0, 123, 255, 0.1);
}

.field-box.selected {
  border-color: #0056b3;
  background: rgba(0, 123, 255, 0.2);
}

/* Field mapper sidebar */
.field-mapper {
  /* Your styles */
}

.field-item {
  padding: 1rem;
  border: 2px solid #e0e0e0;
  border-radius: 6px;
}

/* Toolbar */
.toolbar {
  /* Your styles */
}

.toolbar-button {
  padding: 0.75rem 1rem;
  border-radius: 6px;
}

🔗 Integration with Backend Package

This package is designed to work with @jyothiswarup/pdf-template-engine:

Workflow

  1. Admin Setup (using this package):

    <PDFTemplateBuilder
      onSchemaExport={(schema) => saveToDatabase(schema)}
    />
  2. Save Schema to database:

    {
      "templateId": "invoice-v1",
      "schema": [
        { "key": "customerName", "x": 100, "y": 700, "fontSize": 14, "page": 1 }
      ]
    }
  3. User Data Entry (your form):

    <form onSubmit={handleSubmit}>
      <input name="customerName" />
      {/* Other fields */}
    </form>
  4. Backend Generation (using backend package):

    const { generatePDF } = require('@jyothiswarup/pdf-template-engine');
       
    const template = await db.getTemplate('invoice-v1');
    const userData = req.body; // From form
       
    await generatePDF({
      templatePath: template.pdfUrl,
      outputPath: './output.pdf',
      fields: template.schema,
      data: userData
    });

🛠️ Requirements

  • React >= 18.0.0
  • React DOM >= 18.0.0
  • PDF.js (loaded automatically from CDN, no installation needed)

📝 Coordinate System

The package handles coordinate conversion automatically:

  • Frontend (Canvas): Top-left origin, pixels, scaled by 1.5x
  • Backend (PDF): Top-left origin, points (1/72 inch)
  • Conversion: Automatically handled by exportSchema utility

🐛 Troubleshooting

PDF not loading

  • Check browser console for PDF.js errors
  • Ensure PDF file is valid
  • Check CORS if loading from URL

Fields not appearing

  • Verify fields are added to correct page
  • Check that pages array is populated
  • Ensure getCanvasRef is properly connected

Schema export errors

  • Ensure all fields have valid page numbers
  • Check that pages array matches field pages
  • Verify field coordinates are numbers

Styling issues

  • Make sure you import styles: import '@jyothiswarup/pdf-template-builder/styles';
  • Check for CSS conflicts
  • Verify component classes are not overridden

📄 License

MIT

🔗 Related Packages

🤝 Contributing

Contributions welcome! Please open an issue or PR on GitHub.

📧 Support

For issues and questions:

  • GitHub Issues: [Your repo URL]
  • Email: [Your email]

Made with ❤️ by Jyothiswarup E V