djust docs
Browse documentation

dj-paste — Paste event handling

On this page

dj-paste fires a server event when the user pastes content into a bound element. The client extracts structured payload from the ClipboardEvent — plain text, rich HTML, and any attached files — and sends it as a single handler call. No dj-hook needed.

Every chat app, rich-text editor, and data-import UI needs paste handling. dj-paste is the complete clipboard-to-server pipeline.


Quick start

<textarea dj-paste="handle_paste" placeholder="Paste anything..."></textarea>
from djust import LiveView
from djust.decorators import event_handler

class ChatView(LiveView):
    template_name = "chat/room.html"

    @event_handler
    def handle_paste(self, text: str = "", html: str = "",
                     has_files: bool = False, files: list = None,
                     **kwargs):
        self.last_paste_preview = text[:200]
        if has_files:
            self.put_flash("info", "Received %d file(s) — uploading..." % len(files))

What gets sent to the handler

Every dj-paste event call carries four structured params:

ParamTypeContent
textstrclipboardData.getData('text/plain') — always a string, even if the clipboard was empty.
htmlstrclipboardData.getData('text/html') — rich paste from Word, Google Docs, a web page, etc. Empty when the source was plain text.
has_filesboolTrue if clipboardData.files was non-empty (i.e. the user copied an image or a file).
fileslist[dict]File metadata: {"name", "type", "size"}. The actual bytes are NOT in this dict — see Pasting files → uploads.

The client also forwards any positional args from the attribute syntax:

<textarea dj-paste="handle_paste('chat', 42)"></textarea>

Positional args bind to the handler's leading named parameters, ahead of the paste params. They are not collected into kwargs. Declare them first, or they overwrite text and html:

@event_handler
def handle_paste(self, room: str = "", room_id: int = 0,
                 text: str = "", html: str = "",
                 has_files: bool = False, files: list = None,
                 **kwargs):
    ...

Pasting files → uploads

By default dj-paste only sends metadata about pasted files. To actually upload the bytes, combine dj-paste with dj-upload on the same element — the client routes the clipboard FileList through the existing upload pipeline (the same path file inputs and drag-drop use):

<div dj-paste="handle_paste" dj-upload="chat_image">
    Paste an image here.
</div>
from djust.uploads import UploadMixin

class ChatView(UploadMixin, LiveView):
    def mount(self, request, **kwargs):
        self.allow_upload(
            "chat_image",
            accept=".png,.jpg,.jpeg,.gif,.webp",
            max_file_size=5 * 1024 * 1024,
        )

    @event_handler
    def handle_paste(self, text: str = "", has_files: bool = False, **kwargs):
        if has_files:
            # Files arrive via the upload pipeline — nothing more to do here.
            return
        self.draft = text

A dj-upload element that is not a file input accepts pastes only. For drag-and-drop, add dj-upload-drop="chat_image" as well.

Pasted files fire the same djust:upload:error / djust:upload:progress events as file-input uploads, so progress indicators and size-limit errors Just Work.

Suppressing the native paste

By default, dj-paste lets the browser's own paste happen too — the pasted text ends up in the textarea as usual, and your handler sees it via the event. That's the right behaviour for most editors and rich-text fields.

When you want to intercept the paste completely — for example when you're routing image paste to an upload slot and do not want the image's data URL dumped into a <div contenteditable> — add dj-paste-suppress:

<div dj-paste="handle_paste"
     dj-paste-suppress
     dj-upload="chat_image"
     contenteditable>
    Paste an image here.
</div>

Current limitation: dj-paste-suppress does not suppress a paste that contains files routed to dj-upload, which is the case shown above. The client calls preventDefault() only after it has queued the files for upload, and by then the browser has already performed the native paste. Suppression works for pastes that contain no files.

Combining with dj-confirm and dj-lock

dj-paste participates in the standard interaction pipeline:

  • dj-confirm — show a confirmation dialog before firing the event (useful for "are you sure you want to paste from clipboard?" privacy prompts).
  • dj-lock — skip the event while an earlier handler is still running.
<textarea dj-paste="handle_paste"
          dj-confirm="Import clipboard contents?"
          dj-lock>
</textarea>

When to reach for dj-paste vs dj-input

  • dj-input fires on every keystroke. The server sees the current value of the field. If a user pastes into a dj-input-bound textarea, you get the post-paste value as a single update — no paste metadata, no file support.
  • dj-paste fires once per paste. You receive text + HTML + file metadata. The native paste still happens (unless suppressed), so you can pair dj-paste with dj-input on the same element without conflict:
<textarea dj-input="set_draft"
          dj-paste="handle_rich_paste"
          placeholder="Type or paste..."></textarea>

Use dj-input for "keep the server copy of this field in sync" and dj-paste for "react specifically when something was pasted" (image upload, language detection on a code snippet, CSV parsing, markdown preview).

Edge cases

  • Missing clipboardData — very old browsers. The handler bails silently and the native paste still happens.
  • getData('text/html') throws — some browsers block HTML reads from untrusted contexts. dj-paste catches the error and sends html="".
  • Empty paste — text and html are empty strings, has_files=False, files=[]. The handler still fires; you can use it as a "user attempted paste" signal.
  • Multiple files — every file in clipboardData.files appears in the files list; the upload pipeline uploads them in sequence if dj-upload is set.

See also

  • File uploads — the dj-upload pipeline that dj-paste routes files through.
  • Event handlers — the @event_handler decorator reference.