diff --git a/assets/vpu-qualified-signature-pdf-upload.metadata.json b/assets/vpu-qualified-signature-pdf-upload.metadata.json
new file mode 100644
index 0000000000000000000000000000000000000000..25d61b95bc830be123b7045f6312a08af1caf239
--- /dev/null
+++ b/assets/vpu-qualified-signature-pdf-upload.metadata.json
@@ -0,0 +1,17 @@
+{
+  "element": "vpu-qualified-signature-pdf-upload",
+  "module_src": "vpu-qualified-signature-pdf-upload.js",
+  "routing_name": "qualified-pdf-upload",
+  "name": {
+    "de": "Persönliche aufbringen",
+    "en": "Qualifiedly sign"
+  },
+  "short_name": {
+    "de": "Persönliche Signatur aufbringen",
+    "en": "Qualifiedly sign"
+  },
+  "description": {
+    "de": "Erlaubt das Hochladen von PDF Dateien um sie mit einer persönlichen Signatur zu versehen",
+    "en": "Allows upload of PDF files to qualifiedly sign them"
+  }
+}
diff --git a/assets/vpu-signature.topic.metadata.json b/assets/vpu-signature.topic.metadata.json
index 5f421efe115df843447166a752b811f8f151e7dc..3c6b9ec0930a2674e46c86e3d5bb9896ff7869c8 100644
--- a/assets/vpu-signature.topic.metadata.json
+++ b/assets/vpu-signature.topic.metadata.json
@@ -13,7 +13,8 @@
   },
   "routing_name": "signature",
   "activities": [
-    {"path": "vpu-official-signature-pdf-upload.metadata.json"}
+    {"path": "vpu-official-signature-pdf-upload.metadata.json"},
+    {"path": "vpu-qualified-signature-pdf-upload.metadata.json"}
   ],
   "attributes": []
 }
\ No newline at end of file
diff --git a/rollup.config.js b/rollup.config.js
index 567a122afee18447d01c8d94acfe8a49512aa74b..d446bf13be4ac688f070577a3fd3fb851523e2cc 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -143,6 +143,7 @@ export default {
     input: (build != 'test') ? [
       'src/vpu-signature.js',
       'src/vpu-official-signature-pdf-upload.js',
+      'src/vpu-qualified-signature-pdf-upload.js',
     ] : glob.sync('test/**/*.js'),
     output: {
       dir: 'dist',
diff --git a/src/i18n/de/translation.json b/src/i18n/de/translation.json
index 008adb4f034278b961b6db70dc2b1270ba1a3683..b5d717e3613741ffc792665c7dc4e893d5fed14d 100644
--- a/src/i18n/de/translation.json
+++ b/src/i18n/de/translation.json
@@ -13,6 +13,20 @@
     "re-upload-all-button": "Alle erneut hochladen",
     "re-upload-all-button-title": "Alle fehlgeschlagen Uploads erneut hochladen"
   },
+  "qualified-pdf-upload": {
+    "upload-field-label": "PDF Dateien zum Signieren hochladen",
+    "upload-area-text": "Sie können in diesem Bereich PDF Dateien per Drag & Drop oder per Direktauswahl hochladen",
+    "signed-files-label": "Signierte Dateien",
+    "download-zip-button": "Als ZIP Datei herunterladen",
+    "download-zip-button-tooltip": "Alle signierten Dateien als ZIP Datei herunterladen",
+    "upload-button-label": "PDF Dateien auswählen",
+    "download-file-button-title": "Signiertes PDF herunterladen",
+    "error-files-label": "Fehlgeschlagene Signiervorgänge",
+    "re-upload-file-button-title": "Erneut hochladen",
+    "upload-status-file-text": "({{fileSize}}) wird hochgeladen und verarbeitet...",
+    "re-upload-all-button": "Alle erneut hochladen",
+    "re-upload-all-button-title": "Alle fehlgeschlagen Uploads erneut hochladen"
+  },
   "error-summary": "Ein Fehler ist aufgetreten",
   "error-permission-message": "Sie müssen das Recht auf Amtssignaturen besitzen um diese Funktion nutzen zu können!",
   "error-login-message": "Sie müssen eingeloggt sein um diese Funktion nutzen zu können!"
diff --git a/src/i18n/en/translation.json b/src/i18n/en/translation.json
index 752e914ab8293bc83bc8af1c620fcdeba4d013c2..5705ce8d1a07d325e7446527b2937d734991c44f 100644
--- a/src/i18n/en/translation.json
+++ b/src/i18n/en/translation.json
@@ -13,6 +13,20 @@
     "re-upload-all-button": "Upload all",
     "re-upload-all-button-title": "Upload all failed uploads again"
   },
+  "qualified-pdf-upload": {
+    "upload-field-label": "Upload PDF files to sign",
+    "upload-area-text": "In this area you can upload PDF files via Drag & Drop or by selecting them directly",
+    "signed-files-label": "Signed files",
+    "download-zip-button": "Download ZIP",
+    "download-zip-button-tooltip": "Download all signed files as ZIP file",
+    "upload-button-label": "Select PDF files",
+    "download-file-button-title": "Download signed PDF",
+    "error-files-label": "Failed signing processes",
+    "re-upload-file-button-title": "Upload again",
+    "upload-status-file-text": "({{fileSize}}) is currently uploading and being processed...",
+    "re-upload-all-button": "Upload all",
+    "re-upload-all-button-title": "Upload all failed uploads again"
+  },
   "error-summary": "An error occurred",
   "error-permission-message": "You need have permissions to use the official signature to use this function!",
   "error-login-message": "You need to be logged in to use this function!"
diff --git a/src/vpu-qualified-signature-pdf-upload.js b/src/vpu-qualified-signature-pdf-upload.js
new file mode 100644
index 0000000000000000000000000000000000000000..84e5096ea49613dd96a09179ab01999bb3d69bc7
--- /dev/null
+++ b/src/vpu-qualified-signature-pdf-upload.js
@@ -0,0 +1,320 @@
+import {createI18nInstance} from './i18n.js';
+import {humanFileSize} from 'vpu-common/i18next.js';
+import {css, html} from 'lit-element';
+import VPUSignatureLitElement from "./vpu-signature-lit-element";
+import * as commonUtils from 'vpu-common/utils';
+import * as utils from './utils';
+import JSZip from 'jszip/dist/jszip.js';
+import 'file-saver';
+import * as commonStyles from 'vpu-common/styles';
+import {classMap} from 'lit-html/directives/class-map.js';
+import 'vpu-file-upload';
+
+const i18n = createI18nInstance();
+
+class QualifiedSignaturePdfUpload extends VPUSignatureLitElement {
+    constructor() {
+        super();
+        this.lang = i18n.language;
+        this.entryPointUrl = commonUtils.getAPiUrl();
+        this.signingUrl = this.entryPointUrl + "/qualifiedly_signed_documents/sign";
+        this.signedFiles = [];
+        this.signedFilesCount = 0;
+        this.errorFiles = [];
+        this.errorFilesCount = 0;
+        this.uploadInProgress = false;
+        this.uploadStatusFileName = "";
+        this.uploadStatusText = "";
+    }
+
+    static get properties() {
+        return {
+            lang: { type: String },
+            entryPointUrl: { type: String, attribute: 'entry-point-url' },
+            signedFiles: { type: Array, attribute: false },
+            signedFilesCount: { type: Number, attribute: false },
+            errorFiles: { type: Array, attribute: false },
+            errorFilesCount: { type: Number, attribute: false },
+            uploadInProgress: { type: Boolean, attribute: false },
+            uploadStatusFileName: { type: String, attribute: false },
+            uploadStatusText: { type: String, attribute: false },
+        };
+    }
+
+    connectedCallback() {
+        super.connectedCallback();
+
+        this.updateComplete.then(()=>{
+            const fileUpload = this._("#file-upload");
+            fileUpload.addEventListener('vpu-fileupload-all-start', this.onAllUploadStarted.bind(this));
+            fileUpload.addEventListener('vpu-fileupload-file-start', this.onFileUploadStarted.bind(this));
+            fileUpload.addEventListener('vpu-fileupload-file-finished', this.onFileUploadFinished.bind(this));
+            fileUpload.addEventListener('vpu-fileupload-all-finished', this.onAllUploadFinished.bind(this));
+        });
+    }
+
+    /**
+     * @param ev
+     */
+    onAllUploadStarted(ev) {
+        console.log("Start upload process!");
+        this.uploadInProgress = true;
+    }
+
+    /**
+     * @param ev
+     */
+    onFileUploadStarted(ev) {
+        console.log(ev);
+        this.uploadStatusFileName = ev.detail.fileName;
+        this.uploadStatusText = i18n.t('qualified-pdf-upload.upload-status-file-text', {
+            fileName: ev.detail.fileName,
+            fileSize: humanFileSize(ev.detail.fileSize, false),
+        });
+    }
+
+    /**
+     * @param ev
+     */
+    onFileUploadFinished(ev) {
+        if (ev.detail.status !== 201) {
+            // this doesn't seem to trigger an update() execution
+            this.errorFiles[Math.floor(Math.random() * 1000000)] = ev.detail;
+            // this triggers the correct update() execution
+            this.errorFilesCount++;
+        } else if (ev.detail.json["@type"] === "http://schema.org/MediaObject" ) {
+            // this doesn't seem to trigger an update() execution
+            this.signedFiles.push(ev.detail.json);
+            // this triggers the correct update() execution
+            this.signedFilesCount++;
+        }
+    }
+
+    /**
+     * @param ev
+     */
+    onAllUploadFinished(ev) {
+        console.log("Finished upload process!");
+        this.uploadInProgress = false;
+    }
+
+    update(changedProperties) {
+        changedProperties.forEach((oldValue, propName) => {
+            if (propName === "lang") {
+                i18n.changeLanguage(this.lang);
+            }
+
+            console.log(propName, oldValue);
+        });
+
+        super.update(changedProperties);
+    }
+
+    onLanguageChanged(e) {
+        this.lang = e.detail.lang;
+    }
+
+    /**
+     * Download signed pdf files as zip
+     */
+    zipDownloadClickHandler() {
+        // see: https://stuk.github.io/jszip/
+        let zip = new JSZip();
+        const that = this;
+        let fileNames = [];
+
+        // add all signed pdf files
+        this.signedFiles.forEach((file) => {
+            let fileName = file.name;
+
+            // add pseudo-random string on duplicate file name
+            if (fileNames.indexOf(fileName) !== -1) {
+                fileName = utils.baseName(fileName) + "-" + Math.random().toString(36).substring(7) + ".pdf";
+            }
+
+            fileNames.push(fileName);
+            zip.file(fileName, utils.getPDFFileBase64Content(file), {base64: true});
+        });
+
+        zip.generateAsync({type:"blob"})
+            .then(function(content) {
+                // save with FileSaver.js
+                // see: https://github.com/eligrey/FileSaver.js
+                saveAs(content, "signed-documents.zip");
+
+                that._("#zip-download-button").stop();
+            });
+    }
+
+    /**
+     * Re-Upload all failed files
+     */
+    reUploadAllClickHandler() {
+        const that = this;
+
+        // we need to make a copy and reset the queue or else our queue will run crazy
+        const errorFilesCopy = {...this.errorFiles};
+        this.errorFiles = [];
+        this.errorFilesCount = 0;
+
+        commonUtils.asyncObjectForEach(errorFilesCopy, async (file, id) => {
+            await this.fileUploadClickHandler(file.file, id);
+        });
+
+        that._("#re-upload-all-button").stop();
+    }
+
+    /**
+     * Download one signed pdf file
+     *
+     * @param file
+     */
+    fileDownloadClickHandler(file) {
+        const arr = utils.convertDataURIToBinary(file.contentUrl);
+        const blob = new Blob([arr], { type: utils.getDataURIContentType(file.contentUrl) });
+
+        // see: https://github.com/eligrey/FileSaver.js
+        saveAs(blob, file.name);
+    }
+
+    /**
+     * Uploads a failed pdf file again
+     *
+     * @param file
+     * @param id
+     */
+    async fileUploadClickHandler(file, id) {
+        this.uploadInProgress = true;
+        this.errorFiles.splice(id, 1);
+        this.errorFilesCount = this.errorFiles.length;
+        await this._("#file-upload").uploadFile(file);
+        this.uploadInProgress = false;
+    }
+
+    static get styles() {
+        // language=css
+        return css`
+            ${commonStyles.getThemeCSS()}
+            ${commonStyles.getGeneralCSS()}
+            ${commonStyles.getButtonCSS()}
+            ${commonStyles.getNotificationCSS()}
+
+            h2 {
+                margin-bottom: inherit;
+            }
+
+            .hidden {
+                display: none;
+            }
+
+            .files-block .file {
+                margin: 10px 0;
+            }
+
+            .error-files .file {
+                display: grid;
+                grid-template-columns: 40px auto;
+            }
+
+            .files-block .file .button-box {
+                display: flex;
+                align-items: center;
+            }
+
+            .files-block .file .info {
+                display: inline-block;
+                vertical-align: middle;
+            }
+
+            .file .info strong {
+                font-weight: 600;
+            }
+
+            .notification vpu-mini-spinner {
+                position: relative;
+                top: 2px;
+                margin-right: 5px;
+            }
+
+            .error {
+                color: #e4154b;
+            }
+        `;
+    }
+
+    getSignedFilesHtml() {
+        return this.signedFiles.map(file => html`
+            <div class="file">
+                <a class="is-download"
+                    title="${i18n.t('qualified-pdf-upload.download-file-button-title')}"
+                    @click="${() => {this.fileDownloadClickHandler(file);}}">
+                    ${file.name} (${humanFileSize(file.contentSize)}) <vpu-icon name="download"></vpu-icon></a>
+            </div>
+        `);
+    }
+
+    getErrorFilesHtml() {
+        return this.errorFiles.map((data, id) => html`
+            <div class="file">
+                <div class="button-box">
+                    <button class="button is-small"
+                            title="${i18n.t('qualified-pdf-upload.re-upload-file-button-title')}"
+                            @click="${() => {this.fileUploadClickHandler(data.file, id);}}"><vpu-icon name="reload"></vpu-icon></button>
+                </div>
+                <div class="info">
+                    ${data.file.name} (${humanFileSize(data.file.size)})
+                    <strong class="error">${data.json["hydra:description"]}</strong>
+                </div>
+            </div>
+        `);
+    }
+
+    render() {
+        return html`
+            <div class="${classMap({hidden: !this.isLoggedIn() || !this.hasSignaturePermissions()})}">
+                <div class="field">
+                    <h2>${i18n.t('qualified-pdf-upload.upload-field-label')}</h2>
+                    <div class="control">
+                        <vpu-fileupload id="file-upload" lang="${this.lang}" url="${this.signingUrl}" accept="application/pdf"
+                            text="${i18n.t('qualified-pdf-upload.upload-area-text')}" button-label="${i18n.t('qualified-pdf-upload.upload-button-label')}"></vpu-fileupload>
+                    </div>
+                </div>
+                <div class="field notification is-info ${classMap({hidden: !this.uploadInProgress})}">
+                    <vpu-mini-spinner></vpu-mini-spinner>
+                    <strong>${this.uploadStatusFileName}</strong>
+                    ${this.uploadStatusText}
+                </div>
+                <div class="files-block field ${classMap({hidden: this.signedFilesCount === 0})}">
+                    <h2>${i18n.t('qualified-pdf-upload.signed-files-label')}</h2>
+                    <div class="control">
+                        ${this.getSignedFilesHtml()}
+                    </div>
+                </div>
+                <div class="field ${classMap({hidden: this.signedFilesCount === 0})}">
+                    <div class="control">
+                        <vpu-button id="zip-download-button" value="${i18n.t('qualified-pdf-upload.download-zip-button')}" title="${i18n.t('qualified-pdf-upload.download-zip-button-tooltip')}" @click="${this.zipDownloadClickHandler}" type="is-primary"></vpu-button>
+                    </div>
+                </div>
+                <div class="files-block error-files field ${classMap({hidden: this.errorFilesCount === 0})}">
+                    <h2 class="error">${i18n.t('qualified-pdf-upload.error-files-label')}</h2>
+                    <div class="control">
+                        ${this.getErrorFilesHtml()}
+                    </div>
+                </div>
+                <div class="field ${classMap({hidden: this.errorFilesCount === 0})}">
+                    <div class="control">
+                        <vpu-button id="re-upload-all-button" ?disabled="${this.uploadInProgress}" value="${i18n.t('qualified-pdf-upload.re-upload-all-button')}" title="${i18n.t('qualified-pdf-upload.re-upload-all-button-title')}" @click="${this.reUploadAllClickHandler}" type="is-primary"></vpu-button>
+                    </div>
+                </div>
+            </div>
+            <div class="notification is-warning ${classMap({hidden: this.isLoggedIn()})}">
+                ${i18n.t('error-login-message')}
+            </div>
+            <div class="notification is-danger ${classMap({hidden: this.hasSignaturePermissions() || !this.isLoggedIn()})}">
+                ${i18n.t('error-permission-message')}
+            </div>
+        `;
+    }
+}
+
+commonUtils.defineCustomElement('vpu-qualified-signature-pdf-upload', QualifiedSignaturePdfUpload);