Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Theme
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Create API Key
Add Docs
SurveyJS
https://github.com/surveyjs/survey-library
Admin
A free, MIT-licensed client-side component for rendering dynamic JSON-based forms in any JavaScript
...
Tokens:
91,931
Snippets:
794
Trust Score:
7.9
Update:
1 week ago
Context
Skills
Chat
Benchmark
93.6
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# SurveyJS Form Library SurveyJS Form Library is a free, MIT-licensed, client-side JavaScript component for rendering dynamic, JSON-based forms in any web application. It collects respondent answers and sends all submission data to a database of your choice. The library supports multi-page forms of any length and complexity, pop-up surveys, scored quizzes, timed quizzes, calculator forms, and more. It natively integrates with React, Angular, Vue 3, and Vanilla JavaScript/HTML, with jQuery support through a wrapper over the Knockout version. The library communicates with the server using JSON objects for both form metadata (the survey JSON schema) and response results. Core functionality revolves around the `Model` class from `survey-core`, which drives all rendering-framework-agnostic survey logic. You define a survey's structure as a plain JSON schema (questions, pages, panels, choices, validation rules, conditional logic, themes, and triggers), pass it to the `Model` constructor, and mount the resulting model instance into a framework-specific renderer component (`Survey` for React, `<survey>` for Angular, `<SurveyComponent>` for Vue, or `survey.render()` for Vanilla JS). The library ships with 20+ built-in question types, a powerful expression engine for conditional visibility, a localization engine covering 50+ languages, multiple predefined CSS themes, client-side and server-side validators, incremental save/restore support, and a full event system for hooking into every lifecycle event. --- ## API Reference ### `new Model(surveyJson)` — Create a survey model from a JSON schema Instantiates a `SurveyModel` from a plain JSON object describing pages, questions, panels, conditional logic, and settings. This is the central object used by every framework renderer. ```js import { Model } from "survey-core"; const surveyJson = { title: "Customer Feedback", pages: [{ name: "page1", elements: [{ type: "radiogroup", name: "satisfaction", title: "How satisfied are you with our service?", isRequired: true, choices: [ { value: 5, text: "Very satisfied" }, { value: 4, text: "Satisfied" }, { value: 3, text: "Neutral" }, { value: 2, text: "Dissatisfied" }, { value: 1, text: "Very dissatisfied" } ] }] }, { name: "page2", visibleIf: "{satisfaction} <= 3", elements: [{ type: "comment", name: "improvement", title: "How can we improve?" }] }], completedHtml: "<h3>Thank you for your feedback!</h3>" }; const survey = new Model(surveyJson); // survey is now ready to be passed to a renderer ``` --- ### `survey.render(domElement)` — Render a survey in Vanilla JS / HTML Mounts the survey directly into a DOM element for non-framework (HTML/CSS/JavaScript) applications. ```html <div id="surveyContainer"></div> <link href="https://unpkg.com/survey-core/survey-core.min.css" rel="stylesheet"> <script src="https://unpkg.com/survey-core/survey.core.min.js"></script> <script src="https://unpkg.com/survey-js-ui/survey-js-ui.min.js"></script> <script> const surveyJson = { elements: [{ type: "text", name: "name", title: "Your name:", isRequired: true }, { type: "rating", name: "nps", title: "How likely are you to recommend us?", rateMin: 0, rateMax: 10 }] }; const survey = new Survey.Model(surveyJson); document.addEventListener("DOMContentLoaded", function () { survey.render(document.getElementById("surveyContainer")); }); </script> ``` --- ### React `<Survey model={survey} />` — Render a survey in React Renders a SurveyJS form in a React application. Must be used in a client component (not server-side rendered). For Next.js, use dynamic imports with `ssr: false`. ```js // components/SurveyForm.tsx 'use client' import { useCallback } from 'react'; import 'survey-core/survey-core.css'; import { Model } from 'survey-core'; import { Survey } from 'survey-react-ui'; const surveyJson = { elements: [{ name: "firstName", title: "First name:", type: "text", isRequired: true }, { name: "lastName", title: "Last name:", type: "text" }] }; export default function SurveyForm() { const survey = new Model(surveyJson); const handleComplete = useCallback((survey: Model) => { fetch("https://your-api.com/survey-results", { method: "POST", headers: { "Content-Type": "application/json;charset=UTF-8" }, body: JSON.stringify(survey.data) }) .then(res => { if (!res.ok) throw new Error("Save failed"); }) .catch(err => console.error(err)); }, []); survey.onComplete.add(handleComplete); return <Survey model={survey} />; } // pages/survey/page.tsx (Next.js) import dynamic from 'next/dynamic'; const SurveyForm = dynamic(() => import("@/components/SurveyForm"), { ssr: false }); export default function SurveyPage() { return <SurveyForm />; } ``` --- ### Angular `<survey [model]="surveyModel">` — Render a survey in Angular Renders a SurveyJS form inside an Angular component using the `<survey>` element from `survey-angular-ui`. ```ts // app.component.ts import { Component, OnInit } from '@angular/core'; import { Model } from 'survey-core'; const surveyJson = { elements: [{ type: "text", name: "email", title: "Your email address:", inputType: "email", isRequired: true, validators: [{ type: "email", text: "Please enter a valid email." }] }] }; @Component({ selector: 'app-root', template: ` <survey [model]="surveyModel"></survey> <popup-survey [model]="surveyModel" [isExpanded]="false"></popup-survey> ` }) export class AppComponent implements OnInit { surveyModel!: Model; ngOnInit() { const survey = new Model(surveyJson); survey.onComplete.add((s) => console.log("Results:", s.data)); this.surveyModel = survey; } } ``` --- ### Vue `<SurveyComponent :model="survey" />` — Render a survey in Vue 3 Renders a SurveyJS form inside a Vue 3 component using the `SurveyComponent` from `survey-vue3-ui`. ```html <script setup lang="ts"> import { Model } from 'survey-core'; import { SurveyComponent } from 'survey-vue3-ui'; import 'survey-core/survey-core.css'; const surveyJson = { elements: [{ type: "dropdown", name: "country", title: "Select your country:", choicesByUrl: { url: "https://surveyjs.io/api/CountriesExample", valueName: "name" } }, { type: "checkbox", name: "languages", title: "Which programming languages do you know?", choices: ["JavaScript", "TypeScript", "Python", "Java", "C#"] }] }; const survey = new Model(surveyJson); survey.onComplete.add((s) => console.log(s.data)); </script> <template> <SurveyComponent :model="survey" /> </template> ``` --- ### `survey.onComplete.add(handler)` — Handle form submission Registers a callback that fires when a respondent clicks the Complete button. Use this to send survey results to your backend. The handler receives the survey instance and an options object with `showSaveInProgress()`, `showSaveSuccess()`, and `showSaveError()` methods. ```js import { Model } from "survey-core"; const survey = new Model(surveyJson); survey.onComplete.add((survey, options) => { // Show a "Saving..." indicator in the complete page options.showSaveInProgress(); const payload = { surveyId: "my-survey-001", userId: getCurrentUserId(), // your own auth helper responses: survey.data }; fetch("https://your-api.com/responses", { method: "POST", headers: { "Content-Type": "application/json; charset=utf-8" }, body: JSON.stringify(payload) }) .then(response => { if (!response.ok) throw new Error(`HTTP ${response.status}`); // Show "Saved successfully" on the complete page options.showSaveSuccess(); }) .catch(error => { // Show error message; respondent can retry options.showSaveError("Could not save your responses. Please try again."); console.error(error); }); }); ``` --- ### `survey.data` / `survey.getValue()` / `survey.setValue()` — Access and modify survey results `survey.data` is a key-value JSON object keyed by question `name`. `getValue(name)` and `setValue(name, value)` let you read or write individual question answers programmatically. `getPlainData()` returns a structured array suitable for display tables. ```js import { Model } from "survey-core"; const surveyJson = { elements: [ { type: "text", name: "firstName", title: "First name" }, { type: "text", name: "lastName", title: "Last name" }, { type: "rating", name: "nps", title: "NPS score", rateMin: 0, rateMax: 10 } ] }; const survey = new Model(surveyJson); // Pre-populate fields survey.data = { firstName: "Jane", lastName: "Doe" }; // or individually: survey.setValue("nps", 9); // Read a value console.log(survey.getValue("firstName")); // "Jane" console.log(survey.data); // { firstName: "Jane", lastName: "Doe", nps: 9 } // Get structured result array (useful for building result summaries) const plain = survey.getPlainData(); plain.forEach(item => { console.log(`${item.title}: ${item.displayValue}`); }); // First name: Jane // Last name: Doe // NPS score: 9 // Listen for value changes before and after they happen survey.onValueChanging.add((s, { name, oldValue, value }) => { console.log(`${name}: ${oldValue} → ${value}`); // Optionally override: value = sanitize(value); }); survey.onValueChanged.add((s, { name, value }) => { console.log(`Saved: ${name} = ${value}`); }); ``` --- ### `survey.mergeData(obj)` — Merge data without overwriting existing answers `mergeData` combines new key-value pairs into the existing `survey.data` without erasing answers that are already present, unlike a direct assignment to `survey.data`. ```js import { Model } from "survey-core"; const surveyJson = { elements: [ { type: "text", name: "firstName" }, { type: "text", name: "lastName" }, { type: "text", name: "email" } ] }; const survey = new Model(surveyJson); // Set an initial value survey.data = { firstName: "Alice" }; // Merge without losing firstName survey.mergeData({ lastName: "Smith", email: "alice@example.com" }); console.log(survey.data); // { firstName: "Alice", lastName: "Smith", email: "alice@example.com" } ``` --- ### Built-in validators — Client-side validation in JSON schema Validators are declared directly in the JSON schema using the `validators` array. Supported types: `"numeric"`, `"text"`, `"email"`, `"expression"`, `"answercount"`, `"regex"`. The `notificationType` property (`"error"` | `"warning"` | `"info"`) controls how the message is displayed (default is `"error"`). ```js import { Model } from "survey-core"; const surveyJson = { checkErrorsMode: "onValueChanged", // validate on every keystroke elements: [{ type: "text", name: "age", title: "Your age:", inputType: "number", isRequired: true, requiredErrorText: "Age is required.", validators: [{ type: "numeric", minValue: 18, maxValue: 120, text: "Age must be between 18 and 120." }] }, { type: "text", name: "email", title: "Email address:", validators: [{ type: "email", text: "Enter a valid email address." }] }, { type: "text", name: "zip", title: "ZIP code:", validators: [{ type: "regex", regex: "^[0-9]{5}(-[0-9]{4})?$", text: "Enter a valid US ZIP code." }] }, { type: "comment", name: "bio", title: "Short bio:", validators: [{ type: "text", minLength: 20, text: "Bio is very short — consider adding more details.", notificationType: "warning" }] }] }; const survey = new Model(surveyJson); ``` --- ### `survey.onValidateQuestion.add()` — Custom client-side validation Fires for every question when validation runs. Set `options.error` to surface a custom error message. ```js import { Model } from "survey-core"; const surveyJson = { elements: [{ type: "text", name: "username", title: "Choose a username:" }, { type: "text", name: "password", title: "Password:", inputType: "password" }, { type: "text", name: "confirmPassword", title: "Confirm password:", inputType: "password" }] }; const survey = new Model(surveyJson); survey.onValidateQuestion.add((s, options) => { if (options.name === "username") { const reserved = ["admin", "root", "system"]; if (reserved.includes(options.value)) { options.error = `"${options.value}" is a reserved name. Choose another.`; } } if (options.name === "confirmPassword") { if (options.value !== s.getValue("password")) { options.error = "Passwords do not match."; } } }); ``` --- ### `survey.onServerValidateQuestions.add()` — Async server-side validation Triggered before the survey proceeds to the next page. Perform async operations (e.g., API calls) and call `options.complete()` when done. Set entries in `options.errors` to surface per-question error messages. ```js import { Model } from "survey-core"; const surveyJson = { elements: [{ type: "text", name: "username", title: "Choose a username:" }] }; const survey = new Model(surveyJson); survey.onServerValidateQuestions.add(async (s, options) => { const username = options.data["username"]; if (!username) { options.complete(); return; } try { const res = await fetch(`/api/check-username?name=${encodeURIComponent(username)}`); const { available } = await res.json(); if (!available) { options.errors["username"] = `"${username}" is already taken.`; } } catch (err) { options.errors["username"] = "Could not verify username. Try again."; } finally { options.complete(); // MUST always be called } }); ``` --- ### `registerFunction()` — Register custom expression functions Registers a named function that can be referenced anywhere an expression is valid (e.g., `visibleIf`, `enableIf`, `validators`, `calculatedValues`). Asynchronous functions that fetch remote data are supported by either using `this.returnResult()` or returning a `Promise`. ```js import { Model, registerFunction } from "survey-core"; // Synchronous: check if a value is a valid HEX color function isHexColor(params) { const value = params[0]; return /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(value); } registerFunction({ name: "isHexColor", func: isHexColor }); // Asynchronous: check username availability via API (Promise-based) async function isUsernameAvailable(params) { const username = params[0]; if (!username) return false; const res = await fetch(`/api/check-username?name=${encodeURIComponent(username)}`); const { available } = await res.json(); return available; } registerFunction({ name: "isUsernameAvailable", func: isUsernameAvailable, isAsync: true }); const surveyJson = { checkErrorsMode: "onValueChanged", elements: [{ type: "text", name: "brandColor", title: "Brand color (HEX):", validators: [{ type: "expression", expression: "isHexColor({brandColor})", text: "Enter a valid HEX color, e.g. #FF5733." }] }, { type: "text", name: "username", title: "Username:", validators: [{ type: "expression", expression: "isUsernameAvailable({username})", text: "This username is already taken." }] }] }; const survey = new Model(surveyJson); ``` --- ### `visibleIf` / `enableIf` / `requiredIf` — Conditional visibility on questions, panels, and pages Assign a Boolean expression string to `visibleIf`, `enableIf`, or `requiredIf` on any survey element. The expression engine re-evaluates on every value change and automatically shows/hides, enables/disables, or marks as required the associated element. ```js import { Model } from "survey-core"; const surveyJson = { elements: [{ type: "radiogroup", name: "employed", title: "Are you currently employed?", choices: ["Yes", "No"], isRequired: true }, { type: "text", name: "employer", title: "Employer name:", visibleIf: "{employed} = 'Yes'", requiredIf: "{employed} = 'Yes'" }, { type: "dropdown", name: "jobSeeking", title: "Are you actively job-seeking?", choices: ["Yes", "No", "Not sure"], visibleIf: "{employed} = 'No'" }, { type: "text", name: "birthdate", title: "Date of birth:", inputType: "date" }, { type: "text", name: "drivingLicense", title: "Driver's license number:", // visible only if respondent is 16 or older visibleIf: "age({birthdate}) >= 16", // read-only if over 70 enableIf: "age({birthdate}) <= 70" }] }; const survey = new Model(surveyJson); ``` --- ### Triggers (`complete`, `setvalue`, `copyvalue`, `skip`) — Conditional survey logic Triggers associate a Boolean expression with a side-effect action. Supported types: `complete` (ends the survey), `setvalue` (sets a question value), `copyvalue` (copies a value between questions), and `skip` (jumps to a target question/page). ```js import { Model } from "survey-core"; const surveyJson = { pages: [{ elements: [{ type: "radiogroup", name: "ageGroup", title: "What is your age group?", choices: ["Under 18", "18-64", "65+"] }] }, { elements: [{ type: "comment", name: "parentConsent", title: "Please describe parental consent obtained:" }] }, { elements: [{ type: "checkbox", name: "interests", title: "Select your interests:", choices: ["Technology", "Sports", "Arts", "Travel"] }] }], triggers: [ // Auto-complete survey for under-18 respondents who reach page 2 { "type": "complete", "expression": "{ageGroup} = 'Under 18'" }, // Copy the selected ageGroup to a hidden field { "type": "copyvalue", "expression": "{ageGroup} notempty", "fromName": "ageGroup", "setToName": "ageGroupCopy" }, // Auto-set a value based on a condition { "type": "setvalue", "expression": "{ageGroup} = '65+'", "setToName": "accessibilityFlag", "setValue": true }, // Skip to the interests page for users aged 18-64 { "type": "skip", "expression": "{ageGroup} = '18-64'", "gotoName": "interests" } ] }; const survey = new Model(surveyJson); ``` --- ### `survey.calculatedValues` and `survey.setVariable()` — Dynamic computations and variables `calculatedValues` in the JSON schema define expressions that are re-evaluated automatically whenever referenced question values change. `setVariable(name, value)` stores arbitrary JavaScript-computed values accessible in expressions and dynamic texts. ```js import { Model } from "survey-core"; const surveyJson = { elements: [ { type: "text", name: "unitPrice", title: "Unit price ($):", inputType: "number" }, { type: "text", name: "quantity", title: "Quantity:", inputType: "number" }, { type: "text", name: "discount", title: "Discount (0–1):", inputType: "number" }, { type: "expression", name: "orderTotal", title: "Order total:", expression: "{subtotal} * (1 - {discount})", displayStyle: "currency", currency: "USD" }, { type: "html", name: "footer", html: "© {currentYear} Acme Corp. All rights reserved." } ], calculatedValues: [{ name: "subtotal", expression: "{unitPrice} * {quantity}", includeIntoResult: true }] }; const survey = new Model(surveyJson); // Set a JavaScript-computed variable usable in dynamic texts survey.setVariable("currentYear", new Date().getFullYear()); console.log(survey.getVariable("currentYear")); // 2024 console.log(survey.getVariableNames()); // ["currentYear"] // When the respondent fills in values: survey.setValue("unitPrice", 29.99); survey.setValue("quantity", 3); survey.setValue("discount", 0.1); // calculatedValues.subtotal is automatically 89.97 // orderTotal question displays $80.97 ``` --- ### `survey.validateExpressions()` — Validate survey expressions at design time Checks all expressions in the survey model for syntax errors, unknown variables, unknown functions, and semantic issues. Returns an array of `IExpressionValidationResult` objects with the offending element, property name, and detailed errors. ```js import { Model } from "survey-core"; const surveyJson = { elements: [{ type: "text", name: "score", title: "Score:", inputType: "number" }], triggers: [{ type: "complete", // Intentional typo: 'undefinedQuestion' doesn't exist expression: "{undefinedQuestion} > 100" }] }; const survey = new Model(surveyJson); // Validate all expression types const results = survey.validateExpressions(); results.forEach(result => { let element = result.obj; while (!element.isQuestion && !element.isPanel && !element.isPage && !element.isSurvey) { element = element.getOwner(); } console.warn( `Expression error in "${element.name || "survey"}", property "${result.propertyName}":`, result.errors ); }); // Validate syntax errors only (skip variable/function/semantic checks) const syntaxOnly = survey.validateExpressions({ variables: false, functions: false, semantics: false }); ``` --- ### `survey.applyTheme(themeObj)` — Apply predefined or custom themes Applies a complete visual theme to the survey. Import named theme objects from `survey-core/themes` for built-in themes, or supply your own JSON object with CSS variables exported from the Theme Editor in Survey Creator. ```js import { Model } from "survey-core"; import { LayeredDarkPanelless } from "survey-core/themes"; import { ContrastLight } from "survey-core/themes"; const surveyJson = { /* ... */ }; const survey = new Model(surveyJson); // Apply a built-in theme survey.applyTheme(LayeredDarkPanelless); // Switch to another theme dynamically (e.g., on a button click) function switchTheme() { survey.applyTheme(ContrastLight); } // Apply a custom theme exported from Theme Editor survey.applyTheme({ "themeName": "myBrand", "colorPalette": "light", "isPanelless": false, "cssVariables": { "--sjs-primary-backcolor": "#2E86AB", "--sjs-primary-forecolor": "#ffffff", "--sjs-font-family": "Inter, sans-serif", "--sjs-corner-radius": "8px" } }); // Override CSS classes for specific element types survey.onUpdateQuestionCssClasses.add((s, options) => { if (options.question.getType() === "rating") { options.cssClasses.root += " my-rating-question"; } }); ``` --- ### Localization — Multi-language survey support Enable UI localization by importing the i18n module, switch locales using `survey.locale`, and create multi-language survey content by replacing string values with locale-keyed objects in the JSON schema. Custom locales can be registered via `setupLocale()`. ```js import { Model, getLocaleStrings, setupLocale, surveyLocalization } from "survey-core"; import "survey-core/i18n/french"; import "survey-core/i18n/german"; // Override specific translation strings in the English locale const en = getLocaleStrings("en"); en.pagePrevText = "Back"; en.pageNextText = "Next"; en.completeText = "Submit"; // Register a fully custom locale setupLocale({ localeCode: "es-MX", nativeName: "Español (México)", englishName: "Spanish (Mexico)", strings: { pagePrevText: "Atrás", pageNextText: "Siguiente", completeText: "Enviar" }, rtl: false }); // Multi-language survey content in the JSON schema const surveyJson = { locale: "fr", // Default display locale elements: [{ type: "text", name: "city", title: { default: "City:", fr: "Ville :", de: "Stadt:", "es-MX": "Ciudad:" }, isRequired: true }, { type: "radiogroup", name: "satisfied", title: { default: "Are you satisfied?", fr: "Êtes-vous satisfait ?", de: "Sind Sie zufrieden?" }, choices: [ { value: "yes", text: { default: "Yes", fr: "Oui", de: "Ja" } }, { value: "no", text: { default: "No", fr: "Non", de: "Nein" } } ] }] }; const survey = new Model(surveyJson); // Switch locale at runtime survey.locale = "de"; // Fall back to French when a translation is missing surveyLocalization.defaultLocale = "fr"; ``` --- ### Save and restore incomplete survey progress — `onValueChanged` + `localStorage` / database SurveyJS has no built-in persistence layer; you implement it by handling `onValueChanged`, `onUIStateChanged`, and `onComplete` events. Survey data (`survey.data`) and UI state (`survey.uiState`) are both serializable JSON objects. ```js import { Model } from "survey-core"; const SURVEY_ID = "customer-feedback-v1"; const DATA_KEY = `survey-data-${SURVEY_ID}`; const STATE_KEY = `survey-uistate-${SURVEY_ID}`; const API_URL = `https://your-api.com/responses/${SURVEY_ID}`; const survey = new Model(/* surveyJson */); // --- Restore from localStorage on load --- const savedData = localStorage.getItem(DATA_KEY); const savedState = localStorage.getItem(STATE_KEY); if (savedData) survey.data = JSON.parse(savedData); if (savedState) survey.uiState = JSON.parse(savedState); // --- Persist on every change --- survey.onValueChanged.add((s) => { localStorage.setItem(DATA_KEY, JSON.stringify(s.data)); }); survey.onUIStateChanged.add((s) => { localStorage.setItem(STATE_KEY, JSON.stringify(s.uiState)); }); // --- Submit on completion and clear local cache --- survey.onComplete.add((s, options) => { options.showSaveInProgress(); const userId = /* get from auth context */ "user-42"; s.setValue("userId", userId); fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(s.data) }) .then(res => { if (!res.ok) throw new Error(`HTTP ${res.status}`); options.showSaveSuccess(); localStorage.removeItem(DATA_KEY); localStorage.removeItem(STATE_KEY); }) .catch(err => { options.showSaveError("Your responses could not be saved. Please try again."); console.error(err); }); }); ``` --- ### `Serializer.addProperty()` — Add custom properties to question types Extends any existing question class (or base class) with a new serializable property that appears in Survey Creator's property grid and is persisted in the JSON schema. ```js import { Model, Serializer, Question, ElementFactory } from "survey-core"; // 1. Add a custom property to ALL question types (base "question" class) Serializer.addProperty("question", { name: "helpText", type: "string", category: "general", visibleIndex: 3, default: "" }); // 2. Create a custom question class with its own properties export class ColorPickerQuestion extends Question { getType() { return "colorpicker"; } get defaultColor() { return this.getPropertyValue("defaultColor"); } set defaultColor(val) { this.setPropertyValue("defaultColor", val); } } ElementFactory.Instance.registerElement("colorpicker", (name) => new ColorPickerQuestion(name)); Serializer.addClass( "colorpicker", [{ name: "defaultColor", type: "string", category: "general", default: "#000000" }], () => new ColorPickerQuestion(""), "question" ); // 3. Use the custom type in a survey schema const surveyJson = { elements: [{ type: "colorpicker", name: "brandColor", title: "Pick your brand color:", defaultColor: "#2E86AB" }, { type: "text", name: "notes", title: "Notes:", helpText: "Keep it under 200 characters." // custom property from step 1 }] }; const survey = new Model(surveyJson); console.log(survey.getQuestionByName("brandColor").defaultColor); // "#2E86AB" ``` --- ### Multi-page survey with navigation — `nextPage()`, `prevPage()`, `tryComplete()`, progress bar Build a multi-page survey with conditional page visibility, custom navigation button captions, a progress bar, and a preview page before final submission. ```js import { Model } from "survey-core"; const surveyJson = { firstPageIsStartPage: true, startSurveyText: "Start Survey", pageNextText: "Continue", pagePrevText: "Go Back", completeText: "Submit Answers", showProgressBar: true, progressBarLocation: "top", showPreviewBeforeComplete: true, previewMode: "answeredQuestions", previewText: "Review Answers", showPrevButton: true, completedHtml: "<h2>All done! Thank you.</h2>", completedHtmlOnCondition: [{ expression: "{nps} <= 6", html: "<h2>Thank you. A support agent will reach out shortly.</h2>" }], pages: [{ // Start page (intro) elements: [{ type: "html", html: "<h2>Welcome to our annual survey!</h2><p>This takes about 3 minutes.</p>" }] }, { name: "contactPage", elements: [ { type: "text", name: "email", title: "Email:", inputType: "email", isRequired: true, validators: [{ type: "email" }] }, { type: "text", name: "phone", title: "Phone (optional):", inputType: "tel" } ] }, { name: "feedbackPage", elements: [ { type: "rating", name: "nps", title: "How likely to recommend us? (0–10)", rateMin: 0, rateMax: 10 }, { type: "comment", name: "reason", title: "Why?", visibleIf: "{nps} notempty" } ] }] }; const survey = new Model(surveyJson); // Programmatic navigation (e.g., from custom buttons) const ok = survey.nextPage(); if (!ok) console.warn("Could not advance — check validation errors."); survey.prevPage(); const submitted = survey.tryComplete(); // respects validation if (!submitted) console.warn("Please fix errors before submitting."); ``` --- ### Quiz with `correctAnswer` and time limits — scored quizzes Define the `correctAnswer` on each question to enable automatic quiz scoring. Use `SurveyModel.timeLimit` and `PageModel.timeLimit` to enforce response-time constraints. ```js import { Model } from "survey-core"; const quizJson = { title: "JavaScript Fundamentals Quiz", firstPageIsStartPage: true, startSurveyText: "Start Quiz", showTimerPanel: "top", showTimerPanelMode: "survey", timeLimit: 120, // 2 minutes total for the whole quiz pages: [{ elements: [{ type: "html", html: "<h2>You have 2 minutes to answer 3 questions.</h2>" }] }, { timeLimit: 30, // 30 seconds per page elements: [{ type: "radiogroup", name: "typeof_null", title: "What does `typeof null` return in JavaScript?", choices: ["null", "undefined", "object", "string"], correctAnswer: "object" }] }, { timeLimit: 30, elements: [{ type: "radiogroup", name: "closure", title: "Which term describes a function retaining access to its outer scope?", choicesOrder: "random", choices: ["Prototype", "Closure", "Hoisting", "Currying"], correctAnswer: "Closure" }] }], completedHtml: "<h3>You scored {correctedAnswers} out of {questionCount}.</h3>" + "<p>Correct: {correctedAnswers} | Wrong: {inCorrectedAnswers}</p>" }; const survey = new Model(quizJson); survey.onComplete.add((s) => { console.log("Score:", s.getCorrectedAnswerCount(), "/", s.getQuestionCount()); console.log("Wrong:", s.getInCorrectedAnswerCount()); }); ``` --- ### `choicesByUrl` — Load choices dynamically from a REST API The `choicesByUrl` property on Dropdown, Radiogroup, and Checkbox questions fetches choice data from a URL at render time. Configure `valueName`, `titleName`, and `titleName` mappings to match your API's response shape. ```js import { Model } from "survey-core"; const surveyJson = { elements: [{ type: "dropdown", name: "country", title: "Select your country:", placeholder: "Choose a country...", choicesByUrl: { url: "https://surveyjs.io/api/CountriesExample", valueName: "name" // property in each item to use as the stored value // titleName defaults to valueName when omitted } }, { type: "checkbox", name: "supportedCurrencies", title: "Which currencies does your business accept?", choicesByUrl: { url: "https://openexchangerates.org/api/currencies.json", // Response is { USD: "US Dollar", EUR: "Euro", ... } // SurveyJS automatically handles flat key→value objects }, visibleIf: "{country} notempty" }] }; const survey = new Model(surveyJson); // Optionally listen for loading state survey.onLoadChoicesFromServer.add((s, options) => { console.log(`Loaded ${options.choices.length} choices for "${options.question.name}"`); }); ``` --- ## Summary SurveyJS Form Library covers the full lifecycle of client-side form management: schema definition, dynamic rendering across all major JavaScript frameworks, real-time validation (client-side, server-side, and expression-based), conditional branching logic, incremental save/restore, and final result submission. Its JSON-first design makes it easy to store form schemas in databases and generate them dynamically from a backend, while the event system (`onComplete`, `onValueChanged`, `onValidateQuestion`, `onServerValidateQuestions`, etc.) provides fine-grained hooks at every stage of the user interaction. Framework-specific packages (`survey-react-ui`, `survey-angular-ui`, `survey-vue3-ui`, `survey-js-ui`) sit on top of the platform-agnostic `survey-core` package, meaning business logic written against the `Model` API is fully portable across rendering environments. Common integration patterns include: embedding a multi-page form inside a React/Angular/Vue SPA with result submission to a REST API; using `localStorage` or a database to persist and restore incomplete surveys across sessions; building scored, timed quizzes with `correctAnswer` and `timeLimit`; loading question choices from external REST services via `choicesByUrl`; applying conditional page-skip logic and auto-complete triggers for survey branching; and registering custom expression functions or custom question types via `registerFunction()` and `Serializer.addClass()` to extend the built-in feature set without forking the library.