Skip to content

RESOURCES / BLOG

Easy document signing in NuxtJS

Signing documents is supposed to be an easy task that has become more than necessary as digital documents and remote work thrives. However, many apps make this by requiring you to download desktop apps or via heavy websites. Let us explore how to create a simple website to sign single-page pdf documents.

The final GitHub code can be viewed here.

The final demo on Codesandbox.

We will be using Nuxt.Js, a Vue.Js framework, to build this project due to its improved simplicity and powerful features. To get started, ensure you have NPM v5.2+/v6.1+ or Yarn installed. Open your preferred working directory and run the following command.

yarn create nuxt-app nuxtjs-easy-document-signing
# OR
npm init nuxt-app nuxtjs-easy-document-signing
Code language: PHP (php)

The above command will result in a series of prompts. Here are our recommended defaults.

Recommended defaults

Once the setup is complete, you may enter and run the project.

cd nuxtjs-easy-document-signing

yarn dev
# OR
npm run dev
Code language: PHP (php)

The project will now be running on localhost:3000

Cloudinary is a powerful media management platform. We will be using it for our image storage and manipulations. First, let us install the recommended Nuxt.Js plugin. Run the following command in the root project folder.

yarn add @nuxtjs/cloudinary
# OR
npm install @nuxtjs/cloudinary
Code language: CSS (css)

Let’s add @nuxtjs/cloudinary to the modules section of our nuxt.config.js file.

// nuxt.config.js
export default {
  ...
  modules: [
    '@nuxtjs/cloudinary'
  ]
  ...
}
Code language: JavaScript (javascript)

Now we need to link our project to our Cloudinary account. We do this by configuring the cloud name.

// nuxt.config.js
export default {
  cloudinary: {
    cloudName: process.env.NUXT_ENV_CLOUDINARY_CLOUD_NAME,
    useComponent: true
  }
}
Code language: JavaScript (javascript)

The above refers to the NUXT_ENV_CLOUDINARY_CLOUD_NAME environmental variable. This value should be in the .env file in the root project folder. Create the file and add your cloud name. You can find it on your Cloudinary dashboard.

<!-- .env -->
NUXT_ENV_CLOUDINARY_CLOUD_NAME=<your-cloudinary-cloud-name>
Code language: HTML, XML (xml)

File upload

The first step is allowing our app users to select and upload a PDF document. This is the document that they are going to sign. Let us first set up the HTML to support this.

<!-- pages/index.vue -->
<template>
    ...
      <form @submit.prevent="upload">
        <div>
          <div>
            <div>
              <h3>Upload PDF Document</h3>
              <p>Upload the PDF document you want to sign</p>
            </div>

            <div>
              <div>
                <input @change="handleFile" type="file" accept=".pdf">
              </div>
            </div>
          </div>
          <div>
            <p v-if="uploading">Uploading... </p>
            <button v-else type="submit">
              Select
            </button>
          </div>
        </div>
      </form>
    ...
</template>
Code language: HTML, XML (xml)

With the above form, our users will be able to select a file for upload. Once the file is selected, the handleFile method will be called while the upload method will be called on form submission. Let us set up these methods in our script section.

// pages/index.vue
<script>
export default {
  data(){
    return {
      file:null,
      uploading:false,
      cloudinaryFile:null,
    };
  },
  methods:{
     async handleFile(e) {
      this.file = e.target.files[0];
    },
    async readData(f) {
      return new Promise((resolve) => {
        const reader = new FileReader();
        reader.onloadend = () => resolve(reader.result);
        reader.readAsDataURL(f);
      });
    },
    async upload() {
      this.uploading = true;
      const fileData = await this.readData(this.file);
      this.cloudinaryFile = await this.$cloudinary.upload(fileData, {
        upload_preset: "default-preset",
        folder: "nuxtjs-easy-document-signing",
      });
      this.uploading = false;
      this.$router.push(`/sign/${encodeURIComponent(this.cloudinaryFile.public_id)}` )
    },
  }
}
</script>
Code language: HTML, XML (xml)

When the file is selected, we store it in our state. When the upload method is called, it uploads the file data to Cloudinary then redirects to the /sign/_image_public_id route using the public_id from the Cloudinary instance returned. As the file is now uploaded to our Cloudinary account, we can now sign it.

Document signing pad

We will be using signature pad to sign the document. Let us first install it. Run the installation command in your root project folder.

yarn add signature_pad
# OR
npm install --save signature_pad
Code language: PHP (php)

Once setup is complete let us create the page to receive the /sign/_image_public_id request, this is the pages/sign/_image_public_id.vue page.

touch pages/sign/_image_public_id.vue

Once the page is created, we need to render our PDF document as an image with the canvas element on top of it. First, we receive the URL parameter and create a computed property to return the image URL.

// pages/sign/_image_public_id.vue
<script>
...
export default {
   asyncData ({ params }) {
    return {
       imagePublicId: params.image_public_id
    }
  },
  ...
  computed:{
    imageUrl(){
      return this.$cloudinary.image.url(this.imagePublicId);
    }
  },
  ...
};
</script>
Code language: HTML, XML (xml)

We can now add the HMTL for the image and the canvas.

<!-- pages/sign/_image_public_id.vue -->
<template>
  <div>
    ...
    <section class="sign-container">
      <div class="wrapper border">
        <img :src="imageUrl" height="800" width="600"/>
        <canvas id="signature-pad" height="800" width="600"></canvas>
      </div>
    </section>

    <div>
      <p v-if="uploading">Generating Signature & Uploading... </p>
      <span v-else>
        <button 
          type="button" 
          @click="save"
        >
          Save
        </button>
        
        <button 
          type="button" 
          @click="clear"
        >
          Clear
        </button>
      </span>
    </div>

  </div>
</template>
...

<style scoped>
.sign-container{
  width:100vw;
  height:100vh;
}
.sign-container .wrapper{
  height:800px;
  width:600px;
  margin-left:auto;
  margin-right:auto;
  position:relative;
  -moz-user-select: none;
  -webkit-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

.sign-container .wrapper img{
  top:0;
  left:0;
  position:absolute;
  height:800px;
  width:600px;
}

.sign-container .wrapper #signature-pad{
  position:absolute;
  top:0;
  left:0;
  height:800px;
  width:600px;
}
</style>
Code language: HTML, XML (xml)

We have also added two buttons. One triggers the save method to save the signature and the other triggers the clear method to clear the canvas.

Additionally, we have included the corresponding CSS to ensure all the elements are positioned correctly.

Let us now initialize the canvas as well as add our save and clear logic.

<script>
// pages/sign/_image_public_id.vue

import SignaturePad from "signature_pad";

export default {
  ...
  data(){
    return {
      signaturePad:null,
      uploading:false,
    }
  },
  mounted(){
    this.initializePad();
  },
  ...
  methods:{
    initializePad(){
      this.signaturePad = new SignaturePad(document.getElementById('signature-pad'));
    },
    async save(){
        this.uploading = true;
        
        var data = this.signaturePad.toDataURL('image/png');

        const signatureCloudinaryInstance = await this.$cloudinary.upload(data, {
          folder: 'nuxtjs-easy-document-signing',
          upload_preset: "default-preset",
        });

        this.$router.push(`/preview/${encodeURIComponent(this.imagePublicId)}/${encodeURIComponent(signatureCloudinaryInstance.public_id)}` )
    },
    clear(){
        this.signaturePad.clear();
    }
  }
}
</script>
Code language: HTML, XML (xml)

The initializePad method initializes the signature pad onto the canvas using the signature-pad ID. When we trigger the clear method, the signature pad is cleared.

Once we have finished adding our signature, the save methods retrieves the signature as a PNG image and upload it to Cloudinary. We are then pushed to the /preview/_image_public_id/_signature_public_id route to preview the outcome.

Signed PDF Preview

In order to preview the signed PDF, we first need to create the pages/preview/_image_public_id/_signature_public_id.vue file as it will be responsible for the /preview/_image_public_id/_signature_public_id route.

touch pages/preview/_image_public_id/_signature_public_id.vue

We will first receive our query parameters and store them in our state. Using the state parameters of the image public id and the signature public id, we can now generate a PDF link.

// pages/preview/_image_public_id/_signature_public_id.vue

<script>

export default {
   asyncData ({ params }) {
        return {
            imagePublicId: params.image_public_id,
            signaturePublicId: params.signature_public_id
        }
  },
  computed:{
    pdfLink(){
      return this.$cloudinary.image.url(
        this.imagePublicId,
        {
          transformation:[
            {overlay:this.signaturePublicId.replace(/\//g, ':')},
          ]
        }
        ).replace("/f_auto,q_auto","");
    }
  }
}
</script>
Code language: HTML, XML (xml)

We can now preview the final PDF as well as add a download link to the UI.

<!-- pages/preview/_image_public_id/_signature_public_id.vue -->
<template>
  <div>
    ...

    <div class="text-center">
        <cld-image 
            :public-id="imagePublicId" 
            height="800" 
            width="600"
        >
            <cld-transformation
                :overlay="signaturePublicId.replace(/\//g, ':')"
            />
        </cld-image>
    </div>

    <div>
      <a 
        :href="pdfLink"
        target="_blank"
      >
        View PDF
      </a>
      <nuxt-link to="/" >
        Start again
      </nuxt-link>
    </div>

  </div>
</template>
Code language: HTML, XML (xml)

With the above, we have now allowed our app’s users to be able to sign PDF documents, preview the output and download the signed PDF. To learn more about Cloudinary transformations, feel free to review the documentation. You may also read more about the Signature pad on the Github readme.

Start Using Cloudinary

Sign up for our free plan and start creating stunning visual experiences in minutes.

Sign Up for Free