Skip to main content

Integrate the Document Viewer

The Document Viewer is a web component that lets users edit templates and prepare documents for signing — directly inside your application. This guide shows how to connect it to the GraphQL API to build a complete signing workflow.

Overview

A typical integration follows these steps:

  1. Authenticate — get a token for the viewer
  2. Upload a template — via the GraphQL API
  3. Render the viewer — pass the template ID and token
  4. Capture events — the viewer emits update and validate events as the user works
  5. Send the document — call the send mutation with the recipient and role data from the viewer

Authentication

The viewer needs a valid access token passed to its token attribute. Get one using the authentication flow:

const LEGALESIGN_AUTH_ENDPOINT = 'https://cognito-idp.eu-west-2.amazonaws.com';
const GRAPHQL_ENDPOINT = 'https://graphql.uk.legalesign.com/graphql';

const getToken = async () => {
const response = await fetch(LEGALESIGN_AUTH_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/x-amz-json-1.1',
'X-Amz-Target': 'AWSCognitoIdentityProviderService.InitiateAuth',
},
body: JSON.stringify({
AuthFlow: 'USER_PASSWORD_AUTH',
ClientId: '<your-client-id>',
AuthParameters: {
USERNAME: '<your-username>',
PASSWORD: '<your-password>',
},
}),
});
const data = await response.json();
return data.AuthenticationResult.AccessToken;
};

Compose Mode — Send a Pre-built Document

Compose mode is for workflows where your system already knows the recipients. The user just places signature fields and sends.

1. Render the viewer

<ls-document-viewer
id="viewer"
templateid="dHBsYjQ5YTg5NWQtYWRhMy0xMWYwLWIxZGMtMDY5NzZlZmU0MzIx"
token="YOUR_ACCESS_TOKEN"
mode="compose"
recipients='[
{"email": "jane@example.com", "firstname": "Jane", "lastname": "Smith", "signerIndex": 1},
{"email": "john@example.com", "firstname": "John", "lastname": "Doe", "signerIndex": 2}
]'
filtertoolbox="signature|initials|date|text"
>
<span slot="right-button">
<button id="send-btn" disabled>Send</button>
</span>
</ls-document-viewer>

2. Track validation and role IDs

As the user adds fields, the viewer emits events. You need two things from these events:

  • validate — tells you whether the template has all required fields (at minimum, one signature per recipient)
  • update — provides the current template state, including roleId for each participant
const viewer = document.getElementById('viewer');
const sendBtn = document.getElementById('send-btn');

let isValid = false;
const recipientRoles = {};

viewer.addEventListener('validate', (e) => {
isValid = e.detail.valid;
sendBtn.disabled = !isValid;
});

viewer.addEventListener('update', (e) => {
const roles = e.detail.template.roles;
roles.forEach((role) => {
recipientRoles[role.signerIndex] = role.id;
});
});

The roleId from the update event maps each recipient to their fields on the template. You must include it when sending.

3. Send the document

When the user clicks send, call the send mutation with the recipient data and role IDs captured from the viewer:

sendBtn.addEventListener('click', async () => {
const token = await getToken();

const recipients = [
{
firstName: 'Jane',
lastName: 'Smith',
email: 'jane@example.com',
role: 'Signer',
roleId: recipientRoles[1],
signerIndex: 1,
order: 1,
},
{
firstName: 'John',
lastName: 'Doe',
email: 'john@example.com',
role: 'Signer',
roleId: recipientRoles[2],
signerIndex: 2,
order: 2,
},
];

const response = await fetch(GRAPHQL_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
query: `mutation SendDocument {
send(input: {
groupId: "YOUR_GROUP_ID"
templateId: "dHBsYjQ5YTg5NWQtYWRhMy0xMWYwLWIxZGMtMDY5NzZlZmU0MzIx"
title: "Contract - Jane Smith & John Doe"
recipients: ${JSON.stringify(recipients).replace(/"(\w+)":/g, '$1:')}
sequentialSigning: true
})
}`,
}),
});

const result = await response.json();
const taskId = result.data.send;

// Poll for completion
console.log('Task started:', taskId);
});

The send mutation returns a task ID. Poll the task query to check when sending is complete.

Editor Mode — Template Creation

Editor mode gives users the full template editing experience — adding roles, placing fields, configuring options. Use this when you want users to build reusable templates.

<ls-document-viewer
id="editor"
templateid="dHBsYjQ5YTg5NWQtYWRhMy0xMWYwLWIxZGMtMDY5NzZlZmU0MzIx"
token="YOUR_ACCESS_TOKEN"
mode="editor"
></ls-document-viewer>

In editor mode, the update event fires whenever the template changes. You can use this to track the template state or show a save indicator:

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

editor.addEventListener('update', (e) => {
console.log('Template updated:', e.detail);
});

editor.addEventListener('validate', (e) => {
console.log('Template valid:', e.detail.valid);
});

Template changes are saved automatically by the viewer — you don't need to call any GraphQL mutation to persist edits.

React Integration

The React wrapper provides the same functionality with React-style event handling:

import { useState, useEffect } from 'react';
import { LsDocumentViewer } from 'legalesign-document-viewer-react';

const DocumentCompose = ({ templateId, recipients }) => {
const [token, setToken] = useState(null);
const [isValid, setIsValid] = useState(false);
const [roleMap, setRoleMap] = useState({});

useEffect(() => {
getToken().then(setToken);
}, []);

const handleValidate = (e) => {
setIsValid(e.detail.valid);
};

const handleUpdate = (e) => {
const roles = e.detail.template.roles;
const map = {};
roles.forEach((role) => {
map[role.signerIndex] = role.id;
});
setRoleMap(map);
};

const handleSend = async () => {
const mappedRecipients = recipients.map((r, i) => ({
...r,
roleId: roleMap[r.signerIndex],
order: i + 1,
}));

const response = await fetch(GRAPHQL_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
query: `mutation SendDocument {
send(input: {
groupId: "YOUR_GROUP_ID"
templateId: "${templateId}"
title: "Document"
recipients: ${JSON.stringify(mappedRecipients).replace(/"(\w+)":/g, '$1:')}
})
}`,
}),
});

const result = await response.json();
console.log('Task ID:', result.data.send);
};

if (!token) return null;

return (
<LsDocumentViewer
templateid={templateId}
token={token}
mode="compose"
recipients={JSON.stringify(recipients)}
onValidate={handleValidate}
onUpdate={handleUpdate}
>
<div slot="right-button">
<button onClick={handleSend} disabled={!isValid}>
Send
</button>
</div>
</LsDocumentViewer>
);
};

Witnesses

To add a witness for a signer, set roleType: "WITNESS" and use a signerIndex of the parent signer's index + 100. For example, a witness for signer 2 has signerIndex: 102:

<ls-document-viewer
mode="compose"
recipients='[
{"email": "signer@example.com", "firstname": "Alice", "lastname": "Jones", "signerIndex": 1},
{"email": "signer2@example.com", "firstname": "Bob", "lastname": "Brown", "signerIndex": 2},
{"email": "witness@example.com", "firstname": "Carol", "lastname": "White", "signerIndex": 102, "roleType": "WITNESS"}
]'
...
></ls-document-viewer>

Key Concepts

Viewer conceptGraphQL typeDescription
templateid attributeTemplateThe template being edited or composed
recipients attributeRecipientInputRecipients passed to compose mode
roleId from update eventRoleLinks a recipient to their fields on the template
validate eventWhether the template has all required fields
send mutationsendSends the document after composition
token attributeAccess token from authentication
mode="compose"Streamlined mode for placing fields and sending
mode="editor"Full template editing mode
experience in send inputExperienceControls branding, emails, and signing page — see Experiences explained