AI-powered WordPress widget that generates a fresh technology joke/riddle on demand.
No question bank is ever stored. Every question is generated live by the AI provider you configure, held briefly in a transient, and discarded.
- Visitor opens the widget.
- WordPress requests one fresh question from the configured AI provider.
- The visitor enters an answer.
- WordPress receives the answer through a POST request.
- The server reveals the AI-generated punchline.
- The visitor can request another question.
- WordPress 6.4+
- PHP 7.4+
- One reachable AI provider:
- Ollama — local, self-hosted
- Hugging Face — Inference Providers
- OpenAI-compatible — any OpenAI-shaped chat-completions endpoint
Copy the plugin into your WordPress install and activate it:
cp -r ai-fun-questions /path/to/wp-content/plugins/
wp plugin activate ai-fun-questionsActivation creates the {prefix}_ai_fq_rate_limits table used for rate limiting. If you install by unzipping rather than through WordPress, make sure you activate through WordPress so that table gets created.
Open Settings → AI Fun Questions, pick a provider card, fill in its fields, and save.
Put the shortcode in any page, post, template, or shortcode-enabled area:
[ai_fun_question]
You can place several widgets on one page — each one generates and tracks its own question independently. See Rate limiting before doing so.
| Setting | Option | Default |
|---|---|---|
| Ollama URL | ai_fq_ollama_url |
http://localhost:11434/api/chat |
| Ollama Model | ai_fq_ollama_model |
gemma3 |
ollama serve
ollama pull gemma3The WordPress server must be able to reach the Ollama server. Loopback and private addresses are allowed for this provider by design, since that is how a self-hosted Ollama runs. The allowlist is localhost, 127.0.0.1, and ::1; extend it with filters:
// Allow an extra host.
add_filter(
'ai_fq_allowed_ollama_hosts',
function ( $hosts ) {
$hosts[] = 'ollama.internal';
return $hosts;
}
);
// Or allow any remote host (understand the SSRF trade-off first).
add_filter( 'ai_fq_allow_remote_ollama', '__return_true' );| Setting | Option | Default |
|---|---|---|
| Hugging Face Token | ai_fq_hf_token |
— |
| Hugging Face Model | ai_fq_hf_model |
Qwen/Qwen3-4B-Instruct-2507 |
Requests go to https://router.huggingface.co/v1/chat/completions. Use a model available through Hugging Face Inference Providers.
| Setting | Option | Default |
|---|---|---|
| Endpoint | ai_fq_openai_endpoint |
https://api.openai.com/v1/chat/completions |
| API Key | ai_fq_openai_key |
— |
| Model | ai_fq_openai_model |
gpt-4o-mini |
Note: this provider validates the endpoint with
wp_http_validate_url(), which rejects loopback and private addresses. It therefore cannot currently point at a local LLM server such as LM Studio, llama.cpp, or LocalAI. Use the Ollama provider for local models.
Secrets can live in the database (via the settings screen) or, preferably for production, in wp-config.php:
define( 'AI_FQ_HF_TOKEN', 'your-token' );
define( 'AI_FQ_OPENAI_KEY', 'your-key' );A constant always wins over the stored option, and the settings screen shows a wp-config badge on any field it is supplying.
Important: these lines must go above the
/* That's all, stop editing! */comment inwp-config.php. Anything added afterrequire_once ABSPATH . 'wp-settings.php';is defined too late and silently ignored.
- Stored secrets are never rendered into the page. The field shows dots as a placeholder only to signal that something is saved.
- Submitting the form with a secret field left blank keeps the existing stored value. This is why saving unrelated settings does not wipe your API key.
- To actually remove a stored credential, tick Clear the saved value next to that field and save.
Do not commit credentials to source control.
Public endpoints are unauthenticated by design — the widget has to work for anonymous visitors — so abuse is contained with rate limiting, short-lived widget tokens, client binding, and POST-only answer reveal.
Default allowance is 5 requests per bucket per 60-second window, bucketed per client (IP + user agent). Buckets are prefixed generate| for question generation and answer| for answer submission.
Because every widget on a page fires its own generation request, a page with more than five widgets will see the extras rejected with HTTP 429 on first load. Raise the allowance with the ai_fq_rate_limit filter:
add_filter(
'ai_fq_rate_limit',
function ( $limit, $bucket ) {
return 0 === strpos( $bucket, 'generate|' ) ? 20 : $limit;
},
10,
2
);A filtered value of zero or less is ignored and the default applies. Old rows are cleaned hourly by WP-Cron.
Both routes are public and POST-only. The stored punchline is never exposed through a GET endpoint.
| Method | Route | Purpose |
|---|---|---|
POST |
/wp-json/ai-fun-questions/v1/question |
Generate a question |
POST |
/wp-json/ai-fun-questions/v1/answer |
Submit an answer and reveal the punchline |
Both require an X-AI-FQ-Widget header carrying the widget token. Question tokens are bound to the issuing client and expire after 10 minutes. See docs/rest-api.md.
"The AI service is temporarily unavailable. Please try again."
This is the deliberate generic message shown to visitors; raw provider errors are never returned to the frontend. The real reason is written to the debug log. Enable logging in wp-config.php:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );Then check wp-content/debug.log for lines tagged [AI Fun Questions]. Common causes:
| Log line | Cause |
|---|---|
cURL error 7: Failed to connect |
Provider is not running or the URL/port is wrong |
HTTP status: 401 |
API key or token is missing, wrong, or expired |
HTTP status: 429 |
You have hit the provider's own rate limit |
The AI provider returned an invalid response |
Model did not return usable JSON — try a stronger model |
The AI response is missing the "…" field |
Model returned JSON in the wrong shape |
Widget shows "Please wait before requesting another question."
You hit the plugin's own rate limit (HTTP 429). Wait for the current 60-second window to roll over, or raise the limit with ai_fq_rate_limit.
# PHP syntax check
find . -name '*.php' -exec php -l {} \;
# JavaScript syntax check
node --check assets/js/frontend.js
node --check assets/js/admin.jsSee docs/testing.md for the manual test checklist.
[ai_fun_question]
architecture.mdclaude.mddocs/ai-providers.mddocs/rest-api.mddocs/security.mddocs/frontend.mddocs/testing.mddocs/development.mddocs/production-readiness.md
This project intentionally remains small and modular. It does not include analytics, a question-management UI, user accounts, persistent answer history, or a permanent question bank.