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

@servicetitan/dte-pdf-editor

v1.43.0

Published

A React component library for creating interactive PDF editors with drag-and-drop field placement, data model integration, e-signature support, fillable form fields, **job form (submission) fields**, and calculated fields with formulas.

Downloads

471

Readme

@servicetitan/dte-pdf-editor

A React component library for creating interactive PDF editors with drag-and-drop field placement, data model integration, e-signature support, fillable form fields, job form (submission) fields, and calculated fields with formulas.

Features

  • 📄 PDF Rendering: Display and interact with PDF documents using react-pdf and pdfjs-dist
  • 🎯 Drag & Drop Field Placement: Intuitively place fields on PDF pages by dragging from a sidebar
  • 📊 Data Model Integration: Connect fields to structured data models using JSON Schema
  • ✍️ E-Signature Support: Add signature, initials, date signed, and full name fields
  • 📝 Fillable Fields: Support for text, number, date, checkbox, and radio button inputs
  • 🧮 Calculated Fields: Formula-based fields with validation, autosuggest, and advanced formatting (number, currency, percent, date, rounding, separators)
  • 🗂️ Forms Fields: Bind PDF placements to job form submission values (__submission_fields.* paths), with async field definitions from the host and the same paths usable inside calculated-field formulas
  • 📋 Generic Fields: Custom table and text blocks with configurable rows/cells and inline editing in view mode
  • 👥 Multi-Recipient Support: Assign fields to different recipients with color-coded visualization
  • ⚙️ Field Configuration: Configure field properties including position, size, label, recipient assignment, formula/format for calculated fields, and table structure (header/body rows and cells) for generic table fields
  • 👁️ View Mode: Display PDFs with filled data in a read-only or interactive view mode (including generic fields via PdfViewGeneric)

Recent changes (vs master) — feature/DTE-3867 (forms & submission fields)

  • FieldTypeEnum.forms: New canvas field type bound to a job form field. Paths use the same convention as Unlayer tools: __submission_fields.{formId}.{normalizedFieldId} (hyphens stripped from the field UUID in the path segment).
  • PdfEditor: Optional forms (FormInfo[]) and optional onFormSelect. When both are provided, the host loads field metadata for one or more form IDs and calls sendFormFields with a FormFieldsByFormIdI map (Record<number, FormFieldInfo[]>). The editor preloads forms referenced in existing calculated formulas and hydrates the formula builder as definitions arrive. Omit onFormSelect when not using job forms (no TypeScript requirement on existing consumers).
  • FormulaFieldFormSnapshot: Shared snapshot (formId, fieldId, formName, fieldName, fieldType) attached to PdfField.formSnapshot (forms fields), FieldTypeOption.formSnapshot (drag payloads), and optional formSnapshot on structured-formula field tokens when parseExpression can resolve the path against forms + formFieldsByFormId.
  • Formula builder UI: Slide-out “Forms” list in the sidebar; pick a form, load fields on demand, insert only number/date form fields into formulas (aligned with calculated-field operand types). formulaLoadingFormId surfaces loading state for async field fetches.
  • Exports / utilities: buildFormFieldKey, parseFormFieldKey, normalizeFormFieldIdForPath, buildFormFieldSnapshot, tryBuildFormFieldFormulaSnapshot, SUBMISSION_FIELDS_PATH_PREFIX, and ParseExpressionFormContext for integrators reusing path logic outside the editor.

Earlier milestones still reflected elsewhere in this README: date-aware formulas (day-based + / -, disabled * / / with dates, fieldType on field tokens), generic table/text fields, and Table configs in the field panel.

Installation

npm install @servicetitan/dte-pdf-editor

Peer Dependencies

This package requires the following peer dependencies:

npm install @servicetitan/anvil2@^2.0.2 react@~18.3.1 react-dom@~18.3.1

Quick Start

import { PdfEditor, PdfField, PdfView, SchemaObject } from '@servicetitan/dte-pdf-editor';
import { useState } from 'react';

const pdfUrl = 'https://example.com/document.pdf';

function App() {
    const [fields, setFields] = useState<PdfField[]>([]);
    const [data, setData] = useState<Record<string, any>>({});

    const forms = [
        { id: 1, name: 'Job checklist' },
    ];

    const recipients = [
        { id: 1, name: 'technician', displayName: 'Technician' },
        { id: 2, name: 'customer', displayName: 'Customer' },
    ];

    const dataModel: SchemaObject = {
        type: 'object',
        properties: {
            CustomerName: {
                type: 'string',
                title: 'Customer Name',
                options: { placeholder: 'Enter customer name' },
            },
        },
    };

    return (
        <PdfEditor
            pdfUrl={pdfUrl}
            fields={fields}
            onFieldsChange={setFields}
            dataModel={dataModel}
            recipients={recipients}
            forms={forms}
            onFormSelect={(formIds, sendFormFields) => {
                // Load FormFieldInfo[] for each formId, then:
                sendFormFields({
                    /* [formId]: [{ id, header, itemType: 'number' | 'text' | 'date' }, ...] */
                });
            }}
        />
    );
}

Components

PdfEditor

The main component for editing PDF documents and placing fields.

Props

| Prop | Type | Required | Description | |------------------------|--------------------------------------------------------------------------------------------------|----------|--------------------------------------------------------------------------------------------| | pdfUrl | string | Yes | URL or path to the PDF file | | fields | PdfField[] | No | Array of fields placed on the PDF | | onFieldsChange | (fields: PdfField[]) => void | Yes | Callback when fields are added, modified, or deleted | | onFormSelect | (formIds: number[], sendFormFields: (formFieldsByFormId: FormFieldsByFormIdI) => void) => void | No | With forms, host supplies FormFieldInfo[] per form id for sidebar, formulas, and preload; omit when not using forms | | dataModel | SchemaObject | No | JSON Schema object defining available data model fields | | recipients | RecipientInfo[] | No | Array of recipients that can be assigned to fields | | forms | FormInfo[] | No | Job forms shown in the sidebar and formula builder; omit to hide the Forms field group | | hideFields | FieldTypeEnum[] | No | Field menu groups to hide (e.g. omit forms when not supported) | | hideConditionalLogic | boolean | No | When true, hides display-condition UI in the field config panel | | loading | boolean | No | Whether the PDF is currently loading | | loadingPlaceholder | ReactNode | No | Custom component to display while PDF is loading | | errorPlaceholder | ReactNode | No | Custom component to display if PDF fails to load | | onLoadSuccess | (numPages: number) => void | No | Called when the PDF document loads successfully | | errors | Record<string, string> | No | Map of field id → validation message rendered on the canvas |

Example

<PdfEditor
    pdfUrl="https://example.com/document.pdf"
    fields={fields}
    onFieldsChange={setFields}
    dataModel={dataModel}
    recipients={recipients}
    loading={!pdfUrl}
    loadingPlaceholder={<Spinner />}
    errorPlaceholder={<Alert status="danger" title="Failed to load PDF" />}
/>

PdfView

Component for displaying PDFs with filled data in view mode.

Props

| Prop | Type | Required | Description | |----------------------|----------------------------------------------------------------|----------|----------------------------------------------------------| | pdfUrl | string | Yes | URL or path to the PDF file | | fields | PdfField[] | Yes | Array of fields to display on the PDF | | data | DataModelValues | No | Data object containing values for data model fields | | recipients | RecipientInfo[] | No | Array of recipients for field assignment | | fillingBy | string[] | No | Array of recipient names that are allowed to fill fields | | onDataChange | (changedData: { [path: string]: string \| boolean }) => void | No | Callback when fillable field data changes | | loading | boolean | No | Whether the PDF is currently loading | | loadingPlaceholder | ReactNode | No | Custom component to display while PDF is loading | | errorPlaceholder | ReactNode | No | Custom component to display if PDF fails to load |

Example

<PdfView
    pdfUrl="https://example.com/document.pdf"
    fields={fields}
    data={data}
    recipients={recipients}
    fillingBy={['technician']}
    onDataChange={(changedData) => {
        setData({ ...data, ...changedData });
    }}
/>

Types

PdfField

Represents a field placed on a PDF document.

interface PdfField {
    id: string;
    type: FieldTypeEnum;
    subType?: PdfFieldSubType;
    x: number;
    y: number;
    page: number;
    label: string;
    width: number;
    height: number;
    required?: boolean;
    path?: string;
    formSnapshot?: FormulaFieldFormSnapshot; // for `type: forms` — mirrors bound submission path metadata
    recipient?: string;
    description?: string;
    data?: FieldDataType; // for generic fields (table/text)
    formula?: StructuredFormula; // for calculated fields
    formulaFormat?: CalculatedFieldFormat; // for calculated fields (includes dateFormat)
    displayCondition?: DisplayConditionState | null;
}

DisplayConditionState and related types model optional show/hide rule groups for a field (exported from this package).

FieldTypeEnum

Enumeration of field types:

  • dataModel: Field connected to a data model schema
  • forms: Bound to a job form submission field (path under __submission_fields.*)
  • eSign: E-signature field
  • fillable: User-fillable form field
  • calculated: Formula-based calculated field
  • generic: Custom table or text block (configurable rows/cells, inline editing)

ESignFieldType

Types of e-signature fields:

  • signature: Signature field
  • initials: Initials field
  • dateSigned: Date signed field
  • fullName: Full name field

PdfFieldSubType

Union of fillable, e-sign, generic, and form field sub-types: FillableFieldType | ESignFieldType | GenericFieldType | FormFieldType. Used for PdfField.subType and FieldTypeOption.subType (form fields use FormFieldType: number | text | date).

FillableFieldType

Types of fillable fields:

  • text: Text input
  • number: Number input
  • date: Date picker
  • checkbox: Checkbox
  • radio: Radio button

GenericFieldType

Types of generic fields:

  • table: Configurable table with header and body rows and editable cells
  • text: Text block with inline editing

FieldDataType

For generic fields, PdfField.data can be:

  • GenericFieldTableDataType: showHeader, header (height, cells), body (array of rows with height and cells)
  • GenericFieldTextDataType: value (string)

Calculated Fields (formula & format)

Calculated fields use a structured formula and optional display format:

  • StructuredFormula: Token list (numbers, operators + - * /, parentheses, field references) used for validation and safe editing.
  • CalculatedFieldFormat: Controls result display: resultType (number | currency | percent | date), rounding mode, decimal places, thousands/decimal separators, prefix/postfix text, dateFormat (custom pattern using YYYY, MM, DD, etc.).
  • FormulaToken (field): Field tokens carry fieldType: 'date' | 'number' (schema / fillable / form operands) for date-aware evaluation. Optional formSnapshot is filled when the token path is a submission field (__submission_fields.*) and parseExpression is given ParseExpressionFormContext (forms + formFieldsByFormId).

Date fields in formulas use day-based arithmetic: date + number adds days, date - date yields a day count. Only +, -, and parentheses are allowed when date fields are present.

Schema fields can opt in via options.useInCalculatedFields and options.useInConditionals for data model extraction in formula builder and conditionals.

Job forms (FormInfo, FormFieldInfo, submission data)

  • FormInfo: { id: number; name: string } — entries passed as PdfEditor forms.
  • FormFieldInfo: { id: string; header: string; itemType: 'number' | 'text' | 'date' } — returned from the host for each requested form id.
  • FormFieldsByFormIdI: Record<number, FormFieldInfo[]> — argument to sendFormFields in the optional onFormSelect callback.
  • Preview / PdfView data: Provide values under __submission_fields mirroring the path layout, e.g. { __submission_fields: { [formId]: { [normalizedFieldId]: value } } } (field id keys without hyphens match buildFormFieldKey).

SchemaObject

JSON Schema object defining the data model structure. Supports nested objects, arrays, and various string subtypes ( string, text, html, image).

interface SchemaObject {
    type: 'object';
    properties: Record<string, SchemaNode>;
    title?: string;
    options?: SchemaFieldBaseOptions;
}

RecipientInfo

Information about a recipient that can be assigned to fields.

interface RecipientInfo {
    id: number;
    name: string;
    displayName: string;
}

Usage Examples

Basic Editor with Data Model

import { PdfEditor, PdfField, SchemaObject } from '@servicetitan/dte-pdf-editor';
import { useState } from 'react';

const dataModel: SchemaObject = {
    type: 'object',
    properties: {
        JobInformation: {
            type: 'object',
            properties: {
                JobNumber: {
                    type: 'string',
                    title: 'Job Number',
                    options: { placeholder: 'Enter job number' },
                },
                JobName: {
                    type: 'string',
                    title: 'Job Name',
                    options: { placeholder: 'Enter job name' },
                },
            },
        },
    },
};

function MyEditor() {
    const [fields, setFields] = useState<PdfField[]>([]);

    return (
        <PdfEditor
            pdfUrl="https://example.com/document.pdf"
            fields={fields}
            onFieldsChange={setFields}
            dataModel={dataModel}
        />
    );
}

Editor with Recipients

const recipients = [
    { id: 1, name: 'technician', displayName: 'Technician' },
    { id: 2, name: 'customer', displayName: 'Customer' },
    { id: 3, name: 'accountant', displayName: 'Accountant' },
];

<PdfEditor
    pdfUrl={pdfUrl}
    fields={fields}
    onFieldsChange={setFields}
    recipients={recipients}
/>

View Mode with Data

const [data, setData] = useState({
    'JobInformation.JobNumber': 'JOB-12345',
    'JobInformation.JobName': 'HVAC Installation',
});

<PdfView
    pdfUrl={pdfUrl}
    fields={fields}
    data={data}
    recipients={recipients}
    fillingBy={['technician']}
    onDataChange={(changedData) => {
        setData({ ...data, ...changedData });
    }}
/>

Toggle Between Edit and View Modes

function PdfEditorPage() {
    const [fields, setFields] = useState<PdfField[]>([]);
    const [isView, setIsView] = useState(false);
    const [data, setData] = useState<Record<string, any>>({});

    return (
        <>
            <Button onClick={() => setIsView(!isView)}>
                {isView ? 'Edit' : 'View'}
            </Button>
            {!isView ? (
                <PdfEditor
                    pdfUrl={pdfUrl}
                    fields={fields}
                    onFieldsChange={setFields}
                    dataModel={dataModel}
                    recipients={recipients}
                />
            ) : (
                <PdfView
                    pdfUrl={pdfUrl}
                    fields={fields}
                    data={data}
                    recipients={recipients}
                    fillingBy={['technician']}
                    onDataChange={(changedData) => {
                        setData({ ...data, ...changedData });
                    }}
                />
            )}
        </>
    );
}

Field Operations

Adding Fields

Fields are added by dragging from the sidebar onto the PDF canvas. The sidebar displays:

  • Data Model Fields (Merge Tags): Grouped by schema structure
  • E-Sign Fields: Signature, initials, date signed, full name
  • Fillable Fields: Text, number, date, checkbox, radio
  • Forms (when forms + onFormSelect are provided): Choose a job form, then drag a field onto the canvas; the new PdfField stores path and formSnapshot for the bound submission key
  • Calculated Fields: Formula-based fields (configure formula and format in the config panel)
  • Generic Fields: Text and Table (configure table rows/cells in the config panel; inline editing in view mode)

Configuring Fields

Click on a field to open the configuration panel where you can:

  • Change the label
  • Adjust position and size
  • Assign a recipient
  • Set the required status (fillable fields, except checkbox)
  • Update the data model path
  • For calculated fields: edit formula (with validation and autosuggest from data model, fillable fields, and number/date job form fields via the Forms slide-out), set result type (number/currency/percent/date), rounding, decimals, separators, and date format
  • For forms fields: edit label like other display labels; binding is defined by path / formSnapshot from placement
  • For generic table fields: configure header visibility, header/body row heights, and cell values via Table configs; generic text fields support inline editing in the overlay

Moving and Resizing

  • Move: Click and drag a field to reposition it
  • Resize: Use the resize handles on field corners

Deleting Fields

Select a field and use the delete button in the configuration panel, or press the delete key.

Styling

The package includes default styles. To customize, you can override CSS classes:

  • .dte-pdf-editor: Main editor container
  • .dte-pdf-view-container: View mode container
  • .dte-pdf-wrapper: PDF document wrapper
  • .dte-pdf-field-overlay: Field overlay container
  • .dte-pdf-editor-sidebar-container: Sidebar container

Browser Support

This package requires modern browsers with support for:

  • ES6+
  • Canvas API
  • Drag and Drop API

License

Copyright © ServiceTitan