How to add a signature to a PDF using Vue.js

Table of contents

    How to add a signature to a PDF using Vue.js
    TL;DR
    • 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#signDocument to 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:

    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.

    1. You can install the CLI using npm — which comes with Node.js — or yarn:
    npm install -g @vue/cli
    yarn global add @vue/cli

    You can check the version of Vue.js by running the following:

    Terminal window
    vue --version

    This post uses Vue.js CLI version 3.2.37.

    1. Now, create a new Vue.js project to integrate with Nutrient using the following command:
    Terminal window
    vue create pspdfkit-signature-project

    You’ll be asked some configuration questions. Select the default option, and then change your directory to pspdfkit-signature-project:

    Terminal window
    cd pspdfkit-signature-project

    Step 2 — Adding Nutrient

    1. Add the Nutrient dependency to your project:
    Terminal window
    npm install @nutrient-sdk/viewer
    1. Once installed, create a js directory under the project location with the following command:
    Terminal window
    mkdir -p public/js
    1. Copy the Nutrient Web SDK library assets to the public/js directory:
    Terminal window
    cp -R ./node_modules/@nutrient-sdk/viewer/dist/nutrient-viewer-lib public/js/nutrient-viewer-lib

    Step 3 — Displaying a PDF

    1. Rename the PDF document you want to display in your application to document.pdf, and then add the PDF document to the public directory. You can use this demo document as an example.
    2. Next, create a new component to serve as a wrapper for the Nutrient library. In the src/components/ folder, create a file named NutrientContainer.vue with 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>
    1. In the src folder, replace the contents of the App.vue file 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>
    1. Start the app:
    Terminal window
    npm run serve

    Open 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:
    Terminal window
    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:
    Terminal window
    yarn add node-forge
    • Using npm:
    Terminal window
    npm install node-forge

    Step 6 — Preparing the NutrientContainer.vue component

    In the NutrientContainer.vue component, add code to generate a PKCS#7 signature and sign the document:

    1. Fetch the certificate and private key PEM files from the certs directory.
    2. Use node-forge to create a DER-encoded PKCS#7 signature.
    3. Convert the result to ArrayBuffer format 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

    1. Add a button to trigger the signing process.
    2. With your generatePKCS7 method in place, call the NutrientViewer.Instance#signDocument method in your main Vue component (App.vue) to sign the document. The signDocument method takes two arguments:
      1. Options object (can be null if using defaults).
      2. Callback that provides an ArrayBuffer containing the file contents.

    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 signDocument method calls instance.signDocument(null, pdfContainer.generatePKCS7).
    • This initiates the signing process by passing null for default options and the generatePKCS7 method as the callback.
    1. Test the signing functionality by running the application:
    Terminal window
    npm run serve

    Visit 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.

    Hulya Masharipov

    Hulya Masharipov

    Technical Writer

    Hulya is a frontend web developer and technical writer who enjoys creating responsive, scalable, and maintainable web experiences. She’s passionate about open source, web accessibility, cybersecurity privacy, and blockchain.

    Explore related topics

    Try for free Ready to get started?