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

paste-perfect

v0.1.5

Published

The smartest paste cleaner for every rich text editor in 2025

Readme

✨ Paste Perfect

The smartest paste cleaner for every rich text editor in 2025

npm version bundle size license TypeScript test coverage

One line of code. Clean paste from Notion, Google Docs, Word, Confluence, Figma, and more. With optional AI-powered cleaning using Gemini 1.5 Flash.

Paste Perfect Demo


🚀 Quick Start

npm install paste-perfect
import { cleanPaste } from 'paste-perfect';

// Clean any HTML string
const cleaned = await cleanPaste(dirtyHTML);

// Clean from clipboard event
element.addEventListener('paste', async (e) => {
  e.preventDefault();
  const cleaned = await cleanPaste(e);
  // Insert cleaned HTML...
});

// React hook (automatic!)
import { usePasteCleaner } from 'paste-perfect/react';

function MyEditor() {
  const ref = useRef<HTMLDivElement>(null);
  usePasteCleaner(ref);
  return <div ref={ref} contentEditable />;
}

That's it! 🎉


✨ Features

  • 🎯 Zero Config - Works out of the box with ultra-fast rule-based cleaning
  • 🤖 Optional AI - Smart cleaning with Gemini 1.5 Flash (free tier) when GEMINI_API_KEY is set
  • ⚡ Ultra Fast - Rule-based fallback is <1ms, AI mode <500ms
  • 📦 Tiny Bundle - <10 KB gzipped, zero runtime deps (optional peer deps)
  • 🌳 Tree-Shakable - Only bundle what you use
  • 📚 Works Everywhere - Notion, Google Docs, Word, Apple Pages, PDFs, Confluence, Figma, and more
  • ⚛️ React Ready - One hook for automatic paste cleaning
  • 🔒 Type Safe - Full TypeScript support with strict mode
  • 🎨 Format Support - HTML and Markdown output formats

📖 Documentation

Core API

async function cleanPaste(
  input: string | ClipboardEvent,
  options?: {
    format?: 'html' | 'markdown';
    ai?: boolean | string; // true for env var, or API key string
  }
): Promise<string>

Basic Usage

import { cleanPaste } from 'paste-perfect';

// Clean HTML string
const cleaned = await cleanPaste('<div class="notion-block">Hello</div>');
// → '<div>Hello</div>'

// Clean from clipboard
document.addEventListener('paste', async (e) => {
  e.preventDefault();
  const cleaned = await cleanPaste(e);
  // Insert cleaned content...
});

// Output as Markdown
const markdown = await cleanPaste(dirtyHTML, { format: 'markdown' });
// → '# Title\n\n**Bold** text'

AI Mode

Enable AI-powered cleaning with Gemini 1.5 Flash:

# Set environment variable
export GEMINI_API_KEY=your-api-key
// Automatically uses AI when GEMINI_API_KEY is set
const cleaned = await cleanPaste(dirtyHTML, { ai: true });

// Or provide API key directly
const cleaned = await cleanPaste(dirtyHTML, {
  ai: 'your-api-key-here'
});

// Falls back to rule-based if AI fails or is unavailable

React Hook

import { usePasteCleaner } from 'paste-perfect/react';

function MyEditor() {
  const editorRef = useRef<HTMLDivElement>(null);
  
  usePasteCleaner(editorRef, {
    format: 'html', // or 'markdown'
    ai: true, // enable AI mode
    enabled: true, // toggle on/off
    onPaste: (cleaned) => {
      console.log('Pasted:', cleaned);
    },
  });
  
  return <div ref={editorRef} contentEditable />;
}

🎯 Supported Platforms

Paste Perfect automatically detects and cleans content from:

| Platform | Status | Features | |----------|--------|----------| | Notion | ✅ Full Support | Removes block IDs, notion classes, preserves structure | | Google Docs | ✅ Full Support | Cleans internal IDs, Google Sans fonts | | Microsoft Word | ✅ Full Support | Removes Office XML, conditional comments | | Apple Pages | ✅ Full Support | Cleans Pages metadata, Apple classes | | Confluence | ✅ Full Support | Removes Confluence-specific tags | | Figma | ✅ Full Support | Cleans Figma SVG exports | | PDF | ✅ Full Support | Removes PDF metadata | | Others | ✅ General Cleaning | Removes scripts, styles, comments, MSO styles |


📊 Before & After

Before (Notion Paste)

<div class="notion-block notion-block-not-selectable" 
     data-block-id="abc123" 
     style="position: relative;">
  <span class="notion-inline-code">Hello</span>
  <span class="notion-text" data-token-index="0"> World</span>
</div>

After (Paste Perfect)

<div>
  <span>Hello</span>
  <span> World</span>
</div>

Result: Clean, semantic HTML without platform-specific attributes! ✨


🧪 Examples

Quick Integration Examples

Tiptap Editor

import { Editor } from '@tiptap/core';
import StarterKit from '@tiptap/starter-kit';
import { cleanPaste } from 'paste-perfect';

const editor = new Editor({
  extensions: [StarterKit],
  editorProps: {
    handlePaste: async (view, event) => {
      const html = event.clipboardData?.getData('text/html');
      if (html) {
        event.preventDefault();
        
        // Clean the HTML
        const cleaned = await cleanPaste(html);
        
        // Parse and insert using Tiptap's HTML parser
        const { state, dispatch } = view;
        const { schema } = state;
        
        // Parse HTML to ProseMirror document
        const fragment = DOMParser.fromSchema(schema).parse(
          document.createElement('div').appendChild(
            document.createRange().createContextualFragment(cleaned)
          ).firstElementChild
        );
        
        const transaction = state.tr.replaceSelection(fragment);
        dispatch(transaction);
        return true;
      }
      return false;
    },
  },
});

Editor.js

import EditorJS from '@editorjs/editorjs';
import { cleanPaste } from 'paste-perfect';

const editor = new EditorJS({
  holder: 'editor',
  onReady: () => {
    // Handle paste events
    const holder = document.getElementById('editor');
    
    holder?.addEventListener('paste', async (e) => {
      e.preventDefault();
      const html = e.clipboardData?.getData('text/html');
      
      if (html) {
        // Clean the HTML
        const cleaned = await cleanPaste(html);
        
        // Convert HTML to Editor.js blocks
        // You can use a library like html-to-editorjs or parse manually
        const blocks = parseHTMLToBlocks(cleaned);
        
        // Render blocks in Editor.js
        editor.blocks.render(blocks);
      }
    });
  },
});

Lexical Editor

import { LexicalComposer } from '@lexical/react/LexicalComposer';
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
import { ContentEditable } from '@lexical/react/LexicalContentEditable';
import { $generateHtmlFromNodes } from '@lexical/html';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { cleanPaste } from 'paste-perfect';
import { useEffect } from 'react';

function PastePlugin() {
  const [editor] = useLexicalComposerContext();

  useEffect(() => {
    return editor.registerCommand(
      'paste',
      async (event: ClipboardEvent) => {
        const html = event.clipboardData?.getData('text/html');
        if (html) {
          event.preventDefault();
          
          // Clean the HTML
          const cleaned = await cleanPaste(html);
          
          // Parse and insert into Lexical
          const parser = new DOMParser();
          const dom = parser.parseFromString(cleaned, 'text/html');
          // Use Lexical's HTML import utilities
          // ...
          
          return true;
        }
        return false;
      },
      1
    );
  }, [editor]);

  return null;
}

// Usage
<LexicalComposer initialConfig={...}>
  <RichTextPlugin contentEditable={<ContentEditable />} />
  <PastePlugin />
</LexicalComposer>

ContentEditable (Vanilla JS)

import { cleanPaste } from 'paste-perfect';

const editor = document.getElementById('editor');

editor?.addEventListener('paste', async (e) => {
  e.preventDefault();
  
  // Clean the HTML
  const cleaned = await cleanPaste(e);
  
  // Insert cleaned HTML
  const selection = window.getSelection();
  if (selection && selection.rangeCount > 0) {
    const range = selection.getRangeAt(0);
    range.deleteContents();
    
    const tempDiv = document.createElement('div');
    tempDiv.innerHTML = cleaned;
    const fragment = document.createDocumentFragment();
    while (tempDiv.firstChild) {
      fragment.appendChild(tempDiv.firstChild);
    }
    range.insertNode(fragment);
  }
});

Full Example Projects

Check out our complete example projects:


📦 Installation

npm install paste-perfect
# or
yarn add paste-perfect
# or
pnpm add paste-perfect

Optional Peer Dependencies

# For AI mode
npm install @google/generative-ai

# For React hook
npm install react

🔧 Advanced Usage

Custom Cleaners

import { applyRuleBasedCleaners } from 'paste-perfect';

// Use rule-based cleaning directly
const cleaned = applyRuleBasedCleaners(dirtyHTML);

Check AI Availability

import { isAIAvailable } from 'paste-perfect';

if (isAIAvailable()) {
  // AI mode available
}

🎨 API Reference

cleanPaste(input, options?)

Main cleaning function.

Parameters:

  • input: string | ClipboardEvent - HTML string or clipboard event
  • options?: CleanPasteOptions
    • format?: 'html' | 'markdown' - Output format (default: 'html')
    • ai?: boolean | string - Enable AI mode (default: false)

Returns: Promise<string> - Cleaned HTML or Markdown

usePasteCleaner(ref, options?)

React hook for automatic paste cleaning.

Parameters:

  • ref: React.RefObject<HTMLElement> - Ref to contenteditable element
  • options?: UsePasteCleanerOptions
    • All options from cleanPaste plus:
    • enabled?: boolean - Enable/disable hook (default: true)
    • onPaste?: (cleaned: string) => void - Callback after cleaning

🏗️ Architecture

paste-perfect/
├── src/
│   ├── core.ts          # Main cleanPaste function
│   ├── cleaners/
│   │   ├── rules.ts     # Rule-based cleaners
│   │   └── ai.ts        # AI-powered cleaning
│   ├── react.ts         # React hook
│   └── types.ts         # TypeScript types
├── examples/            # Integration examples
└── dist/               # Built outputs (ESM + CJS)
  • Zero runtime dependencies (except optional peer deps)
  • Tree-shakable - Only import what you need
  • Type-safe - Full TypeScript support
  • Well-tested - 95%+ test coverage

🤝 Contributing

Contributions are welcome! Please read our Contributing Guide first.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'feat: add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📝 License

Licensed under MIT


🙏 Acknowledgments

  • Built with ❤️ using TypeScript
  • AI powered by Google Gemini 1.5 Flash
  • Inspired by the need for clean pastes everywhere

⭐ If you find this useful, please star it on GitHub!

GitHub · Issues · Discussions

Made with ✨ by the Paste Perfect team