How to force my own DIY experimental design for conjoint analysis?

This note is prepared for those familiar with the specifics of discrete choice experimentation. If you need us to help, please feel free to contact us for a quote to customise your experimental design.
DIY experimental design

With Conjointly, you can set up experimental designs of your choosing and perform data collection on Conjointly. Once data have been collected, you will be able to access the standard reports that do not take your design restrictions into account and also download the files for your own analysis that takes your specific design into account.

Experimental design for Claims Test, Product Variant Selector, and Generic Conjoint is generated on the fly. There are special JavaScript hooks that allow you to interfere in the experimental design process and modify the resultant design using customisations. This is useful if you want to force specific restrictions and even a predetermined design.

In this note, we cover all you need to know about customising your DIY experimental design.

For Generic Conjoint


When the survey page is opened, the initializeSurvey method is called. In this method, we initialise the experiment settings. We load the scheme of attributes, restrictions, and weights.


When switching to the frame of the first conjoint block, the renderFrames method is called. This method deletes the old generated frames with answers (if we returned to this frame again with the back button).

This method calls the genericConjointUpdateSettings hook, where you can override the settings of the questionnaire.

The genericConjointUpdateSettings hook

This hook requires the parameter settings. Some properties cannot be changed in the customisation, because that will result in the inability to save the survey:

  • settings.attributes
  • settings.nAlternatives
  • settings.enableNothingOption

Settings that can be changed:

  • settings.prohibitions is a matrix [levels x levels] where if the intersection is 0, then this pair is prohibited. It is completed based on prohibited pairs. You can redefine it and set new pairs.
  • settings.maxQuestions is the maximum number of conjoint questions (can be overridden).
  • settings.userConditions is a [levels] array - you can set weights for certain levels:
    • The greater the weight, the more often it will be shown,
    • 0 will completely hide the level for the user.

Representing levels in generation methods

All levels are represented by a one-dimensional array, i.e. collapsed into a single level. For example, in an experiment with the following attributes:

  • Attribute 1:

    • white,
    • yellow,
    • red
  • Attribute 2:

    • small,
    • large
  • Attribute 3:

    • 1000,
    • 4000,
    • 5000,
    • 6000

Then they will be flattened into one array with the meaning of [white, yellow, red, small, large, 1000, 4000, 5000, 6000]. For the design, it will be just a one-dimensional zero-indexed array of numbers, where 6 corresponds to 4000.

generateNewBlock method

The method returns an array of sets, each set is an array of alternatives, and the alternative is an array of level numbers. During the operation of the method, the levels are named based on the position in the collapsed version (in the above example, the second attribute of the third level will have the number 6).

But at the end of the generateNewBlock method, the level numbers are recoded, and the level numbers are recorded as 1-indexed values within the attribute (i.e. the 6 from the example above becomes 2).

generateNewBlock method

Generic Conjoint and Claims Test return different response structures. There is only one attribute for Claims Test, hence the array has one less nesting level.

You can get into the generation mechanism with several hooks.

The genericConjointIsAlternativeValid hook

It is called when a new alternative is added to the set. Parameters are passed:

  • newAlternative is an array with level numbers (example [0,6])
  • candidate is an array with alternatives already added to the set (example [[0,4],[2,5]])

The method works like a filter. If you return an empty array (return []), then the alternative will not be added to the set (there will be an attempt to generate a new one).

If the alternative is suitable, then you need to return it in the method (return newAlternative).

Example usages:
  • you can make it so that they do not fall into one set with a minimum and maximum price.

  • it is possible to make the color yellow and the size big not shown in the same alternative.

The genericConjointIsSCUValid hook

The method works similarly to genericConjointIsAlternativeValid but works on a processed version of the alternative, in which all price levels are replaced by 0.

This is done in order to check and not allow alternatives that differ only in price.

The genericConjointIsCandidateValid hook

The method is called when the set is added to the block:

  • candidate is an array with alternatives (example [[0,4],[2,5],[0,6]])

  • block is an array with included candidates (example [[[1,4],[2,4],[2,6]],[[0,5],[1,],[2,6]],[[ 2,4],[2,5],[1,4]])

For example, you can look at the prices in the previous block, and make sure that they do not match.

The genericConjointNewAlternativeMaxAttempts hook

It sets the number of attempts to generate a set. The default is 100 attempts.

If you set some complex filters that will reject many options, then it might be worth increasing the maximum number of attempts for generation.

If for this number of attempts it is not possible to generate a set, then the algorithm stops. And as many sets are displayed as it was possible to generate.

For Claims Test and Product Variant Selector

For Claims Test, the principle is similar, but the names of the hooks are slightly different, and the structure of the parameters

  • claimsTestUpdateSettings
  • claimsTestIsCandidateValid
    • candidate is an array with alternatives (example [[0,4,5,7]])
    • block is an array with included candidates (example [[1,4,3,5],[0,1,2,3],[2,6,7,8]])
  • claimsTestNewCandidateMaxAttempts

Example customisations

Example 1: Remove the combination of the first and fifth levels in the Generic Conjoint

This example does not actually require a customisation because you can do this with a forbidden pair.

  function myFilterDeclineCandidate(newAlternative,candidate) {
    if (newAlternative.includes(0) && newAlternative.includes(4)) {
      return [];
    return newAlternative;
  window.hooks.addFilter('genericConjointIsAlternativeValid', 'survey', myFilterDeclineCandidate, 20);
  window.hooks.addFilter('genericConjointNewAlternativeMaxAttempts', 'survey', function() {return 200;}, 20);

Example 2: Remove the first 3 claims from the user if they answered a certain way to the preliminary additional question

Set the maximum number of questions from the answer to the additional question. Remove certain combinations of brands in one set.

  function myFilterDeclineCandidate(candidate, block) 
    // Remove claims 7 and 8 in a single set
    if (candidate.includes(6) && candidate.includes(7)) {
      return [];
    return candidate;
  function myUpdateSettings(settings) {
    settings.maxQuestions = $('#additionalQuestions3-short-answer').get(0).value;
    if ($('#additionalQuestions4-multiple-options-9203').get(0).checked) {
      settings.userConditions = [0,0,0,1,1,1,1,1];
    } else {
      settings.userConditions = [1,1,1,1,1,1,1,1];

  window.hooks.addFilter('claimsTestIsCandidateValid', 'survey', myFilterDeclineCandidate, 20);
  window.hooks.addFilter('claimsTestNewCandidateMaxAttempts', 'survey', function() {return 200;}, 20);
  window.hooks.addAction('claimsTestUpdateSettings', 'survey', myUpdateSettings, 20);

Example 3: Make sure that all alternatives in the same set have the same level for the first attribute

Imagine there are only three levels in the first attribute.

  function myFilterDeclineSet(newSet,block) {
    if (
      !newSet.every( x => x[0] == 0) && 
      !newSet.every( x => x[0] == 1) &&  
      !newSet.every( x => x[0] == 2) 
    ) { 
      return []

    return newSet;
  window.hooks.addFilter('genericConjointIsCandidateValid', 'survey', myFilterDeclineSet, 20);
  window.hooks.addFilter('genericConjointNewAlternativeMaxAttempts', 'survey', function() {return 200;}, 20);
  window.hooks.addFilter('genericConjointNewCandidateMaxAttempts', 'survey', function() {return 20000;}, 20);

Example 4: Make sure that levels 1 and 2 are not shown together in a one-attribute MaxDiff

  var prohibited_combination = [1,2];
  function myFilterDeclineCandidate(candidate, block) {
    if (candidate[0].includes(prohibited_combination[0]-1) &&
        candidate[0].includes(prohibited_combination[1]-1)) {
      return [];
    return candidate;
  window.hooks.addFilter('genericConjointIsCandidateValid', 'survey', myFilterDeclineCandidate, 20);
  window.hooks.addFilter('genericConjointNewAlternativeMaxAttempts', 'survey', function() {return 200;}, 20);