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

@superdoc-dev/template-builder

v1.12.1

Published

React template builder component for SuperDoc

Downloads

4,977

Readme

@superdoc-dev/template-builder

React template building component for SuperDoc that enables document field management using structured content (SDT).

Installation

npm install @superdoc-dev/template-builder

Quick Start

import SuperDocTemplateBuilder from '@superdoc-dev/template-builder';
import 'superdoc/style.css';

function TemplateEditor() {
  return (
    <SuperDocTemplateBuilder
      document={{
        source: 'template.docx',
        mode: 'editing',
      }}
      fields={{
        available: [
          { id: '1324567890', label: 'Customer Name' },
          { id: '1324567891', label: 'Invoice Date' },
          {
            id: '1324567892',
            label: 'Sample Table',
            mode: 'block',
            fieldType: 'signer',
            presetContent: {
              html: '<table style="border-collapse: collapse; width: 100%;"><tr><th style="border: 1px solid #000;">Column A</th><th style="border: 1px solid #000;">Column B</th></tr><tr><td style="border: 1px solid #000;"></td><td style="border: 1px solid #000;"></td></tr></table>',
            },
          },
          { id: '1324567893', label: 'Signature', mode: 'block', fieldType: 'signer' },
        ],
      }}
      onFieldInsert={(field) => {
        console.log('Field inserted:', field.alias, field.fieldType);
      }}
    />
  );
}

Features

  • Trigger Detection - Type {{ (customizable) to insert fields
  • Field Management - Insert, update, delete, and navigate fields
  • Field Discovery - Automatically finds existing fields in documents
  • Field Types - Distinguish owner vs signer fields with visual styling
  • Field Creation - Allow users to create custom fields inline
  • Linked Fields - Insert copies of existing fields that share a group
  • UI Agnostic - Bring your own menus, panels, and components
  • SDT Based - Uses structured content tags for Word compatibility
  • Export - Download as .docx or get a Blob for API storage

API

Component Props

<SuperDocTemplateBuilder
  // Document configuration
  document={{
    source: File | Blob | string,
    mode: 'editing' | 'viewing'
  }}

  // Field configuration
  fields={{
    available: FieldDefinition[],  // Fields user can insert
    initial: TemplateField[],      // Pre-existing fields
    allowCreate: boolean,          // Show "Create New Field" in menu
  }}

  // UI components (optional)
  menu={{
    trigger: '{{',                  // Trigger pattern
    component: CustomFieldMenu      // Custom menu component
  }}

  list={{
    position: 'left' | 'right',    // Sidebar position
    component: CustomFieldList      // Custom list component
  }}

  // Toolbar (optional)
  toolbar={true}                   // Render built-in toolbar container
  // toolbar="#my-toolbar"          // Mount into existing element
  // toolbar={{                     // Configure built-in toolbar
  //   toolbarGroups: ['center'],
  //   excludeItems: ['italic', 'bold'],
  // }}

  // Content Security Policy nonce (optional)
  cspNonce="abc123"

  // Telemetry (optional, enabled by default)
  telemetry={{ enabled: true, metadata: { source: 'template-builder' } }}

  // License key (optional)
  licenseKey="your-license-key"

  // Event handlers
  onReady={() => {}}
  onTrigger={(event) => {}}
  onFieldInsert={(field) => {}}
  onFieldUpdate={(field) => {}}
  onFieldDelete={(fieldId) => {}}
  onFieldsChange={(fields) => {}}
  onFieldSelect={(field) => {}}
  onFieldCreate={(field) => {}}     // Called when user creates a custom field
  onExport={(event) => {}}          // Called after export with fields and blob
/>

Ref Methods

const ref = useRef<SuperDocTemplateBuilderHandle>(null);

// Insert fields
ref.current.insertField({ alias: 'Customer Name', fieldType: 'owner' });
ref.current.insertBlockField({ alias: 'Signature', fieldType: 'signer' });

// Update/delete fields
ref.current.updateField(fieldId, { alias: 'New Name' });
ref.current.deleteField(fieldId);

// Navigation
ref.current.selectField(fieldId);
ref.current.nextField();
ref.current.previousField();

// Get data
const fields = ref.current.getFields();
const blob = await ref.current.exportTemplate({ triggerDownload: false });

// Access SuperDoc instance
const superdoc = ref.current.getSuperDoc();

Field Types

Fields can have a fieldType property to distinguish between different roles (e.g. 'owner' vs 'signer'). This is stored in the SDT tag metadata and flows through the entire system.

Defining field types

const availableFields = [
  { id: '1', label: 'Company Name', fieldType: 'owner' },
  { id: '2', label: 'Signer Name', fieldType: 'signer' },
  { id: '3', label: 'Date' },  // defaults to 'owner'
];

Visual styling

Import the optional CSS to color-code fields in the editor by type:

import '@superdoc-dev/template-builder/field-types.css';

Customize colors via CSS variables:

:root {
  --superdoc-field-owner-color: #629be7;   /* default blue */
  --superdoc-field-signer-color: #d97706;  /* default amber */
}

Accessing field type in callbacks

All field callbacks include fieldType:

onFieldInsert={(field) => {
  console.log(field.fieldType); // 'owner' | 'signer' | ...
}}

onFieldsChange={(fields) => {
  const signerFields = fields.filter(f => f.fieldType === 'signer');
}}

Preset Block Content

Block fields can include presetContent so insertion starts with a predefined structure (for example, a table or list).

const availableFields = [
  {
    id: 'sample_table',
    label: 'Sample Table',
    mode: 'block',
    fieldType: 'data',
    lockMode: 'contentLocked',
    presetContent: {
      html: '<table style="border-collapse: collapse; width: 100%;"><tr><th style="border: 1px solid #000;">Column A</th><th style="border: 1px solid #000;">Column B</th></tr><tr><td style="border: 1px solid #000;"></td><td style="border: 1px solid #000;"></td></tr></table>',
      // or json: { ...ProseMirror JSON... }
    },
  },
];

Notes:

  • presetContent is used only for mode: 'block'.
  • Inline fields ignore presetContent.
  • Prefer presetContent.json when you need the most reliable preservation of structure, semantics, and editor-compatible styling.
  • With presetContent.html, include required attributes and inline styles explicitly so the HTML parser can map them into node attributes.

Custom Field Creation

Enable inline field creation in the dropdown menu:

<SuperDocTemplateBuilder
  fields={{
    available: [...],
    allowCreate: true,
  }}
  onFieldCreate={async (field) => {
    // field.id starts with 'custom_'
    // field.fieldType is 'owner' or 'signer' (user-selected)
    const saved = await api.createField(field);
    return { ...field, id: saved.id }; // return modified field or void
  }}
/>

The create form lets users pick inline/block mode and owner/signer field type.

Linked Fields (Groups)

When a user selects an existing field from the menu, a linked copy is inserted. Linked fields share a group ID and stay in sync. The menu shows an "Existing Fields" section with grouped entries.

When the last field in a group is deleted, the remaining field's group tag is automatically removed.

Custom Components

Field Menu

const CustomFieldMenu = ({
  isVisible,
  position,
  availableFields,
  filteredFields,     // filtered by typed query after {{
  filterQuery,        // the query text
  allowCreate,
  existingFields,     // fields already in the document
  onSelect,
  onSelectExisting,
  onClose,
  onCreateField,
}) => {
  if (!isVisible) return null;

  return (
    <div style={{ position: 'fixed', left: position?.left, top: position?.top }}>
      {filteredFields.map((field) => (
        <button key={field.id} onClick={() => onSelect(field)}>
          {field.label} {field.fieldType && `(${field.fieldType})`}
        </button>
      ))}
      <button onClick={onClose}>Cancel</button>
    </div>
  );
};

Field List

const CustomFieldList = ({ fields, onSelect, onDelete, selectedFieldId }) => {
  return (
    <div>
      <h3>Fields ({fields.length})</h3>
      {fields.map((field) => (
        <div
          key={field.id}
          onClick={() => onSelect(field)}
          style={{ background: selectedFieldId === field.id ? 'lightblue' : 'white' }}
        >
          {field.alias} {field.fieldType && `[${field.fieldType}]`}
          <button onClick={() => onDelete(field.id)}>Delete</button>
        </div>
      ))}
    </div>
  );
};

Field Navigation

Enable Tab/Shift+Tab navigation:

function TemplateEditor() {
  const ref = useRef<SuperDocTemplateBuilderHandle>(null);

  const handleKeyDown = (e) => {
    if (e.key === 'Tab') {
      e.preventDefault();
      if (e.shiftKey) {
        ref.current?.previousField();
      } else {
        ref.current?.nextField();
      }
    }
  };

  return (
    <div onKeyDown={handleKeyDown}>
      <SuperDocTemplateBuilder ref={ref} {...props} />
    </div>
  );
}

Export Template

The exportTemplate method supports two modes via ExportConfig:

Download Mode (Default)

await ref.current?.exportTemplate();
await ref.current?.exportTemplate({ fileName: 'invoice-template' });

Blob Mode (for Database/API)

const blob = await ref.current?.exportTemplate({
  fileName: 'invoice-template',
  triggerDownload: false,
});

if (blob) {
  const formData = new FormData();
  formData.append('template', blob, 'invoice-template.docx');
  await fetch('/api/templates', { method: 'POST', body: formData });
}

onExport Callback

Fires after every export with the field list and optional blob:

<SuperDocTemplateBuilder
  onExport={(event) => {
    console.log(event.fields);    // TemplateField[]
    console.log(event.fileName);  // string
    console.log(event.blob);      // Blob | undefined (only in blob mode)
  }}
/>

Telemetry

Telemetry is enabled by default with source: 'template-builder' metadata. You can override or extend the configuration:

<SuperDocTemplateBuilder
  telemetry={{ enabled: true, metadata: { source: 'my-app', environment: 'production' } }}
/>

For more details, see the Telemetry documentation.

TypeScript

Full TypeScript support included:

import SuperDocTemplateBuilder from '@superdoc-dev/template-builder';
import type {
  TemplateField,
  FieldDefinition,
  TriggerEvent,
  ExportConfig,
  ExportEvent,
  FieldMenuProps,
  FieldListProps,
  SuperDocTemplateBuilderHandle,
} from '@superdoc-dev/template-builder';

License

AGPLv3