How to add a signature to a PDF using Vue.js
Table of contents
- Add electronic signatures to PDFs by drawing, typing, or uploading an image
- Implement digital signatures using X.509 certificates and PKCS#7 format
- Use the node-forge library for cryptographic functions
- Call
NutrientViewer.Instance#signDocumentto sign documents programmatically
This tutorial shows how to add signatures to PDF documents using Nutrient and Vue.js. You’ll set up a viewer to display PDF documents and integrate signing functionality.
Nutrient is a JavaScript PDF library for adding electronic and digital signatures to applications.
Vue.js is a frontend JavaScript framework for building user interfaces and single-page applications. It works for both desktop and mobile development, making it versatile for different project types.
Signing PDFs in Vue.js
Users can add signatures to PDFs by drawing, typing, or selecting an image. The drawing option allows users to freehand draw a signature. The typing option lets them type their names and then add a style to their signatures. The final option is to upload an image that’s already saved in their gallery by selecting and dragging the image into the signature box.
Signature support
The Vue.js signature library supports:
- Electronic signatures, which are created through ink, bitmaps, text, and drawings.
- Digital signatures, which include a certificate that provides a unique identifier for the signer. These are used as proof of the document’s origin and can prevent modification by any other person.
Nutrient’s Vue.js PDF library
Nutrient provides a Vue.js PDF library with documented APIs for annotation, signing, and form filling:
- Annotation tools — Draw, highlight, comment, and add notes with 15+ prebuilt tools
- File support — View PNG, JPG, and TIFF files client-side; MS Office documents with server deployment
- 30+ features — PDF editing, digital signatures, form filling, and real-time collaboration
- Dedicated support — Work directly with our developers
Try it for free, or explore our demo.
Requirements
For adding a signature to a PDF, you’ll need the following software installed:
- Git(opens in a new tab) — An open source version control system used to control projects efficiently.
- Node.js(opens in a new tab) — An open source server provider that uses JavaScript on its server. This post uses version 16.13.0.
Step 1 — Setting up a Vue.js project
To create, build, and run Vue.js applications, you need to install Vue CLI(opens in a new tab), which is standard tooling for Vue.js.
- You can install the CLI using
npm— which comes with Node.js — oryarn:
npm install -g @vue/cliyarn global add @vue/cliYou can check the version of Vue.js by running the following:
vue --versionThis post uses Vue.js CLI version 3.2.37.
- Now, create a new Vue.js project to integrate with Nutrient using the following command:
vue create pspdfkit-signature-projectYou’ll be asked some configuration questions. Select the default option, and then change your directory to pspdfkit-signature-project:
cd pspdfkit-signature-projectStep 2 — Adding Nutrient
- Add the Nutrient dependency to your project:
npm install @nutrient-sdk/viewer- Once installed, create a
jsdirectory under the project location with the following command:
mkdir -p public/js- Copy the Nutrient Web SDK library assets to the
public/jsdirectory:
cp -R ./node_modules/@nutrient-sdk/viewer/dist/nutrient-viewer-lib public/js/nutrient-viewer-libStep 3 — Displaying a PDF
- Rename the PDF document you want to display in your application to
document.pdf, and then add the PDF document to thepublicdirectory. You can use this demo document as an example. - Next, create a new component to serve as a wrapper for the Nutrient library. In the
src/components/folder, create a file namedNutrientContainer.vuewith the following content:
<template> <div class="pdf-container"></div></template>
<script>import NutrientViewer from "@nutrient-sdk/viewer";
/** * Nutrient Web SDK example component. */export default { name: 'NutrientContainer', props: { pdfFile: { type: String, required: true, }, }, mounted() { this.loadNutrient().then((instance) => { this.$emit("loaded", instance); }); }, watch: { pdfFile(val) { if (val) { this.loadNutrient(); } }, }, methods: { async loadNutrient() { NutrientViewer.unload(".pdf-container"); return NutrientViewer.load({ document: this.pdfFile, container: ".pdf-container", }); }, }, beforeUnmount() { NutrientViewer.unload(".pdf-container"); },};</script>
<style scoped>.pdf-container { height: 100vh;}</style>- In the
srcfolder, replace the contents of theApp.vuefile with the following to include the newly created component in your app:
<template> <div id="app"> <label for="file-upload" class="custom-file-upload"> Open PDF </label> <input id="file-upload" type="file" @change="openDocument" class="btn" /> <NutrientContainer :pdfFile="pdfFile" @loaded="handleLoaded" /> </div></template>
<script>import NutrientContainer from "@/components/NutrientContainer";
export default { data() { return { pdfFile: this.pdfFile || "/document.pdf", }; }, components: { NutrientContainer, }, methods: { handleLoaded(instance) { console.log("Nutrient has loaded: ", instance); // Do something. }, openDocument(event) { if (this.pdfFile && this.pdfFile.startsWith('blob:')) { window.URL.revokeObjectURL(this.pdfFile); } this.pdfFile = window.URL.createObjectURL(event.target.files[0]); }, },};</script>
<style>#app { font-family: Avenir, Helvetica, Arial, sans-serif; text-align: center; color: #2c3e50;}
body { margin: 0;}
input[type="file"] { display: none;}
.custom-file-upload { border: 1px solid #ccc; border-radius: 4px; display: inline-block; padding: 6px 12px; cursor: pointer; background: #4A8FED; padding: 10px; color: #fff; font: inherit; font-size: 16px; font-weight: bold;}</style>- Start the app:
npm run serveOpen http://localhost:8080/ in your browser to view the website.
After setting up the viewer, proceed with adding signature functionality to your Vue.js application.
Step 4 — Adding digital signature functionality
Now your project is ready for the digital signature to be added to the PDF, but you have to create a digital signature. This requires two things, outlined below.
- First, you need the X.509 certificate that contains the public key and the signer information to sign digitally. Verify the PKCS#7 certificate using the following command:
openssl pkcs7 -noout -text -print_certs -in example.p7b- Second, you need the self-signed private key and the certificate pair.
Refer to the add digital signatures to PDFs using JavaScript guide for more information on how to create a digital signature.
Step 5 — Installing the node-forge library
You’ll now generate a valid digital signature using the cryptographic Distinguished Encoding Rules (DER) PKCS#7(opens in a new tab) format. For this, use the node-forge library to handle cryptographic functions.
To use node-forge for handling cryptographic functions, you need to install it in your Vue.js project. You can do this with either yarn or npm.
- Using
yarn:
yarn add node-forge- Using
npm:
npm install node-forgeStep 6 — Preparing the NutrientContainer.vue component
In the NutrientContainer.vue component, add code to generate a PKCS#7 signature and sign the document:
- Fetch the certificate and private key PEM files from the certs directory.
- Use
node-forgeto create a DER-encoded PKCS#7 signature. - Convert the result to
ArrayBufferformat for signing.
Update your NutrientContainer.vue component with the following code:
<script>import NutrientViewer from '@nutrient-sdk/viewer';import forge from 'node-forge';
export default { name: 'NutrientContainer', props: { pdfFile: { type: String, required: true, }, }, data() { return { instance: null, }; }, mounted() { this.loadPSPDFKit().then((instance) => { this.$emit('loaded', instance); this.instance = instance; // Save the instance for later use. }); }, watch: { pdfFile(val) { if (val) { this.loadPSPDFKit(); } }, }, methods: { async loadPSPDFKit() { NutrientViewer.unload('.pdf-container'); return NutrientViewer.load({ document: this.pdfFile, container: '.pdf-container', }); }, generatePKCS7({ fileContents }) { const certificatePromise = fetch( 'certs/certificate.pem', ).then((response) => response.text()); const privateKeyPromise = fetch( 'certs/private-key.pem', ).then((response) => response.text());
return new Promise((resolve, reject) => { Promise.all([certificatePromise, privateKeyPromise]) .then(([certificatePem, privateKeyPem]) => { const certificate = forge.pki.certificateFromPem( certificatePem, ); const privateKey = forge.pki.privateKeyFromPem( privateKeyPem, );
const p7 = forge.pkcs7.createSignedData(); p7.content = new forge.util.ByteBuffer(fileContents); p7.addCertificate(certificate); p7.addSigner({ key: privateKey, certificate: certificate, digestAlgorithm: forge.pki.oids.sha256, authenticatedAttributes: [ { type: forge.pki.oids.contentType, value: forge.pki.oids.data, }, { type: forge.pki.oids.messageDigest }, { type: forge.pki.oids.signingTime, value: new Date(), }, ], });
p7.sign({ detached: true }); const result = this.stringToArrayBuffer( forge.asn1.toDer(p7.toAsn1()).getBytes(), ); resolve({ pkcs7: result }); }) .catch(reject); }); }, stringToArrayBuffer(binaryString) { const buffer = new ArrayBuffer(binaryString.length); let bufferView = new Uint8Array(buffer); for (let i = 0, len = binaryString.length; i < len; i++) { bufferView[i] = binaryString.charCodeAt(i); } return buffer; }, }, beforeUnmount() { NutrientViewer.unload('.pdf-container'); },};</script>Step 7 — Calling the NutrientViewer.Instance#signDocument method
- Add a button to trigger the signing process.
- With your
generatePKCS7method in place, call theNutrientViewer.Instance#signDocumentmethod in your main Vue component (App.vue) to sign the document. ThesignDocumentmethod takes two arguments:- Options object (can be
nullif using defaults). - Callback that provides an
ArrayBuffercontaining the file contents.
- Options object (can be
In your App.vue file, update the signDocument method as follows:
<template> <div id="app"> <label for="file-upload" class="custom-file-upload"> Open PDF </label> <input id="file-upload" type="file" @change="openDocument" class="btn" />
<!-- Add the "Sign Document" button --> <button @click="signDocument">Sign Document</button>
<!-- Use ref to reference the NutrientContainer component --> <NutrientContainer ref="pdfContainer" :pdfFile="pdfFile" @loaded="handleLoaded" /> </div></template>
<script>import NutrientContainer from '@/components/NutrientContainer';
export default { data() { return { pdfFile: this.pdfFile || '/document.pdf', instance: null, // Add instance to manage Nutrient instance. }; }, components: { NutrientContainer, }, methods: { handleLoaded(instance) { console.log('Nutrient has loaded: ', instance); this.instance = instance; // Save the instance for later use. }, openDocument(event) { if (this.pdfFile && this.pdfFile.startsWith('blob:')) { window.URL.revokeObjectURL(this.pdfFile); } this.pdfFile = window.URL.createObjectURL( event.target.files[0], ); }, signDocument() { // Access the `NutrientContainer` instance via `$refs`. const pdfContainer = this.$refs.pdfContainer;
if (this.instance && pdfContainer) { this.instance .signDocument(null, pdfContainer.generatePKCS7) // Access the method in `NutrientContainer`. .then(() => { console.log('Document signed.'); }) .catch((error) => { console.error( 'The document could not be signed.', error, ); }); } else { console.error('Nutrient instance is not loaded.'); } }, },};</script>
<style>#app { font-family: Avenir, Helvetica, Arial, sans-serif; text-align: center; color: #2c3e50;}
body { margin: 0;}
input[type='file'] { display: none;}
.custom-file-upload { border: 1px solid #ccc; border-radius: 4px; display: inline-block; padding: 6px 12px; cursor: pointer; background: #4a8fed; padding: 10px; color: #fff; font: inherit; font-size: 16px; font-weight: bold;}
button { margin-top: 20px; padding: 10px 20px; background-color: #4a8fed; color: white; border: none; cursor: pointer; font-size: 16px;}</style>In the code above:
- The
signDocumentmethod callsinstance.signDocument(null, pdfContainer.generatePKCS7). - This initiates the signing process by passing
nullfor default options and thegeneratePKCS7method as the callback.
- Test the signing functionality by running the application:
npm run serveVisit http://localhost:8080 in your browser. Load a PDF document and click the Sign Document button to sign it digitally.
Conclusion
You now have a Vue.js application that can add digital signatures to PDF documents using Nutrient. If you run into issues, contact our Support team.
To test Nutrient Web SDK, request a free trial or explore our demo.