A Chrome extension that supercharges Ellucian Self-Service Banner (SSB) — the registration portal used by hundreds of universities. BetterSSB injects directly into your school's registration pages to add features that Banner should have shipped out of the box.
| Feature | Description |
|---|---|
| Rate My Professor Ratings | Professor ratings and reviews appear inline on search results and class listings — no more tab-switching. |
| Automatic Schedule Builder | Generate optimized schedules from your preferred courses, accounting for time conflicts, gaps, and preferences. |
| Auto-Registration | Queue up your desired sections and auto-register the moment enrollment opens. |
| Semester Planner | Plan future semesters visually with a drag-and-drop calendar and course cart. |
| Enhanced UI | A modern, accessible interface layered on top of Banner's legacy pages. |
| AI Chatbot Advisor | Ask questions about courses, prerequisites, and degree progress in natural language. |
| Transcript & Degree Audit | Upload your transcript and degree requirements to see exactly which courses you're eligible for — prerequisites that aren't met are clearly disabled. |
betterssb/
├── extension/ # Chrome extension (WXT + React + TypeScript)
│ ├── entrypoints/
│ │ ├── background.ts # Service worker: API relay, auth, alarms
│ │ ├── content/ # Injected into Banner pages
│ │ │ ├── index.tsx # Content script entry
│ │ │ └── modules/ # DOM / page-context features
│ │ └── popup/ # Extension popup (main UI)
│ ├── components/ # Shared React components
│ ├── lib/ # API client, storage, messaging helpers
│ ├── hooks/ # Custom React hooks
│ └── types/ # Shared TypeScript types
│
├── backend/ # API server (FastAPI + Python 3.14)
│ └── app/
│ ├── api/ # App router & shared route dependencies
│ ├── modules/ # Per-feature routers, services, schemas, external clients
│ ├── core/ # Config, database, security
│ └── shared/ # Cross-cutting schemas
- Popup — Primary UI: Google sign-in, school selection, schedule builder, semester planner, AI chat, and transcript upload. Talks to the background worker and (for Banner data) to the content script via message passing.
- Content Script — Runs only on Banner URLs (
*://*.edu/StudentRegistrationSsb/*,*://*.edu/ssb/*). Handles anything that must execute in the page: calling Ellucian’s in-page APIs for the popup, injecting Rate My Professor badges on listings, and filling/submitting registration forms for auto-registration. - Background Service Worker — Relays non-SSB work to the backend (schedules, chat, transcript parsing, RMP search), forwards
SSB_*messages to an open Banner tab, registration alarms, andchrome.storage. - Backend API — Rate My Professor lookups, schedule optimization, transcript parsing, chatbot inference, and prerequisite validation.
Where each feature lives
| Feature | Popup | Content |
|---|---|---|
| Sign-in, school | Yes | |
| Schedule builder & semester planner | Yes | (SSB calls run in page) |
| AI chat & transcript tools | Yes | |
| Rate My Professor on course rows | Yes | |
| Auto-registration (CRN fields / submit) | Yes |
| Layer | Technology |
|---|---|
| Extension Framework | WXT v0.20+ |
| UI | React 19, TypeScript 5.9 |
| State | chrome.storage via custom useStorage hook |
| Backend | FastAPI, Python 3.14 |
| Database | PostgreSQL 18 (Docker), SQLAlchemy 2 |
| Cache | Redis 8 (Docker), short-lived data only |
| Auth | Google OAuth 2.0 / OpenID Connect (Authlib), server-side sessions |
| AI / LLM | OpenAI API |
| RMP Data | Rate My Professor GraphQL API |
- Node.js ≥ 22 and pnpm ≥ 9
- Python ≥ 3.14 and uv (Python package manager)
- Docker and Docker Compose (for PostgreSQL and Redis)
- A Chromium-based browser
cd backend
docker compose up -d # starts PostgreSQL on 5432 and Redis on 6379Redis holds short-lived data only (caches, rate limits, OAuth state). It runs with persistence off, so its data is gone after a restart — set a TTL on every key and keep anything durable in Postgres.
cd extension
pnpm install
pnpm dev # dev build with hot reload → extension/.output/chrome-mv3-devcd backend
cp .env.example .env # then fill in your secrets
uv sync
uv run alembic upgrade head # create/update the database schema
uv run uvicorn app.main:app --reload # dev server at http://localhost:8000In production, run migrations, then start Uvicorn without --reload:
uv run alembic upgrade head
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4Behind a load balancer or reverse proxy, set FORWARDED_ALLOW_IPS to the proxy's address so client IPs are read from X-Forwarded-For. WEB_CONCURRENCY can replace --workers.
The app never creates tables itself. After changing a model, generate a migration with uv run alembic revision --autogenerate -m "...", review it, and commit it. Run alembic upgrade head as a deploy step before starting the new version.
- Run
pnpm dev(orpnpm buildfor a production bundle) inextension/. - Open
chrome://extensions, enable Developer mode. - Click Load unpacked and select
extension/.output/chrome-mv3-dev(orextension/.output/chrome-mv3forpnpm build).
- Hot reload —
pnpm devinextension/watches for changes and reloads the extension automatically. - API docs — Visit
http://localhost:8000/docsfor the interactive Swagger UI. - Type safety — Shared types in
extension/types/keep content scripts, popup, and API calls in sync.
Copy backend/.env.example to backend/.env and fill in:
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/postgres
REDIS_URL=redis://localhost:6379/0
GOOGLE_CLIENT_ID=... # Google Cloud Console → OAuth client ID (Web application)
GOOGLE_CLIENT_SECRET=... # Matching secret
GOOGLE_REDIRECT_URI=http://localhost:8000/api/v1/auth/google/callback
FRONTEND_URL=http://localhost:5173
SESSION_SECRET=... # Required, 32+ chars: openssl rand -hex 32
OPENAI_API_KEY=sk-... # Required for chatbot
RMP_AUTH_TOKEN=... # Rate My Professor auth (optional, has fallback)The API won't start without GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and a valid SESSION_SECRET.
Sign-in is Google only, using the OAuth 2.0 authorization code flow with OpenID Connect, via Authlib.
GET /api/v1/auth/google/loginredirects to Google withstate,nonce, and a PKCE challenge. Those values are kept in Redis for 10 minutes. A short-lived signed cookie ties them to the browser that started the flow.GET /api/v1/auth/google/callbackchecksstate, exchanges the code, and validates the ID token locally against Google's keys (signature,iss,aud,exp,nonce). It then upserts the user by Google'ssub(never by email), opens a session, and redirects toFRONTEND_URL. If sign-in fails, it appends?auth_error=access_denied|failed|account_disabled.- Sessions are server-side, in the Postgres
sessionstable. The__Host-Http-sidcookie (Secure,HttpOnly,SameSite=Lax, host-only) holds a random 256-bit token, and only its SHA-256 hash is stored. Sessions expire after 7 days of inactivity or 30 days total. Each sign-in issues a new ID. POST /api/v1/auth/logoutdeletes the session row and clears the cookie.GET /api/v1/auth/mereturns the current user, or401.
Only identity scopes (openid email profile) are requested. Google's access token is discarded after sign-in, and no refresh token is requested or stored.
Cookies are always Secure. In development, use Chrome on http://localhost, which Chrome treats as secure.
The API has no CORS. The web app calls it same-origin at /api (Vite proxies it in dev; in production serve both from one domain with /api routed to the backend). The extension gets the API host in its host_permissions, which lets it call the API without CORS.
The extension reads its config at build time. Copy extension/.env.example to extension/.env (or .env.production for release builds):
WXT_API_URL=http://localhost:8000/api/v1 # Backend API base URL
WXT_GOOGLE_CLIENT_ID=... # Same OAuth client ID as the backend- Fork the repo and create a feature branch.
- Follow the existing code style — Prettier + ESLint for TS, Ruff for Python.
- Write tests for new services.
- Open a PR with a clear description.
MIT