A native WordPress plugin that lets administrators create dynamic, interactive quizzes to capture leads. Results are gated behind a GDPR-compliant lead capture form, and lead data is stored in a dedicated WordPress custom table so it can be exported and managed from the admin dashboard.
- Features
- Installation
- How It Is Made (Architecture)
- How It Works
- File Structure
- Custom Database Table
- Usage
- REST API Reference
- Development & Testing
- Custom Quiz Builder – Create unlimited questions with multiple answers right on the post edit screen.
- Answer Weights – Each answer carries an integer weight (points) that drives result scoring.
- Optional Images – Add images to answers and result brackets via the WordPress Media Library.
- Result Brackets – Define outcomes with title, description, image, and a
min_score/max_scorerange. - GDPR Lead Gate – Scores are hidden until the visitor submits name, email, and consent.
- Vanilla JS Frontend – Instant next/previous transitions with CSS class toggling (zero jQuery on the frontend).
- REST API Submission – A single POST request handles scoring, lead capture, and result delivery.
- Leads Management – Paginated, sortable, searchable admin table built on
WP_List_Table. - CSV Export – One-click, filtered download of all captured leads.
- In the WordPress admin, go to Plugins → Add New → Upload Plugin.
- Choose
wp-lead-capture-quiz.zipand click Install Now. - Click Activate.
Activation creates the custom wp_quiz_leads database table automatically.
- Upload the
wp-lead-capture-quizfolder towp-content/plugins/. - Activate the plugin from the Plugins screen.
The plugin uses a hybrid frontend/backend architecture to stay fast on the client side while keeping scoring and data capture secure on the server.
- Custom Post Type (
wp_quiz) – Every quiz is a CPT entry. This gives you the native post editor, publishing workflow, and admin-styling for free. - Serialized JSON config in
post_meta– Questions, answers, weights, and result brackets are stored as a single serialized JSON array under the_wp_quiz_configmeta key. This data is always read and written together, so a heavyweight relational schema would be unnecessary overhead. - Custom leads table (
wp_quiz_leads) – Leads are high-volume, frequently queried, and better served by a dedicated table than river-of-posts meta. - REST API endpoint –
POST /wp-json/wp-quiz/v1/submitis the single server-side entry point for scoring and storing leads. - Class-based organization – Each responsibility lives in its own class hooked into WordPress actions/filters.
| Class | Responsibility |
|---|---|
WP_Quiz_Activator |
Creates the wp_quiz_leads table on plugin activation (via dbDelta). |
WP_Quiz_CPT |
Registers the wp_quiz CPT, renders the Quiz Builder meta box, and persists the JSON config. |
WP_Quiz_Leads_Table |
WP_List_Table subclass – pagination, sorting, search, quiz filter, bulk delete. |
WP_Quiz_Leads_Admin |
Registers the Leads admin submenu and handles filtered CSV export. |
WP_Quiz_Shortcode |
Registers [lead_quiz id="N"], enqueues frontend assets, and renders the quiz DOM. |
WP_Quiz_REST_API |
Validates submissions, scores answers, writes leads, matches brackets, and returns result HTML. |
- Initial load (frontend): The shortcode renders all questions and answers into the DOM. No result logic or scoring data ever reaches the browser.
- Interaction (frontend): Vanilla JavaScript toggles CSS classes to move between question steps instantly.
- Gate (frontend): After the last question the visitor reaches a lead capture form (name, email, GDPR consent).
- Submission (backend): On submit, one POST request goes to the REST API with the quiz ID, lead info, consent flag, and the array of selected answer IDs.
- Resolution (backend): The server sanitizes input, computes the total score from answer weights, matches it against the result brackets, persists the lead, and returns HTML for the earned result.
- Go to Quizzes → Add New Quiz, give it a title, and open the Quiz Builder Configuration meta box.
- Click + Add Question and fill in the question text.
- Each question has at least 2 answers. For each answer set:
- Answer Text – the option label.
- Weight (Points) – the integer score awarded when this answer is chosen.
- Image (Optional) – pick a media library image.
- Add any number of Result Brackets. Each bracket defines:
- Title & Description
- Optional Image
- Min Score / Max Score – the score range that maps to this outcome.
- Publish the quiz. The meta box shows the ready-to-use shortcode.
- The shortcode renders a progress bar, a series of question steps, and a final form-gate step.
- Visitors click an answer card to select it, then Next. Back returns to the previous step. Transitions are pure CSS class toggles, so navigation is instant.
- The visitor never sees or interacts with scoring data.
- After the final question the visitor must provide Name, a valid Email, and tick the GDPR consent checkbox.
- The browser POSTs the answer IDs to the REST API and shows a loader.
- The server calculates the score, stores the lead, picks the matching result bracket, and returns rendered result HTML, which the frontend drops into the container.
- Under Quizzes → Leads all captured leads are listed in a paginated table.
- Filter by a specific quiz, search by name/email/result, and sort by the sortable columns.
- The Download CSV button exports the currently filtered result set directly as
.csv.
wp-lead-capture-quiz/
├── wp-lead-capture-quiz.php # Main plugin entry, bootstraps all classes
├── includes/
│ ├── class-wp-quiz-activator.php # Activation: creates wp_quiz_leads table
│ ├── class-wp-quiz-cpt.php # CPT + Quiz Builder meta box + saving
│ ├── class-wp-quiz-leads-table.php # WP_List_Table for leads
│ ├── class-wp-quiz-leads-admin.php # Admin menu page + CSV export
│ ├── class-wp-quiz-shortcode.php # [lead_quiz] shortcode + asset loading
│ └── class-wp-quiz-rest-api.php # POST /wp-json/wp-quiz/v1/submit
├── admin/
│ ├── css/admin-quiz-builder.css # Quiz Builder meta box styling
│ └── js/admin-quiz-builder.js # Add/remove questions, answers, media picker
└── public/
├── css/public-quiz.css # Frontend quiz styles
└── js/public-quiz.js # Vanilla JS: steps, selection, submission
Created on activation with dbDelta():
Table: wp_quiz_leads (prefixed on multi-site installs)
| Column | Type | Notes |
|---|---|---|
id |
BIGINT UNSIGNED, PK, AUTO_INCREMENT | Unique lead ID |
quiz_id |
BIGINT UNSIGNED | Post ID of the wp_quiz CPT (KEY quiz_id) |
name |
VARCHAR(255) | Lead name |
email |
VARCHAR(255) | Lead email |
score |
INT | Computed total score |
result_id |
VARCHAR(255) | Matched result bracket title/ID |
consent_given |
TINYINT(1) | 1 = GDPR consent granted |
created_at |
DATETIME | Submission timestamp |
Quiz configuration (serialized JSON in post_meta, key _wp_quiz_config):
{
"questions": [
{
"id": "id_abc123",
"text": "How often do you blog?",
"answers": [
{ "id": "id_def456", "text": "Daily", "weight": 3, "image": "https://..." },
{ "id": "id_ghi789", "text": "Rarely", "weight": 1, "image": "" }
]
}
],
"results": [
{
"id": "id_jkl012",
"title": "You're a Blogging Pro!",
"description": "...",
"min_score": 10,
"max_score": 20,
"image": "https://..."
}
]
}Embed any published quiz with:
[lead_quiz id="123"]
Replace 123 with the quiz's post ID. The shortcode enqueues the frontend stylesheet and script only when a quiz is actually present on the page.
Public endpoint used by the frontend during the submission step. Requires a valid WordPress REST nonce in the X-WP-Nonce header.
Request body (JSON):
{
"quiz_id": 123,
"name": "Jane Doe",
"email": "jane@example.com",
"consent_given": true,
"answers": [
{ "question_id": "id_abc123", "answer_id": "id_def456" },
{ "question_id": "id_ghi789", "answer_id": "id_jkl012" }
]
}Response 200 OK:
{
"success": true,
"score": 7,
"result": "You're a Blogging Pro!",
"html": "<div class=\"wp-quiz-result\">...</div>"
}Common error responses:
| HTTP | Error code | Meaning |
|---|---|---|
400 |
missing_params |
Empty request body |
400 |
invalid_quiz_id |
Missing/invalid quiz ID |
404 |
invalid_quiz |
Quiz post not found |
400 |
missing_name |
Name is required |
400 |
invalid_email |
Email invalid or missing |
400 |
missing_consent |
GDPR consent not given |
500 |
db_error |
Failed to insert lead row |
PHP syntax can be lint-checked on any machine with PHP installed:
for f in wp-lead-capture-quiz.php includes/*.php; do php -l "$f"; done# From the project root
mkdir -p /tmp/opencode/wp-lead-capture-quiz
cp -r wp-lead-capture-quiz.php includes admin public /tmp/opencode/wp-lead-capture-quiz/
cd /tmp/opencode && zip -r /home/demoniodojo/Projects/quiz-wp/wp-lead-capture-quiz.zip wp-lead-capture-quizThe ZIP contains the wp-lead-capture-quiz/ folder at its top level, so WordPress can unpack it cleanly into wp-content/plugins/.
- The frontend uses vanilla JavaScript – no frontend jQuery dependency.
- All quiz scoring and bracket-matching logic stays server-side, so visitors cannot tamper with results.
result_idin the leads table stores the matched bracket'sidvalue as currently implemented.- This plugin is GPLv2-or-later licensed.