Submit a request
Submit a request

Creating Custom Questions

Learn how to create a custom Question type for use with Learnosity's Questions API.

Learnosity Custom Question types allow you to create your own unique Questions, giving you full control over the rendering of the response area, the user interaction with the Question, and the scoring of the response.

A custom Question is defined like any other Question type, by passing a JSON object to the Questions API. In the Question JSON, you provide URLs to JavaScript files containing the program logic that will drive your Question and its scoring.

The first step is to initialise your custom Question via Questions API. Please refer to the table of supported JSON attributes below.

Important: once a session of custom Question type is saved or submitted for server-side processing (such as back-end scoring) Learnosity can only access custom Question attributes supplied in the initialization JSON (see table below). All other attributes added to the Question object after initialization (for example, dynamically added attributes) will be ignored on the server side.

Table 1: Supported attributes for custom Questions

Attribute Name Mandatory Value Comment
response_id Yes Type: string Used in the page where custom Question will be rendered as a part of the class name "question-response_id" of span element (HTML hook), for example:

<span class="learnosity-response question-response_id"></span>
type Yes Type: string For the custom Question type, it should be exactly “custom” (case-sensitive).
js Yes Type: object
Type: string

An object containing URLs to JavaScript files which define AMD modules for the Question and the Scorer.
"js": { "question": "my_custom_question.js", "scorer": "my_custom_scorer.js" }


string containing URL to a JavaScript file which defines an AMD module for the Question, and optionally, Scorer.
"js": "my_custom_combined_file.js"

Important: For successful server-side scoring, the JavaScript files supplied must be accessible externally.

Note: Disabling autoscoring

To disable autoscoring, do not supply a js.scorer attribute.

In this case, automarkable should return false and you will not see a scoring exception as a pop-up window. 

Note: scorer.js return values for automarkable

  • If you don't use the canValidateResponse method, automarkable should return true.
  • If you do use the canValidateResponse method, automarkable should return the canValidateResponse value.
css No Type: string A URL to a CSS file containing styles for the Question.
valid_response Yes Type: string The valid response for your custom Question.

You must pass this attribute even if it is not used in your scoring logic — just pass it as an empty string, for example:

(“valid_response”: “”)

Important: If not provided, server-side scoring will not be triggered.
<any custom attribute> No Type: string /

Type: number /

Type: boolean
Depending on your custom scoring logic, these attributes’ values may or may not be accessible on the back-end (for Learnosity server-side scoring, for example) once the session is saved or submitted.

Important: All attributes dynamically added to the custom Question object after its initialization will be ignored on the server-side.

For more information on supported attributes, please visit the custom attributes documentation.

customQuestionJson = {
    "response_id": "custom-shorttext-response-1",
    "type": "custom",
    "js": {
        "scorer": "//"
    "css": "//",
    "stimulus": "What is the capital of Australia?",
    "valid_response": "Canberra",
    "score": 1

Code example 1: JSON attributes for custom Question initialization


Creating a Learnosity Custom Question with Webpack and JavaScript ES6 

Below is the guide to creating a Learnosity Custom Question using JavaScript ES6 and Webpack which is the recommended approach. In this guide, we use Webpack and React to bundle the code and render the view as an example. During your own development process, you can use any technology you want.

Webpack Setup

The following dependencies are required:

  • webpack
  • webpack-cli
  • @babel/core
  • @babe/preset-env
  • babel-loader
  • css-loader
  • mini-css-extract-plugin
  • node-sass
  • sass-loader
  • style-loader

Below, see the package.json skeleton with predefined scripts:

"name": "es6CustomQuestion",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.8.4",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-object-rest-spread": "^7.8.3",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-react-jsx": "^7.8.3",
"@babel/preset-env": "^7.8.4",
"@babel/preset-react": "^7.8.3",
"babel-loader": "^8.0.6",
"css-loader": "^3.4.2",
"mini-css-extract-plugin": "^0.9.0",
"node-sass": "^4.13.1",
"sass-loader": "^7.1.0",
"style-loader": "^1.1.3",
"webpack": "^4.41.6",
"webpack-cli": "^3.3.11"
"scripts": {
"dev": "webpack -d --display-reasons --progress --colors --watch",
"prod": "webpack -p --display-reasons --progress --colors"
"dependencies": {
"classnames": "^2.2.6",
"react": "^16.4.2",
"react-dom": "^16.4.2"

Code example 2: the package.json skeleton with predefined scripts


Next, we will look at the Webpack configuration skeleton: webpack.config.js:

/*globals require, __dirname*/
const path = require('path');
const appDir = path.resolve(__dirname, 'src/');
const distDir = path.resolve(__dirname, 'dist/');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
context: appDir,
entry: {
question: './question.js',
scorer: './scorer.js'
output: {
path: distDir,
filename: '[name].js'
plugins: [
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: "[name].css",
chunkFilename: "[id].css"
resolve: {
modules: [
alias: {
stylesheets: path.resolve(__dirname, 'www/latest/stylesheets/')
module: {
rules: [
test: /\.(sa|sc|c)ss$/,
use: [
test: /\.js$/,
exclude: /node_modules/,
use: [
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env', '@babel/preset-react'
plugins: [

Code example 3: The Webpack configuration skeleton


Creating the source files for your custom question

Create a folder named src with the following structure:

|___ vendor
|___ views
|___ scorer
|___ question.js
|___ scorer.js

Figure 1: The folder structure under your "src" source folder.


To reuse predefined vendor libraries like underscore.js or jQuery, create a JavaScript file named lrn.js in the vendor folder.

const LRN = {};

export function register(dependencies) {
for (let key in dependencies) {
if (dependencies.hasOwnProperty(key)) {
LRN[key] = dependencies[key];

export default LRN;

Code example 4: Re-using predefined vendor libraries by creating a "lrn.js" file.


The two main entry JavaScript files of the custom question (question.js and scorer.js) must define an AMD module using Learnosity's namespaced require.js instance, LearnosityAmd.define.  To register the predefined vendor libraries like jQuery or underscore.js from Learnosity, import the vendor/lrn file created above and use the method register to register these libraries.

Do the following in question.js:

import '../scss/customReactInput.scss';

import { register } from './vendor/lrn';

/*global LearnosityAmd*/
], function (
) {
// Register Learnosity exposed libraries into LRN util object of vendor/lrn

// Use require instead of import to ensure by the time we resolve the module "scorer",
// Learnosity dependencies have been attached to LRN utils in vendor/lrn
const bundle = require('./views/question');

return {
Question: bundle.default

Code example 5: Registering and importing external libraries in question.js. 


Create the main UI for the custom question:

import React from 'react';
import ReactDOM from 'react-dom';
import LRN from '../vendor/lrn';
import ReactInput from './components/reactInput';

// When this module is resolved you should be able to access all libraries provided by Learnosity through
// LRN util object in 'vendor/lrn'

export default class Question {
constructor(init) {
const { state } = init;

this.init = init; =;
this.el = init.$el.get(0);

this.state = {
disabled: state === 'review' || state === 'preview',
defaultValue: init.response,
validationUIVisible: false,
isValid: null,


// "validate" event can be triggered when Check Answer button is clicked or when public method .validate() is called
// so developer needs to listen to this event to decide if he wants to display the correct answers to user or not
// options.showCorrectAnswers will tell if correct answers for this question should be display or not.
// The value showCorrectAnswers by default is the value of showCorrectAnswers inside initOptions object that is used
// to initialize question app or the value of the options that is passed into public method validate (like question.validate({showCorrectAnswers: false}))'validate', options => {
const isValid = init.getFacade().isValid();

this.state = {
validationUIVisible: true,
showCorrectAnswers: options.showCorrectAnswers

render() {
return ReactDOM.render(

onInputChange = value => {'changed', value);

requestToClearValidationUI = () => {
this.state = {
isValid: null,
showCorrectAnswers: false,
validationUIVisible: false,


Code example 6: Creating the main UI for the custom question


Create the scorer class:

import LRN from '../vendor/lrn';

// When this module is resolved you should be able to access all libraries provided by Learnosity through
// LRN util object in 'vendor/lrn'

function getValidResponse(question) {
return (
LRN._.isObject(question) &&
LRN._.isObject(question.validation) &&
) || {};

export default class Scorer {
constructor(question, response) {
this.question = question;
this.response = response;
this.validResponse = getValidResponse(question);

isValid() {
return this.response && this.response.value === this.validResponse.value;

score() {
return this.isValid() ? this.maxScore() : 0;

maxScore() {
return this.validResponse.score || 1;

canValidateResponse() {
return !!this.validResponse;

Code example 7: Creating the scorer class.


Use custom_widget_options to pass service related information

In some scenarios, you need the clients using your custom questions/features to pass certain information like authentication tokens. This information will be stored inside the initOptions custom_widget_options and you can retrieve those information through the public method init.getCustomWidgetOptions(key: string)

You are required to let your clients know what key you expect them to set in the initOptions custom_widget_options.

// Example of initOptions with custom_widget_options
const initOptions = {
custom_widget_options: {
custom_short_text: {
authenticationToken: '1234-abcd'
const questionsApp = LearnosityApp.init(initOptions);

// Your Custom Question Code
export default class Question {
constructor(init) {
const authenticationToken = init

Code example 8: Retrieving custom_widget_options


Developing a Custom Question with legacy JavaScript ES5

The JavaScript file that you provide must define an AMD module using Learnosity's namespaced require.js instance, LearnosityAmd.define. The module must define a function to act as a constructor for your Question. The module must return an object with a Question property containing the constructor, like so:

LearnosityAmd.define(['jquery-v1.10.2'], function ($) {
    'use strict';

    function CustomShorttext(init) {
        this.init = init;

        init.$el.html('<input type="text" />');

        var $response = $('input', init.$el);

        if (init.response) {

        $response.change(function (event) {
  'changed', event.currentTarget.value);

    return {
        Question: CustomShorttext


Code example 9: defining an AMD module

If certain external assets should be used for custom questions, they could be loaded by including in LearnosityAmd.define. All the assets listed will be successfully loaded on a page once the provided callback in LearnosityAmd.define is called.

                      ''], function ($) {
    'use strict';

    // by this time the assets '' and  
    // '' are loaded successfully

    function CustomShorttext(init, lrnUtils) {

Code example 10: including external asset

The code example below shows how to free up memory properly while using custom Questions with Questions API.

LearnosityAmd.define(['jquery-v1.10.2'], function ($) {
    'use strict';

    function CustomShorttext(init, lrnUtils) {
        this.$el = init.$el;
        //notifying Questions API that the Question is ready (rendered)'ready');

        //setting up a handler for Questions API reset event 
        //which is triggered via Questions API reset() public method'reset', function () {
            //free up any memory if required

Code example 11: freeing memory

Questions API will instantiate your Question by calling this function, passing in two objects. The first object, initOptions contains the following properties:

Table 2: initialization properties for the initOptions object

Property Name Comment
state The state Questions API was initialised in (initialresume, or review).
question The Question object to be rendered.
response Previously saved response data for this response ID. This should be placed in the response area when in resume or review state.
$el A jQuery selection containing the DOM element for your Question's response area. This is where you will render the Question.
events Backbone.js Events object. You must use this to communicate with Questions API (see below).
getFacade A function that returns a facade object containing the public methods of the current Question instance. Add or override public methods on the facade object to make those available on instances of the custom Question object exposed by Questions API, for example:

getI18nLabel A function that returns the internationalization (i18n) text of the provided key. For example:

this.initOptions.getI18nLabel('checkAnswer'); // return "Check Answer" text
questionsApiVersion A string containing the version number of the current instance of Questions API.

The second object, lrnUtils is a facade containing the following:

renderComponent(String componentName, Element hook) A function used to render the given componentName at the specifed element. Used to render Learnosity's provided components like the Check Answer button or the suggested answer list component. The function returns a promise that resolves to a facade containing public methods available on the component. The promise resolves when the component has finished rendering. See the section on custom Question components, below.

Your JavaScript module must render the HTML for the Question inside the provided element. When this is complete, you must notify Questions API that the Question is ready. This example renders a simple text input, and then triggers the ready event:

init.$el.html("<input type=\"text\" />");"ready");

Code example 12: notifying Questions API that the Question is ready


Questions API will trigger an error event if your Question does not call the ready event within an acceptable timeframe.

Your JavaScript module must notify Questions API when the user enters or changes a response to the Question, by triggering a changed event containing the response data. This example uses jQuery's change() method to listen for changes to the text input:

var $response = $("input", init.$el);

$response.change(function (event) {"changed", event.currentTarget.value);
Code example 13: using jQuery’s change() method to listen for changes to text input


You can also save an object as a response:"changed", {"attr1": value1, "attr2": value2});
Code example 14: saving an object as a response


This may be required when your answer has several data points that must be returned (for example, graph coordinates with X and Y values, or a record of the student’s actions).

This ensures responses can be saved as part of the session, or retrieved locally via Questions API public methods, e.g. getResponses().

To implement scoring for your Question, your need to provide a separate JavaScript module that defines a scorer function. Questions API will call this function, passing it the Question object and the response data for the Question. The response data comes directly from the changed events triggered by your Question.

You must define methods on the prototype of the scorer function according to our scoring interface, as shown in the example below. The object returned by your module must define a Scorer property containing the scorer function.

Testing Scoring Functions on the Server-side: score and maxScore

On the server-side, the score and maxScore functions are called once the session is saved or submitted. To test these functions, you can use the Data API online scoring endpoint which uses the same logic as the back-end scoring.


Screenshot: Data API online scoring endpoint demo page


To test your scoring functions:

1. Visit then scroll to the Scoring section at the bottom of the page.

2. On the questions field on that page, paste in the JSON object for your custom Question(s).

    "response_id": "custom-shorttext-response-1",
    "type": "custom",
    "js": {
        "scorer": "//"
    "css": "//",
    "stimulus": "What is the capital of Australia?",
    "valid_response": "Canberra",
    "score": 1
Code example 15: the JSON object for a custom Question


3. On the responses field on that page, paste in the JSON object you can retrieve locally via Questions API public methods, for example, getResponses(). For instance, call questionsApp.getResponses() in the browser console to get the following:

    apiVersion: "v2.119.0"
    type: "object"
    value: "lesdafsdf"
Code example 16: returned data from calling questionsApp.getResponses() in the browser console


Therefore, testing the responses object will look like the following:

    "custom-shorttext-response-1": {
        "value": "Canberra",
        "type": "object",
        "apiVersion": "v2.119.0"
Code example 17: testing the responses object


4. Click Submit to send the request and analyse the result data on the Response tab (you will be switched there automatically). The result data will look like the following:

   "meta": {
      "status": true,
      "timestamp": 1529371443,
      "records": 1
   "data": [
         "response_id": "custom-shorttext-response-1",
         "type": "custom",
         "automarkable": true,
         "is_valid": true,
         "score": 1,
         "max_score": 1,
         "attempted": true,
         "error": null
Code example 18: result data from the Data API endpoint


5. Check that data[].score and data[].maxScore are available (not null) and have correct values. Doing this, you can iterate over your server-side scoring, until your score and maxScore are returning the expected results. Then, you can move this working model into your JavaScript program.


Please refer to the Supported JavaScript Libraries section of this page to see which libraries can be used on the server-side.


Implementing a Custom Scorer

Implementing a scorer will allow responses for the Question to be evaluated by Questions API public methods, such as getScores().

Server-side variables for the CustomScorer function

The CustomScorer function, (which executes on the server-side) takes question and response variables like so: CustomScorer(question, response). Scoring for this is executed remotely, so only certain variables are available in that environment.

The question variable
The question variable is the object (containing JSON attributes) which is passed to initialize Questions API (refer to Table 1: supported attributes for Custom Questions).

For example, if you want to use the valid_response attribute you would call it in the form question.valid_response.

Only the attributes which are passed at initialization time will be processed for server-side scoring.

Important Any attributes dynamically added to the Question object after Questions API has been initialized successfully will be ignored during server side scoring.

The response variable
The response variable is the value attribute retrieved via Questions API public methods, such as getResponses().

For example, calling questionsApp.getResponses() in the browser console returns this object:

Code example 19: the object returned by questionsApp.getResponses()


Therefore, the response is equal to the value attribute of response_id above.

The example below shows how to access attribute1 of value (from code example 10) within the score function.

function CustomScorer(question, response) {
       this.question = question;
       this.response = response;

CustomScorer.prototype.score = function () {
   return this.response.attribute1;
Code example 20: accessing 'attribute1' within the score function


An example of how to call CustomScorer follows.

LearnosityAmd.define(['underscore-v1.5.2'],function (_) {

    /* Constructor for the scorer
     * @param question The question object
     * @param response The response data
    function CustomScorer(question, response) { ... }

    /* Is the response correct?
     * @return boolean
    CustomScorer.prototype.isValid = function () { ... };

    /* The score for the current response.
     * @return float
    CustomScorer.prototype.score = function () { ... };

    /* The maximum score for this question.
     * @return float
    CustomScorer.prototype.maxScore = function () { ... };

    /* Is the provided question json valid to be validated?
     * If this method returns "false" then Check Answer button
     * will not be visible and dispatching public method "validate"
     * will not validate the current question - as the provided question
     * is not validatable.
     * @return boolean
    CustomScorer.prototype.canValidate = function () { ... };

    return {
        Scorer:   CustomScorer
Code example 21: calling CustomQuestion and CustomScorer functions


All Learnosity Question types have a validation UI to give feedback to the user for valid or invalid responses. This feedback can be triggered via Questions API public methods e.g. validateQuestions(). You can add support for this to your Question by listening for a validate event. The parameters for this are listed in the table below.

Table 3: parameters for the validate event handler

Parameter Name Parameter Value Comment
options Object

options = { 
showCorrectAnswers: value

The value depends on the initialization object passed to Questions API.

Default value is true.
The showCorrectAnswers flag has different behaviors based on the state provided.

More information:
showCorrectAnswers Documentation"validate", function (options) { // options object containing "showCorrectAnswers" to tell developer if he/she should display correct answers to user
    var result = init.getFacade.isValid(); // Use facade.isValid(true) to get the detailed report


    if (!result && options.showCorrectAnswers) {
Code example 22: the validate event handler


Important Handler code for the validate event should be provided before the ready event is sent off.

Additionally, apply the following Learnosity class names to the relevant elements of the Question to have your validation UI styled according to Learnosity's standard look and feel.

    // Add "lrn_response_index_visible" class if you want to display the index of current response
    // Add this class to display default Learnosity correct, incorrect style
    .addClass(isCorrect ? "lrn_correct" : "lrn_incorrect")
    // After adding the class "lrn_response_index_visible", you then can inject the response index element
    .prepend("<span class="lrn_responseIndex"><span>1</span></span>")
    // Add this element if you want to display to corresponding validation (cross, tick) icon
    .append("<span class="lrn_validation_icon"/>");
Code example 23: supported class names


The validate event is triggered for all supported states of Questions API (initial, resume, review). Therefore, its event handler is the best place to accommodate your code to manage custom Question in different states.

If you would like to use your Question in resume or review states, you should check for an existing response value when rendering your Question. In review state, you should also show your Question's validation UI.

For instance:"validate", function (options) {

    if (init.response) {

    if (init.state == "review") {
         // show validation UI
Code example 24: checking for an existing response value in review mode


Custom Question components are a way to include common Learnosity controls and UI elements into a custom Question with standard Learnosity styling and behaviour.

Use the lrnUtils object passed to the custom Question's constructor to inject components into the DOM elements of your Question. Simply call lrnUtils.renderComponent(), passing the string name of the desired component and the DOM element where it should be placed. The function returns a promise, which resolves to a facade of public methods that can be called on the component.

See the following examples for each of the available components.


Renders the Check Answer button. On click, the button validates the current custom Question. This button will be invisible during review state.

Available methods:

  • remove()

this.$el.append("<div data-lrn-component=\"suggestedAnswersList\"/>");
this.lrnUtils.renderComponent("CheckAnswerButton", this.$el.find("[data-lrn-component=\"checkAnswer\"]").get(0));
Code example 25: rendering the `Check Answer` button




Renders the Suggested Answers List component. This component will only be visible if the activity's showCorrectAnswers is set to "true".

Available methods:

  • setAnswers()
  • reset()
  • remove()

var self = this;
    .then(function (component) {
        self.suggestedAnswersList = component;

        // Pass in string to display correct answer list without the index
        // self.suggestedAnswersList.setAnswers(correctAnswerText);

        // Pass in an array of object which contains index and label to render a list of suggested answers
            index: 0,
            label: correctAnswerText
Code example 26: rendering the correct answers


You can request access to certain JavaScript libraries as part of your module definition. Please note the supported lists of client-side and server-side libraries, as they differ.

Questions API currently exposes the following JavaScript libraries:

Client-side supported libraries

  • mathcore
  • jquery-v1.10.2
  • backbone-v0.9.10
  • underscore-v1.5.2

Server-side supported libraries

  • mathcore
  • underscore-v1.5.2

LearnosityAmd.define(["jquery-v1.10.2"], function ($) { ... });
Code example 27: defining a library for use on the client-side


You can use this module (Mathcore) to validate math responses (as LaTeX) against the defined specification.

The passed value can be any valid LaTeX string. The specification, on the other hand, is a complex object. We encourage you to build it using the user interface of the Math Formula Question type in Question Editor (see the Question Editor demo pages). This way you can quickly learn which methods and options are available. To do so, simply use the validation tab to build and Source button to preview specification JSON.

For more information, see the full documentation for the Mathcore auto-grading library.

See the Mathcore auto-grading documentation

Here is a demo which shows an example custom implementation of the Short Text Question type. You can rewrite the Question JSON to define your own custom Questions.

Go to demo
Was this article helpful?
2 out of 2 found this helpful

Did you arrive here by accident? If so, learn more about Learnosity by clicking here.