Skip to content

How to Work on Workshops

Our workshops use a step-based approach to teach concepts to campers. A workshop will consist of multiple files, which we refer to as “steps”. These files are named by the challenge ID, to avoid issues with the translation flow. Unfortunately, this makes it difficult to find the file associated with a specific step.

We’ve built a challenge editor tool that helps remedy this. This tool allows you to navigate the available workshops, and the steps for each workshop (in order). There’s also an embedded code editor you can use to work on the files directly.

Workshops should alternate steps in which new concepts are introduced small piece by piece, and steps where there is freedom of implementation.

Instructions should always be written using second person, and the hints usually use a format like “You should have…”, or “This thing should be/have/do/return…”.

If it’s possible to test the effects of the code instead of what’s being written exactly, that is preferred.

For example, you could ask in a step to create a variable inside a function, and that would need to be tested with regex. Ideally you would ask to instead log a specific value with console.log, or an other side effect that can be tested.

We can test the existence and the value of a variable in the global scope, so that’s less of an issue.

For the tests, the chai asserts are available for use. It is preferred to use specific methods instead of the generic assert().

For example, you can test if the variable a has a value of 2 with assert(a === 2), but it is preferred the useage of assert.strictEqual(a, 2) (specifically strictEqual as the equal method is equivalent to using == and we don’t want to have a success if the variable has a value of the string "2").

Consult the curriculum helpers to know what’s available to help you test the code written by the camper.

Workshops will have a dashedName of the kind workshop-cat-photo-app, that is the word workshop followed by the name of the app that is being built.

Workshop titles start with “Build a” or “Design a”, followed by the name of the app.

Some workshops, which are debugging workshop, will be called Debugging a ... and have prefix workshop-debugging in the dashedName.

You can create the workshop with pnpm run create-new-project. The CLI will ask you which certification, chapter, module, and position the workshop belongs to. If you are unsure about these concepts, refer to the curriculum file structure page.

The metadata for workshops require a blockLabel property with a value of workshop, and a blockLayout with a value of challenge-grid.

These instructions will tell you how to use our challenge editor tool to work on the workshops.

The first time you want to use the editor, and after you use clean commands (like clean-and-develop), make sure you are in the root freeCodeCamp directory and run: pnpm challenge-editor-setup.

Once the editor is set up, run pnpm run challenge-editor (again in the root directory) to start both the client and the API that powers the editor.

The client will run on port 3300, so you can access it at http://localhost:3300. The API runs on port 3200, to avoid conflicts with the learn client and server. This will allow you to run the freeCodeCamp application at the same time as the editor, so you can test your changes locally.

The default view will list the available superblocks - these are the certifications. Click on the certification link you want to work on.

This will take you to the list of blocks. These are the workshops. Click on the workshop link you want to work on.

This will take you to a list of steps for the project. If you are working on an existing step, you can click on the step link to open the editor. If you are adding or removing steps, click the Use the step tools link to switch to the step tools for that challenge.

When you click on a step, you’ll be taken to the editor. This is a basic text editor that offers syntax highlighting.

After you have made your changes, click the Save Changes button to save your changes. You will get a browser alert letting you know that your changes are ready to commit.

When you click the Use the step tools link, you’ll be taken to the step tools page. This allows you to add or remove steps from the project.

Clicking this button will add a new step at the end of the project. This step will use the previous step’s code as the seed.

Enter the number of steps you want to add in the input. Then, clicking the button will create many empty steps at the end of the project.

Enter the step number that you want to add. Then, click the Insert Step button to add the step. The following steps will be re-ordered.

Enter the step number you want to delete. Then click the Delete Step button to remove that step. This will automatically update the step numbers for the remaining steps.

You should not have to use this tool unless you’ve manually deleted or added steps. This tool will reorder the step numbers.

If you want to work on the steps manually, in your local IDE, you can run the step management scripts directly.

The tools/challenge-helper-scripts folder contains tools to help facilitate the creation and maintenance of the freeCodeCamp project-based curriculum.

Run pnpm run create-new-project. This opens up a command line UI that guides you through the process. Once that has finished, there should be a new challenge in the English curriculum that you can use for the first step of the project. For example, if you created a project called test-project in the Responsive Web Design certification, it would be in curriculum/challenges/english/blocks/test-project.

If you want to create new steps, the following tools simplify that process.

A one-off script that will automatically add the next step based on the last step in the project. The challenge seed code will use the previous step’s challenge seed code.

  1. Change to the directory of the project.
  2. Run the following command:
Terminal window
pnpm run create-next-step

A one-off script that automatically adds a specified number of steps. The challenge seed code for all steps created will be empty.

  1. Change to the directory of the project.
  2. Run the following command:
Terminal window
pnpm run create-empty-steps X # where X is the number of steps to create.

A one-off script that automatically adds a new step at a specified position, incrementing all subsequent steps (both their titles and in the block’s JSON structure file). The challenge seed code will use the previous step’s challenge seed code with the editable region markers (ERMs) removed.

  1. Change to the directory of the project.
  2. Run the following command:
Terminal window
pnpm run insert-step X # where X is the position to insert the new step.

A one-off script that deletes an existing step, decrementing all subsequent steps (both their titles and in the block’s JSON structure file)

  1. Change to the directory of the project.
  2. Run the following command:
Terminal window
pnpm run delete-step X # where X is the step number to be deleted.

A one-off script that automatically updates the frontmatter in a project’s markdown files so that they are consistent with the block’s JSON structure file. It ensures that each step’s title (and dashedName) matches the block structure’s challengeOrder.

  1. Change to the directory of the project.
  2. Run the following command:
Terminal window
pnpm run update-step-titles

One-off script to parse the step names from the project and update the block’s JSON structure file order to reflect those steps. Useful if you’ve accidentally lost the changes to the block structure file when adding/removing steps.

  1. Change to the directory of the project.
  2. Run the following command:
Terminal window
pnpm run repair-meta

The script is to be used if the step-based project has files that have challenges ids and filenames that do not match.

  1. Be in the root directory
  2. Run the following command:
Terminal window
pnpm run rename-challenges

Each step in a workshop is a Markdown file named after its challenge id. The challengeType and seed language depend on the type of workshop:

challengeTypeWorkshop typeSeed language(s)
0HTML/CSShtml only, or html + css, or html + css + js/jsx
1JavaScriptjs
20Pythonpy
12Bash/SQL/etc.no seed — the workshop runs via a CodeRoad url field

All step files use exactly two --fcc-editable-region-- markers in the seed to highlight where campers should make changes. Only the last step requires a # --solutions-- section.

The demoType: onLoad field is only present in the first step, and only for workshops that have a visual output (i.e. HTML-based). It is not mandatory, if the project design does not require it it can be omitted.

Here is the template for a JavaScript workshop step:

---
id: <ObjectId>
title: Step N
challengeType: X
dashedName: step-n
demoType: onLoad
---
# --description--
Step description in markdown.
# --hints--
Description of what the test checks.
```js
// test code
```
# --seed--
## --seed-contents--
```lang
// seed code
--fcc-editable-region--
--fcc-editable-region--
```
# --solutions--
```lang
// solution code (only required for the last step)
```

Hooks are optional code blocks that run at specific points during test execution. They can be used to set up shared state, install fake timers, or clean up after tests.

Hooks are supported for workshop types 0, 1, and 20.

Four hooks are available:

HookWhen it runs
--before-all--Once, before any test runs
--before-each--Before each individual test
--after-each--After each individual test (in a finally block, so it always runs even if the test fails)
--after-all--Once, after all tests have finished

Execution order:

For challenges that have an HTML file, --before-all-- is injected as a <script> tag into the sandboxed iframe before the user’s HTML is parsed, so it runs before user code. It has full access to DOM APIs and global test helpers like __FakeTimers and $ (jQuery). The user’s code then renders into the page, and each test runs against the live DOM:

1. --before-all--
2. user code (evaluated once)
3. (for each test): --before-each-- → test → --after-each--
4.--after-all--

For challenges that do not have an HTML file, --before-all-- runs once before any test. However, because user code is re-evaluated as part of each individual test (concatenated with --before-each-- and the test in a single eval), variables declared in --before-all-- will not be in scope during tests. To share state across tests, assign to the global object directly (e.g. globalThis.x = 1 instead of let x = 1):

1. --before-all--
2. (for each test): --before-each-- → user code → test → --after-each--
3. --after-all--

Syntax:

Hooks use the same # --hook-name-- heading syntax as other sections. Each hook must contain exactly one code block. Hooks are placed after --description-- and before --hints--:

# --description--
...
# --before-all--
```js
// Runs once before any test. Set up shared state here.
let clock = __FakeTimers.install();
```
# --before-each--
```js
// Runs before each test.
```
# --after-each--
```js
// Runs after each test, even if it fails.
```
# --after-all--
```js
// Runs once after all tests. Clean up here.
clock.uninstall();
```
# --hints--
...

After you’ve committed your changes, check here for how to open a Pull Request.