You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
-**Field validation** — required, minLength, maxLength, min value, max value, email, URL, regex pattern — all configurable per field with custom error messages.
18
18
-**Draft / Publish** — draft edits stay private; the public page and embed always serve the last **published** version, so saving a draft never affects the live form.
19
19
-**Public page** — one toggle generates a hosted page at `/api/strapi-plugin-form-builder-cms/page/:slug` (available once the form is published).
20
20
-**Embed script** — a single `<script>` tag renders the form on any external site with zero dependencies.
21
21
-**Spam & limits** — honeypot field plus optional per-IP hourly rate limiting.
22
+
-**CAPTCHA** — optional Cloudflare Turnstile or Google reCAPTCHA v2 per form, verified server-side (fail-closed). The secret key never reaches the browser.
23
+
-**Hidden tracking fields** — capture UTM parameters, referrers or campaign IDs from the page URL into every submission.
22
24
-**Submission inbox** — search, filter by status (new / read / archived), bulk mark-as-read / archive / delete, per-form column picker, a detail drawer with next/previous navigation, and CSV export.
23
25
-**Form preview** — preview the rendered form inside the admin before publishing.
24
26
@@ -152,6 +154,12 @@ These routes require no authentication and are safe to call from the browser.
@@ -171,6 +179,7 @@ These routes require no authentication and are safe to call from the browser.
171
179
|`radio`| Radio group — configure options in the settings panel |
172
180
|`checkbox`| Single checkbox (agree / consent) |
173
181
|`checkbox-group`| Multiple checkboxes — configure options in the settings panel |
182
+
|`hidden`| Invisible tracking field — prefilled from a URL query parameter (e.g. `utm_source`) or a default value, then stored with the submission |
174
183
|`heading`| Non-input heading label |
175
184
|`paragraph`| Non-input descriptive text |
176
185
|`divider`| Horizontal rule to separate sections |
@@ -204,10 +213,69 @@ Each rule accepts an optional **custom error message**. The same rule type canno
204
213
| Public page | Generates a hosted page URL for the form (served once published) |
205
214
| Honeypot protection | Adds a hidden field to silently discard bot submissions |
206
215
| Rate limiting | Caps submissions per IP each hour (configurable max) |
216
+
| CAPTCHA | None, Cloudflare Turnstile or Google reCAPTCHA v2 — with a **Test secret key** button to validate credentials before publishing |
207
217
| Redirect URL | Where to send the visitor after a successful submit |
208
218
209
219
---
210
220
221
+
## CAPTCHA
222
+
223
+
Protect a form with a human-verification challenge. In the form settings drawer, under **Spam & limits → CAPTCHA**, pick a provider and paste your keys:
- The **site key** is public — it is sent to the browser to render the widget.
231
+
- The **secret key** is private — it is stored server-side and **never** exposed in the public schema. Use the **Test secret key** button to validate it against the provider before publishing.
232
+
- Submissions are verified server-side and **fail closed**: an invalid or missing token is rejected with `400`, even if the client is bypassed.
233
+
- If the widget can't load (e.g. an invalid site key), the visitor sees a clear message instead of a silent failure.
234
+
235
+
> The public page relaxes its Content-Security-Policy just enough to load the chosen provider's script and iframe — only when a CAPTCHA is configured.
236
+
237
+
## Hidden tracking fields
238
+
239
+
Add a **hidden** field to capture campaign data into every submission without showing anything to the visitor:
240
+
241
+
-**Name** — the key stored with the submission (e.g. `utm_source`).
242
+
-**Prefill from URL parameter** — a query-string parameter to read from the page URL. If the form is opened at `?utm_source=google`, that value is captured automatically.
243
+
-**Default value** — used when the URL parameter is absent.
244
+
245
+
The captured value is stored with the submission and included in the CSV export.
246
+
247
+
---
248
+
249
+
## Styling the embedded form
250
+
251
+
The embedded form renders in the host page's DOM (no shadow DOM), so you can restyle it from your own site.
252
+
253
+
**CSS variables** (the recommended, stable API):
254
+
255
+
```css
256
+
#sfb-form-1 { /* the container div, or any ancestor */
257
+
--sfb-accent: #e11d48;
258
+
--sfb-radius: 12px;
259
+
}
260
+
```
261
+
262
+
**Or target the classes directly** — `.sfb-form`, `.sfb-field`, `.sfb-label`, `.sfb-input`, `.sfb-btn`, `.sfb-help`, `.sfb-error`, `.sfb-success`. The plugin's rules use single-class specificity; to reliably win, scope your selector to the form container:
263
+
264
+
```css
265
+
#sfb-form-1.sfb-input { border-width: 2px; }
266
+
```
267
+
268
+
**Per-field class** — give any field a **CSS class** in its settings panel (e.g. `newsletter-email`). It's added to that field's wrapper, so you can style one field from your own stylesheet:
269
+
270
+
```css
271
+
.newsletter-emailinput { border-color: #e11d48; }
272
+
.newsletter-email.sfb-label { font-weight: 700; }
273
+
```
274
+
275
+
The class sits on the field's wrapper (not the `<input>`), so a descendant selector like `.newsletter-email input` naturally out-specifies the plugin's own rules — no `!important` needed.
276
+
277
+
---
278
+
211
279
## Development
212
280
213
281
Clone the repo and link the plugin to a local Strapi project:
@@ -227,6 +295,14 @@ In your local Strapi project:
227
295
npm run develop
228
296
```
229
297
298
+
Run the test suite (Vitest) and type-checks:
299
+
300
+
```bash
301
+
npm test# unit tests
302
+
npm run test:ts:back # type-check the server
303
+
npm run test:ts:front # type-check the admin
304
+
```
305
+
230
306
---
231
307
232
308
## Contributing
@@ -243,7 +319,9 @@ Pull requests are welcome. For major changes please open an issue first to discu
0 commit comments