PrairieLearn questions are built on top of widely-used technologies like HTML, CSS, JavaScript, and Python.


All questions start with a question.html file. This file determines what content students will see when they visit a question.

Here's an extremely simple question:

<p>In what year was the University of Illinois Urbana-Champaign establised?</p>
<input type="text" name="year" />

It shows the question ("In what year was the University of Illinois Urbana-Champaign established?") and an input for the student to submit their answer.

However, this question isn't very useful yet. While it can accept an answer from a student, it has no notion of what the correct answer is, and thus no way to provide feedback to the student. To fix that, we'll use a PrairieLearn element.


PrairieLearn elements provide reusable building blocks for questions. They encapsulate the logic needed to render inputs, collect and grade submissions, and provide useful feedback to students. You use them via special HTML tags with a pl- prefix. The PrairieLearn documentation contains a full list of the available elements, but here are some of the most commonly-used ones:

In the example question we're working with, we want to collect and grade a year value. The <pl-string-input> element contains all the logic necessary to do that.

<p>In what year was the University of Illinois Urbana-Champaign established?</p>
<pl-string-input answers-name="year" correct-answer="1868"></pl-string-input>

The answers-name attribute is a unique key used to store the answer, and the correct-answer attribute specifies the string that will be treated as the correct answer.

We now have a question that can correctly grade itslef - a student entering "1868" as their answer will recieve full marks.


The above question is still relatively simple. It can grade itself, but that's about it. PrairieLearn really shines when you can introduce an element of randomization to questions.

To facilitate randomization, question.html files support the Mustache template syntax. This allows you to easily include dynamically-generated values in your questions. Let's modify our previous question.html file to support randomization:

<p>In what year was the {{params.university_name}} established?</p>
<pl-string-input answers-name="year"></pl-string-input>

We replaced the hardcoded university name with {{params.university_name}} and dropped the hardcoded correct-answer="1868" from the <pl-string-input> element. Now we can randomly select a different university/year pair for each student using this question. To actually generate the parameters, we'll introduce a new file:

Adding a file allows you to write custom logic for generating question, parsing and grading submissions, and providing feedback. You have the full power of Python and many of its libraries (such as numpy, matplotlib, and scipy) at your disposal.

For our example question, we need to randomly select a university name and the year it was established. We can accomplish that with the following Python code in

import random
"University of Illinois at Urbana-Champaign": "1868",
"University of British Columbia": "1908",
"University of California, Berkeley": "1868"
def generate(data):
university_name, year = random.choice(list(UNIVERSITY_YEARS.items()))
data["pararms"]["university_name"] = university_name
data["correct_answers"]["year"] = year

generate(...) is a special function that PrairieLearn will call when it needs to generate a new variant of your question. The data argument is a dictionary that PrairieLearn uses to maintain state and pass that state between different elements in a given question. Let's look at the two values we insert into data in more detail.