---
title: Integrate PDF Generator in Survey Creator
product: Survey Creator
description: Need to generate custom PDF forms? With SurveyJS UI components, you can build and export fillable or read-only forms right in your JavaScript app. View our demo to learn more.
framework: Angular
source: https://surveyjs.io/survey-creator/examples/integrate-pdf-generator/angular
index: https://surveyjs.io/survey-creator/examples/overview.md
---

# Integrate PDF Generator in Survey Creator (Angular)

[SurveyJS PDF Generator](https://surveyjs.io/pdf-generator/documentation/overview) is a JavaScript UI component that enables you to render SurveyJS surveys and forms as PDF files directly in a browser. You can print these PDF forms for offline use, such as at conferences and workshops, or share editable forms with respondents to complete online. This example demonstrates how you can integrate PDF Generator into Survey Creator to generate and preview PDF forms as you configure them.

## Install PDF Generator

PDF Generator is built upon the <a href="https://github.com/parallax/jsPDF#readme" target="_blank">jsPDF</a> library. Select and implement one of the following options to set up PDF Generator:

- Option 1: Insert the jsPDF and PDF Generator scripts within the `<head>` tag on your HTML page.

    ```html
    <head>
        <script src="https://unpkg.com/jspdf/dist/jspdf.umd.min.js"></script>
        <script src="https://unpkg.com/survey-pdf/survey.pdf.min.js"></script>
        <!-- ... -->
    </head>
    ```
- Option 2: Install the `survey-pdf` npm package.           
    The jsPDF npm package will be installed automatically as a dependency.

    ```sh
    npm install survey-pdf
    ```

## Add PDF Generator Settings to the Property Grid

PDF Generator settings allow you to customize page orientation, margins, font, and other document parameters (see [`IDocOptions`](https://surveyjs.io/pdf-generator/documentation/api-reference/idocoptions)). Survey Creator users can change these settings in the Property Grid. To make them available, [add corresponding custom properties](https://surveyjs.io/form-library/documentation/customize-question-types/add-custom-properties-to-a-form) to the `"survey"` class:

```js
Serializer.addProperty("survey", {
    name: "pdfOrientation",
    displayName: "Page orientation",
    type: "buttongroup",
    category: pdfCategoryName,
    categoryIndex: 800,
    default: "auto",
    choices: [ "auto", "p|Portrait", "l|Landscape" ],
    isSerializable: false
});
```

This demo also adds a custom `pdfIsPageBreak` property to the `"question"` class. When enabled for a question, this property inserts a page break before that question:

```js
Serializer.addProperty("question", {
    name: "pdfIsPageBreak:boolean",
    displayName: "Insert a page break before this element",
    category: pdfCategoryName,
    categoryIndex: 800,
    default: false,
    isSerializable: false
});
```

## Generate a PDF Form

To save a survey as a PDF document, follow the steps below:

1. Prepare the PDF document.               
Use the PDF settings from the survey being configured to build an [`IDocOptions`](https://surveyjs.io/pdf-generator/documentation/api-reference/idocoptions) object and apply other customizations to the document. 

1. Instantiate `SurveyPDF`.          
Pass a survey JSON schema and the `IDocOptions` object to the `SurveyPDF` constructor.

1. Call the `save(fileName)` method.            
This method saves a PDF document under a specified name on the user's device.

1. Add a custom toolbar button.        
Combine steps 1 to 3 into a single function and call it by a click on a [custom toolbar button](/survey-creator/examples/toolbar-customization/documentation).

Refer to the Code tab for a full code example.

## Files

### `src/app/components/creator.component.css`

```css
/* You can define custom CSS rules here */
```

### `src/app/components/creator.component.html`

```html
<div style="position: fixed; top: 0; bottom: 0; right: 0; left: 0;">
    <survey-creator [model]="model"></survey-creator>
</div>
```

### `src/app/components/survey_json.js`

```js
export const formJSON = {
  "title": "NPS Survey",
  "pages": [
    {
      "name": "page1",
      "elements": [
        {
          "type": "rating",
          "name": "nps-score",
          "title": "On a scale from 0 to 10, how likely are you to recommend us to a friend or colleague?",
          "rateMin": 0,
          "rateMax": 10,
          "minRateDescription": "Very unlikely",
          "maxRateDescription": "Very likely",
          "rateDescriptionLocation": "bottom"
        },
        {
          "type": "comment",
          "name": "improvements-required",
          "title": "What can we do to make your experience more satisfying?",
          "maxLength": 300
        },
        {
          "type": "checkbox",
          "name": "promoter-features",
          "title": "Which of the following features do you value the most?",
          "description": "Please select no more than three features.",
          "isRequired": true,
          "choices": [
            {
              "value": "performance",
              "text": "Performance"
            },
            {
              "value": "stability",
              "text": "Stability"
            },
            {
              "value": "ui",
              "text": "User interface"
            },
            {
              "value": "complete-functionality",
              "text": "Complete functionality"
            },
            {
              "value": "learning-materials",
              "text": "Learning materials (documentation, demos, code examples)"
            },
            {
              "value": "support",
              "text": "Quality support"
            }
          ],
          "showOtherItem": true,
          "otherPlaceholder": "Please specify...",
          "otherText": "Other features",
          "colCount": 2,
          "maxSelectedChoices": 3
        },
        {
          "type": "boolean",
          "name": "rebuy",
          "title": "Would you buy our product again?"
        }
      ]
    },
    {
      "name": "page3",
      "elements": [
        {
          "type": "radiogroup",
          "name": "testimonial",
          "title": "Would you mind providing us a brief testimonial for the website?",
          "choices": [
            {
              "value": "yes",
              "text": "Sure!"
            },
            {
              "value": "no",
              "text": "No"
            }
          ]
        },
        {
          "type": "text",
          "name": "email",
          "title": "What is your email address?",
          "validators": [
            {
              "type": "email"
            }
          ],
          "placeholder": "Enter your email here"
        }
      ]
    }
  ],
  "showPrevButton": false,
  "widthMode": "static",
  "width": "1000px"
};
```

### `src/app/components/creator.component.ts`

```ts
import { Component, OnInit } from "@angular/core";
import { SurveyCreatorModel } from "survey-creator-core";
import "survey-core/survey.i18n";
import "survey-creator-core/survey-creator-core.i18n";
import { Serializer, Action, ComputedUpdater } from "survey-core";
import { SurveyPDF } from "survey-pdf";
import { getLocaleStrings } from "survey-creator-core";
import { formJSON } from "./survey_json";
import "survey-core/survey-core.css";
import "survey-creator-core/survey-creator-core.css";

import SurveyTheme from "survey-core/themes";
import { registerCreatorTheme } from "survey-creator-core";

registerCreatorTheme(SurveyTheme); // Add predefined Survey Creator UI themes

// Add PDF Generator settings to the Property Grid
const pdfCategoryName = "pdf";
const enStrings = getLocaleStrings("en");
enStrings.pe.tabs[pdfCategoryName] = "Export to PDF";

Serializer.addProperty("survey", {
    name: "pdfOrientation:buttongroup",
    displayName: "Page orientation",
    category: pdfCategoryName,
    categoryIndex: 800,
    default: "auto",
    choices: [ "auto", "p|Portrait", "l|Landscape" ],
    isSerializable: false
});

Serializer.addProperty("survey", {
    name: "pdfFormat",
    displayName: "Page format",
    category: pdfCategoryName,
    default: "default",
    choices: [
        "default|Default", "custom|Custom", "a0|A0", "a1|A1", "a2|A2", "a3|A3", "a4|A4", "a5|A5", "a6|A6", "a7|A7",
        "a8|A8", "a9|A9", "a10|A10", "b0|B0", "b1|B1", "b2|B2", "b3|B3", "b4|B4", "b5|B5", "b6|B6", "b7|B7",
        "b8|B8", "b9|B9", "b10|B10", "c0|C0", "c1|C1", "c2|C2", "c3|C3", "c4|C4", "c5|C5", "c6|C6", "c7|C7",
        "c8|C8", "c9|C9", "c10|C10", "dl|DL", "letter|Letter", "government-letter|Government Letter", "legal|Legal",
        "junior-legal|Junior Legal", "ledger|Ledger", "tabloid|Tabloid", "credit-card|Credit Card"
    ],
    isSerializable: false
});

Serializer.addProperty("survey", {
    name: "pdfPageWidth:number",
    displayName: "Page width",
    category: pdfCategoryName,
    default: 210.0,
    dependsOn: "pdfFormat",
    visibleIf: (obj) => obj.pdfFormat === "custom",
    isSerializable: false
});

Serializer.addProperty("survey", {
    name: "pdfPageHeight:number",
    displayName: "Page height",
    category: pdfCategoryName,
    default: 297.0,
    dependsOn: "pdfFormat",
    visibleIf: (obj) => obj.pdfFormat === "custom",
    isSerializable: false
});

Serializer.addProperty("survey", {
    name: "pdfFontName:buttongroup",
    displayName: "Font name",
    category: pdfCategoryName,
    default: "Helvetica",
    choices: [ "Helvetica", "Courier", "Times" ],
    isSerializable: false
});

Serializer.addProperty("survey", {
    name: "pdfFontSize:number",
    displayName: "Font size",
    category: pdfCategoryName,
    default: 14,
    isSerializable: false
});

Serializer.addProperty("survey", {
    name: "pdfMarginTop:number",
    displayName: "Top margin",
    category: pdfCategoryName,
    default: 10.0,
    isSerializable: false
});

Serializer.addProperty("survey", {
    name: "pdfMarginBottom:number",
    displayName: "Bottom margin",
    category: pdfCategoryName,
    default: 10.0,
    isSerializable: false
});

Serializer.addProperty("survey", {
    name: "pdfMarginLeft:number",
    displayName: "Left margin",
    category: pdfCategoryName,
    default: 10.0,
    isSerializable: false
});

Serializer.addProperty("survey", {
    name: "pdfMarginRight:number",
    displayName: "Right margin",
    category: pdfCategoryName,
    default: 10.0,
    isSerializable: false
});

Serializer.addProperty("survey", {
    name: "pdfReadonly:buttongroup",
    displayName: "Generate a read-only PDF document",
    category: pdfCategoryName,
    default: false,
    isSerializable: false
});

Serializer.addProperty("survey", {
    name: "pdfHtmlRenderAs:buttongroup",
    displayName: "Render mode for HTML questions",
    category: pdfCategoryName,
    default: "auto",
    choices: [ "auto", "standard|Standard", "image|Image" ],
    isSerializable: false
});

Serializer.addProperty("survey", {
    name: "pdfMatrixRenderAs:buttongroup",
    displayName: "Render mode for matrices",
    category: pdfCategoryName,
    default: "auto",
    choices: [ "auto", "list|List" ],
    isSerializable: false
});

Serializer.addProperty("survey", {
    name: "pdfReadonlyRenderAs:buttongroup",
    displayName: "Render mode for read-only questions",
    category: pdfCategoryName,
    default: "auto",
    choices: ["auto", "acroform|AcroForms", "text|Plain text" ],
    isSerializable: false
});

Serializer.addProperty("survey", {
    name: "pdfCompress:boolean",
    displayName: "Compress the PDF document",
    category: pdfCategoryName,
    default: false,
    isSerializable: false
});

Serializer.addProperty("survey", {
    name: "pdfFileName:string",
    displayName: "File name",
    category: pdfCategoryName,
    default: "survey.pdf",
    isSerializable: false
});

Serializer.addProperty("question", {
    name: "pdfIsPageBreak:boolean",
    displayName: "Insert a page break before this element",
    category: pdfCategoryName,
    categoryIndex: 800,
    default: false,
    isSerializable: false
});

function savePdf (survey) {
    const pdfDocOptions = {
        fontSize: survey.pdfFontSize,
        fontName: survey.pdfFontName,
        margins: {
            left: survey.pdfMarginLeft,
            right: survey.pdfMarginRight,
            top: survey.pdfMarginTop,
            bot: survey.pdfMarginBottom
        },
        htmlRenderAs: survey.pdfHtmlRenderAs,
        matrixRenderAs: survey.pdfMatrixRenderAs,
        readonlyRenderAs: survey.pdfReadonlyRenderAs,
        compress: survey.pdfCompress
    };
    if (survey.pdfOrientation !== "auto") {
        pdfDocOptions.orientation = survey.pdfOrientation;
    }
    if (survey.pdfFormat === "custom") {
        pdfDocOptions.format = [survey.pdfFormatWidth, survey.pdfFormatHeight]
    }
    else if (survey.pdfFormat !== "default") {
        pdfDocOptions.format = survey.pdfFormat;
    }
    const surveyPDF = new SurveyPDF(survey.toJSON(), pdfDocOptions);
    surveyPDF.readOnly = survey.pdfReadonly;
    surveyPDF.onRenderQuestion.add((_, options) => {
        options.bricks[0].isPageBreak = options.question.pdfIsPageBreak;
    });
    surveyPDF.save(survey.pdfFileName);
};

@Component({
    // tslint:disable-next-line:component-selector
    selector: "component-survey-creator",
    templateUrl: "./creator.component.html",
    styleUrls: ["./creator.component.css"]
})
export class SurveyCreatorComponent implements OnInit {
    model: SurveyCreatorModel;
    ngOnInit() {
        const creator = new SurveyCreatorModel();
        // Configure a custom toolbar button
        const savePdfAction = new Action({
            id: "svd-save-pdf",
            title: "Download PDF",
            visible: new ComputedUpdater(() => creator.activeTab === "designer"),
            enabled: true,
            action: () => {
                savePdf(creator.survey);
            }
        });
        
        // Add the button to the top toolbar
        creator.toolbar.actions.push(savePdfAction);
        
        // Add the button to the bottom toolbar (visible only on mobile devices)
        creator.footerToolbar.actions.push(savePdfAction);
        
        creator.JSON = formJSON;
        this.model = creator;
    }
}
```

### `src/app/app.component.html`

```html
<component-survey-creator></component-survey-creator>
```

### `src/app/app.component.ts`

```ts
import { Component } from "@angular/core";

@Component({
    selector: "app-root",
    templateUrl: "./app.component.html"
})
export class AppComponent {
    title = "CodeSandbox";
}
```

### `src/app/app.module.ts`

```ts
import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";
import { AppComponent } from "./app.component";
import { SurveyCreatorModule } from "survey-creator-angular";
import { SurveyCreatorComponent } from "./components/creator.component";

@NgModule({
    declarations: [AppComponent, SurveyCreatorComponent],
    imports: [BrowserModule, SurveyCreatorModule],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }
```

### `src/environments/environment.prod.ts`

```ts
export const environment = {
    production: true
};
```

### `src/environments/environment.ts`

```ts
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `.angular-cli.json`.

export const environment = {
    production: false
};
```

### `src/index.html`

```html
<app-root></app-root>
```

### `src/main.ts`

```ts
import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";

import { AppModule } from "./app/app.module";
import { environment } from "./environments/environment";

if (environment.production) {
    enableProdMode();
}

platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch(err => console.log(err));
```

### `src/polyfills.ts`

```ts
/**
 * This file includes polyfills needed by Angular and is loaded before the app.
 * You can add your own extra polyfills to this file.
 *
 * This file is divided into 2 sections:
 *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
 *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
 *      file.
 *
 * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
 * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
 * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
 *
 * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
 */

/***************************************************************************************************
 * BROWSER POLYFILLS
 */

/** IE9, IE10 and IE11 requires all of the following polyfills. **/
// import 'core-js/es6/symbol';
// import 'core-js/es6/object';
// import 'core-js/es6/function';
// import 'core-js/es6/parse-int';
// import 'core-js/es6/parse-float';
// import 'core-js/es6/number';
// import 'core-js/es6/math';
// import 'core-js/es6/string';
// import 'core-js/es6/date';
// import 'core-js/es6/array';
// import 'core-js/es6/regexp';
// import 'core-js/es6/map';
// import 'core-js/es6/weak-map';
// import 'core-js/es6/set';

/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js';  // Run `npm install classlist.js`.

/** IE10 and IE11 requires the following for the Reflect API. */
// import 'core-js/es6/reflect';

/** Evergreen browsers require these. **/
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
import "core-js/proposals/reflect-metadata";

/**
 * Required to support Web Animations `@angular/platform-browser/animations`.
 * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
 **/
// import 'web-animations-js';  // Run `npm install web-animations-js`.

/***************************************************************************************************
 * Zone JS is required by default for Angular itself.
 */
import "zone.js/dist/zone"; // Included with Angular CLI.

/***************************************************************************************************
 * APPLICATION IMPORTS
 */
```

### `src/styles.css`

```css
/* You can add global styles to this file and import other style files */
```

### `src/typings.d.ts`

```ts
/* SystemJS module definition */
declare var module: NodeModule;
interface NodeModule {
    id: string;
}
```

### `.angular-cli.json`

```json
{
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [ "assets", "favicon.ico" ],
      "index": "index.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "prefix": "app",
      "styles": [ "styles.css"  ],
      "scripts": [  ],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
  ]
}
```

### `_tsconfig.json`

```json
{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "allowSyntheticDefaultImports": true,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "resolveJsonModule": true,
    "target": "es2015",
    "module": "es2020",
    "lib": [
      "es2018",
      "dom"
    ]
  }
}
```

### `package.json`

```json
{
  "name": "surveyjs-angular",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build --prod",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "14.1.1",
    "@angular/cdk": "14.1.1",
    "@angular/common": "14.1.1",
    "@angular/compiler": "14.1.1",
    "@angular/core": "14.1.1",
    "@angular/forms": "14.1.1",
    "@angular/platform-browser": "14.1.1",
    "@angular/platform-browser-dynamic": "14.1.1",
    "@angular/router": "14.1.1",
    "core-js": "3.6.4",
    "rxjs": "6.5.4",
    "survey-pdf": "latest",
    "survey-angular-ui": "latest",
    "survey-creator-core": "latest",
    "survey-core": "latest",
    "survey-creator-angular": "latest",
    "tslib": "1.13.0",
    "zone.js": "0.11.7"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~13.0.0",
    "@angular/cli": "~13.0.0",
    "@types/jasmine": "3.6.3",
    "@types/jasminewd2": "2.0.8",
    "@types/node": "14.14.28",
    "codelyzer": "6.0.1",
    "jasmine-core": "3.6.0",
    "jasmine-spec-reporter": "6.0.0",
    "karma": "6.1.1",
    "karma-chrome-launcher": "3.1.0",
    "karma-coverage-istanbul-reporter": "3.0.3",
    "karma-jasmine": "4.0.1",
    "karma-jasmine-html-reporter": "1.5.4",
    "protractor": "7.0.0",
    "ts-node": "9.1.1",
    "tslint": "~6.1.3",
    "typescript": "4.1.5"
  },
  "keywords": [ "angular", "surveyjs" ],
  "description": "SurveyJS-Angular example project"
}
```

## Other Frameworks

- [React](https://surveyjs.io/survey-creator/examples/integrate-pdf-generator/reactjs.md)
- [Vue 3](https://surveyjs.io/survey-creator/examples/integrate-pdf-generator/vue3js.md)
- [jQuery](https://surveyjs.io/survey-creator/examples/integrate-pdf-generator/jquery.md)
- [Vanilla JS](https://surveyjs.io/survey-creator/examples/integrate-pdf-generator/vanillajs.md)
