# SurveyJS Form Library
## Introduction
SurveyJS Form Library is a free, open-source MIT-licensed JavaScript library that renders dynamic JSON-based forms in web applications. It provides a client-side component that allows developers to create multi-page forms, surveys, quizzes, calculator forms, and survey pop-ups without server-side dependencies for form rendering. The library is framework-agnostic with native support for React, Angular, Vue, and Knockout, plus jQuery support through a Knockout wrapper. Forms are defined using JSON schemas, enabling complete separation between form structure and application logic.
The library offers comprehensive form-building capabilities including 20+ accessible input types, input validation, conditional logic, dynamic questions, data aggregation, expression language support, custom widgets, file uploads, e-signatures, and auto-localization to 50+ languages. All form submission data and files are stored on your own servers with no limits on forms, submissions, or uploads. The library features built-in themes, CSS customization options, TypeScript support, and integrates seamlessly with third-party libraries and payment systems. Weekly updates ensure the library stays current with modern web development practices.
## Core API
### Initialize Survey Model
Create a survey instance from a JSON configuration.
```javascript
import { Model } from 'survey-core';
import 'survey-core/survey-core.css';
const surveyJson = {
title: "Customer Feedback Survey",
description: "Help us improve our service",
showProgressBar: "top",
progressBarType: "pages",
pages: [{
name: "page1",
elements: [{
type: "text",
name: "firstName",
title: "First Name:",
isRequired: true
}, {
type: "text",
name: "email",
title: "Email Address:",
inputType: "email",
isRequired: true,
validators: [{
type: "email",
text: "Please enter a valid email address"
}]
}]
}, {
name: "page2",
elements: [{
type: "rating",
name: "satisfaction",
title: "How satisfied are you with our service?",
rateMin: 1,
rateMax: 5,
minRateDescription: "Not satisfied",
maxRateDescription: "Very satisfied"
}]
}],
showQuestionNumbers: "on",
completedHtml: "
Thank you for your feedback!
"
};
const survey = new Model(surveyJson);
// Handle survey completion
survey.onComplete.add((sender, options) => {
const results = sender.data;
console.log("Survey results:", results);
// Send to server
fetch("https://api.example.com/surveys", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(results)
})
.then(response => response.json())
.then(data => {
options.showSaveSuccess("Data saved successfully!");
})
.catch(error => {
options.showSaveError("Error saving data. Please try again.");
});
});
// Render in container (vanilla JS)
survey.render(document.getElementById("surveyContainer"));
```
### Access and Modify Survey Data
Get and set question values programmatically.
```javascript
import { Model } from 'survey-core';
const survey = new Model(surveyJson);
// Set individual question value
survey.setValue("firstName", "John");
survey.setValue("email", "john@example.com");
// Get individual question value
const firstName = survey.getValue("firstName");
console.log("First name:", firstName);
// Set multiple values at once
survey.data = {
firstName: "Jane",
email: "jane@example.com",
age: 28
};
// Get all survey data
const allData = survey.data;
console.log("All survey data:", allData);
// Pre-populate form from existing data (e.g., user profile)
survey.data = {
firstName: userData.firstName,
lastName: userData.lastName,
email: userData.email
};
// Clear all data
survey.clear(true, true); // clearData=true, gotoFirstPage=true
// Work with variables (for expressions, not included in results)
survey.setVariable("currentYear", new Date().getFullYear());
survey.setVariable("discount", 0.15);
// Check if survey is completed
if (survey.state === "completed") {
console.log("Survey already completed");
}
```
### Handle Survey Events
Subscribe to lifecycle and data change events.
```javascript
import { Model } from 'survey-core';
const survey = new Model(surveyJson);
// Value changed event
survey.onValueChanged.add((sender, options) => {
const { name, value, oldValue, question } = options;
console.log(`Question "${name}" changed from "${oldValue}" to "${value}"`);
// Conditional logic based on value change
if (name === "country" && value === "USA") {
sender.setValue("currency", "USD");
}
// Auto-save on value change
localStorage.setItem("surveyData", JSON.stringify(sender.data));
});
// Before value changes (can modify or cancel)
survey.onValueChanging.add((sender, options) => {
if (options.name === "email") {
// Normalize email to lowercase
options.value = options.value.toLowerCase().trim();
}
});
// Page navigation events
survey.onCurrentPageChanging.add((sender, options) => {
const { oldCurrentPage, newCurrentPage } = options;
// Prevent navigation if condition not met
if (oldCurrentPage.name === "terms" && !sender.getValue("acceptTerms")) {
alert("Please accept terms to continue");
options.allowChanging = false;
}
});
survey.onCurrentPageChanged.add((sender, options) => {
console.log("Moved to page:", options.newCurrentPage.title);
// Track page views
analytics.trackPageView(options.newCurrentPage.name);
});
// Before completing survey
survey.onCompleting.add((sender, options) => {
const requiredFields = ["email", "phone"];
const missingFields = requiredFields.filter(field => !sender.getValue(field));
if (missingFields.length > 0) {
alert("Please fill all required fields");
options.allowComplete = false;
}
});
// Survey started
survey.onStarted.add((sender, options) => {
console.log("Survey started at:", new Date());
sender.setVariable("startTime", Date.now());
});
// Question visibility changed
survey.onQuestionVisibleChanged.add((sender, options) => {
const { question, visible } = options;
console.log(`Question "${question.name}" is now ${visible ? "visible" : "hidden"}`);
});
// After rendering question
survey.onAfterRenderQuestion.add((sender, options) => {
const { question, htmlElement } = options;
// Add custom tooltip
if (question.name === "specialField") {
const tooltip = document.createElement("span");
tooltip.innerHTML = " ℹ️";
tooltip.title = "Additional help text";
htmlElement.querySelector(".sv-question__title").appendChild(tooltip);
}
});
```
## Question Types
### Text Input Questions
Create single-line and multi-line text inputs with validation.
```javascript
const surveyJson = {
elements: [
// Basic text input
{
type: "text",
name: "fullName",
title: "Full Name:",
isRequired: true,
placeholder: "Enter your full name"
},
// Email input with validation
{
type: "text",
name: "email",
title: "Email Address:",
inputType: "email",
isRequired: true,
validators: [{
type: "email",
text: "Invalid email format"
}]
},
// Number input with range
{
type: "text",
name: "age",
title: "Age:",
inputType: "number",
validators: [{
type: "numeric",
minValue: 18,
maxValue: 120,
text: "Age must be between 18 and 120"
}]
},
// Password input
{
type: "text",
name: "password",
title: "Password:",
inputType: "password",
validators: [{
type: "regex",
regex: "^(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*]).{8,}$",
text: "Password must contain uppercase, number, and special character"
}]
},
// Multi-line text area
{
type: "comment",
name: "feedback",
title: "Your Feedback:",
rows: 5,
maxLength: 1000,
placeholder: "Tell us what you think..."
},
// Multiple text inputs in one question
{
type: "multipletext",
name: "personalInfo",
title: "Personal Information:",
items: [
{ name: "firstName", title: "First Name", isRequired: true },
{ name: "lastName", title: "Last Name", isRequired: true },
{ name: "phone", title: "Phone", inputType: "tel" }
]
}
]
};
const survey = new Model(surveyJson);
```
### Choice-Based Questions
Create dropdowns, radio buttons, checkboxes, and selection controls.
```javascript
const surveyJson = {
elements: [
// Dropdown with simple choices
{
type: "dropdown",
name: "country",
title: "Select Your Country:",
placeholder: "Choose a country...",
choices: ["USA", "UK", "Canada", "Australia", "Germany"],
isRequired: true,
allowClear: true
},
// Radio group with custom values
{
type: "radiogroup",
name: "plan",
title: "Choose Your Plan:",
choices: [
{ value: "basic", text: "Basic - $9/month" },
{ value: "pro", text: "Professional - $29/month" },
{ value: "enterprise", text: "Enterprise - $99/month" }
],
isRequired: true
},
// Checkboxes with "Select All" option
{
type: "checkbox",
name: "features",
title: "Select Features You Need:",
choices: [
"Email Support",
"Priority Support",
"API Access",
"Custom Branding",
"Advanced Analytics"
],
showSelectAllItem: true,
maxSelectedChoices: 3,
minSelectedChoices: 1
},
// Boolean toggle
{
type: "boolean",
name: "subscribe",
title: "Subscribe to Newsletter",
defaultValue: false,
labelTrue: "Yes, sign me up",
labelFalse: "No, thanks"
},
// Tag box (multi-select searchable)
{
type: "tagbox",
name: "skills",
title: "Your Skills:",
placeholder: "Type to search...",
choices: [
"JavaScript", "Python", "Java", "C++", "Ruby",
"Go", "Rust", "TypeScript", "PHP", "Swift"
],
searchEnabled: true,
closeOnSelect: false
},
// Image picker
{
type: "imagepicker",
name: "avatar",
title: "Choose Your Avatar:",
choices: [
{ value: "lion", imageLink: "https://example.com/lion.png" },
{ value: "tiger", imageLink: "https://example.com/tiger.png" },
{ value: "bear", imageLink: "https://example.com/bear.png" }
],
imageFit: "cover",
imageHeight: 150,
imageWidth: 150
},
// Ranking question
{
type: "ranking",
name: "priorities",
title: "Rank These Priorities (drag to reorder):",
choices: [
"Performance",
"Security",
"User Experience",
"Cost",
"Scalability"
]
},
// Button group
{
type: "buttongroup",
name: "size",
title: "Select Size:",
choices: ["Small", "Medium", "Large", "X-Large"]
}
]
};
const survey = new Model(surveyJson);
```
### Dynamic Choice Loading from API
Load dropdown/radiogroup choices from REST API.
```javascript
const surveyJson = {
elements: [
{
type: "dropdown",
name: "country",
title: "Select Country:",
choicesByUrl: {
url: "https://surveyjs.io/api/CountriesExample",
valueName: "name",
titleName: "name"
},
placeholder: "Loading countries..."
},
// Dependent dropdown - load cities based on selected country
{
type: "dropdown",
name: "city",
title: "Select City:",
visibleIf: "{country} notempty",
choicesByUrl: {
url: "https://api.example.com/cities?country={country}",
valueName: "id",
titleName: "name"
}
}
]
};
const survey = new Model(surveyJson);
// Custom choices loading with authentication
survey.onLoadChoicesFromServer.add((sender, options) => {
const { question, serverResult } = options;
// Transform API response
if (question.name === "products") {
const transformedChoices = serverResult.data.map(item => ({
value: item.id,
text: `${item.name} - $${item.price}`
}));
options.choices = transformedChoices;
}
});
// Handle loading errors
survey.onChoicesLoaded.add((sender, options) => {
const { question, choices, error } = options;
if (error) {
console.error(`Failed to load choices for ${question.name}:`, error);
question.choices = [{ value: "error", text: "Failed to load options" }];
}
});
```
### Rating and Slider Questions
Create rating scales and numeric sliders.
```javascript
const surveyJson = {
elements: [
// Star rating
{
type: "rating",
name: "serviceRating",
title: "Rate Our Service:",
rateMin: 1,
rateMax: 5,
rateStep: 1,
minRateDescription: "Poor",
maxRateDescription: "Excellent",
displayMode: "buttons"
},
// Numeric scale
{
type: "rating",
name: "satisfaction",
title: "How likely are you to recommend us? (0-10)",
rateMin: 0,
rateMax: 10,
displayMode: "buttons"
},
// Slider with labels
{
type: "slider",
name: "budget",
title: "Your Budget Range:",
min: 0,
max: 10000,
step: 500,
defaultValue: 2500,
rangeMin: 0,
rangeMax: 10000
},
// Range slider
{
type: "nouislider",
name: "priceRange",
title: "Select Price Range:",
rangeMin: 0,
rangeMax: 1000,
step: 10,
defaultValue: [100, 500],
tooltips: true,
pipsMode: "positions",
pipsValues: [0, 25, 50, 75, 100],
pipsText: ["$0", "$250", "$500", "$750", "$1000"]
}
]
};
const survey = new Model(surveyJson);
// Calculate NPS score from rating
survey.onComplete.add((sender) => {
const score = sender.getValue("satisfaction");
let npsCategory;
if (score >= 9) npsCategory = "Promoter";
else if (score >= 7) npsCategory = "Passive";
else npsCategory = "Detractor";
console.log(`NPS Category: ${npsCategory}`);
});
```
### Matrix Questions
Create grid-based questions with rows and columns.
```javascript
const surveyJson = {
elements: [
// Single-select matrix
{
type: "matrix",
name: "satisfaction",
title: "Rate each aspect:",
columns: [
{ value: 1, text: "Poor" },
{ value: 2, text: "Fair" },
{ value: 3, text: "Good" },
{ value: 4, text: "Excellent" }
],
rows: [
"Product Quality",
"Customer Service",
"Delivery Speed",
"Value for Money"
],
isAllRowRequired: true
},
// Multi-select matrix with different cell types
{
type: "matrixdropdown",
name: "employees",
title: "Employee Information:",
columns: [
{
name: "name",
title: "Name",
cellType: "text",
isRequired: true
},
{
name: "position",
title: "Position",
cellType: "dropdown",
choices: ["Developer", "Designer", "Manager", "Analyst"]
},
{
name: "salary",
title: "Salary",
cellType: "text",
inputType: "number"
}
],
rows: ["Employee 1", "Employee 2", "Employee 3"]
},
// Dynamic matrix (add/remove rows)
{
type: "matrixdynamic",
name: "tasks",
title: "Project Tasks:",
columns: [
{
name: "task",
title: "Task Description",
cellType: "text"
},
{
name: "priority",
title: "Priority",
cellType: "dropdown",
choices: ["Low", "Medium", "High"]
},
{
name: "hours",
title: "Estimated Hours",
cellType: "text",
inputType: "number"
}
],
rowCount: 2,
minRowCount: 1,
maxRowCount: 10,
addRowText: "Add Task",
removeRowText: "Remove",
confirmDelete: true,
confirmDeleteText: "Are you sure you want to delete this task?"
}
]
};
const survey = new Model(surveyJson);
// Handle matrix cell value changes
survey.onMatrixCellValueChanged.add((sender, options) => {
const { question, row, columnName, value } = options;
if (columnName === "priority" && value === "High") {
console.log(`High priority task added: ${row.getValue("task")}`);
}
});
// Calculate total hours from dynamic matrix
survey.onValueChanged.add((sender, options) => {
if (options.name === "tasks") {
const tasks = sender.getValue("tasks") || [];
const totalHours = tasks.reduce((sum, task) => {
return sum + (parseFloat(task.hours) || 0);
}, 0);
sender.setVariable("totalHours", totalHours);
console.log(`Total estimated hours: ${totalHours}`);
}
});
```
### File Upload Questions
Handle file uploads with custom server integration.
```javascript
const surveyJson = {
elements: [
{
type: "file",
name: "resume",
title: "Upload Your Resume:",
allowMultiple: false,
maxSize: 5242880, // 5MB in bytes
acceptedTypes: ".pdf,.doc,.docx",
isRequired: true,
storeDataAsText: false,
waitForUpload: true
},
{
type: "file",
name: "photos",
title: "Upload Photos (max 3):",
allowMultiple: true,
maxSize: 10485760, // 10MB
acceptedTypes: "image/*",
maxFiles: 3,
imageHeight: 150,
imageWidth: 150
}
]
};
const survey = new Model(surveyJson);
// Handle file upload
survey.onUploadFiles.add((sender, options) => {
const formData = new FormData();
options.files.forEach((file, index) => {
formData.append(`file${index}`, file);
});
fetch("https://api.example.com/upload", {
method: "POST",
headers: {
"Authorization": `Bearer ${authToken}`
},
body: formData
})
.then(response => {
if (!response.ok) throw new Error("Upload failed");
return response.json();
})
.then(data => {
// Call success callback with file URLs/IDs
const uploadedFiles = data.files.map(f => ({
file: f,
content: f.url
}));
options.callback("success", uploadedFiles);
})
.catch(error => {
console.error("Upload error:", error);
options.callback("error", error.message);
});
});
// Handle file download
survey.onDownloadFile.add((sender, options) => {
const { fileValue, callback } = options;
fetch(`https://api.example.com/download/${fileValue.content}`, {
headers: { "Authorization": `Bearer ${authToken}` }
})
.then(response => response.blob())
.then(blob => {
callback("success", blob);
})
.catch(() => {
callback("error");
});
});
// Handle file removal
survey.onClearFiles.add((sender, options) => {
const { fileName, callback } = options;
fetch(`https://api.example.com/delete/${fileName}`, {
method: "DELETE",
headers: { "Authorization": `Bearer ${authToken}` }
})
.then(() => {
callback("success");
})
.catch(() => {
callback("error");
});
});
```
### Signature Pad Question
Capture digital signatures.
```javascript
const surveyJson = {
elements: [
{
type: "signaturepad",
name: "customerSignature",
title: "Please sign below:",
isRequired: true,
height: 200,
width: 500,
penColor: "#000000",
backgroundColor: "#ffffff",
signatureWidth: 2,
allowClear: true,
dataFormat: "png" // or "jpeg", "svg"
}
]
};
const survey = new Model(surveyJson);
survey.onComplete.add((sender) => {
const signature = sender.getValue("customerSignature");
// signature is base64 encoded image data
console.log("Signature data:", signature);
// Send to server
fetch("https://api.example.com/save-signature", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ signature })
});
});
```
## Data Validation
### Built-in Validators
Apply validation rules to questions.
```javascript
const surveyJson = {
elements: [
// Required field
{
type: "text",
name: "username",
title: "Username:",
isRequired: true,
requiredErrorText: "Username cannot be empty"
},
// Text length validation
{
type: "text",
name: "password",
title: "Password:",
inputType: "password",
validators: [{
type: "text",
minLength: 8,
maxLength: 20,
text: "Password must be 8-20 characters"
}]
},
// Numeric range validation
{
type: "text",
name: "age",
title: "Age:",
inputType: "number",
validators: [{
type: "numeric",
minValue: 18,
maxValue: 65,
text: "Age must be between 18 and 65"
}]
},
// Email validation
{
type: "text",
name: "email",
title: "Email:",
inputType: "email",
validators: [{
type: "email",
text: "Please enter a valid email address"
}]
},
// Regular expression validation
{
type: "text",
name: "phone",
title: "Phone Number:",
validators: [{
type: "regex",
regex: "^\\+?[1-9]\\d{9,14}$",
text: "Please enter valid phone format: +1234567890"
}]
},
// Expression validator (cross-field validation)
{
type: "text",
name: "confirmEmail",
title: "Confirm Email:",
validators: [{
type: "expression",
expression: "{email} = {confirmEmail}",
text: "Emails do not match"
}]
},
// Answer count validator (for checkboxes)
{
type: "checkbox",
name: "interests",
title: "Select 2-4 interests:",
choices: ["Sports", "Music", "Reading", "Travel", "Gaming", "Cooking"],
validators: [{
type: "answercount",
minCount: 2,
maxCount: 4,
text: "Please select between 2 and 4 options"
}]
}
],
checkErrorsMode: "onValueChanged", // Validate immediately
textUpdateMode: "onTyping" // Update as user types
};
const survey = new Model(surveyJson);
```
### Custom Validation
Implement custom validation logic.
```javascript
import { Model } from 'survey-core';
const surveyJson = {
elements: [
{
type: "text",
name: "email",
title: "Company Email:"
},
{
type: "text",
name: "promoCode",
title: "Promo Code:"
}
]
};
const survey = new Model(surveyJson);
// Client-side custom validation
survey.onValidateQuestion.add((sender, options) => {
const { name, value } = options;
// Validate company email domain
if (name === "email") {
if (!value.endsWith("@company.com")) {
options.error = "Please use your company email address";
}
}
// Validate promo code format
if (name === "promoCode" && value) {
if (!/^[A-Z]{4}\d{4}$/.test(value)) {
options.error = "Promo code must be 4 letters followed by 4 digits (e.g., ABCD1234)";
}
}
});
// Async server-side validation
survey.onServerValidateQuestions.add((sender, options) => {
const { data, errors, complete } = options;
fetch("https://api.example.com/validate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
// Add errors for invalid fields
if (result.errors) {
Object.keys(result.errors).forEach(field => {
errors[field] = result.errors[field];
});
}
// MUST call complete() when done
complete();
})
.catch(error => {
console.error("Validation error:", error);
complete();
});
});
// Custom error messages
survey.onErrorCustomText.add((sender, options) => {
const { name, error, text } = options;
if (name === "age" && error.getErrorType() === "required") {
options.text = "We need to know your age to continue";
}
});
// Validate entire panel
survey.onValidatePanel.add((sender, options) => {
const { name, data, errors } = options;
if (name === "addressPanel") {
if (!data.street || !data.city || !data.zipCode) {
errors["addressPanel"] = "Please complete all address fields";
}
}
});
```
## Conditional Logic
### Visibility Conditions
Show/hide questions based on answers.
```javascript
const surveyJson = {
elements: [
{
type: "radiogroup",
name: "hasAccount",
title: "Do you have an account?",
choices: ["Yes", "No"],
isRequired: true
},
// Show login fields if user has account
{
type: "text",
name: "username",
title: "Username:",
visibleIf: "{hasAccount} = 'Yes'",
isRequired: true
},
{
type: "text",
name: "password",
title: "Password:",
inputType: "password",
visibleIf: "{hasAccount} = 'Yes'",
isRequired: true
},
// Show registration fields if no account
{
type: "text",
name: "email",
title: "Email:",
visibleIf: "{hasAccount} = 'No'",
isRequired: true
},
{
type: "text",
name: "newPassword",
title: "Create Password:",
inputType: "password",
visibleIf: "{hasAccount} = 'No'",
isRequired: true
},
// Conditional page visibility
{
type: "rating",
name: "satisfaction",
title: "How satisfied are you?",
rateMin: 1,
rateMax: 5
},
{
type: "comment",
name: "positiveFeeback",
title: "What did you like?",
visibleIf: "{satisfaction} >= 4"
},
{
type: "comment",
name: "negativeFeeback",
title: "What can we improve?",
visibleIf: "{satisfaction} <= 2"
},
// Complex conditions with AND/OR
{
type: "text",
name: "discount",
title: "Enter discount code:",
visibleIf: "{satisfaction} >= 4 and {hasAccount} = 'Yes'"
}
]
};
const survey = new Model(surveyJson);
```
### Dynamic Text and Placeholders
Use question values in text with piping.
```javascript
const surveyJson = {
elements: [
{
type: "text",
name: "firstName",
title: "First Name:"
},
{
type: "text",
name: "product",
title: "Product Name:"
},
{
type: "html",
name: "greeting",
html: "
```
### Vanilla JavaScript Integration
Use SurveyJS without frameworks.
```html
SurveyJS Form
```
## Summary
SurveyJS Form Library provides a comprehensive solution for creating dynamic, interactive forms and surveys in JavaScript applications. The library's JSON-based configuration approach enables developers to define complex form structures declaratively, separating form logic from application code. With support for 20+ question types including text inputs, dropdowns, matrices, file uploads, and signature pads, the library handles virtually any data collection requirement from simple contact forms to complex multi-page surveys with conditional branching.
The library excels in enterprise scenarios where data sovereignty and customization are critical. All form data remains on your servers, eliminating third-party service dependencies and associated costs. Built-in validation engines, expression language support for calculations, and comprehensive event systems enable sophisticated form behaviors without external dependencies. The framework-agnostic architecture with first-class React, Angular, and Vue support ensures seamless integration into existing applications. Features like auto-save, conditional logic, dynamic text piping, REST API integration for choice loading, and 40+ built-in theme variations make SurveyJS suitable for everything from customer feedback forms to quiz applications to calculator forms requiring complex computations. Weekly updates and active community support ensure the library remains current with evolving web standards.