How to Add File Attachment Handling to Tiptap Editor: A Complete Guide

The reactjs-tiptap-editor package provides a ready-made Attachment extension that handles file uploads via a configurable upload function and renders styled attachment nodes with automatic file type icons and size formatting.

The hunghg255/reactjs-tiptap-editor repository ships with a comprehensive file attachment system for Tiptap that supports both drag-and-drop interactions and toolbar-based file selection. This solution manages the entire lifecycle from upload progress indication to final rendering, utilizing React node views for real-time feedback and offering full internationalization support.

Install the Attachment Extension

The Attachment extension is included in the main package but requires a subpath import. Install the library if you haven't already:

pnpm add reactjs-tiptap-editor

# or yarn add reactjs-tiptap-editor

# or npm install reactjs-tiptap-editor

Import the extension from the dedicated attachment subpath to keep your bundle size optimized:

import { Attachment } from 'reactjs-tiptap-editor/attachment';

Configure the Upload Handler

At the core of the file attachment handling is the upload configuration option defined in src/extensions/Attachment/Attachment.ts. This async function receives a File object and must return the URL string where the file is stored.

import { useEditor, EditorContent } from '@tiptap/react';
import { Attachment } from 'reactjs-tiptap-editor/attachment';

const editor = useEditor({
  extensions: [
    // ... other extensions
    Attachment.configure({
      upload: async (file: File) => {
        const formData = new FormData();
        formData.append('file', file);
        
        const response = await fetch('/api/upload', {
          method: 'POST',
          body: formData,
        });
        
        const data = await response.json();
        return data.url; // The URL stored in the node attribute
      },
    }),
  ],
});

The extension automatically invokes this handler when users drop files or select them via the file picker, inserting a node with these attributes managed by the addAttributes() method:

  • url – The direct link to the uploaded asset
  • fileName – Original name without extension
  • fileExt – File extension (e.g., pdf, docx)
  • fileSize – Size in bytes (formatted via normalizeFileSize from src/utils/file.ts)
  • fileType – MIME type used for icon selection
  • fileId – Optional internal identifier for your application

Add the Toolbar Button with RichTextAttachment

While the extension supports drag-and-drop, you typically need a toolbar button to trigger the file picker. The repository provides RichTextAttachment located at src/extensions/Attachment/components/RichTextAttachment.tsx.

import { RichTextAttachment } from 'reactjs-tiptap-editor/attachment';

export const Toolbar = ({ editor }) => (
  <div className="toolbar">
    <RichTextAttachment editor={editor} />
  </div>
);

This component wraps the base ActionButton and executes editor.chain().focus().setAttachment().run() when clicked. The setAttachment command is registered by the main extension file and programmatically opens a hidden file input, handles the selection, and manages the upload flow.

Understanding Node Rendering and React Views

The attachment system uses a hybrid rendering approach. When content is saved, the renderHTML method in Attachment.ts generates semantic markup:

<div class="attachment">
  <a href="FILE_URL">
    <span class="attachment__icon">[icon]</span>
    <span class="attachment__text">FILE_NAME.EXT (SIZE)</span>
  </a>
</div>

During editing, the React node view in src/extensions/Attachment/components/NodeViewAttachment/NodeViewAttachment.tsx takes over. This component displays:

  • Uploading state: A placeholder showing localized text from editor.attachment.uploading
  • File icons: Determined by getFileTypeIcon(fileType, true) in FileIcon.tsx
  • Size formatting: Human-readable sizes via the normalizeFileSize utility

If the upload fails or is pending, users see the editor.attachment.please_upload message until the promise resolves.

Customize Internationalization

All user-facing strings are externalized to locale files under src/locales/*.ts. The default English keys include:

  • editor.attachment.tooltip – Toolbar button hover text
  • editor.attachment.uploading – Displayed during file transfer
  • editor.attachment.please_upload – Instruction in the placeholder state

Override these by extending the locale configuration when initializing your editor instance.

Complete Working Example

Here is a minimal, production-ready setup demonstrating file attachment handling in a Tiptap editor:

import { useEditor, EditorContent } from '@tiptap/react';
import { StarterKit } from '@tiptap/starter-kit';
import { Attachment, RichTextAttachment } from 'reactjs-tiptap-editor/attachment';

export default function App() {
  const editor = useEditor({
    extensions: [
      StarterKit,
      Attachment.configure({
        upload: async (file) => {
          // Replace with your actual upload logic
          const formData = new FormData();
          formData.append('file', file);
          const res = await fetch('/api/upload', { method: 'POST', body: formData });
          const { url } = await res.json();
          return url;
        },
      }),
    ],
    content: '<p>Drop files here or use the button below.</p>',
  });

  if (!editor) return null;

  return (
    <div className="editor-wrapper">
      <div className="toolbar">
        <RichTextAttachment editor={editor} />
      </div>
      <EditorContent editor={editor} />
    </div>
  );
}

When users click the attachment button or drop files into the editor, the configured upload handler processes the file, and the node view renders a styled preview with the correct icon and formatted file size.

Summary

  • Import path: Use reactjs-tiptap-editor/attachment for the Attachment extension and RichTextAttachment component
  • Core file: src/extensions/Attachment/Attachment.ts defines the node schema, setAttachment command, and HTML rendering
  • Upload requirement: Provide an async upload function that returns a file URL string
  • React integration: NodeViewAttachment.tsx handles the editing UI with upload progress indicators
  • Toolbar button: RichTextAttachment.tsx provides a ready-made button that triggers the attachment flow
  • Attributes: The system tracks url, fileName, fileExt, fileSize, fileType, and optional fileId for each attachment

Frequently Asked Questions

How do I handle file uploads to my own API endpoint?

Configure the upload option when setting up the Attachment extension. This async function receives the File object and must return the hosted URL as a string. The extension handles the file input, calls your function, and inserts the node only after the promise resolves.

Can I customize the appearance of attachment nodes in the editor?

Yes. The React node view in src/extensions/Attachment/components/NodeViewAttachment/NodeViewAttachment.tsx controls the editing appearance. You can fork this component or wrap it with your own styling. For saved content, override the CSS classes attachment, attachment__icon, and attachment__text that are generated by the renderHTML method.

What file metadata does the extension store?

According to the addAttributes() method in Attachment.ts, the extension persists six fields: url (string), fileName (string), fileExt (string), fileSize (number), fileType (string), and fileId (string, optional). The fileSize value is processed through normalizeFileSize in src/utils/file.ts for human-readable display.

How do I change the text displayed during file uploads?

Modify the locale files in src/locales/ or override the specific keys in your editor configuration. The relevant keys are editor.attachment.uploading for the progress message and editor.attachment.please_upload for the placeholder instruction. These strings are consumed by the NodeViewAttachment component during the upload lifecycle.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →