This guide is for developers who want to contribute to WeBWorK or run it locally for development. For general information and end-user documentation, see README.md and the WeBWorK wiki.
| Component | Technology |
|---|---|
| Backend | Perl, Mojolicious web framework |
| Templates | Mojolicious Embedded Perl (.html.ep files) |
| Frontend | JavaScript (ES6+), Bootstrap |
| CSS | SCSS, PostCSS, Autoprefixer |
| Database | MariaDB |
| Job Queue | Minion (SQLite-backed) |
| Math Rendering | MathJax |
| Problem Generation | PG (separate repository) |
| Deployment | Docker / Hypnotoad / systemd |
- Docker and Docker Compose
- Perl (see
DockerfileStage1for the tested version) - MariaDB (or MySQL equivalent)
- Node.js
- The pg repository cloned alongside webwork2
- System packages for Perl modules (see
DockerfileStage1for the full list)
-
Copy config files:
cp docker-config/docker-compose.dist.yml docker-compose.yml cp docker-config/env.dist .env
-
Create the courses directory:
mkdir -p ../ww-docker-data/courses
-
Build and start (two-stage build, recommended):
docker build --tag webwork-base:forWW220 -f DockerfileStage1 . docker compose build docker compose up -dFor a single-stage build, edit
docker-compose.ymland changedockerfile: DockerfileStage2todockerfile: Dockerfile, then rundocker compose build && docker compose up -d. -
Mount local source for live development (optional):
Uncomment this line in
docker-compose.ymlunder theappservice volumes to mount your local checkout into the container:- ".:/opt/webwork/webwork2"When mounting locally, you must build the frontend assets on your host — the mount replaces the container's pre-built copies:
cd htdocs && npm install && npm run generate-assets
-
Access WeBWorK at http://localhost:8080/webwork2
The Docker entrypoint automatically creates an
admincourse with a default login:- URL: http://localhost:8080/webwork2/admin
- Username:
admin - Password:
admin
-
Disable two-factor authentication (recommended for local development):
Two-factor auth is enabled by default for all courses. To disable it, add the following to
conf/localOverrides.conf(or mount a custom copy in Docker):$twoFA{enabled} = 0;
docker compose logs -f app # Follow application logs
docker compose down # Stop and remove containers
docker compose up -d --build # Rebuild and restart-
Install Perl dependencies. See
DockerfileStage1for the full list of system packages and Perl modules required. -
Set up MariaDB. Create a
webworkdatabase and a user with read/write access. -
Clone the PG repository alongside webwork2:
git clone https://github.com/openwebwork/pg.git ../pg
-
Copy and edit configuration files:
cp conf/site.conf.dist conf/site.conf cp conf/localOverrides.conf.dist conf/localOverrides.conf
In
conf/site.conf, set:$server_root_urltohttp://localhost:3000$pg_dirto the path of yourpgcheckout$database_passwordto your MariaDB password
-
Start the development server (with hot reload):
./bin/dev_scripts/webwork2-morbo
If permissions require it, run as the server user:
sudo -u www-data ./bin/dev_scripts/webwork2-morbo
-
Start the job queue worker (in a separate terminal):
./bin/webwork2 minion worker
Note: the Minion worker does not hot reload. Restart it manually after changing task modules.
-
Create the admin course:
bin/addcourse admin --db-layout=sql_single \ --users=courses.dist/adminClasslist.lst \ --professors=admin
This creates the
admincourse with a default useradmin(password:admin). -
Disable two-factor authentication (recommended for local development):
Add the following to
conf/localOverrides.conf:$twoFA{enabled} = 0;
-
Access WeBWorK at http://localhost:3000/webwork2
webwork2/
├── bin/ # CLI scripts and executables
│ └── dev_scripts/ # Development-only scripts (morbo, etc.)
├── lib/ # Core Perl source code
│ ├── Mojolicious/ # Mojolicious app and plugins
│ └── WeBWorK/ # WeBWorK business logic
│ ├── ContentGenerator/ # Page controllers (one per page type)
│ ├── Authen/ # Authentication modules
│ ├── DB/ # Database layer (Schema, Record, Utils)
│ └── ...
├── templates/ # Mojolicious .html.ep templates
├── htdocs/ # Frontend assets (JS, CSS, images, themes)
│ ├── js/ # JavaScript modules organized by feature
│ ├── css/ # Compiled CSS
│ ├── themes/ # UI themes (math4, math4-red, etc.)
│ └── package.json # Frontend dependencies and build scripts
├── conf/ # Configuration files (.dist templates)
├── assets/ # Static assets (LaTeX themes, stop words)
├── courses.dist/ # Sample course directory structure
├── docker-config/ # Docker configuration and entrypoint
├── doc/ # License files
├── logs/ # Application logs
└── tmp/ # Temporary files
The Mojolicious app is defined in lib/Mojolicious/WeBWorK.pm and started via bin/webwork2:
./bin/webwork2 daemon # Start in development mode
./bin/webwork2 prefork # Start in production mode (hypnotoad)Each page type has a corresponding Perl module in lib/WeBWorK/ContentGenerator/. These modules handle routing, authorization, and rendering for their respective pages. Examples: Grades.pm, ProblemSets.pm, CourseAdmin.pm, Instructor/UserList.pm.
The DB layer has three tiers:
lib/WeBWorK/DB.pm— Top-level API for all database operationslib/WeBWorK/DB/Schema/— Schema definitions and query builderslib/WeBWorK/DB/Record/— Data record objects (user, set, problem, etc.)
The Problem Generation system lives in the separate pg repository. It is loaded at runtime from the path configured in $pg_dir.
Frontend assets live in htdocs/. To work on JavaScript or CSS:
cd htdocs
npm install
npm run generate-assetsSee htdocs/package.json for the full list of frontend dependencies.
Themes are located in htdocs/themes/.
WeBWorK uses a .dist file convention: files ending in .dist are templates that should be copied (without the .dist suffix) and customized. Never modify .dist files directly — your changes will be lost on upgrade.
Config load order:
conf/site.conf— Server-specific settings (URL, DB credentials, PG path)conf/defaults.config— Default values for all options (do not modify)conf/localOverrides.conf— Your customizations, overrides values fromdefaults.config
Optional authentication configs (LTI, LDAP, CAS, SAML2, Shibboleth) can be included from localOverrides.conf.
See conf/README.md for full configuration and deployment documentation.
Formatting is enforced by CI on every pull request.
Configured via .perltidyrc:
- Line width: 120 characters
- Indentation: tabs (4-space equivalent)
- Cuddled else blocks
Format Perl files with:
perltidy -pro=.perltidyrc <file>Configured via .prettierrc:
- Line width: 120 characters
- Single quotes, no trailing commas
- Indentation: tabs
cd htdocs
npm run prettier-check # Check formatting
npm run prettier-format # Auto-fix formattingThe .editorconfig file provides consistent settings across editors (UTF-8, LF line endings, tab indentation).
| Script | Description |
|---|---|
bin/webwork2 |
Main application entry point (Mojolicious commands) |
bin/dev_scripts/webwork2-morbo |
Development server with hot reload |
bin/wwsh |
WeBWorK interactive shell |
bin/addcourse |
Create a new course |
bin/delcourse |
Delete a course |
bin/addadmin |
Add an admin user |
bin/OPL-update |
Update the Open Problem Library |
bin/check_modules.pl |
Verify Perl module dependencies |
bin/importClassList.pl |
Import a class roster |
- Fork the repository and create a feature branch from
develop. - Follow the code style guidelines above — CI will check formatting automatically.
- Open a pull request against
develop. Themainbranch is reserved for hotfix pull requests only. - For discussion or questions, use GitHub Discussions.
For more developer resources, see the WeBWorK developer wiki.
When you mount .:/opt/webwork/webwork2 in Docker, your local files replace the container's pre-built assets. Build them on your host:
cd htdocs && npm install && npm run generate-assetsVerify that htdocs/static-assets.json was created — this is the asset manifest the app uses to resolve hashed filenames. If the file is missing, the app cannot find the compiled CSS/JS and pages will appear unstyled.
Node.js version note: On Node 22+, a fix was applied to htdocs/generate-assets.js to ensure the chokidar ready event fires correctly and static-assets.json is written.
The OPL volume has a stale state — the SQL dump exists but JSON metadata files are missing. Remove the volume and let Docker recreate it:
docker compose down
docker volume rm webwork2_oplVolume
docker compose up -dThe first startup after this will be slower as it re-clones the Open Problem Library.
Add $twoFA{enabled} = 0; to conf/localOverrides.conf and restart the app. See step 6 in the Docker setup above.