Lean CTAs
Plugin WordPress lean para CTAs dinámicos según categoría. Convierte tráfico en leads con zero friction.
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/ctala/lean-ctas/archive/refs/heads/main.zipLightweight WordPress plugin that dynamically injects Call-to-Action blocks into your post content based on post type, taxonomy, or category. Zero dependencies, works with any theme.
Features
- Smart matching — Most specific CTA wins (type+term → term → type → global)
- Per-category colors — Different accent color per CTA
- Dark mode — Auto-detects dark themes, no configuration needed
- Position control — Inline (after paragraph N), end of post, or both
- Any post type — Posts, pages, custom post types
- Any taxonomy — Categories, tags, custom taxonomies
- Shortcode —
[lean_cta]for manual placement - Zero dependencies — Pure PHP, no build tools, no external assets
- Opt-in form mode — Email capture form integrated with Listmonk (double opt-in, no API credentials needed)
Installation
Note: This plugin is not yet available on wordpress.org. Install manually using one of the methods below.
Option 1 — Direct Download (recommended)
- Download the latest zip: lean-ctas.zip
- In your WordPress admin, go to Plugins → Add New → Upload Plugin
- Choose the downloaded
lean-ctas.zipand click Install Now - Activate the plugin
Option 2 — From GitHub
- Go to Releases and download the latest
.zip - Upload via Plugins → Add New → Upload Plugin in wp-admin
Option 3 — Manual (FTP/SSH)
- Download and unzip
lean-ctas.zip - Upload the
lean-ctas/folder to/wp-content/plugins/ - Activate in Plugins → Installed Plugins
Quick Start
- Go to Settings → Lean CTAs
- Check Enable CTAs and select which post types should show CTAs
- Set your default accent color
- Click + Add CTA — fill in title, text, button label, and URL
- Choose position: inline, end of post, or both
- Save — done
How Matching Works
| Priority | Condition | Example |
|---|---|---|
| 1 (highest) | Post type + specific term | Posts in "Technology" category |
| 2 | Specific term (any type) | Any content tagged "startup" |
| 3 | Post type (any term) | All posts |
| 4 (lowest) | Global fallback | Everything else |
One CTA per post — the best match wins.
Color System
Set a default accent color in settings. Each CTA can optionally override it.
Default color: #2563EB (blue)
├── Technology CTA → #2196F3 (override)
├── Startups CTA → #FF6B35 (override)
├── Business CTA → #4CAF50 (override)
└── Fallback CTA → uses default #2563EB
The accent color controls the left border and button background.
Dark Mode
Works automatically. No configuration, no CSS overrides, no code changes.
The plugin detects dark themes through:
prefers-color-scheme: dark(OS-level).darkclass on<html>or<body>data-theme="dark"ordata-color-scheme="dark"attributes- Background luminosity — measures
<body>background color and adapts if it's dark
| Property | Light Theme | Dark Theme |
|---|---|---|
| Background | rgba(0,0,0,.04) |
rgba(255,255,255,.06) |
| Title | #111 |
rgba(255,255,255,.92) |
| Text | #444 |
rgba(255,255,255,.7) |
| Button | Accent color, white text | Same |
Custom CSS
Override with CSS custom properties:
.lean-cta-block {
--lean-accent: #FF6B35;
--lean-bg: rgba(0,0,0,.04);
--lean-title: #111;
--lean-text: #444;
}
Shortcode
[lean_cta] <!-- Best match for current post -->
[lean_cta post_type="glosario"] <!-- Force post type -->
[lean_cta category="131"] <!-- Force category -->
Opt-in Form Mode (v2.4.0+)
A CTA can render an email capture form connected to Listmonk instead of a button link.
How it works
- Set Button type to "Opt-in form (Listmonk)" in the CTA row.
- Enter the Listmonk List UUID (Settings → Lean CTAs → CTA row → Listmonk List UUID). Find it in Listmonk → Lists → Edit. Multiple lists (v2.4.1+): comma-separate UUIDs (
uuid1,uuid2) — the subscriber is added to all of them. - Set Listmonk URL in the global settings (e.g.
https://listmonk.nyx.cristiantala.com). No user/token needed. - Optionally customize the Success message.
Capture webhook routing (v2.5.0+)
Optionally set Capture webhook URL (n8n) in global settings. When set, submissions POST there as JSON (email, list_uuids, page_url, GA client_id/session_id parsed server-side from the visitor's cookies) so an n8n workflow can handle Listmonk + a server-side GA4 generate_lead event (Measurement Protocol, with real session attribution) + any extra plumbing (tagging, CRM). Automatic fallback: if the webhook fails (non-2xx / 4s timeout) the plugin falls back to the direct Listmonk path — a capture is never lost. The webhook URL is server-side only; the browser never sees it.
Cross-sell checkbox — secondary list (v2.6.0+)
An opt-in CTA can offer a second list via checkbox: set Secondary list UUID + Secondary checkbox label in the CTA row. The checkbox is always unchecked by default — pre-ticked consent is invalid under GDPR (Planet49) and silently adding users to higher-frequency lists hurts deliverability. Works on both paths (no-JS and JS); the secondary UUID joins the allowlist. Take-rate is measurable via the list_uuids param in the capture webhook / GA4 event.
Placement anywhere (v2.4.1+)
A CTA with Position: Manual is never auto-injected; render it wherever you want with [lean_cta optin="1"] (e.g. inside a sitewide popup in wp_footer). On successful JS subscription the plugin dispatches a leanctas:subscribed DOM event so your popup can auto-close.
Double opt-in
The plugin sends to Listmonk's public endpoint (/api/public/subscription). If the target list is configured for double opt-in in Listmonk, Listmonk sends the confirmation email automatically. This is intentional — do not change to /api/subscribers.
No-JS fallback
The form works with <form method="post"> and a full page reload if JavaScript is disabled or unavailable. With JS, a ~1.3 KB vanilla fetch script intercepts the submit and swaps the form for the success message without reload.
Caveat if Cloudflare Turnstile (below) is configured: Turnstile itself needs JavaScript to solve the challenge, so a visitor with JS fully disabled cannot subscribe while it's active — that's inherent to any browser captcha, not specific to Turnstile. Leave the Turnstile keys blank to keep the form fully no-JS friendly.
Anti-spam: Cloudflare Turnstile challenge (v2.7.0+)
Honeypot + UUID allowlist + IP rate limit stop a lazy bot but not a distributed one — many source IPs, few requests each, honeypot field never touched. That's what hit ecosistemastartup.com on 12–14 ago 2026 (~331 third-party addresses injected, each also firing a GA4 generate_lead event through the capture webhook).
- Cloudflare dashboard → Turnstile → Add site → get a Site Key and Secret Key (free, no request cap).
- Settings → Lean CTAs → paste both into Turnstile Site Key / Turnstile Secret Key.
- That's it — no code change. Both keys are required to activate; leave either blank and the plugin behaves exactly as it did in 2.6.0 (no captcha shown, no
challenges.cloudflare.comrequest).
Enforced identically on both submission paths (REST /lean-ctas/v1/subscribe and the no-JS admin_post fallback) — a bot switching from one path to the other doesn't help it. Fails closed: once configured, a missing or invalid token is rejected, not silently accepted. See includes/captcha.php for why Turnstile was chosen over reCAPTCHA.
Smoke test (staging)
- Install and activate plugin on a staging WP instance.
- Settings → Lean CTAs: set Listmonk URL to your staging Listmonk instance.
- Add a CTA: Button type = Opt-in form, List UUID =
<your list uuid>, any title/text. - View a post — the form renders server-side (check "View Source", no JS required).
- Disable JS in DevTools → submit form → page redirects back with
?lc_done=ok→ success message visible. - Re-enable JS → submit form → success message swaps in without reload.
- Check Listmonk → Subscribers → the email appears (status "unconfirmed" if double opt-in is on, pending confirmation email).
- Confirm the confirmation email → subscriber becomes active.
- Turnstile — legit path: set both Turnstile keys (use Cloudflare's always-passes test keys on staging: site key
1x00000000000000000000AA, secret key1x0000000000000000000000000000000AA). Submit the form → succeeds exactly as before (widget is invisible with these test keys). - Turnstile — rejection, REST path:
curl -s -X POST '<site>/wp-json/lean-ctas/v1/subscribe' -H 'Content-Type: application/json' -d '{"email":"test@example.com","list_uuid":"<configured uuid>"}'(nocf_turnstile_response) → expect HTTP 400,{"code":"captcha_failed",...}. Confirm nothing was created in Listmonk. - Turnstile — rejection, no-JS path:
curl -s -i -X POST '<site>/wp-admin/admin-post.php' -d 'action=lc_subscribe&lc_email=test@example.com&lc_list=<configured uuid>'(no captcha field) → expect a 302 redirect to?lc_done=err. Confirm nothing was created in Listmonk. - Set only one of the two keys → confirm the form renders and submits with no captcha shown (fail-open on "not configured").
For Daily Shot (ecosistemastartup.com):
- Listmonk URL:
https://listmonk.nyx.cristiantala.com - List UUID:
2c6f425d-96d9-47b6-bc7b-761dd04e185f
Requirements
- WordPress 6.4+
- PHP 8.1+
License
GPL-2.0-or-later
Made with love from Chile by cristiantala.com