Turn PDFs, notes and pasted text into quizzes, flashcards and practice tests — right in your browser.
Turn PDFs, notes and pasted text into quizzes, flashcards, practice tests and study material. Paste your material or upload a text-based PDF, choose how many questions you want and which types, and QuizFlow builds a quiz you can play, score and export — or a flashcard deck you can review with spaced repetition.
It works offline in Quick mode with no AI key needed. Nothing about your document leaves your device in Quick mode. An optional AI layer is available for higher-quality generation, and it is always clearly labelled.
pdfjs-dist, with page-range selection and per-page character counts so empty or scanned pages are obvious. Scanned/image-only PDFs get an honest “no extractable text” message — there is no OCR yet.POST /api/ai/* routes through the Vercel AI Gateway for quiz, flashcard, summary, explanation, weak-topic and regenerate capabilities. It uses the deployment’s shared daily allowance first, with a bring-your-own-key fallback. When no credentials are available it degrades to an honest “AI unavailable” state — never fake questions.| Home | Quiz player |
|---|---|
![]() |
![]() |
idb package) for local-first persistence.pdfjs-dist (in-browser PDF extraction), zod (input validation), lucide-react (icons), clsx + tailwind-merge (class handling).No database, no accounts, no signup. The app is fully usable with zero configuration.
Tests live next to the code they cover as *.test.ts files; only the source modules are listed below.
src/
├── app/ # Next.js App Router — pages, routes and metadata
│ ├── layout.tsx # Root layout: metadata, theme, consent and analytics wiring
│ ├── page.tsx # Landing page
│ ├── globals.css # Tailwind v4 theme + design tokens
│ ├── loading.tsx # Global loading UI
│ ├── error.tsx # Root error boundary
│ ├── not-found.tsx # Custom 404 page
│ ├── robots.ts # robots.txt generator
│ ├── sitemap.ts # XML sitemap generator
│ ├── opengraph-image.tsx # Open Graph social card (rendered with next/og)
│ ├── twitter-image.tsx # Twitter/X social card
│ ├── api/ai/[capability]/route.ts # Serverless AI endpoint: rate-limit → quota → gateway
│ ├── tool/ # Main quiz workspace (upload → configure → generate → play)
│ ├── flashcards/ # Flashcard deck player
│ ├── dashboard/ # Local stats dashboard
│ ├── history/ # Past quizzes, decks and results
│ ├── settings/ # Data export/import, consent, BYOK key, ad flag
│ ├── docs/ # In-app documentation page
│ ├── guides/ # Guide index + guides/[slug] article pages
│ ├── use-cases/ # Use-case index + use-cases/[slug] pages
│ ├── faq/ # FAQ (FAQPage structured data)
│ ├── changelog/ # Changelog page
│ ├── about/ # About the product
│ ├── creator/ # Creator profile (Person structured data)
│ ├── open-source/ # Open-source page
│ ├── privacy/ # Privacy policy
│ ├── terms/ # Terms of use
│ ├── cookies/ # Cookie policy + consent controls
│ └── contact/ # Contact page
│
├── components/ # UI components
│ ├── layout/ # Header, footer, theme toggle + no-flash theme script,
│ │ # consent banner, and analytics loaders (GTM/GA, Vercel)
│ ├── tool/ # SourceInput (paste/PDF + page range), GenerateConfig,
│ │ # QuestionEditor, ExportMenu, and the reserved AdUnit slot
│ ├── player/ # QuizPlayer (MCQ/TF/short/fill) and QuizResults
│ ├── flashcards/ # FlashcardPlayer (flip + self-grading)
│ ├── analytics/ # TrackOnMount helper
│ └── ui/ # Design-system primitives: Button, Badge, ConfirmDialog,
│ # EmptyState, ProgressRing, icons
│
└── lib/ # Pure logic, framework-free where possible
├── types.ts # Shared domain types
├── generator.ts # Deterministic Quick-mode question generator
├── text.ts # Sentence/keyword text heuristics
├── pdf.ts # In-browser PDF text extraction (pdfjs-dist)
├── scoring.ts # Quiz scoring
├── dedupe.ts # Duplicate-question detection
├── srs.ts # SM-2-style spaced-repetition scheduler
├── stats.ts # Derived dashboard stats from local history
├── storage.ts # IndexedDB persistence behind one interface (idb)
├── prefs.ts # Small localStorage preferences
├── share.ts # Share-by-URL encode/decode (zod-validated)
├── export.ts # JSON / CSV / Markdown / printable-HTML exporters
├── id.ts # crypto.randomUUID id generation (with fallback)
├── cn.ts # className merge (clsx + tailwind-merge)
├── analytics.ts # Typed track() with the consent gate
├── audio.ts # Web Audio click sounds
├── tips.ts # Rotating study tips
├── site.ts # Site + creator constants
├── guides.ts # Guides content data
├── use-cases.ts # Use-cases content data
├── og-image.tsx # Shared OG image renderer
└── ai/ # AI layer: catalog, capabilities, models (env defaults),
# quota (daily), rate-limit (per-IP), errors, request, client
Prerequisites: Node 20+ (Next.js 16 needs Node 20.9+) and pnpm.
pnpm i # install dependencies
pnpm dev # start the dev server at http://localhost:3000
pnpm build # production build
pnpm test # run the unit tests (Vitest)
Other useful scripts: pnpm lint, pnpm typecheck, pnpm test:coverage, and pnpm e2e (Playwright smoke).
Every variable is optional. The app builds and the deterministic Quick mode works with none of them set — when AI credentials are missing, AI mode shows a graceful “AI unavailable” fallback and everything else keeps working. Copy .env.example to .env.local to set any of them.
| Variable | Purpose | Required? |
|---|---|---|
AI_GATEWAY_API_KEY |
Server Vercel AI Gateway key (vck_…) for AI mode. On Vercel, OIDC can supply this automatically. |
No — unset means Quick mode only |
VERCEL_OIDC_TOKEN |
Ambient AI Gateway credential injected by vercel dev and by Vercel at deploy time. |
No — managed by Vercel, do not set by hand |
AI_MODEL |
Fast-tier gateway model string. | No — defaults to anthropic/claude-haiku-4.5 |
AI_MODEL_QUALITY |
Quality-tier gateway model string. | No — defaults to anthropic/claude-sonnet-4-5 |
NEXT_PUBLIC_SITE_URL |
Canonical/OG base URL for SEO and the sitemap. | No — defaults to https://quizflow.mkazi.live |
NEXT_PUBLIC_GTM_ID |
Google Tag Manager container id. | No — unset disables analytics entirely |
NEXT_PUBLIC_GA_ID |
Google Analytics id. | No — unset disables analytics entirely |
NEXT_PUBLIC_ADSENSE_ENABLED |
Master switch for reserved ad slots. | No — defaults to false (no ad scripts load) |
NEXT_PUBLIC_ADSENSE_CLIENT_ID |
AdSense publisher id, only used when ads are enabled. | No |
NEXT_PUBLIC_SPONSOR_URL |
Optional sponsor/”buy me a coffee” link shown in the fallback ad slot. | No |
QuizFlow is local-first. Your material is processed in your browser, and your quizzes, decks, results and settings live in IndexedDB on your device.
See docs/PRIVACY.md and docs/SECURITY.md for the full guarantees and threat model.
Deeper reference docs live in docs/:
Vercel is the recommended host and the path this project uses:
pnpm automatically.*.vercel.app URL (this project’s is mk-quizflow.vercel.app).quizflow.mkazi.live)quizflow.mkazi.live.At the DNS provider (Cloudflare, which manages mkazi.live), add the record Vercel asks for:
A quizflow.mkazi.live 76.76.21.21
If you use Cloudflare’s proxy, set this record to DNS only (grey cloud) so Vercel can validate it.
mkquizflow.com)If QuizFlow graduates to its own domain (candidate: mkquizflow.com):
A record to 76.76.21.21 (and a CNAME for www to cname.vercel-dns.com).quizflow.mkazi.live attached and set it to redirect to the new primary domain in Vercel’s domain settings, so old links keep working.Kazi Musharraf — AI Engineer · Full-Stack Developer · Open-Source Builder.
MIT © 2026 Kazi Musharraf.
Built and maintained by Kazi Musharraf. Open source for everyone.