Message API/docs/components/pdf/page-copy-paste

Copy & Paste

Copy pages into the viewer clipboard and paste them into the active PDF.

Overview

Copy and paste work together as one viewer-local clipboard flow. The parent tells the viewer which pages to copy, then later tells it where to paste them.

The parent application never sends page bytes, base64, or a file object between these two actions. The clipboard exists only inside the same viewer iframe session.

  • page-copy uses `pageRange`.
  • page-paste uses `targetPageIndex`.
  • Paste only works after a successful copy in the same viewer session.
  • If `targetPageIndex` is missing, paste fails even if the action name is correct.

How It Works

Think of this as a two-step workflow owned by the viewer.

Host Sends

1. Host sends page-copy

Copy the selected pages from the current PDF into the viewer internal clipboard.

javascript
viewerWindow.postMessage({
  type: 'pageManipulation',
  payload: {
    requestId: 'copy-1',
    action: 'page-copy',
    pageRange: [[1, 3]]
  }
}, '*');
Host Sends

2. Host sends page-paste

Paste those copied pages at the chosen destination index.

javascript
viewerWindow.postMessage({
  type: 'pageManipulation',
  payload: {
    requestId: 'paste-1',
    action: 'page-paste',
    targetPageIndex: 5
  }
}, '*');
Canvas Emits

3. Host listens for pageManipulationResult

Use the returned result to show errors and confirm the clipboard or paste state.

javascript
window.addEventListener('message', (event) => {
  if (event.data?.type !== 'pageManipulationResult') return;
  console.log(event.data.payload);
});

Working Example

This is the minimal host-side copy/paste flow.

function copyPages() {
  viewerWindow.postMessage({
    type: 'pageManipulation',
    payload: {
      requestId: 'copy-1',
      action: 'page-copy',
      pageRange: [[1, 3]]
    }
  }, '*');
}

function pastePages() {
  viewerWindow.postMessage({
    type: 'pageManipulation',
    payload: {
      requestId: 'paste-1',
      action: 'page-paste',
      targetPageIndex: 5
    }
  }, '*');
}

window.addEventListener('message', (event) => {
  if (event.data?.type !== 'pageManipulationResult') return;

  const result = event.data.payload;
  if (!result.success) {
    console.error(result.error);
    return;
  }

  console.log(result.action);
});
Working Example

Complete HTML Example

Use this full standalone HTML document when you want a minimal host page for the copy and paste workflow.

html
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>PDF Copy Paste Demo</title>
  <style>
    body {
      margin: 0;
      font-family: Arial, sans-serif;
      background: #0b1220;
      color: #e5e7eb;
    }
    .app {
      display: grid;
      grid-template-columns: 320px 1fr;
      gap: 12px;
      min-height: 100vh;
      padding: 12px;
    }
    .panel {
      border: 1px solid #2f3b4f;
      border-radius: 12px;
      background: #111827;
      padding: 12px;
      display: grid;
      gap: 10px;
      align-content: start;
    }
    .stack {
      display: grid;
      gap: 8px;
    }
    .row {
      display: flex;
      gap: 8px;
      flex-wrap: wrap;
    }
    input, button, select {
      width: 100%;
      border: 1px solid #2f3b4f;
      border-radius: 8px;
      background: #0f172a;
      color: #e5e7eb;
      padding: 8px 10px;
      font: inherit;
      box-sizing: border-box;
    }
    button {
      width: auto;
      cursor: pointer;
    }
    .pages {
      display: grid;
      gap: 8px;
      max-height: 60vh;
      overflow: auto;
    }
    .page {
      border: 1px solid #2f3b4f;
      border-radius: 10px;
      background: #0d1728;
      padding: 8px;
      display: grid;
      gap: 8px;
    }
    .page.active {
      border-color: #22d3ee;
    }
    iframe {
      width: 100%;
      height: calc(100vh - 24px);
      border: 1px solid #2f3b4f;
      border-radius: 12px;
      background: #04101c;
    }
    img {
      max-width: 100%;
      height: auto;
      display: block;
      background: #fff;
      border-radius: 8px;
    }
    .meta {
      font-size: 12px;
      color: #9ca3af;
    }
  </style>
</head>
<body>
  <div class="app">
    <div class="panel">
      <div class="stack">
      <input id="viewerUrl" value="http://localhost:5173" />
      <input id="fileUrl" placeholder="Public PDF URL" />
      <div class="row">
        <button id="loadViewerBtn">Load Viewer</button>
        <button id="loadPdfBtn">Load PDF</button>
      </div>
      <div class="row">
        <input id="pasteIndexInput" value="0" />
        <button id="pasteBtn">Paste</button>
      </div>
      <div id="pages" class="pages"></div>
    </div>
    </div>
    <iframe id="viewerFrame" title="PDF Copy Paste Demo Viewer"></iframe>
  </div>
  <script>
const viewerFrame = document.getElementById('viewerFrame');
const viewerUrlInput = document.getElementById('viewerUrl');
const fileUrlInput = document.getElementById('fileUrl');
const pagesEl = document.getElementById('pages');
let latestPageList = null;

function post(type, payload) {
  viewerFrame.contentWindow.postMessage({ type, payload }, '*');
}

function loadViewer() {
  viewerFrame.src = viewerUrlInput.value.trim();
}

function loadPdf() {
  post('view', {
    fileUrl: fileUrlInput.value.trim(),
    displayName: 'Demo User',
    username: 'demo.user',
    email: 'demo@example.com'
  });
}

window.addEventListener('message', (event) => {
  if (event.source !== viewerFrame.contentWindow) return;
  const data = event.data;
  if (!data || typeof data !== 'object') return;
  if (data.type === 'pageList') {
    latestPageList = data.payload;
    renderPages(data.payload.pages);
  }
  onViewerMessage(data);
});

function renderPages(pages) {
  if (!pagesEl) return;
  pagesEl.innerHTML = '';
  pages.forEach((page) => {
    const item = document.createElement('div');
    item.className = page.isSelected ? 'page active' : 'page';
    const actions = pageActions(page);
    item.innerHTML = [
      page.thumbnailDataUrl
        ? '<img src="' + page.thumbnailDataUrl + '" width="' + (page.thumbnailWidth || 120) + '" height="' + (page.thumbnailHeight || 160) + '" />'
        : '<div class="meta">No thumbnail data</div>',
      '<div><strong>' + (page.label || ('Page ' + (page.index + 1))) + '</strong></div>',
      '<div class="meta">' + (page.title || 'Default') + '</div>',
      actions
    ].join('');
    wirePageActions(item, page);
    pagesEl.appendChild(item);
  });
}

document.getElementById('loadViewerBtn').addEventListener('click', loadViewer);
document.getElementById('loadPdfBtn').addEventListener('click', loadPdf);

document.getElementById('pasteBtn').addEventListener('click', () => {
  const targetPageIndex = Number(document.getElementById('pasteIndexInput').value);
  post('pageManipulation', { requestId: 'paste-' + targetPageIndex, action: 'page-paste', targetPageIndex });
});
function pageActions() { return '<button data-action="copy">Copy</button>'; }
function wirePageActions(item, page) {
  item.querySelector('[data-action="copy"]').addEventListener('click', () => {
    post('pageManipulation', { requestId: 'copy-' + page.index, action: 'page-copy', pageRange: [[page.index]] });
  });
}
function onViewerMessage(data) {
  if (data.type === 'pageManipulationResult') console.log(data.payload);
}
  </script>
</body>
</html>
Complete HTML Example

Live Preview

Open the focused demo to copy a page and paste it into another position in the same viewer session.

PDF Copy & Paste Demo

Focused live demo for the viewer-local copy/paste workflow.

Preview opens in a large modal for zoom-friendly review.