Skip to content

Latest commit

 

History

History
354 lines (265 loc) · 12.1 KB

File metadata and controls

354 lines (265 loc) · 12.1 KB

SimplePDF Embed using an Iframe

SimplePDF Embed React and Web allow you to easily integrate SimplePDF using a single line of code by displaying the editor in a modal.

If you're however interested in having more control over the way SimplePDF is displayed in your app, such as changing the way the modal looks or dropping it altogether - injecting the editor into a div for example, read on:

With a SimplePDF account (to collect customers' submissions)

Get your own SimplePDF account

Let your users pick the file on their computer

- Replace COMPANY_IDENTIFIER with your own

<iframe src="https://COMPANY_IDENTIFIER.simplepdf.com/editor" frameborder="0">
</iframe>

Open a given PDF file automatically

- Replace COMPANY_IDENTIFIER with your own

- Replace PUBLICLY_AVAILABLE_PDF_URL with the url of the PDF to use.

NOTE: if the PUBLICLY_AVAILABLE_PDF_URL contains query parameters (for example when using S3 or Azure Blob storage presigned URLs), you must encode the URL using encodeURIComponent

<iframe
  src="https://COMPANY_IDENTIFIER.simplepdf.com/editor?open=PUBLICLY_AVAILABLE_PDF_URL"
  frameborder="0"
>
</iframe>

Specifying a context

The context is sent as part of the submission via the webhooks integration: read more

Use-cases:

  • Link a submission back to a customer
  • Specify the environment / configuration of the editor

Do not store sensitive information in the context (!!) as it is available locally to anyone inspecting the code

<iframe
  src="https://COMPANY_IDENTIFIER.simplepdf.com/editor?open=PUBLICLY_AVAILABLE_PDF_URL&context=CONTEXT"
  frameborder="0"
>
</iframe>

Where CONTEXT is a URL safe Base64 encoded stringified JSON.

const context = { customerId: "123", environment: "production" };
const encodedContext = encodeURIComponent(btoa(JSON.stringify(context)));
const url = `https://COMPANY_IDENTIFIER.simplepdf.com/editor?open=PUBLICLY_AVAILABLE_PDF_URL&context=${encodedContext}`;

Without a SimplePDF account (to use the free PDF editor)

Notice how COMPANY_IDENTIFIER has been replaced with embed

Let your users pick the file on their computer

<iframe src="https://embed.simplepdf.com/editor" frameborder="0"> </iframe>

Open a given PDF file automatically

- Replace PUBLICLY_AVAILABLE_PDF_URL with the url of the PDF to use.

<iframe
  src="https://embed.simplepdf.com/editor?open=PUBLICLY_AVAILABLE_PDF_URL"
  frameborder="0"
>
</iframe>

See Data Privacy & companyIdentifier for reserved values (embed, viewer, etc.) and how data is handled.


Iframe Communication

Programmatic control is only available with a SimplePDF account

The iframe communicates using the postMessage API. All messages are JSON strings that must be parsed with JSON.parse().

Implementation

const iframe = document.getElementById("simplepdf-iframe");

// Helper to send events and receive responses
const sendEvent = (type, data = {}) => {
  return new Promise((resolve, reject) => {
    const requestId = `req_${Date.now()}_${Math.random()
      .toString(36)
      .slice(2)}`;

    const handleResponse = (event) => {
      try {
        const payload = JSON.parse(event.data);
        if (
          payload.type === "REQUEST_RESULT" &&
          payload.data.request_id === requestId
        ) {
          window.removeEventListener("message", handleResponse);
          if (payload.data.result.success) {
            resolve(payload.data.result.data);
          } else {
            reject(payload.data.result.error);
          }
        }
      } catch {
        // Ignore non-JSON messages
      }
    };

    window.addEventListener("message", handleResponse);

    iframe.contentWindow.postMessage(
      JSON.stringify({ type, request_id: requestId, data }),
      "*",
    );

    // Timeout after 30 seconds
    setTimeout(() => {
      window.removeEventListener("message", handleResponse);
      reject({ code: "timeout", message: "Request timed out" });
    }, 30000);
  });
};

// Listen for events sent by the iframe
window.addEventListener("message", (event) => {
  try {
    const payload = JSON.parse(event.data);
    switch (payload.type) {
      case "EDITOR_READY":
        console.log("Editor is ready");
        break;
      case "DOCUMENT_LOADED":
        console.log("Document loaded:", payload.data.document_id);
        break;
      case "PAGE_FOCUSED":
        console.log(
          "Page changed:",
          payload.data.current_page,
          "/",
          payload.data.total_pages,
        );
        break;
      case "SUBMISSION_SENT":
        console.log("Submission sent:", payload.data.submission_id);
        break;
    }
  } catch {
    // Ignore non-JSON messages
  }
});

Usage Examples

// Load a document
await sendEvent("LOAD_DOCUMENT", {
  data_url: "https://example.com/document.pdf",
  name: "my-document.pdf",
  page: 1,
});

// Navigate to a specific page
await sendEvent("GO_TO", { page: 3 });

// Select a tool
await sendEvent("SELECT_TOOL", { tool: "TEXT" }); // or "CHECKBOX", "SIGNATURE", "PICTURE", "BOXED_TEXT", null

// Detect fields in the document
await sendEvent("DETECT_FIELDS", {});

// Remove all fields (or specific ones)
await sendEvent("REMOVE_FIELDS", {}); // Remove all
await sendEvent("REMOVE_FIELDS", { page: 1 }); // Remove page 1 only
await sendEvent("REMOVE_FIELDS", {
  field_ids: ["f_kj8n2hd9x3m1p", "f_q7v5c4b6a0wyz"],
}); // Remove specific fields

// Extract document content
const content = await sendEvent("GET_DOCUMENT_CONTENT", {
  extraction_mode: "auto",
});
console.log("Document name:", content.name);
console.log("Pages:", content.pages); // [{ page: 1, content: "..." }, ...]

// Submit the document
await sendEvent("SUBMIT", { download_copy: true });

Events Reference

Outgoing Events (sent by the iframe)

Event Data Description
EDITOR_READY {} Editor has loaded and is ready to receive commands
DOCUMENT_LOADED { document_id: string } A document has been loaded into the editor
PAGE_FOCUSED { previous_page: number | null, current_page: number, total_pages: number } User navigated to a different page
SUBMISSION_SENT { document_id: string, submission_id: string } Document was successfully submitted
REQUEST_RESULT { request_id: string, result: { success: boolean, data?: any, error?: { code: string, message: string } } } Response to an incoming event

Incoming Events (sent to the iframe)

All incoming events require a request_id field and return a REQUEST_RESULT response.

LOAD_DOCUMENT

Load a PDF document into the editor.

Field Type Required Description
data_url string Yes URL or data URL (base64) of the PDF
name string No Display name for the document
page number No Initial page to display (1-indexed)

Complete loading examples:

// Public URL
await sendEvent("LOAD_DOCUMENT", {
  data_url: "https://example.com/public/document.pdf",
  name: "my-document.pdf",
});

// Pre-signed S3 URL (must encode!)
const presignedUrl =
  "https://bucket.s3.amazonaws.com/doc.pdf?AWSAccessKeyId=...";
await sendEvent("LOAD_DOCUMENT", {
  data_url: encodeURIComponent(presignedUrl),
  name: "my-document.pdf",
});

// Base64 data URL
await sendEvent("LOAD_DOCUMENT", {
  data_url: "data:application/pdf;base64,JVBERi0xLjQK...",
  name: "my-document.pdf",
});

// Blob URL (created from File input)
const file = document.getElementById("fileInput").files[0];
const blobUrl = URL.createObjectURL(file);
await sendEvent("LOAD_DOCUMENT", {
  data_url: blobUrl,
  name: file.name,
});

GO_TO

Navigate to a specific page.

Field Type Required Description
page number Yes Page number to navigate to (1-indexed)

SELECT_TOOL

Select a drawing tool or return to cursor mode.

Field Type Required Description
tool string | null Yes "TEXT", "BOXED_TEXT", "CHECKBOX", "SIGNATURE", "PICTURE", or null for cursor

DETECT_FIELDS

Automatically detect form fields in the document.

No data fields required.

REMOVE_FIELDS

Remove fields from the document.

Field Type Required Description
field_ids string[] No Specific field IDs to remove (omit to remove all)
page number No Only remove fields on this page

Response data:

{
  "removed_count": 5
}

GET_DOCUMENT_CONTENT

Extract text content from the loaded document.

Field Type Required Description
extraction_mode string No "auto" (default) or "ocr" to force OCR processing

Response data:

{
  "name": "document.pdf",
  "pages": [
    { "page": 1, "content": "Text content from page 1..." },
    { "page": 2, "content": "Text content from page 2..." }
  ]
}

SUBMIT

Submit the document for processing.

Field Type Required Description
download_copy boolean Yes Whether to trigger a download of the filled PDF

See Retrieving PDF Data for server-side storage and webhook integration options.


Error Codes

Code Description
bad_request:signup_required Feature requires a SimplePDF account
bad_request:editor_not_ready Editor is not ready to handle the event
bad_request:invalid_page Page must be an integer
bad_request:page_out_of_range Requested page does not exist
bad_request:page_not_found Could not get dimensions for the requested page
bad_request:invalid_field_type Unknown field type
bad_request:invalid_tool Unknown tool type
bad_request:event_not_allowed Event is not allowed for your configuration
forbidden:editing_not_allowed Editing is disabled
forbidden:origin_not_whitelisted Origin is not in your allowed origins list
forbidden:whitelist_required Event requires origin whitelisting