lab.js Integration

Overview

Open Lab integrates seamlessly with lab.js, a powerful tool for building interactive online experiments and surveys. This integration allows researchers to create complex tasks in the lab.js builder, export them as JSON files, and incorporate them into the Open Lab Study Builder. Once integrated, these tasks become part of your study flow, enabling participants to complete them via web browsers. lab.js supports a wide range of experiment types, from simple surveys to advanced cognitive tasks with randomization and branching.

To get started with lab.js, visit the official website at https://lab.js.org/ for tutorials and examples. In Open Lab, lab.js tasks are added as components in the Study Builder, where you can preview them, incorporate them into flows with randomization (e.g., between-subjects designs), and collect data securely. Data from lab.js tasks is stored encrypted and can be visualized in real-time dashboards.

There are two ways to bring a lab.js task into Open Lab: (1) upload it straight from the lab.js builder, which opens Open Lab and creates a new task from the upload, or (2) save the task as a JSON file and upload that file yourself, either in the Study Builder or in the “My tasks” area. Either way the result is a task in your account. Participants can take it once you add it to a study (see Adding the task to a study).

Prerequisites

Before integrating lab.js with Open Lab:

  • Open a lab.js builder. No lab.js account is needed. Two builders can send tasks to Open Lab:
  • Ensure your experiment is complete and tested in lab.js, including any media files or custom scripts.
  • Have an Open Lab researcher account, or be ready to create one: the upload hands you over to Open Lab, where you log in or sign up.

Method 1: Upload from the lab.js Builder

Both builders can send a task to Open Lab without any manual file handling. The builder does not ask for your Open Lab credentials or for a task name: it uploads the study, and you finish on Open Lab.

  1. Open the lab.js builder and design your task or survey using components like screens, sequences, loops, and interactive elements such as response collection or conditional branching.
  2. Test the experiment locally in your browser to verify functionality, timing, and data output.
  3. Upload the task:
    • In Open Lab's builder, open the save menu and choose Upload to Open Lab… under “Deploy study online”.
    • In the public lab.js builder, choose Export to Open Lab. Its dialog speaks of a “study”; on Open Lab the upload becomes a task.
  4. The builder sends you to Open Lab. Log in, or sign up if you do not have an account yet. The upload link stays valid for 24 hours; after that, upload again from the builder.
  5. Open Lab shows the task details. The title is taken from the study title in lab.js; change it or add a description if you like, then create the task. It appears under “My tasks”.
  6. Add the task to a study, as described in Adding the task to a study.

Each upload from the builder creates a new task. To change a task you have already uploaded and keep its history, open the task in Open Lab and click Edit in lab.js builder. Open Lab's builder then offers Save to Open Lab, which updates that task instead of creating another one. Every saved file is kept as a revision on the task, and each participant session records the revision it ran.

Screenshot of the lab.js builder interface.
The lab.js builder for creating experiments.

Method 2: Manual JSON Upload in Open Lab

If you prefer to handle the export manually or need to upload existing JSON files, you can do so directly in Open Lab. This method gives you flexibility to upload tasks either while building a study or in the dedicated "My Tasks" area for reuse across studies.

  1. Create and test your experiment in the lab.js builder as described in Method 1.
  2. Save the study as a JSON file from the lab.js builder and keep it on your computer.
  3. In Open Lab, navigate to the Study Builder for a specific study or go to the "My Tasks" section in the researcher dashboard.
  4. When adding a "Lab.js Task" component in the Study Builder, or creating a new task in "My Tasks," select the option to upload the JSON file.
  5. Provide a name and description for the task, then submit. The task is added to “My tasks” (and, when you upload from the Study Builder, to that study's flow).

This manual method is useful for importing pre-existing lab.js files or when direct export isn't available. Uploaded tasks can be previewed and edited in Open Lab.

Screenshot of uploading a lab.js JSON file in Open Lab.
Uploading a lab.js task in the Study Builder.

Adding the Task to a Study

A task on its own is not something participants can take. It becomes part of an experiment when you add it to a study:

  1. Open your study and go to the Study Builder.
  2. Add a Lab.js Task component to the study flow.
  3. In the component's editor, open the My tasks tab and select your task.

The same task can be added to any number of studies. A task you upload from inside the Study Builder (Method 2) is added to the component directly.

How Your Data Reaches Open Lab

Open Lab attaches data transmission to your task for you. You do not need to add anything to your script to make saving work — upload the task and the platform handles the rest.

You do not need the “Data transmission” component

The lab.js builder offers a Data transmission (beta) component, and its description mentions Open Lab among the backends it supports. That component exists for studies you host yourself. On Open Lab it is redundant: the platform already transmits your data when the study ends, so including it sends the whole dataset twice for every participant.

Open Lab now stores only the more complete of the two, so an existing study that uses it is not losing anything. But it doubles the upload each participant makes, and there is no reason to keep it — if your task has one, you can delete it.

Your experiment must reach its end

The complete dataset is sent when the study ends. That means the last component has to have some way to finish. Any of these will do:

  • a response — for example a button the participant clicks to continue
  • a timeout, so the component ends on its own after a set duration
  • a call to this.end() in a script attached to the component

A final screen with none of these never finishes. The study stays open on that screen, the complete dataset is never sent, and the participant is never marked as having completed the study. In the lab.js builder preview you can check this: when an experiment ends, a Download button slides in from the top. If it never appears, your experiment is not ending.

Partial data during the session, complete data at the end

While a participant works through your task, lab.js sends partial data to the server whenever there is a pause of a few seconds. These arrive as incremental datasets and each one carries only the rows recorded since the last send. When the study ends, the full dataset for the whole session is sent in one piece.

So if a study never ends, you are left with only the incremental pieces and the tail of the session is missing.

Careful: seeing “full” data does not prove your experiment ended. If your task contains a Data transmission component, that component sends a full dataset of its own partway through — so a study that never reaches its end can still show full data in your data table and look perfectly healthy. The reliable check is your Participants page: if data is arriving but participants are not being marked Completed, your experiment is not ending.

Passing URL Parameters into a Task

You can pass values into a study from the outside — a between-subjects condition, a panel provider's respondent ID, a recruitment source — by appending query parameters to the study link. This is the usual way to hand a task information that Open Lab does not assign itself, such as a condition an external panel has already allocated. Distribute the study link with your own parameters appended, written exactly as you would expect:

https://app.open-lab.online/studies/A2ZVJ?Set=A

Reading a parameter inside the task

Open Lab makes every parameter from the study link available to your task as a lab.js parameter, under the same name you used in the link. A link ending ?Set=A is readable inside the task as this.parameters.Set, and in any place lab.js accepts a placeholder — a screen's content, a condition on a component, a loop template — as ${parameters.Set}. No script is required:

// Anywhere in the task — for example, a Script on before:prepare
const set = this.parameters.Set;   // 'A'

// Or, with no scripting at all, in any content field:
//   Welcome to condition ${parameters.Set}

This works the same way on every kind of study link, including invitation links, because the value is captured when the participant arrives rather than read back out of the address bar later.

Reading a parameter on a landing or ending page

The same placeholder works in Open Lab's own landing and ending pages, in both the title and the message, under the same names. Write ${ parameters.PROLIFIC_PID } in an ending page and each participant sees their own ID:

Thank you. Your completion code is ${ parameters.survey_code }.

Please return to Prolific with ID ${ parameters.PROLIFIC_PID }.

The names are the ones the task sees, so openlab_group_code and openlab_condition work here too. The editor lists the names your study has received under the message field, and warns about one it has never seen.

Two differences from a lab.js content field are worth knowing. A parameter with no value renders as nothing at all, so write sentences that survive it: “Your ID: ${ parameters.PID }” reads better than “Your ID is ${ parameters.PID }, thank you” when the value is missing. And the value is always inserted as plain text, so a participant cannot put formatting, a link or a script on the page by editing their own URL.

No logic in page content

Only the plain form is substituted. Open Lab does not evaluate expressions in landing and ending pages, so a conditional does not work:

Please return to ${ parameters.PROLIFIC_PID ? "Prolific" : "Sona" }.

  → Please return to .

An expression like that is removed before the page is shown, rather than printed, so a participant never sees source code on screen. The study builder warns about it while you are writing, which is where you are meant to find out. This applies to anything beyond a name — a ternary, ||, a bracketed lookup such as ${ parameters['PID'] }. Text that merely contains braces and does not mention parameters, such as ${name} in a study about programming, is left alone.

To vary the wording by recruitment source, use a Branching component set to By a parameter in the study link, with one ending per source. That gives each source its own page rather than one substituted word, and Open Lab records which branch each participant took so you can see it in the data afterwards.

In a demo the placeholder shows the name in brackets, for example [PROLIFIC_PID]. A demo has no recruitment link, so there is no value to show, and the name is more useful than a blank.

The same mechanism carries the platform's own assignments, which use a reserved openlab_ prefix: openlab_condition from built-in branching (or openlab_condition_1, openlab_condition_2, and so on when you have several factors), and openlab_group_code from groups.

Two rules decide what wins when names collide. A link parameter overrides a parameter of the same name declared in the task itself, which is what lets you use the task's own value as a default and the link to change it. But the platform's own assignments always win over the link, and a link parameter whose name begins with openlab_ is ignored entirely — participants cannot put themselves in a condition or a group by editing the URL.

Sending participants down a branch by their link parameter

A branching component normally allocates participants at random, balanced across the conditions you configured. It can instead route them by a parameter their link carried. In the branching component, set How are participants assigned to By a parameter in the study link, then add rules of the form “PROLIFIC_PID is present, send to Condition 1”. Rules are checked in order and the first match decides.

The common use is a study recruiting from two places at once. Prolific sends PROLIFIC_PID and Sona sends your survey_code, so a rule on each parameter being present sends each group to its own ending and redirect. Use is instead of is present when the decision depends on the value rather than on the parameter arriving. Parameter names must match exactly; values are compared ignoring capitalisation and surrounding spaces.

Participants who match no rule go to whichever condition you nominate under If no rule matches, or through the normal balanced allocation if you leave that set to random. This is the case for anyone arriving from a direct link.

This routes participants, it does not randomise them. A study link is visible to whoever opens it, so a participant who edits the URL chooses their own condition. That is harmless when you are separating recruitment sources, and it invalidates a between-subjects manipulation. Use random assignment for anything where the participant must not choose. Open Lab records how each assignment was made, so routed participants remain identifiable in your data.

Sending each fixed slot to its own task

With fixed slots, the landing page gives each participant one group code from a pool you fill on the study's Participants page (group code assignment Fixed slots: each group code used once). A branching component placed after the landing page can route on that slot, so participants in different slots complete different tasks. In the branching component, set How are participants assigned to By the participant's fixed slot and put a different task in each condition.

A rule can match the slot's code, under the name openlab_group_code, or a value attached to the slots on the Participants page. For example, give slots p1–p3 the value task = chain and p4–p6 the value task = control, then add one rule per value. Slots added later then need only the value, not a new rule. Matching works as for link parameters: first match wins, names must match exactly, and values ignore capitalisation and surrounding spaces. Slots that match no rule go to the condition named under If no rule matches.

Unlike a link parameter, a slot is handed out by Open Lab, so participants cannot choose their condition. The condition is decided once the slot is claimed and stored with the source slot; the task receives its name as openlab_condition, as with any branching component. A preview has no slot, so it picks a condition at random to let you see each one.

The name is always the one you wrote

Wherever you work with a link parameter — this.parameters.Set in the task, the participants table, the Redirect component's value source, the data export — it carries exactly the name you put in the link. You write ?Set=A, and it is Set everywhere.

You may still see lp_Set in the address bar for a moment while the study loads. That prefix is how Open Lab keeps your parameters apart from its own while the participant moves from the study page to the task, and it is removed once they have been captured. It is never the name to use. If you type lp_Set somewhere that wants Set, it will not match anything.

Reading the raw URL (older studies)

Studies built before September 2026 often read the address bar directly, and they still work: for those studies the lp_-prefixed parameters stay in the URL for the whole session, exactly as before. The task runs directly in the participant's page — it is not embedded in an iframe — so window.location.search is readable in the ordinary way.

// Older studies only. Script component, event: before:prepare
const params = new URLSearchParams(window.location.search);
const set = params.get('lp_Set');   // 'A', or null if absent

Do not write new studies this way. Use this.parameters.Set: it is shorter, it needs no prefix, it works the same on every kind of study link, and it does not depend on the address bar — which newer studies clear once the parameters have been captured. Invitation links carry a single-use invitationId, which Open Lab also removes once it has been redeemed so that a refresh or a forwarded link cannot redeem it a second time.

If a script of yours produces no output at all — not even a console.log — the problem is almost never the parameters. It usually means the script is not attached to the component you think it is, or is on a different event. Confirm the script is saved on the task in the builder, and use the Study Builder's Test run to check it fires.

Where the values are stored

Link parameters reach your data whether or not the task ever refers to them. They are saved to the participant record under the same names you used in the link, and are available in three places:

  • On the study's Participants page, click Columns and enable the parameter under Custom parameters. Open Lab lists one entry per parameter name it has seen, so a column only appears once at least one participant has arrived carrying it.
  • In your data exports and reports, alongside the rest of the participant's data.
  • In a Redirect component, where you can forward a value on to an external service — useful for returning a panel provider's respondent ID at the end of the study. In the Study Builder's redirect editor, choose the parameter as the source for an outgoing query parameter. For the full round trip with a specific provider, see Recruitment integrations — Sona, Prolific, CloudResearch, MTurk and lab recruitment systems.

If you only need a random split, you do not need URL parameters at all. The built-in condition randomization in the Study Builder assigns and balances participants for you and injects the result into this.parameters with no scripting. Reach for URL parameters when the assignment is made outside Open Lab; reach for built-in randomization when Open Lab should make it.

Variables in Scripts: var, let and const

lab.js runs each script you attach to a component as a separate function, every time its event fires. A variable declared inside a script with var, let or const exists only inside that one run of that one script. Another script cannot see it, and the next trial starts without it. This is the usual reason a value set in one script reads as undefined in the next.

Keep a value beyond the script that set it in one of three places, depending on what it is for:

  • In the data: this.data.choice = 2 stores the value with the current component's row, so it appears in the downloaded data. The value has to be set by the time the component ends, in an end script or an earlier one (see Adding values to the data). A later component reads the most recent value of that variable as this.state.choice.
  • For the components inside this one: set this.parameters.block = 1 in a before:prepare script on a sequence or loop. Every component nested in it can read this.parameters.block, or use ${parameters.block} in its content.
  • Anywhere, without saving it: window.score = 0 keeps a value for the whole session, for example a running score. It is not written to the data unless a script copies it there.
// Script on the task's first screen, event: before:prepare
window.score = 0;           // shared by every later script

// Script on each trial, event: end
if (this.data.correct) {
  window.score += 1;        // not 'let score = ...': that would start at 0 each trial
}
this.data.runningScore = window.score;   // set before the trial ends, so it is saved

Within a script, use const for a value that does not change and let for one that does, such as a counter. Reassigning a const throws an error that stops the task at that point. var still works but offers nothing over let.

Declare every variable before you use it. A script that assigns to a name that was never declared (writing score = 1 instead of let score = 1) runs in the lab.js builder, where it silently creates a global variable, and it ran on the previous Open Lab platform. On Open Lab it can stop the task with an error such as “score is not defined”. If the error comes from a before:prepare script, the task may not start at all, because lab.js prepares the components before showing the first screen. For a value that should last the whole session, use window.score as in the example above.

lab.js Versions in the Builder and on Open Lab

Open Lab runs every task with lab.js 20.2.4, the version the public lab.js builder also uses. Open Lab's builder at builder.open-lab.online is based on a development version of lab.js 23, and its preview runs your task with that version. Most scripts behave the same with both. This section lists where they differ, so that a task which works in the builder's preview also works for your participants.

How the builder stores scripts

When Open Lab's builder opens a task, it converts the file to the newer format. Scripts are stored under hooks instead of messageHandlers, the events commit and after:end are both renamed lock, and a file saved from the builder is marked as version 21.alpha.3. Open Lab reads both formats: when a participant starts the task, scripts stored under hooks run as usual, and a lock script runs as after:end. The task needs no changes.

The public lab.js builder does not convert files back. It reads scripts only from messageHandlers, so it cannot show the scripts of a file saved by Open Lab's builder. To keep editing a task in the public builder, keep the file you saved there.

Events a script can run on

Open Lab's builder offers five events for a script: before:prepare, after:prepare, run, end and lock. All five run on Open Lab. A lock script runs after the component has ended and its data have been saved, in the preview and on Open Lab alike. One event differs: an after:prepare script does not run in the preview, because lab.js 23 has no such event, but it does run on Open Lab.

Adding values to the data

A value written to this.data is saved with the component's row only if it is set by the time the component ends. Use an end script, or an earlier one. When an end script runs, the participant's response and this.data.correct are already set. A value written in a lock, after:end or commit script arrives after the row has been saved and does not appear in the data. This is the same in the builder's preview and on Open Lab.

What a script can use

The parts of lab.js that scripts usually rely on work the same in both versions: this.parameters, this.data, this.state, this.random, this.files, this.options.content, this.timer, this.parent and this.aggregateParameters. A few parts exist in only one of them:

On Open Lab (lab.js 20.2.4)In the builder's preview (lab.js 23)
The data store (all rows recorded so far)this.options.datastorethis.global.datastore
An object shared by all componentsNot available (this.global is undefined); use window, as in Variables in Scriptsthis.global
Building components from a descriptionlab.util.fromObject(...)lab.core.deserialize(...)
The page element the component is shown inthis.options.elNot available
The after:prepare eventRunsDoes not run
The end:uncontrolled eventDoes not runRuns

A script that uses a part missing from one version stops with an error there. For example, this.global.datastore works in the preview but stops with a TypeError on Open Lab, where this.global is undefined. To read the data store in both, look it up in either place:

// Works on Open Lab and in the builder's preview
const datastore = this.options.datastore || this.global?.datastore;
const rowsSoFar = datastore.data.length;

Internal objects such as this.internals.controller exist in both versions but hold different things, so a script that reaches into them may work in only one.

Testing a task with scripts

Participants run your task with lab.js 20.2.4, so the builder's preview does not show how a script that uses the parts above will behave for them. Before collecting data, run the task on Open Lab with the Study Builder's Test run or a preview of the study.

Advanced Features

Open Lab supports advanced lab.js features like randomization in between-subjects designs via branching components. Data from lab.js tasks is automatically collected and can be analyzed in real-time dashboards. For custom setups, ensure your lab.js experiment handles data saving correctly for Open Lab's backend.

Test run: After adding a lab.js task to a study, use the Study Builder's Test run feature (the beaker icon on a task) to auto-play the task and catch mechanical errors — failing to load, JavaScript errors, dead-ends, or no data — before inviting participants.

Tips: Use lab.js's data exchange configuration for seamless integration. If issues arise, check the lab.js documentation for troubleshooting export and compatibility details.

Next Steps

With lab.js integrated, explore the following sections to build and manage your studies on the Open Lab platform:

  • Study Setup

    • Study Builder: Design study flows, upload lab.js JSON files for tasks and surveys.
    • Study Management: Learn how to create, edit, and publish studies.
  • Data

© 2025 Open Lab Platform. All rights reserved.