freecodecamp-curriculum

Comprehensive guide for contributing to and working with freeCodeCamp's open-source codebase and curriculum platform

INSTALLATION
npx skills add https://github.com/aradotso/trending-skills --skill freecodecamp-curriculum
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

freeCodeCamp Curriculum & Platform Development

Skill by ara.so — Daily 2026 Skills collection.

freeCodeCamp.org is a free, open-source learning platform with thousands of interactive coding challenges, certifications, and a full-stack curriculum. The codebase includes a React/TypeScript frontend, Node.js/Fastify backend, and a YAML/Markdown-based curriculum system.

Architecture Overview

freeCodeCamp/

├── api/                   # Fastify API server (TypeScript)

├── client/                # Gatsby/React frontend (TypeScript)

├── curriculum/            # All challenges and certifications (YAML/Markdown)

│   └── challenges/

│       ├── english/

│       │   ├── responsive-web-design/

│       │   ├── javascript-algorithms-and-data-structures/

│       │   └── ...

│       └── ...

├── tools/

│   ├── challenge-helper-scripts/  # CLI tools for curriculum authoring

│   └── ui-components/             # Shared React components

├── config/                # Shared configuration

└── e2e/                   # Playwright end-to-end tests

Local Development Setup

Prerequisites

  • Node.js 20+ (use nvm or fnm)
  • pnpm 9+
  • MongoDB (local or Atlas)
  • A GitHub account (for OAuth)

1. Fork & Clone

git clone https://github.com/<YOUR_USERNAME>/freeCodeCamp.git

cd freeCodeCamp

2. Install Dependencies

pnpm install

3. Configure Environment

cp sample.env .env

Key .env variables to set:

# MongoDB

MONGOHQ_URL=mongodb://127.0.0.1:27017/freecodecamp

# GitHub OAuth (create at github.com/settings/developers)

GITHUB_ID=$GITHUB_OAUTH_CLIENT_ID

GITHUB_SECRET=$GITHUB_OAUTH_CLIENT_SECRET

# Auth

JWT_SECRET=$YOUR_JWT_SECRET

SESSION_SECRET=$YOUR_SESSION_SECRET

# Email (optional for local dev)

SENDGRID_API_KEY=$SENDGRID_API_KEY

4. Seed the Database

pnpm run seed

5. Start Development Servers

# Start everything (API + Client)

pnpm run develop

# Or start individually:

pnpm run develop:api      # Fastify API on :3000

pnpm run develop:client   # Gatsby on :8000

Curriculum Challenge Structure

Challenges are stored as YAML/Markdown files under curriculum/challenges/.

Challenge File Format

# curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/comment-your-javascript-code.md

---

id: bd7123c8c441eddfaeb5bdef  # unique MongoDB ObjectId-style string

title: Comment Your JavaScript Code

challengeType: 1              # 1=JS, 0=HTML, 2=JSX, 3=Vanilla JS, 5=Project, 7=Video

forumTopicId: 16783

dashedName: comment-your-javascript-code

---

# --description--

Comments are lines of code that JavaScript will intentionally ignore.

// This is an in-line comment.

/ This is a multi-line comment /


# --instructions--

Try creating one of each type of comment.

# --hints--

hint 1

assert(code.match(/(\/\/)/).length > 0);


hint 2

assert(code.match(/(\/\[\s\S]+?\\/)/).length > 0);


# --seed--

## --seed-contents--

// Your starting code here


# --solutions--

// inline comment

/* multi-line

comment */

Challenge Types

TypeValueDescription
HTML0HTML/CSS challenges
JavaScript1JS algorithm challenges
JSX2React component challenges
Vanilla JS3DOM manipulation
Python7Python challenges
Project5Certification projects
Video11Video-based lessons

---

Creating a New Challenge

Using the Helper Script


# Create a new challenge interactively

pnpm run create-challenge

# Or use the helper directly

cd tools/challenge-helper-scripts

pnpm run create-challenge --superblock responsive-web-design --block css-flexbox

Manual Creation

  • Find the correct directory under curriculum/challenges/english/
  • Create a new .md file with a unique ID
# Generate a unique challenge ID

node -e "const {ObjectID} = require('mongodb'); console.log(new ObjectID().toString())"
  • Follow the challenge file format above

Validate Your Challenge

# Lint and validate all curriculum files

pnpm run test:curriculum

# Test a specific challenge

pnpm run test:curriculum -- --challenge <challenge-id>

# Test a specific block

pnpm run test:curriculum -- --block basic-javascript

Writing Challenge Tests

Tests use a custom assertion library. Inside # --hints-- blocks:

JavaScript Challenges

# --hints--

`myVariable` should be declared with `let`.

assert.match(code, /let\s+myVariable/);


The function should return `true` when passed `42`.

assert.strictEqual(myFunction(42), true);


The DOM should contain an element with id `main`.

const el = document.getElementById('main');

assert.exists(el);

Available Test Utilities


// DOM access (for HTML challenges)

document.querySelector('#my-id')

document.getElementById('test')

// Code inspection

assert.match(code, /regex/);          // raw source code string

assert.include(code, 'someString');

// Value assertions (Chai-style)

assert.strictEqual(actual, expected);

assert.isTrue(value);

assert.exists(value);

assert.approximately(actual, expected, delta);

// For async challenges

// Use __helpers object

const result = await fetch('/api/test');

assert.strictEqual(result.status, 200);

API Development (Fastify)

Route Structure

// api/src/routes/example.ts

import { type FastifyPluginCallbackTypebox } from '../helpers/plugin-callback-typebox';

import { Type } from '@fastify/type-provider-typebox';

export const exampleRoutes: FastifyPluginCallbackTypebox = (

  fastify,

  _options,

  done

) => {

  fastify.get(

    '/example/:id',

    {

      schema: {

        params: Type.Object({

          id: Type.String()

        }),

        response: {

          200: Type.Object({

            data: Type.String()

          })

        }

      }

    },

    async (req, reply) => {

      const { id } = req.params;

      return reply.send({ data: `Result for ${id}` });

    }

  );

  done();

};

Adding a New API Route

// api/src/app.ts - register the plugin

import { exampleRoutes } from './routes/example';

await fastify.register(exampleRoutes, { prefix: '/api' });

Database Access (Mongoose)

// api/src/schemas/user.ts

import mongoose from 'mongoose';

const userSchema = new mongoose.Schema({

  email: { type: String, required: true, unique: true },

  completedChallenges: [

    {

      id: String,

      completedDate: Number,

      solution: String

    }

  ]

});

export const User = mongoose.model('User', userSchema);

Client (Gatsby/React) Development

Adding a New Page

// client/src/pages/my-new-page.tsx

import React from 'react';

import { Helmet } from 'react-helmet';

import { useTranslation } from 'react-i18next';

const MyNewPage = (): JSX.Element => {

  const { t } = useTranslation();

  return (

    <>

      <Helmet>

        <title>{t('page-title.my-new-page')} | freeCodeCamp.org</title>

      </Helmet>

      <main>

        <h1>{t('headings.my-new-page')}</h1>

      </main>

    </>

  );

};

export default MyNewPage;

Using the Redux Store

// client/src/redux/selectors.ts pattern

import { createSelector } from 'reselect';

import { RootState } from './types';

export const userSelector = (state: RootState) => state.app.user;

export const completedChallengesSelector = createSelector(

  userSelector,

  user => user?.completedChallenges ?? []

);
// In a component

import { useAppSelector } from '../redux/hooks';

import { completedChallengesSelector } from '../redux/selectors';

const MyComponent = () => {

  const completedChallenges = useAppSelector(completedChallengesSelector);

  return <div>{completedChallenges.length} challenges completed</div>;

};

i18n Translations

// Add keys to client/i18n/locales/english/translations.json

{

  "my-component": {

    "title": "My Title",

    "description": "My description with {{variable}}"

  }

}

// Use in component

const { t } = useTranslation();

t('my-component.title');

t('my-component.description', { variable: 'value' });

Testing

Unit Tests (Jest)

# Run all unit tests

pnpm test

# Run tests for a specific package

pnpm --filter api test

pnpm --filter client test

# Watch mode

pnpm --filter client test -- --watch

Curriculum Tests

# Validate all challenges

pnpm run test:curriculum

# Validate specific superblock

pnpm run test:curriculum -- --superblock javascript-algorithms-and-data-structures

# Lint challenge markdown

pnpm run lint:curriculum

E2E Tests (Playwright)

# Run all e2e tests

pnpm run test:e2e

# Run specific test file

pnpm run test:e2e -- e2e/learn.spec.ts

# Run with UI

pnpm run test:e2e -- --ui

Writing E2E Tests

// e2e/my-feature.spec.ts

import { test, expect } from '@playwright/test';

test('user can complete a challenge', async ({ page }) => {

  await page.goto('/learn/javascript-algorithms-and-data-structures/basic-javascript/comment-your-javascript-code');

  // Fill in the code editor

  await page.locator('.monaco-editor').click();

  await page.keyboard.type('// inline comment\n/* block comment */');

  // Run tests

  await page.getByRole('button', { name: /run the tests/i }).click();

  // Check results

  await expect(page.getByText('Tests Passed')).toBeVisible();

});

Key pnpm Scripts Reference

# Development

pnpm run develop              # Start all services

pnpm run develop:api          # API only

pnpm run develop:client       # Client only

# Building

pnpm run build                # Build everything

pnpm run build:api            # Build API

pnpm run build:client         # Build client (Gatsby)

# Testing

pnpm test                     # Unit tests

pnpm run test:curriculum      # Validate curriculum

pnpm run test:e2e             # Playwright e2e

# Linting

pnpm run lint                 # ESLint all packages

pnpm run lint:curriculum      # Curriculum markdown lint

# Database

pnpm run seed                 # Seed DB with curriculum data

pnpm run seed:certified-user  # Seed a test certified user

# Utilities

pnpm run create-challenge     # Interactive challenge creator

pnpm run clean                # Clean build artifacts

Superblock &#x26; Block Naming Conventions

Superblocks map to certifications. Directory names use kebab-case:

responsive-web-design/

javascript-algorithms-and-data-structures/

front-end-development-libraries/

data-visualization/

relational-database/

back-end-development-and-apis/

quality-assurance/

scientific-computing-with-python/

data-analysis-with-python/

machine-learning-with-python/

coding-interview-prep/

the-odin-project/

project-euler/

Block directories within a superblock:

responsive-web-design/

├── basic-html-and-html5/

├── basic-css/

├── applied-visual-design/

├── css-flexbox/

└── css-grid/

Common Patterns &#x26; Gotchas

Challenge ID Generation

Every challenge needs a unique 24-character hex ID:

// tools/challenge-helper-scripts/helpers/id-gen.ts

import { ObjectId } from 'bson';

export const generateId = (): string => new ObjectId().toHexString();

Adding Forum Links

Every challenge needs a forumTopicId linking to forum.freecodecamp.org:

forumTopicId: 301090  # Must be a real forum post ID

Curriculum Meta Files

Each block needs a _meta.json:

{

  "name": "Basic JavaScript",

  "dashedName": "basic-javascript",

  "order": 0,

  "time": "5 hours",

  "template": "",

  "required": [],

  "isUpcomingChange": false,

  "isBeta": false,

  "isLocked": false,

  "isPrivate": false

}

Testing with Authentication

// In e2e tests, use the test user fixture

import { authedUser } from './fixtures/authed-user';

test.use({ storageState: 'playwright/.auth/user.json' });

test('authenticated action', async ({ page }) => {

  // page is already logged in

  await page.goto('/settings');

  await expect(page.getByText('Account Settings')).toBeVisible();

});

Troubleshooting

MongoDB Connection Issues

# Check if MongoDB is running

mongosh --eval "db.adminCommand('ping')"

# Start MongoDB (macOS with Homebrew)

brew services start mongodb-community

# Use in-memory MongoDB for tests

MONGOHQ_URL=mongodb://127.0.0.1:27017/freecodecamp-test pnpm test

Port Conflicts

# API runs on 3000, Client on 8000

lsof -i :3000

kill -9 <PID>

Curriculum Validation Failures

# See detailed error output

pnpm run test:curriculum -- --verbose

# Common issues:

# - Missing forumTopicId

# - Duplicate challenge IDs

# - Invalid challengeType

# - Malformed YAML frontmatter

Node/pnpm Version Mismatch

# Use the project's required versions

node --version   # Should match .nvmrc

pnpm --version   # Should match packageManager in package.json

nvm use          # Switches to correct Node version

Client Build Errors

# Clear Gatsby cache

pnpm --filter client run clean

pnpm run develop:client

Contributing Workflow

# 1. Create a feature branch

git checkout -b fix/challenge-typo-in-basic-js

# 2. Make changes and test

pnpm run test:curriculum

pnpm test

# 3. Lint

pnpm run lint

# 4. Commit using conventional commits

git commit -m "fix(curriculum): correct typo in basic-javascript challenge"

# 5. Push and open PR against main

git push origin fix/challenge-typo-in-basic-js

Commit message prefixes: fix:, feat:, chore:, docs:, refactor:, test:

Resources

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card